Partial refactoring

This commit is contained in:
Philip Love committed 2021-10-29 02:42:43 -04:00
1 parent 7fee1bf4df
commit ea397ff3b7
53 files changed
+171697 -30

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
----- Changes made outside of /Projects/Portfolio-Management/ -----
Projects/ProjectsSchema.json:
cp Algorithmic-Trading Portfolio-Management - Some stuff definitely unneded here
Projects/Community-Plugins/UI/Function-Libraries/
ActionSwitch.js:
cp case 'Add Missing Plugin Trading Mines' case 'Add Missing Plugin Portfolio Mines'
cp case 'Add Missing Plugin Trading Systems' case 'Add Missing Plugin Portfolio Systems'
cp case 'Add Missing Plugin Trading Engines' case 'Add Missing Plugin Portfolio Engines'
PluginsFunctions.js:
cp addMissingPluginTradingMines addMissingPluginPortfolioMines
cp addMissingPluginTradingSystems addMissingPluginPortfolioSystems
cp addMissingPluginTradingEngines addMissingPluginPortfolioEngines
../Utilities/Plugins.js:
function getPluginFolderNamesByNodeType(nodeType):
add case 'Portfolio Engine'
add case 'Portfolio Mine'
add case 'Portfolio System'
Projects/Data-Mining/UI/Function-Libraries/
MineFunctions.js:
cp scanBotArray(dataMine.tradingBots) scanBotArray(dataMine.portfolioBots)
Skipping:
Projects/Foundations/TS/Function-Libraries/OutputManagementFunctions.js:
tradingProcessDate, VARS...INDEX_MAP->TRADING_PROCESSING_DAILY_FILES, ...
Projects/Foundations/TS/Function-Libraries/
SingleMarketFunctions.js:
cp findNodeInNodeMesh(outputDatasetNode, 'Trading Bot') ...'Portfolio Bot')
Line 117 if...else add portfolioMineNode
Projects/Foundations/TS/Process-Modules/
Dataset.js:
cp startingNode.type === 'Market Trading Products'
ProcessOutput.js:
cp line 27-29, 124-126
StatusReport.js:
Lines 102, 116, cp 172-222 dealing with finding process definitions the report depends on has been cloned from Trading -> Portfolio
Projects/Foundations/UI/Function-Libraries/
ActionSwitch.js:
cp case statements run-all, stop-all, add-missing, references, products, etc.
Line 506 && 512 may not need to have been cloned but I did for now - regarding live and paper trading.
Skipping:
Projects/Foundations/UI/Function-Libraries/ChartingSpaceFunctions.js
Projects/Foundations/UI/Function-Libraries/CryptoEcosystemFunctions.js
Projects/Foundations/UI/Function-Libraries/
DataStorageFunctions.js:
Many things cloned. But on line 156 only did 'addMissingSession(livePortfolioSessionArray) not back|forward|paper
Skipping:
Projects/Foundations/UI/Function-Libraries - Appears 'tradingSystem' var here is just a parameter so no need change.
@@ -53,6 +53,21 @@ function newCommunityPluginsActionSwitch() {
UI.projects.communityPlugins.functionLibraries.pluginsFunctions.addMissingPluginTradingEngines(action.node, action.rootNodes)
}
break
case 'Add Missing Plugin Portfolio Mines':
{
UI.projects.communityPlugins.functionLibraries.pluginsFunctions.addMissingPluginPortfolioMines(action.node, action.rootNodes)
}
break
case 'Add Missing Plugin Portfolio Systems':
{
UI.projects.communityPlugins.functionLibraries.pluginsFunctions.addMissingPluginPortfolioSystems(action.node, action.rootNodes)
}
break
case 'Add Missing Plugin Portfolio Engines':
{
UI.projects.communityPlugins.functionLibraries.pluginsFunctions.addMissingPluginPortfolioEngines(action.node, action.rootNodes)
}
break
case 'Add Missing Plugin Learning Mines':
{
UI.projects.communityPlugins.functionLibraries.pluginsFunctions.addMissingPluginLearningMines(action.node, action.rootNodes)
@@ -6,6 +6,9 @@ function newPluginsFunctionLibraryPluginsFunctions() {
addMissingPluginTradingMines: addMissingPluginTradingMines,
addMissingPluginTradingSystems: addMissingPluginTradingSystems,
addMissingPluginTradingEngines: addMissingPluginTradingEngines,
addMissingPluginPortfolioMines: addMissingPluginPortfolioMines,
addMissingPluginPortfolioSystems: addMissingPluginPortfolioSystems,
addMissingPluginPortfolioEngines: addMissingPluginPortfolioEngines,
addMissingPluginLearningMines: addMissingPluginLearningMines,
addMissingPluginLearningSystems: addMissingPluginLearningSystems,
addMissingPluginLearningEngines: addMissingPluginLearningEngines,
@@ -113,6 +116,54 @@ function newPluginsFunctionLibraryPluginsFunctions() {
}
}
function addMissingPluginPortfolioMines(node, rootNodes) {
let projectName = UI.projects.communityPlugins.utilities.plugins.getProjectName(node)
if (projectName === "" || projectName === undefined) {
if (node.payload.parentNode !== undefined) {
node.payload.parentNode.payload.uiObject.setErrorMessage("Config codeName must have the name of the project.")
return
}
}
UI.projects.communityPlugins.utilities.plugins.getPluginFileNames(projectName, 'Portfolio-Mines', onNamesArrived)
function onNamesArrived(fileNames) {
UI.projects.communityPlugins.utilities.plugins.addMissingPluginFiles(node, fileNames, 'Portfolio-Mines', 'Portfolio Mine', 'Portfolio-Management')
}
}
function addMissingPluginTradingSystems(node, rootNodes) {
let projectName = UI.projects.communityPlugins.utilities.plugins.getProjectName(node)
if (projectName === "" || projectName === undefined) {
if (node.payload.parentNode !== undefined) {
node.payload.parentNode.payload.uiObject.setErrorMessage("Config codeName must have the name of the project.")
return
}
}
UI.projects.communityPlugins.utilities.plugins.getPluginFileNames(projectName, 'Portfolio-Systems', onNamesArrived)
function onNamesArrived(fileNames) {
UI.projects.communityPlugins.utilities.plugins.addMissingPluginFiles(node, fileNames, 'Portfolio-Systems', 'Portfolio System', 'Portfolio-Management')
}
}
function addMissingPluginTradingEngines(node, rootNodes) {
let projectName = UI.projects.communityPlugins.utilities.plugins.getProjectName(node)
if (projectName === "" || projectName === undefined) {
if (node.payload.parentNode !== undefined) {
node.payload.parentNode.payload.uiObject.setErrorMessage("Config codeName must have the name of the project.")
return
}
}
UI.projects.communityPlugins.utilities.plugins.getPluginFileNames(projectName, 'Portfolio-Engines', onNamesArrived)
function onNamesArrived(fileNames) {
UI.projects.communityPlugins.utilities.plugins.addMissingPluginFiles(node, fileNames, 'Portfolio-Engines', 'Portfolio Engine', 'Portfolio-Management')
}
}
function addMissingPluginLearningMines(node, rootNodes) {
let projectName = UI.projects.communityPlugins.utilities.plugins.getProjectName(node)
if (projectName === "" || projectName === undefined) {
@@ -73,6 +73,15 @@ function newPluginsUtilitiesPlugins() {
case 'Trading System': {
return 'Trading-Systems'
}
case 'Portfolio Engine': {
return 'Portfolio-Engines'
}
case 'Portfolio Mine': {
return 'Portfolio-Mines'
}
case 'Portfolio System': {
return 'Portfolio-Systems'
}
case 'Assets': {
return 'Assets'
}
@@ -68,6 +68,7 @@ function newDataMiningFunctionLibraryMineFunctions() {
scanBotArray(dataMine.apiDataFetcherBots)
scanBotArray(dataMine.indicatorBots)
scanBotArray(dataMine.tradingBots)
scanBotArray(dataMine.portfolioBots)
scanBotArray(dataMine.learningBots)
function scanBotArray(botArray) {
@@ -74,7 +74,7 @@
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[WARN] start -> Product Definition " + outputDatasetNode.referenceParent.parentNode.name + " without a Data Building Procedure. Product Definition Name = " + outputDatasetNode.referenceParent.parentNode.name);
}
if (outputDatasetNode.referenceParent.config.codeName === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Dataset witn no codeName defined. Product Dataset = " + JSON.stringify(outputDatasetNode.referenceParent));
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Dataset with no codeName defined. Product Dataset = " + JSON.stringify(outputDatasetNode.referenceParent));
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
@@ -86,7 +86,7 @@
}
if (outputDatasetNode.referenceParent.parentNode.config.codeName === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Product Definition witn no codeName defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode));
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Product Definition with no codeName defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode));
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
@@ -95,6 +95,9 @@
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDatasetNode, 'Trading Bot')
}
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDatasetNode, 'Portfolio Bot')
}
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDatasetNode, 'Learning Bot')
}
@@ -105,7 +108,7 @@
}
if (botNode.config.codeName === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined.");
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Bot with no codeName defined.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
@@ -114,28 +117,37 @@
if (dataMineNode === undefined) {
let tradingMineNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDatasetNode, 'Trading Mine')
if (tradingMineNode === undefined) {
let learningMineNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDatasetNode, 'Learning Mine')
if (learningMineNode === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine, Trading Mine or Learning Mine.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
} else {
if (learningMineNode.config.codeName === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Learning Mine witn no codeName defined.");
let portfolioMineNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDatasetNode, 'Portfolio Mine');
if (portfolioMineNode === undefined) {
let learningMineNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDatasetNode, 'Learning Mine')
if (learningMineNode === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine, Trading Mine or Learning Mine.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
} else {
if (learningMineNode.config.codeName === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Learning Mine with no codeName defined.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
}
} else {
if (portfolioMineNode.config.codeName === undefined) {
TS.project.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Portfolio Mine with no codeName defined.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return;
}
}
} else {
if (tradingMineNode.config.codeName === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Trading Mine witn no codeName defined.");
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Trading Mine with no codeName defined.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
}
} else {
if (dataMineNode.config.codeName === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined.");
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] start -> Data Mine with no codeName defined.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
@@ -48,7 +48,7 @@
/* Basic Valdidations */
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.processDependencies !== undefined) {
/*
First we will get all the data dependencies of the bot, as defined at the Trading Mine.
First we will get all the data dependencies of the bot, as defined at the Trading|Portfolio Mine.
*/
thisObject.curatedDependencyNodeArray = TS.projects.visualScripting.utilities.nodeFunctions.nodeBranchToArray(TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.processDependencies, 'Data Dependency')
if (thisObject.curatedDependencyNodeArray.length === 0) {
@@ -59,7 +59,7 @@
/*
We will filter out declared dependencies that are not present in the workspace.
This will allow the user to have less Data Mines loaded at the workspace
that the ones that a Trading Mine depends on.
that the ones that a Trading|Portfolio Mine depends on.
*/
thisObject.curatedDependencyNodeArray = TS.projects.visualScripting.utilities.nodeFunctions.filterOutNodeWihtoutReferenceParentFromNodeArray(thisObject.curatedDependencyNodeArray)
@@ -127,7 +127,7 @@
let alreadyCalledBack = false
/*
The current curatedDependencyNodeArray that we have includes all the dependencies daclared in the Data Mine | Trading Mine | Learning Mine
The current curatedDependencyNodeArray that we have includes all the dependencies daclared in the Data Mine | Trading Mine | Portfolio Mine | Learning Mine
process dependencies, minus the ones without reference parent, meaning that references Data Mines that are
not present at the workspace. From all the remaining dependencies there will be others that we need to
filter out, and they are the ones which the user does not have a data product anywhere on the network where
@@ -1,6 +1,6 @@
exports.newFoundationsProcessModulesDataset = function (processIndex) {
/*
This module represents a Dataset at a certain localtion on the
This module represents a Dataset at a certain location on the
network, and it is able to load a file from that dataset or
write one into it.
*/
@@ -68,7 +68,7 @@ exports.newFoundationsProcessModulesDataset = function (processIndex) {
}
if (thisObject.node.parentNode.parentNode.parentNode === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] initialize -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode));
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] initialize -> Bot not attached to a Data Mine, Trading Mine, or Portfolio Mine. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode));
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
@@ -188,7 +188,7 @@ exports.newFoundationsProcessModulesDataset = function (processIndex) {
The problem that we need to solve here is the following:
A. We are interested in a particular Exchange and Market.
B. We have the Network we received from the UI whe the user ran the Task.
B. We have the Network we received from the UI where the user ran the Task.
C. We have the Product Definition that is the parent node of the Data Set represented by this object.
D. This Product Definition is referenced by one or more Data Products nodes at some branch of the LAN Network Hierarchy.
E. We need to find the right Data Product node, the one who is itself a descendent of the Market Data Products
@@ -197,7 +197,7 @@ exports.newFoundationsProcessModulesDataset = function (processIndex) {
So, the strategy we will implement is the following:
1. We will scan all the branches of the Network received from the UI.
2. Once we find a Market Data Products, Market Trading Products o Market Learning Products we will check
2. Once we find a Market Data Products, Market Trading Products, Market Portfolio Products or Market Learning Products we will check
that they are referencing the market and exchange we need, other wise we ignore them and continue
with the scanning.
3. Once found, we will go into their own branches and find a path until the Product Definition we need to reach.
@@ -244,8 +244,9 @@ exports.newFoundationsProcessModulesDataset = function (processIndex) {
}
if (
startingNode.type === 'Market Data Products' ||
startingNode.type === 'Market Trading Products' ||
startingNode.type === 'Market Data Products' ||
startingNode.type === 'Market Trading Products' ||
startingNode.type === 'Market Portfolio Products' ||
startingNode.type === 'Market Learning Products'
) {
/*
@@ -15,7 +15,7 @@
function raiseEvents(lastFile, timeFrames, callBackFunction) {
try {
/* We will reaise the events for the datasets impacted by the process that just finished. */
/* We will raise the events for the datasets impacted by the process that just finished. */
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.processOutput !== undefined) {
let outputDatasets = TS.projects.visualScripting.utilities.nodeFunctions.nodeBranchToArray(TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.processOutput, 'Output Dataset')
@@ -27,6 +27,9 @@
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDataset, 'Trading Bot')
}
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDataset, 'Portfolio Bot')
}
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDataset, 'Sensor Bot')
}
@@ -63,7 +66,7 @@
return
}
} else {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(botNode));
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine, Trading Mine or Portfolio Mine. Bot = " + JSON.stringify(botNode));
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return
}
@@ -121,6 +124,9 @@
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDataset, 'Trading Bot')
}
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDataset, 'Portfolio Bot')
}
if (botNode === undefined) {
botNode = TS.projects.visualScripting.utilities.nodeFunctions.findNodeInNodeMesh(outputDataset, 'Sensor Bot')
}
@@ -153,7 +159,7 @@
throw (TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
}
} else {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine | Trading Mine | Learning Mine. Bot = " + JSON.stringify(botNode));
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine | Trading Mine | Portfolio Mine | Learning Mine. Bot = " + JSON.stringify(botNode));
throw (TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
}
} else {
@@ -98,7 +98,8 @@
}
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE !== undefined && statusDependencyNode.bottype === "Trading Bot") {
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE !== undefined &&
(statusDependencyNode.bottype === "Trading Bot" || statusDependencyNode.bottype === "Portfolio Bot") ) {
sessionPath = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_FOLDER_NAME + "/"
}
@@ -112,6 +113,7 @@
if (checkThisDataBranch(lanNetworkNode.dataTasks) === true) { return }
if (checkThisTradingBranch(lanNetworkNode.testingTradingTasks) === true) { return }
if (checkThisTradingBranch(lanNetworkNode.productionTradingTasks) === true) { return }
if (checkThisPortfolioBranch(lanNetworkNode.portfolioTasks) === true) { return }
if (checkThisLearningBranch(lanNetworkNode.learningTasks) === true) { return }
function checkThisDataBranch(branch) {
@@ -219,6 +221,58 @@
}
}
function checkThisPortfolioBranch(branch) {
if (branch === undefined) { return }
for (let z = 0; z < branch.projectPortfolioTasks.length; z++) {
let projectPortfolioTasks = branch.projectPortfolioTasks[z]
for (let j = 0; j < projectPortfolioTasks.exchangePortfolioTasks.length; j++) {
let exchangeTasks = projectPortfolioTasks.exchangePortfolioTasks[j]
for (let p = 0; p < exchangeTasks.marketPortfolioTasks.length; p++) {
let marketTasks = exchangeTasks.marketPortfolioTasks[p]
if (marketTasks.referenceParent === undefined) { continue }
if (marketTasks.referenceParent.id !== TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.id) { continue }
for (let q = 0; q < marketTasks.PortfolioMineTasks.length; q++) {
let mineTasks = marketTasks.PortfolioMineTasks[q]
for (let k = 0; k < mineTasks.taskManagers.length; k++) {
let taskManager = mineTasks.taskManagers[k]
for (let m = 0; m < taskManager.tasks.length; m++) {
let task = taskManager.tasks[m]
if (task.bot === undefined) { continue }
if (task.bot.processes === undefined) { continue }
for (let n = 0; n < task.bot.processes.length; n++) {
let process = task.bot.processes[n]
if (process.referenceParent === undefined) { continue }
let processDefinition = process.referenceParent
if (processThisDependsOn.id === processDefinition.id) {
if (process.type === 'Portfolio Process Instance') {
if (process.session !== undefined) {
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.id !== process.session.id) {
continue
}
} else {
continue
}
}
/*
We found where the task that runs the process definition this status report depends on
and where it is located on the network.
*/
thisObject.lanNetworkNode = lanNetworkNode
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + lanNetworkNode.name + " -> host = " + lanNetworkNode.config.host + ' -> port = ' + lanNetworkNode.config.webPort + '.')
fileStorage = TS.projects.foundations.taskModules.fileStorage.newFileStorage(processIndex, lanNetworkNode.config.host, lanNetworkNode.config.webPort)
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE)
return true
}
}
}
}
}
}
}
}
}
function checkThisLearningBranch(branch) {
if (branch === undefined) { return }
for (let z = 0; z < branch.projectLearningTasks.length; z++) {
@@ -332,11 +386,11 @@
let canUserPrevious
/*
If we are funning Trading Engines, we can not allow ourselves to use a Status Report that is not
the latest one, because it might contain transactioinal information related to the context of the
If we are running Trading Engines, we can not allow ourselves to use a Status Report that is not
the latest one, because it might contain transactional information related to the context of the
operations the trading engine is doing.
On the contraty, if we are running a Sensor bot, API Data Fetcher, or an Indicator we might, if necesary,
On the contrary, if we are running a Sensor bot, API Data Fetcher, or an Indicator we might, if necesary,
use a previous version of a Status Report since there will be no big impact, just some reprocessing.
*/
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE !== undefined) {
@@ -73,6 +73,16 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.taskFunctions.stopAllExchangeTradingTasks(action.node)
}
break
case 'Run All Exchange Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllExchangePortfolioTasks(action.node)
}
break
case 'Stop All Exchange Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.stopAllExchangePortfolioTasks(action.node)
}
break
case 'Run All Exchange Learning Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllExchangeLearningTasks(action.node)
@@ -103,6 +113,16 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.taskFunctions.stopAllProjectTradingTasks(action.node)
}
break
case 'Run All Project Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllProjectPortfolioTasks(action.node)
}
break
case 'Stop All Project Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.stopAllProjectPortfolioTasks(action.node)
}
break
case 'Run All Project Learning Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllProjectLearningTasks(action.node)
@@ -133,6 +153,16 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.taskFunctions.stopAllMarketTradingTasks(action.node)
}
break
case 'Run All Market Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllMarketPortfolioTasks(action.node)
}
break
case 'Stop All Market Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.stopAllMarketPortfolioTasks(action.node)
}
break
case 'Run All Market Learning Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllMarketLearningTasks(action.node)
@@ -163,6 +193,16 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.taskFunctions.stopAllTradingMineTasks(action.node)
}
break
case 'Run All Portfolio Mine Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllPortfolioMineTasks(action.node)
}
break
case 'Stop All Portfolio Mine Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.stopAllPortfolioMineTasks(action.node)
}
break
case 'Run All Learning Mine Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.runAllLearningMineTasks(action.node)
@@ -213,6 +253,26 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.taskFunctions.addMissingTradingMineTasks(action.node, action.rootNodes)
}
break
case 'Add Missing Project Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.addMissingProjectPortfolioTasks(action.node, action.rootNodes)
}
break
case 'Add Missing Exchange Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.addMissingExchangePortfolioTasks(action.node, action.rootNodes)
}
break
case 'Add Missing Market Portfolio Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.addMissingMarketPortfolioTasks(action.node)
}
break
case 'Add Missing Portfolio Mine Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.addMissingPortfolioMineTasks(action.node, action.rootNodes)
}
break
case 'Add Missing Project Learning Tasks':
{
UI.projects.foundations.functionLibraries.taskFunctions.addMissingProjectLearningTasks(action.node, action.rootNodes)
@@ -288,6 +348,16 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingTradingSessionReferences(action.node, action.rootNodes)
}
break
case 'Add All Portfolio Mine Products':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addAllPortfolioMineProducts(action.node, action.rootNodes)
}
break
case 'Add Missing Portfolio Session References':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingPortfolioSessionReferences(action.node, action.rootNodes)
}
break
case 'Add Missing Learning Session References':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingLearningSessionReferences(action.node, action.rootNodes)
@@ -303,6 +373,11 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingMarketTradingProducts(action.node, action.rootNodes)
}
break
case 'Add Missing Market Portfolio Products':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingMarketPortfolioProducts(action.node, action.rootNodes)
}
break
case 'Add Missing Market Learning Products':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingMarketLearningProducts(action.node, action.rootNodes)
@@ -318,6 +393,11 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingExchangeTradingProducts(action.node, action.rootNodes)
}
break
case 'Add Missing Exchange Portfolio Products':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingExchangePortfolioProducts(action.node, action.rootNodes)
}
break
case 'Add Missing Exchange Data Products':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingExchangeDataProducts(action.node, action.rootNodes)
@@ -333,6 +413,11 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingProjectTradingProducts(action.node, action.rootNodes)
}
break
case 'Add Missing Project Portfolio Products':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingProjectPortfolioProducts(action.node, action.rootNodes)
}
break
case 'Add Missing Project Data Products':
{
UI.projects.foundations.functionLibraries.dataStorageFunctions.addMissingProjectDataProducts(action.node, action.rootNodes)
@@ -430,6 +515,18 @@ function newFoundationsActionSwitch() {
UI.projects.foundations.spaces.floatingSpace.uiObjectConstructor.createUiObject(true, action.node.payload)
}
break
case 'Switch To Live Portfolio':
{
action.node.type = "Live Portfolio Session"
UI.projects.foundations.spaces.floatingSpace.uiObjectConstructor.createUiObject(true, action.node.payload)
}
break
case 'Switch To Paper Portfolio':
{
action.node.type = "Paper Portfolio Session"
UI.projects.foundations.spaces.floatingSpace.uiObjectConstructor.createUiObject(true, action.node.payload)
}
break
case 'Switch To Backtesting':
{
action.node.type = "Backtesting Session"
@@ -15,7 +15,12 @@ function newFoundationsFunctionLibraryDataStorageFunctions() {
addMissingProjectDataProducts: addMissingProjectDataProducts,
addMissingProjectTradingProducts: addMissingProjectTradingProducts,
addMissingProjectLearningProducts: addMissingProjectLearningProducts,
createSessionReference: createSessionReference
createSessionReference: createSessionReference,
addAllPortfolioMineProducts: addAllPortfolioMineProducts,
addMissingPortfolioSessionReferences: addMissingPortfolioSessionReferences,
addMissingMarketPortfolioProducts: addMissingMarketPortfolioProducts,
addMissingExchangePortfolioProducts: addMissingExchangePortfolioProducts,
addMissingProjectPortfolioProducts: addMissingProjectPortfolioProducts,
}
return thisObject
@@ -45,6 +50,7 @@ function newFoundationsFunctionLibraryDataStorageFunctions() {
scanBotArray(mine.apiDataFetcherBots)
scanBotArray(mine.indicatorBots)
scanBotArray(mine.tradingBots)
scanBotArray(mine.portfolioBots)
scanBotArray(mine.learningBots)
function scanBotArray(botArray) {
@@ -92,6 +98,17 @@ function newFoundationsFunctionLibraryDataStorageFunctions() {
}
}
function addAllPortfolioMineProducts(node, rootNodes) {
for (let i = 0; i < rootNodes.length; i++) {
let rootNode = rootNodes[i]
if (rootNode.type === 'Portfolio Mine') {
let portfolioMineProducts = UI.projects.visualScripting.functionLibraries.uiObjectsFromNodes.addUIObject(node, 'Portfolio Mine Products')
UI.projects.visualScripting.functionLibraries.attachDetach.referenceAttachNode(portfolioMineProducts, rootNode)
}
}
}
function addAllLearningMineProducts(node, rootNodes) {
for (let i = 0; i < rootNodes.length; i++) {
let rootNode = rootNodes[i]
@@ -130,6 +147,27 @@ function newFoundationsFunctionLibraryDataStorageFunctions() {
}
}
function addMissingPortfolioSessionReferences(node, rootNodes) {
let lanNetworkNode = UI.projects.visualScripting.utilities.meshes.findNodeInNodeMesh(node, 'LAN Network Node', undefined, true, false, true, false)
if (lanNetworkNode === undefined) { return }
let livePortfolioSessionsArray = UI.projects.visualScripting.utilities.branches.nodeBranchToArray(lanNetworkNode, 'Live Portfolio Session')
addMissingSession(livePortfolioSessionsArray)
function addMissingSession(sessionsArray) {
for (let i = 0; i < sessionsArray.length; i++) {
let session = sessionsArray[i]
/* We will filter out all the sessions that does not belong to the market we are in */
let marketPortfolioTasks = UI.projects.visualScripting.utilities.meshes.findNodeInNodeMesh(session, 'Market Portfolio Tasks', undefined, true, false, true, false)
if (node.payload.referenceParent.id !== marketPortfolioTasks.payload.referenceParent.id) { continue }
if (UI.projects.visualScripting.utilities.nodeChildren.isMissingChildrenById(node, session, true) === true) {
createSessionReference(node, session, 'Portfolio Session Reference')
}
}
}
}
function addMissingLearningSessionReferences(node, rootNodes) {
let lanNetworkNode = UI.projects.visualScripting.utilities.meshes.findNodeInNodeMesh(node, 'LAN Network Node', undefined, true, false, true, false)
if (lanNetworkNode === undefined) { return }
@@ -166,6 +204,10 @@ function newFoundationsFunctionLibraryDataStorageFunctions() {
addMissingMarketProducts(node, rootNodes, 'Market Trading Products')
}
function addMissingMarketPortfolioProducts(node, rootNodes) {
addMissingMarketProducts(node, rootNodes, 'Market Portfolio Products')
}
function addMissingMarketLearningProducts(node, rootNodes) {
addMissingMarketProducts(node, rootNodes, 'Market Learning Products')
}
@@ -192,6 +234,10 @@ function newFoundationsFunctionLibraryDataStorageFunctions() {
addMissingExchange(node, rootNodes, 'Exchange Trading Products')
}
function addMissingExchangePortfolioProducts(node, rootNodes) {
addMissingExchange(node, rootNodes, 'Exchange Portfolio Products')
}
function addMissingExchangeLearningProducts(node, rootNodes) {
addMissingExchange(node, rootNodes, 'Exchange Learning Products')
}
@@ -224,6 +270,10 @@ function newFoundationsFunctionLibraryDataStorageFunctions() {
addMissingProject(node, rootNodes, 'Project Trading Products')
}
function addMissingProjectPortfolioProducts(node, rootNodes) {
addMissingProject(node, rootNodes, 'Project Portfolio Products')
}
function addMissingProjectLearningProducts(node, rootNodes) {
addMissingProject(node, rootNodes, 'Project Learning Products')
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
{"type":"Portfolio System","name":"BB Top Bounce","config":"{ \n\"codeName\": \"\"\n}","project":"Foundations","portfolioStrategies":[{"type":"Portfolio Strategy","name":"S1 - 3H Down","project":"Foundations","triggerStage":{"type":"Trigger Stage","name":"New Trigger Stage","project":"Foundations","triggerOn":{"type":"Trigger On Event","name":"New Trigger On Event","project":"Foundations","situations":[{"type":"Situation","name":"New Situation","project":"Foundations","conditions":[{"type":"Condition","name":"Previous Breakup","project":"Foundations","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.candle.previous.close > chart.at01hs.bollingerBand.previous.movingAverage + chart.at01hs.bollingerBand.previous.deviation","project":"Foundations","id":"99948a6e-e866-4339-86ba-eac367226312","savedPayload":{"position":{"x":30780.386277758334,"y":30051.498744389715},"targetPosition":{"x":31072.097253877637,"y":30121.53235354649},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"f0534c68-ab47-4370-b1fd-b3be4c1c262f","savedPayload":{"position":{"x":31072.097253877637,"y":30121.53235354649},"targetPosition":{"x":31363.80822999694,"y":30191.565962703262},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"Current Close In","project":"Foundations","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.bollingerBand.movingAverage + chart.at01hs.bollingerBand.deviation","project":"Foundations","id":"d3bd1690-1f94-484b-ac3c-7be2c7be85ed","savedPayload":{"position":{"x":30878.398033371974,"y":29838.894811327777},"targetPosition":{"x":31121.103131684456,"y":30015.23038701552},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":1,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"e2d7334d-cf52-44d9-a20e-5be201ca6799","savedPayload":{"position":{"x":31121.103131684456,"y":30015.23038701552},"targetPosition":{"x":31363.80822999694,"y":30191.565962703262},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"Current Red Candle","project":"Foundations","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at01hs.candle.close < chart.at01hs.candle.open","project":"Foundations","id":"d1fa707f-eb75-4b1c-8117-688b9b94e539","savedPayload":{"position":{"x":31050.30909116737,"y":29679.98186409081},"targetPosition":{"x":31207.058660582155,"y":29935.773913397035},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"fb71bcfb-2a40-4e8a-b8e8-b492128350a0","savedPayload":{"position":{"x":31207.058660582155,"y":29935.773913397035},"targetPosition":{"x":31363.80822999694,"y":30191.565962703262},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"3H Down","code":"chart.at03hs.bollingerChannel.direction * chart.atAnyTimeFrame.externalSignal.timestamp > chart.atAnyTimeFrame.externalSignal.timestamp","project":"Foundations","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at03hs.bollingerChannel.direction === \"Down\"","project":"Foundations","id":"8c8c5128-b88b-44a5-8e36-d5c61127404f","savedPayload":{"position":{"x":31269.9475509728,"y":29598.95295834618},"targetPosition":{"x":31316.87789048487,"y":29895.25946052472},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"id":"22246178-a8f9-4ec8-b55b-36cf7a24fbff","savedPayload":{"position":{"x":31316.87789048487,"y":29895.25946052472},"targetPosition":{"x":31363.80822999694,"y":30191.565962703262},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{"isRunning":false}}},{"type":"Condition","name":"3H Medium, Steep or Extreme","project":"Foundations","javascriptCode":{"type":"Javascript Code","name":"New Javascript Code","code":"chart.at03hs.bollingerSubChannel.slope === \"Medium\" || chart.at03hs.bollingerSubChannel.slope === \"Steep\" || chart.at03hs.bollingerSubChannel.slope === \"Extreme\" ","project":"Foundations","id":"bf8df41c-c62f-4cd6-840e-1dd538359c3b","savedPayload":{"position":{"x":31503.875448310486,"y":29608.144010464657},"targetPosition":{"x":31433.841839153712,"y":29899.85498658396},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,Line truncated
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,84 @@
{
"type": "Portfolio Bot",
"menuItems": [
{
"action": "Configure",
"label": "Configure",
"iconPathOn": "configuration",
"iconPathOff": "configuration",
"dontShowAtFullscreen": true,
"actionFunction": "uiObject.configEditor.activate",
"actionProject": "Foundations"
},
{
"action": "Add UI Object",
"label": "Add Process Definition",
"relatedUiObject": "Process Definition",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting",
"relatedUiObjectProject": "Foundations"
},
{
"action": "Add UI Object",
"label": "Add Product Definition",
"relatedUiObject": "Product Definition",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting",
"relatedUiObjectProject": "Foundations"
},
{
"action": "Add UI Object",
"label": "Add Product Definition Folder",
"relatedUiObject": "Product Definition Folder",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting",
"relatedUiObjectProject": "Foundations"
},
{
"action": "Delete UI Object",
"actionProject": "Visual-Scripting",
"askConfirmation": true,
"confirmationLabel": "Confirm to Delete",
"label": "Delete",
"iconPathOn": "delete-entity",
"iconPathOff": "delete-entity",
"actionFunction": "payload.executeAction"
}
],
"isTitleAllwaysVisible": true,
"addLeftIcons": true,
"editors": {
"config": true
},
"initialValues": {
"config": "{ \n \"codeName\": \"\"\n}"
},
"level": 3,
"attachingRules": {
"compatibleTypes": "->Portfolio Mine->"
},
"childrenNodesProperties": [
{
"name": "processes",
"type": "array",
"childType": "Process Definition",
"autoAdd": true,
"project": "Foundations"
},
{
"name": "products",
"type": "array",
"childType": "Product Definition",
"autoAdd": true,
"project": "Foundations"
},
{
"name": "productDefinitionFolders",
"type": "array",
"childType": "Product Definition Folder",
"autoAdd": true,
"project": "Foundations"
}
],
"propertyNameAtParent": "portfolioBots"
}
@@ -0,0 +1,49 @@
{
"type": "Portfolio Engine",
"menuItems": [
{
"action": "Add Missing Children",
"label": "Add Missing Children",
"relatedUiObject": "Portfolio Engine",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting",
"relatedUiObjectProject": "Portfolio-Management"
},
{
"action": "Delete UI Object",
"actionProject": "Visual-Scripting",
"askConfirmation": true,
"confirmationLabel": "Confirm to Delete",
"label": "Delete",
"iconPathOn": "delete-entity",
"iconPathOff": "delete-entity",
"actionFunction": "payload.executeAction"
}
],
"isHierarchyHead": true,
"addLeftIcons": true,
"level": 0,
"childrenNodesProperties": [
{
"name": "portfolioCurrent",
"type": "node",
"childType": "Portfolio Current",
"autoAdd": false,
"project": "Foundations"
},
{
"name": "portfolioLast",
"type": "node",
"childType": "Portfolio Last",
"autoAdd": false,
"project": "Foundations"
},
{
"name": "exchangeOrders",
"type": "node",
"childType": "Exchange Orders",
"autoAdd": false,
"project": "Foundations"
}
]
}
@@ -0,0 +1,63 @@
{
"type": "Portfolio-Management Project",
"menuItems": [
{
"action": "Add Missing Children",
"label": "Add Missing Children",
"iconPathOn": "portfolio-management-project",
"iconPathOff": "portfolio-management-project",
"iconProject": "Portfolio-Management",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting"
},
{
"action": "Add UI Object",
"label": "Add Portfolio Mine",
"relatedUiObject": "Portfolio Mine",
"relatedUiObjectProject": "Portfolio-Management",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting"
},
{
"action": "Add UI Object",
"label": "Add Portfolio Engine",
"relatedUiObject": "Portfolio Engine",
"relatedUiObjectProject": "Portfolio-Management",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting"
},
{
"action": "Add UI Object",
"label": "Add Portfolio System",
"relatedUiObject": "Portfolio System",
"relatedUiObjectProject": "Portfolio-Management",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting"
},
{
"action": "Delete UI Object",
"actionProject": "Visual-Scripting",
"askConfirmation": true,
"confirmationLabel": "Confirm to Delete",
"actionFunction": "payload.executeAction",
"label": "Delete",
"iconPathOn": "delete-entity",
"iconPathOff": "delete-entity"
}
],
"isProjectHead": true,
"isTitleAllwaysVisible": false,
"addLeftIcons": true,
"isPinned": true,
"positionLocked": true,
"icon": "portfolio-management-project",
"childrenNodesProperties": [
{
"name": "projectDefinition",
"type": "node",
"childType": "Project Definition",
"project": "Foundations",
"autoAdd": true
}
]
}
@@ -0,0 +1,65 @@
{
"type": "Portfolio Mine",
"menuItems": [
{
"action": "Configure",
"label": "Configure",
"iconPathOn": "configuration",
"iconPathOff": "configuration",
"iconProject": "Foundations",
"dontShowAtFullscreen": true,
"actionFunction": "uiObject.configEditor.activate",
"actionProject": "Foundations"
},
{
"action": "Add UI Object",
"label": "Add Portfolio Bot",
"relatedUiObject": "Portfolio Bot",
"relatedUiObjectProject": "Portfolio-Management",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting"
},
{
"action": "Add UI Object",
"label": "Add Plotter",
"relatedUiObject": "Plotter",
"relatedUiObjectProject": "Foundations",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting"
},
{
"action": "Delete UI Object",
"actionProject": "Visual-Scripting",
"askConfirmation": true,
"confirmationLabel": "Confirm to Delete",
"label": "Delete",
"iconPathOn": "delete-entity",
"iconPathOff": "delete-entity",
"actionFunction": "payload.executeAction"
}
],
"isTitleAllwaysVisible": true,
"isHierarchyHead": true,
"addLeftIcons": true,
"editors": {
"config": true
},
"level": 0,
"initialValues": {
"config": "{ \n\"codeName\": \"Type a name with no spaces or dashes\"\n}"
},
"childrenNodesProperties": [
{
"name": "portfolioBots",
"type": "array",
"childType": "Portfolio Bot",
"project": "Portfolio-Management"
},
{
"name": "plotters",
"type": "array",
"childType": "Plotter",
"project": "Foundations"
}
]
}
@@ -0,0 +1,52 @@
{
"type": "Portfolio System",
"menuItems": [
{
"action": "Add UI Object",
"label": "Add Portfolio Strategy",
"relatedUiObject": "Portfolio Strategy",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting",
"relatedUiObjectProject": "Foundations"
},
{
"action": "Add UI Object",
"disableIfPropertyIsDefined": true,
"propertyToCheckFor": "dynamicIndicators",
"label": "Add Dynamic Indicators",
"relatedUiObject": "Dynamic Indicators",
"actionFunction": "payload.executeAction",
"actionProject": "Visual-Scripting",
"relatedUiObjectProject": "Foundations"
},
{
"action": "Delete UI Object",
"actionProject": "Visual-Scripting",
"askConfirmation": true,
"confirmationLabel": "Confirm to Delete",
"label": "Delete",
"iconPathOn": "delete-entity",
"iconPathOff": "delete-entity",
"actionFunction": "payload.executeAction"
}
],
"isHierarchyHead": true,
"addLeftIcons": true,
"level": 0,
"childrenNodesProperties": [
{
"name": "portfolioStrategies",
"type": "array",
"childType": "Portfolio Strategy",
"autoAdd": true,
"project": "Foundations"
},
{
"name": "dynamicIndicators",
"type": "node",
"childType": "Dynamic Indicators",
"autoAdd": true,
"project": "Foundations"
}
]
}
@@ -0,0 +1,45 @@
{
"type": "Portfolio Bot",
"definition": {
"text": "A portfolio bot is a computer program that—based on datasets exposed as products by other bots (counting sensors, indicators and even other portfolio bots)—applies the portfolio logic defined on a Portfolio System to, on one side, generate a complete portfolio simulation (outputting datasets that include trades, the action of strategies, validation of conditions, etc.), and on the other side, manage the execution of orders when on a forward testing or live portfolio session.",
"updated": 1609861419818,
"translations": [
{
"language": "ES",
"text": "Un Portfolio Bot es un programa informático que, basado en conjuntos de datos expuestos como productos por otros bots (sensores de conteo, indicadores e incluso otros portfolio bots ), aplica la lógica portfolio definida en un Portfolio System para, por un lado, generar una simulación de portfolio completa (generando conjuntos de datos que incluyen operaciones, la acción de estrategias, validación de condiciones, etc.) y, por otro lado, gestiona la ejecución de órdenes en una sesión ya sea en vivo o no.",
"updated": 1624375084844
}
]
},
"paragraphs": [
{
"style": "Block",
"text": "Content"
},
{
"style": "Text",
"text": "In Superalgos, a portfolio bot is a computer program designed to leverage the Superalgos infrastructure to provide smart portfolio features to users building, testing, and deploying portfolio systems. As such, a portfolio bot does not feature portfolio logic in and of itself. Instead, it executes the logic defined in portfolio systems."
},
{
"style": "Text",
"text": "Put in other words, when Superalgos users define portfolio systems and the portfolio strategies within, they are not building a portfolio bot. Instead, they are defining a set of instructions that a portfolio bot will then interpret and execute."
},
{
"style": "Text",
"text": "Portfolio bots have access to every piece of infrastructure built in Superalgos. One of the noteworthy sections of the infrastructure relevant to portfolio bots are portfolio mines themselves. Portfolio bots may be quite complex pieces of software. To name two of the most prominent characteristics: the bot needs to consider multiple dimensions of information and needs to be highly reliable, as its activity entails monetary transactions."
},
{
"style": "Text",
"text": "Portfolio mines make building portfolio bots a more accessible feat, providing a framework to produce compartmentalized definitions for the bots' processes and products, just like data mines do for sensors and indicators."
},
{
"style": "Block",
"text": "Configuring"
},
{
"style": "Title",
"text": "Portfolio Bot Configuration",
"updated": 1609861438022
}
]
}
@@ -0,0 +1,66 @@
{
"type": "Portfolio Engine",
"definition": {
"text": "The portfolio engine hierarchy is the data structure used by the portfolio bot to keep runtime information highly accessible and exposed to others.",
"translations": [
{
"language": "ES",
"text": "La jerarquia portfolio engine es la estructura de datos utilizada por el robot comercial para mantener la información en tiempo de ejecución altamente accesible y expuesta a otros.",
"updated": 1624452474129
}
]
},
"paragraphs": [
{
"style": "Block",
"text": "Content"
},
{
"style": "Text",
"text": "You will use this hierarchy for two main purposes:"
},
{
"style": "List",
"text": "To let your portfolio systems access the information processed by the portfolio bot. This allows strategies to keep track of and react to current and past events&mdash;including those involving the exchange, such as orders placed or filled&mdash;as the bot is running."
},
{
"style": "List",
"text": "To keep track of the actions of the portfolio bot via the design space visual environment and panels over the charts. By analyzing runtime information, you may gain a detailed understanding of what happens, when, and why, throughout a portfolio session."
},
{
"style": "Title",
"text": "Frequently Asked Questions"
},
{
"style": "Subtitle",
"text": "Can a Portfolio Engine be shared between two Portfolio Bots?"
},
{
"style": "Text",
"text": "The same portfolio engine can be used from multiple portfolio sessions, because it is just a node-based representation of a data structure.",
"updated": 1621987910896
},
{
"style": "Text",
"text": "Every time a Task is ran, a snapshot of that data structure, together with a snapshot of the Network and the Bots definitions at their Data Mine is sent from the UI to the Task Server, and from there is consumed by the running Bot. That means that a second Bot can receive the same Portfolio Engine snapshot and it will never know if other bots were fed with the same information or not.",
"updated": 1621987965915
},
{
"style": "Subtitle",
"text": "Do I need to change something at the Portfolio Engine?"
},
{
"style": "Text",
"text": "You don't need to physically change anything inside portfolio engine via the UI. The portfolio engine is a visual representation of the data structure that the portfolio system uses to store its values, so to speak."
},
{
"style": "Subtitle",
"text": "Can I change the values inside the Portfolio Engine?"
},
{
"style": "Text",
"text": "You access the Portfolio Engine from within your portfolio system if there is something in there you would like to change. This is the whole purpose of the User Defined Code node.",
"updated": 1621988418300
}
]
}
@@ -0,0 +1,41 @@
{
"type": "Portfolio Mine",
"definition": {
"text": "A portfolio mine is a hierarchy that contains definitions of portfolio bots and their plotters. These definitions make up the actual programs of such bots&mdash;the source code.",
"translations": [
{
"language": "ES",
"text": "Un portfolio mine es una jerarquía que contiene definiciones de portfolio bots y sus conspiradores. Estas definiciones constituyen los programas reales de dichos bots: el código fuente.",
"updated": 1628906153772
}
]
},
"paragraphs": [
{
"style": "Block",
"text": "Content"
},
{
"style": "Text",
"text": "Altough Superalgos is starting out with a single portfolio bot, the system is prepared to feature as many portfolio bots as developers are willing to create. As such, a portfolio mine is a catalog of portfolio bots."
},
{
"style": "Text",
"text": "Notice that, in the context of Superalgos, a portfolio bot has nothing to do with the portfolio logic. Think of portfolio logic as the business rules which—in Superalgos—are defined in portfolio systems. Anyone may build a Portfolio System, meaning that creating portfolio systems and the portfolio strategies within does not require coding.",
"updated": 1609861342619
},
{
"style": "Text",
"text": "Portfolio mines work similarly to data mines in the sense that they provide the same kind of tools to structure the definitions of bots, including process definitions, product definitions, and plotters."
},
{
"style": "Block",
"text": "Configuring"
},
{
"style": "Title",
"text": "Portfolio Mine Configuration",
"updated": 1609861240385
}
]
}
@@ -0,0 +1,39 @@
{
"type": "Portfolio System",
"definition": {
"text": "A portfolio system is a framework handling the low-level logic that serves to structure the processes and methods used to implement and deploy portfolio strategies.",
"translations": [
{
"language": "ES",
"text": "Un portfolio system es un marco que maneja la lógica de bajo nivel que sirve para estructurar los procesos y métodos utilizados para implementar y desplegar estrategias de portfolio.",
"updated": 1624375355452
}
]
},
"paragraphs": [
{
"style": "Block",
"text": "Content"
},
{
"style": "Text",
"text": "In practical terms, a portfolio system is a hierarchical arrangement organizing the actionable aspects of your investment plan. The hierarchy contains definitions regarding any number of portfolio strategies, all operating on the same market and sharing the same initial capital allocation."
},
{
"style": "Text",
"text": "You use a portfolio system to define strategies following the Superalgos Protocol, splitting strategies into four stages: trigger, open, manage, and close."
},
{
"style": "Text",
"text": "The concept of describing strategies in stages is fundamental to the methodical aspect of the portfolio system, as it provides a framework to run every strategy with the same framework, which contributes to developing scalable portfolio systems that may grow to any number of strategies."
},
{
"style": "Text",
"text": "When a portfolio system features more than one strategy, the first strategy has precedence over the second, the second over the third, and so on. This means that strategies are evaluated in a sequence. When a given strategy is triggered-on, the remaining strategies in the queue are no longer evaluated until the strategy triggers off. In other words, when multiple strategies are deployed within a single portfolio system, only one strategy may trade at any given moment, and precedence is given by the order around the portfolio system node."
},
{
"style": "Text",
"text": "As a corollary to the above, if you wish strategies to operate in different markets, or you wish strategies to be able to take positions simultaneously, then you must set up those strategies in different portfolio systems."
}
]
}
@@ -0,0 +1,284 @@
exports.newPortfolioManagementBotModulesExchangeAPI = function (processIndex) {
let MODULE_NAME = "Exchange API";
let thisObject = {
getOrder: getOrder,
createOrder: createOrder,
cancelOrder: cancelOrder,
initialize: initialize,
finalize: finalize
};
let portfolioSystem
let exchangeId
let options = {}
let exchange
const ccxt = SA.nodeModules.ccxt
return thisObject;
function initialize() {
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
exchangeId = TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.parentNode.parentNode.config.codeName
let key
let secret
let uid
let password
let sandBox = TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.parentNode.parentNode.config.sandbox || false;
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference !== undefined) {
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent !== undefined) {
key = TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent.config.codeName
secret = TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent.config.secret
uid = TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent.config.uid
password = TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent.config.password
}
}
const exchangeClass = ccxt[exchangeId]
const exchangeConstructorParams = {
'apiKey': key,
'secret': secret,
'uid': uid,
'password': password,
'timeout': 30000,
'enableRateLimit': true,
verbose: false,
'adjustForTimeDifference': true,
options: options
}
exchange = new exchangeClass(exchangeConstructorParams)
// enable sandbox mode based on exchange sandbox param value
if (sandBox) {
exchange.setSandboxMode(sandBox);
}
console.log('Sandbox mode = ' + sandBox);
// uncomment the following line if you want to log the exchange api being used
// console.log(exchange.urls.api);
}
function finalize() {
portfolioSystem = undefined
exchangeId = undefined
options = undefined
exchange = undefined
}
async function getOrder(portfolioSystemOrder, portfolioEngineOrder) {
let orderId = portfolioEngineOrder.exchangeId.value
const symbol = TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName + '/' + TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName
/* Basic Logging */
logInfo("getOrder -> orderId = " + orderId)
logInfo("getOrder -> symbol = " + symbol)
/* Basic Validations */
if (exchange.has['fetchOrder'] === false) {
logError("getOrder -> Exchange does not support fetchOrder command.");
return
}
try {
let order = await exchange.fetchOrder(orderId, symbol)
logInfo("getOrder -> Response from the Exchange: " + JSON.stringify(order));
return order
} catch (err) {
const message = 'Get Order Unexpected Error'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
let contextInfo = {
symbol: symbol,
orderId: orderId
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, portfolioSystemOrder.name, undefined, undefined, undefined, contextInfo)
portfolioSystem.addError([portfolioSystemOrder.id, message, docs])
logError("getOrder -> Error = " + err.message);
if (/[0-9a-z]+ NotFound/.test(err.message)) {
logInfo("getOrder -> NotFound, so order is actually closed.")
order = {
status: 'NotFound'
}
return order
}
}
}
async function createOrder(portfolioSystemOrder, portfolioEngineOrder) {
let price = portfolioEngineOrder.rate.value // CCXT: how much quote currency you are willing to pay for a trade lot of base currency (for limit orders only)
let type // CCXT: a string literal type of order, ccxt currently unifies market and limit orders only
let side // CCXT: a string literal for the direction of your order, buy or sell
let symbol = TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName + '/' + TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName // CCXT: a string literal symbol of the market you wish to trade on, like BTC/USD, ZEC/ETH, DOGE/DASH, etc
let amount = portfolioEngineOrder.orderBaseAsset.size.value // CCXT: how much of currency you want to trade, in Base Asset.
switch (portfolioSystemOrder.type) {
case 'Market Buy Order': {
type = 'market'
side = 'buy'
break
}
case 'Market Sell Order': {
type = 'market'
side = 'sell'
break
}
case 'Limit Buy Order': {
type = 'limit'
side = 'buy'
break
}
case 'Limit Sell Order': {
type = 'limit'
side = 'sell'
break
}
}
/* Basic Logging */
logInfo("createOrder -> symbol = " + symbol);
logInfo("createOrder -> side = " + side);
logInfo("createOrder -> type = " + type);
logInfo("createOrder -> amount = " + amount);
logInfo("createOrder -> price = " + price);
/* Basic Validations */
if (side !== "buy" && side !== "sell") {
logError("createOrder -> side must be either 'buy' or 'sell'.");
return
}
if (exchange.has['createOrder'] === false) {
logError("createOrder -> Exchange does not support createOrder command.");
return
}
try {
let order = await (exchange.createOrder(symbol, type, side, amount, price))
logInfo("createOrder -> Response from the Exchange: " + JSON.stringify(order));
return order.id
} catch (err) {
let exchangeApiKey = TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent
let message = 'Create Order Unexpected Error'
logError("createOrder -> Error = " + err.message)
logError("createOrder -> symbol = " + symbol);
logError("createOrder -> side = " + side);
logError("createOrder -> type = " + type);
logError("createOrder -> amount = " + amount);
logError("createOrder -> price = " + price);
if (
err.message.indexOf('API-key format invalid') >= 0 ||
err.message.indexOf('Invalid API-key, IP, or permissions for action.') >= 0 ||
err.message.indexOf('"code":-2015') >= 0
) {
logError('The exchange says the API key provided is not good or it is not in the correct format. This is what you are using:')
errorFooter()
message = 'Invalid Exchange API Key'
}
if (
err.message.indexOf('Signature for this request is not valid.') >= 0 ||
err.message.indexOf('"code":-1022') >= 0
) {
logError('The exchange says the secret provided is not good, incorrect or not in the correct format. This is what you are using:')
errorFooter()
message = 'Invalid Exchange API Secret'
}
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
let contextInfo = {
symbol: symbol,
side: side,
type: type,
amount: amount,
price: price,
key: exchangeApiKey.config.codeName,
secret: exchangeApiKey.config.secret,
keyLength: exchangeApiKey.config.codeName.length,
secretLength: exchangeApiKey.config.secret.length
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, exchangeApiKey.name, undefined, exchangeApiKey.config, undefined, contextInfo)
portfolioSystem.addError([exchangeApiKey.id, message, docs])
function errorFooter() {
logError('key: ->' + exchangeApiKey.config.codeName + '<-')
logError('secret: ->' + exchangeApiKey.config.secret + '<-')
logError('key.length:' + exchangeApiKey.config.codeName.length)
logError('secret.length:' + exchangeApiKey.config.secret.length)
logError('Double check that you copied the key at the codeName property and the secret at secret property without bringing any invisible caracter from the exchange web site, or leaving any character from the sample text. Also check that the keys are not disabled at the exchange ot that they are not restricted by IP.')
logError('As a remainder. Binance key and secret are 64 bytes in lenght each one. ')
}
}
}
async function cancelOrder(portfolioSystemOrder, portfolioEngineOrder) {
let orderId = portfolioEngineOrder.exchangeId.value
/* Basic Logging */
logInfo("cancelOrder -> Entering function. orderId = " + orderId);
const symbol = TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName + '/' + TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName
/* Basic Validations */
if (exchange.has['fetchOrder'] === false) {
logError("cancelOrder -> Exchange does not support fetchOrder command.");
return
}
try {
let order = await exchange.cancelOrder(orderId, symbol)
logInfo("cancelOrder -> Response from the Exchange: " + JSON.stringify(order));
return true
} catch (err) {
const message = 'Cancel Order Unexpected Error'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
let contextInfo = {
symbol: symbol,
orderId: orderId
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, portfolioSystemOrder.name, undefined, undefined, undefined, contextInfo)
portfolioSystem.addError([portfolioSystemOrder.id, message, docs])
logError("cancelOrder -> Error = " + err.message);
}
}
function logInfo(message) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[INFO] ' + message)
}
function logError(message) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] ' + message)
}
};
@@ -0,0 +1,352 @@
exports.newPortfolioManagementBotModulesOrdersCalculations = function (processIndex) {
/*
When we are live portfolio, we need to syncronize with the exchange.
*/
let thisObject = {
actualSizeCalculation: actualSizeCalculation,
actualRateCalculation: actualRateCalculation,
feesToBePaidCalculation: feesToBePaidCalculation,
amountReceivedCalculation: amountReceivedCalculation,
percentageFilledCalculation: percentageFilledCalculation,
feesPaidCalculation: feesPaidCalculation,
sizeFilledCalculation: sizeFilledCalculation,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let portfolioSystem
let sessionParameters
return thisObject
function initialize() {
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
}
function finalize() {
sessionParameters = undefined
portfolioEngine = undefined
portfolioSystem = undefined
}
async function actualSizeCalculation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
When we submit the order to the exchange, we do it specifying the order size
in Base Asset. It is mandatory to be in Base Asset as per CCXT Library API and
probably as per the Exchange API too.
The exchange might take that size or might change it a little
bit. For example, if it does not accept as many decimals as we are sending.
We have also seen that for Market Orders, the exchange usually does not take
the size requested exactly. Sometimes is a little bit less or even a little bit
more. For Limit orders it is usually the same.
If it happens that the size accepted (Actual Size) is different than the
size requested (Size), we need to make several adjustments so that the
accounting syncronizes with reality.
*/
/* This calculation needs to happen only once, the first time the order is checked. */
if (portfolioEngineOrder.orderBaseAsset.actualSize.value !== portfolioEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
if ( order.amount !== undefined) {
/* We receive the actual size from the exchange at the order.amount field. */
portfolioEngineOrder.orderBaseAsset.actualSize.value = order.amount
}
recalculateActualSize()
recalculateSizePlaced()
function recalculateActualSize() {
/*
We will recalculate the Quoted Asset Actual Size. This will also give to it an initial value.
In the next formula, we are unsing the rate of the order because we dont know yet the Actual Rate.
This rate might be replaced afteerwards for the Actual Rate when this is calculated again once
the Actual Rate is known.
*/
portfolioEngineOrder.orderQuotedAsset.actualSize.value = portfolioEngineOrder.orderBaseAsset.actualSize.value * portfolioEngineOrder.rate.value
portfolioEngineOrder.orderBaseAsset.actualSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderBaseAsset.actualSize.value, 10)
portfolioEngineOrder.orderQuotedAsset.actualSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderQuotedAsset.actualSize.value, 10)
}
function recalculateSizePlaced() {
/*
We will also recalculate the Size Placed that was previously calculated with the order Size,
and now we will do it with the order Actual Size.
We need to unaccount the previous size placed and correctly account
for the the new actual sizes we now know.
*/
portfolioEngineStage.stageBaseAsset.sizePlaced.value =
portfolioEngineStage.stageBaseAsset.sizePlaced.value -
portfolioEngineOrder.orderBaseAsset.size.value
portfolioEngineStage.stageBaseAsset.sizePlaced.value =
portfolioEngineStage.stageBaseAsset.sizePlaced.value +
portfolioEngineOrder.orderBaseAsset.actualSize.value
portfolioEngineStage.stageQuotedAsset.sizePlaced.value =
portfolioEngineStage.stageQuotedAsset.sizePlaced.value -
portfolioEngineOrder.orderQuotedAsset.size.value
portfolioEngineStage.stageQuotedAsset.sizePlaced.value =
portfolioEngineStage.stageQuotedAsset.sizePlaced.value +
portfolioEngineOrder.orderQuotedAsset.actualSize.value
portfolioEngineStage.stageBaseAsset.sizePlaced.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineStage.stageBaseAsset.sizePlaced.value, 10)
portfolioEngineStage.stageQuotedAsset.sizePlaced.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
}
async function actualRateCalculation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
Actual Rate Calculation: Let's remember that the exchange may not take the rate
we send it with the order (in Limit orders) and for any reason change it a little bit.
For example we might have sent a rate with too many decimals and the exchange truncate
them. Another thing that might happen is that we send a rate that is matching orders already
at the order book. In this case we would get an actual rate not only different than the one
we sent, but also, better since the portfolio engine inside the exchange would match orders
with the first orders best by price.
In the case of Market Orders we don't even send a rate. For all those reason
we get and store the Actual Rate at which the order was filled.
*/
if (order.average !== undefined) {
/*
We will use the average whenever is available. As of today it is not 100% clear when this is
available, but it seems sometimes it is not. In those cases we use the price.
*/
portfolioEngineOrder.orderStatistics.actualRate.value = order.average
} else if (order.price !== undefined) {
/*
We use the order.price when the average is not available.
*/
portfolioEngineOrder.orderStatistics.actualRate.value = order.price
}
portfolioEngineOrder.orderStatistics.actualRate.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderStatistics.actualRate.value, 10)
/*
If the Actual Rate happens to be exctly the same than the order rate, then there is
nothing else to do here. Otherwise there are adjustments to be made.
*/
if (portfolioEngineOrder.orderStatistics.actualRate.value === portfolioEngineOrder.rate.value) { return }
let previousQuotedAssetActualSize = portfolioEngineOrder.orderQuotedAsset.actualSize.value
recalculateActualSize()
recalculateSizePlaced()
function recalculateActualSize() {
/*
Now we know the Actual Rate at which the order was filled. Since the actual rate
is not the same as the Rate we defined for the order, we need to syncronize
the Actual Order Size for Quoted Asset since it was calculated with the Order Size that we
now know it is not the one really used at the exchange. We will recalculate the
Actual Size in Quoted Asset and not in Base Asset, since the Actual Size in Base Asset
is the one we accepted by the exchange, so that is fixed, regardless of how we initially
got the Order Size in Base Asset, either from direct imput from the user or calculated
from the Order Size in Quoted Asset input it by the user. So here we go.
*/
previousQuotedAssetActualSize = portfolioEngineOrder.orderQuotedAsset.actualSize.value
portfolioEngineOrder.orderQuotedAsset.actualSize.value =
portfolioEngineOrder.orderBaseAsset.actualSize.value *
portfolioEngineOrder.orderStatistics.actualRate.value
portfolioEngineOrder.orderQuotedAsset.actualSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderQuotedAsset.actualSize.value, 10)
const message = 'Calculating - Actual Size Recalculated'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
previousQuotedAssetActualSize: previousQuotedAssetActualSize,
recalculatedQuotedAssetActualSize: portfolioEngineOrder.orderQuotedAsset.actualSize.value,
actualRate: portfolioEngineOrder.orderStatistics.actualRate.value,
orderRate: portfolioEngineOrder.rate.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[portfolioEngineOrder.orderQuotedAsset.actualSize.id, portfolioEngineOrder.orderStatistics.actualRate.id],
message,
docs
]
)
}
function recalculateSizePlaced() {
let previousStageQuotedAssetSizePlaced = portfolioEngineStage.stageQuotedAsset.sizePlaced.value
/*
Since we changed the Actual Size in Quoted Asset, we need to fix the Size Placed in Quoted Asset
that was previously calculated with the previous value of actualSize.
*/
portfolioEngineStage.stageQuotedAsset.sizePlaced.value =
portfolioEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetActualSize
portfolioEngineStage.stageQuotedAsset.sizePlaced.value =
portfolioEngineStage.stageQuotedAsset.sizePlaced.value +
portfolioEngineOrder.orderQuotedAsset.actualSize.value
portfolioEngineStage.stageQuotedAsset.sizePlaced.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineStage.stageQuotedAsset.sizePlaced.value, 10)
const message = 'Calculating - Size Placed Recalculated'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
previousStageQuotedAssetSizePlaced: previousStageQuotedAssetSizePlaced,
recalculatedStageQuotedAssetSizePlaced: portfolioEngineStage.stageQuotedAsset.sizePlaced.value,
actualRate: portfolioEngineOrder.orderStatistics.actualRate.value,
orderRate: portfolioEngineOrder.rate.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[portfolioEngineStage.stageQuotedAsset.sizePlaced.id, portfolioEngineOrder.orderStatistics.actualRate.id],
message,
docs
]
)
}
}
async function feesToBePaidCalculation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
*/
switch (portfolioEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
'feesToBePaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.taker,
100
)
break
}
case 'Limit Order': {
await applyFeePercentage(
'feesToBePaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.maker,
100
)
break
}
}
}
async function percentageFilledCalculation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
Percentage Filled Calculation: The only relevant information we get from the exchange is the order.filled.
We know this field will go up until reaching the Actual Size. Once it reaches that number
the order will be 100% filled.
*/
if (order.filled !== undefined) {
portfolioEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / portfolioEngineOrder.orderBaseAsset.actualSize.value
portfolioEngineOrder.orderStatistics.percentageFilled.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderStatistics.percentageFilled.value, 10)
}
}
async function feesPaidCalculation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
Within the order information received from the exchange we can not see the fees paid so we need to
calculate them ourselves. Knwowing the Fees Paid is critical to later update the balances correctly,
since at every trade we will receive less than just the Actual Size. In fact we will receive the Actual
Size minus the Fees Paid.
The percentage at which the exchange will charge fees needs to be configured at the the Session Fees Parameters.
*/
switch (portfolioEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
'feesPaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.taker,
portfolioEngineOrder.orderStatistics.percentageFilled.value
)
break
}
case 'Limit Order': {
await applyFeePercentage(
'feesPaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.maker,
portfolioEngineOrder.orderStatistics.percentageFilled.value
)
break
}
}
}
async function sizeFilledCalculation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
CCXT returns order.filled with an amount denominated in Base Asset. We will
take it from there for our Order Base Asset. The amount in order.filled does
not reflect the Fees Paid, even if we are paying the fees in Base Asset.
*/
if (order.filled !== undefined) {
portfolioEngineOrder.orderBaseAsset.sizeFilled.value = order.filled
portfolioEngineOrder.orderBaseAsset.sizeFilled.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderBaseAsset.sizeFilled.value, 10)
}
/*
For Quoted Asset, CCXT does not return any field with the size filled, so we
need to calculate that by ourselves.
*/
portfolioEngineOrder.orderQuotedAsset.sizeFilled.value =
portfolioEngineOrder.orderQuotedAsset.actualSize.value * portfolioEngineOrder.orderStatistics.percentageFilled.value / 100
portfolioEngineOrder.orderQuotedAsset.sizeFilled.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
}
async function amountReceivedCalculation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
This calculation is for informational purposes, so that users do not have to calculate
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
are selling then it will be in Quoted Asset.
*/
switch (true) {
case portfolioSystemOrder.type === 'Market Buy Order' || portfolioSystemOrder.type === 'Limit Buy Order': {
/*
In this case the Amount Received is in Base Asset.
*/
portfolioEngineOrder.orderBaseAsset.amountReceived.value =
portfolioEngineOrder.orderBaseAsset.sizeFilled.value -
portfolioEngineOrder.orderBaseAsset.feesPaid.value
break
}
case portfolioSystemOrder.type === 'Market Sell Order' || portfolioSystemOrder.type === 'Limit Sell Order': {
/*
In this case the Amount Received is in Quoted Asset.
*/
portfolioEngineOrder.orderQuotedAsset.amountReceived.value =
portfolioEngineOrder.orderQuotedAsset.sizeFilled.value -
portfolioEngineOrder.orderQuotedAsset.feesPaid.value
break
}
}
}
}
@@ -0,0 +1,538 @@
exports.newPortfolioManagementBotModulesOrdersSimulations = function (processIndex) {
/*
When we are backtesting or paper portfolio, we need to simulate what would have happened at the exchange.
*/
let thisObject = {
actualSizeSimulation: actualSizeSimulation,
actualRateSimulation: actualRateSimulation,
feesToBePaidSimulation: feesToBePaidSimulation,
amountReceivedSimulation: amountReceivedSimulation,
percentageFilledSimulation: percentageFilledSimulation,
feesPaidSimulation: feesPaidSimulation,
sizeFilledSimulation: sizeFilledSimulation,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let portfolioSystem
let sessionParameters
return thisObject
function initialize() {
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
}
function finalize() {
sessionParameters = undefined
portfolioEngine = undefined
portfolioSystem = undefined
}
async function actualSizeSimulation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, applyFeePercentage) {
/*
The Actual Size is the size the exchange accepts for the order,
regarless of the size sent in the request to the exchange.
While backtesting and paper portfolio, we do not really need to
simulate an Actual Size. We will just copy to that field the
size of the order.
This calculation needs to happen only once, so as to set these values.
*/
if (portfolioEngineOrder.orderBaseAsset.actualSize.value !== portfolioEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
portfolioEngineOrder.orderBaseAsset.actualSize.value = portfolioEngineOrder.orderBaseAsset.size.value
portfolioEngineOrder.orderQuotedAsset.actualSize.value = portfolioEngineOrder.orderQuotedAsset.size.value
}
function actualRateSimulation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, applyFeePercentage) {
/* Actual Rate Simulation */
let previousQuotedAssetActualSize = portfolioEngineOrder.orderQuotedAsset.actualSize.value
if (portfolioEngineOrder.orderStatistics.actualRate.value === portfolioEngineOrder.orderStatistics.actualRate.config.initialValue) {
/*
The default value for the Actual Rate is the rate of the order.
*/
portfolioEngineOrder.orderStatistics.actualRate.value = portfolioEngineOrder.rate.value
}
basedOnSessionParameters()
basedOnPortfolioSystem()
considerBestMatchWithOrderBook()
recalculateActualSize()
recalculateSizePlaced()
function basedOnSessionParameters() {
/*
The default way to calculate the Actual Rate is to apply the Slippage defined
at the Session Parameters Definition. We are not going to apply the default if there
is a definition for this order on how to calculate the actual rate.
*/
if (portfolioSystemOrder.simulatedExchangeEvents !== undefined) {
if (portfolioSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) {
return
}
}
switch (portfolioEngineOrder.type) {
case 'Market Order': {
/* Actual Rate is simulated based on the Session Paremeters */
let slippageAmount = portfolioEngineOrder.rate.value * sessionParameters.slippage.config.marketOrderRate / 100
if (slippageAmount !== 0) {
switch (portfolioSystemOrder.type) {
case 'Market Sell Order': {
portfolioEngineOrder.orderStatistics.actualRate.value = portfolioEngineOrder.rate.value - slippageAmount
portfolioEngineOrder.orderStatistics.actualRate.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderStatistics.actualRate.value, 10)
const message = 'Simulating - Slippage Subtracted'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
slippageAmount: slippageAmount,
orderRate: portfolioEngineOrder.rate.value,
actualRate: portfolioEngineOrder.orderStatistics.actualRate.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[sessionParameters.slippage.id, portfolioEngineOrder.rate.id, portfolioEngineOrder.orderStatistics.actualRate.id],
message,
docs
]
)
break
}
case 'Market Buy Order': {
portfolioEngineOrder.orderStatistics.actualRate.value = portfolioEngineOrder.rate.value + slippageAmount
portfolioEngineOrder.orderStatistics.actualRate.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderStatistics.actualRate.value, 10)
const message = 'Simulating - Slippage Added'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
slippageAmount: slippageAmount,
orderRate: portfolioEngineOrder.rate.value,
actualRate: portfolioEngineOrder.orderStatistics.actualRate.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[sessionParameters.slippage.id, portfolioEngineOrder.rate.id, portfolioEngineOrder.orderStatistics.actualRate.id],
message,
docs
]
)
break
}
}
}
break
}
case 'Limit Order': {
/* In Limit Orders the actual rate is the rate of the order, there is no slippage */
portfolioEngineOrder.orderStatistics.actualRate.value = portfolioEngineOrder.rate.value
break
}
}
}
function basedOnPortfolioSystem() {
/*
If the user specified a Formula to simulate the Actual Rate for this order,
we use that formula here. Note that if this Formula exists, this will override
the calculation based on Slippage.
*/
if (portfolioSystemOrder.simulatedExchangeEvents !== undefined) {
if (portfolioSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) {
if (portfolioSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) {
/* Calculate this only once for this order */
if (portfolioEngineOrder.orderStatistics.actualRate.value === portfolioEngineOrder.orderStatistics.actualRate.config.initialValue) {
let newValue = portfolioSystem.formulas.get(portfolioSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id)
newValue = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(newValue, 10)
const message = 'Simulating - Actual Rate Based On Formula'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
previousActualRate: portfolioEngineOrder.orderStatistics.actualRate.value,
recalculatedActualRate: newValue
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
portfolioSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id,
message,
docs
]
)
portfolioEngineOrder.orderStatistics.actualRate.value = newValue
}
}
}
}
}
function considerBestMatchWithOrderBook() {
/*
Until here we have got the Actual Rate based on the formula definition or based on session parameters slippage.
The last check is about watching what happened in the market. Let's remember that the exchange will
fill the order with the best possible matches at it's order book. That means that if the rate
we set for the order was too low (for a sale order) or too hight (for a buy order), the actual rate
should be better than expected.
We don't know what it is at the order book, but whe know that the last candle includes trades that bounced between
the min an max of the candle. That could tell us the above the max and below the min, the order would be matched
with orders at the order book. For that reason, we do this:
*/
switch (true) {
case portfolioSystemOrder.type === 'Market Buy Order' || portfolioSystemOrder.type === 'Limit Buy Order': {
if (portfolioEngineOrder.orderStatistics.actualRate.value > portfolioEngine.portfolioCurrent.portfolioEpisode.candle.max.value) {
const message = 'Simulating - Actual Rate Too Hight'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
previousActualRate: portfolioEngineOrder.orderStatistics.actualRate.value,
recalculatedActualRate: portfolioEngine.portfolioCurrent.portfolioEpisode.candle.max.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[portfolioEngineOrder.orderStatistics.actualRate.id, portfolioEngine.portfolioCurrent.portfolioEpisode.candle.max.id],
message,
docs
]
)
portfolioEngineOrder.orderStatistics.actualRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.max.value
}
break
}
case portfolioSystemOrder.type === 'Market Sell Order' || portfolioSystemOrder.type === 'Limit Sell Order': {
if (portfolioEngineOrder.orderStatistics.actualRate.value < portfolioEngine.portfolioCurrent.portfolioEpisode.candle.min.value) {
const message = 'Simulating - Actual Rate Too Low'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
previousActualRate: portfolioEngineOrder.orderStatistics.actualRate.value,
recalculatedActualRate: portfolioEngine.portfolioCurrent.portfolioEpisode.candle.max.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[portfolioEngineOrder.orderStatistics.actualRate.id, portfolioEngine.portfolioCurrent.portfolioEpisode.candle.min.id],
message,
docs
]
)
portfolioEngineOrder.orderStatistics.actualRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.min.value
}
break
}
}
portfolioEngineOrder.orderStatistics.actualRate.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderStatistics.actualRate.value, 10)
}
function recalculateActualSize() {
if (portfolioEngineOrder.orderStatistics.actualRate.value === portfolioEngineOrder.rate.value) { return }
/*
Now that we have an Actual Rate, we need to adjust the Actual Size in Quoted Asset, since
Base Asset is what the exchange accepts as input and give information at. Quotes Asset properties are always calculated.
*/
portfolioEngineOrder.orderQuotedAsset.actualSize.value =
portfolioEngineOrder.orderBaseAsset.actualSize.value *
portfolioEngineOrder.orderStatistics.actualRate.value
portfolioEngineOrder.orderQuotedAsset.actualSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderQuotedAsset.actualSize.value, 10)
const message = 'Simulating - Actual Size Recalculated'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
previousQuotedAssetActualSize: previousQuotedAssetActualSize,
recalculatedQuotedAssetActualSize: portfolioEngineOrder.orderQuotedAsset.actualSize.value,
actualRate: portfolioEngineOrder.orderStatistics.actualRate.value,
orderRate: portfolioEngineOrder.rate.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[portfolioEngineOrder.orderQuotedAsset.actualSize.id, portfolioEngineOrder.orderStatistics.actualRate.id],
message,
docs
]
)
}
function recalculateSizePlaced() {
if (portfolioEngineOrder.orderStatistics.actualRate.value === portfolioEngineOrder.rate.value) { return }
/*
Since the Actual Rate might have changed, we need to recalculate the Size Placed, where we accumulate all the Size Placed of
all orders of a Stage. For Base Asset there is nothing to do, since the Actual Rate does not have an inpact on it.
For Quoted Asset, we need to first unaccount what this same orded added before to Size Placed (with the precious Actual Size) and
account with the new Actual Size.
*/
let previousStageQuotedAssetSizePlaced = portfolioEngineStage.stageQuotedAsset.sizePlaced.value
portfolioEngineStage.stageQuotedAsset.sizePlaced.value =
portfolioEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetActualSize
portfolioEngineStage.stageQuotedAsset.sizePlaced.value =
portfolioEngineStage.stageQuotedAsset.sizePlaced.value +
portfolioEngineOrder.orderQuotedAsset.actualSize.value
portfolioEngineStage.stageQuotedAsset.sizePlaced.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineStage.stageQuotedAsset.sizePlaced.value, 10)
const message = 'Simulating - Size Placed Recalculated'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Warning - ' + message,
placeholder: {}
}
contextInfo = {
previousStageQuotedAssetSizePlaced: previousStageQuotedAssetSizePlaced,
recalculatedStageQuotedAssetSizePlaced: portfolioEngineStage.stageQuotedAsset.sizePlaced.value,
actualRate: portfolioEngineOrder.orderStatistics.actualRate.value,
orderRate: portfolioEngineOrder.rate.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addWarning(
[
[portfolioEngineStage.stageQuotedAsset.sizePlaced.id, portfolioEngineOrder.orderStatistics.actualRate.id],
message,
docs
]
)
}
}
async function feesToBePaidSimulation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, applyFeePercentage) {
/*
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
*/
switch (portfolioEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
'feesToBePaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.taker,
100
)
break
}
case 'Limit Order': {
await applyFeePercentage(
'feesToBePaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.maker,
100
)
break
}
}
}
function percentageFilledSimulation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, applyFeePercentage) {
/*
When we are live, it is the exchange who knows which order was filled and in
which amount. Since we are backtesting here, we need to simulate what happens
at the exchange. We can not simulate that an order is filled if the candle we
see did not touch the order.
For limit buy orders we are going to check if the price was below the order actual rate, and
for limit sell orders we are going to check if the price was above the order actual rate.
If we have a partial filling defined specifically for this order,
we are going to use it to estimate how much of the order we will consider filled.
*/
let orderWasHit
switch (portfolioSystemOrder.type) {
case 'Limit Buy Order': {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.candle.min.value <= portfolioEngineOrder.orderStatistics.actualRate.value) {
orderWasHit = true
}
break
}
case 'Limit Sell Order': {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.candle.max.value >= portfolioEngineOrder.orderStatistics.actualRate.value) {
orderWasHit = true
}
break
}
case 'Market Buy Order': {
orderWasHit = true
break
}
case 'Market Sell Order': {
orderWasHit = true
break
}
}
if (orderWasHit === true) {
/*
Initially, we will assume that 100% of the order was filled. This might be overwritten
if there is a configuration for this order that details how much is must be considered
filled each time we check this.
*/
portfolioEngineOrder.orderStatistics.percentageFilled.value = 100
/* Based on the Portfolio System Definition */
if (portfolioSystemOrder.simulatedExchangeEvents !== undefined) {
if (portfolioSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) {
if (portfolioSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) {
/* Percentage Filled */
let percentageFilled = portfolioSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100
if (portfolioEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) {
percentageFilled = 100 - portfolioEngineOrder.orderStatistics.percentageFilled.value
}
portfolioEngineOrder.orderStatistics.percentageFilled.value = portfolioEngineOrder.orderStatistics.percentageFilled.value + percentageFilled
portfolioEngineOrder.orderStatistics.percentageFilled.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderStatistics.percentageFilled.value, 10)
}
}
}
}
}
function feesPaidSimulation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, applyFeePercentage) {
/*
The way the Fess Paid are calculated is as follows: From the order Actual Size we apply the % of fee,
that would give us the total fees for that order. To that we apply the % filled of the orders,
that give use the final result of the fees paid for the current % filled.
But before that, we will see if there is an specific definition on how to simulate the fees for
this order, or if there is not, we use the Fees Structure defined at the Session Parameters.
*/
/* Based on the Portfolio System Definition */
if (portfolioSystemOrder.simulatedExchangeEvents !== undefined) {
if (portfolioSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
if (portfolioSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
applyFeePercentage(
'feesPaid',
portfolioEngineOrder,
portfolioSystemOrder,
portfolioSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage,
portfolioEngineOrder.orderStatistics.percentageFilled.value
)
return
}
}
}
/*
Another way to simulate the Fees Paid is by using the Session Paremeters configuration for Fees.
*/
switch (portfolioEngineOrder.type) {
case 'Market Order': {
applyFeePercentage(
'feesPaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.taker,
portfolioEngineOrder.orderStatistics.percentageFilled.value
)
break
}
case 'Limit Order': {
applyFeePercentage(
'feesPaid',
portfolioEngineOrder,
portfolioSystemOrder,
sessionParameters.feeStructure.config.maker,
portfolioEngineOrder.orderStatistics.percentageFilled.value
)
break
}
}
}
function sizeFilledSimulation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, applyFeePercentage) {
/*
Size Filled is the amount of Base Asset or Quoted Asset that at the exchange matches
another counterparty order. We calculate this using the Percentage Filled we already simulated.
*/
portfolioEngineOrder.orderBaseAsset.sizeFilled.value =
portfolioEngineOrder.orderBaseAsset.actualSize.value *
portfolioEngineOrder.orderStatistics.percentageFilled.value / 100
portfolioEngineOrder.orderQuotedAsset.sizeFilled.value =
portfolioEngineOrder.orderQuotedAsset.actualSize.value *
portfolioEngineOrder.orderStatistics.percentageFilled.value / 100
portfolioEngineOrder.orderBaseAsset.sizeFilled.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderBaseAsset.sizeFilled.value, 10)
portfolioEngineOrder.orderQuotedAsset.sizeFilled.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
}
async function amountReceivedSimulation(portfolioEngineStage, portfolioSystemOrder, portfolioEngineOrder, order, applyFeePercentage) {
/*
This simulation is for informational purposes, so that users do not have to calculate
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
are selling then it will be in Quoted Asset.
*/
switch (true) {
case portfolioSystemOrder.type === 'Market Buy Order' || portfolioSystemOrder.type === 'Limit Buy Order': {
/*
In this case the Amount Received is in Base Asset.
*/
portfolioEngineOrder.orderBaseAsset.amountReceived.value =
portfolioEngineOrder.orderBaseAsset.sizeFilled.value -
portfolioEngineOrder.orderBaseAsset.feesPaid.value
break
}
case portfolioSystemOrder.type === 'Market Sell Order' || portfolioSystemOrder.type === 'Limit Sell Order': {
/*
In this case the Amount Received is in Quoted Asset.
*/
portfolioEngineOrder.orderQuotedAsset.amountReceived.value =
portfolioEngineOrder.orderQuotedAsset.sizeFilled.value -
portfolioEngineOrder.orderQuotedAsset.feesPaid.value
break
}
}
}
}
@@ -0,0 +1,229 @@
exports.newPortfolioManagementBotModulesPortfolioEngine = function (processIndex) {
/*
We call the Portfolio Engine to the data structure that is needed in order to exevute the
portfolio protocol with the specific rules defined at the Portfolio System.
*/
let thisObject = {
mantain: mantain,
reset: reset,
getNodeById: getNodeById,
cloneValues: cloneValues,
setCurrentCandle: setCurrentCandle,
setCurrentCycle: setCurrentCycle,
initializeNode: initializeNode,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let sessionParameters
let nodesMap = new Map()
return thisObject
function initialize() {
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
initializeNodeMap(portfolioEngine)
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_FIRST_LOOP === true) {
/*
Here we will go through all the nodes in the Portfolio Engine hiriarchy and
apply the initial value to the value property when needed
*/
initializeNode(portfolioEngine)
}
}
function finalize() {
portfolioEngine = undefined
sessionParameters = undefined
nodesMap = undefined
}
function mantain() {
}
function reset() {
}
function getNodeById(NodeId) {
return nodesMap.get(NodeId)
}
function setCurrentCandle(candle) {
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value = candle.begin
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value = candle.end
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.open.value = candle.open
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value = candle.close
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.min.value = candle.min
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.max.value = candle.max
}
function setCurrentCycle(cycle) {
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.value = cycle
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastBegin.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.begin.value
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastEnd.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.end.value
switch (cycle) {
case 'First': {
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.begin.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value +
sessionParameters.timeFrame.config.value
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.end.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value +
sessionParameters.timeFrame.config.value +
sessionParameters.timeFrame.config.value / 2 - 1
break
}
case 'Second': {
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.begin.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value +
sessionParameters.timeFrame.config.value +
sessionParameters.timeFrame.config.value / 2
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.end.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value +
sessionParameters.timeFrame.config.value +
sessionParameters.timeFrame.config.value * 4 / 4 - 1
break
}
}
/*
We can not have the last begin or last end to be zero, because that would prevent
objects starting with lastBegin in zero to being saved on file. For that reason
we will do this:
*/
if (portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastBegin.value === 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastBegin.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.begin.value
}
if (portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastEnd.value === 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastEnd.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.end.value
}
}
function cloneValues(originNode, destinationNode) {
if (originNode === undefined) { return }
if (destinationNode === undefined) { return }
/* Here copy the node value */
destinationNode.value = originNode.value
/* Now we go down through all the children of the origin node, assuming the destination node has the same children structure*/
let schemaDocument = TS.projects.foundations.globals.taskConstants.APP_SCHEMA_MAP.get(originNode.project + '-' + originNode.type)
if (schemaDocument === undefined) { return }
if (schemaDocument.childrenNodesProperties !== undefined) {
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
for (let i = 0; i < schemaDocument.childrenNodesProperties.length; i++) {
let property = schemaDocument.childrenNodesProperties[i]
switch (property.type) {
case 'node': {
if (property.name !== previousPropertyName) {
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
cloneValues(originNode[property.name], destinationNode[property.name])
}
previousPropertyName = property.name
}
break
}
case 'array': {
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
let originNodePropertyArray = originNode[property.name]
let destinationNodePropertyArray = destinationNode[property.name]
for (let m = 0; m < originNodePropertyArray.length; m++) {
cloneValues(originNodePropertyArray[m], destinationNodePropertyArray[m])
}
}
break
}
}
}
}
}
function initializeNodeMap(node) {
if (node === undefined) { return }
/* Add to Node Map */
nodesMap.set(node.id, node)
/* Now we go down through all this node children */
let schemaDocument = TS.projects.foundations.globals.taskConstants.APP_SCHEMA_MAP.get(node.project + '-' + node.type)
if (schemaDocument === undefined) { return }
if (schemaDocument.childrenNodesProperties !== undefined) {
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
for (let i = 0; i < schemaDocument.childrenNodesProperties.length; i++) {
let property = schemaDocument.childrenNodesProperties[i]
switch (property.type) {
case 'node': {
if (property.name !== previousPropertyName) {
if (node[property.name] !== undefined) {
initializeNodeMap(node[property.name])
}
previousPropertyName = property.name
}
break
}
case 'array': {
if (node[property.name] !== undefined) {
let nodePropertyArray = node[property.name]
for (let m = 0; m < nodePropertyArray.length; m++) {
initializeNodeMap(nodePropertyArray[m])
}
}
break
}
}
}
}
}
function initializeNode(node) {
if (node === undefined) { return }
/* Here we initialize the node value */
if (node.config !== undefined) {
if (node.config.initialValue !== undefined) {
node.value = node.config.initialValue
}
}
/* Now we go down through all this node children */
let schemaDocument = TS.projects.foundations.globals.taskConstants.APP_SCHEMA_MAP.get(node.project + '-' + node.type)
if (schemaDocument === undefined) { return }
if (schemaDocument.childrenNodesProperties !== undefined) {
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
for (let i = 0; i < schemaDocument.childrenNodesProperties.length; i++) {
let property = schemaDocument.childrenNodesProperties[i]
switch (property.type) {
case 'node': {
if (property.name !== previousPropertyName) {
if (node[property.name] !== undefined) {
initializeNode(node[property.name])
}
previousPropertyName = property.name
}
break
}
case 'array': {
if (node[property.name] !== undefined) {
let nodePropertyArray = node[property.name]
for (let m = 0; m < nodePropertyArray.length; m++) {
initializeNode(nodePropertyArray[m])
}
}
break
}
}
}
}
}
}
@@ -0,0 +1,319 @@
exports.newPortfolioManagementBotModulesPortfolioEpisode = function (processIndex) {
/*
This module packages all functions related to Episodes.
*/
let thisObject = {
mantain: mantain,
reset: reset,
openEpisode: openEpisode,
updateExitType: updateExitType,
closeEpisode: closeEpisode,
cycleBasedStatistics: cycleBasedStatistics,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let portfolioSystem
let sessionParameters
return thisObject
function initialize() {
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
}
function finalize() {
portfolioEngine = undefined
portfolioSystem = undefined
sessionParameters = undefined
}
function mantain() {
updateCounters()
updateStatistics()
updateEnds()
updateDistanceToPortfolioEventsCounters()
}
function reset() {
resetPortfolioEngineDataStructure()
}
function openEpisode() {
/*
This function is called each time the simulation starts. That does not mean the Episode
must be opened there, since it might happen that it is looping at the end of the market
or the task / session was restarted. We will only execute if the episode was never opened before.
*/
if (portfolioEngine.portfolioCurrent.portfolioEpisode.begin.value !== portfolioEngine.portfolioCurrent.portfolioEpisode.begin.config.initialValue) { return }
/* Starting begin and end */
portfolioEngine.portfolioCurrent.portfolioEpisode.begin.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value
portfolioEngine.portfolioCurrent.portfolioEpisode.end.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value
/* Getting the begin Balance from the session configuration */
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.beginBalance.value = sessionParameters.sessionBaseAsset.config.initialBalance
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.beginBalance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
/* The current balance is also the begin balance, that is how this starts. */
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value = sessionParameters.sessionBaseAsset.config.initialBalance
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
/* Recording the opening at the Portfolio Engine Data Structure */
portfolioEngine.portfolioCurrent.portfolioEpisode.status.value = 'Open'
portfolioEngine.portfolioCurrent.portfolioEpisode.serialNumber.value = 1
portfolioEngine.portfolioCurrent.portfolioEpisode.identifier.value = SA.projects.foundations.utilities.miscellaneousFunctions.genereteUniqueId()
portfolioEngine.portfolioCurrent.portfolioEpisode.beginRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
}
function updateExitType(exitType) {
portfolioEngine.portfolioCurrent.portfolioEpisode.exitType.value = exitType
}
function closeEpisode() {
portfolioEngine.portfolioCurrent.portfolioEpisode.status.value = 'Closed'
portfolioEngine.portfolioCurrent.portfolioEpisode.end.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value
portfolioEngine.portfolioCurrent.portfolioEpisode.endRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value
}
function resetEpisode() {
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.initializeNode(portfolioEngine.portfolioCurrent.learningEpisode)
}
function updateEnds() {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.status.value === 'Open') {
portfolioEngine.portfolioCurrent.portfolioEpisode.end.value = portfolioEngine.portfolioCurrent.portfolioEpisode.end.value + sessionParameters.timeFrame.config.value
portfolioEngine.portfolioCurrent.portfolioEpisode.endRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value
}
}
function resetPortfolioEngineDataStructure() {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.status.value === 'Closed') {
resetEpisode()
}
}
function updateCounters() {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.status.value === 'Open') {
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.periods.value++
}
}
function updateStatistics() {
/* Daus Calculation */
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.days.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.periods.value *
sessionParameters.timeFrame.config.value /
SA.projects.foundations.globals.timeConstants.ONE_DAY_IN_MILISECONDS
}
function cycleBasedStatistics() {
calculateAssetsStatistics()
calculateEpisodeStatistics()
function calculateAssetsStatistics() {
/* Updating Profit Loss */
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value -
sessionParameters.sessionBaseAsset.config.initialBalance
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value -
sessionParameters.sessionQuotedAsset.config.initialBalance
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value, 10)
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value, 10)
/*
Updating ROI
https://www.investopedia.com/articles/basics/10/guide-to-calculating-roi.asp
*/
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.ROI.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value /
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.beginBalance.value * 100
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.ROI.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value /
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.beginBalance.value * 100
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.ROI.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.ROI.value, 10)
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.ROI.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.ROI.value, 10)
/* Updating Hit Ratio */
if (portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.positions.value > 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.hitRatio.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.hits.value /
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.positions.value
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.hitRatio.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.hits.value /
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.positions.value
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.hitRatio.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.hitRatio.value, 10)
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.hitRatio.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.hitRatio.value, 10)
}
/*
Updating Annualized Rate Of Return
https://www.investopedia.com/terms/a/annualized-rate.asp
*/
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.annualizedRateOfReturn.value =
Math.pow(
(
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.beginBalance.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value
) / portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.beginBalance.value
,
(365 / portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.days.value)
) - 1
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.annualizedRateOfReturn.value =
Math.pow(
(
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.beginBalance.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value
) / portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.beginBalance.value
,
(365 / portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.days.value)
) - 1
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.annualizedRateOfReturn.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.annualizedRateOfReturn.value, 10)
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.annualizedRateOfReturn.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.annualizedRateOfReturn.value, 10)
/* Updating Hit or Fail */
if (portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value > 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.hitFail.value = 'Hit'
} else {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.hitFail.value = 'Fail'
}
if (portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value > 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.hitFail.value = 'Hit'
} else {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.hitFail.value = 'Fail'
}
}
function calculateEpisodeStatistics() {
/* Updating Profit Loss */
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.profitLoss.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value * portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.profitLoss.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.profitLoss.value, 10)
/*
Updating ROI
https://www.investopedia.com/articles/basics/10/guide-to-calculating-roi.asp
*/
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.ROI.value =
(
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value * portfolioEngine.portfolioCurrent.portfolioEpisode.endRate.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value
) / (
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.beginBalance.value * portfolioEngine.portfolioCurrent.portfolioEpisode.beginRate.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.beginBalance.value
) * 100
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.ROI.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.ROI.value, 10)
/*
Updating Annualized Rate Of Return
https://www.investopedia.com/terms/a/annualized-rate.asp
*/
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.annualizedRateOfReturn.value =
Math.pow(
(
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.beginBalance.value * portfolioEngine.portfolioCurrent.portfolioEpisode.beginRate.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.beginBalance.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value
) /
(
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.beginBalance.value * portfolioEngine.portfolioCurrent.portfolioEpisode.beginRate.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.beginBalance.value
)
,
(365 / portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.days.value)
) - 1
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.annualizedRateOfReturn.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.annualizedRateOfReturn.value, 10)
/* Updating Hit or Fail */
if (portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.profitLoss.value > 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.hitFail.value = 'Hit'
} else {
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeStatistics.hitFail.value = 'Fail'
}
}
}
function updateDistanceToPortfolioEventsCounters() {
/* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.triggerOn.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.triggerOn.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.triggerOff.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.triggerOff.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.takePosition.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.takePosition.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.closePosition.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.closePosition.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.nextPhase.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.nextPhase.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.moveToPhase.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.moveToPhase.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.createOrder.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.createOrder.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.cancelOrder.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.cancelOrder.value++
}
if (
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.closeOrder.value > 0
) {
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.closeOrder.value++
}
}
}
@@ -0,0 +1,123 @@
exports.newPortfolioManagementBotModulesPortfolioExecution = function (processIndex) {
/*
The Portfolio Execution modules manages the execution nodes and execution algoritms.
*/
const MODULE_NAME = 'Portfolio Execution'
let thisObject = {
mantain: mantain,
reset: reset,
runExecution: runExecution,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let portfolioSystem
let sessionParameters
let portfolioOrdersModuleObject = TS.projects.portfolioManagement.botModules.portfolioOrders.newPortfolioManagementBotModulesPortfolioOrders(processIndex)
return thisObject
function initialize() {
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
portfolioOrdersModuleObject.initialize()
}
function finalize() {
portfolioSystem = undefined
portfolioEngine = undefined
sessionParameters = undefined
portfolioOrdersModuleObject.finalize()
portfolioOrdersModuleObject = undefined
}
function mantain() {
portfolioOrdersModuleObject.mantain()
}
function reset() {
portfolioOrdersModuleObject.reset()
}
async function runExecution(
executionNode,
portfolioEngineStage
) {
try {
/* Portfolio Engine Stage Validations */
if (portfolioEngineStage.stageBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'Stage Base Asset Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageBaseAsset.targetSize === undefined) { badDefinitionUnhandledException(undefined, 'Target Size Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageBaseAsset.sizePlaced === undefined) { badDefinitionUnhandledException(undefined, 'Size Placed Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'Size Filled Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'Fees Paid Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageQuotedAsset === undefined) { badDefinitionUnhandledException(undefined, 'Stage Quoted Asset Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageQuotedAsset.targetSize === undefined) { badDefinitionUnhandledException(undefined, 'Target Size Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageQuotedAsset.sizePlaced === undefined) { badDefinitionUnhandledException(undefined, 'Size Placed Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'Size Filled Node Missing', portfolioEngineStage) }
if (portfolioEngineStage.stageQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'Fees Paid Node Missing', portfolioEngineStage) }
await checkExecutionAlgorithms(executionNode)
async function checkExecutionAlgorithms(executionNode) {
for (let i = 0; i < executionNode.executionAlgorithms.length; i++) {
let executionAlgorithm = executionNode.executionAlgorithms[i]
await portfolioOrdersModuleObject.checkOrders(portfolioEngineStage, executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode)
await portfolioOrdersModuleObject.checkOrders(portfolioEngineStage, executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode)
await portfolioOrdersModuleObject.checkOrders(portfolioEngineStage, executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode)
await portfolioOrdersModuleObject.checkOrders(portfolioEngineStage, executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode)
}
}
} catch (err) {
if (err !== 'Error Already Recorded') {
const message = 'Execution Unexpected Error'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err)
portfolioSystem.addError([executionNode.id, message, docs])
}
if (typeof err === 'string' || err instanceof String) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err)
}
if (err.stack !== undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err.stack)
}
}
}
function badDefinitionUnhandledException(err, message, node) {
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
portfolioSystem.addError([node.id, message, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> " + message);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config));
if (err !== undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack);
}
throw 'Error Already Recorded'
}
}
@@ -0,0 +1,77 @@
exports.newPortfolioManagementBotModulesPortfolioOutput = function (processIndex) {
/*
This module will load if necesary all the data outputs so that they can be appended with new
records if needed. After running the simulation, it will save all the data outputs.
*/
const MODULE_NAME = 'Portfolio Output'
let thisObject = {
start: start
}
return thisObject
async function start(
chart,
market,
exchange,
timeFrame,
timeFrameLabel,
portfolioProcessDate
) {
try {
let fileStorage = TS.projects.foundations.taskModules.fileStorage.newFileStorage(processIndex)
if (timeFrame > TS.projects.foundations.globals.timeFrames.dailyTimeFramesArray()[0][0]) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES = false
} else {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES = true
}
/* Preparing everything for the Simulation */
let portfolioSimulationModuleObject = TS.projects.portfolioManagement.botModules.portfolioSimulation.newPortfolioManagementBotModulesPortfolioSimulation(processIndex)
let outputDatasets = TS.projects.visualScripting.utilities.nodeFunctions.nodeBranchToArray(TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.processOutput, 'Output Dataset')
let outputDatasetsMap = new Map()
await TS.projects.foundations.functionLibraries.outputManagementFunctions.readFiles(
processIndex,
outputDatasets,
outputDatasetsMap,
timeFrameLabel,
portfolioProcessDate,
fileStorage,
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine.portfolioCurrent.portfolioEpisode.headOfTheMarket.value
)
await portfolioSimulationModuleObject.runSimulation(
chart,
market,
exchange,
outputDatasetsMap,
writeOutputFiles
)
portfolioSimulationModuleObject.finalize()
return
function writeOutputFiles() {
TS.projects.foundations.functionLibraries.outputManagementFunctions.writeFiles(
processIndex,
outputDatasets,
outputDatasetsMap,
timeFrameLabel,
portfolioProcessDate,
fileStorage
)
}
} catch (err) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[ERROR] start -> err = ' + err.stack)
throw (TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE)
}
}
}
@@ -0,0 +1,534 @@
exports.newPortfolioManagementBotModulesPortfolioPosition = function (processIndex) {
/*
This module packages all functions related to Positions.
*/
const MODULE_NAME = 'Portfolio Position'
let thisObject = {
mantain: mantain,
reset: reset,
cycleBasedStatistics: cycleBasedStatistics,
openPosition: openPosition,
closingPosition: closingPosition,
closePosition: closePosition,
applyStopLossFormula: applyStopLossFormula,
applyTakeProfitFormula: applyTakeProfitFormula,
updateStopLoss: updateStopLoss,
updateTakeProfit: updateTakeProfit,
initialTargets: initialTargets,
updateEnds: updateEnds,
resetPortfolioEngineDataStructure: resetPortfolioEngineDataStructure,
updateCounters: updateCounters,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let portfolioSystem
let sessionParameters
return thisObject
function initialize() {
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
}
function finalize() {
portfolioEngine = undefined
portfolioSystem = undefined
sessionParameters = undefined
}
function mantain() {
updateCounters()
updateEnds()
}
function reset() {
resetPortfolioEngineDataStructure()
}
function openPosition(situationName) {
/* Starting begin and end */
portfolioEngine.portfolioCurrent.position.begin.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastBegin.value
portfolioEngine.portfolioCurrent.position.end.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastEnd.value
/* Recording the opening at the Portfolio Engine Data Structure */
portfolioEngine.portfolioCurrent.position.status.value = 'Open'
portfolioEngine.portfolioCurrent.position.serialNumber.value = portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.positions.value + 1
portfolioEngine.portfolioCurrent.position.identifier.value = SA.projects.foundations.utilities.miscellaneousFunctions.genereteUniqueId()
portfolioEngine.portfolioCurrent.position.beginRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
portfolioEngine.portfolioCurrent.position.positionBaseAsset.beginBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.beginBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value
portfolioEngine.portfolioCurrent.position.situationName.value = situationName
/* Initializing Stop and Take Phase */
portfolioEngine.portfolioCurrent.position.stopLoss.stopLossPhase.value = 1
portfolioEngine.portfolioCurrent.position.takeProfit.takeProfitPhase.value = 1
/* Updating Episode Counters */
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.positions.value++
/* Inicializing this counter */
portfolioEngine.portfolioCurrent.portfolioEpisode.distanceToPortfolioEvent.takePosition.value = 1
/* Remember the balance we had before taking the position to later calculate profit or loss */
portfolioEngine.portfolioCurrent.position.positionBaseAsset.beginBalance = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.beginBalance = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value
}
function closingPosition(exitType) {
portfolioEngine.portfolioCurrent.position.status.value = 'Closing'
portfolioEngine.portfolioCurrent.position.exitType.value = exitType
/*
By the time we figured out that the stop loss or take profit were hit, or
for whatever reason the position needs to be closed, the take profit and
stop loss values for the next candle have already been calculated. In order
to avoid being plotted, we will put them in zero.
*/
portfolioEngine.portfolioCurrent.position.stopLoss.value = 0
portfolioEngine.portfolioCurrent.position.takeProfit.value = 0
}
function closePosition() {
portfolioEngine.portfolioCurrent.position.status.value = 'Closed'
portfolioEngine.portfolioCurrent.position.end.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastEnd.value
portfolioEngine.portfolioCurrent.position.endRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
portfolioEngine.portfolioCurrent.position.positionBaseAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value
/*
Now that the position is closed, it is the right time to move this position from current to last at the Portfolio Engine data structure.
*/
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.cloneValues(portfolioEngine.portfolioCurrent.position, portfolioEngine.portfolioLast.position)
cycleBasedStatistics()
/* Updating Hits & Fails */
if (portfolioEngine.portfolioCurrent.position.positionBaseAsset.profitLoss.value > 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.hits.value++
}
if (portfolioEngine.portfolioCurrent.position.positionBaseAsset.profitLoss.value < 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.fails.value++
}
if (portfolioEngine.portfolioCurrent.position.positionQuotedAsset.profitLoss.value > 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.hits.value++
}
if (portfolioEngine.portfolioCurrent.position.positionQuotedAsset.profitLoss.value < 0) {
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.fails.value++
}
}
function applyStopLossFormula(formulas, formulaId) {
updateStopLossTakeProfitFinalValue(portfolioEngine.portfolioCurrent.position.stopLoss)
portfolioEngine.portfolioCurrent.position.stopLoss.value = formulas.get(formulaId)
updateStopLossTakeProfitInitialValue(portfolioEngine.portfolioCurrent.position.stopLoss)
updateStopLossTakeProfitBeginEnd(portfolioEngine.portfolioCurrent.position.stopLoss)
}
function applyTakeProfitFormula(formulas, formulaId) {
updateStopLossTakeProfitFinalValue(portfolioEngine.portfolioCurrent.position.takeProfit)
portfolioEngine.portfolioCurrent.position.takeProfit.value = formulas.get(formulaId)
updateStopLossTakeProfitInitialValue(portfolioEngine.portfolioCurrent.position.takeProfit)
updateStopLossTakeProfitBeginEnd(portfolioEngine.portfolioCurrent.position.takeProfit)
}
function updateStopLossTakeProfitInitialValue(node) {
/*
We store the first Stop Loss or Take Profit value in a separate node.
To know if it is the first one we compare its current value to the initial value.
*/
if (node.initialValue.value === node.initialValue.config.initialValue) {
node.initialValue.value = node.value
}
}
function updateStopLossTakeProfitFinalValue(node) {
/*
We set the final value just before calculating the new value, since the new calculated
value is going to be discarded if the position is closed after it is calculated, but
during the same candle.
*/
node.finalValue.value = node.value
}
function updateStopLossTakeProfitBeginEnd(node) {
/*
Both the Stop Loss and the Take Profit have their own Begin and End.
The reason for this is because they represent targets to be checked
at the next candle, so it does not apply that they share the begin and
end of the position itself.
*/
node.begin.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value +
sessionParameters.timeFrame.config.value
node.end.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value +
sessionParameters.timeFrame.config.value
}
function updateStopLoss(phase) {
portfolioEngine.portfolioCurrent.position.stopLoss.stopLossPhase.value = phase
}
function updateTakeProfit(phase) {
portfolioEngine.portfolioCurrent.position.takeProfit.takeProfitPhase.value = phase
}
function initialTargets(portfolioSystemStageNode, portfolioEngineStageNode, stageName) {
if (portfolioSystemStageNode.initialTargets === undefined) {
const message = 'Initial Targets Node Missing'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode, docs)
}
setTargetRate()
return setTargetSize(stageName)
function setTargetRate() {
if (portfolioSystemStageNode.initialTargets.targetRate === undefined) {
const message = 'Target Rate Node Missing'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets, docs)
}
if (portfolioSystemStageNode.initialTargets.targetRate.formula === undefined) {
const message = 'Formula Node Missing'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetRate, docs)
}
let value = portfolioSystem.formulas.get(portfolioSystemStageNode.initialTargets.targetRate.formula.id)
if (value === undefined) {
const message = 'Target Rate Node Value Undefined'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetRate, docs)
}
if (isNaN(value)) {
const message = 'Target Rate Node Value Not A Number'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, value, undefined)
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetRate, docs)
}
switch (portfolioSystemStageNode.type) {
case 'Open Stage': {
portfolioEngine.portfolioCurrent.position.entryTargetRate.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value, 10)
break
}
case 'Close Stage': {
portfolioEngine.portfolioCurrent.position.exitTargetRate.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value, 10)
break
}
}
}
function setTargetSize(stageName) {
/* Basic Validation */
if (
portfolioSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined &&
portfolioSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined
) {
// 'Only Target Size In Base Asset or Target Size In Quoted Asset is allowed.'
const message = 'Only One Target Size Allowed'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets, docs)
}
/* Position In Base Asset */
if (portfolioSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined) {
if (portfolioSystemStageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) {
let value = portfolioSystem.formulas.get(portfolioSystemStageNode.initialTargets.targetSizeInBaseAsset.formula.id)
if (value === undefined) {
const message = 'Target Size Value Undefined'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetSizeInBaseAsset, docs)
}
if (value === 0) {
if (stageName === 'Close Stage') {
return false;
}
const message = 'Target Size Value Zero'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetSizeInBaseAsset, docs)
}
switch (portfolioSystemStageNode.type) {
case 'Open Stage': {
portfolioEngine.portfolioCurrent.position.positionBaseAsset.entryTargetSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value, 10)
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.entryTargetSize.value =
TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value * portfolioEngine.portfolioCurrent.position.entryTargetRate.value, 10)
break
}
case 'Close Stage': {
portfolioEngine.portfolioCurrent.position.positionBaseAsset.exitTargetSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value, 10)
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.exitTargetSize.value =
TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value * portfolioEngine.portfolioCurrent.position.exitTargetRate.value, 10)
break
}
}
/* Remember how the end user defined this stage. */
portfolioEngineStageNode.stageDefinedIn.value = 'Base Asset'
} else {
const message = 'Formula Node Missing'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetSizeInBaseAsset, docs)
}
}
/* Position In Quoted Asset */
if (portfolioSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined) {
if (portfolioSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) {
let value = portfolioSystem.formulas.get(portfolioSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula.id)
if (value === undefined) {
const message = 'Target Size Value Undefined'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetSizeInQuotedAsset, docs)
}
if (value === 0) {
const message = 'Target Size Value Zero'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetSizeInQuotedAsset, docs)
}
switch (portfolioSystemStageNode.type) {
case 'Open Stage': {
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.entryTargetSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value, 10)
portfolioEngine.portfolioCurrent.position.positionBaseAsset.entryTargetSize.value =
TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value / portfolioEngine.portfolioCurrent.position.entryTargetRate.value, 10)
break
}
case 'Close Stage': {
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.exitTargetSize.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value, 10)
portfolioEngine.portfolioCurrent.position.positionBaseAsset.exitTargetSize.value =
TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(value / portfolioEngine.portfolioCurrent.position.exitTargetRate.value, 10)
break
}
}
/* Remember how the end user defined this stage. */
portfolioEngineStageNode.stageDefinedIn.value = 'Quoted Asset'
} else {
const message = 'Formula Node Missing'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
badDefinitionUnhandledException(undefined, message, portfolioSystemStageNode.initialTargets.targetSizeInQuotedAsset, docs)
}
}
}
}
function updateEnds() {
if (portfolioEngine.portfolioCurrent.position.status.value === 'Open') {
portfolioEngine.portfolioCurrent.position.end.value = portfolioEngine.portfolioCurrent.position.end.value + sessionParameters.timeFrame.config.value
portfolioEngine.portfolioCurrent.position.endRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
portfolioEngine.portfolioCurrent.position.positionBaseAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.endBalance.value = portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value
}
}
function resetPortfolioEngineDataStructure() {
if (portfolioEngine.portfolioCurrent.position.status.value === 'Closed') {
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.initializeNode(portfolioEngine.portfolioCurrent.position)
}
}
function updateCounters() {
if (portfolioEngine.portfolioCurrent.position.status.value === 'Open') {
portfolioEngine.portfolioCurrent.position.positionCounters.periods.value++
}
}
function cycleBasedStatistics() {
calculateAssetsStatistics()
calculatePositionStatistics()
function calculateAssetsStatistics() {
/* Profit Loss Calculation */
portfolioEngine.portfolioCurrent.position.positionBaseAsset.profitLoss.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value -
portfolioEngine.portfolioCurrent.position.positionBaseAsset.beginBalance
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.profitLoss.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value -
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.beginBalance
portfolioEngine.portfolioCurrent.position.positionBaseAsset.profitLoss.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.position.positionBaseAsset.profitLoss.value, 10)
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.profitLoss.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.position.positionQuotedAsset.profitLoss.value, 10)
/* ROI Calculation */
portfolioEngine.portfolioCurrent.position.positionBaseAsset.ROI.value =
portfolioEngine.portfolioCurrent.position.positionBaseAsset.profitLoss.value * 100 /
portfolioEngine.portfolioCurrent.strategyOpenStage.stageBaseAsset.targetSize.value
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.ROI.value =
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.profitLoss.value * 100 /
portfolioEngine.portfolioCurrent.strategyOpenStage.stageQuotedAsset.targetSize.value
portfolioEngine.portfolioCurrent.position.positionBaseAsset.ROI.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.position.positionBaseAsset.ROI.value, 10)
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.ROI.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.position.positionQuotedAsset.ROI.value, 10)
/* Hit Fail Calculation */
if (portfolioEngine.portfolioCurrent.position.positionBaseAsset.ROI.value > 0) {
portfolioEngine.portfolioCurrent.position.positionBaseAsset.hitFail.value = 'Hit'
}
if (portfolioEngine.portfolioCurrent.position.positionBaseAsset.ROI.value < 0) {
portfolioEngine.portfolioCurrent.position.positionBaseAsset.hitFail.value = 'Fail'
}
if (portfolioEngine.portfolioCurrent.position.positionBaseAsset.ROI.value === 0) {
portfolioEngine.portfolioCurrent.position.positionBaseAsset.hitFail.value = 'Even'
}
if (portfolioEngine.portfolioCurrent.position.positionQuotedAsset.ROI.value > 0) {
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.hitFail.value = 'Hit'
}
if (portfolioEngine.portfolioCurrent.position.positionQuotedAsset.ROI.value < 0) {
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.hitFail.value = 'Fail'
}
if (portfolioEngine.portfolioCurrent.position.positionQuotedAsset.ROI.value === 0) {
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.hitFail.value = 'Even'
}
}
function calculatePositionStatistics() {
/* Profit Loss Calculation */
portfolioEngine.portfolioCurrent.position.positionStatistics.profitLoss.value =
(
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.profitLoss.value * portfolioEngine.portfolioCurrent.position.endRate.value +
portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.profitLoss.value
)
portfolioEngine.portfolioCurrent.position.positionStatistics.profitLoss.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.position.positionStatistics.profitLoss.value, 10)
/* ROI Calculation */
portfolioEngine.portfolioCurrent.position.positionStatistics.ROI.value =
(
portfolioEngine.portfolioCurrent.position.positionStatistics.profitLoss.value
)
* 100 /
(
portfolioEngine.portfolioCurrent.position.positionBaseAsset.beginBalance * portfolioEngine.portfolioCurrent.position.beginRate.value +
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.beginBalance
)
portfolioEngine.portfolioCurrent.position.positionStatistics.ROI.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.position.positionStatistics.ROI.value, 10)
/* Hit Fail Calculation */
if (portfolioEngine.portfolioCurrent.position.positionStatistics.ROI.value > 0) {
portfolioEngine.portfolioCurrent.position.positionStatistics.hitFail.value = 'Hit'
}
if (portfolioEngine.portfolioCurrent.position.positionStatistics.ROI.value < 0) {
portfolioEngine.portfolioCurrent.position.positionStatistics.hitFail.value = 'Fail'
}
if (portfolioEngine.portfolioCurrent.position.positionStatistics.ROI.value === 0) {
portfolioEngine.portfolioCurrent.position.positionStatistics.hitFail.value = 'Even'
}
/* Days Calculation */
portfolioEngine.portfolioCurrent.position.positionStatistics.days.value =
portfolioEngine.portfolioCurrent.position.positionCounters.periods.value *
sessionParameters.timeFrame.config.value / SA.projects.foundations.globals.timeConstants.ONE_DAY_IN_MILISECONDS
portfolioEngine.portfolioCurrent.position.positionStatistics.days.value = TS.projects.foundations.utilities.miscellaneousFunctions.truncateToThisPrecision(portfolioEngine.portfolioCurrent.position.positionStatistics.days.value, 10)
}
}
function badDefinitionUnhandledException(err, message, node, docs) {
portfolioSystem.addError([node.id, message, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> " + message);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config));
if (err !== undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack);
}
throw 'Error Already Recorded'
}
}
@@ -0,0 +1,320 @@
exports.newPortfolioManagementBotModulesPortfolioProcess = function (processIndex) {
/*
This Module will load all the process data dependencies from files and send them downstream.
After execution, will save the time range and status report of the process.
*/
const MODULE_NAME = "Portfolio Process"
let thisObject = {
start: start
}
return thisObject
async function start(statusDependencies, dataDependenciesModule, callBackFunction) {
try {
let dataFiles = new Map()
let multiTimeFrameDataFiles = new Map()
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT = TS.projects.portfolioManagement.botModules.portfolioEngine.newPortfolioManagementBotModulesPortfolioEngine(processIndex)
let portfolioOutputModuleObject = TS.projects.portfolioManagement.botModules.portfolioOutput.newPortfolioManagementBotModulesPortfolioOutput(processIndex)
let currentTimeFrame = {}
/* Context Variables */
let contextVariables = {
lastFile: undefined, // Datetime of the last file files successfully produced by this process.
dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history.
dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process.
}
if (
TS.projects.foundations.functionLibraries.processFilesFunctions.getContextVariables(
processIndex,
contextVariables,
statusDependencies,
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime,
callBackFunction
) !== true) { return }
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_FIRST_LOOP === true && TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_RESUMING === false) {
/*
Here is where the Portfolio Engine and Portfolio Systems received are moved to the simulation state.
*/
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_NODE
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_SYSTEM_NODE
}
/* We set up the Portfolio Engine Module. */
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.initialize()
/* Initializing the Portfolio Process Date */
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_FIRST_LOOP === true && TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_RESUMING === false) {
/*
This function is going to be called many times by the Portfolio Bot Loop.
Only during the first execution and when the User is not resuming the execution
of a stopped session / task; we are going to initialize the Process Date Time.
This variable tell us which day we are standing at, specially while working
with Daily Files. From this Date is that we are going to load the Daily Files.
*/
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine.portfolioCurrent.portfolioEpisode.processDate.value =
TS.projects.foundations.utilities.dateTimeFunctions.removeTime(TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime).valueOf()
}
/*
This is the Date that is going to be used across the execution of this Portfolio Process.
We need this because it has a different life cycle than the processData stored at the
Portfolio Engine data structure. This date has to remain the same during the whole execution
of the Portfolio Process until the end, inclusind the writing of Data Ranges and Status Reports.
The processDate of the Portfolio Engine data structure on the other hand can be changed during
the simulation loop, once we discover that all candles from a certain date have benn processed.
Here is the point where we sync one and the other.
*/
let portfolioProcessDate = TS.projects.foundations.utilities.dateTimeFunctions.removeTime(TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine.portfolioCurrent.portfolioEpisode.processDate.value)
if (
await TS.projects.foundations.functionLibraries.dataDependenciesFunctions.processSingleFiles(
processIndex,
dataFiles,
multiTimeFrameDataFiles,
dataDependenciesModule
) === false) {
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Waiting for Data Mining to be run')
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
return
}
if (await TS.projects.foundations.functionLibraries.dataDependenciesFunctions.processMarketFiles(
processIndex,
dataFiles,
multiTimeFrameDataFiles,
dataDependenciesModule,
currentTimeFrame,
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.label,
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime,
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.finalDatetime
) === false) {
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Waiting for Data Mining to be run')
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
return
}
/*
These are the Data Structures used by end users at Conditions Code or Formulas.
*/
let chart = {}
let market = {}
let exchange = {}
/*
Here we check if we need to get Daily Files or not. As an optimization, when
we are running on a Time Frame of 1hs or above, we are not going to load
dependencies on Daily Files. The way we recognize that is by checking if
we alreaady set a value to currentTimeFrame.value. We are also not going to loop
through days if we are processing market files.
*/
if (currentTimeFrame.value) {
/* We are processing Market Files */
/*
With all the indicators data files loaded, we will build the chart object
data structure that will be used in user-defied conditions and formulas.
*/
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Waking up')
TS.projects.foundations.functionLibraries.dataDependenciesFunctions.buildDataStructures(
processIndex,
dataDependenciesModule,
multiTimeFrameDataFiles,
currentTimeFrame,
chart,
market,
exchange,
callBackFunction
)
if (checkThereAreCandles() === true) {
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Running')
await outputManagement()
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Saving')
await TS.projects.foundations.functionLibraries.processFilesFunctions.writeProcessFiles(processIndex, contextVariables, currentTimeFrame, portfolioProcessDate, statusDependencies)
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Sleeping')
} else {
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Waiting for Data Mining to be up to date. No candles found.')
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
return
}
} else {
/* We are processing Daily Files */
do {
TS.projects.foundations.functionLibraries.sessionFunctions.emitSessionStatus(TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS, TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY)
/*
We update the Portfolio Process Date with the date calculated at the simulation.
We will use this date to load indicator and output files. After that we will
use it to save Output Files and later the Data Ranges. This is the point where
the date calculated by the Simulation is applied at the Portfolio Process Level.
*/
portfolioProcessDate = TS.projects.foundations.utilities.dateTimeFunctions.removeTime(TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine.portfolioCurrent.portfolioEpisode.processDate.value)
if (checkStopTaskGracefully() === false) { break }
if (checkStopProcessing() === false) { break }
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Waking up')
if (
await TS.projects.foundations.functionLibraries.dataDependenciesFunctions.processDailyFiles(
processIndex,
dataFiles,
multiTimeFrameDataFiles,
dataDependenciesModule,
currentTimeFrame,
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.label,
portfolioProcessDate
) === false) {
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Waiting for Data Mining to be run')
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
return
}
/*
With all the indicators data files loaded, we will build the chart object
data structure that will be used in user-defied conditions and formulas.
*/
TS.projects.foundations.functionLibraries.dataDependenciesFunctions.buildDataStructures(
processIndex,
dataDependenciesModule,
multiTimeFrameDataFiles,
currentTimeFrame,
chart,
market,
exchange,
callBackFunction
)
/*
The process of generating the output includes the portfolio simulation.
*/
if (checkThereAreCandles() === true) {
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Running')
await outputManagement()
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Saving')
await TS.projects.foundations.functionLibraries.processFilesFunctions.writeProcessFiles(processIndex, contextVariables, currentTimeFrame, portfolioProcessDate, statusDependencies)
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Sleeping')
} else {
TS.projects.foundations.functionLibraries.sessionFunctions.sessionHeartBeat(processIndex, undefined, undefined, 'Waiting for Data Mining to be up to date')
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
return
}
/*
If for any reason the session was stopped, we will break this loop and exit the process.
*/
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING === true) { break }
/*
When we get to the end of the market, we need to break this process loop in order
to let time pass, new information be collected from the exchange, new data built
into indicators, and eventually a new execution of this process.
*/
if (checkStopHeadOfTheMarket() === false) { break }
}
while (true)
}
checkIfSessionMustStop()
/*
Everything worked as expected. We return an OK code and wait for
the Bot Loop to call us again later.
*/
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE)
function checkThereAreCandles() {
let sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
let candles = chart[propertyName].candles
if (candles === undefined || candles.length === 0) {
return false
} else {
return true
}
}
function checkStopHeadOfTheMarket() {
/*
We need to check if we have reached the head of the market in order to know
when to break the Daily Files Process loop and give time for a new candles /
indicators to be built and continue the processing once this process is called
again.
*/
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine.portfolioCurrent.portfolioEpisode.headOfTheMarket.value === true) {
return false
}
}
function checkStopTaskGracefully() {
/* Validation that we dont need to stop. */
if (TS.projects.foundations.globals.taskVariables.IS_TASK_STOPPING === true) {
return false
}
}
function checkStopProcessing() {
/* Validation that we dont need to stop. */
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING === true) {
return false
}
}
async function outputManagement() {
await portfolioOutputModuleObject.start(
chart,
market,
exchange,
currentTimeFrame.value,
currentTimeFrame.label,
portfolioProcessDate
)
/*
From here on, all other loops executions wont be the first execution and also
we will consider that it is not resuming a previous execution as well.
*/
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_FIRST_LOOP = false
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_RESUMING = false
}
function checkIfSessionMustStop() {
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type === 'Backtesting Session') {
/*
Backtests needs only one execution of this process to complete.
*/
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[IMPORTANT] checkIfSessionMustStop -> Backtesting Session Finished. Stopping the Session now. ')
TS.projects.foundations.functionLibraries.sessionFunctions.stopSession(processIndex, 'Backtesting Session Finished.')
}
}
}
catch (err) {
/* An unhandled exception occured. in this case we return Fail and log the stack. */
if (err.stack) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] start -> Unhandled Exception. Will Abort this process. err = " + err.stack)
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE)
return
}
/* Some expected file was not found. We will return a RETRY code and move on. */
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] File not Found. Will Retry the Process Loop.")
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
return
}
/* Some other handled exception occured. We return Fail and move on. */
if (err.result !== TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] start -> Handled Exception. Will Abort this process. err = " + err.message)
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE)
return
}
}
}
}
@@ -0,0 +1,414 @@
exports.newPortfolioManagementBotModulesPortfolioRecords = function (processIndex) {
/*
This module facilitates the appending of records to the output of the process.
*/
const MODULE_NAME = 'Portfolio Records'
let thisObject = {
appendRecords: appendRecords,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let portfolioSystem
let sessionParameters
let outputDatasetsMap
return thisObject
function initialize(pOutputDatasetsMap) {
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
outputDatasetsMap = pOutputDatasetsMap // These are the files turned into arrays, stored in a Map by Product codeName.
}
function finalize() {
portfolioEngine = undefined
portfolioSystem = undefined
sessionParameters = undefined
outputDatasetsMap = undefined
}
function appendRecords() {
/*
Here we add records to the output files. At the product config property nodePath
we have a pointer to the node that have the information we need to extract.
Later, based on the product record definition we will extract each individual value.
*/
let outputDatasets = TS.projects.visualScripting.utilities.nodeFunctions.nodeBranchToArray(TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.processOutput, 'Output Dataset')
for (let i = 0; i < outputDatasets.length; i++) {
let outputDatasetNode = outputDatasets[i]
let dataset = outputDatasetNode.referenceParent
let product = dataset.parentNode
let outputDatasetArray = outputDatasetsMap.get(product.config.codeName)
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES === true && dataset.config.type === 'Daily Files') {
persistRecords()
}
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES === false && dataset.config.type === 'Market Files') {
persistRecords()
}
function persistRecords() {
/*
There are a few products configured to be saved only at an specific cycle.
*/
if (product.config.saveAtCycle !== undefined) {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.value !== product.config.saveAtCycle) {
return
}
}
/* Clean the file from information of previous executions */
//pruneOutputFile(product, outputDatasetArray, portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value)
/* Clean Open Records */
if (product.config.saveAsObjects === true) {
for (let j = 0; j < product.record.properties.length; j++) {
let recordProperty = product.record.properties[j]
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) {
/* Remove Open Records */
spliceOpenRecords(j, product, outputDatasetArray)
break
}
}
}
/*
The product root can be a node or a node property of type array.
*/
let productRoot
try {
productRoot = eval(product.config.nodePath)
} catch (err) {
badDefinitionUnhandledException(err, 'Cannot get productRoot -> nodePath = ' + product.config.nodePath, product, undefined)
}
if (product.config.nodePathType === 'array') {
/*
This means that the configured nodePath is not pointing to a node, but to a node property that is an array.
For that reason we will assume that each element of the array is a record to be outputed
*/
for (let index = 0; index < productRoot.length; index++) {
/*
The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values.
*/
let productRootNode = productRoot[index]
let record = scanRecordDefinition(product, productRootNode, index)
if (record !== undefined) {
/*
We will add the index value to the record itself, so that the plotter can know to which
brach of the portfolio engine data structure it belongs.
*/
record.push(index)
persistIndividualRecord(record, product, outputDatasetArray)
}
}
} else {
/*
This means that the configured nodePath points to a single node, which is the one whose children constitutes
the record to be saved at the output file.
*/
/*
The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values.
*/
let productRootNode = productRoot
let record = scanRecordDefinition(product, productRootNode)
if (record !== undefined) {
persistIndividualRecord(record, product, outputDatasetArray)
}
}
}
}
function scanRecordDefinition(product, productRootNode, index) {
let record = []
for (let j = 0; j < product.record.properties.length; j++) {
let recordProperty = product.record.properties[j]
/*
The Property Root Node is the Root of the Hiriarchy branch we must find in order
to get the node where we are going to extract the value. Initially
we point it to the Product Root Node, because that is the default in case
a property does not have at its configuration a different nodePath configured
pointing to an specific Root for the property.
*/
let propertyRootNode = productRootNode
/*
If we find at the configuration a nodePath, then we take this path to find
the Root Node specifically for this property only.
*/
if (recordProperty.config.nodePath !== undefined) {
try {
propertyRootNode = eval(recordProperty.config.nodePath)
} catch (err) {
badDefinitionUnhandledException(err, 'Error Evaluating Record Property nodePath -> nodePath = ' + recordProperty.config.nodePath, product, recordProperty)
}
}
/*
The Target Node is the node from where we are going to exctract the value.
We will use the codeName of the Record Property to match it with
any of the properties of the Root Node to get the Target Node.
*/
let targetNode
try {
targetNode = propertyRootNode[recordProperty.config.codeName]
} catch (err) {
badDefinitionUnhandledException(err, 'Error setting Target Node.', product, recordProperty)
}
/*
If the codeName of the Record Property can not match the name of the property at
the target node, the user can explicitly specify the property name at the configuration,
and in those cases we need to use that. This happens when there are many Record Properties
pointing to the same property at the Target Node.
*/
if (recordProperty.config.childProperty !== undefined) {
targetNode = propertyRootNode[recordProperty.config.childProperty]
}
/*
It can happen that intead of having a Node in targetNode what we have is an
array of nodes. We need to pick one of the elements of the array and for that
we use the Index value we find at the configuration of the Record Property.
*/
if (recordProperty.config.index !== undefined) {
try {
targetNode = targetNode[recordProperty.config.index]
} catch (err) {
badDefinitionUnhandledException(err, 'Error setting Target Node.', product, recordProperty)
}
}
/*
By Default the value is extracted from the value property of the Target Node.
But it might happen the the Target Node does not exist for example when there is an Array
of Nodes defined in the Record Properties but not all of them exist at the Root Node.
We filter out those cases by not extracting the value from the value property.
*/
let value = 0 // This is a default value, since we do not want null in files because it breakes JSON format.
if (targetNode !== undefined) {
if (targetNode.type !== undefined) {
/*
In this case the Target Node is really a node (since it has a type), so we extract the value
from its value property.
*/
value = targetNode.value
if (value !== undefined && value !== null) {
if (recordProperty.config.decimals !== undefined) {
try {
value = Number(value.toFixed(recordProperty.config.decimals))
} catch (err) {
badDefinitionUnhandledException(err, 'Error applying configured decimals.', product, recordProperty)
}
}
}
} else {
/*
In this case the Target Node is not really node, but the value itself. Se we return this as the
value of the Record Property.
*/
value = targetNode
}
}
/* We are not going to add records where the begin or end are missing */
if (
recordProperty.config.codeName === 'begin' && value === 0 ||
recordProperty.config.codeName === 'end' && value === 0
) { return }
if (recordProperty.config.isString !== true && Array.isArray(value) !== true) {
value = safeNumericValue(value)
}
if (recordProperty.config.isString === true && Array.isArray(value) !== true) {
value = safeStringValue(value)
}
record.push(value)
}
return record
}
function persistIndividualRecord(record, product, outputDatasetArray) {
if (product.config.saveAsObjects === true) {
/* For saving objects we need to take care of a different set of rules. */
for (let j = 0; j < product.record.properties.length; j++) {
let recordProperty = product.record.properties[j]
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) {
let propertyValue = record[j]
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES) {
if (product.config.doNotCutObjectInDays !== true) {
/*
By default we will cut objects in days.
*/
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
outputDatasetArray.push(record)
}
} else {
/*
When dealing with Daily Files, we need to avoid to write an open object at the last 'candle' of the day,
since the object will be duplicated on the next day. How do we know we are positioned at the last candle
of the day? Easy: the end of the candle must be 1 millisecod before the next day. That happens at any
time frame.
*/
let currentDay = new Date(portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value)
let nextDay = new Date(portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value + 1)
if (currentDay.getUTCDate() !== nextDay.getUTCDate()) {
/*
We will save the object only if it is closed, becasuse we are at the last candle of the day.
*/
if (propertyValue === product.config.propertyValueThatClosesObject) {
outputDatasetArray.push(record)
}
} else {
/*
When we are not at the end of the day, we will save the object normally, like in market files.
*/
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
outputDatasetArray.push(record)
}
}
}
}
else {
/*
For Market Files we will add a record everytime that proeprty value does not match this
*/
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
outputDatasetArray.push(record)
}
}
break
}
}
} else {
/*
When we are not dealing with objects, we add every record to the existing file except
for the ones that are filtered out at the Product Definition.
*/
if (product.config.propertyNameThatDefinesStatus !== undefined && product.config.propertyValueThatPreventsSavingObject !== undefined) {
for (let j = 0; j < product.record.properties.length; j++) {
let recordProperty = product.record.properties[j]
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesStatus) {
let propertyValue = record[j]
if (product.config.propertyValueThatPreventsSavingObject === "->Empty Array->" && propertyValue.length === 0) {
break
}
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
outputDatasetArray.push(record)
}
break
}
}
} else {
outputDatasetArray.push(record)
}
}
}
function safeNumericValue(value) {
/*
The purpose of this function is to check that value variable does not have a value that
will later break the JSON format of files where this is going to be stored at.
*/
if (value === Infinity) {
value = Number.MAX_SAFE_INTEGER
}
if (value === undefined) {
value = 0
}
if (value === null) {
value = 0
}
if (isNaN(value)) {
value = 0
}
return value
}
function safeStringValue(value) {
/*
The purpose of this function is to check that value variable does not have a value that
will later break the JSON format of files where this is going to be stored at.
*/
if (value === undefined) {
value = ''
}
if (value === null) {
value = ''
}
return value
}
function pruneOutputFile(product, outputFile, currentEnd) {
if (outputFile.isPrunned === true) { return }
/*
When a session is resumed, we will be potentially reading output files belonging to a previous session execution.
For that reason we need to prune all the records that are beyond the current candle. We do not delete everything
because we might be resuming a stopped session, which is fine.
*/
for (let i = 0; i < outputFile.length; i++) {
let record = outputFile[i]
for (let j = 0; j < product.record.properties.length; j++) {
let recordProperty = product.record.properties[j]
if (recordProperty.config.codeName === 'end') {
let end = record[j]
if (end >= currentEnd) {
outputFile.splice(i, 1)
/*
This will execute the next prune call in the next iteration of the NodeJs event loop
allowing for other callbacks to be executed. It also prevents the error
'Maximum call stack size exceeded', since the call is not placed at the call stack.
*/
setImmediate(pruneOutputFile, product, outputFile, currentEnd)
return
}
}
}
}
outputFile.isPrunned = true
}
function spliceOpenRecords(j, product, outputDatasetArray) {
/*
Before adding records to the output file, we need to remove all open records,
because we will be adding the same records later with potentially
new or updated information.
*/
for (let i = 0; i < outputDatasetArray.length; i++) {
let dataRecord = outputDatasetArray[i]
if (dataRecord !== undefined) {
let dataRecordValue = dataRecord[j]
if (dataRecordValue !== product.config.propertyValueThatClosesObject) {
outputDatasetArray.splice(i, 1)
spliceOpenRecords(j, product, outputDatasetArray)
return
}
}
}
}
}
function badDefinitionUnhandledException(err, message, product, recordProperty) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] appendRecords -> " + message);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] appendRecords -> product.name = " + product.name);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] appendRecords -> product.config = " + JSON.stringify(product.config));
if (recordProperty) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.name = " + recordProperty.name);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.config.codeName = " + recordProperty.config.codeName);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.config = " + JSON.stringify(recordProperty.config));
}
if (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, "[ERROR] appendRecords -> err.stack = " + err.stack);
}
throw 'Can not continue with a Definition Error like this.'
}
}
@@ -0,0 +1,679 @@
exports.newPortfolioManagementBotModulesPortfolioSimulation = function (processIndex) {
/*
This Module represents the portfolio simulacion. Escentially a loop through a set of candles and
the execution at each loop cycle of the Portfolio System Protocol.
*/
const MODULE_NAME = 'Portfolio Simulation -> ' + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.name
let thisObject = {
finalize: finalize,
runSimulation: runSimulation
}
return thisObject
function finalize() {
thisObject = undefined
}
async function runSimulation(
chart,
market,
exchange,
outputDatasetsMap,
writeFiles,
) {
try {
let portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
let portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
let sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime)
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime)
/* These are the Modules we will need to run the Simulation */
let portfolioRecordsModuleObject = TS.projects.portfolioManagement.botModules.portfolioRecords.newPortfolioManagementBotModulesPortfolioRecords(processIndex)
portfolioRecordsModuleObject.initialize(outputDatasetsMap)
let portfolioSystemModuleObject = TS.projects.portfolioManagement.botModules.portfolioSystem.newPortfolioManagementBotModulesPortfolioSystem(processIndex)
portfolioSystemModuleObject.initialize()
let portfolioEpisodeModuleObject = TS.projects.portfolioManagement.botModules.portfolioEpisode.newPortfolioManagementBotModulesPortfolioEpisode(processIndex)
portfolioEpisodeModuleObject.initialize()
/* Setting up the candles array: The whole simulation is based on the array of candles at the time-frame defined at the session parameters. */
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
let candles = chart[propertyName].candles
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES) {
/*
We need to purge from the candles array all the candles from the previous day
that comes when processing daily files.
*/
let dailyCandles = []
for (let i = 0; i < candles.length; i++) {
let candle = candles[i]
if (candle.begin >= TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine.portfolioCurrent.portfolioEpisode.processDate.value) {
dailyCandles.push(candle)
}
}
candles = dailyCandles
} else {
candles = chart[propertyName].candles
}
/* Variables needed for heartbeat functionality */
let heartBeatDate
let previousHeartBeatDate
/*
Estimation of the Initial Candle to Process in this Run.
*/
let initialCandle
if (
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_FIRST_LOOP === true &&
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_RESUMING === false) {
/* Estimate Initial Candle based on the timeRange configured for the session. */
let firstEnd = candles[0].end
let targetEnd = sessionParameters.timeRange.config.initialDatetime
let diff = targetEnd - firstEnd
let amount = diff / sessionParameters.timeFrame.config.value
initialCandle = Math.trunc(amount)
if (initialCandle < 0) { initialCandle = 0 }
if (initialCandle > candles.length - 1) {
/*
This will happen when the sessionParameters.timeRange.config.initialDatetime is beyond the last candle available,
meaning that the dataSet needs to be updated with more up-to-date data.
*/
console.log('DEBUGGING ..................', initialCandle, candles.length)
TS.projects.foundations.functionLibraries.sessionFunctions.stopSession(processIndex, 'Data is not up-to-date enough. Please start the Data Mining Operation.')
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[IMPORTANT] runSimulation -> Data is not up-to-date enough. Stopping the Session now. ')
return
}
} else {
/*
In this case we already have at the last candle index the next candle to be
processed. We will just continue with this candle.
*/
initialCandle = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value
}
/*
Main Simulation Loop
*/
/* We are going to use this to exit the loop if needed. */
let breakLoop = false
/*
We will assume that we are at the head of the market here. We do this
because the loop could be empty and no validation is going to run. If the
loop is not empty, then the lascCandle() check will override this value
depending on if we really are at the head of the market or not.
*/
portfolioEngine.portfolioCurrent.portfolioEpisode.headOfTheMarket.value = true
/*
This is the main simulation loop. It will go through the initial candle
until one less than the last candle available. We will never process the last
candle available since it is not considered a closed candle, but a candle
that still can change. So effectively will be processing all closed candles.
*/
for (let i = initialCandle; i < candles.length - 1; i++) {
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value = i
/* This is the current candle the Simulation is working at. */
let candle = candles[portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value]
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toUTCString())
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toUTCString())
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.setCurrentCandle(candle) // We move the current candle we are standing at, to the portfolio engine data structure to make it available to anyone, including conditions and formulas.
/* We emit a heart beat so that the UI can now where we are at the overal process.*/
heartBeat()
/* Opening the Episode, if needed. */
portfolioEpisodeModuleObject.openEpisode()
if (checkInitialDatetime() === false) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> loop -> Candle Before the Initia Date Time @ ' + (new Date(candle.begin)).toUTCString())
continue
}
positionDataStructuresAtCurrentCandle()
/* The chart was recalculated based on the current candle. */
portfolioSystemModuleObject.updateChart(
chart,
exchange,
market
)
/*
Do the stuff needed previous to the run like
Episode Counters and Statistics update. Mantaince is done
once per simulation candle.
*/
portfolioSystemModuleObject.mantain()
portfolioEpisodeModuleObject.mantain()
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.mantain()
/*
Run the first cycle of the Portfolio System. In this first cycle we
give some room so that orders can be canceled or filled and we can
write those records into the output memory. During this cycle new
orders can not be created, since otherwise the could be cancelled at
the second cycle without spending real time at the order book.
*/
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.setCurrentCycle('First')
await runCycle()
/*
We check if we need to stop before appending the records so that the stop
reason is also propery recorded. Note also that we check this after the first
cycle, where orders have not been submitted to the exchange yet, but we
had the chance to check for the status of placed orders or even cancel
the ones that needed cancellation.
*/
checkIfWeNeedToStopBetweenCycles()
/* Add new records to the process output */
portfolioRecordsModuleObject.appendRecords()
if (breakLoop === true) { break }
/*
Run the second cycle of the Portfolio System. During this second run
some new orders might be created at slots freed up during the first
run. This allows for example for a Limit Order to be cancelled during the
first run, and the same Limit Order definition to spawn a new order
without the need to wait until the next candle. Orders can not be cancelled
during the second cycle.
*/
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.setCurrentCycle('Second')
await runCycle()
checkIfWeNeedToStopAfterBothCycles()
/* Add new records to the process output */
portfolioRecordsModuleObject.appendRecords()
if (breakLoop === true) { break }
async function runCycle() {
/* Reset Data Structures */
portfolioSystemModuleObject.reset()
portfolioEpisodeModuleObject.reset()
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.reset()
let infoMessage = 'Processing candle # ' + portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value + ' @ the ' + portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.value + ' cycle.'
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> loop -> ' + infoMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Info - Candle And Cycle',
placeholder: {}
}
contextInfo = {
candleIndex: portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value,
cycle: portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.value
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addInfo([portfolioSystem.id, infoMessage, docs])
await portfolioSystemModuleObject.run()
}
function checkIfWeNeedToStopBetweenCycles() {
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING === true) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.')
updateEpisode('Session Stopped')
breakLoop = true
return
}
if (TS.projects.foundations.globals.taskVariables.IS_TASK_STOPPING === true) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.')
updateEpisode('Task Stopped')
breakLoop = true
return
}
if (checkFinalDatetime() === false) {
closeEpisode('Final Datetime Reached')
breakLoop = true
TS.projects.foundations.functionLibraries.sessionFunctions.stopSession(processIndex, 'Final Datetime Reached')
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[IMPORTANT] runSimulation -> Final Datetime Reached. Stopping the Session now. ')
return
}
if (checkMinimunAndMaximunBalance() === false) {
closeEpisode('Min or Max Balance Reached')
breakLoop = true
TS.projects.foundations.functionLibraries.sessionFunctions.stopSession(processIndex, 'Min or Max Balance Reached')
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[IMPORTANT] runSimulation -> Min or Max Balance Reached. Stopping the Session now. ')
return
}
}
function checkIfWeNeedToStopAfterBothCycles() {
if (checkNextCandle() === false) {
updateEpisode('All Candles Processed')
breakLoop = true
return
}
}
}
portfolioSystemModuleObject.finalize()
portfolioRecordsModuleObject.finalize()
portfolioEpisodeModuleObject.finalize()
portfolioSystemModuleObject = undefined
portfolioRecordsModuleObject = undefined
portfolioEpisodeModuleObject = undefined
await writeFiles()
function closeEpisode(exitType) {
portfolioEpisodeModuleObject.updateExitType(exitType)
portfolioEpisodeModuleObject.closeEpisode()
}
function updateEpisode(exitType) {
portfolioEpisodeModuleObject.updateExitType(exitType)
}
function heartBeat() {
let hartbeatText = ''
if (sessionParameters.heartbeats !== undefined) {
if (sessionParameters.heartbeats.config.date === true || sessionParameters.heartbeats.config.candleIndex === true) {
/* We will produce a simulation level heartbeat in order to inform the user this is running. */
heartBeatDate = new Date(Math.trunc(portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value / SA.projects.foundations.globals.timeConstants.ONE_DAY_IN_MILISECONDS) * SA.projects.foundations.globals.timeConstants.ONE_DAY_IN_MILISECONDS + SA.projects.foundations.globals.timeConstants.ONE_DAY_IN_MILISECONDS)
let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime)
let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime)
let currentDateString = heartBeatDate.getUTCFullYear() + '-' + SA.projects.foundations.utilities.miscellaneousFunctions.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + SA.projects.foundations.utilities.miscellaneousFunctions.pad(heartBeatDate.getUTCDate(), 2)
let currentDate = new Date(heartBeatDate)
let percentage = TS.projects.foundations.utilities.dateTimeFunctions.getPercentage(fromDate, currentDate, lastDate)
/*
Theere are a few tasks that we need to do only when the date changes,
otherwise it would be suboptimal.
*/
if (heartBeatDate.valueOf() !== previousHeartBeatDate) {
previousHeartBeatDate = heartBeatDate.valueOf()
let processingDate = heartBeatDate.getUTCFullYear() + '-' + SA.projects.foundations.utilities.miscellaneousFunctions.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + SA.projects.foundations.utilities.miscellaneousFunctions.pad(heartBeatDate.getUTCDate(), 2)
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> loop -> Simulation ' + TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY + ' Loop # ' + portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value + ' @ ' + processingDate)
/* Logging to console and disk */
if (TS.projects.foundations.utilities.dateTimeFunctions.areTheseDatesEqual(currentDate, new Date()) === false) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.newInternalLoop(currentDate, percentage)
}
/* Date only hearbeat */
if (sessionParameters.heartbeats.config.date === true && sessionParameters.heartbeats.config.candleIndex === false) {
hartbeatText = hartbeatText + currentDateString
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, hartbeatText, percentage)
return
}
}
/*
When the Candle Index nees to be shown, then we can not send the hearbet
only when the dates changes, we have to send it for every candle.
It might also contain the date information.
*/
if (sessionParameters.heartbeats.config.candleIndex === true) {
if (sessionParameters.heartbeats.config.date === true) {
hartbeatText = hartbeatText + currentDateString
}
hartbeatText = hartbeatText + ' Candle # ' + portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, hartbeatText, percentage)
}
}
}
}
function positionDataStructuresAtCurrentCandle() {
/*
In conditions and Formulas, we want users to have an easy sintax to refer to indicators. In order to achieve that, we need the user to have
easy access to the current candle for instance, or the current bollinger band, meaning the one the Simulation is currently standing at.
For that reason we do the following processing, to have at the chart data structure the current objects of each indicator / time frame.
*/
for (const exchangeCodeName in exchange) {
let currentExchange = exchange[exchangeCodeName]
for (const baseAssetCodeName in currentExchange.market) {
let currentBaseAsset = currentExchange.market[baseAssetCodeName]
for (const quotedAssetCodeName in currentBaseAsset) {
let currentQuotedAsset = currentBaseAsset[quotedAssetCodeName]
let currentChart = currentQuotedAsset.chart
positionChart(currentChart)
}
}
}
function positionChart(chart) {
let dataDependencies = TS.projects.visualScripting.utilities.nodeFunctions.nodeBranchToArray(TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.processDependencies, 'Data Dependency')
dataDependencies = TS.projects.visualScripting.utilities.nodeFunctions.filterOutNodeWihtoutReferenceParentFromNodeArray(dataDependencies)
/* Finding the Current Element on Daily Files */
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES) {
for (let j = 0; j < TS.projects.foundations.globals.timeFrames.dailyTimeFramesArray().length; j++) {
let mapKey = TS.projects.foundations.globals.timeFrames.dailyTimeFramesArray()[j][1]
let propertyName = 'at' + mapKey.replace('-', '')
let thisChart = chart[propertyName]
if (thisChart === undefined) { continue }
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Time-Frame-Daily') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
if (thisChart[pluralVariableName] !== undefined) {
let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName)
if (currentElement !== undefined) {
thisChart[singularVariableName] = currentElement
}
}
}
}
}
/* Finding the Current Element on Market Files */
for (let j = 0; j < TS.projects.foundations.globals.timeFrames.marketTimeFramesArray().length; j++) {
let mapKey = TS.projects.foundations.globals.timeFrames.marketTimeFramesArray()[j][1]
let propertyName = 'at' + mapKey.replace('-', '')
let thisChart = chart[propertyName]
if (thisChart === undefined) { continue }
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Time-Frame-Market') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
if (thisChart[pluralVariableName] !== undefined) {
let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName)
if (currentElement !== undefined) {
thisChart[singularVariableName] = currentElement
}
}
}
}
/* Finding the Current Element At Single Files*/
let propertyName = 'atAnyTimeFrame'
let thisChart = chart[propertyName]
if (thisChart === undefined) { return }
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Single-File') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let elementArray = thisChart[pluralVariableName]
let currentElement
if (elementArray !== undefined) {
currentElement = elementArray[elementArray.length - 1]
}
thisChart[singularVariableName] = currentElement
}
}
}
function getElement(pArray, datasetName) {
if (pArray === undefined) { return }
try {
let element
for (let i = 0; i < pArray.length; i++) {
element = pArray[i]
if (portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value === element.end) { // when there is an exact match at the end we take that element
return element
} else {
if (
i > 0 &&
element.end > portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value
) {
let previousElement = pArray[i - 1]
if (previousElement.end < portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value) {
return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element.
} else {
return
}
}
}
}
return
} catch (err) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName)
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[ERROR] runSimulation -> getElement -> err = ' + err.stack)
throw (TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE)
}
}
function checkNextCandle() {
/*
We need to check that the candle we have just processed it is not the last candle.
The candle at the head of the market is already skipped from the loop because it has not closed yet.
Note: for Daily Files, this means that the last candle of each day will never be processed.
The first +1 is because array indexes are based on 0.
The second +1 is because we need to compare the next candle (remember that the loops allways avoid the
last candle of the dataset available.)
*/
if (portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value + 1 + 1 === candles.length) {
/*
When processing daily files, we need a mechanism to turn from one day to the next one.
That mechanism is the one implemented here. If we detect that the next candle is the last candle of
the day, we will advance current process day one day. By doing so, during the next execution, the
simulation will receive the candles and indicators files of the next day.
*/
let candlesPerDay = SA.projects.foundations.globals.timeConstants.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value
if (
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_PROCESSING_DAILY_FILES &&
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value + 1 + 1 === candlesPerDay
) {
/*
Here we found that the next candle of the dataset is the last candle of the day.
It is time to move to the next day so as to receive at the next execution, the indicator files from
the next day. At the same time we will reset the index to be pointing to
the first candle of the new dataset we shall receive. The first candle of the next day starts
at index 0, so we will position the index now at zero.
*/
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value = 0
portfolioEngine.portfolioCurrent.portfolioEpisode.processDate.value =
portfolioEngine.portfolioCurrent.portfolioEpisode.processDate.value + SA.projects.foundations.globals.timeConstants.ONE_DAY_IN_MILISECONDS
return false
}
/*
We reached the head of the market but we are not at the last candle of a day during
Daily Files processing. We will advance to the next candle index anyways because in the
next execution it will likely have more candles at the dataset. And if it does not,
it will just wait there until it does.
*/
portfolioEngine.portfolioCurrent.portfolioEpisode.headOfTheMarket.value = true
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value++
return false
} else {
/* Wd did not reach the head of the market */
portfolioEngine.portfolioCurrent.portfolioEpisode.headOfTheMarket.value = false
portfolioEngine.portfolioCurrent.portfolioEpisode.candle.index.value++
return true
}
}
function checkInitialDatetime() {
/* Here we check that the current candle is not before the initial datetime defined at the session parameters.*/
if (portfolioEngine.portfolioCurrent.portfolioEpisode.candle.end.value < sessionParameters.timeRange.config.initialDatetime) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.')
return false
}
return true
}
function checkFinalDatetime() {
/* Here we check that the next candle is not after of the user-defined final datetime at the session parameters.*/
if (portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value + sessionParameters.timeFrame.config.value > sessionParameters.timeRange.config.finalDatetime) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.')
return false
}
return true
}
function checkMinimunAndMaximunBalance() {
/* Checks for Minimun and Maximun Balance. We do the check while not inside any strategy only. */
if (
portfolioEngine.portfolioCurrent.strategy.index.value === portfolioEngine.portfolioCurrent.strategy.index.config.initialValue
) {
/*
We will perform this check only when we are not inside a position,
because there the balances have shifted from their resting position.
*/
let stopRunningDate = (new Date(portfolioEngine.portfolioCurrent.portfolioEpisode.candle.begin.value)).toUTCString()
if (sessionParameters.sessionBaseAsset.config.minimumBalance !== undefined) {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) {
const errorMessage = 'Min Balance reached @ ' + stopRunningDate
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Minimum Balance Reached',
placeholder: {}
}
let contextInfo = {
timestamp: stopRunningDate,
episodeBaseAssetBalance: portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value,
sessionBaseAssetMinimumBalance: sessionParameters.sessionBaseAsset.config.minimumBalance
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addError([portfolioSystem.id, errorMessage, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage)
return false
}
}
if (sessionParameters.sessionBaseAsset.config.maximumBalance !== undefined) {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value >= sessionParameters.sessionBaseAsset.config.maximumBalance) {
const errorMessage = 'Max Balance reached @ ' + stopRunningDate
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Maximum Balance Reached',
placeholder: {}
}
let contextInfo = {
timestamp: stopRunningDate,
episodeBaseAssetBalance: portfolioEngine.portfolioCurrent.portfolioEpisode.episodeBaseAsset.balance.value,
sessionBaseAssetMaximumBalance: sessionParameters.sessionBaseAsset.config.maximumBalance
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addError([portfolioSystem.id, errorMessage, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage)
return false
}
}
if (sessionParameters.sessionQuotedAsset.config.minimumBalance !== undefined) {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value <= sessionParameters.sessionQuotedAsset.config.minimumBalance) {
const errorMessage = 'Min Balance reached @ ' + stopRunningDate
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Minimum Balance Reached',
placeholder: {}
}
let contextInfo = {
timestamp: stopRunningDate,
episodeQuotedAssetBalance: portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value,
sessionQuotedAssetMinimumBalance: sessionParameters.sessionQuotedAsset.config.minimumBalance
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addError([portfolioSystem.id, errorMessage, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage)
return false
}
}
if (sessionParameters.sessionQuotedAsset.config.maximumBalance !== undefined) {
if (portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value >= sessionParameters.sessionQuotedAsset.config.maximumBalance) {
const errorMessage = 'Max Balance reached @ ' + stopRunningDate
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Maximum Balance Reached',
placeholder: {}
}
let contextInfo = {
timestamp: stopRunningDate,
episodeQuotedAssetBalance: portfolioEngine.portfolioCurrent.portfolioEpisode.episodeQuotedAsset.balance.value,
sessionQuotedAssetMaximumBalance: sessionParameters.sessionQuotedAsset.config.maximumBalance
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, undefined, undefined, undefined, undefined, contextInfo)
portfolioSystem.addError([portfolioSystem.id, errorMessage, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage)
return false
}
}
}
return true
}
} catch (err) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[ERROR] runSimulation -> err = ' + err.stack)
throw (TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE)
}
}
}
@@ -0,0 +1,87 @@
exports.newPortfolioManagementBotModulesPortfolioStrategy = function (processIndex) {
/*
This module packages all functions related to Strategies.
*/
let thisObject = {
mantain: mantain,
reset: reset,
openStrategy: openStrategy,
closeStrategy: closeStrategy,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let sessionParameters
return thisObject
function initialize() {
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
}
function finalize() {
portfolioEngine = undefined
sessionParameters = undefined
}
function mantain() {
updateCounters()
updateEnds()
}
function reset() {
resetPortfolioEngineDataStructure()
}
function openStrategy(index, situationName, strategyName) {
/* Starting begin and end */
portfolioEngine.portfolioCurrent.strategy.begin.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastBegin.value
portfolioEngine.portfolioCurrent.strategy.end.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastEnd.value
/* Recording the opening at the Portfolio Engine Data Structure */
portfolioEngine.portfolioCurrent.strategy.status.value = 'Open'
portfolioEngine.portfolioCurrent.strategy.serialNumber.value = portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.strategies.value
portfolioEngine.portfolioCurrent.strategy.identifier.value = SA.projects.foundations.utilities.miscellaneousFunctions.genereteUniqueId()
portfolioEngine.portfolioCurrent.strategy.beginRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.min.value
portfolioEngine.portfolioCurrent.strategy.index.value = index
portfolioEngine.portfolioCurrent.strategy.situationName.value = situationName
portfolioEngine.portfolioCurrent.strategy.strategyName.value = strategyName
/* Updating Episode Counters */
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.strategies.value++
}
function closeStrategy(exitType) {
portfolioEngine.portfolioCurrent.strategy.status.value = 'Closed'
portfolioEngine.portfolioCurrent.strategy.exitType.value = exitType
portfolioEngine.portfolioCurrent.strategy.end.value = portfolioEngine.portfolioCurrent.portfolioEpisode.cycle.lastEnd.value
portfolioEngine.portfolioCurrent.strategy.endRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.min.value
/*
Now that the strategy is closed, it is the right time to move this strategy from current to last at the Portfolio Engine data structure.
*/
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.cloneValues(portfolioEngine.portfolioCurrent.strategy, portfolioEngine.portfolioLast.strategy)
}
function updateEnds() {
if (portfolioEngine.portfolioCurrent.strategy.status.value === 'Open') {
portfolioEngine.portfolioCurrent.strategy.end.value = portfolioEngine.portfolioCurrent.strategy.end.value + sessionParameters.timeFrame.config.value
portfolioEngine.portfolioCurrent.strategy.endRate.value = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
}
}
function resetPortfolioEngineDataStructure() {
if (portfolioEngine.portfolioCurrent.strategy.status.value === 'Closed') {
TS.projects.foundations.globals.processModuleObjects.MODULE_OBJECTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_MODULE_OBJECT.initializeNode(portfolioEngine.portfolioCurrent.strategy)
}
}
function updateCounters() {
if (portfolioEngine.portfolioCurrent.strategy.status.value === 'Open') {
portfolioEngine.portfolioCurrent.strategy.strategyCounters.periods.value++
}
}
}
@@ -0,0 +1,451 @@
exports.newPortfolioManagementBotModulesPortfolioSystem = function (processIndex) {
/*
The Portfolio System is the user defined set of rules compliant with the Portfolio Protocol that
defines the portfolio logic to be applied during each cycle of the Simulation.
*/
const MODULE_NAME = 'Portfolio System'
let thisObject = {
mantain: mantain,
reset: reset,
run: run,
updateChart: updateChart,
initialize: initialize,
finalize: finalize
}
/*
These 3 are the main data structures available to users
when writing conditions and formulas.
*/
let chart
let exchange
let market
let portfolioSystem
let portfolioEngine
let sessionParameters
let dynamicIndicators
let portfolioStagesModuleObject = TS.projects.portfolioManagement.botModules.portfolioStages.newPortfolioManagementBotModulesPortfolioStages(processIndex)
let taskParameters = {
market: TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName +
'/' +
TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName
}
return thisObject
function initialize() {
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
portfolioSystem.conditions = new Map()
portfolioSystem.formulas = new Map()
portfolioStagesModuleObject.initialize()
/* Adding Functions used elsewhere to Portfolio System Definition */
portfolioSystem.checkConditions = function (situation, passed) {
/* Given a Situation, we check if all children conditions are true or not */
for (let m = 0; m < situation.conditions.length; m++) {
let condition = situation.conditions[m]
let value = false
if (portfolioSystem.conditions.get(condition.id) !== undefined) {
value = portfolioSystem.conditions.get(condition.id)
}
if (value === true) {
portfolioSystem.highlights.push(condition.id)
}
else {
passed = false
}
portfolioSystem.values.push([condition.id, value])
}
return passed
}
portfolioSystem.evalConditions = function (startingNode, descendentOfNodeType) {
evalNode(startingNode, 'Conditions', descendentOfNodeType)
}
portfolioSystem.evalFormulas = function (startingNode, descendentOfNodeType) {
evalNode(startingNode, 'Formulas', descendentOfNodeType)
}
portfolioSystem.evalUserCode = function (startingNode, descendentOfNodeType) {
evalNode(startingNode, 'User Codes', descendentOfNodeType);
}
portfolioSystem.addError = function (errorDataArray) {
/*
This function adds to the array of error info a rate.
This rate will later help plotting the error at the
charts.
*/
let rate = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
errorDataArray.push(rate)
portfolioSystem.errors.push(errorDataArray)
}
portfolioSystem.addWarning = function (warningDataArray) {
/*
This function adds to the array of warning info a rate.
This rate will later help plotting the warning at the
charts.
*/
let rate = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
warningDataArray.push(rate)
portfolioSystem.warnings.push(warningDataArray)
}
portfolioSystem.addInfo = function (infoDataArray) {
/*
This function adds to the array of warning info a rate.
This rate will later help plotting the warning at the
charts.
*/
let rate = portfolioEngine.portfolioCurrent.portfolioEpisode.candle.close.value
infoDataArray.push(rate)
portfolioSystem.infos.push(infoDataArray)
}
function isItInside(elementWithTimestamp, elementWithBeginEnd) {
/*
The function is to allow in Conditions and Formulas to easily
know when an element with timestamp (like the ones of
single files) are inside the time range
of an element with a time range, like a candle.
*/
if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) {
return true
} else {
return false
}
}
}
function finalize() {
portfolioStagesModuleObject.finalize()
portfolioStagesModuleObject = undefined
chart = undefined
exchange = undefined
market = undefined
portfolioSystem.conditions = undefined
portfolioSystem.formulas = undefined
portfolioSystem.highlights = undefined
portfolioSystem.errors = undefined
portfolioSystem.warnings = undefined
portfolioSystem.infos = undefined
portfolioSystem.values = undefined
portfolioSystem.status = undefined
portfolioSystem.progress = undefined
portfolioSystem.running = undefined
portfolioSystem = undefined
portfolioEngine = undefined
sessionParameters = undefined
taskParameters = undefined
}
function mantain() {
portfolioStagesModuleObject.mantain()
}
function reset() {
portfolioStagesModuleObject.reset()
portfolioSystem.highlights = []
portfolioSystem.errors = []
portfolioSystem.warnings = []
portfolioSystem.infos = []
portfolioSystem.values = []
portfolioSystem.status = []
portfolioSystem.progress = []
portfolioSystem.running = []
portfolioSystem.announcements = []
}
function updateChart(pChart, pExchange, pMarket) {
/*
We need these 3 data structures to be a local objects
accessible while evaluating conditions and formulas.
*/
chart = pChart
exchange = pExchange
market = pMarket
portfolioStagesModuleObject.updateChart(pChart, pExchange, pMarket)
}
function buildDynamicIndicators() {
if (portfolioSystem.dynamicIndicators !== undefined) {
dynamicIndicators = {}
/* Eval Dynamic Indicators */
portfolioSystem.evalFormulas(portfolioSystem.dynamicIndicators, 'Indicator Function')
for (let i = 0; i < portfolioSystem.dynamicIndicators.indicatorFunctions.length; i++) {
let indicatorFunction = portfolioSystem.dynamicIndicators.indicatorFunctions[i]
if (indicatorFunction.formula === undefined) { return }
if (indicatorFunction.config.codeName === undefined) { return }
dynamicIndicators[indicatorFunction.config.codeName] = portfolioSystem.formulas.get(indicatorFunction.formula.id)
}
}
}
async function run() {
try {
/* Dynamic Indicators */
buildDynamicIndicators()
/* Run the Trigger Stage */
portfolioStagesModuleObject.runTriggerStage()
/* Run the Open Stage */
await portfolioStagesModuleObject.runOpenStage()
/* Run the Manage Stage */
portfolioStagesModuleObject.runManageStage()
/* Run the Close Stage */
await portfolioStagesModuleObject.runCloseStage()
/* Validation if we need to exit the position */
portfolioStagesModuleObject.exitPositionValidation()
portfolioStagesModuleObject.cycleBasedStatistics()
} catch (err) {
/*
If an error ocurred during execution, it was alreeady logged and
included at the errors array. That means we need to do nothing here,
just prevent the execution to be halted for not handling exceptions.
*/
if (typeof err === 'string' || err instanceof String) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err)
}
if (err.stack !== undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err.stack)
}
}
}
function evalNode(node, evaluating, descendentOfNodeType, isDescendent) {
if (node === undefined) { return }
/* Verify if this node is decendent of the specified node type */
if (isDescendent !== true) {
if (node.type === descendentOfNodeType) {
isDescendent = true
}
}
/* Here we check if there is a codition to be evaluated */
if (node.type === 'Condition' && evaluating === 'Conditions') {
/* We will eval this condition */
if (isDescendent === true) {
evalCondition(node)
}
}
/* Here we check if there is a formula to be evaluated */
if ((node.type === 'Formula' || node.type === 'Announcement Formula') && evaluating === 'Formulas') {
if (node.code !== undefined) {
/* We will eval this formula */
if (isDescendent === true) {
evalFormula(node)
}
}
}
/* Here we check if there is a User Defined-Javascript Code to be evaluated: */
if (node.type === 'Javascript Code' && evaluating === 'User Codes') {
if (node.code !== undefined) {
if (isDescendent === true) {
evalJSCode(node);
}
}
}
/* Now we go down through all this node children */
let schemaDocument = TS.projects.foundations.globals.taskConstants.APP_SCHEMA_MAP.get(node.project + '-' + node.type)
if (schemaDocument === undefined) { return }
if (schemaDocument.childrenNodesProperties !== undefined) {
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
for (let i = 0; i < schemaDocument.childrenNodesProperties.length; i++) {
let property = schemaDocument.childrenNodesProperties[i]
switch (property.type) {
case 'node': {
if (property.name !== previousPropertyName) {
if (node[property.name] !== undefined) {
evalNode(node[property.name], evaluating, descendentOfNodeType, isDescendent)
}
previousPropertyName = property.name
}
break
}
case 'array': {
if (node[property.name] !== undefined) {
let nodePropertyArray = node[property.name]
for (let m = 0; m < nodePropertyArray.length; m++) {
evalNode(nodePropertyArray[m], evaluating, descendentOfNodeType, isDescendent)
}
}
break
}
}
}
}
}
function evalCondition(node) {
let value
let errorMessage
let docs
/*
The code can be at the condition node if it was done with the Conditions Editor, or it can also be
at a Javascript Code node. If there is a Javascript object we will give it preference and take the code
from there. Otherwise we will take the code from the Condition node.
*/
let code = node.code
let nodeName = node.name
if (node.javascriptCode !== undefined) {
if (node.javascriptCode.code !== undefined) {
code = node.javascriptCode.code
nodeName = node.javascriptCode.name
}
}
try {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[INFO] evalCondition -> ' + node.name + ' -> code = ' + code)
value = eval(code)
} catch (err) {
/*
One possible error is that the conditions references a .previous that is undefined. This
will not be considered an error.
*/
value = false
errorMessage = err.message
docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Evaluating Condition Error',
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, nodeName, code, undefined)
}
portfolioSystem.conditions.set(node.id, value)
if (value === true) {
portfolioSystem.highlights.push(node.id)
}
if (errorMessage !== undefined) {
portfolioSystem.addError([node.id, errorMessage, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] evalCondition -> errorMessage = ' + errorMessage)
}
if (value !== undefined) {
portfolioSystem.values.push([node.id, value])
}
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[INFO] evalCondition -> value = ' + value)
}
function evalFormula(node) {
let value
let errorMessage
let docs
try {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[INFO] evalFormula -> ' + node.name + ' -> code = ' + node.code)
value = eval(node.code)
} catch (err) {
/*
One possible error is that the formula references a .previous that is undefined. This
will not be considered an error.
*/
value = 0
errorMessage = err.message
docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Evaluating Formula Error',
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, node.name, node.code, undefined)
}
if (errorMessage !== undefined) {
portfolioSystem.addError([node.id, errorMessage, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[INFO] evalFormula -> errorMessage = ' + errorMessage)
return
} else {
if (value !== undefined) {
if (node.type === 'Formula' && isNaN(value)) {
docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Formula Value Not A Number',
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, node.name, node.code, undefined, value)
portfolioSystem.addError([node.id, 'Formula needs to return a numeric value.', docs])
return
}
portfolioSystem.values.push([node.id, value])
portfolioSystem.formulas.set(node.id, value)
} else {
docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Formula Value Not A Number',
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, undefined, node.name, node.code, undefined, value)
portfolioSystem.addError([node.id, 'Formula needs to return a numeric value.', docs])
return
}
}
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[INFO] evalFormula -> value = ' + value)
}
function evalJSCode(node) {
let value
let errorMessage
let docs
try {
value = eval(node.code);
} catch (err) {
value = 0
errorMessage = err.message
docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - Evaluating User Code Error',
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, node.name, node.code, undefined)
}
if (errorMessage !== undefined) {
portfolioSystem.addError([node.id, errorMessage, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[INFO] evalFormula -> errorMessage = ' + errorMessage)
return
}
}
}
@@ -0,0 +1,481 @@
exports.newPortfolioManagementBotModulesSnapshots = function(processIndex) {
/*
This module encapsulates the snapshots functionality.
*/
const MODULE_NAME = 'Snapshots'
let thisObject = {
updateChart: updateChart,
strategyEntry: strategyEntry,
strategyExit: strategyExit,
positionEntry: positionEntry,
positionExit: positionExit,
addCodeToSnapshot: addCodeToSnapshot,
initialize: initialize,
finalize: finalize
}
let portfolioEngine
let portfolioSystem
let sessionParameters
const SNAPSHOTS_FOLDER_NAME = 'Snapshots'
/* Snapshots of Strategies and Positions */
let snapshots = {
headers: [],
strategies: [],
positions: []
}
let positionKeys = new Map() // This is to prevent adding the same indicator / variable to the output collection.
let strategyKeys = new Map()
let createHeaders = true // This is going to be true only once
let createCloseHeaders = true // This is going to be true only once
let closeValues
let strategyValues
let addToStrategyValues
let positionValues
let addToPositionValues
let snapshotAvailable = false
/*
These 3 are the main data structures available to users
when writing conditions and formulas.
*/
let chart
let exchange
let market
return thisObject
function initialize() {
portfolioEngine = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioEngine
portfolioSystem = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SIMULATION_STATE.portfolioSystem
sessionParameters = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters
// first trade forces overwrite of the existing file
if (portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.positions.value > 1) {
snapshotAvailable = true
createHeaders = false // This is going to be true only once
createCloseHeaders = false
}
}
function finalize() {
//trigger exits added for extra save moment for datapreserve
if (portfolioEngine.portfolioCurrent.strategy.begin.value > 0) {
strategyExit()
positionExit()
}
writeSnapshotFiles()
portfolioEngine = undefined
portfolioSystem = undefined
sessionParameters = undefined
positionKeys = undefined
strategyKeys = undefined
chart = undefined
exchange = undefined
market = undefined
}
function updateChart(pChart, pExchange, pMarket) {
/*
We need these 3 data structures to be a local objects
accessible while evaluating conditions and formulas.
*/
chart = pChart
exchange = pExchange
market = pMarket
}
function strategyEntry() {
/*
We will record here a snapshot of all values used in conditions and formulas within the Portfolio System.
If the strategy exists with a trade, this information will be added to the snapshots file.
*/
strategyKeys = new Map()
strategyValues = []
addToStrategyValues = true
evalNode(portfolioSystem)
/* Turn off this */
createHeaders = false
addToStrategyValues = false
}
function strategyExit() {
getResults(portfolioEngine.portfolioCurrent.strategy.begin.value, portfolioEngine.portfolioCurrent.strategy.end.value)
let valuesArray = closeValues.concat(strategyValues)
snapshots.strategies.push(valuesArray)
}
function positionEntry() {
positionKeys = new Map()
positionValues = []
addToPositionValues = true
evalNode(portfolioSystem)
/* Turn off this */
createHeaders = false
addToPositionValues = false
}
function positionExit() {
getResults(portfolioEngine.portfolioCurrent.position.begin.value, portfolioEngine.portfolioCurrent.position.end.value)
let valuesArray = closeValues.concat(positionValues)
snapshots.positions.push(valuesArray)
}
function evalNode(node) {
if (node === undefined) { return }
/* Here we check if there is a codition to be evaluated */
if (node.type === 'Condition') {
/* We will eval this condition */
evalCondition(node)
}
/* Here we check if there is a formula to be evaluated */
if (node.type === 'Formula') {
if (node.code !== undefined) {
/* We will eval this formula */
evalFormula(node)
}
}
/* Now we go down through all this node children */
let schemaDocument = TS.projects.foundations.globals.taskConstants.APP_SCHEMA_MAP.get(node.project + '-' + node.type)
if (schemaDocument === undefined) { return }
if (schemaDocument.childrenNodesProperties !== undefined) {
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
for (let i = 0; i < schemaDocument.childrenNodesProperties.length; i++) {
let property = schemaDocument.childrenNodesProperties[i]
switch (property.type) {
case 'node':
{
if (property.name !== previousPropertyName) {
if (node[property.name] !== undefined) {
evalNode(node[property.name])
}
previousPropertyName = property.name
}
break
}
case 'array':
{
if (node[property.name] !== undefined) {
let nodePropertyArray = node[property.name]
for (let m = 0; m < nodePropertyArray.length; m++) {
evalNode(nodePropertyArray[m])
}
}
break
}
}
}
}
}
function evalCondition(node) {
/*
The code can be at the condition node if it was done with the Conditions Editor, or it can also be
at a Javascript Code node. If there is a Javascript object we will give it preference and take the code
from there. Otherwise we will take the code from the Condition node.
*/
let nodeWithCode = node
if (node.javascriptCode !== undefined) {
if (node.javascriptCode.code !== undefined) {
nodeWithCode = node.javascriptCode
}
}
addCodeToSnapshot(nodeWithCode)
}
function evalFormula(node) {
let nodeWithCode = node
addCodeToSnapshot(nodeWithCode)
}
function addCodeToSnapshot(nodeWithCode) {
if (nodeWithCode === undefined) { return }
if (nodeWithCode.code === undefined) { return }
try {
const code = nodeWithCode.code
let instructionsArray = code.split(' ')
for (let i = 0; i < instructionsArray.length; i++) {
let instruction = instructionsArray[i]
instruction = instruction.replace('(', '')
instruction = instruction.replace(')', '')
instruction = instruction.replace(/</g, '')
instruction = instruction.replace(/>/g, '')
instruction = instruction.replace(/<=/g, '')
instruction = instruction.replace(/>=/g, '')
instruction = instruction.replace(/!=/g, '')
instruction = instruction.replace(/!==/g, '')
instruction = instruction.replace(/==/g, '')
instruction = instruction.replace(/===/g, '')
instruction = instruction.replace(/{/g, '')
instruction = instruction.replace(/}/g, '')
if (instruction.indexOf('chart') >= 0) {
let parts = instruction.split('.')
let timeFrame = parts[1]
let product = parts[2]
let property
checkPrevious(3)
function checkPrevious(index) {
property = parts[index]
if (property === 'previous') {
product = product + '.previous'
checkPrevious(index + 1)
}
}
// Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10
if (timeFrame !== 'atAnyTimeFrame') {
timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7)
}
let key = timeFrame + '-' + product + '-' + property
let existingKey
if (addToStrategyValues === true) {
addValues(strategyKeys, strategyValues)
}
if (addToPositionValues === true) {
addValues(positionKeys, positionValues)
}
function addValues(keys, values) {
existingKey = keys.get(key)
if (existingKey === undefined) { // means that at the current loop this property of this product was not used before.
keys.set(key, key)
if (createHeaders === true) {
snapshots.headers.push(key)
}
try {
let value = eval(instruction)
values.push(value)
} catch (err) {
const message = 'Snapshot Unexpected Error'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
let contextInfo = {
instruction: instruction
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, nodeWithCode.name, nodeWithCode.code, undefined, undefined, contextInfo)
portfolioSystem.addError([nodeWithCode.id, message, docs])
values.push(0)
}
}
}
}
}
} catch (err) {
const message = 'Snapshot Unexpected Error'
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS LF Portfolio Bot Error - ' + message,
placeholder: {}
}
TS.projects.education.utilities.docsFunctions.buildPlaceholder(docs, err, nodeWithCode.name, nodeWithCode.code, undefined)
portfolioSystem.addError([nodeWithCode.id, message, docs])
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> nodeWithCode.code = ' + nodeWithCode.code)
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack)
}
}
function getResults(openDatetime, closeDatetime) {
let baseOpen = portfolioEngine.portfolioCurrent.strategyCloseStage.stageBaseAsset.sizeFilled.value - portfolioEngine.portfolioCurrent.strategyCloseStage.stageBaseAsset.feesPaid.value
let baseClose = portfolioEngine.portfolioCurrent.strategyOpenStage.stageBaseAsset.sizeFilled.value - portfolioEngine.portfolioCurrent.strategyOpenStage.stageBaseAsset.feesPaid.value
let quoteOpen = portfolioEngine.portfolioCurrent.strategyOpenStage.stageQuotedAsset.sizeFilled.value - portfolioEngine.portfolioCurrent.strategyOpenStage.stageQuotedAsset.feesPaid.value
let quoteClose = portfolioEngine.portfolioCurrent.strategyCloseStage.stageQuotedAsset.sizeFilled.value - portfolioEngine.portfolioCurrent.strategyCloseStage.stageQuotedAsset.feesPaid.value
let qouteProfit = ((quoteClose / quoteOpen * 100) - 100)
let baseProfit = ((baseClose / baseOpen * 100) - 100)
let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Open Position in Base Asset', 'Close Position in Base Asset', 'Open Position in Quoted Asset', 'Close Position in Quoted Asset', 'Result In Base Asset', 'Result In Quoted Asset', 'ROI in Base Asset', '% P/L Base Asset', 'P/L Type Base Asset', 'ROI in Quoted Asset', '% P/L Quoted Asset', 'P/L Type Quoted Asset', 'Exit Type', '# Periods']
closeValues = [
portfolioEngine.portfolioCurrent.portfolioEpisode.portfolioEpisodeCounters.positions.value, // Position Number
(new Date(openDatetime)).toISOString(), // Open Datetime
(new Date(closeDatetime)).toISOString(), // Open Datetime
portfolioEngine.portfolioCurrent.strategy.strategyName.value, // Strategy Name
portfolioEngine.portfolioCurrent.strategy.situationName.value, // Trigger On Situation
portfolioEngine.portfolioCurrent.position.situationName.value, // Take Position Situation
baseOpen, // open position in base asset
baseClose, // close position in base asset
quoteOpen, //open quote in base asset
quoteClose, // close quote in base asset
portfolioEngine.portfolioCurrent.position.positionBaseAsset.hitFail.value, // Result in Base Asset
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.hitFail.value, // Result in Base Asset
portfolioEngine.portfolioCurrent.position.positionBaseAsset.ROI.value, // ROI in Base Asset <- not sure this works as intended
baseProfit + '%', // % profit / loss in base Asset
baseProfit === 0 ? 'Even' : (baseProfit > 0 ? "Profit" : "Loss"),
portfolioEngine.portfolioCurrent.position.positionQuotedAsset.ROI.value, // ROI in Quoted Asset <- not sure this works as intended
qouteProfit + '%', // % profit loss in quoted Asset
qouteProfit === 0 ? 'Even' : (qouteProfit > 0 ? "Profit" : "Loss"),
portfolioEngine.portfolioCurrent.position.exitType.value, // Exit Type
portfolioEngine.portfolioCurrent.position.positionCounters.periods.value // periods counter
]
if (createCloseHeaders === true) {
snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshots.headers)))
createCloseHeaders = false
}
}
function writeSnapshotFiles() {
if (sessionParameters.snapshots !== undefined) {
if (sessionParameters.snapshots.config.strategy === true) {
writeSnapshotFile(snapshots.strategies, 'Strategies')
}
}
if (sessionParameters.snapshots !== undefined) {
if (sessionParameters.snapshots.config.strategy === true) {
writeSnapshotFile(snapshots.positions, 'Positions')
}
}
}
function writeSnapshotFile(snapshotArray, pFileName) {
try {
let fileStorage = TS.projects.foundations.taskModules.fileStorage.newFileStorage(processIndex);
let fileName = pFileName + '.csv'
let filePath = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).FILE_PATH_ROOT + '/Output/' + TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_FOLDER_NAME + '/' + SNAPSHOTS_FOLDER_NAME;
filePath += '/' + fileName
//Get contents of current file if available, otherwise start empty file
if (snapshotAvailable) {
getSnapShotFile(filePath)
} else {
writeOutput('')
}
function writeOutput(fileContent) {
let separator = '\r\n';
let contentArray = [];
// if we have file content extract rows
if (fileContent != '') {
contentArray = fileContent.split(separator)
}
parseRecord(snapshots.headers)
for (let i = 0; i < snapshotArray.length; i++) {
let record = snapshotArray[i];
if (record.length > 0) {
parseRecord(record)
}
}
function parseRecord(record) {
// if we have undefined values, we need to extract the previous stored values for this trade and append them to the new record for completion
if (record[record.length - 1] === undefined && contentArray.length > 0) {
let rowToMutateArray = contentArray[contentArray.length - 1].split(',')
// check if are for sure on the same tradenumber and merge the data and remove previous line incl separator
if (rowToMutateArray[0] == record[0]) {
record.pop()
rowToMutateArray.splice(0, record.length)
record = record.concat(rowToMutateArray)
fileContent = fileContent.replace(contentArray[contentArray.length - 1], '')
fileContent = fileContent.replace(/\r\n$/, '')
}
}
for (let j = 0; j < record.length; j++) {
let property = record[j]
fileContent = fileContent + '' + property
if (j !== record.length - 1) {
fileContent = fileContent + ","
}
}
fileContent = fileContent + separator
}
fileContent = "" + fileContent + "";
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated)
function onFileCreated(err) {
if (err.result !== TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] writeSnapshotFile -> onFileCreated -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] writeSnapshotFile -> onFileCreated -> filePath = ' + filePath);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] writeSnapshotFile -> onFileCreated -> market = ' + TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName + '_' + TS.projects.foundations.globals.taskConstants.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName);
}
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(
processIndex
).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(
MODULE_NAME,
'[ERROR] writeSnapshotFile -> err = ' + err.stack
)
}
//get file contents
function getSnapShotFile(filePath) {
try {
let fileStorage = TS.projects.foundations.taskModules.fileStorage.newFileStorage(processIndex);
fileStorage.getTextFile(filePath, onFileReceived)
function onFileReceived(err, text) {
if (err.result === TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result) {
try {
// remove the last linebreak and seperator which is introduced extra during save
text = text.replace(/\n$/, '')
text = text.replace(/\r\n$/, '')
writeOutput(text)
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> fileName = ' + filePath);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> filePath = ' + filePath);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> Asuming this is a temporary situation. Requesting a Retry.');
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE)
}
} else {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> filePath = ' + filePath);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err;
callBackFunction(err)
}
}
} catch (error) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> err = ' + err.stack);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME, '[ERROR] start -> loadExistingFiles -> loopBody -> readExistingFile -> onFileReceived -> filePath = ' + filePath);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err;
callBackFunction(err);
}
}
}
}
@@ -0,0 +1,642 @@
exports.newPortfolioManagementBotModulesPortfolioSession = function (processIndex) {
const MODULE_NAME = "Portfolio Session"
let thisObject = {
initialize: initialize
}
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SOCIAL_BOTS_MODULE =
TS.projects.socialBots.botModules.socialBots.newSocialBotsBotModulesSocialBots(processIndex)
return thisObject;
function initialize(callBackFunction) {
try {
/* Check if there is a session */
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> Cannot run without a Session.");
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
return;
}
/*
We will store here the session key, which we will need everytine
we need to emit an event related to the session itself.
*/
let VARIABLES_BY_PROCESS_INDEX = TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex)
VARIABLES_BY_PROCESS_INDEX.SESSION_KEY =
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.name +
'-' +
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type +
'-' +
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.id
/*
We will also store the session folder name, to be used for debug logging and session output.
*/
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.config.folderName === undefined) {
VARIABLES_BY_PROCESS_INDEX.SESSION_FOLDER_NAME = TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type.replace(' ', '-').replace(' ', '-') + '-' + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.id
} else {
VARIABLES_BY_PROCESS_INDEX.SESSION_FOLDER_NAME = TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type.replace(' ', '-').replace(' ', '-') + '-' + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.config.folderName
}
/*
We will store all session keys on a map so as to be able to send an event to all
of them when the task stops.
*/
TS.projects.foundations.globals.taskVariables.SESSION_MAP.set(
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY)
/* Listen to event to start or stop the session. */
TS.projects.foundations.globals.taskConstants.EVENT_SERVER_CLIENT_MODULE_OBJECT.listenToEvent(
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
'Portfolio Session Status',
undefined,
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
undefined,
onSessionStatus)
TS.projects.foundations.globals.taskConstants.EVENT_SERVER_CLIENT_MODULE_OBJECT.listenToEvent(
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
'Run Portfolio Session',
undefined,
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
undefined,
onSessionRun)
TS.projects.foundations.globals.taskConstants.EVENT_SERVER_CLIENT_MODULE_OBJECT.listenToEvent(
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
'Stop Portfolio Session',
undefined,
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
undefined,
onSessionStop)
TS.projects.foundations.globals.taskConstants.EVENT_SERVER_CLIENT_MODULE_OBJECT.listenToEvent(
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
'Resume Portfolio Session',
undefined,
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY,
undefined,
onSessionResume)
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE)
return
function onSessionStatus() {
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS === 'Running') {
let event = {
status: 'Portfolio Session Runnning'
}
TS.projects.foundations.globals.taskConstants.EVENT_SERVER_CLIENT_MODULE_OBJECT.raiseEvent(
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY, 'Status Response', event)
} else {
let event = {
status: 'Portfolio Session Not Runnning'
}
TS.projects.foundations.globals.taskConstants.EVENT_SERVER_CLIENT_MODULE_OBJECT.raiseEvent(
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY, 'Status Response', event)
}
}
function onSessionRun(message) {
try {
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
if (
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS === 'Idle' ||
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS === 'Running'
) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] onSessionRun -> Event received to run the Session while it was already running. ")
return
}
/* We are going to initialize here these constants whose values are comming at the event. */
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_SYSTEM_NODE = JSON.parse(message.event.portfolioSystem)
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).TRADING_ENGINE_NODE = JSON.parse(message.event.portfolioEngine)
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE = JSON.parse(message.event.session)
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter)
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_RESUMING = false
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_FIRST_LOOP = true
setUpSessionFolderName()
/* We validate all parameters received and complete some that might be missing if needed. */
if (checkParemeters() === false) { return }
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SOCIAL_BOTS_MODULE.initialize()
let allGood
switch (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type) {
case 'Backtesting Session': {
allGood = startBackTesting(message)
break
}
case 'Live Portfolio Session': {
allGood = startLivePortfolio(message)
break
}
case 'Fordward Testing Session': {
allGood = startFordwardTesting(message)
break
}
case 'Paper Portfolio Session': {
allGood = startPaperPortfolio(message)
break
}
}
if (allGood === true) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS = 'Idle'
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING = false
} else {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING = true
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[IMPORTANT] onSessionRun -> Stopping the Session now. ')
}
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SOCIAL_BOTS_MODULE.sendMessage(
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type + " '" +
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.name + "' is starting.")
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> onSessionRun -> err = " + err.stack);
}
}
function onSessionStop() {
TS.projects.foundations.functionLibraries.sessionFunctions.stopSession(processIndex, 'Session Stopped From the User Interface.')
}
function onSessionResume(message) {
try {
if (TS.projects.foundations.functionLibraries.sessionFunctions.stopSession === undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] onSessionResume -> Event received to resume the Session that have never be ran before. ")
return
}
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
if (
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS === 'Idle' ||
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS === 'Running') {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] onSessionResume -> Event received to resume the Session while it was already running. ")
return
}
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_RESUMING = true
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING = false
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SOCIAL_BOTS_MODULE.sendMessage(
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type + " '" +
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.name + "' is resuming.")
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> onSessionResume -> err = " + err.stack);
}
}
function setUpSessionFolderName() {
/*
The session object is overwritten when the session is run. For that reason we
need to setup again the folder name at the Session level.
Set the folderName for logging, reports, context and data output
*/
let config
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.config !== undefined) {
config = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.config
if (config.folderName === undefined) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_FOLDER_NAME =
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type.replace(' ', '-').replace(' ', '-') + '-' +
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.id
} else {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_FOLDER_NAME =
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type.replace(' ', '-').replace(' ', '-') + '-' +
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.config.folderName
}
}
}
function checkParemeters() {
/*
Here we check all the Session Parameters received. If something critical is missing we abort returning false. If something
non critical is missing, we complete it with a default value.
*/
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters === undefined) {
let errorMessage = "Parameters Node Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(
processIndex,
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE,
errorMessage,
docs
)
return false
}
/* Time Range */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange === undefined) { // if the Time Range is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange = {
name: 'Missing Time Range',
type: 'Time Range',
config: {
initialDatetime: (new Date()).valueOf(),
finalDatetime: (new Date()).valueOf() + SA.projects.foundations.globals.timeConstants.ONE_YEAR_IN_MILISECONDS
}
}
} else {
/* Check that we received valid dates */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type === 'Backtesting Session') {
if (isNaN(new Date(TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime)).valueOf()) {
let errorMessage = "Invalid Initial Datetime Property Value"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {
currentValue: {
style: "Json",
text: TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime
}
}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange, errorMessage, docs)
return false
}
}
if (isNaN(new Date(TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.finalDatetime)).valueOf()) {
let errorMessage = "Invalid Initial Datetime Property Value"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {
currentValue: {
style: "Json",
text: TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.finalDatetime
}
}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange, errorMessage, docs)
return false
}
}
/* Session Type Forced Values */
let today = (new Date()).valueOf()
let aYearAgo = today - SA.projects.foundations.globals.timeConstants.ONE_YEAR_IN_MILISECONDS
let aYearFromNow = today + SA.projects.foundations.globals.timeConstants.ONE_YEAR_IN_MILISECONDS
switch (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type) {
case 'Backtesting Session': {
useDefaultDatetimes(aYearAgo, today)
break
}
case 'Live Portfolio Session': {
useDefaultDatetimes(today, aYearFromNow)
break
}
case 'Fordward Testing Session': {
useDefaultDatetimes(today, aYearFromNow)
break
}
case 'Paper Portfolio Session': {
useDefaultDatetimes(today, aYearFromNow)
break
}
}
function useDefaultDatetimes(initialDefault, finalDefault) {
/*
Note that inside the system, we are going to deal with these
dates in their numeric value representation.
*/
/* Initial Datetime */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type === 'Backtesting Session') {
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime === undefined) {
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime = initialDefault
} else {
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime = (new Date(TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime)).valueOf()
}
} else {
/* Non backtest session can start from the past only if explicitly configured that way */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime === undefined || TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.allowStartingFromThePast !== true) {
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime = initialDefault
} else {
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime = (new Date(TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.initialDatetime)).valueOf()
}
}
/* Final Datetime */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.finalDatetime === undefined) {
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.finalDatetime = finalDefault
} else {
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.finalDatetime = (new Date(TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeRange.config.finalDatetime)).valueOf()
}
}
/* Time Frame */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame === undefined) {
let errorMessage = "Time Frame Node Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters, errorMessage, docs)
return false
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.label === undefined) {
let errorMessage = "Label Property Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame, errorMessage, docs)
return false
}
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.value = getTimeFrameFromLabel(TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.label)
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.value === undefined) {
let errorMessage = "Invalid Label Property Value"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {
currentValue: {
style: "Json",
text: TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.label
}
}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame, errorMessage, docs)
return false
}
/* Session Base Asset */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset === undefined) {
let errorMessage = "Session Base Asset Node Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters, errorMessage, docs)
return false
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset.config.initialBalance === undefined) {
let errorMessage = "Initial Balance Property Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset, errorMessage, docs)
return false
}
/* Session Quoted Asset */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset === undefined) {
let errorMessage = "Session Quoted Asset Node Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters, errorMessage, docs)
return false
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset.config.initialBalance === undefined) {
let errorMessage = "Initial Balance Property Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkParemeters -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset, errorMessage, docs)
return false
}
/* Slippage */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage === undefined) { // if the Slippage is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage = {
name: 'Missing Slippage',
type: 'Slippage',
config: {
marketOrderRate: 0,
stopLoss: 0,
takeProfit: 0
}
}
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage.config.marketOrderRate === undefined) { // if the marketOrderRate is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage.config.marketOrderRate = 0
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage.config.stopLoss === undefined) { // if the stopLoss is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage.config.stopLoss = 0
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage.config.takeProfit === undefined) { // if the takeProfit is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.slippage.config.takeProfit = 0
}
/* Fee Structure */
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.feeStructure === undefined) { // if the Fee Structure is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.feeStructure = {
name: 'Missing Fee Structure',
type: 'Fee Structure',
config: {
maker: 0,
taker: 0
}
}
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.feeStructure.config.maker === undefined) { // if the maker is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.feeStructure.config.maker = 0
}
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.feeStructure.config.taker === undefined) { // if the taker is missing we create a default one.
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.feeStructure.config.taker = 0
}
return true
}
function startBackTesting(message) {
return true
}
function checkKey() {
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference === undefined) {
let errorMessage = "Key Reference Node Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkKey -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.taskConstants.TASK_NODE, errorMessage, docs)
return false
}
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent === undefined) {
let errorMessage = "Exchange Account Key Node Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkKey -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference, errorMessage, docs)
return false
}
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent.config.codeName === undefined) {
let errorMessage = "Codename Property Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkKey -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent, errorMessage, docs)
return false
}
if (TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent.config.secret === undefined) {
let errorMessage = "Secret Property Missing"
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> checkKey -> " + errorMessage)
let docs = {
project: 'Foundations',
category: 'Topic',
type: 'TS Portfolio Session Error - ' + errorMessage,
placeholder: {}
}
TS.projects.foundations.functionLibraries.sessionFunctions.sessionError(processIndex, TS.projects.foundations.globals.taskConstants.TASK_NODE.keyReference.referenceParent, errorMessage, docs)
return false
}
return true
}
function startLivePortfolio() {
return checkKey()
}
function startFordwardTesting(message) {
if (checkKey() === false) { return false }
/* Reduce the balance */
let balancePercentage = 1 // This is the default value
if (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.config.balancePercentage !== undefined) {
balancePercentage = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.config.balancePercentage
}
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset.config.initialBalance = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset.config.initialBalance * balancePercentage / 100
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset.config.initialBalance = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset.config.initialBalance * balancePercentage / 100
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset.config.minimumBalance = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset.config.minimumBalance * balancePercentage / 100
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset.config.minimumBalance = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset.config.minimumBalance * balancePercentage / 100
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset.config.maximumBalance = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionBaseAsset.config.maximumBalance * balancePercentage / 100
TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset.config.maximumBalance = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.sessionQuotedAsset.config.maximumBalance * balancePercentage / 100
TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.normalWaitTime = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.value
return true
}
function startPaperPortfolio(message) {
return true
}
function getTimeFrameFromLabel(timeFrameLabel) {
for (let i = 0; i < TS.projects.foundations.globals.timeFrames.marketTimeFramesArray().length; i++) {
let value = TS.projects.foundations.globals.timeFrames.marketTimeFramesArray()[i][0]
let label = TS.projects.foundations.globals.timeFrames.marketTimeFramesArray()[i][1]
if (timeFrameLabel === label) {
return value
}
}
for (let i = 0; i < TS.projects.foundations.globals.timeFrames.dailyTimeFramesArray().length; i++) {
let value = TS.projects.foundations.globals.timeFrames.dailyTimeFramesArray()[i][0]
let label = TS.projects.foundations.globals.timeFrames.dailyTimeFramesArray()[i][1]
if (timeFrameLabel === label) {
return value
}
}
}
} catch (err) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> err = " + err.stack);
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
}
}
};
@@ -0,0 +1,616 @@
exports.newPortfolioManagementBotModulesSingleMarketPortfolioBot = function (processIndex) {
const MODULE_NAME = "Single Market Portfolio Bot";
let session = TS.projects.portfolioManagement.botModules.portfolioSession.newPortfolioManagementBotModulesPortfolioSession(processIndex)
let nextLoopTimeoutHandle;
let thisObject = {
initialize: initialize,
run: run
};
return thisObject;
function initialize(callBackFunction) {
try {
/* We will check that we have received all the nodes needed to run this bot. */
if (TS.projects.foundations.functionLibraries.singleMarketFunctions.checkUpstreamOfTaskNode(processIndex) === false) {
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE)
return
}
/* Here we setup the path prefix that will be used when writing data or logs to disk. */
TS.projects.foundations.functionLibraries.singleMarketFunctions.initializeFilePathRoot(processIndex)
session.initialize(callBackFunction)
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] initialize -> err = " + err.stack);
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
}
}
function run(callBackFunction) {
try {
/* Some initial values*/
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING = true;
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
'[IMPORTANT] run -> Stopping the Session now. ')
loop();
function loop() {
try {
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Running...")
function pad(str, max) {
str = str.toString();
return str.length < max ? pad(" " + str, max) : str;
}
/* For each loop we want to create a new log file. */
if (TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT !== undefined) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.finalize()
}
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT = TS.projects.foundations.taskModules.debugLog.newFoundationsTaskModulesDebugLog(processIndex)
TS.projects.foundations.globals.taskVariables.LOGGER_MAP.set(MODULE_NAME + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].id, TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT)
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).MAIN_LOOP_COUNTER++;
let nextWaitTime;
/* We tell the UI that we are running. */
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Running...")
/* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_DATETIME = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution.
/* Main Loop Console Logging */
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.newMainLoop()
/* Checking if we need to need to emit any event */
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS === 'Idle' && TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING === false) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS = 'Running'
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_BEGIN = new Date()
}
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS === 'Running' && TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING === true) {
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS = 'Stopped'
}
TS.projects.foundations.functionLibraries.sessionFunctions.emitSessionStatus(TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_STATUS, TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY)
/* Checking if we should process this loop or not.*/
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).IS_SESSION_STOPPING === true) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> Waiting for " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type + " " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.name + " to be run.")
console.log(new Date().toISOString() + " " + pad(TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.config.codeName, 20) + " " + pad(TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName, 30)
+ " Waiting for " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type + " " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.name + " to be run. ");
nextWaitTime = 'Waiting for Session';
loopControl(nextWaitTime);
return
}
/* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */
let processExecutionEvents
let userBot;
let processFramework;
let statusDependencies;
let dataDependencies;
initializeProcessExecutionEvents();
function initializeProcessExecutionEvents() {
try {
processExecutionEvents = TS.projects.foundations.processModules.processExecutionEvents.newFoundationsProcessModulesProcessExecutionEvents(processIndex)
processExecutionEvents.initialize(onInizialized);
function onInizialized(err) {
try {
switch (err.result) {
case TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well.");
startProcessExecutionEvents()
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Retry Later. Requesting Execution Retry.");
nextWaitTime = 'Retry';
loopControl(nextWaitTime);
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process.");
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
function startProcessExecutionEvents() {
try {
processExecutionEvents.start(onStarted);
function onStarted(err) {
try {
switch (err.result) {
case TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well.");
if (TS.projects.foundations.globals.taskVariables.IS_TASK_STOPPING === true) {
loopControl()
return
}
initializeStatusDependencies();
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> Retry Later. Requesting Execution Retry.");
nextWaitTime = 'Retry';
loopControl(nextWaitTime);
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process.");
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
function initializeStatusDependencies() {
try {
statusDependencies = TS.projects.foundations.processModules.statusDependencies.newFoundationsProcessModulesStatusDependencies(processIndex);
statusDependencies.initialize(onInizialized);
function onInizialized(err) {
try {
switch (err.result) {
case TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well.");
initializeDataDependencies();
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> initializeStatusDependencies -> onInizialized -> Retry Later. Requesting Execution Retry.");
nextWaitTime = 'Retry';
loopControl(nextWaitTime);
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process.");
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
function initializeDataDependencies() {
try {
dataDependencies = TS.projects.foundations.processModules.dataDependencies.newFoundationsProcessModulesDataDependencies(processIndex);
dataDependencies.initialize(onInizialized);
function onInizialized(err) {
try {
switch (err.result) {
case TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well.")
switch (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.framework.name) {
case 'Low-Frequency-Portfolio-Process': {
processFramework = TS.projects.portfolioManagement.botModules.portfolioProcess.newPortfolioManagementBotModulesPortfolioProcess(processIndex);
startProcessFramework();
break;
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported.");
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.framework.name);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> initializeDataDependencies -> onInizialized -> Retry Later. Requesting Execution Retry.");
nextWaitTime = 'Retry';
loopControl(nextWaitTime);
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process.");
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> initializeDataDependencies -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
function startProcessFramework() {
try {
processFramework.start(statusDependencies, dataDependencies, onFinished);
function onFinished(err) {
try {
processFramework = undefined
dataDependencies.finalize()
dataDependencies = undefined
statusDependencies.finalize()
statusDependencies = undefined
switch (err.result) {
case TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well.");
nextWaitTime = 'Normal';
finishProcessExecutionEvents()
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> startProcessFramework -> onFinished -> Retry Later. Requesting Execution Retry.");
nextWaitTime = 'Retry';
loopControl(nextWaitTime);
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process.");
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
case TS.projects.foundations.globals.standardResponses.CUSTOM_OK_RESPONSE.result: {
switch (err.message) {
case "Dependency does not exist.": {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep.");
nextWaitTime = 'Sleep';
loopControl(nextWaitTime);
return;
}
case "Dependency not ready.": {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep.");
nextWaitTime = 'Sleep';
loopControl(nextWaitTime);
return;
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> startProcessFramework -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
function finishProcessExecutionEvents() {
try {
processExecutionEvents.finish(onFinished);
function onFinished(err) {
try {
processExecutionEvents.finalize()
processExecutionEvents = undefined
switch (err.result) {
case TS.projects.foundations.globals.standardResponses.DEFAULT_OK_RESPONSE.result: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well.");
nextWaitTime = 'Normal';
loopControl(nextWaitTime);
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> Retry Later. Requesting Execution Retry.");
nextWaitTime = 'Retry';
loopControl(nextWaitTime);
return;
}
case TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process.");
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
default: {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message);
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
function loopControl(nextWaitTime) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime)
/* We show we reached the end of the loop. */
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Running...")
/* Here we check if we must stop the loop gracefully. */
shallWeStop(onStop, onContinue);
function onStop() {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!")
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.persist();
TS.projects.foundations.globals.taskConstants.EVENT_SERVER_CLIENT_MODULE_OBJECT.raiseEvent(TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_KEY, 'Stopped')
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return;
}
function onContinue() {
/* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */
switch (nextWaitTime) {
case 'Waiting for Session': {
let waitTime = TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.sessionRunWaitTime
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
let waitingTime = waitTime / 1000 / 60
let label = 'minute/s'
if (waitingTime < 1) {
waitingTime = waitTime / 1000
label = 'seconds'
}
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Waiting " + waitingTime + " " + label + " for " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.type + " " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].session.name + " to be run. ")
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.persist();
}
break
case 'Normal': {
let waitTime
if (TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).WAIT_FOR_EXECUTION_FINISHED_EVENT === true) {
waitTime = 0
} else {
switch (TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.type) {
case 'Live Portfolio Session': {
waitTime = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.value
break
}
case 'Fordward Tessting Session': {
waitTime = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.value
break
}
case 'Paper Portfolio Session': {
waitTime = TS.projects.foundations.globals.processConstants.CONSTANTS_BY_PROCESS_INDEX_MAP.get(processIndex).SESSION_NODE.portfolioParameters.timeFrame.config.value
break
}
case 'Backtesting Session': {
waitTime = 0
break
}
}
}
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s.")
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
let waitingTime = waitTime / 1000 / 60
let label = 'minute/s'
if (waitingTime < 1) {
waitingTime = waitTime / 1000
label = 'seconds'
}
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Waiting " + waitingTime + " " + label + " for next execution.")
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.persist();
}
break;
case 'Retry': {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> loopControl -> Restarting Loop in " + (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.retryWaitTime / 1000) + " seconds.")
nextLoopTimeoutHandle = setTimeout(loop, TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.retryWaitTime);
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Trying to recover from some problem. Waiting " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.retryWaitTime / 1000 + " seconds for next execution.")
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.persist();
}
break;
case 'Sleep': {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> loopControl -> Restarting Loop in " + (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.sleepWaitTime / 60000) + " minutes.")
nextLoopTimeoutHandle = setTimeout(loop, TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.sleepWaitTime);
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Waiting " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.sleepWaitTime / 60000 + " minutes for next execution.")
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.persist();
}
break;
case 'Coma': {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[INFO] run -> loop -> loopControl -> Restarting Loop in " + (TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.comaWaitTime / 3600000) + " hours.")
nextLoopTimeoutHandle = setTimeout(loop, TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.comaWaitTime);
TS.projects.foundations.functionLibraries.processFunctions.processHeartBeat(processIndex, undefined, undefined, "Waiting " + TS.projects.foundations.globals.taskConstants.TASK_NODE.bot.processes[processIndex].referenceParent.config.comaWaitTime / 3600000 + " hours for next execution.")
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.persist();
}
break;
}
}
}
function shallWeStop(stopCallBack, continueCallBack) {
try {
/* IMPORTANT: This function is exactly the same on the 3 modules. */
if (!TS.projects.foundations.globals.taskVariables.IS_TASK_STOPPING) {
continueCallBack();
} else {
stopCallBack();
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> shallWeStop -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> loop -> err = " + err.stack);
TS.projects.foundations.globals.processVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).UNEXPECTED_ERROR = err
TS.projects.foundations.functionLibraries.processFunctions.stopProcess(processIndex, callBackFunction, nextLoopTimeoutHandle)
return
}
}
} catch (err) {
TS.projects.foundations.globals.taskVariables.UNEXPECTED_ERROR = err
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).PROCESS_INSTANCE_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
"[ERROR] run -> err = " + err.stack);
clearTimeout(nextLoopTimeoutHandle);
callBackFunction(TS.projects.foundations.globals.standardResponses.DEFAULT_FAIL_RESPONSE);
}
}
};
@@ -0,0 +1,42 @@
function newPortfolioManagementActionSwitch() {
let thisObject = {
executeAction: executeAction,
initialize: initialize,
finalize: finalize
}
return thisObject
function finalize() {
}
function initialize() {
/* Nothing to initialize since a Function Library does not hold any state. */
}
async function executeAction(action) {
switch (action.name) {
case 'Run Portfolio Session':
{
UI.projects.portfolioManagement.functionLibraries.portfolioSessionFunctions.runSession(action.node, false, action.callBackFunction)
}
break
case 'Resume Portfolio Session':
{
UI.projects.portfolioManagement.functionLibraries.portfolioSessionFunctions.runSession(action.node, true, action.callBackFunction)
}
break
case 'Stop Portfolio Session':
{
UI.projects.portfolioManagement.functionLibraries.portfolioSessionFunctions.stopSession(action.node, action.callBackFunction)
}
break
default: {
console.log("[WARN] Action sent to Portfolio-Management Action Switch does not belong here. Verify at the App Schema file of the node that triggered this action that the actionProject is pointing to the right project. -> Action = " + action.name + " -> Action Node Name = " + action.node.name)
}
}
}
}
@@ -0,0 +1,350 @@
function newPortfolioManagementFunctionLibraryPortfolioSessionFunctions() {
let thisObject = {
syncronizeSessionWithBackEnd: syncronizeSessionWithBackEnd,
runSession: runSession,
stopSession: stopSession
}
return thisObject
function syncronizeSessionWithBackEnd(node) {
let validationsResult = validations(node)
if (validationsResult === undefined) {
/* If something fails at validations we just quit. */
return
}
let lanNetworkNode = validationsResult.lanNetworkNode
if (lanNetworkNode === undefined) {
/* Nodes that do not belong to a network can not get ready. */
return
}
let eventsServerClient = UI.projects.foundations.spaces.designSpace.workspace.eventsServerClients.get(lanNetworkNode.id)
/* First we setup everything so as to listen to the response from the Task Server */
let eventSubscriptionIdOnStatus
let key = node.name + '-' + node.type + '-' + node.id
eventsServerClient.listenToEvent(key, 'Status Response', undefined, node.id, onResponse, onStatus)
function onResponse(message) {
eventSubscriptionIdOnStatus = message.eventSubscriptionId
}
function onStatus(message) {
eventsServerClient.stopListening(key, eventSubscriptionIdOnStatus, node.id)
if (message.event.status === 'Portfolio Session Runnning') {
node.payload.uiObject.menu.internalClick('Run Portfolio Session')
}
}
/* Second we ask the Task Server if this Session is Running. */
eventsServerClient.raiseEvent(key, 'Portfolio Session Status')
}
function runSession(node, resume, callBackFunction) {
if (UI.environment.DEMO_MODE === true) {
if (window.location.hostname !== 'localhost') {
node.payload.uiObject.setWarningMessage('Superalgos is running is DEMO MODE. This means that you can not RUN Sessions.', 5)
callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE)
return
}
}
let validationsResult = validations(node)
if (validationsResult === undefined) {
/* If something fails at validations we just quit. */
return
}
let lanNetworkNode = validationsResult.lanNetworkNode
if (lanNetworkNode === undefined) {
/* This means that the validations failed. */
callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE)
return
}
let eventsServerClient = UI.projects.foundations.spaces.designSpace.workspace.eventsServerClients.get(lanNetworkNode.id)
let key = node.name + '-' + node.type + '-' + node.id
/* Setup Listener to check Parent Task Status of the Session */
let eventSubscriptionIdOnStatus
let eventHandlerKey = "Task Client - " + node.payload.parentNode.payload.parentNode.payload.parentNode.id
eventsServerClient.listenToEvent(eventHandlerKey, 'Task Status', undefined, node.id, onResponse, onStatus)
function onResponse(message) {
eventSubscriptionIdOnStatus = message.eventSubscriptionId
}
function onStatus(message) {
eventsServerClient.stopListening(eventHandlerKey, eventSubscriptionIdOnStatus, node.id)
/* If Task is Not Running Display Error */
if (message.event.status === 'Task Process Not Running') {
node.payload.uiObject.setErrorMessage('Parent Task Not Running.')
callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE)
} else {
node.payload.uiObject.run(eventsServerClient, callBackFunction)
}
}
/* Raise Event to Check Task Status */
let eventKey = {}
eventKey.taskId = node.payload.parentNode.payload.parentNode.payload.parentNode.id
eventsServerClient.raiseEvent("Task Manager", 'Task Status', eventKey)
let lightingPath = '' +
'Portfolio System->' +
'Dynamic Indicators->Indicator Function->Formula->' +
'Portfolio Strategy->' +
'Trigger Stage->Trigger On Event->Trigger Off Event->Take Position Event->' +
'Open Stage->' +
'Manage Stage->' +
'Managed Stop Loss->Managed Take Profit->' +
'Phase->Formula->Next Phase Event->Move To Phase Event->Phase->' +
'Situation->Condition->Javascript Code->' +
'Close Stage->' +
'Initial Targets->Target Size In Base Asset->Target Size In Quoted Asset->Target Rate->Formula->' +
'Open Execution->Close Execution->' +
'Execution Algorithm->Market Buy Order->Market Sell Order->Limit Buy Order->Limit Sell Order->' +
'Order Rate->Formula->' +
'Create Order Event->Cancel Order Event->' +
'Close Stage Event->' +
'Announcement->Announcement Formula->Announcement Condition->' +
'Size In Base Asset->Size In Quoted Asset->Position Rate->Formula->' +
'Situation->Condition->Javascript Code->' +
'Market Order->Limit Order->' +
'Simulated Exchange Events->Simulated Partial Fill->Simulated Actual Rate->Simulated Fees Paid->Formula->' +
'User Defined Code->Javascript Code->'
let portfolioSystem = UI.projects.visualScripting.functionLibraries.protocolNode.getProtocolNode(node.portfolioSystemReference.payload.referenceParent, false, true, true, false, false, lightingPath)
lightingPath = '' +
'Portfolio Engine->' +
'Dynamic Indicators->Indicator Function->' +
'Portfolio Current->Portfolio Last->Previous->' +
'Portfolio Episode->' +
'Episode Base Asset->Episode Quoted Asset->Portfolio Episode Counters->Portfolio Episode Statistics->' +
'Strategies->Positions->Orders->Hits->Fails->User Defined Counter->' +
'Profit Loss->Hit Ratio->Hit Fail->Days->ROI->Annualized Rate Of Return->User Defined Statistic->' +
'Candle->Cycle->' +
'Begin->End->Last Begin->Last End->Open->Close->Min->Max->Index->' +
'Distance To Portfolio Event->' +
'Head Of The Market->Process Date->' +
'Trigger On->Trigger Off->Take Position->Close Position->Next Phase->Move To Phase->Create Order->Cancel Order->Close Order->' +
'Strategy->' +
'Strategy Counters->' +
'Position->' +
'Entry Target Rate->Exit Target Rate->' +
'Stop Loss->Stop Loss Phase->Stop Loss Position->Begin->End->Initial Value->Final Value->' +
'Take Profit->Take Profit Phase->Take Profit Position->Begin->End->Initial Value->Final Value->' +
'Position Counters->' +
'Position Statistics->Days->User Defined Statistic->' +
'Position Base Asset->Position Quoted Asset->Entry Target Size->Exit Target Size->' +
'Profit Loss->ROI->Hit Fail->' +
'Exchange Orders->Market Buy Orders->Market Sell Orders->Limit Buy Orders->Limit Sell Orders->' +
'Market Order->Limit Order->' +
'Exchange Id->Rate->Order Name->Algorithm Name->Lock->' +
'Order Counters->' +
'Order Base Asset->Order Quoted Asset->' +
'Actual Size->Size->Size Filled->Amount Received->Fees Paid->Fees To Be Paid->' +
'Order Statistics->Percentage Filled->Actual Rate->Days->User Defined Statistic->' +
'Strategy Trigger Stage->Strategy Open Stage->Strategy Manage Stage->Strategy Close Stage->' +
'Begin->End->Exit Type->Status->Begin Rate->End Rate->Stage Base Asset->Stage Quoted Asset->Size Placed->Target Size->Size Filled->Fees Paid->Stage Defined In->' +
'Serial Number->Identifier->Begin->End->Begin Rate->End Rate->Strategy Name->Status->Exit Type->' +
'Balance->Begin Balance->End Balance->' +
'Index->Situation Name->Formula->Periods->' +
'User Defined Variables->User Defined Variable->'
let portfolioEngine = UI.projects.visualScripting.functionLibraries.protocolNode.getProtocolNode(node.portfolioEngineReference.payload.referenceParent, false, true, true, false, false, lightingPath)
lightingPath = '' +
'Backtesting Session->Paper Portfolio Session->Forward Testing Session->Live Portfolio Session->' +
'Portfolio Parameters->' +
'Session Base Asset->Session Quoted Asset->Time Range->Time Frame->Slippage->Fee Structure->Snapshots->Heartbeats->User Defined Parameters->' +
'Social Bots->Telegram Bot->Discord Bot->Slack Bot->Twitter Bot->' +
'Social Bot Command->Formula->' +
'Exchange Account Asset->Asset->'
let session = UI.projects.visualScripting.functionLibraries.protocolNode.getProtocolNode(node, false, true, true, false, false, lightingPath)
let defaultExchange = UI.projects.visualScripting.utilities.nodeConfig.loadConfigProperty(validationsResult.exchange.payload, 'codeName')
let defaultMarket =
UI.projects.visualScripting.utilities.nodeConfig.loadConfigProperty(validationsResult.market.baseAsset.payload.referenceParent.payload, 'codeName')
+ '-' +
UI.projects.visualScripting.utilities.nodeConfig.loadConfigProperty(validationsResult.market.quotedAsset.payload.referenceParent.payload, 'codeName')
let dependencyFilter = UI.projects.foundations.functionLibraries.dependenciesFilter.createDependencyFilter(
defaultExchange,
defaultMarket,
node.portfolioSystemReference.payload.referenceParent
)
/* Raise event to run the session */
let event = {
session: JSON.stringify(session),
portfolioSystem: JSON.stringify(portfolioSystem),
portfolioEngine: JSON.stringify(portfolioEngine),
dependencyFilter: JSON.stringify(dependencyFilter),
resume: resume
}
if (resume !== true) {
eventsServerClient.raiseEvent(key, 'Run Portfolio Session', event)
} else {
eventsServerClient.raiseEvent(key, 'Resume Portfolio Session', event)
}
if (node.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {
callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE)
return
}
}
function stopSession(node, callBackFunction) {
if (UI.environment.DEMO_MODE === true) {
if (window.location.hostname !== 'localhost') {
node.payload.uiObject.setWarningMessage('Superalgos is running is DEMO MODE. This means that you can not STOP Sessions.', 5)
callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE)
return
}
}
let validationsResult = validations(node)
if (validationsResult === undefined) {
/* If something fails at validations we just quit. */
return
}
let lanNetworkNode = validationsResult.lanNetworkNode
if (lanNetworkNode === undefined) {
/* This means that the validations failed. */
callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE)
return
}
let eventsServerClient = UI.projects.foundations.spaces.designSpace.workspace.eventsServerClients.get(lanNetworkNode.id)
let key = node.name + '-' + node.type + '-' + node.id
eventsServerClient.raiseEvent(key, 'Stop Portfolio Session')
node.payload.uiObject.stop(callBackFunction, undefined, true)
}
function validations(node) {
let result = {}
if (node.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs a Process Instance parent to be able to run.')
return
}
if (node.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside a Process Instance node.')
return
}
if (node.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside a Task.')
return
}
if (node.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside a Task Manager.')
return
}
result.taskManager = node.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode
if (result.taskManager.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside Mine Tasks.')
return
}
if (result.taskManager.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside Market Tasks.')
return
}
if (result.taskManager.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside Exchange Tasks.')
return
}
if (result.taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside a Testing or Production Portfolio Tasks.')
return
}
if (result.taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to be inside a Network Node.')
return
}
result.lanNetworkNode = UI.projects.visualScripting.utilities.meshes.findNodeInNodeMesh(result.taskManager, 'LAN Network Node', undefined, true, false, true, false)
if (node.portfolioSystemReference === undefined) {
node.payload.uiObject.setErrorMessage('Session needs a child Portfolio System Reference.')
return
}
if (node.portfolioEngineReference === undefined) {
node.payload.uiObject.setErrorMessage('Session needs a child Portfolio Engine Reference.')
return
}
if (node.portfolioSystemReference.payload.referenceParent === undefined) {
node.payload.uiObject.setErrorMessage('Portfolio System Reference needs to reference a Portfolio System.')
return
}
if (node.portfolioEngineReference.payload.referenceParent === undefined) {
node.payload.uiObject.setErrorMessage('Portfolio Engine Reference needs to reference a Portfolio Engine.')
return
}
if (result.taskManager.payload.parentNode.payload.parentNode.payload.referenceParent === undefined) {
node.payload.uiObject.setErrorMessage('Session needs to have a Default Market.')
return
}
result.market = result.taskManager.payload.parentNode.payload.parentNode.payload.referenceParent
if (result.market.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Default Market needs to be a child of Exchange Markets.')
return
}
if (result.market.payload.parentNode.payload.parentNode === undefined) {
node.payload.uiObject.setErrorMessage('Exchange Markets neeed to be a child of Crypto Exchange.')
return
}
if (result.market.baseAsset === undefined) {
node.payload.uiObject.setErrorMessage('Default Market needs to have a Base Asset.')
return
}
if (result.market.quotedAsset === undefined) {
node.payload.uiObject.setErrorMessage('Default Market needs to have a Quoted Asset.')
return
}
if (result.market.baseAsset.payload.referenceParent === undefined) {
node.payload.uiObject.setErrorMessage('Market Base Asset needs to reference an Asset.')
return
}
if (result.market.quotedAsset.payload.referenceParent === undefined) {
node.payload.uiObject.setErrorMessage('Market Quoted Asset needs to reference an Asset.')
return
}
result.exchange = result.market.payload.parentNode.payload.parentNode
return result
}
}
+152
View File
@@ -151,6 +151,158 @@
"Trading Tasks"
]
},
{
"name": "Portfolio-Management",
"propertyName": "portfolioManagement",
"UI": {
"functionLibraries": [
{
"name": "Portfolio Session Functions",
"propertyName": "portfolioSessionFunctions",
"functionName": "newPortfolioManagementFunctionLibraryPortfolioSessionFunctions"
}
]
},
"TS": {
"botModules": [
{
"name": "Single Market Portfolio Bot",
"propertyName": "singleMarketPortfolioBot",
"functionName": "newPortfolioManagementBotModulesSingleMarketPortfolioBot",
"fileName": "SingleMarketPortfolioBot.js",
"folderName": "Portfolio-Bot",
"botType": "Portfolio Bot"
},
{
"name": "Portfolio Session",
"propertyName": "portfolioSession",
"functionName": "newPortfolioManagementBotModulesPortfolioSession",
"fileName": "PortfolioSession.js",
"folderName": "Portfolio-Bot"
},
{
"name": "Portfolio Process",
"propertyName": "portfolioProcess",
"functionName": "newPortfolioManagementBotModulesPortfolioProcess",
"fileName": "PortfolioProcess.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Engine",
"propertyName": "portfolioEngine",
"functionName": "newPortfolioManagementBotModulesPortfolioEngine",
"fileName": "PortfolioEngine.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Output",
"propertyName": "portfolioOutput",
"functionName": "newPortfolioManagementBotModulesPortfolioOutput",
"fileName": "PortfolioOutput.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Simulation",
"propertyName": "portfolioSimulation",
"functionName": "newPortfolioManagementBotModulesPortfolioSimulation",
"fileName": "PortfolioSimulation.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Episode",
"propertyName": "portfolioEpisode",
"functionName": "newPortfolioManagementBotModulesPortfolioEpisode",
"fileName": "PortfolioEpisode.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Records",
"propertyName": "portfolioRecords",
"functionName": "newPortfolioManagementBotModulesPortfolioRecords",
"fileName": "PortfolioRecords.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio System",
"propertyName": "portfolioSystem",
"functionName": "newPortfolioManagementBotModulesPortfolioSystem",
"fileName": "PortfolioSystem.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Stages",
"propertyName": "portfolioStages",
"functionName": "newPortfolioManagementBotModulesPortfolioStages",
"fileName": "PortfolioStages.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Strategy",
"propertyName": "portfolioStrategy",
"functionName": "newPortfolioManagementBotModulesPortfolioStrategy",
"fileName": "PortfolioStrategy.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Execution",
"propertyName": "portfolioExecution",
"functionName": "newPortfolioManagementBotModulesPortfolioExecution",
"fileName": "PortfolioExecution.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Position",
"propertyName": "portfolioPosition",
"functionName": "newPortfolioManagementBotModulesPortfolioPosition",
"fileName": "PortfolioPosition.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Snapshots",
"propertyName": "snapshots",
"functionName": "newPortfolioManagementBotModulesSnapshots",
"fileName": "Snapshots.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Portfolio Orders",
"propertyName": "portfolioOrders",
"functionName": "newPortfolioManagementBotModulesPortfolioOrders",
"fileName": "PortfolioOrders.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Orders Simulations",
"propertyName": "ordersSimulations",
"functionName": "newPortfolioManagementBotModulesOrdersSimulations",
"fileName": "OrdersSimulations.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Orders Calculations",
"propertyName": "ordersCalculations",
"functionName": "newPortfolioManagementBotModulesOrdersCalculations",
"fileName": "OrdersCalculations.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio"
},
{
"name": "Exchange API",
"propertyName": "exchangeAPI",
"functionName": "newPortfolioManagementBotModulesExchangeAPI",
"fileName": "ExchangeAPI.js",
"folderName": "Portfolio-Bot/Low-Frequency-Portfolio/APIs"
}
]
},
"plugins": [
"Portfolio Mines",
"Portfolio Systems",
"Portfolio Engines"
],
"tasks": [
"Portfolio Tasks"
]
},
{
"name": "Machine-Learning",
"propertyName": "machineLearning",