Merge remote-tracking branch 'upstream/develop' into portfolio-management
This commit is contained in:
commit
68258e27f0
53 files changed
+171697
-30
No files matched your search
+60
@@ -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
|
||||
+33431
File diff suppressed because it is too large.
Load diff
+3812
File diff suppressed because it is too large.
Load diff
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+45
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
+66
@@ -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—including those involving the exchange, such as orders placed or filled—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
|
||||
}
|
||||
]
|
||||
}
|
||||
+41
@@ -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—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
|
||||
}
|
||||
]
|
||||
}
|
||||
+39
@@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
+284
@@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
+352
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+538
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+229
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+319
@@ -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++
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
+1332
File diff suppressed because it is too large.
Load diff
+77
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+534
@@ -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'
|
||||
}
|
||||
}
|
||||
+320
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+414
@@ -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.'
|
||||
}
|
||||
}
|
||||
+679
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1327
File diff suppressed because it is too large.
Load diff
+87
@@ -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++
|
||||
}
|
||||
}
|
||||
}
|
||||
+451
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+481
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
+616
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user