From dbfea9c7db1f27de8d0511a2a576ecf4669d588d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 25 Jul 2020 20:04:59 +0200 Subject: [PATCH 001/112] Time Frames Filter --- MultiPeriodDaily.js | 44 +++++++++++++++++++++++++++++++-- MultiPeriodMarket.js | 58 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/MultiPeriodDaily.js b/MultiPeriodDaily.js index 2d5e9741d..24d605d62 100644 --- a/MultiPeriodDaily.js +++ b/MultiPeriodDaily.js @@ -276,9 +276,19 @@ } function timeFramesLoopBody() { + const timeFrame = global.dailyFilePeriods[n][0] const timeFrameLabel = global.dailyFilePeriods[n][1] + /* Check Time Frames Filter */ + if (bot.dailyTimeFrames !== undefined) { + if (bot.dailyTimeFrames.includes(timeFrameLabel) === false) { + /* We are not going to process this Time Frame */ + timeFramesControlLoop() + return + } + } + if (processConfig.framework.validPeriods !== undefined) { let validPeriod = false; for (let i = 0; i < processConfig.framework.validPeriods.length; i++) { @@ -417,8 +427,6 @@ } function generateOutput() { - const timeFrame = global.dailyFilePeriods[n][0] - const timeFrameLabel = global.dailyFilePeriods[n][1] indicatorOutputModule.start( dataFiles, @@ -502,6 +510,38 @@ } global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) + writeTimeFramesFile(productCodeName, callBack) + } + } + + function writeTimeFramesFile(productCodeName, callBack) { + + let timeFramesArray = [] + for (let n = 0; n < global.dailyFilePeriods.length; n++) { + let timeFrameLabel = global.dailyFilePeriods[n][1] + + /* Check Time Frames Filter */ + if (bot.marketTimeFrames !== undefined) { + if (bot.dailyTimeFrames.includes(timeFrameLabel) === true) { + timeFramesArray.push(timeFrameLabel) + } + } else { + timeFramesArray.push(timeFrameLabel) + } + } + + let fileContent = JSON.stringify(timeFramesArray) + let fileName = '/Time.Frames.json'; + let filePath = bot.filePathRoot + "/Output/" + productCodeName + "/" + bot.process + fileName; + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> writeTimeFramesFiles -> onFileCreated -> err = " + err.stack) + callBack(err) + return + } callBack(global.DEFAULT_OK_RESPONSE) } } diff --git a/MultiPeriodMarket.js b/MultiPeriodMarket.js index 3e29f55f7..02a35a702 100644 --- a/MultiPeriodMarket.js +++ b/MultiPeriodMarket.js @@ -7,6 +7,7 @@ }; let utilities = UTILITIES.newCloudUtilities(logger) + let fileStorage = FILE_STORAGE.newFileStorage(logger) let statusDependencies; let dataDependenciesModule; @@ -31,6 +32,7 @@ } function finalize() { + fileStorage = undefined dataFiles = undefined statusDependencies = undefined dataDependenciesModule = undefined @@ -130,11 +132,65 @@ if (n < global.marketFilesPeriods.length) { timeFramesLoopBody() } else { - writeStatusReport(callBackFunction) + writeTimeFramesFiles(onTimeFrameFilesWritten) + function onTimeFrameFilesWritten() { + writeStatusReport(callBackFunction) + } } } } + function writeTimeFramesFiles(callBack) { + let outputDatasetIndex = -1; + controlLoop() + + function productLoopBody() { + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + writeTimeFramesFile(productCodeName, controlLoop) + } + + function controlLoop() { + outputDatasetIndex++ + if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { + productLoopBody() + } else { + callBack() + } + } + } + + function writeTimeFramesFile(productCodeName, callBack) { + + let timeFramesArray = [] + for (let n = 0; n < global.marketFilesPeriods.length; n++) { + let timeFrameLabel = global.marketFilesPeriods[n][1] + + /* Check Time Frames Filter */ + if (bot.marketTimeFrames !== undefined) { + if (bot.marketTimeFrames.includes(timeFrameLabel) === true) { + timeFramesArray.push(timeFrameLabel) + } + } else { + timeFramesArray.push(timeFrameLabel) + } + } + + let fileContent = JSON.stringify(timeFramesArray) + let fileName = '/Time.Frames.json'; + let filePath = bot.filePathRoot + "/Output/" + productCodeName + "/" + bot.process + fileName; + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> writeTimeFramesFile -> onFileCreated -> err = " + err.stack) + callBack(err) + return + } + callBack() + } + } + function writeStatusReport(callBack) { let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period-Market" let thisReport = statusDependencies.statusReports.get(reportKey) From bbdbc274b4d0f76c673c560fa91513b0b1503460 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 28 Jul 2020 12:46:28 +0200 Subject: [PATCH 002/112] Update TradingProcess.js --- LowFrequencyTrading/TradingProcess.js | 70 ++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index eb23e6ae6..4ce95db5c 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -255,11 +255,11 @@ } } - /* - We do market files first since if the simulation is run on daily files, there will be a loop to getch each daus files and we do not need - that loop to reload market files. - */ function processMarketFiles() { + /* + We do market files first since if the simulation is run on daily files, there will be a loop to getch each daus files and we do not need + that loop to reload market files. + */ let n; timeFramesLoop(); @@ -603,22 +603,25 @@ bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution. - if (currentTimeFrame > global.dailyFilePeriods[0][0]) { - writeMarketStatusReport(onMarketStatusReport) - } else { - writeDataRanges(currentTimeFrameLabel, onWritten); + writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel, onTimeFrameFilesWritten) + function onTimeFrameFilesWritten() { + writeDataRanges(currentTimeFrameLabel, onDataRangesWritten); } - function onWritten(err) { + function onDataRangesWritten(err) { try { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack); callBackFunction(err); return; } - writeDailyStatusReport(bot.tradingProcessDate, onDailyStatusReport); + if (currentTimeFrame > global.dailyFilePeriods[0][0]) { + writeMarketStatusReport(onMarketStatusReport) + } else { + writeDailyStatusReport(bot.tradingProcessDate, onDailyStatusReport); + } + } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -727,6 +730,51 @@ thisReport.save(callBack); } + function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel, callBack) { + let outputDatasetIndex = -1; + controlLoop() + + function productLoopBody() { + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily', dailyFileWritten) + + function dailyFileWritten() { + writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Market', controlLoop) + } + } + + function controlLoop() { + outputDatasetIndex++ + if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { + productLoopBody() + } else { + callBack() + } + } + } + + function writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, processType, callBack) { + + let timeFramesArray = [] + timeFramesArray.push(currentTimeFrameLabel) + + let fileContent = JSON.stringify(timeFramesArray) + let fileName = '/Time.Frames.json'; + + let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + processType + fileName; + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> writeTimeFramesFile -> onFileCreated -> err = " + err.stack) + callBack(err) + return + } + callBack() + } + } + function trimDataFile(dataFile, recordDefinition) { /* Here we will discard all the records in a file that are outside of the current time range. From 50b378843d0e39d251680b8d9099a190b88f67a4 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 28 Jul 2020 13:27:33 +0200 Subject: [PATCH 003/112] Update TradingSystem.js --- LowFrequencyTrading/TradingSystem.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 7aafa99d0..64670330d 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -272,8 +272,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM */ value = false - if (code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || - code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1 + if (code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 ) { /* We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. @@ -313,8 +312,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM */ value = 0 - if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || - node.code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1 + if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 ) { /* We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. From 3bdd8577436357b66f8df5c37115f88d677b6177 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 28 Jul 2020 17:27:42 +0200 Subject: [PATCH 004/112] Bug Dataset Updated Fixed --- LowFrequencyTrading/TradingProcess.js | 10 ++++++---- MultiPeriodDaily.js | 5 ++++- ProcessOutput.js | 1 - 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 4ce95db5c..aee4a0f74 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -617,9 +617,9 @@ } if (currentTimeFrame > global.dailyFilePeriods[0][0]) { - writeMarketStatusReport(onMarketStatusReport) + writeMarketStatusReport(currentTimeFrameLabel, onMarketStatusReport) } else { - writeDailyStatusReport(bot.tradingProcessDate, onDailyStatusReport); + writeDailyStatusReport(bot.tradingProcessDate, currentTimeFrameLabel, onDailyStatusReport); } } catch (err) { @@ -709,22 +709,24 @@ } } - function writeDailyStatusReport(lastFileDate, callBack) { + function writeDailyStatusReport(lastFileDate, currentTimeFrameLabel, callBack) { let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.currentDaytime; thisReport.file.lastFile = lastFileDate; thisReport.file.simulationState = bot.simulationState; + thisReport.file.timeFrames = currentTimeFrameLabel thisReport.save(callBack); } - function writeMarketStatusReport(callBack) { + function writeMarketStatusReport(currentTimeFrameLabel, callBack) { let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.processDatetime; thisReport.file.simulationState = bot.simulationState; + thisReport.file.timeFrames = currentTimeFrameLabel logger.newInternalLoop(bot.codeName, bot.process, bot.tradingProcessDate); thisReport.save(callBack); diff --git a/MultiPeriodDaily.js b/MultiPeriodDaily.js index 24d605d62..b3d0899ce 100644 --- a/MultiPeriodDaily.js +++ b/MultiPeriodDaily.js @@ -521,7 +521,7 @@ let timeFrameLabel = global.dailyFilePeriods[n][1] /* Check Time Frames Filter */ - if (bot.marketTimeFrames !== undefined) { + if (bot.dailyTimeFrames !== undefined) { if (bot.dailyTimeFrames.includes(timeFrameLabel) === true) { timeFramesArray.push(timeFrameLabel) } @@ -554,6 +554,9 @@ thisReport.file.lastFile = lastFileDate; thisReport.file.interExecutionMemoryArray = interExecutionMemoryArray; thisReport.file.beginingOfMarket = beginingOfMarket.toUTCString() + if (bot.dailyTimeFrames !== undefined) { + thisReport.file.timeFrames = bot.dailyTimeFrames + } thisReport.save(callBack) bot.hasTheBotJustStarted = false; diff --git a/ProcessOutput.js b/ProcessOutput.js index f9cb330d1..d9ee5a6ba 100644 --- a/ProcessOutput.js +++ b/ProcessOutput.js @@ -88,7 +88,6 @@ } global.EVENT_SERVER_CLIENT.createEventHandler(key, 'Dataset Updated') global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Dataset Updated', event) - } } } From 00c47a74783faae4cbd777c02976fc80d569f706 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 29 Jul 2020 11:16:05 +0200 Subject: [PATCH 005/112] Bug Fixes --- LowFrequencyTrading/Announcements.js | 6 +----- LowFrequencyTrading/TradingProcess.js | 2 +- LowFrequencyTrading/TradingSimulation.js | 16 ++++++++++++---- LowFrequencyTrading/TradingSystem.js | 8 ++++++++ 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/LowFrequencyTrading/Announcements.js b/LowFrequencyTrading/Announcements.js index ec8c25d75..2c63c88d1 100644 --- a/LowFrequencyTrading/Announcements.js +++ b/LowFrequencyTrading/Announcements.js @@ -41,11 +41,7 @@ exports.newAnnouncements = function newAnnouncements(bot, logger) { if (canAnnounce === true) { let text = announcement.config.text if (announcement.formula !== undefined) { - try { - text = eval(announcement.formula.code) - } catch (err) { - tradingSystem.errors.push([announcement.formula.id, err.message]) - } + text = tradingSystem.formulas.get(announcement.formula.id) } if (bot.SESSION.socialBots !== undefined) { diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index aee4a0f74..7e66fe1e9 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -797,7 +797,7 @@ let dataRecord = dataFile[i] let begin = dataRecord[beginIndex] let end = dataRecord[endIndex] - if (end < bot.SESSION.parameters.timeRange.config.initialDatetime) { continue } + if (end < bot.SESSION.parameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element if (begin > bot.SESSION.parameters.timeRange.config.finalDatetime) { continue } result.push(dataRecord) } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 58470f756..4e8f655b2 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -240,8 +240,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue } let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement + if (thisChart[pluralVariableName] !== undefined) { + let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName) + if (currentElement !== undefined) { + thisChart[singularVariableName] = currentElement + } + } } } } @@ -257,8 +261,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue } let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement + if (thisChart[pluralVariableName] !== undefined) { + let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName) + if (currentElement !== undefined) { + thisChart[singularVariableName] = currentElement + } + } } } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 64670330d..a1755803f 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -656,6 +656,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(manageStage, 'Manage Stage') evalFormulas(manageStage, 'Manage Stage') + if ( + tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' || + tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage') { + let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + evalConditions(stageNode, 'Initial Definition') + evalFormulas(stageNode, 'Initial Definition') + } + /* Stop Loss Management */ checkStopPhasesEvents() calculateStopLoss() From bb0a790f3792f9f223a6af5c8d4b58596e630d3c Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 29 Jul 2020 13:55:59 +0200 Subject: [PATCH 006/112] Update TradingExecution.js --- LowFrequencyTrading/TradingExecution.js | 52 +++++++++++++++++++++---- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index b588689b7..74e82c1da 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -242,14 +242,23 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function actualRateSimulation() { /* Actual Rate Simulation */ + let calculatedBasedOnTradingSystem = false + + /* Based on the Trading System Definition */ if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { /* Calculate this only once for this order */ if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { + calculatedBasedOnTradingSystem = true + } } } - } else { + } + + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { switch (tradingEngineOrder.type) { case 'Market Order': { /* Actual Rate is simulated based on the Session Paremeters */ @@ -278,13 +287,20 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function feesPaidSimulation() { /* Fees Paid Simulation */ + let calculatedBasedOnTradingSystem = false + + /* Based on the Trading System Definition */ if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { if (tradingEngineOrder.orderStatistics.feesPaid.value === tradingEngineOrder.orderStatistics.feesPaid.config.initialValue) { tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + calculatedBasedOnTradingSystem = true } } - } else { + } + + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { /* Fees are simulated based on the Session Paremeters */ switch (tradingEngineOrder.type) { case 'Market Order': { @@ -335,8 +351,28 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function amountReceivedSimulation() { /* Amount Received */ - tradingEngineOrder.orderStatistics.amountReceived.value = tradingEngineOrder.orderStatistics.sizeFilled.value * tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate - tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) + let calculatedBasedOnOrderDefinition = false + + /* Based on the Order Definition */ + if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { + let simulatedActualRate = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + if (simulatedActualRate !== undefined) { + tradingEngineOrder.orderStatistics.amountReceived.value = tradingEngineOrder.orderStatistics.sizeFilled.value * simulatedActualRate + tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) + calculatedBasedOnOrderDefinition = true + } + } + } + } + + /* Based on Candle Close Value */ + if (calculatedBasedOnOrderDefinition === false) { + let simulatedActualRate = tradingEngine.current.candle.close.value + tradingEngineOrder.orderStatistics.amountReceived.value = tradingEngineOrder.orderStatistics.sizeFilled.value * simulatedActualRate + tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) + } } } @@ -454,9 +490,9 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngineOrder.orderStatistics.sizeFilled.value + tradingEngineOrder.orderStatistics.feesPaid.value /* Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value + previousAmountReceived + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + previousAmountReceived /* Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngineOrder.orderStatistics.amountReceived.value + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - tradingEngineOrder.orderStatistics.amountReceived.value break } @@ -468,9 +504,9 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngineOrder.orderStatistics.sizeFilled.value - tradingEngineOrder.orderStatistics.feesPaid.value /* Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value - previousAmountReceived + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - previousAmountReceived /* Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngineOrder.orderStatistics.amountReceived.value + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngineOrder.orderStatistics.amountReceived.value break } From d296ffeb516db5ff06aec0e9c6d13e387d153ae1 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 30 Jul 2020 11:42:30 +0200 Subject: [PATCH 007/112] Lock Implemented --- LowFrequencyTrading/TradingExecution.js | 14 +++- LowFrequencyTrading/TradingPosition.js | 38 +++++++++-- LowFrequencyTrading/TradingSystem.js | 85 ++++++++++++++++++++----- 3 files changed, 114 insertions(+), 23 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 74e82c1da..0bcc149f2 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -92,6 +92,16 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE switch (tradingEngineOrder.status.value) { case 'Not Open': { { + /* + Check if we can create an order based on the config value for spawnMultipleOrders. + Trading System Orders that cannot spawn more than one Trading Engine Order needs to check if + at the Trading Engine Order the lock is Open or Closed. + */ + if (tradingSystemOrder.config.spawnMultipleOrders !== true) { + if (tradingEngineOrder.identifier.value === 'Closed') { + continue + } + } /* Check if we need to Create this Order */ if (stageIsClosing === true) { continue } let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) @@ -131,7 +141,9 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE async function openOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { /* Order Size Calculation */ - tradingEngineOrder.size.value = tradingSystem.formulas.get(executionAlgorithm.positionSize.formula.id) * tradingSystemOrder.config.positionSizePercentage / 100 + let positionSize = tradingSystem.formulas.get(executionAlgorithm.positionSize.formula.id) + if (positionSize === undefined) { return } + tradingEngineOrder.size.value = positionSize * tradingSystemOrder.config.positionSizePercentage / 100 if (stageOrdersSize.value + tradingEngineOrder.size.value > stageSizeLimit.value) { /* We reduce the size to the remaining size of the position. */ tradingEngineOrder.size.value = stageSizeLimit.value - stageOrdersSize.value diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 8e38d100d..c7ac8a39d 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -182,14 +182,42 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } function updateStatistics() { + if (bot.sessionAndMarketBaseAssetsAreEqual) { - tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.baseAsset.value - tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.value + /* Profit Loss Calculation */ + tradingEngine.current.position.positionStatistics.profitLoss.value = + tradingEngine.current.balance.baseAsset.value - + tradingEngine.previous.balance.baseAsset.value + + tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value - + tradingEngine.previous.balance.quotedAsset.value / tradingEngine.current.position.beginRate.value + + /* ROI Calculation */ + tradingEngine.current.position.positionStatistics.ROI.value = + tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / + tradingEngine.current.position.size.value } else { - tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.previous.balance.quotedAsset.value - tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.value + /* Profit Loss Calculation */ + tradingEngine.current.position.positionStatistics.profitLoss.value = + tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value - + tradingEngine.previous.balance.baseAsset.value * tradingEngine.current.position.beginRate.value + + tradingEngine.current.balance.quotedAsset.value - + tradingEngine.previous.balance.quotedAsset.value + + /* ROI Calculation */ + tradingEngine.current.position.positionStatistics.ROI.value = + tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / + (tradingEngine.current.position.size.value * tradingEngine.current.position.beginRate.value) } - tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + + /* Days Calculation */ + tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * + sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + + tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) + tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) + tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10) + + /* Hit Fail Calculation */ if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { tradingEngine.current.position.positionStatistics.hitFail.value = 'HIT' } else { diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index a1755803f..4a0d75026 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -143,27 +143,27 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage executionNode = stageNode.openExecution - resetExecution(executionNode) + resetExecution(executionNode, tradingEngine.current.strategy.openStageStatus.value) stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage executionNode = stageNode.closeExecution - resetExecution(executionNode) + resetExecution(executionNode, tradingEngine.current.strategy.closeStageStatus.value) - function resetExecution(executionNode) { + function resetExecution(executionNode, stageStatus) { resetExecutionAlgorithms(executionNode) function resetExecutionAlgorithms(executionNode) { for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { let executionAlgorithm = executionNode.executionAlgorithms[i] - resetOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) - resetOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) - resetOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) - resetOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) + resetOrders(executionAlgorithm.marketBuyOrders) + resetOrders(executionAlgorithm.marketSellOrders) + resetOrders(executionAlgorithm.limitBuyOrders) + resetOrders(executionAlgorithm.limitSellOrders) } } - function resetOrders(orders, executionAlgorithm, executionNode) { + function resetOrders(orders) { for (let i = 0; i < orders.length; i++) { let tradingSystemOrder = orders[i] if (tradingSystemOrder.referenceParent === undefined) { continue } @@ -172,6 +172,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (tradingEngineOrder.status.value === 'Closed') { /* We reset the order data structure inside the Trading Engine to its initial value */ tradingEngineOrder.initialize(tradingEngineOrder) + if (tradingSystemOrder.config.spawnMultipleOrders !== true) { + /* + We close the lock so as to prevent this data structure to be used again during this same stage execution. + */ + if (stageStatus === 'Open') { + tradingEngineOrder.lock.value = 'Closed' + } + } } } } @@ -205,7 +213,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } /* Here we check if there is a formula to be evaluated */ - if (node.type === 'Formula' && evaluating === 'Formulas') { + if ((node.type === 'Formula' || node.type === 'Announcement Formula') && evaluating === 'Formulas') { if (node.code !== undefined) { /* We will eval this formula */ if (isDescendent === true) { @@ -322,13 +330,18 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } - tradingSystem.formulas.set(node.id, value) - if (error !== undefined) { tradingSystem.errors.push([node.id, error]) + return } if (value !== undefined) { + if (node.type === 'Formula' && isNaN(value)) { + tradingSystem.errors.push([node.id, 'Formula needs to return a number.']) + return + } + tradingSystem.values.push([node.id, value]) + tradingSystem.formulas.set(node.id, value) } logger.write(MODULE_NAME, '[INFO] evalFormula -> value = ' + value) @@ -567,6 +580,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM */ if (tradingEngine.current.position.openStageFilledSize.value === tradingEngine.current.position.size.value) { tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') + } else { + /* Check the Close Stage Event */ + evalConditions(stageNode, 'Close Stage Event') + if (checkStopStageEvent(stageNode) === true) { + tradingStrategyModule.updateStageStatus('Open Stage', 'Closing') + } } } @@ -597,6 +616,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM */ if (tradingEngine.current.position.openStageFilledSize.value === tradingEngine.current.position.openStageOrdersSize.value) { tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') + } else { + /* Check the Close Stage Event */ + evalConditions(stageNode, 'Close Stage Event') + if (checkStopStageEvent(stageNode) === true) { + tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') + } } } @@ -958,13 +983,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Close Execution') evalFormulas(stageNode, 'Close Execution') - await tradingExecutionModule.checkExecution(executionNode, false, false, tradingEngine.current.position.openStageFilledSize, tradingEngine.current.position.closeStageOrdersSize, tradingEngine.current.position.closeStageFilledSize) + let stageLimitSize = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.candle.close.value + await tradingExecutionModule.checkExecution(executionNode, false, false, stageLimitSize, tradingEngine.current.position.closeStageOrdersSize, tradingEngine.current.position.closeStageFilledSize) - /* Close the Stage Validation */ - if (tradingEngine.current.position.closeStageFilledSize.value === tradingEngine.current.position.openStageFilledSize.value) { - /* - The Close Stage is closed when the fillSize reaches the filledSize of the Open Stage. - */ + /* Check the Close Stage Event */ + evalConditions(stageNode, 'Close Stage Event') + if (checkStopStageEvent(stageNode) === true) { tradingStrategyModule.updateStageStatus('Close Stage', 'Closed') } } @@ -1100,6 +1124,33 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } + function checkStopStageEvent(stage) { + + /* Check the Close Stage Event. */ + let stopStageEvent = stage.stopStageEvent + if (stopStageEvent !== undefined) { + for (let k = 0; k < stopStageEvent.situations.length; k++) { + let situation = stopStageEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(stopStageEvent.id) + tradingSystem.highlights.push(stage.id) + + announcementsModule.makeAnnoucements(stopStageEvent) + return true + } + } + } + } + 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 From 68febdcf74a152d1ec574a3c7a9ee9ce9d25a75c Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 1 Aug 2020 11:05:19 +0200 Subject: [PATCH 008/112] Code Cleaning --- LowFrequencyTrading/TradingExecution.js | 17 ++++++++++-- LowFrequencyTrading/TradingSystem.js | 37 +++++++++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 0bcc149f2..0d36253fc 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -43,11 +43,22 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE announcementsModule = undefined } - async function checkExecution(executionNode, stageIsOpening, stageIsClosing, stageSizeLimit, stageOrdersSize, stageFilledSize) { + async function checkExecution( + executionNode, + stageIsOpening, + stageIsClosing, + stageSizeLimit, + stageOrdersSizeInBaseAsset, + stageFilledSizeInBaseAsset, + stageOrdersSizeInQuotedAsset, + stageFilledSizeInQuotedAsset + ) { /* Enforcing Precision Limit */ - stageSizeLimit.value = global.PRECISE(stageSizeLimit.value, 10) - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + stageOrdersSizeInBaseAsset.value = global.PRECISE(stageOrdersSizeInBaseAsset.value, 10) + stageOrdersSizeInBaseAsset.value = global.PRECISE(stageOrdersSizeInBaseAsset.value, 10) + stageOrdersSizeInQuotedAsset.value = global.PRECISE(stageOrdersSizeInQuotedAsset.value, 10) + stageFilledSizeInQuotedAsset.value = global.PRECISE(stageFilledSizeInQuotedAsset.value, 10) await checkExecutionAlgorithms(executionNode) diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 4a0d75026..81790873e 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -551,7 +551,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') - await tradingExecutionModule.checkExecution(executionNode, true, false, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) + await tradingExecutionModule.checkExecution( + executionNode, + true, + false, + tradingEngine.current.position.size, + tradingEngine.current.position.openStageOrdersSize, + tradingEngine.current.position.openStageFilledSize + ) /* From here on, the state is officially Open */ tradingStrategyModule.updateStageStatus('Open Stage', 'Open') @@ -571,8 +578,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') - await tradingExecutionModule.checkExecution(executionNode, false, false, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) - + await tradingExecutionModule.checkExecution( + executionNode, + false, + false, + tradingEngine.current.position.size, + tradingEngine.current.position.openStageOrdersSize, + tradingEngine.current.position.openStageFilledSize + ) /* The Open is finished when the fillSize reaches the Position Size. This can happens at any time when we update the filledSize value when we see @@ -605,7 +618,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') - await tradingExecutionModule.checkExecution(executionNode, false, true, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) + await tradingExecutionModule.checkExecution( + executionNode, + false, + true, + tradingEngine.current.position.size, + tradingEngine.current.position.openStageOrdersSize, + tradingEngine.current.position.openStageFilledSize + ) } /* @@ -984,7 +1004,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalFormulas(stageNode, 'Close Execution') let stageLimitSize = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.candle.close.value - await tradingExecutionModule.checkExecution(executionNode, false, false, stageLimitSize, tradingEngine.current.position.closeStageOrdersSize, tradingEngine.current.position.closeStageFilledSize) + await tradingExecutionModule.checkExecution( + executionNode, + false, + false, + stageLimitSize, + tradingEngine.current.position.closeStageOrdersSize, + tradingEngine.current.position.closeStageFilledSize + ) /* Check the Close Stage Event */ evalConditions(stageNode, 'Close Stage Event') From 6fb5bdb62778e005195c32843d3258da3dad38ec Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 1 Aug 2020 15:29:45 +0200 Subject: [PATCH 009/112] Open order Implemented --- LowFrequencyTrading/ExchangeAPI.js | 12 +- LowFrequencyTrading/TradingExecution.js | 191 +++++++++++++++++++----- LowFrequencyTrading/TradingPosition.js | 91 ++++++++++- LowFrequencyTrading/TradingSystem.js | 83 +++------- 4 files changed, 271 insertions(+), 106 deletions(-) diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index 633f9a59c..3c4861120 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -74,10 +74,20 @@ async function createOrder(tradingSystemOrder, tradingEngineOrder) { let price = tradingEngineOrder.rate.value // CCXT: how much quote currency you are willing to pay for a trade lot of base currency (for limit orders only) - let amount = tradingEngineOrder.size.value // CCXT: how much of currency you want to trade 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 = bot.market.baseAsset + '/' + bot.market.quotedAsset // CCXT: a string literal symbol of the market you wish to trade on, like BTC/USD, ZEC/ETH, DOGE/DASH, etc + let amount // CCXT: how much of currency you want to trade + + /* + We need at the amount a value denominated in Base Asset, if we do not + have it, then we convert the value we have in Quoteed Asset. + */ + if (tradingEngineOrder.orderBaseAsset.size.value > 0) { + amount = tradingEngineOrder.orderBaseAsset.size.value + } else { + amount = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value + } switch (tradingSystemOrder.type) { case 'Market Buy Order': { diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 0d36253fc..f074e96f4 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -47,18 +47,45 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE executionNode, stageIsOpening, stageIsClosing, - stageSizeLimit, - stageOrdersSizeInBaseAsset, - stageFilledSizeInBaseAsset, - stageOrdersSizeInQuotedAsset, - stageFilledSizeInQuotedAsset + stageSizeLimitInBaseAsset, + stageSizeLimitInQuotedAsset, + tradingEngineStage ) { - /* Enforcing Precision Limit */ - stageOrdersSizeInBaseAsset.value = global.PRECISE(stageOrdersSizeInBaseAsset.value, 10) - stageOrdersSizeInBaseAsset.value = global.PRECISE(stageOrdersSizeInBaseAsset.value, 10) - stageOrdersSizeInQuotedAsset.value = global.PRECISE(stageOrdersSizeInQuotedAsset.value, 10) - stageFilledSizeInQuotedAsset.value = global.PRECISE(stageFilledSizeInQuotedAsset.value, 10) + /* Trading Stage Validations */ + if (tradingEngineStage.stageBaseAsset === undefined) { continue } + if (tradingEngineStage.stageBaseAsset.size === undefined) { continue } + if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { continue } + if (tradingEngineStage.stageBaseAsset.amountReceived === undefined) { continue } + if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { continue } + if (tradingEngineStage.stageQuotedAsset === undefined) { continue } + if (tradingEngineStage.stageQuotedAsset.size === undefined) { continue } + if (tradingEngineStage.stageQuotedAsset.sizeFilled === undefined) { continue } + if (tradingEngineStage.stageQuotedAsset.amountReceived === undefined) { continue } + if (tradingEngineStage.stageQuotedAsset.feesPaid === undefined) { continue } + + /* + The user choice of asset to define the size of the position, + defines the asset that will track the information kept about the + stage at the trading engine data structure. We set this asset + to be used here in this way: + */ + let tradingEngineStageAsset + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + tradingEngineStageAsset = tradingEngineStage.stageBaseAsset + } else { + tradingEngineStageAsset = tradingEngineStage.stageQuotedAsset + } + /* + The asset type of the position size chosen by the user, also defines + the stage limit to be on the same asset type. + */ + let stageSizeLimit + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + stageSizeLimit = stageSizeLimitInBaseAsset + } else { + stageSizeLimit = stageSizeLimitInQuotedAsset + } await checkExecutionAlgorithms(executionNode) @@ -90,7 +117,6 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (tradingEngineOrder.begin === undefined) { continue } if (tradingEngineOrder.end === undefined) { continue } if (tradingEngineOrder.rate === undefined) { continue } - if (tradingEngineOrder.size === undefined) { continue } if (tradingEngineOrder.status === undefined) { continue } if (tradingEngineOrder.algorithmName === undefined) { continue } if (tradingEngineOrder.orderCounters === undefined) { continue } @@ -100,9 +126,33 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (tradingEngineOrder.orderStatistics.actualRate === undefined) { continue } if (tradingEngineOrder.orderStatistics.feesPaid === undefined) { continue } + if (tradingEngineOrder.orderBaseAsset.size === undefined) { continue } + if (tradingEngineOrder.orderBaseAsset.sizeFilled === undefined) { continue } + if (tradingEngineOrder.orderBaseAsset.amountReceived === undefined) { continue } + if (tradingEngineOrder.orderBaseAsset.feesPaid === undefined) { continue } + + if (tradingEngineOrder.orderQuotedAsset.size === undefined) { continue } + if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { continue } + if (tradingEngineOrder.orderQuotedAsset.amountReceived === undefined) { continue } + if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { continue } + + /* + The asset type of the position size chosen by the user, defines + the asset at the order data strutcture where information is going to + be stored. + */ + let traingEngineOrderAsset + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + traingEngineOrderAsset = tradingEngineOrder.orderBaseAsset + } else { + traingEngineOrderAsset = tradingEngineOrder.orderQuotedAsset + } + switch (tradingEngineOrder.status.value) { case 'Not Open': { { + /* When the stage is closing we can not create new orders */ + if (stageIsClosing === true) { continue } /* Check if we can create an order based on the config value for spawnMultipleOrders. Trading System Orders that cannot spawn more than one Trading Engine Order needs to check if @@ -114,7 +164,6 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } } /* Check if we need to Create this Order */ - if (stageIsClosing === true) { continue } let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) if (situationName !== undefined) { @@ -151,32 +200,22 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } async function openOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { - /* Order Size Calculation */ - let positionSize = tradingSystem.formulas.get(executionAlgorithm.positionSize.formula.id) - if (positionSize === undefined) { return } - tradingEngineOrder.size.value = positionSize * tradingSystemOrder.config.positionSizePercentage / 100 - if (stageOrdersSize.value + tradingEngineOrder.size.value > stageSizeLimit.value) { - /* We reduce the size to the remaining size of the position. */ - tradingEngineOrder.size.value = stageSizeLimit.value - stageOrdersSize.value - tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) - } - /* Check Order Size */ - if (tradingEngineOrder.size.value <= 0) { return } + calculateOrderSize() - /* Order Rate Calculation */ - tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. - if (tradingSystemOrder.positionRate !== undefined) { - if (tradingSystemOrder.positionRate.formula !== undefined) { - tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) - tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) - } - } + /* Check Size: We are not going to create Orders which size is equal or less to zero. */ + if (traingEngineOrderAsset.size.value <= 0) { return } + + calculateOrderRate() /* Place Order at the Exchange */ let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) if (result !== true) { return } + /* Update Stage Counters */ + tradingEngineStageAsset.value = tradingEngineStageAsset.value + tradingEngineOrder.size.value + tradingEngineStageAsset.value = global.PRECISE(tradingEngineStageAsset.value, 10) + /* Updating Episode Counters */ tradingEngine.episode.episodeCounters.orders.value++ @@ -193,9 +232,93 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.algorithmName.value = executionAlgorithm.name tradingEngineOrder.situationName.value = situationName - /* Update Stage Orders Size */ - stageOrdersSize.value = stageOrdersSize.value + tradingEngineOrder.size.value - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + function calculateOrderSize() { + /* + If the position size was defined in Base Asset, then + the algorithm needs to define its size also in Base Asset. + The same if it was deined in Quoted Asset. + */ + let sizeFormula + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + /* Position was defined in Base Asset */ + if (executionAlgorithm.sizeInBaseAsset !== undefined) { + if (executionAlgorithm.sizeInBaseAsset.formula !== undefined) { + sizeFormula = executionAlgorithm.sizeInBaseAsset.formula + } else { + const errorText = 'Size In Base Asset needs a child Formula. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.sizeInBaseAsset.id, errorText]) + throw (errorText) + } + } else { + const errorText = 'Execution Algorithm needs a child Size In Base Asset. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.id, errorText]) + throw (errorText) + } + } else { + /* Position was defined in Quoted Asset */ + if (executionAlgorithm.sizeInQuotedAsset !== undefined) { + if (executionAlgorithm.sizeInQuotedAsset.formula !== undefined) { + sizeFormula = executionAlgorithm.sizeInQuotedAsset.formula + } else { + const errorText = 'Size In Quoted Asset needs a child Formula. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.sizeInQuotedAsset.id, errorText]) + throw (errorText) + } + } else { + const errorText = 'Execution Algorithm needs a child Size In Quoted Asset. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.id, errorText]) + throw (errorText) + } + } + + /* Order Size Calculation */ + let algorithmSize = tradingSystem.formulas.get(sizeFormula.id) + if (algorithmSize === undefined) { + const errorText = 'Execution Algorithm Size cannot be undefined. Fix this please.' + tradingSystem.errors.push([sizeFormula.id, errorText]) + throw (errorText) + } + + /* Validate that this config exists */ + if (tradingSystemOrder.config.positionSizePercentage === undefined) { + const errorText = 'Config positionSizePercentage does not exist. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.id, errorText]) + throw (errorText) + } + + traingEngineOrderAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 + traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) + + /* Check against the Stage Size Limit */ + if (tradingEngineStageAsset.size.value + traingEngineOrderAsset.size.value > stageSizeLimit.value) { + /* We reduce the size to the remaining size of the position. */ + traingEngineOrderAsset.size.value = stageSizeLimit.value - tradingEngineStageAsset.value + traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) + } + } + + function calculateOrderRate() { + /* Order Rate Calculation */ + tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. + if (tradingSystemOrder.positionRate !== undefined) { + if (tradingSystemOrder.positionRate.formula !== undefined) { + tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) + + if (tradingEngineOrder.rate.value === undefined) { + const errorText = 'Rate cannot be undefined. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) + throw (errorText) + } + + if (tradingEngineOrder.rate.value <= 0) { + const errorText = 'Rate cannot be less or equal to zero. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) + throw (errorText) + } + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + } + } + } } async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index c7ac8a39d..0f1d3e074 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -15,7 +15,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng applySlippageToTakeProfit: applySlippageToTakeProfit, updateStopLoss: updateStopLoss, updateTakeProfit: updateTakeProfit, - updateSizeAndRate: updateSizeAndRate, + initializeSizeAndRate: initializeSizeAndRate, updateEnds: updateEnds, updateStatus: updateStatus, updateCounters: updateCounters, @@ -41,18 +41,26 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } function openPosition(situationName) { + + /* Recording the opening at the Trading Engine Data Structure */ tradingEngine.current.position.status.value = 'Open' tradingEngine.current.position.serialNumber.value = tradingEngine.episode.episodeCounters.positions.value tradingEngine.current.position.identifier.value = global.UNIQUE_ID() tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.position.situationName.value = situationName + /* Initializing Stop and Take Profit Stage / Phase */ tradingEngine.current.position.stopLoss.stopLossStage.value = 'Open Stage' tradingEngine.current.position.takeProfit.takeProfitStage.value = 'Open Stage' tradingEngine.current.position.stopLoss.stopLossPhase.value = 0 tradingEngine.current.position.takeProfit.takeProfitPhase.value = 0 + + /* Updating Episode Counters */ + tradingEngine.episode.episodeCounters.positions.value++ + + /* Inicializing this counter */ + tradingEngine.current.distanceToEvent.takePosition.value = 1 } function closingPosition(exitType) { @@ -91,11 +99,84 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.takeProfit.takeProfitStage.value = stage } - function updateSizeAndRate(size, rate) { - tradingEngine.current.position.size.value = global.PRECISE(size, 10) - tradingEngine.current.position.rate.value = global.PRECISE(rate, 10) + function initializeSizeAndRate() { + + setPositionSize() + setPositionRate() tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value + + function setPositionSize() { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + + /* Basic Validation */ + if ( + strategy.openStage.initialDefinition.positionSizeInBaseAsset !== undefined && + strategy.openStage.initialDefinition.positionSizeInQuotedAsset !== undefined + ) { + const errorText = 'Only Position Size In Base Asset or Position Size In Quoted Asset is allowed. Remove one of them please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.id, errorText]) + throw (errorText) + } + + /* Position In Base Asset */ + if (strategy.openStage.initialDefinition.positionSizeInBaseAsset !== undefined) { + if (strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula !== undefined) { + let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula.id) + if (value === undefined) { + const errorText = 'Position Size In Base Asset cannot be undefined. Fix this please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula.id, errorText]) + throw (errorText) + } + if (value === undefined) { + const errorText = 'Position Size In Base Asset cannot be zero. Fix this please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula.id, errorText]) + throw (errorText) + } + tradingEngine.current.position.positionBaseAsset.size.value = global.PRECISE(value, 10) + } else { + const errorText = 'You need to specify a Formula for this.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInBaseAsset.id, errorText]) + throw (errorText) + } + } + + /* Position In Quoted Asset */ + if (strategy.openStage.initialDefinition.positionSizeInQuotedAsset !== undefined) { + if (strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula !== undefined) { + let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula.id) + if (value === undefined) { + const errorText = 'Position Size In Quoted Asset cannot be undefined. Fix this please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula.id, errorText]) + throw (errorText) + } + if (value === undefined) { + const errorText = 'Position Size In Quoted Asset cannot be zero. Fix this please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula.id, errorText]) + throw (errorText) + } + tradingEngine.current.position.positionQuotedAsset.size.value = global.PRECISE(value, 10) + } else { + const errorText = 'You need to specify a Formula for this.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInQuotedAsset.id, errorText]) + throw (errorText) + } + } + } + + function setPositionRate() { + const DEFAULT_VALUE = tradingEngine.current.candle.close.value + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + + if (strategy.openStage === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition.positionRate === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition.positionRate.formula === undefined) return DEFAULT_VALUE + + let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id) + if (value === undefined) return DEFAULT_VALUE + return value + } } function preventStopLossDistortion() { diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 81790873e..bae1bf683 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -484,12 +484,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingSystem.highlights.push(triggerStage.takePosition.id) tradingSystem.highlights.push(triggerStage.id) - /* Updating Episode Counters */ - tradingEngine.episode.episodeCounters.positions.value++ - - /* Inicializing this counter */ - tradingEngine.current.distanceToEvent.takePosition.value = 1 - tradingPositionModule.openPosition(situation.name) announcementsModule.makeAnnoucements(triggerStage.takePosition) @@ -538,16 +532,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Initial Definition') evalFormulas(stageNode, 'Initial Definition') - getReadyToTakePosition() - - calculateTakeProfit() - calculateStopLoss() - /* Remember the balance we had before taking the position to later calculate profit or loss */ tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - /* Check Execution in opening stage node */ + /* Position size and rate */ + tradingPositionModule.initializeSizeAndRate() + + /* Check Execution at open stage node */ evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') @@ -555,12 +547,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM executionNode, true, false, - tradingEngine.current.position.size, - tradingEngine.current.position.openStageOrdersSize, - tradingEngine.current.position.openStageFilledSize + tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit + tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit + tradingEngine.current.position.strategyOpenStage ) - /* From here on, the state is officially Open */ + /* From here on, the state is officialy Open */ tradingStrategyModule.updateStageStatus('Open Stage', 'Open') return } @@ -582,9 +574,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM executionNode, false, false, - tradingEngine.current.position.size, - tradingEngine.current.position.openStageOrdersSize, - tradingEngine.current.position.openStageFilledSize + tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit + tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit + tradingEngine.current.position.strategyOpenStage ) /* The Open is finished when the fillSize reaches the Position Size. @@ -622,9 +614,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM executionNode, false, true, - tradingEngine.current.position.size, - tradingEngine.current.position.openStageOrdersSize, - tradingEngine.current.position.openStageFilledSize + tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit + tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit + tradingEngine.current.position.strategyOpenStage ) } @@ -644,46 +636,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } } - - function getReadyToTakePosition() { - - /* Position size and rate */ - tradingPositionModule.updateSizeAndRate(getPositionSize(), getPositionRate()) - - function getPositionSize() { - let balance - if (bot.sessionAndMarketBaseAssetsAreEqual) { - balance = tradingEngine.current.balance.baseAsset.value - } else { - balance = tradingEngine.current.balance.quotedAsset.value - } - const DEFAULT_VALUE = balance - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - - if (strategy.openStage === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition.positionSize === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition.positionSize.formula === undefined) return DEFAULT_VALUE - - let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionSize.formula.id) - if (value === undefined) return DEFAULT_VALUE - return value - } - - function getPositionRate() { - const DEFAULT_VALUE = tradingEngine.current.candle.close.value - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - - if (strategy.openStage === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition.positionRate === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition.positionRate.formula === undefined) return DEFAULT_VALUE - - let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id) - if (value === undefined) return DEFAULT_VALUE - return value - } - } } function manageStage() { @@ -1003,14 +955,13 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Close Execution') evalFormulas(stageNode, 'Close Execution') - let stageLimitSize = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.candle.close.value await tradingExecutionModule.checkExecution( executionNode, false, false, - stageLimitSize, - tradingEngine.current.position.closeStageOrdersSize, - tradingEngine.current.position.closeStageFilledSize + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled, // Stage Size Limit + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled, // Stage Size Limit + tradingEngine.current.position.strategyCloseStage ) /* Check the Close Stage Event */ From 89d6edc343c391efe4b1e09797a84cd64282b334 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 1 Aug 2020 19:35:50 +0200 Subject: [PATCH 010/112] MOre sutff --- LowFrequencyTrading/TradingExecution.js | 473 +++++++++++++++--------- LowFrequencyTrading/TradingStrategy.js | 16 +- LowFrequencyTrading/TradingSystem.js | 76 ++-- 3 files changed, 350 insertions(+), 215 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index f074e96f4..2eedca830 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -168,12 +168,13 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (situationName !== undefined) { /* Open a new order */ - await openOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) + await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) } } break } case 'Open': { + /* Update this order properties */ tradingEngineOrder.end.value = tradingEngine.current.candle.end.value tradingEngineOrder.orderCounters.periods.value++ tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS @@ -184,13 +185,22 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Check Events that happens at the Exchange, if needed. */ await checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) - /* If the stage is closing or the order is not still Open, we wont be cancelling orders based on defined events */ + /* + In the previous steps, we might have discovered that the order was cancelled + at the exchange, or filled, so the order might still not be Open. + If the stage is closing or the order is not Open, we wont be cancelling orders + based on defined events. + */ if (stageIsClosing !== true && tradingEngineOrder.status.value === 'Open') { /* Check if we need to Cancel this Order */ let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) if (situationName !== undefined) { + + /* Simulate Order Cancelation, if needed. */ simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + + /* Cancel the order at the Exchange, if needed. */ await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') } } @@ -199,22 +209,23 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } } - async function openOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { + async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { + calculateOrderRate() calculateOrderSize() /* Check Size: We are not going to create Orders which size is equal or less to zero. */ if (traingEngineOrderAsset.size.value <= 0) { return } - calculateOrderRate() - /* Place Order at the Exchange */ let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) if (result !== true) { return } /* Update Stage Counters */ - tradingEngineStageAsset.value = tradingEngineStageAsset.value + tradingEngineOrder.size.value - tradingEngineStageAsset.value = global.PRECISE(tradingEngineStageAsset.value, 10) + tradingEngineStage.stageBaseAsset.size.value = tradingEngineStage.stageBaseAsset.size.value + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) + tradingEngineStage.stageQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.size.value + tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) /* Updating Episode Counters */ tradingEngine.episode.episodeCounters.orders.value++ @@ -232,6 +243,29 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.algorithmName.value = executionAlgorithm.name tradingEngineOrder.situationName.value = situationName + function calculateOrderRate() { + /* Order Rate Calculation */ + tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. + if (tradingSystemOrder.positionRate !== undefined) { + if (tradingSystemOrder.positionRate.formula !== undefined) { + tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) + + if (tradingEngineOrder.rate.value === undefined) { + const errorText = 'Rate cannot be undefined. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) + throw (errorText) + } + + if (tradingEngineOrder.rate.value <= 0) { + const errorText = 'Rate cannot be less or equal to zero. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) + throw (errorText) + } + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + } + } + } + function calculateOrderSize() { /* If the position size was defined in Base Asset, then @@ -290,60 +324,50 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) /* Check against the Stage Size Limit */ - if (tradingEngineStageAsset.size.value + traingEngineOrderAsset.size.value > stageSizeLimit.value) { + if ( + tradingEngineStageAsset.size.value + traingEngineOrderAsset.size.value > stageSizeLimit.value) { /* We reduce the size to the remaining size of the position. */ traingEngineOrderAsset.size.value = stageSizeLimit.value - tradingEngineStageAsset.value traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) } - } - function calculateOrderRate() { - /* Order Rate Calculation */ - tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. - if (tradingSystemOrder.positionRate !== undefined) { - if (tradingSystemOrder.positionRate.formula !== undefined) { - tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) - - if (tradingEngineOrder.rate.value === undefined) { - const errorText = 'Rate cannot be undefined. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) - throw (errorText) - } - - if (tradingEngineOrder.rate.value <= 0) { - const errorText = 'Rate cannot be less or equal to zero. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) - throw (errorText) - } - tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + /* We are going to ESTIMATE the size in the oposite asset type, because we will need it later */ + switch (traingEngineOrderAsset.type) { + case 'Order Base Asset': { + tradingEngineOrder.orderQuotedAsset.size = tradingEngineOrder.orderBaseAsset.size * tradingEngineOrder.rate + tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) + } + case 'Order Quoted Asset': { + tradingEngineOrder.orderBaseAsset.size = tradingEngineOrder.orderQuotedAsset.size / tradingEngineOrder.rate + tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) } } } - } - async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { + async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return true + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return true + } + } + + let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) + + if (orderId !== undefined) { + tradingEngineOrder.exchangeId.value = orderId return true } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return true - } - } - - let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) - - if (orderId !== undefined) { - tradingEngineOrder.exchangeId.value = orderId - return true } } @@ -368,7 +392,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Filter by what is defined at the Strategy */ if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } - let previousSizeFilled = tradingEngineOrder.orderStatistics.sizeFilled.value + let previousSizeFilled = tradingEngineOrderAsset.sizeFilled.value let previousPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value let previousAmountReceived = tradingEngineOrder.orderStatistics.amountReceived.value let previousFeesPaid = tradingEngineOrder.orderStatistics.feesPaid.value @@ -379,7 +403,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE sizeFilledSimulation() amountReceivedSimulation() - doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid) + doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousFeesPaid) /* If the Stage is Closing and this order is still open, we need to cancel it now */ if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { @@ -589,129 +613,187 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) { - let previousSizeFilled = tradingEngineOrder.orderStatistics.sizeFilled.value - let previousPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value - let previousAmountReceived = tradingEngineOrder.orderStatistics.amountReceived.value - let previousFeesPaid = tradingEngineOrder.orderStatistics.feesPaid.value + let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value + let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value + let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value + let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - /* Fees Paid */ - tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value - order.amount - tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10) - - /* Percentage Filled */ - tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - - /* Size Filled */ - tradingEngineOrder.orderStatistics.sizeFilled.value = order.filled - tradingEngineOrder.orderStatistics.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.sizeFilled.value, 10) - - /* Amount Received */ - tradingEngineOrder.orderStatistics.amountReceived.value = order.cost - tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) - - /* Actual Rate */ + /* Actual Rate Calculation */ tradingEngineOrder.orderStatistics.actualRate.value = order.price tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid) + /* Fees Paid Calculation */ + /* + As a response from the exchange we can not always get the fees. + For that reason we need to estimate them base of the information that we do have. + CCXT provides order.amount which represents the size we set for the order minus the + fees taken by the exchange, all denominated in Base Asset. + In this way if we substract to the order size this order.amount + we can get the fees. All this is denominated in base asset because that is how CCXT works. + The fees then can be estimated in Quoted Asset using the Actual Rate. + */ + tradingEngineOrder.orderBaseAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.size.value - order.amount + tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.feesPaid.value * tradingEngineOrder.orderStatistics.actualRate.value + tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) + + /* Percentage Filled Calculation */ + tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + + /* Size Filled Calculation */ + /* + CCXT returns order.filled with an amount denominated in Base Asset. We will + take it from there for our Order Base Asset. For our Order Quoted Asset we + will use the field order.cost. + */ + tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled + tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = order.cost + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) + + doTheAccounting( + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) } - function doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid) { + function doTheAccounting( + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) { - /* Unaccount for the previous Stage Filled Size */ - stageFilledSize.value = stageFilledSize.value - tradingEngineOrder.size.value * previousPercentageFilled / 100 - /* Account for the current Stage Filled Size */ - stageFilledSize.value = stageFilledSize.value + tradingEngineOrder.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) + updateStageAssets() + updateBalances() + function updateStageAssets() { + /* Stage Base Asset: Undo the previous accounting */ + tradingEngineStage.stageBaseAsset.sizeFilled.value = + tradingEngineStage.stageBaseAsset.value - + previousBaseAssetSizeFilled - /* Balances Update */ - switch (true) { - case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { + tradingEngineStage.stageBaseAsset.feesPaid.value = + tradingEngineStage.feesPaid.value - + previousBaseAssetFeesPaid - /* Undo the previous accounting */ - tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - previousFeesPaid - previousSizeFilled - /* Account the current filling and fees */ - tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngineOrder.orderStatistics.sizeFilled.value + tradingEngineOrder.orderStatistics.feesPaid.value + /* Stage Base Asset: Account the current filling and fees */ + tradingEngineStage.stageBaseAsset.sizeFilled.value = + tradingEngineStage.stageBaseAsset.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value - /* Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + previousAmountReceived - /* Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - tradingEngineOrder.orderStatistics.amountReceived.value + tradingEngineStage.stageBaseAsset.feesPaid.value = + tradingEngineStage.feesPaid.value + + tradingEngineOrder.orderBaseAsset.feesPaid.value - break - } - case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { + /* Stage Quote Asset: Undo the previous accounting */ + tradingEngineStage.stageQuotedAsset.sizeFilled.value = + tradingEngineStage.stageQuotedAsset.value - + previousQuotedAssetSizeFilled - /* Undo the previous accounting */ - tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + previousFeesPaid + previousSizeFilled - /* Account the current filling and fees */ - tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngineOrder.orderStatistics.sizeFilled.value - tradingEngineOrder.orderStatistics.feesPaid.value + tradingEngineStage.stageQuotedAsset.feesPaid.value = + tradingEngineStage.feesPaid.value - + previousQuotedAssetFeesPaid - /* Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - previousAmountReceived - /* Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngineOrder.orderStatistics.amountReceived.value + /* Stage Quote Asset: Account the current filling and fees */ + tradingEngineStage.stageQuotedAsset.sizeFilled.value = + tradingEngineStage.stageQuotedAsset.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value - break - } - } - } + tradingEngineStage.stageQuotedAsset.feesPaid.value = + tradingEngineStage.feesPaid.value + + tradingEngineOrder.orderQuotedAsset.feesPaid.value - function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + tradingEngineStage.stageBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizeFilled.value, 10) + tradingEngineStage.stageBaseAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageBaseAsset.feesPaid.value, 10) - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - break - } - case 'Live Trading Session': { - return - } - case 'Fordward Testing Session': { - return - } - case 'Paper Trading Session': { - break - } + tradingEngineStage.stageQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizeFilled.value, 10) + tradingEngineStage.stageQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.feesPaid.value, 10) } - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = exitType + function updateBalances() { + /* Balances Update */ + switch (true) { + case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { - /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + /* Balance Base Asset: Undo the previous accounting */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value - + previousBaseAssetSizeFilled - /* Since the order was cancelled, we remove the unfilled amount of the order from here */ - let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value - let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100 - stageOrdersSize.value = stageOrdersSize.value - unfilledSize - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - } + /* Balance Base Asset: Account the current filling and fees */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value - async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + /* Balance Quoted Asset: Undo the previous accounting */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value + + previousQuotedAssetSizeFilled + + previousQuotedAssetFeesPaid - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return - } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return + /* Balance Quoted Asset: Account the current filling and fees */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value - + tradingEngineOrder.orderQuotedAsset.sizeFilled.value - + tradingEngineOrder.orderQuotedAsset.feesPaid.value + break + } + case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { + + /* Balance Base Asset: Undo the previous accounting */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value + + previousBaseAssetSizeFilled + + previousBaseAssetFeesPaid + + /* Balance Base Asset: Account the current filling and fees */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value - + tradingEngineOrder.orderBaseAsset.sizeFilled.value - + tradingEngineOrder.orderBaseAsset.feesPaid.value + + /* Balance Quoted Asset: Undo the previous accounting */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value - + previousQuotedAssetSizeFilled + + /* Balance Quoted Asset: Account the current filling and fees */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + break + } } + tradingEngine.current.balance.baseAsset.value = global.PRECISE(tradingEngine.current.balance.baseAsset.value, 10) + tradingEngine.current.balance.quotedAsset.value = global.PRECISE(tradingEngine.current.balance.quotedAsset.value, 10) } - /* Check if we can cancel the order at the Exchange. */ - let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) - if (result === true) { + function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + break + } + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { + break + } + } + /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = exitType @@ -724,45 +806,80 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100 stageOrdersSize.value = stageOrdersSize.value - unfilledSize stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - - /* - Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. - To sync our accounting, we need to check the order one last time and if it changed, fix it. - */ - - let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - - if (order === undefined) { return } - - syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) } - } - function checkOrderEvent(event, order, executionAlgorithm, executionNode) { - if (event !== undefined) { - for (let k = 0; k < event.situations.length; k++) { - let situation = event.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true + async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return + } + } - passed = tradingSystem.checkConditions(situation, passed) + /* Check if we can cancel the order at the Exchange. */ + let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) + if (result === true) { + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = exitType - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(event.id) - tradingSystem.highlights.push(order.id) - tradingSystem.highlights.push(executionAlgorithm.id) - tradingSystem.highlights.push(executionNode.id) + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 - announcementsModule.makeAnnoucements(event) - return situation.name // if the event is triggered, we return the name of the situation that passed + /* Since the order was cancelled, we remove the unfilled amount of the order from here */ + let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100 + stageOrdersSize.value = stageOrdersSize.value - unfilledSize + stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + + /* + Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. + To sync our accounting, we need to check the order one last time and if it changed, fix it. + */ + + let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) + + if (order === undefined) { return } + + syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) + } + } + + function checkOrderEvent(event, order, executionAlgorithm, executionNode) { + if (event !== undefined) { + for (let k = 0; k < event.situations.length; k++) { + let situation = event.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(event.id) + tradingSystem.highlights.push(order.id) + tradingSystem.highlights.push(executionAlgorithm.id) + tradingSystem.highlights.push(executionNode.id) + + announcementsModule.makeAnnoucements(event) + return situation.name // if the event is triggered, we return the name of the situation that passed + } } } } } } -} diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index adcf0ca71..7edf964a4 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -63,22 +63,28 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng } } - function updateStageStatus(stage, status) { + function updateStageStatus(stage, status, exitType) { switch (stage) { case 'Trigger Stage': { - tradingEngine.current.strategy.triggerStageStatus.value = status + tradingEngine.current.strategyTriggerStage.status.value = status break } case 'Open Stage': { - tradingEngine.current.strategy.openStageStatus.value = status + tradingEngine.current.strategyOpenStage.status.value = status + if (exitType !== undefined) { + tradingEngine.current.strategyOpenStage.exitType.value = exitType + } break } case 'Manage Stage': { - tradingEngine.current.strategy.manageStageStatus.value = status + tradingEngine.current.strategyManageStage.status.value = status break } case 'Close Stage': { - tradingEngine.current.strategy.closeStageStatus.value = status + tradingEngine.current.strategyCloseStage.status.value = status + if (exitType !== undefined) { + tradingEngine.current.strategyCloseStage.exitType.value = exitType + } break } } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index bae1bf683..a66ba79b3 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -143,11 +143,11 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage executionNode = stageNode.openExecution - resetExecution(executionNode, tradingEngine.current.strategy.openStageStatus.value) + resetExecution(executionNode, tradingEngine.current.strategyOpenStage.status.value) stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage executionNode = stageNode.closeExecution - resetExecution(executionNode, tradingEngine.current.strategy.closeStageStatus.value) + resetExecution(executionNode, tradingEngine.current.strategyCloseStage.status.value) function resetExecution(executionNode, stageStatus) { @@ -422,7 +422,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } function checkTriggerOff() { - if (tradingEngine.current.strategy.triggerStageStatus.value === 'Open') { + if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] let triggerStage = strategy.triggerStage @@ -459,7 +459,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM function checkTakePosition() { if ( - tradingEngine.current.strategy.triggerStageStatus.value === 'Open' + tradingEngine.current.strategyTriggerStage.status.value === 'Open' ) { let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] let triggerStage = strategy.triggerStage @@ -510,7 +510,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM async function openStage() { /* Abort Open Stage Check */ - if (tradingEngine.current.strategy.openStageStatus.value === 'Open' && tradingEngine.current.strategy.closeStageStatus.value === 'Open') { + if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') { /* if the Close stage is opened while the open stage is still open that means that we need to stop placing orders, check what happened to the orders already placed, @@ -520,7 +520,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } /* Opening Status Procedure */ - if (tradingEngine.current.strategy.openStageStatus.value === 'Opening') { + if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') { /* This procedure is intended to run only once */ let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage @@ -558,7 +558,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } /* Open Status Procedure */ - if (tradingEngine.current.strategy.openStageStatus.value === 'Open') { + if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { /* While the Open Stage is Open, we do our regular stuff: place orders and check what happened to the orders already placed. @@ -579,23 +579,26 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingEngine.current.position.strategyOpenStage ) /* - The Open is finished when the fillSize reaches the Position Size. + The Open is finished when the fillSize + feesPaid reaches the Position Size. This can happens at any time when we update the filledSize value when we see at the exchange that orders were filled. */ - if (tradingEngine.current.position.openStageFilledSize.value === tradingEngine.current.position.size.value) { - tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.filledSize.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { + tradingStrategyModule.updateStageStatus('Open Stage', 'Closed', 'Position Size Filled') } else { /* Check the Close Stage Event */ evalConditions(stageNode, 'Close Stage Event') if (checkStopStageEvent(stageNode) === true) { - tradingStrategyModule.updateStageStatus('Open Stage', 'Closing') + tradingStrategyModule.updateStageStatus('Open Stage', 'Closing', 'Close Stage Event') } } } /* Closing Status Procedure */ - if (tradingEngine.current.strategy.openStageStatus.value === 'Closing') { + if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') { /* During the closing stage status, we do not place new orders, just check if the ones placed were filled, and cancel the ones not filled. @@ -621,33 +624,30 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } /* - The Closing is finished when the fillSize reaches the ordersSize. + The Closing is finished when the fillSize + feesPaid reaches the ordersSize. This can happens either because we update the filledSize value when we see at the exchange that orders were filled, or we reduce the ordersSize when we cancel not yet filled orders. */ - if (tradingEngine.current.position.openStageFilledSize.value === tradingEngine.current.position.openStageOrdersSize.value) { + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.filledSize.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') - } else { - /* Check the Close Stage Event */ - evalConditions(stageNode, 'Close Stage Event') - if (checkStopStageEvent(stageNode) === true) { - tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') - } } } } function manageStage() { /* Opening Status Procedure */ - if (tradingEngine.current.strategy.manageStageStatus.value === 'Opening') { + if (tradingEngine.current.strategyManageStage.status.value === 'Opening') { /* We jump the management at the loop cycle where the position is being taken. */ tradingStrategyModule.updateStageStatus('Manage Stage', 'Open') return } /* Open Status Procedure */ - if (tradingEngine.current.strategy.manageStageStatus.value === 'Open') { + if (tradingEngine.current.strategyManageStage.status.value === 'Open') { let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] let manageStage = strategy.manageStage evalConditions(manageStage, 'Manage Stage') @@ -933,7 +933,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM async function closeStage() { /* Opening Status Procedure */ - if (tradingEngine.current.strategy.closeStageStatus.value === 'Opening') { + if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') { /* This will happen only once, as soon as the Take Profit or Stop was hit. We wil not be placing orders at this time because we do not know @@ -945,7 +945,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } /* Open Status Procedure */ - if (tradingEngine.current.strategy.closeStageStatus.value === 'Open') { + if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { /* This will happen as long as the Close Stage is Open. */ @@ -964,19 +964,31 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingEngine.current.position.strategyCloseStage ) - /* Check the Close Stage Event */ - evalConditions(stageNode, 'Close Stage Event') - if (checkStopStageEvent(stageNode) === true) { - tradingStrategyModule.updateStageStatus('Close Stage', 'Closed') + /* + The Close Stage is finished when the fillSize + feesPaid reaches the Open Fill Size. + This can happens at any time when we update the filledSize value when we see + at the exchange that orders were filled. + */ + if ( + tradingEngine.current.strategyCloseStage.stageBaseAsset.filledSize.value + + tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.filledSize.value * 0.999) { + tradingStrategyModule.updateStageStatus('Close Stage', 'Closed', 'Open Stage fillSize Filled') + } else { + /* Check the Close Stage Event */ + evalConditions(stageNode, 'Close Stage Event') + if (checkStopStageEvent(stageNode) === true) { + tradingStrategyModule.updateStageStatus('Close Stage', 'Closing', 'Close Stage Event') + } } } /* Exit Position Validation */ if ( - tradingEngine.current.strategy.triggerStageStatus.value === 'Closed' && - tradingEngine.current.strategy.openStageStatus.value === 'Closed' && - tradingEngine.current.strategy.manageStageStatus.value === 'Closed' && - tradingEngine.current.strategy.closeStageStatus.value === 'Closed' + tradingEngine.current.strategyTriggerStage.status.value === 'Closed' && + tradingEngine.current.strategyOpenStage.status.value === 'Closed' && + tradingEngine.current.strategyManageStage.status.value === 'Closed' && + tradingEngine.current.strategyCloseStage.status.value === 'Closed' ) { /* Exiting Everything now. */ From 0fc5cb29292a579837088620945025d208467ba2 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 2 Aug 2020 13:04:00 +0200 Subject: [PATCH 011/112] All calculated --- LowFrequencyTrading/ExchangeAPI.js | 12 +- LowFrequencyTrading/TradingExecution.js | 290 ++++++++++++++---------- LowFrequencyTrading/TradingPosition.js | 136 +++-------- LowFrequencyTrading/TradingSystem.js | 141 ++++++++++-- 4 files changed, 318 insertions(+), 261 deletions(-) diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index 3c4861120..b94740fe3 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -77,17 +77,7 @@ 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 = bot.market.baseAsset + '/' + bot.market.quotedAsset // CCXT: a string literal symbol of the market you wish to trade on, like BTC/USD, ZEC/ETH, DOGE/DASH, etc - let amount // CCXT: how much of currency you want to trade - - /* - We need at the amount a value denominated in Base Asset, if we do not - have it, then we convert the value we have in Quoteed Asset. - */ - if (tradingEngineOrder.orderBaseAsset.size.value > 0) { - amount = tradingEngineOrder.orderBaseAsset.size.value - } else { - amount = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value - } + let amount = tradingEngineOrder.orderBaseAsset.size.value // CCXT: how much of currency you want to trade, in Base Asset. switch (tradingSystemOrder.type) { case 'Market Buy Order': { diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 2eedca830..154a52db3 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -217,11 +217,11 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Check Size: We are not going to create Orders which size is equal or less to zero. */ if (traingEngineOrderAsset.size.value <= 0) { return } - /* Place Order at the Exchange */ + /* Place Order at the Exchange, if needed. */ let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) if (result !== true) { return } - /* Update Stage Counters */ + /* Update Stage Size */ tradingEngineStage.stageBaseAsset.size.value = tradingEngineStage.stageBaseAsset.size.value + tradingEngineOrder.orderBaseAsset.size.value tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) tradingEngineStage.stageQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.size.value + tradingEngineOrder.orderQuotedAsset.size.value @@ -392,18 +392,24 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Filter by what is defined at the Strategy */ if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } - let previousSizeFilled = tradingEngineOrderAsset.sizeFilled.value - let previousPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value - let previousAmountReceived = tradingEngineOrder.orderStatistics.amountReceived.value - let previousFeesPaid = tradingEngineOrder.orderStatistics.feesPaid.value + let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value + let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value + let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value + let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value actualRateSimulation() feesPaidSimulation() - orderFillingSimulation() + percentageFilledSimulation() sizeFilledSimulation() - amountReceivedSimulation() - doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousFeesPaid) + doTheAccounting( + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) /* If the Stage is Closing and this order is still open, we need to cancel it now */ if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { @@ -462,8 +468,21 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Based on the Trading System Definition */ if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { - if (tradingEngineOrder.orderStatistics.feesPaid.value === tradingEngineOrder.orderStatistics.feesPaid.config.initialValue) { - tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + + calculatedBasedOnTradingSystem = true + } + + if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + calculatedBasedOnTradingSystem = true } } @@ -474,19 +493,36 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Fees are simulated based on the Session Paremeters */ switch (tradingEngineOrder.type) { case 'Market Order': { - tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * bot.SESSION.parameters.feeStructure.config.taker / 100 + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 + break } case 'Limit Order': { - tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * bot.SESSION.parameters.feeStructure.config.maker / 100 + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 + break } } } - tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10) + tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) } - function orderFillingSimulation() { + function percentageFilledSimulation() { /* Order Filling Simulation */ if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { @@ -515,34 +551,16 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function sizeFilledSimulation() { /* Size Filled */ - tradingEngineOrder.orderStatistics.sizeFilled.value = tradingEngineOrder.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - tradingEngineOrder.orderStatistics.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.sizeFilled.value, 10) - } + tradingEngineOrder.orderBaseAsset.sizeFilled.value = + tradingEngineOrder.orderBaseAsset.size.value * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - function amountReceivedSimulation() { - /* Amount Received */ - let calculatedBasedOnOrderDefinition = false + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = + tradingEngineOrder.orderQuotedAsset.size.value * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - /* Based on the Order Definition */ - if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { - let simulatedActualRate = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) - if (simulatedActualRate !== undefined) { - tradingEngineOrder.orderStatistics.amountReceived.value = tradingEngineOrder.orderStatistics.sizeFilled.value * simulatedActualRate - tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) - calculatedBasedOnOrderDefinition = true - } - } - } - } - - /* Based on Candle Close Value */ - if (calculatedBasedOnOrderDefinition === false) { - let simulatedActualRate = tradingEngine.current.candle.close.value - tradingEngineOrder.orderStatistics.amountReceived.value = tradingEngineOrder.orderStatistics.sizeFilled.value * simulatedActualRate - tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) - } + tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) } } @@ -776,24 +794,57 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngine.current.balance.quotedAsset.value = global.PRECISE(tradingEngine.current.balance.quotedAsset.value, 10) } - function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + } - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - break - } - case 'Live Trading Session': { - return - } - case 'Fordward Testing Session': { - return - } - case 'Paper Trading Session': { - break - } + function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + break } + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { + break + } + } + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = exitType + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 + + recalculateStageSize(tradingEngineOrder) + } + + async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return + } + } + + /* Check if we can cancel the order at the Exchange. */ + let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) + if (result === true) { /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = exitType @@ -801,85 +852,74 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Initialize this */ tradingEngine.current.distanceToEvent.closeOrder.value = 1 - /* Since the order was cancelled, we remove the unfilled amount of the order from here */ - let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value - let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100 - stageOrdersSize.value = stageOrdersSize.value - unfilledSize - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + /* + Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. + To sync our accounting, we need to check the order one last time and if it changed, fix it. + */ + + let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) + + if (order === undefined) { return } + + syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) + + recalculateStageSize() } + } - async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + function recalculateStageSize(tradingEngineOrder) { + /* + Since the order is Cancelled, we need to adjust the stage size. Remember that the Stage Size + accumulates for each asset, the order size placed at the exchange. A cancelation means that + only the part filled can be considered placed, so we need to substract from the stage size + the remainder. To achieve this with the information we currently have, we are going first + to unaccount the order size, and the account only the sizeFilled + the feesPaid. + */ + tradingEngineStage.stageBaseAsset.size.value = + tradingEngineStage.stageBaseAsset.size.value - + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageQuotedAsset.size.value = + tradingEngineStage.stageQuotedAsset.size.value - + tradingEngineOrder.orderQuotedAsset.size.value - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return + tradingEngineStage.stageBaseAsset.size.value = + tradingEngineStage.stageBaseAsset.size.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value + + tradingEngineOrder.orderBaseAsset.feesPaid.value + tradingEngineStage.stageQuotedAsset.size.value = + tradingEngineStage.stageQuotedAsset.size.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + + tradingEngineOrder.orderQuotedAsset.feesPaid.value + + tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) + tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) + } + + function checkOrderEvent(event, order, executionAlgorithm, executionNode) { + if (event !== undefined) { + for (let k = 0; k < event.situations.length; k++) { + let situation = event.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return - } - } - /* Check if we can cancel the order at the Exchange. */ - let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) - if (result === true) { - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = exitType + passed = tradingSystem.checkConditions(situation, passed) - /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(event.id) + tradingSystem.highlights.push(order.id) + tradingSystem.highlights.push(executionAlgorithm.id) + tradingSystem.highlights.push(executionNode.id) - /* Since the order was cancelled, we remove the unfilled amount of the order from here */ - let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value - let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100 - stageOrdersSize.value = stageOrdersSize.value - unfilledSize - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - - /* - Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. - To sync our accounting, we need to check the order one last time and if it changed, fix it. - */ - - let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - - if (order === undefined) { return } - - syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) - } - } - - function checkOrderEvent(event, order, executionAlgorithm, executionNode) { - if (event !== undefined) { - for (let k = 0; k < event.situations.length; k++) { - let situation = event.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(event.id) - tradingSystem.highlights.push(order.id) - tradingSystem.highlights.push(executionAlgorithm.id) - tradingSystem.highlights.push(executionNode.id) - - announcementsModule.makeAnnoucements(event) - return situation.name // if the event is triggered, we return the name of the situation that passed - } + announcementsModule.makeAnnoucements(event) + return situation.name // if the event is triggered, we return the name of the situation that passed } } } } } +} diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 0f1d3e074..6e3ef5790 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -9,10 +9,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng closePosition: closePosition, applyStopLossFormula: applyStopLossFormula, applyTakeProfitFormula: applyTakeProfitFormula, - preventStopLossDistortion: preventStopLossDistortion, - applySlippageToStopLoss: applySlippageToStopLoss, - preventTakeProfitDistortion: preventTakeProfitDistortion, - applySlippageToTakeProfit: applySlippageToTakeProfit, updateStopLoss: updateStopLoss, updateTakeProfit: updateTakeProfit, initializeSizeAndRate: initializeSizeAndRate, @@ -20,6 +16,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng updateStatus: updateStatus, updateCounters: updateCounters, updateStatistics: updateStatistics, + updateResults: updateResults, resetPosition: resetPosition, initialize: initialize, finalize: finalize @@ -179,71 +176,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } } - function preventStopLossDistortion() { - /* - Hit Point Validation - - This prevents misscalculations when a formula places the stop loss in this case way beyond the market price. - If we take the stop loss value at those situation would be a huge distortion of facts. - */ - if (bot.sessionAndMarketBaseAssetsAreEqual) { - if (tradingEngine.current.position.stopLoss.value < tradingEngine.current.candle.min.value) { - tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.min.value - } - } else { - if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.candle.max.value) { - tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.max.value - } - } - } - - function applySlippageToStopLoss() { - let slippedStopLoss = tradingEngine.current.position.stopLoss.value - - /* Apply the Slippage */ - let slippageAmount = slippedStopLoss * bot.SESSION.parameters.slippage.config.stopLoss / 100 - - if (bot.sessionAndMarketBaseAssetsAreEqual) { - slippedStopLoss = slippedStopLoss + slippageAmount - } else { - slippedStopLoss = slippedStopLoss - slippageAmount - } - - tradingEngine.current.position.endRate.value = slippedStopLoss - } - - function preventTakeProfitDistortion() { - /* - Hit Point Validation: - - This prevents misscalculations when a formula places the take profit in this case way beyond the market price. - If we take the stop loss value at those situation would be a huge distortion of facts. - */ - if (bot.sessionAndMarketBaseAssetsAreEqual) { - if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.candle.max.value) { - tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.max.value - } - } else { - if (tradingEngine.current.position.takeProfit.value < tradingEngine.current.candle.min.value) { - tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.min.value - } - } - } - - function applySlippageToTakeProfit() { - let slippedTakeProfit = tradingEngine.current.position.takeProfit.value - /* Apply the Slippage */ - let slippageAmount = slippedTakeProfit * bot.SESSION.parameters.slippage.config.takeProfit / 100 - - if (bot.sessionAndMarketBaseAssetsAreEqual) { - slippedTakeProfit = slippedTakeProfit + slippageAmount - } else { - slippedTakeProfit = slippedTakeProfit - slippageAmount - } - - tradingEngine.current.position.endRate.value = slippedTakeProfit - } - function updateEnds() { if (tradingEngine.current.position.status.value === 'Open') { tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value @@ -264,45 +196,47 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function updateStatistics() { - if (bot.sessionAndMarketBaseAssetsAreEqual) { - /* Profit Loss Calculation */ - tradingEngine.current.position.positionStatistics.profitLoss.value = - tradingEngine.current.balance.baseAsset.value - - tradingEngine.previous.balance.baseAsset.value + - tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value - - tradingEngine.previous.balance.quotedAsset.value / tradingEngine.current.position.beginRate.value - - /* ROI Calculation */ - tradingEngine.current.position.positionStatistics.ROI.value = - tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / - tradingEngine.current.position.size.value - } else { - /* Profit Loss Calculation */ - tradingEngine.current.position.positionStatistics.profitLoss.value = - tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value - - tradingEngine.previous.balance.baseAsset.value * tradingEngine.current.position.beginRate.value + - tradingEngine.current.balance.quotedAsset.value - - tradingEngine.previous.balance.quotedAsset.value - - /* ROI Calculation */ - tradingEngine.current.position.positionStatistics.ROI.value = - tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / - (tradingEngine.current.position.size.value * tradingEngine.current.position.beginRate.value) - } - /* Days Calculation */ - tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * + tradingEngine.current.position.positionStatistics.days.value = + tradingEngine.current.position.positionCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS - tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) - tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10) + } + + function updateResults() { + /* Profit Loss Calculation */ + tradingEngine.current.position.positionBaseAsset.profitLoss.value = + tradingEngine.current.balance.baseAsset.value - + tradingEngine.previous.balance.baseAsset.value + + tradingEngine.current.position.positionQuotedAsset.profitLoss.value = + tradingEngine.current.balance.quotedAsset.value - + tradingEngine.previous.balance.quotedAsset.value + + tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) + + /* ROI Calculation */ + tradingEngine.current.position.positionBaseAsset.ROI.value = + tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / + tradingEngine.current.position.positionBaseAsset.size.value + + tradingEngine.current.position.positionQuotedAsset.ROI.value = + tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / + tradingEngine.current.position.positionQuotedAsset.size.value + + tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) /* Hit Fail Calculation */ - if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { - tradingEngine.current.position.positionStatistics.hitFail.value = 'HIT' + if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'HIT' } else { - tradingEngine.current.position.positionStatistics.hitFail.value = 'FAIL' + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'FAIL' + } + if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'HIT' + } else { + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'FAIL' } } } \ No newline at end of file diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index a66ba79b3..45c6a0d11 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -902,8 +902,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM ) { logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.') - tradingPositionModule.preventStopLossDistortion() - tradingPositionModule.applySlippageToStopLoss() tradingPositionModule.closingPosition('Stop Loss') tradingStrategyModule.updateStageStatus('Close Stage', 'Opening') tradingStrategyModule.updateStageStatus('Manage Stage', 'Closed') @@ -918,8 +916,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM ) { logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.') - tradingPositionModule.preventTakeProfitDistortion() - tradingPositionModule.applySlippageToTakeProfit() tradingPositionModule.closingPosition('Take Profit') tradingStrategyModule.updateStageStatus('Close Stage', 'Opening') tradingStrategyModule.updateStageStatus('Manage Stage', 'Closed') @@ -997,28 +993,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM function exitPositionAndStrategy() { - /* Needed for Episode Statistics */ + /* Position Statistics & Results */ tradingPositionModule.updateStatistics() + tradingPositionModule.updateResults() - /* Recalculating Episode Counters */ - if (tradingEngine.current.position.positionStatistics.profitLoss.value > 0) { - tradingEngine.episode.episodeCounters.hits.value++ - } else { - tradingEngine.episode.episodeCounters.fails.value++ - } - - /* Recalculating Episode Statistics */ - if (bot.sessionAndMarketBaseAssetsAreEqual) { - tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - sessionParameters.sessionBaseAsset.config.initialBalance - tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionBaseAsset.config.initialBalance - 1 - tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.episodeCounters.positions.value - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days.value * 365 - } else { - tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - sessionParameters.sessionQuotedAsset.config.initialBalance - tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionQuotedAsset.config.initialBalance - 1 - tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.episodeCounters.positions.value - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days.value * 365 - } + updatingEpisodeAssets() + updatingEpisodeStatistics() /* Taking Position Snapshot */ if (bot.SESSION.type === 'Backtesting Session') { @@ -1047,6 +1027,119 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Distance to Events Updates */ tradingEngine.current.distanceToEvent.closePosition.value = 1 tradingEngine.current.distanceToEvent.triggerOff.value = 1 + + function updatingEpisodeAssets() { + + /* Updating Hits & Fails */ + if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeBaseAsset.hits.value++ + } else { + tradingEngine.episode.episodeBaseAsset.fails.value++ + } + if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeQuotedAsset.hits.value++ + } else { + tradingEngine.episode.episodeQuotedAsset.fails.value++ + } + + /* Updating Profit Loss */ + tradingEngine.episode.episodeBaseAsset.profitLoss.value = + tradingEngine.current.balance.baseAsset.value - + sessionParameters.sessionBaseAsset.config.initialBalance + + tradingEngine.episode.episodeQuotedAsset.profitLoss.value = + tradingEngine.current.balance.quotedAsset.value - + sessionParameters.sessionQuotedAsset.config.initialBalance + + tradingEngine.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.profitLoss.value, 10) + tradingEngine.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.episode.episodeBaseAsset.ROI.value = + (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeBaseAsset.profitLoss.value) / + sessionParameters.sessionBaseAsset.config.initialBalance - 1 + + tradingEngine.episode.episodeQuotedAsset.ROI.value = + (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeQuotedAsset.profitLoss.value) / + sessionParameters.sessionQuotedAsset.config.initialBalance - 1 + + tradingEngine.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.ROI.value, 10) + tradingEngine.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.ROI.value, 10) + + /* Updating Hit Ratio */ + tradingEngine.episode.episodeBaseAsset.hitRatio.value = + tradingEngine.episode.episodeBaseAsset.hits.value / + tradingEngine.episode.episodeCounters.positions.value + + tradingEngine.episode.episodeQuotedAsset.hitRatio.value = + tradingEngine.episode.episodeQuotedAsset.hits.value / + tradingEngine.episode.episodeCounters.positions.value + + tradingEngine.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.hitRatio.value, 10) + tradingEngine.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.hitRatio.value, 10) + + /* Updating Anualized Rate Of Return */ + tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = + tradingEngine.episode.episodeBaseAsset.ROI.value / + tradingEngine.episode.episodeStatistics.days.value * 365 + + tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = + tradingEngine.episode.episodeQuotedAsset.ROI.value / + tradingEngine.episode.episodeStatistics.days.value * 365 + + tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) + tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.episode.episodeBaseAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Hit' + } else { + tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Fail' + } + if (tradingEngine.episode.episodeQuotedAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Hit' + } else { + tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Fail' + } + } + + function updatingEpisodeStatistics() { + + /* Updating Profit Loss */ + tradingEngine.episode.episodeStatistics.profitLoss.value = + tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + tradingEngine.episode.episodeQuotedAsset.profitLoss.value + + tradingEngine.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeStatistics.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.episode.episodeStatistics.ROI.value = + ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + + tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + + tradingEngine.episode.episodeQuotedAsset.profitLoss.value + ) / ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + ) - 1 + + tradingEngine.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.episode.episodeStatistics.ROI.value, 10) + + /* Updating Anualized Rate Of Return */ + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = + tradingEngine.episode.episodeStatistics.ROI.value / + tradingEngine.episode.episodeStatistics.days.value * 365 + + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.episode.episodeStatistics.profitLoss.value > 0) { + tradingEngine.episode.episodeStatistics.hitFail.value = 'Hit' + } else { + tradingEngine.episode.episodeStatistics.hitFail.value = 'Fail' + } + } } } From d9b06694e29836803bebfb39faf6e43ac2f1a8c1 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 2 Aug 2020 14:57:51 +0200 Subject: [PATCH 012/112] All coded --- LowFrequencyTrading/TradingSimulation.js | 62 ++++++++++++++---------- LowFrequencyTrading/TradingSystem.js | 54 +++++++++++++++++++-- Session.js | 12 ----- 3 files changed, 86 insertions(+), 42 deletions(-) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 4e8f655b2..5c2d65067 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -62,9 +62,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let heartBeatDate let previousHeartBeatDate - /* This determines where we are standing */ - bot.sessionAndMarketBaseAssetsAreEqual = (sessionParameters.sessionBaseAsset.referenceParent.referenceParent.config.codeName === bot.market.baseAsset) - initializeLoop() function initializeLoop() { @@ -363,36 +360,49 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if ( tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue ) { - /* - First thing to do is to check if we are below the minimum balance, or above the maximun balance. - If we are, we are not gooing to trigger on again. + /* + We will perform this check only when we are not inside a position, + because there the balances have shifted from their resting position. */ - let minimumBalance - let maximumBalance - let balance - if (bot.sessionAndMarketBaseAssetsAreEqual) { - balance = tradingEngine.current.balance.baseAsset.value - minimumBalance = sessionParameters.sessionBaseAsset.config.minimumBalance - maximumBalance = sessionParameters.sessionBaseAsset.config.maximumBalance - } else { - balance = tradingEngine.current.balance.quotedAsset.value - minimumBalance = sessionParameters.sessionQuotedAsset.config.minimumBalance - maximumBalance = sessionParameters.sessionQuotedAsset.config.maximumBalance + let stopRunningDate = (new Date(tradingEngine.current.candle.begin.value)).toLocaleString() + + if (sessionParameters.sessionBaseAsset.config.minimumBalance !== undefined) { + if (tradingEngine.current.balance.baseAsset.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) { + const errorMessage = 'Min Balance reached @ ' + stopRunningDate + tradingSystem.errors.push([tradingSystem.id, errorMessage]) + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } + return false + } } - let stopRunningDate = new Date(tradingEngine.current.candle.begin.value) - if (balance <= minimumBalance) { - tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) } - return false + if (sessionParameters.sessionBaseAsset.config.maximumBalance !== undefined) { + if (tradingEngine.current.balance.baseAsset.value >= sessionParameters.sessionBaseAsset.config.maximumBalance) { + const errorMessage = 'Max Balance reached @ ' + stopRunningDate + tradingSystem.errors.push([tradingSystem.id, errorMessage]) + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } + return false + } } - if (balance >= maximumBalance) { - tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) } - return false + if (sessionParameters.sessionQuotedAsset.config.minimumBalance !== undefined) { + if (tradingEngine.current.balance.quotedAsset.value <= sessionParameters.sessionQuotedAsset.config.minimumBalance) { + const errorMessage = 'Min Balance reached @ ' + stopRunningDate + tradingSystem.errors.push([tradingSystem.id, errorMessage]) + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } + return false + } } + + if (sessionParameters.sessionQuotedAsset.config.maximumBalance !== undefined) { + if (tradingEngine.current.balance.quotedAsset.value >= sessionParameters.sessionQuotedAsset.config.maximumBalance) { + const errorMessage = 'Max Balance reached @ ' + stopRunningDate + tradingSystem.errors.push([tradingSystem.id, errorMessage]) + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } + return false + } + } + } return true } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 45c6a0d11..b28a7e40c 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -664,15 +664,49 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Stop Loss Management */ checkStopPhasesEvents() calculateStopLoss() + calculateStopLossPosition() /* Take Profit Management */ checkTakeProfitPhaseEvents() calculateTakeProfit() + calculateTakeProfitPosition() /* Checking if Stop or Take Profit were hit */ checkStopLossOrTakeProfitWasHit() } + function calculateTakeProfitPosition() { + /* + The position of the Stop Loss (above or below the price) is needed in order to know + later if the price hit the Stop Loss or not at every Simulation cycle. When we get + the first values of the simulation of the Stop Loss we check if it is above or below + the Position Rate, and we assign the values Above or Below to it. + */ + if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) { + if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.rate) { + tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above' + } else { + tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below' + } + } + } + + function calculateTakeProfitPosition() { + /* + The position of the Take Profit (above or below the price) is needed in order to know + later if the price hit the Take Profit or not at every Simulation cycle. When we get + the first values of the simulation of the Take Profits we check if it is above or below + the Position Rates, and we assign the values Above or Below to it. + */ + if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) { + if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.rate) { + tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above' + } else { + tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below' + } + } + } + function checkStopPhasesEvents() { let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] let openStage = strategy.openStage @@ -897,8 +931,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ if ( - (bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value) || - (!bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value) + ( + tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Above' && + tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value + ) || + ( + tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Below' && + tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value + ) ) { logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.') @@ -911,8 +951,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ if ( - (bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value) || - (!bot.sessionAndMarketBaseAssetsAreEqual && tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value) + ( + tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Below' && + tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value + ) || + ( + tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Above' && + tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value + ) ) { logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.') diff --git a/Session.js b/Session.js index 056d01a56..6f627e326 100644 --- a/Session.js +++ b/Session.js @@ -229,12 +229,6 @@ bot.sessionError(bot.SESSION.parameters.sessionBaseAsset, errorMessage) return false } - if (bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance === undefined) { - bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance = 0 - } - if (bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance === undefined) { - bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance = Number.MAX_SAFE_INTEGER - } /* Session Quoted Asset */ if (bot.SESSION.parameters.sessionQuotedAsset === undefined) { @@ -249,12 +243,6 @@ bot.sessionError(bot.SESSION.parameters.sessionQuotedAsset, errorMessage) return false } - if (bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance === undefined) { - bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance = 0 - } - if (bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance === undefined) { - bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance = Number.MAX_SAFE_INTEGER - } /* Slippage */ if (bot.SESSION.parameters.slippage === undefined) { // if the Slippage is missing we create a default one. From 519da0ea2c5b560d0e8311982633c85d27df5e77 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 2 Aug 2020 20:32:10 +0200 Subject: [PATCH 013/112] Running Again --- LowFrequencyTrading/TradingExecution.js | 20 ++++++++-------- LowFrequencyTrading/TradingPosition.js | 3 +++ LowFrequencyTrading/TradingRecords.js | 13 +++++++++-- LowFrequencyTrading/TradingSystem.js | 31 ++++++++++++++----------- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 154a52db3..69cd93704 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -53,16 +53,16 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE ) { /* Trading Stage Validations */ - if (tradingEngineStage.stageBaseAsset === undefined) { continue } - if (tradingEngineStage.stageBaseAsset.size === undefined) { continue } - if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { continue } - if (tradingEngineStage.stageBaseAsset.amountReceived === undefined) { continue } - if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { continue } - if (tradingEngineStage.stageQuotedAsset === undefined) { continue } - if (tradingEngineStage.stageQuotedAsset.size === undefined) { continue } - if (tradingEngineStage.stageQuotedAsset.sizeFilled === undefined) { continue } - if (tradingEngineStage.stageQuotedAsset.amountReceived === undefined) { continue } - if (tradingEngineStage.stageQuotedAsset.feesPaid === undefined) { continue } + if (tradingEngineStage.stageBaseAsset === undefined) { return } + if (tradingEngineStage.stageBaseAsset.size === undefined) { return } + if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { return } + if (tradingEngineStage.stageBaseAsset.amountReceived === undefined) { return } + if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { return } + if (tradingEngineStage.stageQuotedAsset === undefined) { return } + if (tradingEngineStage.stageQuotedAsset.size === undefined) { return } + if (tradingEngineStage.stageQuotedAsset.sizeFilled === undefined) { return } + if (tradingEngineStage.stageQuotedAsset.amountReceived === undefined) { return } + if (tradingEngineStage.stageQuotedAsset.feesPaid === undefined) { return } /* The user choice of asset to define the size of the position, diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 6e3ef5790..9965b2250 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -23,17 +23,20 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } let tradingEngine + let tradingSystem let sessionParameters return thisObject function initialize() { + tradingSystem = bot.simulationState.tradingSystem tradingEngine = bot.simulationState.tradingEngine sessionParameters = bot.SESSION.parameters } function finalize() { tradingEngine = undefined + tradingSystem = undefined sessionParameters = undefined } diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index 789d67feb..08d453871 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -133,7 +133,12 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { We will use the codeName of the Record Property to match it with the any of the properties of the Root Node to get the Target Node. */ - let targetNode = propertyRootNode[recordProperty.config.codeName] + let targetNode + try { + targetNode = propertyRootNode[recordProperty.config.codeName] + } catch (err) { + console.log(err.stack) + } /* 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, @@ -167,7 +172,11 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { value = targetNode.value if (recordProperty.config.decimals !== undefined) { - value = Number(value.toFixed(recordProperty.config.decimals)) + try { + value = Number(value.toFixed(recordProperty.config.decimals)) + } catch (err) { + console.log(err.stack) + } } } else { /* diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index b28a7e40c..27aa04e69 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -549,7 +549,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM false, tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit - tradingEngine.current.position.strategyOpenStage + tradingEngine.current.strategyOpenStage ) /* From here on, the state is officialy Open */ @@ -576,15 +576,15 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM false, tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit - tradingEngine.current.position.strategyOpenStage + tradingEngine.current.strategyOpenStage ) /* The Open is finished when the fillSize + feesPaid reaches the Position Size. - This can happens at any time when we update the filledSize value when we see + This can happens at any time when we update the sizeFilled value when we see at the exchange that orders were filled. */ if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.filledSize.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { tradingStrategyModule.updateStageStatus('Open Stage', 'Closed', 'Position Size Filled') @@ -607,7 +607,10 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM let executionNode = stageNode.openExecution /* Check if there are unfilled orders */ - if (tradingEngine.current.position.openStageOrdersSize.value !== tradingEngine.current.position.openStageFilledSize.value) { + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value < + tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { /* There are unfilled orders, we will check if they were executed, and cancel the ones that were not. */ evalConditions(stageNode, 'Open Execution') @@ -619,18 +622,18 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM true, tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit - tradingEngine.current.position.strategyOpenStage + tradingEngine.current.strategyOpenStage ) } /* The Closing is finished when the fillSize + feesPaid reaches the ordersSize. - This can happens either because we update the filledSize value when we see + This can happens either because we update the sizeFilled value when we see at the exchange that orders were filled, or we reduce the ordersSize when we cancel not yet filled orders. */ if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.filledSize.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') @@ -675,7 +678,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM checkStopLossOrTakeProfitWasHit() } - function calculateTakeProfitPosition() { + function calculateStopLossPosition() { /* The position of the Stop Loss (above or below the price) is needed in order to know later if the price hit the Stop Loss or not at every Simulation cycle. When we get @@ -980,7 +983,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM This will happen only once, as soon as the Take Profit or Stop was hit. We wil not be placing orders at this time because we do not know the total filled size of the Open Stage. By skiping execution now - we allow the open stage to get a final value for filledSize that we can use. + we allow the open stage to get a final value for sizeFilled that we can use. */ tradingStrategyModule.updateStageStatus('Close Stage', 'Open') return @@ -1003,18 +1006,18 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM false, tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled, // Stage Size Limit tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled, // Stage Size Limit - tradingEngine.current.position.strategyCloseStage + tradingEngine.current.strategyCloseStage ) /* The Close Stage is finished when the fillSize + feesPaid reaches the Open Fill Size. - This can happens at any time when we update the filledSize value when we see + This can happens at any time when we update the sizeFilled value when we see at the exchange that orders were filled. */ if ( - tradingEngine.current.strategyCloseStage.stageBaseAsset.filledSize.value + + tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.filledSize.value * 0.999) { + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value * 0.999) { tradingStrategyModule.updateStageStatus('Close Stage', 'Closed', 'Open Stage fillSize Filled') } else { /* Check the Close Stage Event */ From 859bf4d43eb6ad4f885ac67a88e7153cc29db70d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 3 Aug 2020 09:04:00 +0200 Subject: [PATCH 014/112] Bug Fixes --- LowFrequencyTrading/TradingPosition.js | 24 +++++++++++++++++------- LowFrequencyTrading/TradingSystem.js | 4 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 9965b2250..d787bd07c 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -165,17 +165,27 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } function setPositionRate() { - const DEFAULT_VALUE = tradingEngine.current.candle.close.value let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - if (strategy.openStage === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition.positionRate === undefined) return DEFAULT_VALUE - if (strategy.openStage.initialDefinition.positionRate.formula === undefined) return DEFAULT_VALUE + if (strategy.openStage.initialDefinition.positionRate === undefined) { + const errorText = 'Position Rate Node not Found. Fix this please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.id, errorText]) + throw (errorText) + } + if (strategy.openStage.initialDefinition.positionRate.formula === undefined) { + const errorText = 'Formula of Position Rate Node not Found. Fix this please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionRate.id, errorText]) + throw (errorText) + } let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id) - if (value === undefined) return DEFAULT_VALUE - return value + if (value === undefined) { + const errorText = 'Position Rate can not be undefined. Fix this please.' + tradingSystem.errors.push([strategy.openStage.initialDefinition.positionRate.formula.id, errorText]) + throw (errorText) + } + + tradingEngine.current.position.rate.value = value } } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 27aa04e69..7e8679141 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -686,7 +686,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM the Position Rate, and we assign the values Above or Below to it. */ if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) { - if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.rate) { + if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.rate.value) { tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above' } else { tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below' @@ -702,7 +702,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM the Position Rates, and we assign the values Above or Below to it. */ if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) { - if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.rate) { + if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.rate.value) { tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above' } else { tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below' From 4842d6fbf323d78918d485a52a94f0f3866205cf Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 3 Aug 2020 09:56:06 +0200 Subject: [PATCH 015/112] Update TradingRecords.js --- LowFrequencyTrading/TradingRecords.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index 08d453871..6c8af0d50 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -122,10 +122,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { try { propertyRootNode = eval(recordProperty.config.nodePath) } catch (err) { - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.name = " + recordProperty.name); - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.config.nodePath = " + recordProperty.config.nodePath); - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> err.stack = " + err.stack); - throw 'Can not continue with a Definition Error like this.' + unhandledException(err, 'Error Evaluation Record Property nodePath.', product, recordProperty) } } /* @@ -137,7 +134,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { try { targetNode = propertyRootNode[recordProperty.config.codeName] } catch (err) { - console.log(err.stack) + unhandledException(err, 'Error setting Target Node.', product, recordProperty) } /* If the codeName of the Record Property can not match the name of the property at @@ -175,7 +172,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { try { value = Number(value.toFixed(recordProperty.config.decimals)) } catch (err) { - console.log(err.stack) + unhandledException(err, 'Error applying configured decimals.', product, recordProperty) } } } else { @@ -327,4 +324,13 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { } } } + + function unhandledException(err, message, product, recordProperty) { + logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> " + message); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> product.name = " + product.name); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.name = " + recordProperty.name); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.config.codeName = " + recordProperty.config.codeName); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> err.stack = " + err.stack); + throw 'Can not continue with a Definition Error like this.' + } } \ No newline at end of file From 2d50c0357dc0cb2f49988910b5f58035762af237 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 3 Aug 2020 10:40:06 +0200 Subject: [PATCH 016/112] error handling improved --- LowFrequencyTrading/TradingExecution.js | 1527 ++++++++++++----------- LowFrequencyTrading/TradingRecords.js | 8 +- 2 files changed, 776 insertions(+), 759 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 69cd93704..dd5c0af13 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -52,299 +52,515 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineStage ) { - /* Trading Stage Validations */ - if (tradingEngineStage.stageBaseAsset === undefined) { return } - if (tradingEngineStage.stageBaseAsset.size === undefined) { return } - if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { return } - if (tradingEngineStage.stageBaseAsset.amountReceived === undefined) { return } - if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { return } - if (tradingEngineStage.stageQuotedAsset === undefined) { return } - if (tradingEngineStage.stageQuotedAsset.size === undefined) { return } - if (tradingEngineStage.stageQuotedAsset.sizeFilled === undefined) { return } - if (tradingEngineStage.stageQuotedAsset.amountReceived === undefined) { return } - if (tradingEngineStage.stageQuotedAsset.feesPaid === undefined) { return } + try { + /* Trading Engine Stage Validations */ + if (tradingEngineStage.stageBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.stageBaseAsset === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.size === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.sizeFilled === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.feesPaid === undefined', tradingEngineStage) } + if (tradingEngineStage.stageQuotedAsset === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.size === undefined', tradingEngineStage) } + if (tradingEngineStage.stageQuotedAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.size === undefined', tradingEngineStage) } + if (tradingEngineStage.stageQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.sizeFilled === undefined', tradingEngineStage) } + if (tradingEngineStage.stageQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.feesPaid === undefined', tradingEngineStage) } - /* - The user choice of asset to define the size of the position, - defines the asset that will track the information kept about the - stage at the trading engine data structure. We set this asset - to be used here in this way: - */ - let tradingEngineStageAsset - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - tradingEngineStageAsset = tradingEngineStage.stageBaseAsset - } else { - tradingEngineStageAsset = tradingEngineStage.stageQuotedAsset - } - /* - The asset type of the position size chosen by the user, also defines - the stage limit to be on the same asset type. - */ - let stageSizeLimit - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - stageSizeLimit = stageSizeLimitInBaseAsset - } else { - stageSizeLimit = stageSizeLimitInQuotedAsset - } - - await checkExecutionAlgorithms(executionNode) - - async function checkExecutionAlgorithms(executionNode) { - for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { - let executionAlgorithm = executionNode.executionAlgorithms[i] - await checkOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) - await checkOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) - await checkOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) - await checkOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) + /* + The user choice of asset to define the size of the position, + defines the asset that will track the information kept about the + stage at the trading engine data structure. We set this asset + to be used here in this way: + */ + let tradingEngineStageAsset + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + tradingEngineStageAsset = tradingEngineStage.stageBaseAsset + } else { + tradingEngineStageAsset = tradingEngineStage.stageQuotedAsset + } + /* + The asset type of the position size chosen by the user, also defines + the stage limit to be on the same asset type. + */ + let stageSizeLimit + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + stageSizeLimit = stageSizeLimitInBaseAsset + } else { + stageSizeLimit = stageSizeLimitInQuotedAsset } - } - async function checkOrders(orders, executionAlgorithm, executionNode) { - for (let i = 0; i < orders.length; i++) { + await checkExecutionAlgorithms(executionNode) - let tradingSystemOrder = orders[i] - - /* Trading System Validations */ - if (tradingSystemOrder.config.positionSizePercentage === undefined) { continue } - if (tradingSystemOrder.referenceParent === undefined) { continue } - - let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) - - /* Trading Engine Validations */ - if (tradingEngineOrder.serialNumber === undefined) { continue } - if (tradingEngineOrder.identifier === undefined) { continue } - if (tradingEngineOrder.exchangeId === undefined) { continue } - if (tradingEngineOrder.begin === undefined) { continue } - if (tradingEngineOrder.end === undefined) { continue } - if (tradingEngineOrder.rate === undefined) { continue } - if (tradingEngineOrder.status === undefined) { continue } - if (tradingEngineOrder.algorithmName === undefined) { continue } - if (tradingEngineOrder.orderCounters === undefined) { continue } - if (tradingEngineOrder.orderCounters.periods === undefined) { continue } - if (tradingEngineOrder.orderStatistics.days === undefined) { continue } - if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { continue } - if (tradingEngineOrder.orderStatistics.actualRate === undefined) { continue } - if (tradingEngineOrder.orderStatistics.feesPaid === undefined) { continue } - - if (tradingEngineOrder.orderBaseAsset.size === undefined) { continue } - if (tradingEngineOrder.orderBaseAsset.sizeFilled === undefined) { continue } - if (tradingEngineOrder.orderBaseAsset.amountReceived === undefined) { continue } - if (tradingEngineOrder.orderBaseAsset.feesPaid === undefined) { continue } - - if (tradingEngineOrder.orderQuotedAsset.size === undefined) { continue } - if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { continue } - if (tradingEngineOrder.orderQuotedAsset.amountReceived === undefined) { continue } - if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { continue } - - /* - The asset type of the position size chosen by the user, defines - the asset at the order data strutcture where information is going to - be stored. - */ - let traingEngineOrderAsset - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - traingEngineOrderAsset = tradingEngineOrder.orderBaseAsset - } else { - traingEngineOrderAsset = tradingEngineOrder.orderQuotedAsset + async function checkExecutionAlgorithms(executionNode) { + for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { + let executionAlgorithm = executionNode.executionAlgorithms[i] + await checkOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) + await checkOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) + await checkOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) + await checkOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) } + } - switch (tradingEngineOrder.status.value) { - case 'Not Open': { - { - /* When the stage is closing we can not create new orders */ - if (stageIsClosing === true) { continue } - /* - Check if we can create an order based on the config value for spawnMultipleOrders. - Trading System Orders that cannot spawn more than one Trading Engine Order needs to check if - at the Trading Engine Order the lock is Open or Closed. - */ - if (tradingSystemOrder.config.spawnMultipleOrders !== true) { - if (tradingEngineOrder.identifier.value === 'Closed') { - continue + async function checkOrders(orders, executionAlgorithm, executionNode) { + for (let i = 0; i < orders.length; i++) { + + let tradingSystemOrder = orders[i] + + /* Trading System Validations */ + if (tradingSystemOrder.config.positionSizePercentage === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.positionSizePercentage === undefined', tradingSystemOrder) } + if (tradingSystemOrder.referenceParent === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder) } + + let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) + + /* Trading Engine Order Validations */ + if (tradingEngineOrder.serialNumber === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.serialNumber === undefined', tradingEngineOrder) } + if (tradingEngineOrder.identifier === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.identifier === undefined', tradingEngineOrder) } + if (tradingEngineOrder.exchangeId === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.exchangeId === undefined', tradingEngineOrder) } + if (tradingEngineOrder.begin === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.begin === undefined', tradingEngineOrder) } + if (tradingEngineOrder.end === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.end === undefined', tradingEngineOrder) } + if (tradingEngineOrder.rate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate === undefined', tradingEngineOrder) } + if (tradingEngineOrder.status === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.status === undefined', tradingEngineOrder) } + if (tradingEngineOrder.algorithmName === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.algorithmName === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderCounters === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderCounters.periods === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters.periods === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderStatistics.days === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.days === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.percentageFilled === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderStatistics.actualRate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.actualRate === undefined', tradingEngineOrder) } + + if (tradingEngineOrder.orderBaseAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.size === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.sizeFilled === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.feesPaid === undefined', tradingEngineOrder) } + + if (tradingEngineOrder.orderQuotedAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.size === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.feesPaid === undefined', tradingEngineOrder) } + + /* + The asset type of the position size chosen by the user, defines + the asset at the order data strutcture where information is going to + be stored. + */ + let traingEngineOrderAsset + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + traingEngineOrderAsset = tradingEngineOrder.orderBaseAsset + } else { + traingEngineOrderAsset = tradingEngineOrder.orderQuotedAsset + } + + switch (tradingEngineOrder.status.value) { + case 'Not Open': { + { + /* When the stage is closing we can not create new orders */ + if (stageIsClosing === true) { continue } + /* + Check if we can create an order based on the config value for spawnMultipleOrders. + Trading System Orders that cannot spawn more than one Trading Engine Order needs to check if + at the Trading Engine Order the lock is Open or Closed. + */ + if (tradingSystemOrder.config.spawnMultipleOrders !== true) { + if (tradingEngineOrder.identifier.value === 'Closed') { + continue + } + } + /* Check if we need to Create this Order */ + let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (situationName !== undefined) { + + /* Open a new order */ + await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) } } - /* Check if we need to Create this Order */ - let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) - if (situationName !== undefined) { + break + } + case 'Open': { + /* Update this order properties */ + tradingEngineOrder.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.orderCounters.periods.value++ + tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS - /* Open a new order */ - await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) + /* Simulate Events that happens at the Exchange, if needed. */ + simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) + + /* Check Events that happens at the Exchange, if needed. */ + await checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) + + /* + In the previous steps, we might have discovered that the order was cancelled + at the exchange, or filled, so the order might still not be Open. + If the stage is closing or the order is not Open, we wont be cancelling orders + based on defined events. + */ + if (stageIsClosing !== true && tradingEngineOrder.status.value === 'Open') { + + /* Check if we need to Cancel this Order */ + let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (situationName !== undefined) { + + /* Simulate Order Cancelation, if needed. */ + simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + + /* Cancel the order at the Exchange, if needed. */ + await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + } } } + } + } + } + + async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { + + calculateOrderRate() + calculateOrderSize() + + /* Check Size: We are not going to create Orders which size is equal or less to zero. */ + if (traingEngineOrderAsset.size.value <= 0) { return } + + /* Place Order at the Exchange, if needed. */ + let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) + if (result !== true) { return } + + /* Update Stage Size */ + tradingEngineStage.stageBaseAsset.size.value = tradingEngineStage.stageBaseAsset.size.value + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) + tradingEngineStage.stageQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.size.value + tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) + + /* Updating Episode Counters */ + tradingEngine.episode.episodeCounters.orders.value++ + + /* Initialize this */ + tradingEngine.current.distanceToEvent.createOrder.value = 1 + + /* Create Order Procedure */ + tradingEngineOrder.status.value = 'Open' + tradingEngineOrder.identifier.value = global.UNIQUE_ID() + tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value + tradingEngineOrder.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.serialNumber.value = tradingEngine.episode.episodeCounters.orders.value + tradingEngineOrder.orderName.value = tradingSystemOrder.name + tradingEngineOrder.algorithmName.value = executionAlgorithm.name + tradingEngineOrder.situationName.value = situationName + + function calculateOrderRate() { + /* Order Rate Calculation */ + tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. + if (tradingSystemOrder.positionRate !== undefined) { + if (tradingSystemOrder.positionRate.formula !== undefined) { + tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) + + if (tradingEngineOrder.rate.value === undefined) { + const errorText = 'Rate cannot be undefined. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) + throw (errorText) + } + + if (tradingEngineOrder.rate.value <= 0) { + const errorText = 'Rate cannot be less or equal to zero. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) + throw (errorText) + } + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + } + } + } + + function calculateOrderSize() { + /* + If the position size was defined in Base Asset, then + the algorithm needs to define its size also in Base Asset. + The same if it was deined in Quoted Asset. + */ + let sizeFormula + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + /* Position was defined in Base Asset */ + if (executionAlgorithm.sizeInBaseAsset !== undefined) { + if (executionAlgorithm.sizeInBaseAsset.formula !== undefined) { + sizeFormula = executionAlgorithm.sizeInBaseAsset.formula + } else { + const errorText = 'Size In Base Asset needs a child Formula. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.sizeInBaseAsset.id, errorText]) + throw (errorText) + } + } else { + const errorText = 'Execution Algorithm needs a child Size In Base Asset. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.id, errorText]) + throw (errorText) + } + } else { + /* Position was defined in Quoted Asset */ + if (executionAlgorithm.sizeInQuotedAsset !== undefined) { + if (executionAlgorithm.sizeInQuotedAsset.formula !== undefined) { + sizeFormula = executionAlgorithm.sizeInQuotedAsset.formula + } else { + const errorText = 'Size In Quoted Asset needs a child Formula. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.sizeInQuotedAsset.id, errorText]) + throw (errorText) + } + } else { + const errorText = 'Execution Algorithm needs a child Size In Quoted Asset. Fix this please.' + tradingSystem.errors.push([executionAlgorithm.id, errorText]) + throw (errorText) + } + } + + /* Order Size Calculation */ + let algorithmSize = tradingSystem.formulas.get(sizeFormula.id) + if (algorithmSize === undefined) { + const errorText = 'Execution Algorithm Size cannot be undefined. Fix this please.' + tradingSystem.errors.push([sizeFormula.id, errorText]) + throw (errorText) + } + + /* Validate that this config exists */ + if (tradingSystemOrder.config.positionSizePercentage === undefined) { + const errorText = 'Config positionSizePercentage does not exist. Fix this please.' + tradingSystem.errors.push([tradingSystemOrder.id, errorText]) + throw (errorText) + } + + traingEngineOrderAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 + traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) + + /* Check against the Stage Size Limit */ + if ( + tradingEngineStageAsset.size.value + traingEngineOrderAsset.size.value > stageSizeLimit.value) { + /* We reduce the size to the remaining size of the position. */ + traingEngineOrderAsset.size.value = stageSizeLimit.value - tradingEngineStageAsset.value + traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) + } + + /* We are going to ESTIMATE the size in the oposite asset type, because we will need it later */ + switch (traingEngineOrderAsset.type) { + case 'Order Base Asset': { + tradingEngineOrder.orderQuotedAsset.size = tradingEngineOrder.orderBaseAsset.size * tradingEngineOrder.rate + tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) + } + case 'Order Quoted Asset': { + tradingEngineOrder.orderBaseAsset.size = tradingEngineOrder.orderQuotedAsset.size / tradingEngineOrder.rate + tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) + } + } + } + + async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return true + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return true + } + } + + let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) + + if (orderId !== undefined) { + tradingEngineOrder.exchangeId.value = orderId + return true + } + } + } + + function simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { break } - case 'Open': { - /* Update this order properties */ - tradingEngineOrder.end.value = tradingEngine.current.candle.end.value - tradingEngineOrder.orderCounters.periods.value++ - tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { + break + } + } - /* Simulate Events that happens at the Exchange, if needed. */ - simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) + /* Filter by what is defined at the Strategy */ + if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } - /* Check Events that happens at the Exchange, if needed. */ - await checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) + let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value + let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value + let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value + let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - /* - In the previous steps, we might have discovered that the order was cancelled - at the exchange, or filled, so the order might still not be Open. - If the stage is closing or the order is not Open, we wont be cancelling orders - based on defined events. - */ - if (stageIsClosing !== true && tradingEngineOrder.status.value === 'Open') { + actualRateSimulation() + feesPaidSimulation() + percentageFilledSimulation() + sizeFilledSimulation() - /* Check if we need to Cancel this Order */ - let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) - if (situationName !== undefined) { + doTheAccounting( + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) - /* Simulate Order Cancelation, if needed. */ - simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + /* If the Stage is Closing and this order is still open, we need to cancel it now */ + if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { + simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') + } - /* Cancel the order at the Exchange, if needed. */ - await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + function actualRateSimulation() { + /* Actual Rate Simulation */ + let calculatedBasedOnTradingSystem = false + + /* Based on the Trading System Definition */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { + /* Calculate this only once for this order */ + if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { + tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { + calculatedBasedOnTradingSystem = true + } + } + } + } + + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { + switch (tradingEngineOrder.type) { + case 'Market Order': { + /* Actual Rate is simulated based on the Session Paremeters */ + let slippageAmount = tradingEngineOrder.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100 + switch (tradingSystemOrder.type) { + case 'Market Sell Order': { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount + break + } + case 'Market Buy Order': { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount + break + } + } + break + } + case 'Limit Order': { + /* In Limit Orders the actual rate is the rate of the order, there is no slippage */ + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + break + } + } + } + tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + } + + function feesPaidSimulation() { + /* Fees Paid Simulation */ + let calculatedBasedOnTradingSystem = false + + /* Based on the Trading System Definition */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { + if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + + calculatedBasedOnTradingSystem = true + } + + if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + + calculatedBasedOnTradingSystem = true + } + } + } + + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { + /* Fees are simulated based on the Session Paremeters */ + switch (tradingEngineOrder.type) { + case 'Market Order': { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 + + break + } + case 'Limit Order': { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 + + break + } + } + } + tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) + } + + function percentageFilledSimulation() { + /* Order Filling Simulation */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { + + /* Percentage Filled */ + let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 + if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { + percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + } + tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + + /* Check if we need to close */ + if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Filled' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 } } } } - } - } - async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { + function sizeFilledSimulation() { + /* Size Filled */ + tradingEngineOrder.orderBaseAsset.sizeFilled.value = + tradingEngineOrder.orderBaseAsset.size.value * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - calculateOrderRate() - calculateOrderSize() + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = + tradingEngineOrder.orderQuotedAsset.size.value * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - /* Check Size: We are not going to create Orders which size is equal or less to zero. */ - if (traingEngineOrderAsset.size.value <= 0) { return } - - /* Place Order at the Exchange, if needed. */ - let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) - if (result !== true) { return } - - /* Update Stage Size */ - tradingEngineStage.stageBaseAsset.size.value = tradingEngineStage.stageBaseAsset.size.value + tradingEngineOrder.orderBaseAsset.size.value - tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) - tradingEngineStage.stageQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.size.value + tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) - - /* Updating Episode Counters */ - tradingEngine.episode.episodeCounters.orders.value++ - - /* Initialize this */ - tradingEngine.current.distanceToEvent.createOrder.value = 1 - - /* Create Order Procedure */ - tradingEngineOrder.status.value = 'Open' - tradingEngineOrder.identifier.value = global.UNIQUE_ID() - tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value - tradingEngineOrder.end.value = tradingEngine.current.candle.end.value - tradingEngineOrder.serialNumber.value = tradingEngine.episode.episodeCounters.orders.value - tradingEngineOrder.orderName.value = tradingSystemOrder.name - tradingEngineOrder.algorithmName.value = executionAlgorithm.name - tradingEngineOrder.situationName.value = situationName - - function calculateOrderRate() { - /* Order Rate Calculation */ - tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. - if (tradingSystemOrder.positionRate !== undefined) { - if (tradingSystemOrder.positionRate.formula !== undefined) { - tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) - - if (tradingEngineOrder.rate.value === undefined) { - const errorText = 'Rate cannot be undefined. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) - throw (errorText) - } - - if (tradingEngineOrder.rate.value <= 0) { - const errorText = 'Rate cannot be less or equal to zero. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) - throw (errorText) - } - tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) - } + tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) } } - function calculateOrderSize() { - /* - If the position size was defined in Base Asset, then - the algorithm needs to define its size also in Base Asset. - The same if it was deined in Quoted Asset. - */ - let sizeFormula - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - /* Position was defined in Base Asset */ - if (executionAlgorithm.sizeInBaseAsset !== undefined) { - if (executionAlgorithm.sizeInBaseAsset.formula !== undefined) { - sizeFormula = executionAlgorithm.sizeInBaseAsset.formula - } else { - const errorText = 'Size In Base Asset needs a child Formula. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.sizeInBaseAsset.id, errorText]) - throw (errorText) - } - } else { - const errorText = 'Execution Algorithm needs a child Size In Base Asset. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.id, errorText]) - throw (errorText) - } - } else { - /* Position was defined in Quoted Asset */ - if (executionAlgorithm.sizeInQuotedAsset !== undefined) { - if (executionAlgorithm.sizeInQuotedAsset.formula !== undefined) { - sizeFormula = executionAlgorithm.sizeInQuotedAsset.formula - } else { - const errorText = 'Size In Quoted Asset needs a child Formula. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.sizeInQuotedAsset.id, errorText]) - throw (errorText) - } - } else { - const errorText = 'Execution Algorithm needs a child Size In Quoted Asset. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.id, errorText]) - throw (errorText) - } - } - - /* Order Size Calculation */ - let algorithmSize = tradingSystem.formulas.get(sizeFormula.id) - if (algorithmSize === undefined) { - const errorText = 'Execution Algorithm Size cannot be undefined. Fix this please.' - tradingSystem.errors.push([sizeFormula.id, errorText]) - throw (errorText) - } - - /* Validate that this config exists */ - if (tradingSystemOrder.config.positionSizePercentage === undefined) { - const errorText = 'Config positionSizePercentage does not exist. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.id, errorText]) - throw (errorText) - } - - traingEngineOrderAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 - traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) - - /* Check against the Stage Size Limit */ - if ( - tradingEngineStageAsset.size.value + traingEngineOrderAsset.size.value > stageSizeLimit.value) { - /* We reduce the size to the remaining size of the position. */ - traingEngineOrderAsset.size.value = stageSizeLimit.value - tradingEngineStageAsset.value - traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) - } - - /* We are going to ESTIMATE the size in the oposite asset type, because we will need it later */ - switch (traingEngineOrderAsset.type) { - case 'Order Base Asset': { - tradingEngineOrder.orderQuotedAsset.size = tradingEngineOrder.orderBaseAsset.size * tradingEngineOrder.rate - tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) - } - case 'Order Quoted Asset': { - tradingEngineOrder.orderBaseAsset.size = tradingEngineOrder.orderQuotedAsset.size / tradingEngineOrder.rate - tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) - } - } - } - - async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { + async function checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) { /* Filter by Session Type */ switch (bot.SESSION.type) { @@ -362,489 +578,238 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } } - let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) + let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - if (orderId !== undefined) { - tradingEngineOrder.exchangeId.value = orderId - return true - } - } - } + if (order === undefined) { return } - function simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) { + const AT_EXCHANGE_STATUS = { + OPEN: 'open', + CLOSED: 'closed', + CANCELLED: 'canceled' + } - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - break + /* Status Checks */ + if (order.remaining === 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Filled' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 } - case 'Live Trading Session': { - return + if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Closed at the Exchange' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 } - case 'Fordward Testing Session': { - return + if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 } - case 'Paper Trading Session': { - break + + syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) + + /* Forced Cancellation Check */ + if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { + await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') } } - /* Filter by what is defined at the Strategy */ - if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } + function syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) { - let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value - let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value - let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value - let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value + let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value + let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value + let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value + let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - actualRateSimulation() - feesPaidSimulation() - percentageFilledSimulation() - sizeFilledSimulation() - - doTheAccounting( - tradingSystemOrder, - tradingEngineOrder, - previousBaseAssetSizeFilled, - previousQuotedAssetSizeFilled, - previousBaseAssetFeesPaid, - previousQuotedAssetFeesPaid - ) - - /* If the Stage is Closing and this order is still open, we need to cancel it now */ - if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { - simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') - } - - function actualRateSimulation() { - /* Actual Rate Simulation */ - let calculatedBasedOnTradingSystem = false - - /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { - /* Calculate this only once for this order */ - if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { - tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) - if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { - calculatedBasedOnTradingSystem = true - } - } - } - } - - /* Based on the Session Parameters Definition */ - if (calculatedBasedOnTradingSystem === false) { - switch (tradingEngineOrder.type) { - case 'Market Order': { - /* Actual Rate is simulated based on the Session Paremeters */ - let slippageAmount = tradingEngineOrder.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100 - switch (tradingSystemOrder.type) { - case 'Market Sell Order': { - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount - break - } - case 'Market Buy Order': { - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount - break - } - } - break - } - case 'Limit Order': { - /* In Limit Orders the actual rate is the rate of the order, there is no slippage */ - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - break - } - } - } + /* Actual Rate Calculation */ + tradingEngineOrder.orderStatistics.actualRate.value = order.price tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - } - function feesPaidSimulation() { - /* Fees Paid Simulation */ - let calculatedBasedOnTradingSystem = false - - /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { - if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 - - calculatedBasedOnTradingSystem = true - } - - if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 - - calculatedBasedOnTradingSystem = true - } - } - } - - /* Based on the Session Parameters Definition */ - if (calculatedBasedOnTradingSystem === false) { - /* Fees are simulated based on the Session Paremeters */ - switch (tradingEngineOrder.type) { - case 'Market Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 - - break - } - case 'Limit Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 - - break - } - } - } + /* Fees Paid Calculation */ + /* + As a response from the exchange we can not always get the fees. + For that reason we need to estimate them base of the information that we do have. + CCXT provides order.amount which represents the size we set for the order minus the + fees taken by the exchange, all denominated in Base Asset. + In this way if we substract to the order size this order.amount + we can get the fees. All this is denominated in base asset because that is how CCXT works. + The fees then can be estimated in Quoted Asset using the Actual Rate. + */ + tradingEngineOrder.orderBaseAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.size.value - order.amount tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.feesPaid.value * tradingEngineOrder.orderStatistics.actualRate.value tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) - } - function percentageFilledSimulation() { - /* Order Filling Simulation */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { - - /* Percentage Filled */ - let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 - if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { - percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value - } - tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - - /* Check if we need to close */ - if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Filled' - - /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 - } - } - } - } - - function sizeFilledSimulation() { - /* Size Filled */ - tradingEngineOrder.orderBaseAsset.sizeFilled.value = - tradingEngineOrder.orderBaseAsset.size.value * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = - tradingEngineOrder.orderQuotedAsset.size.value * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + /* Percentage Filled Calculation */ + tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + /* Size Filled Calculation */ + /* + CCXT returns order.filled with an amount denominated in Base Asset. We will + take it from there for our Order Base Asset. For our Order Quoted Asset we + will use the field order.cost. + */ + tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = order.cost tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) - } - } - async function checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return true - } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return true - } + doTheAccounting( + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) } - let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - - if (order === undefined) { return } - - const AT_EXCHANGE_STATUS = { - OPEN: 'open', - CLOSED: 'closed', - CANCELLED: 'canceled' - } - - /* Status Checks */ - if (order.remaining === 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Filled' - - /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 - } - if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Closed at the Exchange' - - /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 - } - if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' - - /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 - } - - syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) - - /* Forced Cancellation Check */ - if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { - await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') - } - } - - function syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) { - - let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value - let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value - let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value - let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - - /* Actual Rate Calculation */ - tradingEngineOrder.orderStatistics.actualRate.value = order.price - tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - - /* Fees Paid Calculation */ - /* - As a response from the exchange we can not always get the fees. - For that reason we need to estimate them base of the information that we do have. - CCXT provides order.amount which represents the size we set for the order minus the - fees taken by the exchange, all denominated in Base Asset. - In this way if we substract to the order size this order.amount - we can get the fees. All this is denominated in base asset because that is how CCXT works. - The fees then can be estimated in Quoted Asset using the Actual Rate. - */ - tradingEngineOrder.orderBaseAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.size.value - order.amount - tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) - tradingEngineOrder.orderQuotedAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.feesPaid.value * tradingEngineOrder.orderStatistics.actualRate.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) - - /* Percentage Filled Calculation */ - tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - - /* Size Filled Calculation */ - /* - CCXT returns order.filled with an amount denominated in Base Asset. We will - take it from there for our Order Base Asset. For our Order Quoted Asset we - will use the field order.cost. - */ - tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled - tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = order.cost - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) - - doTheAccounting( + function doTheAccounting( tradingSystemOrder, tradingEngineOrder, previousBaseAssetSizeFilled, previousQuotedAssetSizeFilled, previousBaseAssetFeesPaid, previousQuotedAssetFeesPaid - ) - } + ) { - function doTheAccounting( - tradingSystemOrder, - tradingEngineOrder, - previousBaseAssetSizeFilled, - previousQuotedAssetSizeFilled, - previousBaseAssetFeesPaid, - previousQuotedAssetFeesPaid - ) { + updateStageAssets() + updateBalances() - updateStageAssets() - updateBalances() + function updateStageAssets() { + /* Stage Base Asset: Undo the previous accounting */ + tradingEngineStage.stageBaseAsset.sizeFilled.value = + tradingEngineStage.stageBaseAsset.value - + previousBaseAssetSizeFilled - function updateStageAssets() { - /* Stage Base Asset: Undo the previous accounting */ - tradingEngineStage.stageBaseAsset.sizeFilled.value = - tradingEngineStage.stageBaseAsset.value - - previousBaseAssetSizeFilled + tradingEngineStage.stageBaseAsset.feesPaid.value = + tradingEngineStage.feesPaid.value - + previousBaseAssetFeesPaid - tradingEngineStage.stageBaseAsset.feesPaid.value = - tradingEngineStage.feesPaid.value - - previousBaseAssetFeesPaid + /* Stage Base Asset: Account the current filling and fees */ + tradingEngineStage.stageBaseAsset.sizeFilled.value = + tradingEngineStage.stageBaseAsset.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value - /* Stage Base Asset: Account the current filling and fees */ - tradingEngineStage.stageBaseAsset.sizeFilled.value = - tradingEngineStage.stageBaseAsset.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value + tradingEngineStage.stageBaseAsset.feesPaid.value = + tradingEngineStage.feesPaid.value + + tradingEngineOrder.orderBaseAsset.feesPaid.value - tradingEngineStage.stageBaseAsset.feesPaid.value = - tradingEngineStage.feesPaid.value + - tradingEngineOrder.orderBaseAsset.feesPaid.value + /* Stage Quote Asset: Undo the previous accounting */ + tradingEngineStage.stageQuotedAsset.sizeFilled.value = + tradingEngineStage.stageQuotedAsset.value - + previousQuotedAssetSizeFilled - /* Stage Quote Asset: Undo the previous accounting */ - tradingEngineStage.stageQuotedAsset.sizeFilled.value = - tradingEngineStage.stageQuotedAsset.value - - previousQuotedAssetSizeFilled + tradingEngineStage.stageQuotedAsset.feesPaid.value = + tradingEngineStage.feesPaid.value - + previousQuotedAssetFeesPaid - tradingEngineStage.stageQuotedAsset.feesPaid.value = - tradingEngineStage.feesPaid.value - - previousQuotedAssetFeesPaid + /* Stage Quote Asset: Account the current filling and fees */ + tradingEngineStage.stageQuotedAsset.sizeFilled.value = + tradingEngineStage.stageQuotedAsset.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value - /* Stage Quote Asset: Account the current filling and fees */ - tradingEngineStage.stageQuotedAsset.sizeFilled.value = - tradingEngineStage.stageQuotedAsset.value + - tradingEngineOrder.orderQuotedAsset.sizeFilled.value + tradingEngineStage.stageQuotedAsset.feesPaid.value = + tradingEngineStage.feesPaid.value + + tradingEngineOrder.orderQuotedAsset.feesPaid.value - tradingEngineStage.stageQuotedAsset.feesPaid.value = - tradingEngineStage.feesPaid.value + - tradingEngineOrder.orderQuotedAsset.feesPaid.value + tradingEngineStage.stageBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizeFilled.value, 10) + tradingEngineStage.stageBaseAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageBaseAsset.feesPaid.value, 10) - tradingEngineStage.stageBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizeFilled.value, 10) - tradingEngineStage.stageBaseAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageBaseAsset.feesPaid.value, 10) + tradingEngineStage.stageQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizeFilled.value, 10) + tradingEngineStage.stageQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.feesPaid.value, 10) + } + + function updateBalances() { + /* Balances Update */ + switch (true) { + case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { + + /* Balance Base Asset: Undo the previous accounting */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value - + previousBaseAssetSizeFilled + + /* Balance Base Asset: Account the current filling and fees */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value + + /* Balance Quoted Asset: Undo the previous accounting */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value + + previousQuotedAssetSizeFilled + + previousQuotedAssetFeesPaid + + /* Balance Quoted Asset: Account the current filling and fees */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value - + tradingEngineOrder.orderQuotedAsset.sizeFilled.value - + tradingEngineOrder.orderQuotedAsset.feesPaid.value + break + } + case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { + + /* Balance Base Asset: Undo the previous accounting */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value + + previousBaseAssetSizeFilled + + previousBaseAssetFeesPaid + + /* Balance Base Asset: Account the current filling and fees */ + tradingEngine.current.balance.baseAsset.value = + tradingEngine.current.balance.baseAsset.value - + tradingEngineOrder.orderBaseAsset.sizeFilled.value - + tradingEngineOrder.orderBaseAsset.feesPaid.value + + /* Balance Quoted Asset: Undo the previous accounting */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value - + previousQuotedAssetSizeFilled + + /* Balance Quoted Asset: Account the current filling and fees */ + tradingEngine.current.balance.quotedAsset.value = + tradingEngine.current.balance.quotedAsset.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + break + } + } + tradingEngine.current.balance.baseAsset.value = global.PRECISE(tradingEngine.current.balance.baseAsset.value, 10) + tradingEngine.current.balance.quotedAsset.value = global.PRECISE(tradingEngine.current.balance.quotedAsset.value, 10) + } - tradingEngineStage.stageQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizeFilled.value, 10) - tradingEngineStage.stageQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.feesPaid.value, 10) } - function updateBalances() { - /* Balances Update */ - switch (true) { - case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { + function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { - /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value - - previousBaseAssetSizeFilled - - /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value - - /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value + - previousQuotedAssetSizeFilled + - previousQuotedAssetFeesPaid - - /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value - - tradingEngineOrder.orderQuotedAsset.sizeFilled.value - - tradingEngineOrder.orderQuotedAsset.feesPaid.value + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { break } - case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { - - /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value + - previousBaseAssetSizeFilled + - previousBaseAssetFeesPaid - - /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value - - tradingEngineOrder.orderBaseAsset.sizeFilled.value - - tradingEngineOrder.orderBaseAsset.feesPaid.value - - /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value - - previousQuotedAssetSizeFilled - - /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value + - tradingEngineOrder.orderQuotedAsset.sizeFilled.value + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { break } } - tradingEngine.current.balance.baseAsset.value = global.PRECISE(tradingEngine.current.balance.baseAsset.value, 10) - tradingEngine.current.balance.quotedAsset.value = global.PRECISE(tradingEngine.current.balance.quotedAsset.value, 10) - } - } - - function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - break - } - case 'Live Trading Session': { - return - } - case 'Fordward Testing Session': { - return - } - case 'Paper Trading Session': { - break - } - } - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = exitType - - /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 - - recalculateStageSize(tradingEngineOrder) - } - - async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return - } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return - } - } - - /* Check if we can cancel the order at the Exchange. */ - let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) - if (result === true) { /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = exitType @@ -852,74 +817,126 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Initialize this */ tradingEngine.current.distanceToEvent.closeOrder.value = 1 - /* - Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. - To sync our accounting, we need to check the order one last time and if it changed, fix it. - */ - - let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - - if (order === undefined) { return } - - syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) - - recalculateStageSize() + recalculateStageSize(tradingEngineOrder) } - } - function recalculateStageSize(tradingEngineOrder) { - /* - Since the order is Cancelled, we need to adjust the stage size. Remember that the Stage Size - accumulates for each asset, the order size placed at the exchange. A cancelation means that - only the part filled can be considered placed, so we need to substract from the stage size - the remainder. To achieve this with the information we currently have, we are going first - to unaccount the order size, and the account only the sizeFilled + the feesPaid. - */ - tradingEngineStage.stageBaseAsset.size.value = - tradingEngineStage.stageBaseAsset.size.value - - tradingEngineOrder.orderBaseAsset.size.value - tradingEngineStage.stageQuotedAsset.size.value = - tradingEngineStage.stageQuotedAsset.size.value - - tradingEngineOrder.orderQuotedAsset.size.value + async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { - tradingEngineStage.stageBaseAsset.size.value = - tradingEngineStage.stageBaseAsset.size.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value + - tradingEngineOrder.orderBaseAsset.feesPaid.value - tradingEngineStage.stageQuotedAsset.size.value = - tradingEngineStage.stageQuotedAsset.size.value + - tradingEngineOrder.orderQuotedAsset.sizeFilled.value + - tradingEngineOrder.orderQuotedAsset.feesPaid.value - - tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) - tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) - } - - function checkOrderEvent(event, order, executionAlgorithm, executionNode) { - if (event !== undefined) { - for (let k = 0; k < event.situations.length; k++) { - let situation = event.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return + } + } - passed = tradingSystem.checkConditions(situation, passed) + /* Check if we can cancel the order at the Exchange. */ + let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) + if (result === true) { + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = exitType - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(event.id) - tradingSystem.highlights.push(order.id) - tradingSystem.highlights.push(executionAlgorithm.id) - tradingSystem.highlights.push(executionNode.id) + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 - announcementsModule.makeAnnoucements(event) - return situation.name // if the event is triggered, we return the name of the situation that passed + /* + Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. + To sync our accounting, we need to check the order one last time and if it changed, fix it. + */ + + let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) + + if (order === undefined) { return } + + syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) + + recalculateStageSize() + } + } + + function recalculateStageSize(tradingEngineOrder) { + /* + Since the order is Cancelled, we need to adjust the stage size. Remember that the Stage Size + accumulates for each asset, the order size placed at the exchange. A cancelation means that + only the part filled can be considered placed, so we need to substract from the stage size + the remainder. To achieve this with the information we currently have, we are going first + to unaccount the order size, and the account only the sizeFilled + the feesPaid. + */ + tradingEngineStage.stageBaseAsset.size.value = + tradingEngineStage.stageBaseAsset.size.value - + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageQuotedAsset.size.value = + tradingEngineStage.stageQuotedAsset.size.value - + tradingEngineOrder.orderQuotedAsset.size.value + + tradingEngineStage.stageBaseAsset.size.value = + tradingEngineStage.stageBaseAsset.size.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value + + tradingEngineOrder.orderBaseAsset.feesPaid.value + tradingEngineStage.stageQuotedAsset.size.value = + tradingEngineStage.stageQuotedAsset.size.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + + tradingEngineOrder.orderQuotedAsset.feesPaid.value + + tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) + tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) + } + + function checkOrderEvent(event, order, executionAlgorithm, executionNode) { + if (event !== undefined) { + for (let k = 0; k < event.situations.length; k++) { + let situation = event.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(event.id) + tradingSystem.highlights.push(order.id) + tradingSystem.highlights.push(executionAlgorithm.id) + tradingSystem.highlights.push(executionNode.id) + + announcementsModule.makeAnnoucements(event) + return situation.name // if the event is triggered, we return the name of the situation that passed + } } } } + } catch (err) { + if (typeof err === 'string' || err instanceof String) { + logger.write(MODULE_NAME, '[ERROR] checkExecution -> err = ' + err) + } + if (err.stack !== undefined) { + logger.write(MODULE_NAME, '[ERROR] checkExecution -> err = ' + err.stack) + } } } + + function badDefinitionUnhandledException(err, message, node) { + tradingSystem.errors.push([node.id, message]) + + logger.write(MODULE_NAME, "[ERROR] -> " + message); + logger.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name); + logger.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type); + logger.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config)); + if (err !== undefined) { + logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack); + } + throw 'It is not safe to continue with a Definition Error like this. Please fix the problem and try again.' + } } diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index 6c8af0d50..3aff3d3d3 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -122,7 +122,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { try { propertyRootNode = eval(recordProperty.config.nodePath) } catch (err) { - unhandledException(err, 'Error Evaluation Record Property nodePath.', product, recordProperty) + badDefinitionUnhandledException(err, 'Error Evaluation Record Property nodePath.', product, recordProperty) } } /* @@ -134,7 +134,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { try { targetNode = propertyRootNode[recordProperty.config.codeName] } catch (err) { - unhandledException(err, 'Error setting Target Node.', product, recordProperty) + badDefinitionUnhandledException(err, 'Error setting Target Node.', product, recordProperty) } /* If the codeName of the Record Property can not match the name of the property at @@ -172,7 +172,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { try { value = Number(value.toFixed(recordProperty.config.decimals)) } catch (err) { - unhandledException(err, 'Error applying configured decimals.', product, recordProperty) + badDefinitionUnhandledException(err, 'Error applying configured decimals.', product, recordProperty) } } } else { @@ -325,7 +325,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { } } - function unhandledException(err, message, product, recordProperty) { + function badDefinitionUnhandledException(err, message, product, recordProperty) { logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> " + message); logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> product.name = " + product.name); logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.name = " + recordProperty.name); From fcff9493799e245dc5db650563f523d9bc465755 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 3 Aug 2020 13:09:42 +0200 Subject: [PATCH 017/112] Update TradingExecution.js --- LowFrequencyTrading/TradingExecution.js | 60 +++++-------------------- 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index dd5c0af13..ba3904f3d 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -164,7 +164,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (situationName !== undefined) { /* Open a new order */ - await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) + await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, traingEngineOrderAsset, situationName) } } break @@ -205,7 +205,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } } - async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { + async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, traingEngineOrderAsset, situationName) { calculateOrderRate() calculateOrderSize() @@ -246,17 +246,9 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (tradingSystemOrder.positionRate.formula !== undefined) { tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) - if (tradingEngineOrder.rate.value === undefined) { - const errorText = 'Rate cannot be undefined. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) - throw (errorText) - } + if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingEngineOrder) } + if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingEngineOrder) } - if (tradingEngineOrder.rate.value <= 0) { - const errorText = 'Rate cannot be less or equal to zero. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.positionRate.formula.id, errorText]) - throw (errorText) - } tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) } } @@ -271,50 +263,22 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE let sizeFormula if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { /* Position was defined in Base Asset */ - if (executionAlgorithm.sizeInBaseAsset !== undefined) { - if (executionAlgorithm.sizeInBaseAsset.formula !== undefined) { - sizeFormula = executionAlgorithm.sizeInBaseAsset.formula - } else { - const errorText = 'Size In Base Asset needs a child Formula. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.sizeInBaseAsset.id, errorText]) - throw (errorText) - } - } else { - const errorText = 'Execution Algorithm needs a child Size In Base Asset. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.id, errorText]) - throw (errorText) - } + if (executionAlgorithm.sizeInBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInBaseAsset === undefined', executionAlgorithm) } + if (executionAlgorithm.sizeInBaseAsset.formula === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInBaseAsset.formula === undefined', executionAlgorithm.sizeInBaseAsset) } + sizeFormula = executionAlgorithm.sizeInBaseAsset.formula } else { /* Position was defined in Quoted Asset */ - if (executionAlgorithm.sizeInQuotedAsset !== undefined) { - if (executionAlgorithm.sizeInQuotedAsset.formula !== undefined) { - sizeFormula = executionAlgorithm.sizeInQuotedAsset.formula - } else { - const errorText = 'Size In Quoted Asset needs a child Formula. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.sizeInQuotedAsset.id, errorText]) - throw (errorText) - } - } else { - const errorText = 'Execution Algorithm needs a child Size In Quoted Asset. Fix this please.' - tradingSystem.errors.push([executionAlgorithm.id, errorText]) - throw (errorText) - } + if (executionAlgorithm.sizeInQuotedAsset === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInQuotedAsset === undefined', executionAlgorithm) } + if (executionAlgorithm.sizeInQuotedAsset.formula === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInQuotedAsset.formula === undefined', executionAlgorithm.sizeInBaseAsset) } + sizeFormula = executionAlgorithm.sizeInQuotedAsset.formula } /* Order Size Calculation */ let algorithmSize = tradingSystem.formulas.get(sizeFormula.id) - if (algorithmSize === undefined) { - const errorText = 'Execution Algorithm Size cannot be undefined. Fix this please.' - tradingSystem.errors.push([sizeFormula.id, errorText]) - throw (errorText) - } + if (algorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'algorithmSize === undefined', sizeFormula) } /* Validate that this config exists */ - if (tradingSystemOrder.config.positionSizePercentage === undefined) { - const errorText = 'Config positionSizePercentage does not exist. Fix this please.' - tradingSystem.errors.push([tradingSystemOrder.id, errorText]) - throw (errorText) - } + if (tradingSystemOrder.config.positionSizePercentage === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.positionSizePercentage === undefined', executionAlgorithm) } traingEngineOrderAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) From 6cc55c2de46cc51b6c3f5e4c9747402cf95d9b4d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 3 Aug 2020 14:18:25 +0200 Subject: [PATCH 018/112] Update TradingExecution.js --- LowFrequencyTrading/TradingExecution.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index ba3904f3d..5a4687696 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -294,12 +294,12 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* We are going to ESTIMATE the size in the oposite asset type, because we will need it later */ switch (traingEngineOrderAsset.type) { case 'Order Base Asset': { - tradingEngineOrder.orderQuotedAsset.size = tradingEngineOrder.orderBaseAsset.size * tradingEngineOrder.rate - tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value + tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) } case 'Order Quoted Asset': { - tradingEngineOrder.orderBaseAsset.size = tradingEngineOrder.orderQuotedAsset.size / tradingEngineOrder.rate - tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) + tradingEngineOrder.orderBaseAsset.size.value = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value + tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) } } } @@ -655,38 +655,38 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function updateStageAssets() { /* Stage Base Asset: Undo the previous accounting */ tradingEngineStage.stageBaseAsset.sizeFilled.value = - tradingEngineStage.stageBaseAsset.value - + tradingEngineStage.stageBaseAsset.sizeFilled.value - previousBaseAssetSizeFilled tradingEngineStage.stageBaseAsset.feesPaid.value = - tradingEngineStage.feesPaid.value - + tradingEngineStage.stageBaseAsset.feesPaid.value - previousBaseAssetFeesPaid /* Stage Base Asset: Account the current filling and fees */ tradingEngineStage.stageBaseAsset.sizeFilled.value = - tradingEngineStage.stageBaseAsset.value + + tradingEngineStage.stageBaseAsset.sizeFilled.value + tradingEngineOrder.orderBaseAsset.sizeFilled.value tradingEngineStage.stageBaseAsset.feesPaid.value = - tradingEngineStage.feesPaid.value + + tradingEngineStage.stageBaseAsset.feesPaid.value + tradingEngineOrder.orderBaseAsset.feesPaid.value /* Stage Quote Asset: Undo the previous accounting */ tradingEngineStage.stageQuotedAsset.sizeFilled.value = - tradingEngineStage.stageQuotedAsset.value - + tradingEngineStage.stageQuotedAsset.sizeFilled.value - previousQuotedAssetSizeFilled tradingEngineStage.stageQuotedAsset.feesPaid.value = - tradingEngineStage.feesPaid.value - + tradingEngineStage.stageQuotedAsset.feesPaid.value - previousQuotedAssetFeesPaid /* Stage Quote Asset: Account the current filling and fees */ tradingEngineStage.stageQuotedAsset.sizeFilled.value = - tradingEngineStage.stageQuotedAsset.value + + tradingEngineStage.stageQuotedAsset.sizeFilled.value + tradingEngineOrder.orderQuotedAsset.sizeFilled.value tradingEngineStage.stageQuotedAsset.feesPaid.value = - tradingEngineStage.feesPaid.value + + tradingEngineStage.stageQuotedAsset.feesPaid.value + tradingEngineOrder.orderQuotedAsset.feesPaid.value tradingEngineStage.stageBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizeFilled.value, 10) From 983bb1056797bf4ae8b4775944346daa3a3feb23 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 3 Aug 2020 14:53:56 +0200 Subject: [PATCH 019/112] Bug Fixes --- LowFrequencyTrading/Snapshots.js | 8 +++++--- LowFrequencyTrading/TradingExecution.js | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/LowFrequencyTrading/Snapshots.js b/LowFrequencyTrading/Snapshots.js index 89c10767b..49456433f 100644 --- a/LowFrequencyTrading/Snapshots.js +++ b/LowFrequencyTrading/Snapshots.js @@ -252,7 +252,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) { function getResults(openDatetime, closeDatetime) { - let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type'] + let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result In Base Asset', 'Result In Quoted Asset', 'ROI in Base Asset', 'ROI in Quoted Asset', 'Exit Type'] closeValues = [ tradingEngine.episode.episodeCounters.positions.value, // Position Number (new Date(openDatetime)).toISOString(), // Open Datetime @@ -260,8 +260,10 @@ exports.newSnapshots = function newSnapshots(bot, logger) { tradingEngine.current.strategy.strategyName.value, // Strategy Name tradingEngine.current.strategy.situationName.value, // Trigger On Situation tradingEngine.current.position.situationName.value, // Take Position Situation - tradingEngine.current.position.positionStatistics.hitFail.value, // Result - tradingEngine.current.position.positionStatistics.ROI.value, // ROI + tradingEngine.current.position.positionBaseAsset.hitFail.value, // Result in Base Asset + tradingEngine.current.position.positionQuotedAsset.hitFail.value, // Result in Base Asset + tradingEngine.current.position.positionBaseAsset.ROI.value, // ROI in Base Asseet + tradingEngine.current.position.positionQuotedAsset.ROI.value, // ROI in Quoted Asset tradingEngine.current.position.exitType.value // Exit Type ] diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 5a4687696..b9ae0bcb0 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -247,6 +247,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingEngineOrder) } + if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingEngineOrder) } if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingEngineOrder) } tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) @@ -276,6 +277,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Order Size Calculation */ let algorithmSize = tradingSystem.formulas.get(sizeFormula.id) if (algorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'algorithmSize === undefined', sizeFormula) } + if (isNaN(algorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(algorithmSize) === true', sizeFormula) } /* Validate that this config exists */ if (tradingSystemOrder.config.positionSizePercentage === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.positionSizePercentage === undefined', executionAlgorithm) } @@ -287,7 +289,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if ( tradingEngineStageAsset.size.value + traingEngineOrderAsset.size.value > stageSizeLimit.value) { /* We reduce the size to the remaining size of the position. */ - traingEngineOrderAsset.size.value = stageSizeLimit.value - tradingEngineStageAsset.value + traingEngineOrderAsset.size.value = stageSizeLimit.value - tradingEngineStageAsset.size.value traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) } From 9fecfc8c87f9e5347f796bfc5051e098ffe1ea76 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 4 Aug 2020 11:32:08 +0200 Subject: [PATCH 020/112] Trading Stages Moduule --- LowFrequencyTrading/Snapshots.js | 7 +- LowFrequencyTrading/TradingExecution.js | 8 +- LowFrequencyTrading/TradingPosition.js | 1 + LowFrequencyTrading/TradingSimulation.js | 31 +- LowFrequencyTrading/TradingStages.js | 1179 ++++++++++++++++++++++ LowFrequencyTrading/TradingStrategy.js | 28 - LowFrequencyTrading/TradingSystem.js | 1120 +------------------- 7 files changed, 1250 insertions(+), 1124 deletions(-) create mode 100644 LowFrequencyTrading/TradingStages.js diff --git a/LowFrequencyTrading/Snapshots.js b/LowFrequencyTrading/Snapshots.js index 49456433f..4c085855a 100644 --- a/LowFrequencyTrading/Snapshots.js +++ b/LowFrequencyTrading/Snapshots.js @@ -5,7 +5,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) { const MODULE_NAME = 'Snapshots' let thisObject = { - reset: reset, + updateChart: updateChart, strategyEntry: strategyEntry, strategyExit: strategyExit, positionEntry: positionEntry, @@ -60,9 +60,8 @@ exports.newSnapshots = function newSnapshots(bot, logger) { chart = undefined } - function reset(pChart) { - /* This function helps reset data structures at every cycle of the simulation loop/=. */ - chart = pChart // We need chat to be a local object accessible from conditions and formulas. + function updateChart(pChart) { + chart = pChart } function strategyEntry() { diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index b9ae0bcb0..a05bab963 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -5,7 +5,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE const MODULE_NAME = 'Trading Execution' let thisObject = { - checkExecution: checkExecution, + runExecution: runExecution, initialize: initialize, finalize: finalize } @@ -43,7 +43,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE announcementsModule = undefined } - async function checkExecution( + async function runExecution( executionNode, stageIsOpening, stageIsClosing, @@ -884,10 +884,10 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } } catch (err) { if (typeof err === 'string' || err instanceof String) { - logger.write(MODULE_NAME, '[ERROR] checkExecution -> err = ' + err) + logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err) } if (err.stack !== undefined) { - logger.write(MODULE_NAME, '[ERROR] checkExecution -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err.stack) } } } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index d787bd07c..4bfea4f61 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -71,6 +71,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function closePosition() { tradingEngine.current.position.status.value = 'Closed' tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.position.endRate.value = tradingEngine.current.candle.close.value /* Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure. */ diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 5c2d65067..cc07cb796 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -33,14 +33,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) } if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) } - /* Stop Loss Management TODO */ - const MIN_STOP_LOSS_VALUE = 0.0000000001 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result. - const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER - - /* Take Profit Management TODO */ - const MIN_TAKE_PROFIT_VALUE = 0.0000000001 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result. - const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER - /* These are the Modules we will need to run the Simulation */ const TRADING_ENGINE_MODULE = require('./TradingEngine.js') let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) @@ -131,25 +123,18 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT heartBeat() positionChartAtCurrentCandle() - tradingSystemModule.reset(chart) - tradingSystemModule.mantainPositions() - tradingSystemModule.mantainStrategies() - tradingSystemModule.mantainOrders() + + /* The chart was recalculated based on the current candle. */ + tradingSystemModule.updateChart(chart) + + /* Do the stuff needed previous to the run */ + tradingSystemModule.mantain() tradingEngineModule.updateEpisodeCountersAndStatistics() tradingEngineModule.updateDistanceToEventsCounters() - /* Run the Trigger Stage */ - tradingSystemModule.triggerStage() - - /* Run the Open Stage */ - await tradingSystemModule.openStage() - - /* Run the Manage Stage */ - tradingSystemModule.manageStage() - - /* Run the Close Stage */ - await tradingSystemModule.closeStage() + /* Run one cycle of the Trading System*/ + await tradingSystemModule.run() /* Add new records to the process output */ tradingRecordsModule.appendRecords() diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js new file mode 100644 index 000000000..1ecc9f5fc --- /dev/null +++ b/LowFrequencyTrading/TradingStages.js @@ -0,0 +1,1179 @@ +exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineModule) { + /* + This module packages all functions related to Stages. + */ + const MODULE_NAME = 'Trading Stages' + let thisObject = { + updateChart: updateChart, + mantain: mantain, + runTriggerStage: runTriggerStage, + runOpenStage: runOpenStage, + runManageStage: runManageStage, + runCloseStage: runCloseStage, + updateEnds: updateEnds, + updateStatus: updateStatus, + updateCounters: updateCounters, + updateStatistics: updateStatistics, + resetStage: resetStage, + initialize: initialize, + finalize: finalize + } + + const TRADING_STRATEGY_MODULE = require('./TradingStrategy.js') + let tradingStrategyModule = TRADING_STRATEGY_MODULE.newTradingStrategy(bot, logger, tradingEngineModule) + + const TRADING_POSITION_MODULE = require('./TradingPosition.js') + let tradingPositionModule = TRADING_POSITION_MODULE.newTradingPosition(bot, logger, tradingEngineModule) + + const TRADING_ENGINE_EXECUTION = require('./TradingExecution.js') + let tradingExecutionModule = TRADING_ENGINE_EXECUTION.newTradingExecution(bot, logger, tradingEngineModule) + + const ANNOUNCEMENTS_MODULE = require('./Announcements.js') + let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger) + + const SNAPSHOTS_MODULE = require('./Snapshots.js') + let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger) + + + let tradingEngine + let tradingSystem + let sessionParameters + + return thisObject + + function initialize() { + tradingSystem = bot.simulationState.tradingSystem + tradingEngine = bot.simulationState.tradingEngine + sessionParameters = bot.SESSION.parameters + + tradingStrategyModule.initialize() + tradingPositionModule.initialize() + announcementsModule.initialize() + snapshotsModule.initialize() + tradingExecutionModule.initialize() + } + + function finalize() { + tradingEngine = undefined + tradingSystem = undefined + sessionParameters = undefined + + tradingStrategyModule.finalize() + tradingStrategyModule = undefined + + tradingPositionModule.finalize() + tradingPositionModule = undefined + + announcementsModule.finalize() + announcementsModule = undefined + + snapshotsModule.finalize() + snapshotsModule = undefined + + tradingExecutionModule.finalize() + tradingExecutionModule = undefined + } + + function updateChart(pChart) { + snapshotsModule.updateChart(pChart) + } + + function mantain() { + mantainPositions() + mantainStrategies() + mantainStages() + mantainOrders() + + function mantainStrategies() { + tradingStrategyModule.updateStatus() + tradingStrategyModule.updateCounters() + tradingStrategyModule.updateEnds() + } + + function mantainStages() { + updateStatus() + updateCounters() + updateEnds() + } + + function mantainPositions() { + tradingPositionModule.updateStatus() + tradingPositionModule.updateCounters() + tradingPositionModule.updateEnds() + } + + function mantainOrders() { + /* Reset All Exchange Orders that are on Closed status, to their initial value */ + if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return } + + let stageNode + let executionNode + + stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + executionNode = stageNode.openExecution + resetExecution(executionNode, tradingEngine.current.strategyOpenStage.status.value) + + stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + executionNode = stageNode.closeExecution + resetExecution(executionNode, tradingEngine.current.strategyCloseStage.status.value) + + function resetExecution(executionNode, stageStatus) { + + resetExecutionAlgorithms(executionNode) + + function resetExecutionAlgorithms(executionNode) { + for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { + let executionAlgorithm = executionNode.executionAlgorithms[i] + resetOrders(executionAlgorithm.marketBuyOrders) + resetOrders(executionAlgorithm.marketSellOrders) + resetOrders(executionAlgorithm.limitBuyOrders) + resetOrders(executionAlgorithm.limitSellOrders) + } + } + + function resetOrders(orders) { + for (let i = 0; i < orders.length; i++) { + let tradingSystemOrder = orders[i] + if (tradingSystemOrder.referenceParent === undefined) { continue } + let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) + if (tradingEngineOrder.status === undefined) { continue } + if (tradingEngineOrder.status.value === 'Closed') { + /* We reset the order data structure inside the Trading Engine to its initial value */ + tradingEngineOrder.initialize(tradingEngineOrder) + if (tradingSystemOrder.config.spawnMultipleOrders !== true) { + /* + We close the lock so as to prevent this data structure to be used again during this same stage execution. + */ + if (stageStatus === 'Open') { + tradingEngineOrder.lock.value = 'Closed' + } + } + } + } + } + } + } + } + + function runTriggerStage() { + /* + We check if we will be triggering on, off or taking position. + */ + checkTriggerOn() + checkTriggerOff() + checkTakePosition() + + function checkTriggerOn() { + if ( + tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue + ) { + /* + To pick a new strategy we will check that any of the situations of the trigger on is true. + Once we enter into one strategy, we will ignore market conditions for others. However there is also + a strategy trigger off which can be hit before taking a position. If hit, we would + be outside a strategy again and looking for the conditions to enter all over again. + */ + tradingSystem.evalConditions(tradingSystem, 'Trigger On Event') + + for (let j = 0; j < tradingSystem.tradingStrategies.length; j++) { + if ( // If a strategy was already picked during the loop, we exit the loop + tradingEngine.current.strategy.index.value !== tradingEngine.current.strategy.index.config.initialValue + ) { continue } + + let strategy = tradingSystem.tradingStrategies[j] + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.triggerOn !== undefined) { + for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { + let situation = triggerStage.triggerOn.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(triggerStage.triggerOn.id) + tradingSystem.highlights.push(triggerStage.id) + + tradingStrategyModule.openStrategy(j, situation.name, strategy.name) + + /* Updating Episode Counters */ + tradingEngine.episode.episodeCounters.strategies.value++ + + /* Initialize this */ + tradingEngine.current.distanceToEvent.triggerOn.value = 1 + + announcementsModule.makeAnnoucements(triggerStage.triggerOn) + announcementsModule.makeAnnoucements(triggerStage) + + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.strategy === true) { + snapshotsModule.strategyEntry() + } + } + } + + changeStageStatus('Trigger Stage', 'Open') + } + } + } + } + } + } + } + + function checkTriggerOff() { + if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { + + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let triggerStage = strategy.triggerStage + + tradingSystem.evalConditions(strategy, 'Trigger Off Event') + + if (triggerStage !== undefined) { + if (triggerStage.triggerOff !== undefined) { + for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { + let situation = triggerStage.triggerOff.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(triggerStage.triggerOff.id) + tradingSystem.highlights.push(triggerStage.id) + + tradingEngine.current.distanceToEvent.triggerOff.value = 1 + announcementsModule.makeAnnoucements(triggerStage.triggerOff) + changeStageStatus('Trigger Stage', 'Closed') + tradingStrategyModule.closeStrategy('Trigger Off') + } + } + } + } + } + } + + function checkTakePosition() { + if ( + tradingEngine.current.strategyTriggerStage.status.value === 'Open' + ) { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let triggerStage = strategy.triggerStage + + tradingSystem.evalConditions(strategy, 'Take Position Event') + tradingSystem.evalFormulas(strategy, 'Take Position Event') + + if (triggerStage !== undefined) { + if (triggerStage.takePosition !== undefined) { + for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { + let situation = triggerStage.takePosition.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(triggerStage.takePosition.id) + tradingSystem.highlights.push(triggerStage.id) + + tradingPositionModule.openPosition(situation.name) + + announcementsModule.makeAnnoucements(triggerStage.takePosition) + announcementsModule.makeAnnoucements(strategy.openStage) + + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.position === true) { + snapshotsModule.positionEntry() + } + } + } + + changeStageStatus('Trigger Stage', 'Closed') + changeStageStatus('Open Stage', 'Opening') + changeStageStatus('Manage Stage', 'Opening') + } + } + } + } + } + } + } + + async function runOpenStage() { + /* Abort Open Stage Check */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') { + /* + if the Close stage is opened while the open stage is still open that means that + we need to stop placing orders, check what happened to the orders already placed, + and cancel all unfilled limit orders. We call this the Closing status of the Open Stage. + */ + changeStageStatus('Open Stage', 'Closing') + } + + /* Opening Status Procedure */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') { + + /* This procedure is intended to run only once */ + let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let executionNode = stageNode.openExecution + + /* Reset the Exchange Orders data structure to its initial value */ + tradingEngine.exchangeOrders.initialize(tradingEngine.exchangeOrders) + + tradingSystem.evalConditions(stageNode, 'Initial Definition') + tradingSystem.evalFormulas(stageNode, 'Initial Definition') + + /* Remember the balance we had before taking the position to later calculate profit or loss */ + tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + + /* Position size and rate */ + tradingPositionModule.initializeSizeAndRate() + + /* Check Execution at open stage node */ + tradingSystem.evalConditions(stageNode, 'Open Execution') + tradingSystem.evalFormulas(stageNode, 'Open Execution') + + await tradingExecutionModule.runExecution( + executionNode, + true, + false, + tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit + tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit + tradingEngine.current.strategyOpenStage + ) + + /* From here on, the state is officialy Open */ + changeStageStatus('Open Stage', 'Open') + return + } + + /* Open Status Procedure */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { + /* + While the Open Stage is Open, we do our regular stuff: place orders and check + what happened to the orders already placed. + */ + let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let executionNode = stageNode.openExecution + + /* Every Loop Cycle Activity */ + tradingSystem.evalConditions(stageNode, 'Open Execution') + tradingSystem.evalFormulas(stageNode, 'Open Execution') + + await tradingExecutionModule.runExecution( + executionNode, + false, + false, + tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit + tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit + tradingEngine.current.strategyOpenStage + ) + /* + The Open is finished when the fillSize + feesPaid reaches the Position Size. + This can happens at any time when we update the sizeFilled value when we see + at the exchange that orders were filled. + */ + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { + changeStageStatus('Open Stage', 'Closed', 'Position Size Filled') + } else { + /* Check the Close Stage Event */ + tradingSystem.evalConditions(stageNode, 'Close Stage Event') + if (checkStopStageEvent(stageNode) === true) { + changeStageStatus('Open Stage', 'Closing', 'Close Stage Event') + } + } + } + + /* Closing Status Procedure */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') { + /* + During the closing stage status, we do not place new orders, just check + if the ones placed were filled, and cancel the ones not filled. + */ + let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let executionNode = stageNode.openExecution + + /* Check if there are unfilled orders */ + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value < + tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { + /* There are unfilled orders, we will check if they were executed, and cancel the ones that were not. */ + + tradingSystem.evalConditions(stageNode, 'Open Execution') + tradingSystem.evalFormulas(stageNode, 'Open Execution') + + await tradingExecutionModule.runExecution( + executionNode, + false, + true, + tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit + tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit + tradingEngine.current.strategyOpenStage + ) + } + + /* + The Closing is finished when the fillSize + feesPaid reaches the ordersSize. + This can happens either because we update the sizeFilled value when we see + at the exchange that orders were filled, or we reduce the ordersSize when + we cancel not yet filled orders. + */ + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { + changeStageStatus('Open Stage', 'Closed') + } + } + } + + function runManageStage() { + /* Opening Status Procedure */ + if (tradingEngine.current.strategyManageStage.status.value === 'Opening') { + /* We jump the management at the loop cycle where the position is being taken. */ + changeStageStatus('Manage Stage', 'Open') + return + } + + /* Open Status Procedure */ + if (tradingEngine.current.strategyManageStage.status.value === 'Open') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let manageStage = strategy.manageStage + tradingSystem.evalConditions(manageStage, 'Manage Stage') + tradingSystem.evalFormulas(manageStage, 'Manage Stage') + + if ( + tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' || + tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage') { + let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + tradingSystem.evalConditions(stageNode, 'Initial Definition') + tradingSystem.evalFormulas(stageNode, 'Initial Definition') + } + + /* Stop Loss Management */ + checkStopPhasesEvents() + calculateStopLoss() + calculateStopLossPosition() + + /* Take Profit Management */ + checkTakeProfitPhaseEvents() + calculateTakeProfit() + calculateTakeProfitPosition() + + /* Checking if Stop or Take Profit were hit */ + checkStopLossOrTakeProfitWasHit() + } + + function calculateStopLoss() { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialStopLoss !== undefined) { + phase = openStage.initialDefinition.initialStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value] + } + } + } + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.managedStopLoss !== undefined) { + phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1] + } + } + + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = tradingEngine.current.position.stopLoss.value + tradingPositionModule.applyStopLossFormula(tradingSystem.formulas, phase.formula.id) + + if (tradingEngine.current.position.stopLoss.value !== previousValue) { + announcementsModule.makeAnnoucements(phase) + } + } + } + } + + function calculateTakeProfit() { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialTakeProfit !== undefined) { + phase = openStage.initialDefinition.initialTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value] + } + } + } + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.managedTakeProfit !== undefined) { + phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1] + } + } + + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = tradingEngine.current.position.takeProfit.value + tradingPositionModule.applyTakeProfitFormula(tradingSystem.formulas, phase.formula.id) + + if (tradingEngine.current.position.takeProfit.value !== previousValue) { + announcementsModule.makeAnnoucements(phase) + } + } + } + } + + function calculateStopLossPosition() { + /* + The position of the Stop Loss (above or below the price) is needed in order to know + later if the price hit the Stop Loss or not at every Simulation cycle. When we get + the first values of the simulation of the Stop Loss we check if it is above or below + the Position Rate, and we assign the values Above or Below to it. + */ + if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) { + if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.rate.value) { + tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above' + } else { + tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below' + } + } + } + + function calculateTakeProfitPosition() { + /* + The position of the Take Profit (above or below the price) is needed in order to know + later if the price hit the Take Profit or not at every Simulation cycle. When we get + the first values of the simulation of the Take Profits we check if it is above or below + the Position Rates, and we assign the values Above or Below to it. + */ + if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) { + if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.rate.value) { + tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above' + } else { + tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below' + } + } + } + + function checkStopPhasesEvents() { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let phaseIndex + let phase + let stopLoss + let stage + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { + stage = openStage + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialStopLoss !== undefined) { + parentNode = openStage.initialDefinition + phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value + stopLoss = openStage.initialDefinition.initialStopLoss + phase = stopLoss.phases[phaseIndex] + } + } + } + + if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { + stage = manageStage + if (manageStage.managedStopLoss !== undefined) { + parentNode = manageStage + phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1 + stopLoss = manageStage.managedStopLoss + phase = stopLoss.phases[phaseIndex] + } + } + + if (parentNode !== undefined) { + if (phase === undefined) { return } // trying to jump to a phase that does not exists. + + /* Check the next Phase Event. */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(nextPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(stopLoss.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage') + + announcementsModule.makeAnnoucements(nextPhaseEvent) + return // only one event can pass at the time + } + } + } + + /* Check the Move to Phase Events. */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + let situation = moveToPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(moveToPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(stopLoss.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < stopLoss.phases.length; q++) { + if (stopLoss.phases[q].id === moveToPhase.id) { + tradingPositionModule.updateStopLoss(q + 1, 'Manage Stage') + } + } + } else { + tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.']) + continue + } + + announcementsModule.makeAnnoucements(moveToPhaseEvent) + return // only one event can pass at the time + } + } + } + } + } + } + + function checkTakeProfitPhaseEvents() { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let phaseIndex + let phase + let takeProfit + let stage + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { + stage = openStage + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.initialTakeProfit !== undefined) { + parentNode = openStage.initialDefinition + phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value + takeProfit = openStage.initialDefinition.initialTakeProfit + phase = takeProfit.phases[phaseIndex] + } + } + } + + if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { + stage = manageStage + if (manageStage.managedTakeProfit !== undefined) { + parentNode = manageStage + phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1 + takeProfit = manageStage.managedTakeProfit + phase = takeProfit.phases[phaseIndex] + } + } + + if (parentNode !== undefined) { + if (phase === undefined) { return } // trying to jump to a phase that does not exists. + + /* Check the next Phase Event. */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(nextPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(takeProfit.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage') + + announcementsModule.makeAnnoucements(nextPhaseEvent) + return // only one event can pass at the time + } + } + } + + /* Check the Move to Phase Events. */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + let situation = moveToPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(moveToPhaseEvent.id) + tradingSystem.highlights.push(phase.id) + tradingSystem.highlights.push(takeProfit.id) + tradingSystem.highlights.push(parentNode.id) + tradingSystem.highlights.push(stage.id) + + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < takeProfit.phases.length; q++) { + if (takeProfit.phases[q].id === moveToPhase.id) { + tradingPositionModule.updateTakeProfit(q + 1, 'Manage Stage') + } + } + } else { + tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.']) + continue + } + + announcementsModule.makeAnnoucements(moveToPhaseEvent) + return // only one event can pass at the time + } + } + } + } + } + } + + function checkStopLossOrTakeProfitWasHit() { + { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + /* + Checking what happened since the last execution. We need to know if the Stop Loss + or our Take Profit were hit. + */ + + /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ + if ( + ( + tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Above' && + tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value + ) || + ( + tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Below' && + tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value + ) + ) { + logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.') + + tradingPositionModule.closingPosition('Stop Loss') + changeStageStatus('Close Stage', 'Opening') + changeStageStatus('Manage Stage', 'Closed') + announcementsModule.makeAnnoucements(strategy.closeStage) + return + } + + /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ + if ( + ( + tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Below' && + tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value + ) || + ( + tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Above' && + tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value + ) + ) { + logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.') + + tradingPositionModule.closingPosition('Take Profit') + changeStageStatus('Close Stage', 'Opening') + changeStageStatus('Manage Stage', 'Closed') + announcementsModule.makeAnnoucements(strategy.closeStage) + return + } + return + } + } + } + + async function runCloseStage() { + /* Opening Status Procedure */ + if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') { + /* + This will happen only once, as soon as the Take Profit or Stop was hit. + We wil not be placing orders at this time because we do not know + the total filled size of the Open Stage. By skiping execution now + we allow the open stage to get a final value for sizeFilled that we can use. + */ + changeStageStatus('Close Stage', 'Open') + return + } + + /* Open Status Procedure */ + if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { + /* + This will happen as long as the Close Stage is Open. + */ + let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + let executionNode = stageNode.closeExecution + + tradingSystem.evalConditions(stageNode, 'Close Execution') + tradingSystem.evalFormulas(stageNode, 'Close Execution') + + await tradingExecutionModule.runExecution( + executionNode, + false, + false, + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled, // Stage Size Limit + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled, // Stage Size Limit + tradingEngine.current.strategyCloseStage + ) + + /* + The Close Stage is finished when the fillSize + feesPaid reaches the Open Fill Size. + This can happens at any time when we update the sizeFilled value when we see + at the exchange that orders were filled. + */ + if ( + tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value * 0.999) { + changeStageStatus('Close Stage', 'Closed', 'Open Stage fillSize Filled') + } else { + /* Check the Close Stage Event */ + tradingSystem.evalConditions(stageNode, 'Close Stage Event') + if (checkStopStageEvent(stageNode) === true) { + changeStageStatus('Close Stage', 'Closing', 'Close Stage Event') + } + } + } + + /* Exit Position Validation */ + if ( + tradingEngine.current.strategyTriggerStage.status.value === 'Closed' && + tradingEngine.current.strategyOpenStage.status.value === 'Closed' && + tradingEngine.current.strategyManageStage.status.value === 'Closed' && + tradingEngine.current.strategyCloseStage.status.value === 'Closed' + ) { + + /* Exiting Everything now. */ + exitPositionAndStrategy() + } + + function exitPositionAndStrategy() { + + /* Position Statistics & Results */ + tradingPositionModule.updateStatistics() + tradingPositionModule.updateResults() + + updatingEpisodeAssets() + updatingEpisodeStatistics() + + /* Taking Position Snapshot */ + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.position === true) { + snapshotsModule.positionExit() + } + } + } + + /* Taking Strategy Snapshot */ + if (bot.SESSION.type === 'Backtesting Session') { + if (sessionParameters.snapshots !== undefined) { + if (sessionParameters.snapshots.config.strategy === true) { + snapshotsModule.strategyExit() + } + } + } + + /* Close the Position */ + tradingPositionModule.closePosition() + + /* Close the Strategy */ + tradingStrategyModule.closeStrategy('Position Closed') + + /* Distance to Events Updates */ + tradingEngine.current.distanceToEvent.closePosition.value = 1 + tradingEngine.current.distanceToEvent.triggerOff.value = 1 + + function updatingEpisodeAssets() { + + /* Updating Hits & Fails */ + if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeBaseAsset.hits.value++ + } else { + tradingEngine.episode.episodeBaseAsset.fails.value++ + } + if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeQuotedAsset.hits.value++ + } else { + tradingEngine.episode.episodeQuotedAsset.fails.value++ + } + + /* Updating Profit Loss */ + tradingEngine.episode.episodeBaseAsset.profitLoss.value = + tradingEngine.current.balance.baseAsset.value - + sessionParameters.sessionBaseAsset.config.initialBalance + + tradingEngine.episode.episodeQuotedAsset.profitLoss.value = + tradingEngine.current.balance.quotedAsset.value - + sessionParameters.sessionQuotedAsset.config.initialBalance + + tradingEngine.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.profitLoss.value, 10) + tradingEngine.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.episode.episodeBaseAsset.ROI.value = + (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeBaseAsset.profitLoss.value) / + sessionParameters.sessionBaseAsset.config.initialBalance - 1 + + tradingEngine.episode.episodeQuotedAsset.ROI.value = + (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeQuotedAsset.profitLoss.value) / + sessionParameters.sessionQuotedAsset.config.initialBalance - 1 + + tradingEngine.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.ROI.value, 10) + tradingEngine.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.ROI.value, 10) + + /* Updating Hit Ratio */ + tradingEngine.episode.episodeBaseAsset.hitRatio.value = + tradingEngine.episode.episodeBaseAsset.hits.value / + tradingEngine.episode.episodeCounters.positions.value + + tradingEngine.episode.episodeQuotedAsset.hitRatio.value = + tradingEngine.episode.episodeQuotedAsset.hits.value / + tradingEngine.episode.episodeCounters.positions.value + + tradingEngine.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.hitRatio.value, 10) + tradingEngine.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.hitRatio.value, 10) + + /* Updating Anualized Rate Of Return */ + tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = + tradingEngine.episode.episodeBaseAsset.ROI.value / + tradingEngine.episode.episodeStatistics.days.value * 365 + + tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = + tradingEngine.episode.episodeQuotedAsset.ROI.value / + tradingEngine.episode.episodeStatistics.days.value * 365 + + tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) + tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.episode.episodeBaseAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Hit' + } else { + tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Fail' + } + if (tradingEngine.episode.episodeQuotedAsset.profitLoss.value > 0) { + tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Hit' + } else { + tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Fail' + } + } + + function updatingEpisodeStatistics() { + + /* Updating Profit Loss */ + tradingEngine.episode.episodeStatistics.profitLoss.value = + tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + tradingEngine.episode.episodeQuotedAsset.profitLoss.value + + tradingEngine.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeStatistics.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.episode.episodeStatistics.ROI.value = + ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + + tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + + tradingEngine.episode.episodeQuotedAsset.profitLoss.value + ) / ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + ) - 1 + + tradingEngine.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.episode.episodeStatistics.ROI.value, 10) + + /* Updating Anualized Rate Of Return */ + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = + tradingEngine.episode.episodeStatistics.ROI.value / + tradingEngine.episode.episodeStatistics.days.value * 365 + + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.episode.episodeStatistics.profitLoss.value > 0) { + tradingEngine.episode.episodeStatistics.hitFail.value = 'Hit' + } else { + tradingEngine.episode.episodeStatistics.hitFail.value = 'Fail' + } + } + } + } + + function checkStopStageEvent(stage) { + + /* Check the Close Stage Event. */ + let stopStageEvent = stage.stopStageEvent + if (stopStageEvent !== undefined) { + for (let k = 0; k < stopStageEvent.situations.length; k++) { + let situation = stopStageEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(stopStageEvent.id) + tradingSystem.highlights.push(stage.id) + + announcementsModule.makeAnnoucements(stopStageEvent) + return true + } + } + } + } + + function changeStageStatus(stageName, newStatus, exitType) { + let stage + switch (stageName) { + case 'Trigger Stage': { + stage = tradingEngine.current.strategyTriggerStage + break + } + case 'Open Stage': { + stage = tradingEngine.current.strategyOpenStage + break + } + case 'Manage Stage': { + stage = tradingEngine.current.strategyManageStage + break + } + case 'Close Stage': { + stage = tradingEngine.current.strategyCloseStage + break + } + } + stage.status.value = newStatus + if (exitType !== undefined) { + stage.exitType.value = exitType + } + if (stage.status.value === 'Open') { + openStage(stage) + } + if (stage.status.value === 'Close') { + closeStage(stage) + } + + function openStage(stage) { + /* Recording the opening at the Trading Engine Data Structure */ + stage.begin.value = tradingEngine.current.candle.begin.value + stage.beginRate.value = tradingEngine.current.candle.close.value + stage.end.value = tradingEngine.current.candle.end.value + } + + function closeStage(stage) { + /* Recording the closing at the Trading Engine Data Structure */ + stage.end.value = tradingEngine.current.candle.end.value + stage.endRate.value = tradingEngine.current.candle.close.value + } + } + + function resetStage(stage) { + stage.initialize(stage) + } + + function updateEnds() { + if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { + tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.candle.end.value + } + if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { + tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.candle.end.value + } + if (tradingEngine.current.strategyManageStage.status.value === 'Open') { + tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.candle.end.value + } + if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { + tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.candle.end.value + } + } + + function updateStatus() { + if (tradingEngine.current.strategyTriggerStage.status.value === 'Closed') { + resetStage(tradingEngine.current.strategyTriggerStage) + } + if (tradingEngine.current.strategyOpenStage.status.value === 'Closed') { + resetStage(tradingEngine.current.strategyOpenStage) + } + if (tradingEngine.current.strategyManageStage.status.value === 'Closed') { + resetStage(tradingEngine.current.strategyManageStage) + } + if (tradingEngine.current.strategyCloseStage.status.value === 'Closed') { + resetStage(tradingEngine.current.strategyCloseStage) + } + } + + function updateCounters() { + + } + + function updateStatistics() { + + } +} \ No newline at end of file diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index 7edf964a4..0728c2b0c 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -7,7 +7,6 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng openStrategy: openStrategy, closeStrategy: closeStrategy, updateEnds: updateEnds, - updateStageStatus: updateStageStatus, updateStatus: updateStatus, updateCounters: updateCounters, resetStrategy: resetStrategy, @@ -63,33 +62,6 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng } } - function updateStageStatus(stage, status, exitType) { - switch (stage) { - case 'Trigger Stage': { - tradingEngine.current.strategyTriggerStage.status.value = status - break - } - case 'Open Stage': { - tradingEngine.current.strategyOpenStage.status.value = status - if (exitType !== undefined) { - tradingEngine.current.strategyOpenStage.exitType.value = exitType - } - break - } - case 'Manage Stage': { - tradingEngine.current.strategyManageStage.status.value = status - break - } - case 'Close Stage': { - tradingEngine.current.strategyCloseStage.status.value = status - if (exitType !== undefined) { - tradingEngine.current.strategyCloseStage.exitType.value = exitType - } - break - } - } - } - function updateStatus() { if (tradingEngine.current.strategy.status.value === 'Closed') { resetStrategy() diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 7e8679141..d371ae225 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -5,14 +5,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM */ const MODULE_NAME = 'Trading System' let thisObject = { - reset: reset, - mantainStrategies: mantainStrategies, - mantainPositions: mantainPositions, - mantainOrders: mantainOrders, - triggerStage: triggerStage, - openStage: openStage, - manageStage: manageStage, - closeStage: closeStage, + mantain: mantain, + run: run, + updateChart: updateChart, initialize: initialize, finalize: finalize } @@ -22,20 +17,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM let tradingEngine let sessionParameters - const TRADING_STRATEGY_MODULE = require('./TradingStrategy.js') - let tradingStrategyModule = TRADING_STRATEGY_MODULE.newTradingStrategy(bot, logger, tradingEngineModule) + const TRADING_STAGES_MODULE = require('./TradingStages.js') + let tradingStagesModule = TRADING_STAGES_MODULE.newTradingStages(bot, logger, tradingEngineModule) - const TRADING_POSITION_MODULE = require('./TradingPosition.js') - let tradingPositionModule = TRADING_POSITION_MODULE.newTradingPosition(bot, logger, tradingEngineModule) - - const TRADING_ENGINE_EXECUTION = require('./TradingExecution.js') - let tradingExecutionModule = TRADING_ENGINE_EXECUTION.newTradingExecution(bot, logger, tradingEngineModule) - - const ANNOUNCEMENTS_MODULE = require('./Announcements.js') - let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger) - - const SNAPSHOTS_MODULE = require('./Snapshots.js') - let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger) return thisObject @@ -47,11 +31,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingSystem.conditions = new Map() tradingSystem.formulas = new Map() - tradingStrategyModule.initialize() - tradingPositionModule.initialize() - announcementsModule.initialize() - snapshotsModule.initialize() - tradingExecutionModule.initialize() + tradingStagesModule.initialize() /* Adding Functions used elsewhere to Trading System Definition */ tradingSystem.checkConditions = function (situation, passed) { @@ -73,23 +53,31 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } return passed } + + tradingSystem.evalConditions = function (startingNode, descendentOfNodeType) { + evalNode(startingNode, 'Conditions', descendentOfNodeType) + } + + tradingSystem.evalFormulas = function (startingNode, descendentOfNodeType) { + evalNode(startingNode, 'Formulas', descendentOfNodeType) + } + + 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() { - tradingStrategyModule.finalize() - tradingStrategyModule = undefined - - tradingPositionModule.finalize() - tradingPositionModule = undefined - - announcementsModule.finalize() - announcementsModule = undefined - - snapshotsModule.finalize() - snapshotsModule = undefined - - tradingExecutionModule.finalize() - tradingExecutionModule = undefined + tradingStagesModule.finalize() + tradingStagesModule = undefined chart = undefined @@ -108,90 +96,39 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM sessionParameters = undefined } - function reset(pChart) { - /* This function helps reset data structures at every cycle of the simulation loop/=. */ - chart = pChart // We need chat to be a local object accessible from conditions and formulas. - snapshotsModule.reset(chart) + function mantain() { + reset() + tradingStagesModule.mantain() - tradingSystem.highlights = [] - tradingSystem.errors = [] - tradingSystem.values = [] - tradingSystem.status = [] - tradingSystem.progress = [] - tradingSystem.running = [] - tradingSystem.announcements = [] - } - - function mantainStrategies() { - tradingStrategyModule.updateStatus() - tradingStrategyModule.updateCounters() - tradingStrategyModule.updateEnds() - } - - function mantainPositions() { - tradingPositionModule.updateStatus() - tradingPositionModule.updateCounters() - tradingPositionModule.updateEnds() - } - - function mantainOrders() { - /* Reset All Exchange Orders that are on Closed status, to their initial value */ - if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return } - - let stageNode - let executionNode - - stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - executionNode = stageNode.openExecution - resetExecution(executionNode, tradingEngine.current.strategyOpenStage.status.value) - - stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage - executionNode = stageNode.closeExecution - resetExecution(executionNode, tradingEngine.current.strategyCloseStage.status.value) - - function resetExecution(executionNode, stageStatus) { - - resetExecutionAlgorithms(executionNode) - - function resetExecutionAlgorithms(executionNode) { - for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { - let executionAlgorithm = executionNode.executionAlgorithms[i] - resetOrders(executionAlgorithm.marketBuyOrders) - resetOrders(executionAlgorithm.marketSellOrders) - resetOrders(executionAlgorithm.limitBuyOrders) - resetOrders(executionAlgorithm.limitSellOrders) - } - } - - function resetOrders(orders) { - for (let i = 0; i < orders.length; i++) { - let tradingSystemOrder = orders[i] - if (tradingSystemOrder.referenceParent === undefined) { continue } - let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) - if (tradingEngineOrder.status === undefined) { continue } - if (tradingEngineOrder.status.value === 'Closed') { - /* We reset the order data structure inside the Trading Engine to its initial value */ - tradingEngineOrder.initialize(tradingEngineOrder) - if (tradingSystemOrder.config.spawnMultipleOrders !== true) { - /* - We close the lock so as to prevent this data structure to be used again during this same stage execution. - */ - if (stageStatus === 'Open') { - tradingEngineOrder.lock.value = 'Closed' - } - } - } - } - } + function reset() { + /* This function helps reset data structures at every cycle of the simulation loop/=. */ + tradingSystem.highlights = [] + tradingSystem.errors = [] + tradingSystem.values = [] + tradingSystem.status = [] + tradingSystem.progress = [] + tradingSystem.running = [] + tradingSystem.announcements = [] } } - function evalConditions(startingNode, descendentOfNodeType) { - evalNode(startingNode, 'Conditions', descendentOfNodeType) + function updateChart(pChart) { + chart = pChart // We need chat to be a local object accessible from conditions and formulas. + tradingStagesModule.updateChart(pChart) } - function evalFormulas(startingNode, descendentOfNodeType) { - evalNode(startingNode, 'Formulas', descendentOfNodeType) + async function run() { + /* Run the Trigger Stage */ + tradingStagesModule.runTriggerStage() + + /* Run the Open Stage */ + await tradingStagesModule.runOpenStage() + + /* Run the Manage Stage */ + tradingStagesModule.runManageStage() + + /* Run the Close Stage */ + await tradingStagesModule.runCloseStage() } function evalNode(node, evaluating, descendentOfNodeType, isDescendent) { @@ -347,952 +284,5 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM logger.write(MODULE_NAME, '[INFO] evalFormula -> value = ' + value) logger.write(MODULE_NAME, '[INFO] evalFormula -> error = ' + error) } - - function triggerStage() { - /* - We check if we will be triggering on, off or taking position. - */ - checkTriggerOn() - checkTriggerOff() - checkTakePosition() - - function checkTriggerOn() { - if ( - tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue - ) { - /* - To pick a new strategy we will check that any of the situations of the trigger on is true. - Once we enter into one strategy, we will ignore market conditions for others. However there is also - a strategy trigger off which can be hit before taking a position. If hit, we would - be outside a strategy again and looking for the conditions to enter all over again. - */ - evalConditions(tradingSystem, 'Trigger On Event') - - for (let j = 0; j < tradingSystem.tradingStrategies.length; j++) { - if ( // If a strategy was already picked during the loop, we exit the loop - tradingEngine.current.strategy.index.value !== tradingEngine.current.strategy.index.config.initialValue - ) { continue } - - let strategy = tradingSystem.tradingStrategies[j] - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - if (triggerStage.triggerOn !== undefined) { - for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { - let situation = triggerStage.triggerOn.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(triggerStage.triggerOn.id) - tradingSystem.highlights.push(triggerStage.id) - - tradingStrategyModule.openStrategy(j, situation.name, strategy.name) - - /* Updating Episode Counters */ - tradingEngine.episode.episodeCounters.strategies.value++ - - /* Initialize this */ - tradingEngine.current.distanceToEvent.triggerOn.value = 1 - - announcementsModule.makeAnnoucements(triggerStage.triggerOn) - announcementsModule.makeAnnoucements(triggerStage) - - if (bot.SESSION.type === 'Backtesting Session') { - if (sessionParameters.snapshots !== undefined) { - if (sessionParameters.snapshots.config.strategy === true) { - snapshotsModule.strategyEntry() - } - } - } - - tradingStrategyModule.updateStageStatus('Trigger Stage', 'Open') - } - } - } - } - } - } - } - - function checkTriggerOff() { - if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { - - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let triggerStage = strategy.triggerStage - - evalConditions(strategy, 'Trigger Off Event') - - if (triggerStage !== undefined) { - if (triggerStage.triggerOff !== undefined) { - for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { - let situation = triggerStage.triggerOff.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(triggerStage.triggerOff.id) - tradingSystem.highlights.push(triggerStage.id) - - tradingEngine.current.distanceToEvent.triggerOff.value = 1 - announcementsModule.makeAnnoucements(triggerStage.triggerOff) - tradingStrategyModule.updateStageStatus('Trigger Stage', 'Closed') - tradingStrategyModule.closeStrategy('Trigger Off') - } - } - } - } - } - } - - function checkTakePosition() { - if ( - tradingEngine.current.strategyTriggerStage.status.value === 'Open' - ) { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let triggerStage = strategy.triggerStage - - evalConditions(strategy, 'Take Position Event') - evalFormulas(strategy, 'Take Position Event') - - if (triggerStage !== undefined) { - if (triggerStage.takePosition !== undefined) { - for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { - let situation = triggerStage.takePosition.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(triggerStage.takePosition.id) - tradingSystem.highlights.push(triggerStage.id) - - tradingPositionModule.openPosition(situation.name) - - announcementsModule.makeAnnoucements(triggerStage.takePosition) - announcementsModule.makeAnnoucements(strategy.openStage) - - if (bot.SESSION.type === 'Backtesting Session') { - if (sessionParameters.snapshots !== undefined) { - if (sessionParameters.snapshots.config.position === true) { - snapshotsModule.positionEntry() - } - } - } - - tradingStrategyModule.updateStageStatus('Trigger Stage', 'Closed') - tradingStrategyModule.updateStageStatus('Open Stage', 'Opening') - tradingStrategyModule.updateStageStatus('Manage Stage', 'Opening') - } - } - } - } - } - } - } - - async function openStage() { - /* Abort Open Stage Check */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') { - /* - if the Close stage is opened while the open stage is still open that means that - we need to stop placing orders, check what happened to the orders already placed, - and cancel all unfilled limit orders. We call this the Closing status of the Open Stage. - */ - tradingStrategyModule.updateStageStatus('Open Stage', 'Closing') - } - - /* Opening Status Procedure */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') { - - /* This procedure is intended to run only once */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = stageNode.openExecution - - /* Reset the Exchange Orders data structure to its initial value */ - tradingEngine.exchangeOrders.initialize(tradingEngine.exchangeOrders) - - evalConditions(stageNode, 'Initial Definition') - evalFormulas(stageNode, 'Initial Definition') - - /* Remember the balance we had before taking the position to later calculate profit or loss */ - tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - - /* Position size and rate */ - tradingPositionModule.initializeSizeAndRate() - - /* Check Execution at open stage node */ - evalConditions(stageNode, 'Open Execution') - evalFormulas(stageNode, 'Open Execution') - - await tradingExecutionModule.checkExecution( - executionNode, - true, - false, - tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit - tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit - tradingEngine.current.strategyOpenStage - ) - - /* From here on, the state is officialy Open */ - tradingStrategyModule.updateStageStatus('Open Stage', 'Open') - return - } - - /* Open Status Procedure */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { - /* - While the Open Stage is Open, we do our regular stuff: place orders and check - what happened to the orders already placed. - */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = stageNode.openExecution - - /* Every Loop Cycle Activity */ - evalConditions(stageNode, 'Open Execution') - evalFormulas(stageNode, 'Open Execution') - - await tradingExecutionModule.checkExecution( - executionNode, - false, - false, - tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit - tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit - tradingEngine.current.strategyOpenStage - ) - /* - The Open is finished when the fillSize + feesPaid reaches the Position Size. - This can happens at any time when we update the sizeFilled value when we see - at the exchange that orders were filled. - */ - if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { - tradingStrategyModule.updateStageStatus('Open Stage', 'Closed', 'Position Size Filled') - } else { - /* Check the Close Stage Event */ - evalConditions(stageNode, 'Close Stage Event') - if (checkStopStageEvent(stageNode) === true) { - tradingStrategyModule.updateStageStatus('Open Stage', 'Closing', 'Close Stage Event') - } - } - } - - /* Closing Status Procedure */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') { - /* - During the closing stage status, we do not place new orders, just check - if the ones placed were filled, and cancel the ones not filled. - */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = stageNode.openExecution - - /* Check if there are unfilled orders */ - if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value < - tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { - /* There are unfilled orders, we will check if they were executed, and cancel the ones that were not. */ - - evalConditions(stageNode, 'Open Execution') - evalFormulas(stageNode, 'Open Execution') - - await tradingExecutionModule.checkExecution( - executionNode, - false, - true, - tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit - tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit - tradingEngine.current.strategyOpenStage - ) - } - - /* - The Closing is finished when the fillSize + feesPaid reaches the ordersSize. - This can happens either because we update the sizeFilled value when we see - at the exchange that orders were filled, or we reduce the ordersSize when - we cancel not yet filled orders. - */ - if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { - tradingStrategyModule.updateStageStatus('Open Stage', 'Closed') - } - } - } - - function manageStage() { - /* Opening Status Procedure */ - if (tradingEngine.current.strategyManageStage.status.value === 'Opening') { - /* We jump the management at the loop cycle where the position is being taken. */ - tradingStrategyModule.updateStageStatus('Manage Stage', 'Open') - return - } - - /* Open Status Procedure */ - if (tradingEngine.current.strategyManageStage.status.value === 'Open') { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let manageStage = strategy.manageStage - evalConditions(manageStage, 'Manage Stage') - evalFormulas(manageStage, 'Manage Stage') - - if ( - tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' || - tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage') { - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - evalConditions(stageNode, 'Initial Definition') - evalFormulas(stageNode, 'Initial Definition') - } - - /* Stop Loss Management */ - checkStopPhasesEvents() - calculateStopLoss() - calculateStopLossPosition() - - /* Take Profit Management */ - checkTakeProfitPhaseEvents() - calculateTakeProfit() - calculateTakeProfitPosition() - - /* Checking if Stop or Take Profit were hit */ - checkStopLossOrTakeProfitWasHit() - } - - function calculateStopLossPosition() { - /* - The position of the Stop Loss (above or below the price) is needed in order to know - later if the price hit the Stop Loss or not at every Simulation cycle. When we get - the first values of the simulation of the Stop Loss we check if it is above or below - the Position Rate, and we assign the values Above or Below to it. - */ - if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) { - if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.rate.value) { - tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above' - } else { - tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below' - } - } - } - - function calculateTakeProfitPosition() { - /* - The position of the Take Profit (above or below the price) is needed in order to know - later if the price hit the Take Profit or not at every Simulation cycle. When we get - the first values of the simulation of the Take Profits we check if it is above or below - the Position Rates, and we assign the values Above or Below to it. - */ - if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) { - if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.rate.value) { - tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above' - } else { - tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below' - } - } - } - - function checkStopPhasesEvents() { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let phaseIndex - let phase - let stopLoss - let stage - - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { - stage = openStage - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialStopLoss !== undefined) { - parentNode = openStage.initialDefinition - phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - stopLoss = openStage.initialDefinition.initialStopLoss - phase = stopLoss.phases[phaseIndex] - } - } - } - - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { - stage = manageStage - if (manageStage.managedStopLoss !== undefined) { - parentNode = manageStage - phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1 - stopLoss = manageStage.managedStopLoss - phase = stopLoss.phases[phaseIndex] - } - } - - if (parentNode !== undefined) { - if (phase === undefined) { return } // trying to jump to a phase that does not exists. - - /* Check the next Phase Event. */ - let nextPhaseEvent = phase.nextPhaseEvent - if (nextPhaseEvent !== undefined) { - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - let situation = nextPhaseEvent.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(nextPhaseEvent.id) - tradingSystem.highlights.push(phase.id) - tradingSystem.highlights.push(stopLoss.id) - tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) - - tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage') - - announcementsModule.makeAnnoucements(nextPhaseEvent) - return // only one event can pass at the time - } - } - } - - /* Check the Move to Phase Events. */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n] - if (moveToPhaseEvent !== undefined) { - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - let situation = moveToPhaseEvent.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(moveToPhaseEvent.id) - tradingSystem.highlights.push(phase.id) - tradingSystem.highlights.push(stopLoss.id) - tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) - - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < stopLoss.phases.length; q++) { - if (stopLoss.phases[q].id === moveToPhase.id) { - tradingPositionModule.updateStopLoss(q + 1, 'Manage Stage') - } - } - } else { - tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.']) - continue - } - - announcementsModule.makeAnnoucements(moveToPhaseEvent) - return // only one event can pass at the time - } - } - } - } - } - } - - function checkTakeProfitPhaseEvents() { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let phaseIndex - let phase - let takeProfit - let stage - - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { - stage = openStage - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialTakeProfit !== undefined) { - parentNode = openStage.initialDefinition - phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - takeProfit = openStage.initialDefinition.initialTakeProfit - phase = takeProfit.phases[phaseIndex] - } - } - } - - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { - stage = manageStage - if (manageStage.managedTakeProfit !== undefined) { - parentNode = manageStage - phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1 - takeProfit = manageStage.managedTakeProfit - phase = takeProfit.phases[phaseIndex] - } - } - - if (parentNode !== undefined) { - if (phase === undefined) { return } // trying to jump to a phase that does not exists. - - /* Check the next Phase Event. */ - let nextPhaseEvent = phase.nextPhaseEvent - if (nextPhaseEvent !== undefined) { - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - let situation = nextPhaseEvent.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(nextPhaseEvent.id) - tradingSystem.highlights.push(phase.id) - tradingSystem.highlights.push(takeProfit.id) - tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) - - tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage') - - announcementsModule.makeAnnoucements(nextPhaseEvent) - return // only one event can pass at the time - } - } - } - - /* Check the Move to Phase Events. */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n] - if (moveToPhaseEvent !== undefined) { - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - let situation = moveToPhaseEvent.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(moveToPhaseEvent.id) - tradingSystem.highlights.push(phase.id) - tradingSystem.highlights.push(takeProfit.id) - tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) - - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < takeProfit.phases.length; q++) { - if (takeProfit.phases[q].id === moveToPhase.id) { - tradingPositionModule.updateTakeProfit(q + 1, 'Manage Stage') - } - } - } else { - tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.']) - continue - } - - announcementsModule.makeAnnoucements(moveToPhaseEvent) - return // only one event can pass at the time - } - } - } - } - } - } - - function checkStopLossOrTakeProfitWasHit() { - { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - /* - Checking what happened since the last execution. We need to know if the Stop Loss - or our Take Profit were hit. - */ - - /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ - if ( - ( - tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Above' && - tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value - ) || - ( - tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Below' && - tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value - ) - ) { - logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.') - - tradingPositionModule.closingPosition('Stop Loss') - tradingStrategyModule.updateStageStatus('Close Stage', 'Opening') - tradingStrategyModule.updateStageStatus('Manage Stage', 'Closed') - announcementsModule.makeAnnoucements(strategy.closeStage) - return - } - - /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ - if ( - ( - tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Below' && - tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value - ) || - ( - tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Above' && - tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value - ) - ) { - logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.') - - tradingPositionModule.closingPosition('Take Profit') - tradingStrategyModule.updateStageStatus('Close Stage', 'Opening') - tradingStrategyModule.updateStageStatus('Manage Stage', 'Closed') - announcementsModule.makeAnnoucements(strategy.closeStage) - return - } - return - } - } - } - - async function closeStage() { - /* Opening Status Procedure */ - if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') { - /* - This will happen only once, as soon as the Take Profit or Stop was hit. - We wil not be placing orders at this time because we do not know - the total filled size of the Open Stage. By skiping execution now - we allow the open stage to get a final value for sizeFilled that we can use. - */ - tradingStrategyModule.updateStageStatus('Close Stage', 'Open') - return - } - - /* Open Status Procedure */ - if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { - /* - This will happen as long as the Close Stage is Open. - */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage - let executionNode = stageNode.closeExecution - - evalConditions(stageNode, 'Close Execution') - evalFormulas(stageNode, 'Close Execution') - - await tradingExecutionModule.checkExecution( - executionNode, - false, - false, - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled, // Stage Size Limit - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled, // Stage Size Limit - tradingEngine.current.strategyCloseStage - ) - - /* - The Close Stage is finished when the fillSize + feesPaid reaches the Open Fill Size. - This can happens at any time when we update the sizeFilled value when we see - at the exchange that orders were filled. - */ - if ( - tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value * 0.999) { - tradingStrategyModule.updateStageStatus('Close Stage', 'Closed', 'Open Stage fillSize Filled') - } else { - /* Check the Close Stage Event */ - evalConditions(stageNode, 'Close Stage Event') - if (checkStopStageEvent(stageNode) === true) { - tradingStrategyModule.updateStageStatus('Close Stage', 'Closing', 'Close Stage Event') - } - } - } - - /* Exit Position Validation */ - if ( - tradingEngine.current.strategyTriggerStage.status.value === 'Closed' && - tradingEngine.current.strategyOpenStage.status.value === 'Closed' && - tradingEngine.current.strategyManageStage.status.value === 'Closed' && - tradingEngine.current.strategyCloseStage.status.value === 'Closed' - ) { - - /* Exiting Everything now. */ - exitPositionAndStrategy() - } - - function exitPositionAndStrategy() { - - /* Position Statistics & Results */ - tradingPositionModule.updateStatistics() - tradingPositionModule.updateResults() - - updatingEpisodeAssets() - updatingEpisodeStatistics() - - /* Taking Position Snapshot */ - if (bot.SESSION.type === 'Backtesting Session') { - if (sessionParameters.snapshots !== undefined) { - if (sessionParameters.snapshots.config.position === true) { - snapshotsModule.positionExit() - } - } - } - - /* Taking Strategy Snapshot */ - if (bot.SESSION.type === 'Backtesting Session') { - if (sessionParameters.snapshots !== undefined) { - if (sessionParameters.snapshots.config.strategy === true) { - snapshotsModule.strategyExit() - } - } - } - - /* Close the Position */ - tradingPositionModule.closePosition() - - /* Close the Strategy */ - tradingStrategyModule.closeStrategy('Position Closed') - - /* Distance to Events Updates */ - tradingEngine.current.distanceToEvent.closePosition.value = 1 - tradingEngine.current.distanceToEvent.triggerOff.value = 1 - - function updatingEpisodeAssets() { - - /* Updating Hits & Fails */ - if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeBaseAsset.hits.value++ - } else { - tradingEngine.episode.episodeBaseAsset.fails.value++ - } - if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeQuotedAsset.hits.value++ - } else { - tradingEngine.episode.episodeQuotedAsset.fails.value++ - } - - /* Updating Profit Loss */ - tradingEngine.episode.episodeBaseAsset.profitLoss.value = - tradingEngine.current.balance.baseAsset.value - - sessionParameters.sessionBaseAsset.config.initialBalance - - tradingEngine.episode.episodeQuotedAsset.profitLoss.value = - tradingEngine.current.balance.quotedAsset.value - - sessionParameters.sessionQuotedAsset.config.initialBalance - - tradingEngine.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.profitLoss.value, 10) - tradingEngine.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.profitLoss.value, 10) - - /* Updating ROI */ - tradingEngine.episode.episodeBaseAsset.ROI.value = - (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeBaseAsset.profitLoss.value) / - sessionParameters.sessionBaseAsset.config.initialBalance - 1 - - tradingEngine.episode.episodeQuotedAsset.ROI.value = - (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeQuotedAsset.profitLoss.value) / - sessionParameters.sessionQuotedAsset.config.initialBalance - 1 - - tradingEngine.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.ROI.value, 10) - tradingEngine.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.ROI.value, 10) - - /* Updating Hit Ratio */ - tradingEngine.episode.episodeBaseAsset.hitRatio.value = - tradingEngine.episode.episodeBaseAsset.hits.value / - tradingEngine.episode.episodeCounters.positions.value - - tradingEngine.episode.episodeQuotedAsset.hitRatio.value = - tradingEngine.episode.episodeQuotedAsset.hits.value / - tradingEngine.episode.episodeCounters.positions.value - - tradingEngine.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.hitRatio.value, 10) - tradingEngine.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.hitRatio.value, 10) - - /* Updating Anualized Rate Of Return */ - tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = - tradingEngine.episode.episodeBaseAsset.ROI.value / - tradingEngine.episode.episodeStatistics.days.value * 365 - - tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = - tradingEngine.episode.episodeQuotedAsset.ROI.value / - tradingEngine.episode.episodeStatistics.days.value * 365 - - tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) - tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) - - /* Updating Hit or Fail */ - if (tradingEngine.episode.episodeBaseAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Hit' - } else { - tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Fail' - } - if (tradingEngine.episode.episodeQuotedAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Hit' - } else { - tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Fail' - } - } - - function updatingEpisodeStatistics() { - - /* Updating Profit Loss */ - tradingEngine.episode.episodeStatistics.profitLoss.value = - tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + - tradingEngine.episode.episodeQuotedAsset.profitLoss.value - - tradingEngine.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeStatistics.profitLoss.value, 10) - - /* Updating ROI */ - tradingEngine.episode.episodeStatistics.ROI.value = - ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + - tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + - sessionParameters.sessionQuotedAsset.config.initialBalance + - tradingEngine.episode.episodeQuotedAsset.profitLoss.value - ) / ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + - sessionParameters.sessionQuotedAsset.config.initialBalance - ) - 1 - - tradingEngine.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.episode.episodeStatistics.ROI.value, 10) - - /* Updating Anualized Rate Of Return */ - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = - tradingEngine.episode.episodeStatistics.ROI.value / - tradingEngine.episode.episodeStatistics.days.value * 365 - - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value, 10) - - /* Updating Hit or Fail */ - if (tradingEngine.episode.episodeStatistics.profitLoss.value > 0) { - tradingEngine.episode.episodeStatistics.hitFail.value = 'Hit' - } else { - tradingEngine.episode.episodeStatistics.hitFail.value = 'Fail' - } - } - } - } - - function calculateStopLoss() { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialStopLoss !== undefined) { - phase = openStage.initialDefinition.initialStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value] - } - } - } - - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.managedStopLoss !== undefined) { - phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1] - } - } - - if (phase !== undefined) { - if (phase.formula !== undefined) { - let previousValue = tradingEngine.current.position.stopLoss.value - tradingPositionModule.applyStopLossFormula(tradingSystem.formulas, phase.formula.id) - - if (tradingEngine.current.position.stopLoss.value !== previousValue) { - announcementsModule.makeAnnoucements(phase) - } - } - } - } - - function calculateTakeProfit() { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialTakeProfit !== undefined) { - phase = openStage.initialDefinition.initialTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value] - } - } - } - - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.managedTakeProfit !== undefined) { - phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1] - } - } - - if (phase !== undefined) { - if (phase.formula !== undefined) { - let previousValue = tradingEngine.current.position.takeProfit.value - tradingPositionModule.applyTakeProfitFormula(tradingSystem.formulas, phase.formula.id) - - if (tradingEngine.current.position.takeProfit.value !== previousValue) { - announcementsModule.makeAnnoucements(phase) - } - } - } - } - - function checkStopStageEvent(stage) { - - /* Check the Close Stage Event. */ - let stopStageEvent = stage.stopStageEvent - if (stopStageEvent !== undefined) { - for (let k = 0; k < stopStageEvent.situations.length; k++) { - let situation = stopStageEvent.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(stopStageEvent.id) - tradingSystem.highlights.push(stage.id) - - announcementsModule.makeAnnoucements(stopStageEvent) - return true - } - } - } - } - - 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 - } - } } From 1fa7dc756ed7fd7f60e53452a0cdec1b24e1a0c0 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 4 Aug 2020 15:41:08 +0200 Subject: [PATCH 021/112] Stage Target size --- LowFrequencyTrading/TradingExecution.js | 105 ++++++++---------------- LowFrequencyTrading/TradingPosition.js | 4 +- LowFrequencyTrading/TradingStages.js | 67 +++++++++++---- 3 files changed, 87 insertions(+), 89 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index a05bab963..33e0706d8 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -45,47 +45,23 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE async function runExecution( executionNode, - stageIsOpening, stageIsClosing, - stageSizeLimitInBaseAsset, - stageSizeLimitInQuotedAsset, tradingEngineStage ) { try { /* Trading Engine Stage Validations */ - if (tradingEngineStage.stageBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.stageBaseAsset === undefined', tradingEngineStage) } - if (tradingEngineStage.stageBaseAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.size === undefined', tradingEngineStage) } - if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.sizeFilled === undefined', tradingEngineStage) } - if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineStage.feesPaid === undefined', tradingEngineStage) } - if (tradingEngineStage.stageQuotedAsset === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.size === undefined', tradingEngineStage) } - if (tradingEngineStage.stageQuotedAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.size === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset.targetSize === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.targetSize === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset.sizePlaced === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.sizePlaced === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.sizeFilled === undefined', tradingEngineStage) } + if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.feesPaid === undefined', tradingEngineStage) } + if (tradingEngineStage.stageQuotedAsset === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset === undefined', tradingEngineStage) } + if (tradingEngineStage.stageQuotedAsset.targetSize === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.targetSize === undefined', tradingEngineStage) } + if (tradingEngineStage.stageQuotedAsset.sizePlaced === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.sizePlaced === undefined', tradingEngineStage) } if (tradingEngineStage.stageQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.sizeFilled === undefined', tradingEngineStage) } if (tradingEngineStage.stageQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.feesPaid === undefined', tradingEngineStage) } - /* - The user choice of asset to define the size of the position, - defines the asset that will track the information kept about the - stage at the trading engine data structure. We set this asset - to be used here in this way: - */ - let tradingEngineStageAsset - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - tradingEngineStageAsset = tradingEngineStage.stageBaseAsset - } else { - tradingEngineStageAsset = tradingEngineStage.stageQuotedAsset - } - /* - The asset type of the position size chosen by the user, also defines - the stage limit to be on the same asset type. - */ - let stageSizeLimit - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - stageSizeLimit = stageSizeLimitInBaseAsset - } else { - stageSizeLimit = stageSizeLimitInQuotedAsset - } - await checkExecutionAlgorithms(executionNode) async function checkExecutionAlgorithms(executionNode) { @@ -132,18 +108,6 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined', tradingEngineOrder) } if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.feesPaid === undefined', tradingEngineOrder) } - /* - The asset type of the position size chosen by the user, defines - the asset at the order data strutcture where information is going to - be stored. - */ - let traingEngineOrderAsset - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - traingEngineOrderAsset = tradingEngineOrder.orderBaseAsset - } else { - traingEngineOrderAsset = tradingEngineOrder.orderQuotedAsset - } - switch (tradingEngineOrder.status.value) { case 'Not Open': { { @@ -164,7 +128,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (situationName !== undefined) { /* Open a new order */ - await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, traingEngineOrderAsset, situationName) + await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) } } break @@ -205,23 +169,23 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } } - async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, traingEngineOrderAsset, situationName) { + async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { calculateOrderRate() calculateOrderSize() /* Check Size: We are not going to create Orders which size is equal or less to zero. */ - if (traingEngineOrderAsset.size.value <= 0) { return } + if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) { return } /* Place Order at the Exchange, if needed. */ let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) if (result !== true) { return } - /* Update Stage Size */ - tradingEngineStage.stageBaseAsset.size.value = tradingEngineStage.stageBaseAsset.size.value + tradingEngineOrder.orderBaseAsset.size.value - tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) - tradingEngineStage.stageQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.size.value + tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) + /* Update Stage Placed Size */ + tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10) + tradingEngineStage.stageQuotedAsset.sizePlaced.value = tradingEngineStage.stageQuotedAsset.sizePlaced.value + tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) /* Updating Episode Counters */ tradingEngine.episode.episodeCounters.orders.value++ @@ -282,27 +246,30 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Validate that this config exists */ if (tradingSystemOrder.config.positionSizePercentage === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.positionSizePercentage === undefined', executionAlgorithm) } - traingEngineOrderAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 - traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + /* Position was defined in Base Asset */ + tradingEngineOrder.orderBaseAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value + } else { + /* Position was defined in Quoted Asset */ + tradingEngineOrder.orderQuotedAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 + tradingEngineOrder.orderBaseAsset.size.value = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value + } + tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) + tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) /* Check against the Stage Size Limit */ if ( - tradingEngineStageAsset.size.value + traingEngineOrderAsset.size.value > stageSizeLimit.value) { - /* We reduce the size to the remaining size of the position. */ - traingEngineOrderAsset.size.value = stageSizeLimit.value - tradingEngineStageAsset.size.value - traingEngineOrderAsset.size.value = global.PRECISE(traingEngineOrderAsset.size.value, 10) + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value > + tradingEngineStage.stageBaseAsset.targetSize.value + ) { + tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value } - - /* We are going to ESTIMATE the size in the oposite asset type, because we will need it later */ - switch (traingEngineOrderAsset.type) { - case 'Order Base Asset': { - tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value - tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) - } - case 'Order Quoted Asset': { - tradingEngineOrder.orderBaseAsset.size.value = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value - tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) - } + if ( + tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value > + tradingEngineStage.stageQuotedAsset.targetSize.value + ) { + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value } } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 4bfea4f61..464087bb7 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -11,7 +11,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng applyTakeProfitFormula: applyTakeProfitFormula, updateStopLoss: updateStopLoss, updateTakeProfit: updateTakeProfit, - initializeSizeAndRate: initializeSizeAndRate, + initializePositionSizeAndRate: initializePositionSizeAndRate, updateEnds: updateEnds, updateStatus: updateStatus, updateCounters: updateCounters, @@ -100,7 +100,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.takeProfit.takeProfitStage.value = stage } - function initializeSizeAndRate() { + function initializePositionSizeAndRate() { setPositionSize() setPositionRate() diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 1ecc9f5fc..571ff3b3a 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -344,7 +344,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value /* Position size and rate */ - tradingPositionModule.initializeSizeAndRate() + tradingPositionModule.initializePositionSizeAndRate() + initializeOpenStageSizeTarget() /* Check Execution at open stage node */ tradingSystem.evalConditions(stageNode, 'Open Execution') @@ -352,10 +353,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, - true, false, - tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit - tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit tradingEngine.current.strategyOpenStage ) @@ -380,9 +378,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, false, - false, - tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit - tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit tradingEngine.current.strategyOpenStage ) /* @@ -395,6 +390,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { changeStageStatus('Open Stage', 'Closed', 'Position Size Filled') + initializeCloseStageSizeTarget() } else { /* Check the Close Stage Event */ tradingSystem.evalConditions(stageNode, 'Close Stage Event') @@ -425,10 +421,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, - false, true, - tradingEngine.current.position.positionBaseAsset.size, // Stage Size Limit - tradingEngine.current.position.positionQuotedAsset.size, // Stage Size Limit tradingEngine.current.strategyOpenStage ) } @@ -444,8 +437,35 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { changeStageStatus('Open Stage', 'Closed') + initializeCloseStageSizeTarget() } } + + function initializeOpenStageSizeTarget() { + /* + Here we will transform the position size into targets for each asset of the stage. + */ + if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.size.value + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = + tradingEngine.current.position.positionBaseAsset.size.value * tradingEngine.current.position.rate.value + } else { + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.size.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = + tradingEngine.current.position.positionQuotedAsset.size.value / tradingEngine.current.position.rate.value + } + + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value, 10) + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value, 10) + } + + function initializeCloseStageSizeTarget() { + /* + Here we will move the size filled of the open stage to the size target of the close stage. + */ + tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value = tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + } } function runManageStage() { @@ -874,9 +894,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, false, - false, - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled, // Stage Size Limit - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled, // Stage Size Limit tradingEngine.current.strategyCloseStage ) @@ -901,10 +918,24 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM /* Exit Position Validation */ if ( - tradingEngine.current.strategyTriggerStage.status.value === 'Closed' && - tradingEngine.current.strategyOpenStage.status.value === 'Closed' && - tradingEngine.current.strategyManageStage.status.value === 'Closed' && - tradingEngine.current.strategyCloseStage.status.value === 'Closed' + ( + tradingEngine.current.strategyTriggerStage.status.value === 'Closed' || + tradingEngine.current.strategyTriggerStage.status.value === tradingEngine.current.strategyTriggerStage.status.config.initialValue + ) + && + ( + tradingEngine.current.strategyOpenStage.status.value === 'Closed' || + tradingEngine.current.strategyOpenStage.status.value === tradingEngine.current.strategyOpenStage.status.config.initialValue + ) + && + ( + tradingEngine.current.strategyManageStage.status.value === 'Closed' || + tradingEngine.current.strategyManageStage.status.value === tradingEngine.current.strategyManageStage.status.config.initialValue + ) + && + ( + tradingEngine.current.strategyCloseStage.status.value === 'Closed' + ) ) { /* Exiting Everything now. */ @@ -1117,7 +1148,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if (stage.status.value === 'Open') { openStage(stage) } - if (stage.status.value === 'Close') { + if (stage.status.value === 'Closed') { closeStage(stage) } From 0c4ef8338d51153ce6d6d1ae312b0063f48ae04f Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 4 Aug 2020 18:32:52 +0200 Subject: [PATCH 022/112] Update TradingStages.js --- LowFrequencyTrading/TradingStages.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 571ff3b3a..c565598e4 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -388,7 +388,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value * 0.999) { changeStageStatus('Open Stage', 'Closed', 'Position Size Filled') initializeCloseStageSizeTarget() } else { @@ -413,7 +413,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value < - tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value * 0.999) { /* There are unfilled orders, we will check if they were executed, and cancel the ones that were not. */ tradingSystem.evalConditions(stageNode, 'Open Execution') @@ -435,7 +435,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.position.positionBaseAsset.size.value * 0.999) { + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value * 0.999) { changeStageStatus('Open Stage', 'Closed') initializeCloseStageSizeTarget() } @@ -905,8 +905,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value * 0.999) { - changeStageStatus('Close Stage', 'Closed', 'Open Stage fillSize Filled') + tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced.value * 0.999) { + changeStageStatus('Close Stage', 'Closed', 'Size Placed Filled') } else { /* Check the Close Stage Event */ tradingSystem.evalConditions(stageNode, 'Close Stage Event') From 269524a358848b263a49cf5bd8fac3c785a96eeb Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 4 Aug 2020 20:05:04 +0200 Subject: [PATCH 023/112] Update TradingRecords.js --- LowFrequencyTrading/TradingRecords.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index 3aff3d3d3..c1646ef0c 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -151,7 +151,11 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { we use the Index value we find at the configuration of the Record Property. */ if (recordProperty.config.index !== undefined) { - targetNode = targetNode[recordProperty.config.index] + 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. From f5a1d6339b70d3693e29ddbaebc8c39b821ee58a Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 5 Aug 2020 20:31:06 +0200 Subject: [PATCH 024/112] Entry Exit Target Size and Rate implementation --- LowFrequencyTrading/TradingExecution.js | 68 ++++----- LowFrequencyTrading/TradingPosition.js | 194 ++++++++++++++---------- LowFrequencyTrading/TradingStages.js | 160 ++++++++++++------- LowFrequencyTrading/TradingSystem.js | 24 ++- server.js | 2 +- 5 files changed, 266 insertions(+), 182 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 33e0706d8..ca32989fb 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -80,7 +80,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE let tradingSystemOrder = orders[i] /* Trading System Validations */ - if (tradingSystemOrder.config.positionSizePercentage === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.positionSizePercentage === undefined', tradingSystemOrder) } + if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } if (tradingSystemOrder.referenceParent === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder) } let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) @@ -204,11 +204,13 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.situationName.value = situationName function calculateOrderRate() { - /* Order Rate Calculation */ - tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. - if (tradingSystemOrder.positionRate !== undefined) { - if (tradingSystemOrder.positionRate.formula !== undefined) { - tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) + /* By default this is the order rate and it is the rate that applies to Market Orders */ + tradingEngineOrder.rate.value = tradingEngine.current.candle.close.value + + /* Optional Rate Definition */ + if (tradingSystemOrder.orderRate !== undefined) { + if (tradingSystemOrder.orderRate.formula !== undefined) { + tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id) if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingEngineOrder) } if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingEngineOrder) } @@ -220,45 +222,24 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } function calculateOrderSize() { - /* - If the position size was defined in Base Asset, then - the algorithm needs to define its size also in Base Asset. - The same if it was deined in Quoted Asset. - */ - let sizeFormula - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - /* Position was defined in Base Asset */ - if (executionAlgorithm.sizeInBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInBaseAsset === undefined', executionAlgorithm) } - if (executionAlgorithm.sizeInBaseAsset.formula === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInBaseAsset.formula === undefined', executionAlgorithm.sizeInBaseAsset) } - sizeFormula = executionAlgorithm.sizeInBaseAsset.formula - } else { - /* Position was defined in Quoted Asset */ - if (executionAlgorithm.sizeInQuotedAsset === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInQuotedAsset === undefined', executionAlgorithm) } - if (executionAlgorithm.sizeInQuotedAsset.formula === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.sizeInQuotedAsset.formula === undefined', executionAlgorithm.sizeInBaseAsset) } - sizeFormula = executionAlgorithm.sizeInQuotedAsset.formula - } + /* Validate that this config exists */ + if (executionAlgorithm.config.percentageOfStageTargetSize === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.config.percentageOfStageTargetSize === undefined', executionAlgorithm) } + if (isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true', executionAlgorithm) } - /* Order Size Calculation */ - let algorithmSize = tradingSystem.formulas.get(sizeFormula.id) - if (algorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'algorithmSize === undefined', sizeFormula) } - if (isNaN(algorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(algorithmSize) === true', sizeFormula) } + let algorithmSizeInBaseAsset = tradingEngineStage.stageBaseAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100 + let algorithmSizeInQuotedAsset = tradingEngineStage.stageQuotedAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100 /* Validate that this config exists */ - if (tradingSystemOrder.config.positionSizePercentage === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.positionSizePercentage === undefined', executionAlgorithm) } + if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } + if (isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true', tradingSystemOrder) } - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - /* Position was defined in Base Asset */ - tradingEngineOrder.orderBaseAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 - tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value - } else { - /* Position was defined in Quoted Asset */ - tradingEngineOrder.orderQuotedAsset.size.value = algorithmSize * tradingSystemOrder.config.positionSizePercentage / 100 - tradingEngineOrder.orderBaseAsset.size.value = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value - } - tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) - tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) + /* Size in Base Asset */ + tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 - /* Check against the Stage Size Limit */ + /* Size in Quoted Asset */ + tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 + + /* Check that the Size calculated would not surpass Stage Target Size */ if ( tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value > tradingEngineStage.stageBaseAsset.targetSize.value @@ -271,6 +252,9 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE ) { tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value } + + tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) + tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) } async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { @@ -481,11 +465,11 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function sizeFilledSimulation() { /* Size Filled */ tradingEngineOrder.orderBaseAsset.sizeFilled.value = - tradingEngineOrder.orderBaseAsset.size.value * + (tradingEngineOrder.orderBaseAsset.size.value - tradingEngineOrder.orderBaseAsset.feesPaid.value) * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 tradingEngineOrder.orderQuotedAsset.sizeFilled.value = - tradingEngineOrder.orderQuotedAsset.size.value * + (tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value) * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 464087bb7..2b30fe00e 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -11,7 +11,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng applyTakeProfitFormula: applyTakeProfitFormula, updateStopLoss: updateStopLoss, updateTakeProfit: updateTakeProfit, - initializePositionSizeAndRate: initializePositionSizeAndRate, + initialTargets: initialTargets, updateEnds: updateEnds, updateStatus: updateStatus, updateCounters: updateCounters, @@ -100,93 +100,122 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.takeProfit.takeProfitStage.value = stage } - function initializePositionSizeAndRate() { + function initialTargets(stageNode) { - setPositionSize() - setPositionRate() + if (stageNode.initialTargets === undefined) { + const message = 'Stage without Initial Targets node. Add one please.' + badDefinitionUnhandledException(undefined, message, stageNode) + } - tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value + setTargetRate() + setTargetSize() - function setPositionSize() { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - - /* Basic Validation */ - if ( - strategy.openStage.initialDefinition.positionSizeInBaseAsset !== undefined && - strategy.openStage.initialDefinition.positionSizeInQuotedAsset !== undefined - ) { - const errorText = 'Only Position Size In Base Asset or Position Size In Quoted Asset is allowed. Remove one of them please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.id, errorText]) - throw (errorText) + function setTargetRate() { + if (stageNode.initialTargets.targetRate === undefined) { + const message = 'Target Rate Node not Found. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets) + } + if (stageNode.initialTargets.targetRate.formula === undefined) { + const message = 'Formula of Target Rate Node not Found. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate) } - /* Position In Base Asset */ - if (strategy.openStage.initialDefinition.positionSizeInBaseAsset !== undefined) { - if (strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula !== undefined) { - let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula.id) - if (value === undefined) { - const errorText = 'Position Size In Base Asset cannot be undefined. Fix this please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula.id, errorText]) - throw (errorText) - } - if (value === undefined) { - const errorText = 'Position Size In Base Asset cannot be zero. Fix this please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInBaseAsset.formula.id, errorText]) - throw (errorText) - } - tradingEngine.current.position.positionBaseAsset.size.value = global.PRECISE(value, 10) - } else { - const errorText = 'You need to specify a Formula for this.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInBaseAsset.id, errorText]) - throw (errorText) + let value = tradingSystem.formulas.get(stageNode.initialTargets.targetRate.formula.id) + if (value === undefined) { + const message = 'Target Rate can not be undefined. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate.formula) + } + if (isNaN(value)) { + const message = 'Target Rate must be a number. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate.formula) + } + + switch (stageNode.type) { + case 'Open Stage': { + tradingEngine.current.position.entryTargetRate.value = global.PRECISE(value, 10) + break } - } - - /* Position In Quoted Asset */ - if (strategy.openStage.initialDefinition.positionSizeInQuotedAsset !== undefined) { - if (strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula !== undefined) { - let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula.id) - if (value === undefined) { - const errorText = 'Position Size In Quoted Asset cannot be undefined. Fix this please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula.id, errorText]) - throw (errorText) - } - if (value === undefined) { - const errorText = 'Position Size In Quoted Asset cannot be zero. Fix this please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInQuotedAsset.formula.id, errorText]) - throw (errorText) - } - tradingEngine.current.position.positionQuotedAsset.size.value = global.PRECISE(value, 10) - } else { - const errorText = 'You need to specify a Formula for this.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionSizeInQuotedAsset.id, errorText]) - throw (errorText) + case 'Close Stage': { + tradingEngine.current.position.exitTargetRate.value = global.PRECISE(value, 10) + break } } } - function setPositionRate() { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - - if (strategy.openStage.initialDefinition.positionRate === undefined) { - const errorText = 'Position Rate Node not Found. Fix this please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.id, errorText]) - throw (errorText) - } - if (strategy.openStage.initialDefinition.positionRate.formula === undefined) { - const errorText = 'Formula of Position Rate Node not Found. Fix this please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionRate.id, errorText]) - throw (errorText) + function setTargetSize() { + /* Basic Validation */ + if ( + stageNode.initialTargets.targetSizeInBaseAsset !== undefined && + stageNode.initialTargets.targetSizeInQuotedAsset !== undefined + ) { + const message = 'Only Target Size In Base Asset or Target Size In Quoted Asset is allowed. Remove one of them please.' + badDefinitionUnhandledException(undefined, message, stageNode) } - let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id) - if (value === undefined) { - const errorText = 'Position Rate can not be undefined. Fix this please.' - tradingSystem.errors.push([strategy.openStage.initialDefinition.positionRate.formula.id, errorText]) - throw (errorText) + /* Position In Base Asset */ + if (stageNode.initialTargets.targetSizeInBaseAsset !== undefined) { + if (stageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) { + let value = tradingSystem.formulas.get(stageNode.initialTargets.targetSizeInBaseAsset.formula.id) + if (value === undefined) { + const message = 'Target Size In Base Asset cannot be undefined. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset.formula) + } + if (value === undefined) { + const message = 'Target Size In Base Asset cannot be zero. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset.formula) + } + + switch (stageNode.type) { + case 'Open Stage': { + tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = global.PRECISE(value, 10) + tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = + global.PRECISE(value * tradingEngine.current.position.entryTargetRate.value, 10) + break + } + case 'Close Stage': { + tradingEngine.current.position.positionBaseAsset.exitTargetSize.value = global.PRECISE(value, 10) + tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value = + global.PRECISE(value * tradingEngine.current.position.exitTargetRate.value, 10) + break + } + } + } else { + const message = 'You need to specify a Formula for this.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset) + } } - tradingEngine.current.position.rate.value = value + /* Position In Quoted Asset */ + if (stageNode.initialTargets.targetSizeInQuotedAsset !== undefined) { + if (stageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) { + let value = tradingSystem.formulas.get(stageNode.initialTargets.targetSizeInQuotedAsset.formula.id) + if (value === undefined) { + const message = 'Target Size In Quoted Asset cannot be undefined. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula) + } + if (value === undefined) { + const message = 'Target Size In Quoted Asset cannot be zero. Fix this please.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula) + } + switch (stageNode.type) { + case 'Open Stage': { + tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = global.PRECISE(value, 10) + tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = + global.PRECISE(value / tradingEngine.current.position.entryTargetRate.value, 10) + break + } + case 'Close Stage': { + tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value = global.PRECISE(value, 10) + tradingEngine.current.position.positionBaseAsset.exitTargetSize.value = + global.PRECISE(value / tradingEngine.current.position.exitTargetRate.value, 10) + break + } + } + } else { + const errorText = 'You need to specify a Formula for this.' + badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula) + } + } } } @@ -233,11 +262,11 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* ROI Calculation */ tradingEngine.current.position.positionBaseAsset.ROI.value = tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionBaseAsset.size.value + tradingEngine.current.position.positionBaseAsset.entryTargetSize.value tradingEngine.current.position.positionQuotedAsset.ROI.value = tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionQuotedAsset.size.value + tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) @@ -253,4 +282,17 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'FAIL' } } + + function badDefinitionUnhandledException(err, message, node) { + tradingSystem.errors.push([node.id, message]) + + logger.write(MODULE_NAME, "[ERROR] -> " + message); + logger.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name); + logger.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type); + logger.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config)); + if (err !== undefined) { + logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack); + } + throw 'It is not safe to continue with a Definition Error like this. Please fix the problem and try again.' + } } \ No newline at end of file diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index c565598e4..809d63361 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -343,9 +343,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - /* Position size and rate */ - tradingPositionModule.initializePositionSizeAndRate() - initializeOpenStageSizeTarget() + /* Entry Position size and rate */ + tradingSystem.evalFormulas(stageNode, 'Initial Targets') + tradingPositionModule.initialTargets(stageNode) + initializeStageTargetSize() /* Check Execution at open stage node */ tradingSystem.evalConditions(stageNode, 'Open Execution') @@ -381,16 +382,36 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingEngine.current.strategyOpenStage ) /* - The Open is finished when the fillSize + feesPaid reaches the Position Size. - This can happens at any time when we update the sizeFilled value when we see - at the exchange that orders were filled. + The Open is finished when the placedSize reaches the targetSize in any of the + stage assets, and the fillSize + feesPaid reaches the placedSize also in any + of the stage Assets. + This can happens at any time when we update the sizeFilled and the feesPaid values + when we see at the exchange that orders were filled. */ if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value * 0.999) { + ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize + + || + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize + ) + && + ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value + + || + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value + ) + ) { changeStageStatus('Open Stage', 'Closed', 'Position Size Filled') - initializeCloseStageSizeTarget() } else { /* Check the Close Stage Event */ tradingSystem.evalConditions(stageNode, 'Close Stage Event') @@ -409,63 +430,51 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage let executionNode = stageNode.openExecution - /* Check if there are unfilled orders */ - if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value < - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value * 0.999) { - /* There are unfilled orders, we will check if they were executed, and cancel the ones that were not. */ + /* + Check if there are unfilled orders, we will check if they were executed, + and cancel the ones that were not. + */ + tradingSystem.evalConditions(stageNode, 'Open Execution') + tradingSystem.evalFormulas(stageNode, 'Open Execution') - tradingSystem.evalConditions(stageNode, 'Open Execution') - tradingSystem.evalFormulas(stageNode, 'Open Execution') - - await tradingExecutionModule.runExecution( - executionNode, - true, - tradingEngine.current.strategyOpenStage - ) - } + await tradingExecutionModule.runExecution( + executionNode, + true, + tradingEngine.current.strategyOpenStage + ) /* The Closing is finished when the fillSize + feesPaid reaches the ordersSize. This can happens either because we update the sizeFilled value when we see at the exchange that orders were filled, or we reduce the ordersSize when - we cancel not yet filled orders. + we cancel not yet filled orders. Here it does not matter the targetSize since + this status represent a force closure of this stage. */ if ( tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value * 0.999) { + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value + + || + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value + ) { changeStageStatus('Open Stage', 'Closed') - initializeCloseStageSizeTarget() } } - function initializeOpenStageSizeTarget() { + function initializeStageTargetSize() { /* Here we will transform the position size into targets for each asset of the stage. */ - if (tradingEngine.current.position.positionBaseAsset.size.value > 0) { - tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.size.value - tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = - tradingEngine.current.position.positionBaseAsset.size.value * tradingEngine.current.position.rate.value - } else { - tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.size.value - tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = - tradingEngine.current.position.positionQuotedAsset.size.value / tradingEngine.current.position.rate.value - } + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.entryTargetSize.value + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value, 10) tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value, 10) } - - function initializeCloseStageSizeTarget() { - /* - Here we will move the size filled of the open stage to the size target of the close stage. - */ - tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value = tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value - tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value - } } function runManageStage() { @@ -876,6 +885,12 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM the total filled size of the Open Stage. By skiping execution now we allow the open stage to get a final value for sizeFilled that we can use. */ + + /* Exit Position size and rate */ + tradingSystem.evalFormulas(stageNode, 'Initial Targets') + tradingPositionModule.initialTargets(stageNode) + + initializeStageTargetSize() changeStageStatus('Close Stage', 'Open') return } @@ -898,14 +913,35 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM ) /* - The Close Stage is finished when the fillSize + feesPaid reaches the Open Fill Size. - This can happens at any time when we update the sizeFilled value when we see - at the exchange that orders were filled. + The Open is finished when the placedSize reaches the targetSize in any of the + stage assets, and the fillSize + feesPaid reaches the placedSize also in any + of the stage Assets. + This can happens at any time when we update the sizeFilled and the feesPaid values + when we see at the exchange that orders were filled. */ if ( - tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced.value * 0.999) { + ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize + + || + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize + ) + && + ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value + + || + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value + ) + ) { changeStageStatus('Close Stage', 'Closed', 'Size Placed Filled') } else { /* Check the Close Stage Event */ @@ -942,6 +978,17 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM exitPositionAndStrategy() } + function initializeStageTargetSize() { + /* + Here we will transform the position size into targets for each asset of the stage. + */ + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.exitTargetSize.value + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value, 10) + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value, 10) + } + function exitPositionAndStrategy() { /* Position Statistics & Results */ @@ -1187,15 +1234,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function updateStatus() { if (tradingEngine.current.strategyTriggerStage.status.value === 'Closed') { - resetStage(tradingEngine.current.strategyTriggerStage) + resetStage(tradingEngine.current.strategyTriggerStage.status) } if (tradingEngine.current.strategyOpenStage.status.value === 'Closed') { - resetStage(tradingEngine.current.strategyOpenStage) + resetStage(tradingEngine.current.strategyOpenStage.status) } if (tradingEngine.current.strategyManageStage.status.value === 'Closed') { - resetStage(tradingEngine.current.strategyManageStage) + resetStage(tradingEngine.current.strategyManageStage.status) } if (tradingEngine.current.strategyCloseStage.status.value === 'Closed') { + resetStage(tradingEngine.current.strategyTriggerStage) + resetStage(tradingEngine.current.strategyOpenStage) + resetStage(tradingEngine.current.strategyManageStage) resetStage(tradingEngine.current.strategyCloseStage) } } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index d371ae225..767973b32 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -118,17 +118,25 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } async function run() { - /* Run the Trigger Stage */ - tradingStagesModule.runTriggerStage() + try { + /* Run the Trigger Stage */ + tradingStagesModule.runTriggerStage() - /* Run the Open Stage */ - await tradingStagesModule.runOpenStage() + /* Run the Open Stage */ + await tradingStagesModule.runOpenStage() - /* Run the Manage Stage */ - tradingStagesModule.runManageStage() + /* Run the Manage Stage */ + tradingStagesModule.runManageStage() - /* Run the Close Stage */ - await tradingStagesModule.runCloseStage() + /* Run the Close Stage */ + await tradingStagesModule.runCloseStage() + } 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. + */ + } } function evalNode(node, evaluating, descendentOfNodeType, isDescendent) { diff --git a/server.js b/server.js index b9e30080f..1d5b92c6c 100644 --- a/server.js +++ b/server.js @@ -41,7 +41,7 @@ global.UNIQUE_ID = function () { } global.PRECISE = function (floatNumber, precision) { - return parseFloat(Number.parseFloat(floatNumber).toPrecision(precision)) + return this.parseFloat(floatNumber.toFixed(precision)) } process.on('uncaughtException', function (err) { From 11aa3d6bf7d9864cc6359a2c97fed1006ada2bf2 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 6 Aug 2020 11:45:01 +0200 Subject: [PATCH 025/112] Bug Fixes --- LowFrequencyTrading/TradingStages.js | 5 +++-- LowFrequencyTrading/TradingSystem.js | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 809d63361..0acd5488e 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -586,7 +586,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM the Position Rate, and we assign the values Above or Below to it. */ if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) { - if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.rate.value) { + if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.entryTargetRate.value) { tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above' } else { tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below' @@ -602,7 +602,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM the Position Rates, and we assign the values Above or Below to it. */ if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) { - if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.rate.value) { + if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.entryTargetRate.value) { tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above' } else { tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below' @@ -885,6 +885,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM the total filled size of the Open Stage. By skiping execution now we allow the open stage to get a final value for sizeFilled that we can use. */ + let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage /* Exit Position size and rate */ tradingSystem.evalFormulas(stageNode, 'Initial Targets') diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 767973b32..b84db7f45 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -136,6 +136,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM 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) { + logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err) + } + if (err.stack !== undefined) { + logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err.stack) + } } } From 65ddc2e805ef661fc9dce906fb6df7f31c4171c0 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 6 Aug 2020 14:34:08 +0200 Subject: [PATCH 026/112] bugs fixed --- LowFrequencyTrading/TradingExecution.js | 4 +++- LowFrequencyTrading/TradingStages.js | 28 ++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index ca32989fb..9ae658ce5 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -303,7 +303,9 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } /* Filter by what is defined at the Strategy */ - if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } + if (tradingSystemOrder.simulatedExchangeEvents === undefined) { + badDefinitionUnhandledException(undefined, 'tradingSystemOrder.simulatedExchangeEvents === undefined', tradingSystemOrder) + } let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 0acd5488e..89ba6e02a 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -922,25 +922,25 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM */ if ( ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize + tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced >= + tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize || - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced >= - tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize + tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced >= + tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize ) && ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value + tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced.value || - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value + tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizeFilled.value + + tradingEngine.current.strategyCloseStage.stageQuotedAsset.feesPaid.value >= + tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced.value ) ) { changeStageStatus('Close Stage', 'Closed', 'Size Placed Filled') @@ -983,11 +983,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM /* Here we will transform the position size into targets for each asset of the stage. */ - tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.exitTargetSize.value - tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value + tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.exitTargetSize.value + tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value - tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value, 10) - tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value, 10) + tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value, 10) + tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value, 10) } function exitPositionAndStrategy() { From 2348a2bacbd9552dd889d1626589d00cdf9aa870 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 6 Aug 2020 23:17:43 +0200 Subject: [PATCH 027/112] Fixes --- FileStorage.js | 2 +- LowFrequencyTrading/TradingPosition.js | 40 ++++++++++++++++++++++++-- LowFrequencyTrading/TradingStages.js | 10 ++----- server.js | 1 - 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/FileStorage.js b/FileStorage.js index 1b55b9af7..f47b86545 100644 --- a/FileStorage.js +++ b/FileStorage.js @@ -163,7 +163,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } function retry() { - //console.log(new Date(), ' Retry #: ' + currentRetryGetTextFile, fileLocation ) + console.log(new Date(), ' Retry #: ' + currentRetryGetTextFile, fileLocation) if (currentRetryGetTextFile < MAX_RETRY) { currentRetryGetTextFile++ logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> retry -> Will try to read the file again -> Retry #: ' + currentRetryGetTextFile) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 2b30fe00e..436b0786f 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -15,8 +15,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng updateEnds: updateEnds, updateStatus: updateStatus, updateCounters: updateCounters, - updateStatistics: updateStatistics, - updateResults: updateResults, resetPosition: resetPosition, initialize: initialize, finalize: finalize @@ -47,6 +45,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.serialNumber.value = tradingEngine.episode.episodeCounters.positions.value tradingEngine.current.position.identifier.value = global.UNIQUE_ID() tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value + tradingEngine.current.position.beginRate.value = tradingEngine.current.candle.close.value tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value tradingEngine.current.position.situationName.value = situationName @@ -72,6 +71,10 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.status.value = 'Closed' tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value tradingEngine.current.position.endRate.value = tradingEngine.current.candle.close.value + + /* Position Statistics & Results */ + updateStatistics() + updateResults() /* Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure. */ @@ -239,6 +242,37 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function updateStatistics() { + /* Profit Loss Calculation */ + tradingEngine.current.position.positionStatistics.profitLoss.value = + ( + tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value + + tradingEngine.current.balance.quotedAsset.value + ) - + ( + tradingEngine.previous.balance.baseAsset.value * tradingEngine.current.position.beginRate.value + + tradingEngine.previous.balance.quotedAsset.value + ) + tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) + + /* ROI Calculation */ + tradingEngine.current.position.positionStatistics.ROI.value = + ( + tradingEngine.current.position.positionStatistics.profitLoss.value + ) + * 100 / + ( + tradingEngine.previous.balance.baseAsset.value * tradingEngine.current.position.beginRate.value + + tradingEngine.previous.balance.quotedAsset.value + ) + tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) + + /* Hit Fail Calculation */ + if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { + tradingEngine.current.position.positionStatistics.hitFail.value = 'HIT' + } else { + tradingEngine.current.position.positionStatistics.hitFail.value = 'FAIL' + } + /* Days Calculation */ tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * @@ -258,6 +292,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.previous.balance.quotedAsset.value tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) + tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) /* ROI Calculation */ tradingEngine.current.position.positionBaseAsset.ROI.value = @@ -269,6 +304,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) + tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) /* Hit Fail Calculation */ if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 89ba6e02a..6483f86ab 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -992,13 +992,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function exitPositionAndStrategy() { - /* Position Statistics & Results */ - tradingPositionModule.updateStatistics() - tradingPositionModule.updateResults() - - updatingEpisodeAssets() - updatingEpisodeStatistics() - /* Taking Position Snapshot */ if (bot.SESSION.type === 'Backtesting Session') { if (sessionParameters.snapshots !== undefined) { @@ -1020,6 +1013,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM /* Close the Position */ tradingPositionModule.closePosition() + updatingEpisodeAssets() + updatingEpisodeStatistics() + /* Close the Strategy */ tradingStrategyModule.closeStrategy('Position Closed') diff --git a/server.js b/server.js index 1d5b92c6c..80d7a1c9e 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,4 @@ /* Callbacks default responses. */ - global.DEFAULT_OK_RESPONSE = { result: "Ok", message: "Operation Succeeded" From f4583230c6b39359bc41cfb169b3f8dc028304be Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 7 Aug 2020 09:21:30 +0200 Subject: [PATCH 028/112] Current Episode --- LowFrequencyTrading/Snapshots.js | 2 +- LowFrequencyTrading/TradingEngine.js | 4 +- LowFrequencyTrading/TradingExecution.js | 4 +- LowFrequencyTrading/TradingPosition.js | 16 ++-- LowFrequencyTrading/TradingRecords.js | 27 ++++-- LowFrequencyTrading/TradingStages.js | 104 ++++++++++++------------ LowFrequencyTrading/TradingStrategy.js | 2 +- 7 files changed, 86 insertions(+), 73 deletions(-) diff --git a/LowFrequencyTrading/Snapshots.js b/LowFrequencyTrading/Snapshots.js index 4c085855a..11b3e6eae 100644 --- a/LowFrequencyTrading/Snapshots.js +++ b/LowFrequencyTrading/Snapshots.js @@ -253,7 +253,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) { let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result In Base Asset', 'Result In Quoted Asset', 'ROI in Base Asset', 'ROI in Quoted Asset', 'Exit Type'] closeValues = [ - tradingEngine.episode.episodeCounters.positions.value, // Position Number + tradingEngine.current.episode.episodeCounters.positions.value, // Position Number (new Date(openDatetime)).toISOString(), // Open Datetime (new Date(closeDatetime)).toISOString(), // Open Datetime tradingEngine.current.strategy.strategyName.value, // Strategy Name diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index e10bfe927..502be24ad 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -142,8 +142,8 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } function updateEpisodeCountersAndStatistics() { - tradingEngine.episode.episodeCounters.periods.value++ - tradingEngine.episode.episodeStatistics.days.value = tradingEngine.episode.episodeCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + tradingEngine.current.episode.episodeCounters.periods.value++ + tradingEngine.current.episode.episodeStatistics.days.value = tradingEngine.current.episode.episodeCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS } function updateDistanceToEventsCounters() { diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 9ae658ce5..ec2bada64 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -188,7 +188,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) /* Updating Episode Counters */ - tradingEngine.episode.episodeCounters.orders.value++ + tradingEngine.current.episode.episodeCounters.orders.value++ /* Initialize this */ tradingEngine.current.distanceToEvent.createOrder.value = 1 @@ -198,7 +198,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.identifier.value = global.UNIQUE_ID() tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value tradingEngineOrder.end.value = tradingEngine.current.candle.end.value - tradingEngineOrder.serialNumber.value = tradingEngine.episode.episodeCounters.orders.value + tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value tradingEngineOrder.orderName.value = tradingSystemOrder.name tradingEngineOrder.algorithmName.value = executionAlgorithm.name tradingEngineOrder.situationName.value = situationName diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 436b0786f..b725758b9 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -42,7 +42,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* Recording the opening at the Trading Engine Data Structure */ tradingEngine.current.position.status.value = 'Open' - tradingEngine.current.position.serialNumber.value = tradingEngine.episode.episodeCounters.positions.value + tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value tradingEngine.current.position.identifier.value = global.UNIQUE_ID() tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value tradingEngine.current.position.beginRate.value = tradingEngine.current.candle.close.value @@ -56,7 +56,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.takeProfit.takeProfitPhase.value = 0 /* Updating Episode Counters */ - tradingEngine.episode.episodeCounters.positions.value++ + tradingEngine.current.episode.episodeCounters.positions.value++ /* Inicializing this counter */ tradingEngine.current.distanceToEvent.takePosition.value = 1 @@ -268,9 +268,9 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* Hit Fail Calculation */ if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { - tradingEngine.current.position.positionStatistics.hitFail.value = 'HIT' + tradingEngine.current.position.positionStatistics.hitFail.value = 'Hit' } else { - tradingEngine.current.position.positionStatistics.hitFail.value = 'FAIL' + tradingEngine.current.position.positionStatistics.hitFail.value = 'Fail' } /* Days Calculation */ @@ -308,14 +308,14 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* Hit Fail Calculation */ if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { - tradingEngine.current.position.positionBaseAsset.hitFail.value = 'HIT' + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit' } else { - tradingEngine.current.position.positionBaseAsset.hitFail.value = 'FAIL' + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail' } if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { - tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'HIT' + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit' } else { - tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'FAIL' + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail' } } diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index c1646ef0c..5402bea7f 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -71,7 +71,12 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { /* The product root can be a node or a node property of type array. */ - let productRoot = eval(product.config.nodePath) + 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') { /* @@ -122,7 +127,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { try { propertyRootNode = eval(recordProperty.config.nodePath) } catch (err) { - badDefinitionUnhandledException(err, 'Error Evaluation Record Property nodePath.', product, recordProperty) + badDefinitionUnhandledException(err, 'Error Evaluation Record Property nodePath -> nodePath = ' + recordProperty.config.nodePath, product, recordProperty) } } /* @@ -330,11 +335,19 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { } function badDefinitionUnhandledException(err, message, product, recordProperty) { - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> " + message); - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> product.name = " + product.name); - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.name = " + recordProperty.name); - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.config.codeName = " + recordProperty.config.codeName); - logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> err.stack = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> " + message); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> product.name = " + product.name); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> product.config = " + JSON.stringify(product.config)); + + if (recordProperty) { + logger.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.name = " + recordProperty.name); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.config.codeName = " + recordProperty.config.codeName); + logger.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.config = " + JSON.stringify(recordProperty.config)); + } + + if (err) { + logger.write(MODULE_NAME, "[ERROR] appendRecords -> err.stack = " + err.stack); + } throw 'Can not continue with a Definition Error like this.' } } \ No newline at end of file diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 6483f86ab..2e1550282 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -203,7 +203,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingStrategyModule.openStrategy(j, situation.name, strategy.name) /* Updating Episode Counters */ - tradingEngine.episode.episodeCounters.strategies.value++ + tradingEngine.current.episode.episodeCounters.strategies.value++ /* Initialize this */ tradingEngine.current.distanceToEvent.triggerOn.value = 1 @@ -1027,112 +1027,112 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM /* Updating Hits & Fails */ if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeBaseAsset.hits.value++ + tradingEngine.current.episode.episodeBaseAsset.hits.value++ } else { - tradingEngine.episode.episodeBaseAsset.fails.value++ + tradingEngine.current.episode.episodeBaseAsset.fails.value++ } if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeQuotedAsset.hits.value++ + tradingEngine.current.episode.episodeQuotedAsset.hits.value++ } else { - tradingEngine.episode.episodeQuotedAsset.fails.value++ + tradingEngine.current.episode.episodeQuotedAsset.fails.value++ } /* Updating Profit Loss */ - tradingEngine.episode.episodeBaseAsset.profitLoss.value = + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = tradingEngine.current.balance.baseAsset.value - sessionParameters.sessionBaseAsset.config.initialBalance - tradingEngine.episode.episodeQuotedAsset.profitLoss.value = + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - sessionParameters.sessionQuotedAsset.config.initialBalance - tradingEngine.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.profitLoss.value, 10) - tradingEngine.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.profitLoss.value, 10) + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10) /* Updating ROI */ - tradingEngine.episode.episodeBaseAsset.ROI.value = - (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeBaseAsset.profitLoss.value) / + tradingEngine.current.episode.episodeBaseAsset.ROI.value = + (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value) / sessionParameters.sessionBaseAsset.config.initialBalance - 1 - tradingEngine.episode.episodeQuotedAsset.ROI.value = - (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeQuotedAsset.profitLoss.value) / + tradingEngine.current.episode.episodeQuotedAsset.ROI.value = + (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value) / sessionParameters.sessionQuotedAsset.config.initialBalance - 1 - tradingEngine.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.ROI.value, 10) - tradingEngine.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.ROI.value, 10) + tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10) /* Updating Hit Ratio */ - tradingEngine.episode.episodeBaseAsset.hitRatio.value = - tradingEngine.episode.episodeBaseAsset.hits.value / - tradingEngine.episode.episodeCounters.positions.value + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = + tradingEngine.current.episode.episodeBaseAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value - tradingEngine.episode.episodeQuotedAsset.hitRatio.value = - tradingEngine.episode.episodeQuotedAsset.hits.value / - tradingEngine.episode.episodeCounters.positions.value + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = + tradingEngine.current.episode.episodeQuotedAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value - tradingEngine.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.hitRatio.value, 10) - tradingEngine.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.hitRatio.value, 10) + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) /* Updating Anualized Rate Of Return */ - tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = - tradingEngine.episode.episodeBaseAsset.ROI.value / - tradingEngine.episode.episodeStatistics.days.value * 365 + tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = + tradingEngine.current.episode.episodeBaseAsset.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 - tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = - tradingEngine.episode.episodeQuotedAsset.ROI.value / - tradingEngine.episode.episodeStatistics.days.value * 365 + tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = + tradingEngine.current.episode.episodeQuotedAsset.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 - tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) - tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) /* Updating Hit or Fail */ - if (tradingEngine.episode.episodeBaseAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Hit' + if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit' } else { - tradingEngine.episode.episodeBaseAsset.hitFail.value = 'Fail' + tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Fail' } - if (tradingEngine.episode.episodeQuotedAsset.profitLoss.value > 0) { - tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Hit' + if (tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Hit' } else { - tradingEngine.episode.episodeQuotedAsset.hitFail.value = 'Fail' + tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail' } } function updatingEpisodeStatistics() { /* Updating Profit Loss */ - tradingEngine.episode.episodeStatistics.profitLoss.value = - tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + - tradingEngine.episode.episodeQuotedAsset.profitLoss.value + tradingEngine.current.episode.episodeStatistics.profitLoss.value = + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - tradingEngine.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.episode.episodeStatistics.profitLoss.value, 10) + tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) /* Updating ROI */ - tradingEngine.episode.episodeStatistics.ROI.value = + tradingEngine.current.episode.episodeStatistics.ROI.value = ( sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + - tradingEngine.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + sessionParameters.sessionQuotedAsset.config.initialBalance + - tradingEngine.episode.episodeQuotedAsset.profitLoss.value + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value ) / ( sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + sessionParameters.sessionQuotedAsset.config.initialBalance ) - 1 - tradingEngine.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.episode.episodeStatistics.ROI.value, 10) + tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) /* Updating Anualized Rate Of Return */ - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = - tradingEngine.episode.episodeStatistics.ROI.value / - tradingEngine.episode.episodeStatistics.days.value * 365 + tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = + tradingEngine.current.episode.episodeStatistics.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value, 10) /* Updating Hit or Fail */ - if (tradingEngine.episode.episodeStatistics.profitLoss.value > 0) { - tradingEngine.episode.episodeStatistics.hitFail.value = 'Hit' + if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' } else { - tradingEngine.episode.episodeStatistics.hitFail.value = 'Fail' + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail' } } } diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index 0728c2b0c..5006e3eb6 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -28,7 +28,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng function openStrategy(index, situationName, strategyName) { tradingEngine.current.strategy.status.value = 'Open' - tradingEngine.current.strategy.serialNumber.value = tradingEngine.episode.episodeCounters.strategies.value + tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value tradingEngine.current.strategy.identifier.value = global.UNIQUE_ID() tradingEngine.current.strategy.begin.value = tradingEngine.current.candle.begin.value tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value From d36c6e7b7f7d1caeaa0de8f704a824f409fac7e5 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 7 Aug 2020 13:11:52 +0200 Subject: [PATCH 029/112] Episode Module Implemented --- LowFrequencyTrading/Snapshots.js | 2 +- LowFrequencyTrading/TradingEngine.js | 6 - LowFrequencyTrading/TradingEpisode.js | 203 +++++++++++++++++++++++ LowFrequencyTrading/TradingPosition.js | 6 +- LowFrequencyTrading/TradingSimulation.js | 58 +++++-- LowFrequencyTrading/TradingStages.js | 135 ++------------- LowFrequencyTrading/TradingStrategy.js | 10 +- 7 files changed, 269 insertions(+), 151 deletions(-) create mode 100644 LowFrequencyTrading/TradingEpisode.js diff --git a/LowFrequencyTrading/Snapshots.js b/LowFrequencyTrading/Snapshots.js index 11b3e6eae..1f4559fb3 100644 --- a/LowFrequencyTrading/Snapshots.js +++ b/LowFrequencyTrading/Snapshots.js @@ -253,7 +253,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) { let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result In Base Asset', 'Result In Quoted Asset', 'ROI in Base Asset', 'ROI in Quoted Asset', 'Exit Type'] closeValues = [ - tradingEngine.current.episode.episodeCounters.positions.value, // Position Number + tradingEngine.current.episode.episodeCounters.positions.value, // Position Number (new Date(openDatetime)).toISOString(), // Open Datetime (new Date(closeDatetime)).toISOString(), // Open Datetime tradingEngine.current.strategy.strategyName.value, // Strategy Name diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 502be24ad..981f71183 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -8,7 +8,6 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { getNodeById: getNodeById, cloneValues: cloneValues, setCurrentCandle: setCurrentCandle, - updateEpisodeCountersAndStatistics: updateEpisodeCountersAndStatistics, updateDistanceToEventsCounters: updateDistanceToEventsCounters, initialize: initialize, finalize: finalize @@ -141,11 +140,6 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } } - function updateEpisodeCountersAndStatistics() { - tradingEngine.current.episode.episodeCounters.periods.value++ - tradingEngine.current.episode.episodeStatistics.days.value = tradingEngine.current.episode.episodeCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS - } - function updateDistanceToEventsCounters() { /* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */ if ( diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js new file mode 100644 index 000000000..194532833 --- /dev/null +++ b/LowFrequencyTrading/TradingEpisode.js @@ -0,0 +1,203 @@ +exports.newTradingEpisode = function newTradingEpisode(bot, logger) { + /* + This module packages all functions related to Episodes. + */ + const MODULE_NAME = 'Trading Episode' + let thisObject = { + openEpisode: openEpisode, + closingEpisode: closingEpisode, + closeEpisode: closeEpisode, + updateEnds: updateEnds, + resetTradingEngineDataStructure: resetTradingEngineDataStructure, + updateCounters: updateCounters, + updateStatistics: updateStatistics, + calculateResults: calculateResults, + calculateStatistics: calculateStatistics, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + let tradingSystem + let sessionParameters + + return thisObject + + function initialize() { + tradingSystem = bot.simulationState.tradingSystem + tradingEngine = bot.simulationState.tradingEngine + sessionParameters = bot.SESSION.parameters + } + + function finalize() { + tradingEngine = undefined + tradingSystem = undefined + sessionParameters = undefined + } + + function openEpisode() { + + /* Recording the opening at the Trading Engine Data Structure */ + tradingEngine.current.episode.status.value = 'Open' + tradingEngine.current.episode.serialNumber.value = 1 + tradingEngine.current.episode.identifier.value = global.UNIQUE_ID() + tradingEngine.current.episode.begin.value = tradingEngine.current.candle.begin.value + tradingEngine.current.episode.beginRate.value = tradingEngine.current.candle.close.value + } + + function closingEpisode(exitType) { + tradingEngine.current.episode.status.value = 'Closing' + tradingEngine.current.episode.exitType.value = exitType + } + + function closeEpisode() { + tradingEngine.current.episode.status.value = 'Closed' + tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value + } + + function resetEpisode() { + tradingEngine.current.episode.initialize(tradingEngine.current.episode) + } + + function updateEnds() { + if (tradingEngine.current.episode.status.value === 'Open') { + tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value + } + } + + function resetTradingEngineDataStructure() { + if (tradingEngine.current.episode.status.value === 'Closed') { + resetEpisode() + } + } + + function updateCounters() { + if (tradingEngine.current.episode.status.value === 'Open') { + tradingEngine.current.episode.episodeCounters.periods.value++ + } + } + + function updateStatistics() { + + /* Daus Calculation */ + tradingEngine.current.episode.episodeStatistics.days.value = + tradingEngine.current.episode.episodeCounters.periods.value * + sessionParameters.timeFrame.config.value / + global.ONE_DAY_IN_MILISECONDS + } + + function calculateStatistics() { + + /* Updating Profit Loss */ + tradingEngine.current.episode.episodeStatistics.profitLoss.value = + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + + tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.current.episode.episodeStatistics.ROI.value = + ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) / ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + ) - 1 + + tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) + + /* Updating Anualized Rate Of Return */ + tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = + tradingEngine.current.episode.episodeStatistics.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 + + tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' + } else { + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail' + } + } + + function calculateResults() { + + /* Updating Hits & Fails */ + if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeBaseAsset.hits.value++ + } else { + tradingEngine.current.episode.episodeBaseAsset.fails.value++ + } + if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeQuotedAsset.hits.value++ + } else { + tradingEngine.current.episode.episodeQuotedAsset.fails.value++ + } + + /* Updating Profit Loss */ + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = + tradingEngine.current.balance.baseAsset.value - + sessionParameters.sessionBaseAsset.config.initialBalance + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = + tradingEngine.current.balance.quotedAsset.value - + sessionParameters.sessionQuotedAsset.config.initialBalance + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.current.episode.episodeBaseAsset.ROI.value = + (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value) / + sessionParameters.sessionBaseAsset.config.initialBalance - 1 + + tradingEngine.current.episode.episodeQuotedAsset.ROI.value = + (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value) / + sessionParameters.sessionQuotedAsset.config.initialBalance - 1 + + tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10) + + /* Updating Hit Ratio */ + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = + tradingEngine.current.episode.episodeBaseAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value + + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = + tradingEngine.current.episode.episodeQuotedAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value + + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) + + /* Updating Anualized Rate Of Return */ + tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = + tradingEngine.current.episode.episodeBaseAsset.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 + + tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = + tradingEngine.current.episode.episodeQuotedAsset.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 + + tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Fail' + } + if (tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail' + } + } +} \ No newline at end of file diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index b725758b9..885292b4e 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -13,7 +13,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng updateTakeProfit: updateTakeProfit, initialTargets: initialTargets, updateEnds: updateEnds, - updateStatus: updateStatus, + resetTradingEngineDataStructure: resetTradingEngineDataStructure, updateCounters: updateCounters, resetPosition: resetPosition, initialize: initialize, @@ -46,7 +46,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.identifier.value = global.UNIQUE_ID() tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value tradingEngine.current.position.beginRate.value = tradingEngine.current.candle.close.value - tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value tradingEngine.current.position.situationName.value = situationName /* Initializing Stop and Take Profit Stage / Phase */ @@ -225,10 +224,11 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function updateEnds() { if (tradingEngine.current.position.status.value === 'Open') { tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.position.endRate.value = tradingEngine.current.candle.close.value } } - function updateStatus() { + function resetTradingEngineDataStructure() { if (tradingEngine.current.position.status.value === 'Closed') { resetPosition() } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index cc07cb796..509b3ca87 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -46,6 +46,10 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger, tradingEngineModule) tradingSystemModule.initialize() + const TRADING_EPISODE_MODULE = require('./TradingEpisode.js') + let tradingEpisodeModule = TRADING_EPISODE_MODULE.newTradingEpisode(bot, logger) + tradingEpisodeModule.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 @@ -53,6 +57,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT /* Variables needed for heartbeat functionality */ let heartBeatDate let previousHeartBeatDate + let firstLoopExecution = true initializeLoop() @@ -106,13 +111,18 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading engine data structure to make it available to anyone, including conditions and formulas. - if (checkIfWeAreAtTheHeadOfTheMarket() === false) { - afterLoop() + if (firstLoopExecution === true) { + tradingEpisodeModule.openEpisode() + firstLoopExecution = false + } + + if (checkLastCandle() === false) { + closeEpisode('Last Candle Reached') return } if (checkMinimunAndMaximunBalance() === false) { - afterLoop() + closeEpisode('Min or Max Balance Reached') return } @@ -130,15 +140,15 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT /* Do the stuff needed previous to the run */ tradingSystemModule.mantain() - tradingEngineModule.updateEpisodeCountersAndStatistics() + /* Episode Counters and Statistics */ + tradingEpisodeModule.updateCounters() + tradingEpisodeModule.updateStatistics() + tradingEngineModule.updateDistanceToEventsCounters() /* Run one cycle of the Trading System*/ await tradingSystemModule.run() - /* Add new records to the process output */ - tradingRecordsModule.appendRecords() - controlLoop() } @@ -147,33 +157,51 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (bot.STOP_SESSION === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') - afterLoop() + closeEpisode('Session Stopped') return } if (global.STOP_TASK_GRACEFULLY === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') - afterLoop() + closeEpisode('Task Stopped') return } - tradingEngine.current.candle.index.value++ - if (tradingEngine.current.candle.index.value < candles.length) { + if (tradingEngine.current.candle.index.value + 1 < candles.length) { + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords() + + tradingEngine.current.candle.index.value++ + setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. } else { - afterLoop() + closeEpisode('All Candles Processed') } } + function closeEpisode(exitType) { + tradingEpisodeModule.closingEpisode(exitType) + tradingEpisodeModule.closeEpisode() + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords() + + afterLoop() + } + function afterLoop() { + tradingEngineModule.finalize() tradingSystemModule.finalize() tradingRecordsModule.finalize() + tradingEpisodeModule.finalize() tradingEngineModule = undefined tradingSystemModule = undefined tradingRecordsModule = undefined + tradingEpisodeModule = undefined callback() } @@ -301,7 +329,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } - function checkIfWeAreAtTheHeadOfTheMarket() { + function checkLastCandle() { /* We skip the candle at the head of the market because it has not closed yet. The procedure to determine if we are at the head of the market is different when we are processing Market Files than when we are processing Daily Files. TODO Check that the procedure is good for Beta 6 @@ -310,7 +338,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value if (tradingEngine.current.candle.index.value === candles.length - 1) { if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } return false /* Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators. @@ -320,7 +348,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } else { // We are processing Market Files if (tradingEngine.current.candle.index.value === candles.length - 1) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } return false } } diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 2e1550282..8e7507047 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -11,7 +11,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM runManageStage: runManageStage, runCloseStage: runCloseStage, updateEnds: updateEnds, - updateStatus: updateStatus, + resetTradingEngineDataStructure: resetTradingEngineDataStructure, updateCounters: updateCounters, updateStatistics: updateStatistics, resetStage: resetStage, @@ -34,6 +34,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM const SNAPSHOTS_MODULE = require('./Snapshots.js') let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger) + const TRADING_EPISODE_MODULE = require('./TradingEpisode.js') + let tradingEpisodeModule = TRADING_EPISODE_MODULE.newTradingEpisode(bot, logger) let tradingEngine let tradingSystem @@ -51,6 +53,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM announcementsModule.initialize() snapshotsModule.initialize() tradingExecutionModule.initialize() + tradingEpisodeModule.initialize() } function finalize() { @@ -72,6 +75,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingExecutionModule.finalize() tradingExecutionModule = undefined + + tradingEpisodeModule.finalize() + tradingEpisodeModule = undefined } function updateChart(pChart) { @@ -85,19 +91,19 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM mantainOrders() function mantainStrategies() { - tradingStrategyModule.updateStatus() + tradingStrategyModule.resetTradingEngineDataStructure() tradingStrategyModule.updateCounters() tradingStrategyModule.updateEnds() } function mantainStages() { - updateStatus() + resetTradingEngineDataStructure() updateCounters() updateEnds() } function mantainPositions() { - tradingPositionModule.updateStatus() + tradingPositionModule.resetTradingEngineDataStructure() tradingPositionModule.updateCounters() tradingPositionModule.updateEnds() } @@ -202,9 +208,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingStrategyModule.openStrategy(j, situation.name, strategy.name) - /* Updating Episode Counters */ - tradingEngine.current.episode.episodeCounters.strategies.value++ - /* Initialize this */ tradingEngine.current.distanceToEvent.triggerOn.value = 1 @@ -1013,8 +1016,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM /* Close the Position */ tradingPositionModule.closePosition() - updatingEpisodeAssets() - updatingEpisodeStatistics() + tradingEpisodeModule.calculateResults() + tradingEpisodeModule.calculateStatistics() /* Close the Strategy */ tradingStrategyModule.closeStrategy('Position Closed') @@ -1023,118 +1026,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingEngine.current.distanceToEvent.closePosition.value = 1 tradingEngine.current.distanceToEvent.triggerOff.value = 1 - function updatingEpisodeAssets() { - - /* Updating Hits & Fails */ - if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeBaseAsset.hits.value++ - } else { - tradingEngine.current.episode.episodeBaseAsset.fails.value++ - } - if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeQuotedAsset.hits.value++ - } else { - tradingEngine.current.episode.episodeQuotedAsset.fails.value++ - } - - /* Updating Profit Loss */ - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = - tradingEngine.current.balance.baseAsset.value - - sessionParameters.sessionBaseAsset.config.initialBalance - - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = - tradingEngine.current.balance.quotedAsset.value - - sessionParameters.sessionQuotedAsset.config.initialBalance - - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10) - - /* Updating ROI */ - tradingEngine.current.episode.episodeBaseAsset.ROI.value = - (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value) / - sessionParameters.sessionBaseAsset.config.initialBalance - 1 - - tradingEngine.current.episode.episodeQuotedAsset.ROI.value = - (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value) / - sessionParameters.sessionQuotedAsset.config.initialBalance - 1 - - tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10) - - /* Updating Hit Ratio */ - tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = - tradingEngine.current.episode.episodeBaseAsset.hits.value / - tradingEngine.current.episode.episodeCounters.positions.value - - tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = - tradingEngine.current.episode.episodeQuotedAsset.hits.value / - tradingEngine.current.episode.episodeCounters.positions.value - - tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) - - /* Updating Anualized Rate Of Return */ - tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = - tradingEngine.current.episode.episodeBaseAsset.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 - - tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = - tradingEngine.current.episode.episodeQuotedAsset.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 - - tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) - - /* Updating Hit or Fail */ - if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Fail' - } - if (tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail' - } - } - - function updatingEpisodeStatistics() { - - /* Updating Profit Loss */ - tradingEngine.current.episode.episodeStatistics.profitLoss.value = - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - - tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) - - /* Updating ROI */ - tradingEngine.current.episode.episodeStatistics.ROI.value = - ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + - sessionParameters.sessionQuotedAsset.config.initialBalance + - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - ) / ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + - sessionParameters.sessionQuotedAsset.config.initialBalance - ) - 1 - - tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) - - /* Updating Anualized Rate Of Return */ - tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = - tradingEngine.current.episode.episodeStatistics.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 - - tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value, 10) - - /* Updating Hit or Fail */ - if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { - tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' - } else { - tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail' - } - } } } @@ -1229,7 +1120,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } } - function updateStatus() { + function resetTradingEngineDataStructure() { if (tradingEngine.current.strategyTriggerStage.status.value === 'Closed') { resetStage(tradingEngine.current.strategyTriggerStage.status) } diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index 5006e3eb6..3572dee68 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -7,7 +7,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng openStrategy: openStrategy, closeStrategy: closeStrategy, updateEnds: updateEnds, - updateStatus: updateStatus, + resetTradingEngineDataStructure: resetTradingEngineDataStructure, updateCounters: updateCounters, resetStrategy: resetStrategy, initialize: initialize, @@ -33,11 +33,13 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng tradingEngine.current.strategy.begin.value = tradingEngine.current.candle.begin.value tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value tradingEngine.current.strategy.beginRate.value = tradingEngine.current.candle.min.value - tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value tradingEngine.current.strategy.index.value = index tradingEngine.current.strategy.situationName.value = situationName tradingEngine.current.strategy.strategyName.value = strategyName + + /* Updating Episode Counters */ + tradingEngine.current.episode.episodeCounters.strategies.value++ } function closeStrategy(exitType) { @@ -58,11 +60,11 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng function updateEnds() { if (tradingEngine.current.strategy.status.value === 'Open') { tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.close.value } } - function updateStatus() { + function resetTradingEngineDataStructure() { if (tradingEngine.current.strategy.status.value === 'Closed') { resetStrategy() } From 83ffc6b24b336414fa65a96818edd3894c154b62 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 7 Aug 2020 19:41:13 +0200 Subject: [PATCH 030/112] pollishiing code --- LowFrequencyTrading/TradingEpisode.js | 5 ++--- LowFrequencyTrading/TradingPosition.js | 7 +------ LowFrequencyTrading/TradingSimulation.js | 15 ++++++++++++--- LowFrequencyTrading/TradingStages.js | 9 ++++----- LowFrequencyTrading/TradingStrategy.js | 7 +------ 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 194532833..850d7acda 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -5,7 +5,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { const MODULE_NAME = 'Trading Episode' let thisObject = { openEpisode: openEpisode, - closingEpisode: closingEpisode, + updateExitType: updateExitType, closeEpisode: closeEpisode, updateEnds: updateEnds, resetTradingEngineDataStructure: resetTradingEngineDataStructure, @@ -45,8 +45,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { tradingEngine.current.episode.beginRate.value = tradingEngine.current.candle.close.value } - function closingEpisode(exitType) { - tradingEngine.current.episode.status.value = 'Closing' + function updateExitType(exitType) { tradingEngine.current.episode.exitType.value = exitType } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 885292b4e..7a5b2ede1 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -15,7 +15,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng updateEnds: updateEnds, resetTradingEngineDataStructure: resetTradingEngineDataStructure, updateCounters: updateCounters, - resetPosition: resetPosition, initialize: initialize, finalize: finalize } @@ -80,10 +79,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position) } - function resetPosition() { - tradingEngine.current.position.initialize(tradingEngine.current.position) - } - function applyStopLossFormula(formulas, formulaId) { tradingEngine.current.position.stopLoss.value = formulas.get(formulaId) } @@ -230,7 +225,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function resetTradingEngineDataStructure() { if (tradingEngine.current.position.status.value === 'Closed') { - resetPosition() + tradingEngine.current.position.initialize(tradingEngine.current.position) } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 509b3ca87..1ae53ff62 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -157,14 +157,14 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (bot.STOP_SESSION === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') - closeEpisode('Session Stopped') + updateEpisode('Session Stopped') return } if (global.STOP_TASK_GRACEFULLY === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') - closeEpisode('Task Stopped') + updateEpisode('Task Stopped') return } @@ -182,7 +182,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } function closeEpisode(exitType) { - tradingEpisodeModule.closingEpisode(exitType) + tradingEpisodeModule.updateExitType(exitType) tradingEpisodeModule.closeEpisode() /* Add new records to the process output */ @@ -191,6 +191,15 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT afterLoop() } + function updateEpisode(exitType) { + tradingEpisodeModule.updateExitType(exitType) + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords() + + afterLoop() + } + function afterLoop() { tradingEngineModule.finalize() diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 8e7507047..206cc6836 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -14,7 +14,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM resetTradingEngineDataStructure: resetTradingEngineDataStructure, updateCounters: updateCounters, updateStatistics: updateStatistics, - resetStage: resetStage, initialize: initialize, finalize: finalize } @@ -1101,10 +1100,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } } - function resetStage(stage) { - stage.initialize(stage) - } - function updateEnds() { if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.candle.end.value @@ -1136,6 +1131,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM resetStage(tradingEngine.current.strategyManageStage) resetStage(tradingEngine.current.strategyCloseStage) } + + function resetStage(stage) { + stage.initialize(stage) + } } function updateCounters() { diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index 3572dee68..81e7d03b9 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -9,7 +9,6 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng updateEnds: updateEnds, resetTradingEngineDataStructure: resetTradingEngineDataStructure, updateCounters: updateCounters, - resetStrategy: resetStrategy, initialize: initialize, finalize: finalize } @@ -53,10 +52,6 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng tradingEngineModule.cloneValues(tradingEngine.current.strategy, tradingEngine.last.strategy) } - function resetStrategy() { - tradingEngine.current.strategy.initialize(tradingEngine.current.strategy) - } - function updateEnds() { if (tradingEngine.current.strategy.status.value === 'Open') { tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value @@ -66,7 +61,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng function resetTradingEngineDataStructure() { if (tradingEngine.current.strategy.status.value === 'Closed') { - resetStrategy() + tradingEngine.current.strategy.initialize(tradingEngine.current.strategy) } } From ce5277bfe4c53b7dad429e50e3d86e562a62da0a Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 7 Aug 2020 20:05:59 +0200 Subject: [PATCH 031/112] Update TradingStages.js --- LowFrequencyTrading/TradingStages.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 206cc6836..790c33339 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -1103,15 +1103,19 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function updateEnds() { if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategyTriggerStage.endRate.value = tradingEngine.current.candle.close.value } if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategyOpenStage.endRate.value = tradingEngine.current.candle.close.value } if (tradingEngine.current.strategyManageStage.status.value === 'Open') { tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategyManageStage.endRate.value = tradingEngine.current.candle.close.value } if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategyCloseStage.endRate.value = tradingEngine.current.candle.close.value } } From 6f7727164c209a79e7bd6223fdb7361fd4f92f58 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 8 Aug 2020 09:40:59 +0200 Subject: [PATCH 032/112] episode balance --- LowFrequencyTrading/TradingEngine.js | 7 +++-- LowFrequencyTrading/TradingEpisode.js | 34 ++++++++++++++++------ LowFrequencyTrading/TradingExecution.js | 36 ++++++++++++------------ LowFrequencyTrading/TradingPosition.js | 8 +++--- LowFrequencyTrading/TradingSimulation.js | 8 +++--- LowFrequencyTrading/TradingStages.js | 12 ++------ LowFrequencyTrading/TradingStrategy.js | 10 +++++-- 7 files changed, 66 insertions(+), 49 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 981f71183..435a53619 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -20,15 +20,16 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { return thisObject function initialize() { - /* Here we will go through all the nodes in the Trading Engine hiriarchy and apply the initial value to the value property when needed */ tradingEngine = bot.simulationState.tradingEngine sessionParameters = bot.SESSION.parameters if (bot.FIRST_EXECUTION === true) { + /* + Here we will go through all the nodes in the Trading Engine hiriarchy and + apply the initial value to the value property when needed + */ initializeNode(tradingEngine) - tradingEngine.current.balance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance - tradingEngine.current.balance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance } } diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 850d7acda..061b4dafe 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -36,13 +36,27 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { } 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. + */ + if (bot.FIRST_EXECUTION === true) { + /* Getting the begin Balance from the session configuration */ + tradingEngine.current.episode.beginBalance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.beginBalance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance - /* Recording the opening at the Trading Engine Data Structure */ - tradingEngine.current.episode.status.value = 'Open' - tradingEngine.current.episode.serialNumber.value = 1 - tradingEngine.current.episode.identifier.value = global.UNIQUE_ID() - tradingEngine.current.episode.begin.value = tradingEngine.current.candle.begin.value - tradingEngine.current.episode.beginRate.value = tradingEngine.current.candle.close.value + /* The current balance is also the begin balance, that is how this starts. */ + tradingEngine.current.episode.balance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.balance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance + + /* Recording the opening at the Trading Engine Data Structure */ + tradingEngine.current.episode.status.value = 'Open' + tradingEngine.current.episode.serialNumber.value = 1 + tradingEngine.current.episode.identifier.value = global.UNIQUE_ID() + tradingEngine.current.episode.begin.value = tradingEngine.current.candle.begin.value + tradingEngine.current.episode.beginRate.value = tradingEngine.current.candle.close.value + } } function updateExitType(exitType) { @@ -53,6 +67,8 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { tradingEngine.current.episode.status.value = 'Closed' tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.episode.endBalance.baseAsset.value = tradingEngine.current.episode.balance.baseAsset.value + tradingEngine.current.episode.endBalance.quotedAsset.value = tradingEngine.current.episode.balance.quotedAsset.value } function resetEpisode() { @@ -63,6 +79,8 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { if (tradingEngine.current.episode.status.value === 'Open') { tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.episode.endBalance.baseAsset.value = tradingEngine.current.episode.balance.baseAsset.value + tradingEngine.current.episode.endBalance.quotedAsset.value = tradingEngine.current.episode.balance.quotedAsset.value } } @@ -141,11 +159,11 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { /* Updating Profit Loss */ tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = - tradingEngine.current.balance.baseAsset.value - + tradingEngine.current.episode.balance.baseAsset.value - sessionParameters.sessionBaseAsset.config.initialBalance tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = - tradingEngine.current.balance.quotedAsset.value - + tradingEngine.current.episode.balance.quotedAsset.value - sessionParameters.sessionQuotedAsset.config.initialBalance tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index ec2bada64..173c818cc 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -657,24 +657,24 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value - + tradingEngine.current.episode.balance.baseAsset.value = + tradingEngine.current.episode.balance.baseAsset.value - previousBaseAssetSizeFilled /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value + + tradingEngine.current.episode.balance.baseAsset.value = + tradingEngine.current.episode.balance.baseAsset.value + tradingEngineOrder.orderBaseAsset.sizeFilled.value /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value + + tradingEngine.current.episode.balance.quotedAsset.value = + tradingEngine.current.episode.balance.quotedAsset.value + previousQuotedAssetSizeFilled + previousQuotedAssetFeesPaid /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value - + tradingEngine.current.episode.balance.quotedAsset.value = + tradingEngine.current.episode.balance.quotedAsset.value - tradingEngineOrder.orderQuotedAsset.sizeFilled.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value break @@ -682,31 +682,31 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value + + tradingEngine.current.episode.balance.baseAsset.value = + tradingEngine.current.episode.balance.baseAsset.value + previousBaseAssetSizeFilled + previousBaseAssetFeesPaid /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.balance.baseAsset.value = - tradingEngine.current.balance.baseAsset.value - + tradingEngine.current.episode.balance.baseAsset.value = + tradingEngine.current.episode.balance.baseAsset.value - tradingEngineOrder.orderBaseAsset.sizeFilled.value - tradingEngineOrder.orderBaseAsset.feesPaid.value /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value - + tradingEngine.current.episode.balance.quotedAsset.value = + tradingEngine.current.episode.balance.quotedAsset.value - previousQuotedAssetSizeFilled /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.balance.quotedAsset.value = - tradingEngine.current.balance.quotedAsset.value + + tradingEngine.current.episode.balance.quotedAsset.value = + tradingEngine.current.episode.balance.quotedAsset.value + tradingEngineOrder.orderQuotedAsset.sizeFilled.value break } } - tradingEngine.current.balance.baseAsset.value = global.PRECISE(tradingEngine.current.balance.baseAsset.value, 10) - tradingEngine.current.balance.quotedAsset.value = global.PRECISE(tradingEngine.current.balance.quotedAsset.value, 10) + tradingEngine.current.episode.balance.baseAsset.value = global.PRECISE(tradingEngine.current.episode.balance.baseAsset.value, 10) + tradingEngine.current.episode.balance.quotedAsset.value = global.PRECISE(tradingEngine.current.episode.balance.quotedAsset.value, 10) } } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 7a5b2ede1..2c80908b1 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -240,8 +240,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* Profit Loss Calculation */ tradingEngine.current.position.positionStatistics.profitLoss.value = ( - tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value + - tradingEngine.current.balance.quotedAsset.value + tradingEngine.current.episode.balance.baseAsset.value * tradingEngine.current.position.endRate.value + + tradingEngine.current.episode.balance.quotedAsset.value ) - ( tradingEngine.previous.balance.baseAsset.value * tradingEngine.current.position.beginRate.value + @@ -279,11 +279,11 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function updateResults() { /* Profit Loss Calculation */ tradingEngine.current.position.positionBaseAsset.profitLoss.value = - tradingEngine.current.balance.baseAsset.value - + tradingEngine.current.episode.balance.baseAsset.value - tradingEngine.previous.balance.baseAsset.value tradingEngine.current.position.positionQuotedAsset.profitLoss.value = - tradingEngine.current.balance.quotedAsset.value - + tradingEngine.current.episode.balance.quotedAsset.value - tradingEngine.previous.balance.quotedAsset.value tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 1ae53ff62..092c77b90 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -390,7 +390,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let stopRunningDate = (new Date(tradingEngine.current.candle.begin.value)).toLocaleString() if (sessionParameters.sessionBaseAsset.config.minimumBalance !== undefined) { - if (tradingEngine.current.balance.baseAsset.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) { + if (tradingEngine.current.episode.balance.baseAsset.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) { const errorMessage = 'Min Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } @@ -399,7 +399,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (sessionParameters.sessionBaseAsset.config.maximumBalance !== undefined) { - if (tradingEngine.current.balance.baseAsset.value >= sessionParameters.sessionBaseAsset.config.maximumBalance) { + if (tradingEngine.current.episode.balance.baseAsset.value >= sessionParameters.sessionBaseAsset.config.maximumBalance) { const errorMessage = 'Max Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } @@ -408,7 +408,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (sessionParameters.sessionQuotedAsset.config.minimumBalance !== undefined) { - if (tradingEngine.current.balance.quotedAsset.value <= sessionParameters.sessionQuotedAsset.config.minimumBalance) { + if (tradingEngine.current.episode.balance.quotedAsset.value <= sessionParameters.sessionQuotedAsset.config.minimumBalance) { const errorMessage = 'Min Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } @@ -417,7 +417,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (sessionParameters.sessionQuotedAsset.config.maximumBalance !== undefined) { - if (tradingEngine.current.balance.quotedAsset.value >= sessionParameters.sessionQuotedAsset.config.maximumBalance) { + if (tradingEngine.current.episode.balance.quotedAsset.value >= sessionParameters.sessionQuotedAsset.config.maximumBalance) { const errorMessage = 'Max Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 790c33339..350865b9d 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -85,16 +85,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function mantain() { mantainPositions() - mantainStrategies() + tradingStrategyModule.mantain() mantainStages() mantainOrders() - function mantainStrategies() { - tradingStrategyModule.resetTradingEngineDataStructure() - tradingStrategyModule.updateCounters() - tradingStrategyModule.updateEnds() - } - function mantainStages() { resetTradingEngineDataStructure() updateCounters() @@ -342,8 +336,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.evalFormulas(stageNode, 'Initial Definition') /* Remember the balance we had before taking the position to later calculate profit or loss */ - tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.episode.balance.baseAsset.value + tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.episode.balance.quotedAsset.value /* Entry Position size and rate */ tradingSystem.evalFormulas(stageNode, 'Initial Targets') diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index 81e7d03b9..4f48686b3 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -4,11 +4,9 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng */ const MODULE_NAME = 'Trading Strategy' let thisObject = { + mantain: mantain, openStrategy: openStrategy, closeStrategy: closeStrategy, - updateEnds: updateEnds, - resetTradingEngineDataStructure: resetTradingEngineDataStructure, - updateCounters: updateCounters, initialize: initialize, finalize: finalize } @@ -25,6 +23,12 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng tradingEngine = undefined } + function mantain() { + resetTradingEngineDataStructure() + updateCounters() + updateEnds() + } + function openStrategy(index, situationName, strategyName) { tradingEngine.current.strategy.status.value = 'Open' tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value From 61a26c7158f07adab0fc3ed8f2b340779cf7ee8d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 8 Aug 2020 11:00:24 +0200 Subject: [PATCH 033/112] mantain standarized --- LowFrequencyTrading/TradingEngine.js | 6 +++++- LowFrequencyTrading/TradingEpisode.js | 12 ++++++++---- LowFrequencyTrading/TradingPosition.js | 7 +++++++ LowFrequencyTrading/TradingSimulation.js | 8 ++++---- LowFrequencyTrading/TradingStages.js | 8 +------- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 435a53619..965bf5b83 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -5,10 +5,10 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { */ const MODULE_NAME = 'Trading Engine' let thisObject = { + mantain: mantain, getNodeById: getNodeById, cloneValues: cloneValues, setCurrentCandle: setCurrentCandle, - updateDistanceToEventsCounters: updateDistanceToEventsCounters, initialize: initialize, finalize: finalize } @@ -39,6 +39,10 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { nodesMap = undefined } + function mantain() { + updateDistanceToEventsCounters() + } + function getNodeById(NodeId) { return nodesMap.get(NodeId) } diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 061b4dafe..cff7dfdd4 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -4,13 +4,10 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { */ const MODULE_NAME = 'Trading Episode' let thisObject = { + mantain: mantain, openEpisode: openEpisode, updateExitType: updateExitType, closeEpisode: closeEpisode, - updateEnds: updateEnds, - resetTradingEngineDataStructure: resetTradingEngineDataStructure, - updateCounters: updateCounters, - updateStatistics: updateStatistics, calculateResults: calculateResults, calculateStatistics: calculateStatistics, initialize: initialize, @@ -35,6 +32,13 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { sessionParameters = undefined } + function mantain() { + resetTradingEngineDataStructure() + updateCounters() + updateStatistics() + updateEnds() + } + function openEpisode() { /* This function is called each time the simulation starts. That does not mean the Episode diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 2c80908b1..acfe51f6e 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -4,6 +4,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng */ const MODULE_NAME = 'Trading Position' let thisObject = { + mantain: mantain, openPosition: openPosition, closingPosition: closingPosition, closePosition: closePosition, @@ -37,6 +38,12 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng sessionParameters = undefined } + function mantain() { + resetTradingEngineDataStructure() + updateCounters() + updateEnds() + } + function openPosition(situationName) { /* Recording the opening at the Trading Engine Data Structure */ diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 092c77b90..35da754cd 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -140,11 +140,11 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT /* Do the stuff needed previous to the run */ tradingSystemModule.mantain() - /* Episode Counters and Statistics */ - tradingEpisodeModule.updateCounters() - tradingEpisodeModule.updateStatistics() + /* Episode Counters and Statistics update */ + tradingEpisodeModule.mantain() - tradingEngineModule.updateDistanceToEventsCounters() + /* Run your maintaince procedure */ + tradingEngineModule.mantain() /* Run one cycle of the Trading System*/ await tradingSystemModule.run() diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 350865b9d..8487b3d83 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -84,7 +84,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } function mantain() { - mantainPositions() + tradingPositionModule.mantain() tradingStrategyModule.mantain() mantainStages() mantainOrders() @@ -95,12 +95,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM updateEnds() } - function mantainPositions() { - tradingPositionModule.resetTradingEngineDataStructure() - tradingPositionModule.updateCounters() - tradingPositionModule.updateEnds() - } - function mantainOrders() { /* Reset All Exchange Orders that are on Closed status, to their initial value */ if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return } From 8eace36d961531f9e567d1b7964844a593400500 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 8 Aug 2020 12:26:04 +0200 Subject: [PATCH 034/112] Balance Reorganized --- LowFrequencyTrading/TradingEngine.js | 2 -- LowFrequencyTrading/TradingEpisode.js | 20 ++++++------- LowFrequencyTrading/TradingExecution.js | 36 ++++++++++++------------ LowFrequencyTrading/TradingPosition.js | 26 ++++++++++------- LowFrequencyTrading/TradingSimulation.js | 8 +++--- LowFrequencyTrading/TradingStages.js | 4 +-- 6 files changed, 50 insertions(+), 46 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 965bf5b83..7916f7b46 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -48,8 +48,6 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } function setCurrentCandle(candle) { - cloneValues(tradingEngine.current.candle, tradingEngine.previous.candle) - tradingEngine.current.candle.begin.value = candle.begin tradingEngine.current.candle.end.value = candle.end tradingEngine.current.candle.open.value = candle.open diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index cff7dfdd4..b8c21f513 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -47,12 +47,12 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { */ if (bot.FIRST_EXECUTION === true) { /* Getting the begin Balance from the session configuration */ - tradingEngine.current.episode.beginBalance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance - tradingEngine.current.episode.beginBalance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value = sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value = sessionParameters.sessionQuotedAsset.config.initialBalance /* The current balance is also the begin balance, that is how this starts. */ - tradingEngine.current.episode.balance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance - tradingEngine.current.episode.balance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.balance.value = sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.balance.value = sessionParameters.sessionQuotedAsset.config.initialBalance /* Recording the opening at the Trading Engine Data Structure */ tradingEngine.current.episode.status.value = 'Open' @@ -71,8 +71,8 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { tradingEngine.current.episode.status.value = 'Closed' tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value - tradingEngine.current.episode.endBalance.baseAsset.value = tradingEngine.current.episode.balance.baseAsset.value - tradingEngine.current.episode.endBalance.quotedAsset.value = tradingEngine.current.episode.balance.quotedAsset.value + tradingEngine.current.episode.episodeBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngine.current.episode.episodeQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value } function resetEpisode() { @@ -83,8 +83,8 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { if (tradingEngine.current.episode.status.value === 'Open') { tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value - tradingEngine.current.episode.endBalance.baseAsset.value = tradingEngine.current.episode.balance.baseAsset.value - tradingEngine.current.episode.endBalance.quotedAsset.value = tradingEngine.current.episode.balance.quotedAsset.value + tradingEngine.current.episode.episodeBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngine.current.episode.episodeQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value } } @@ -163,11 +163,11 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { /* Updating Profit Loss */ tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = - tradingEngine.current.episode.balance.baseAsset.value - + tradingEngine.current.episode.episodeBaseAsset.balance.value - sessionParameters.sessionBaseAsset.config.initialBalance tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = - tradingEngine.current.episode.balance.quotedAsset.value - + tradingEngine.current.episode.episodeQuotedAsset.balance.value - sessionParameters.sessionQuotedAsset.config.initialBalance tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 173c818cc..d58869724 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -657,24 +657,24 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.episode.balance.baseAsset.value = - tradingEngine.current.episode.balance.baseAsset.value - + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - previousBaseAssetSizeFilled /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.episode.balance.baseAsset.value = - tradingEngine.current.episode.balance.baseAsset.value + + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngineOrder.orderBaseAsset.sizeFilled.value /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.episode.balance.quotedAsset.value = - tradingEngine.current.episode.balance.quotedAsset.value + + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value + previousQuotedAssetSizeFilled + previousQuotedAssetFeesPaid /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.episode.balance.quotedAsset.value = - tradingEngine.current.episode.balance.quotedAsset.value - + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - tradingEngineOrder.orderQuotedAsset.sizeFilled.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value break @@ -682,31 +682,31 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.episode.balance.baseAsset.value = - tradingEngine.current.episode.balance.baseAsset.value + + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value + previousBaseAssetSizeFilled + previousBaseAssetFeesPaid /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.episode.balance.baseAsset.value = - tradingEngine.current.episode.balance.baseAsset.value - + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - tradingEngineOrder.orderBaseAsset.sizeFilled.value - tradingEngineOrder.orderBaseAsset.feesPaid.value /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.episode.balance.quotedAsset.value = - tradingEngine.current.episode.balance.quotedAsset.value - + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - previousQuotedAssetSizeFilled /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.episode.balance.quotedAsset.value = - tradingEngine.current.episode.balance.quotedAsset.value + + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value + tradingEngineOrder.orderQuotedAsset.sizeFilled.value break } } - tradingEngine.current.episode.balance.baseAsset.value = global.PRECISE(tradingEngine.current.episode.balance.baseAsset.value, 10) - tradingEngine.current.episode.balance.quotedAsset.value = global.PRECISE(tradingEngine.current.episode.balance.quotedAsset.value, 10) + tradingEngine.current.episode.episodeBaseAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.balance.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.balance.value, 10) } } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index acfe51f6e..dad19389b 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -52,6 +52,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.identifier.value = global.UNIQUE_ID() tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value tradingEngine.current.position.beginRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.position.positionBaseAsset.beginBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngine.current.position.positionQuotedAsset.beginBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value tradingEngine.current.position.situationName.value = situationName /* Initializing Stop and Take Profit Stage / Phase */ @@ -76,6 +78,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.status.value = 'Closed' tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value tradingEngine.current.position.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value /* Position Statistics & Results */ updateStatistics() @@ -227,6 +231,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng if (tradingEngine.current.position.status.value === 'Open') { tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value tradingEngine.current.position.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value } } @@ -247,12 +253,12 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* Profit Loss Calculation */ tradingEngine.current.position.positionStatistics.profitLoss.value = ( - tradingEngine.current.episode.balance.baseAsset.value * tradingEngine.current.position.endRate.value + - tradingEngine.current.episode.balance.quotedAsset.value + tradingEngine.current.episode.episodeBaseAsset.balance.value * tradingEngine.current.position.endRate.value + + tradingEngine.current.episode.episodeQuotedAsset.balance.value ) - ( - tradingEngine.previous.balance.baseAsset.value * tradingEngine.current.position.beginRate.value + - tradingEngine.previous.balance.quotedAsset.value + tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + + tradingEngine.current.position.positionQuotedAsset.beginBalance ) tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) @@ -263,8 +269,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng ) * 100 / ( - tradingEngine.previous.balance.baseAsset.value * tradingEngine.current.position.beginRate.value + - tradingEngine.previous.balance.quotedAsset.value + tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + + tradingEngine.current.position.positionQuotedAsset.beginBalance ) tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) @@ -286,12 +292,12 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function updateResults() { /* Profit Loss Calculation */ tradingEngine.current.position.positionBaseAsset.profitLoss.value = - tradingEngine.current.episode.balance.baseAsset.value - - tradingEngine.previous.balance.baseAsset.value + tradingEngine.current.episode.episodeBaseAsset.balance.value - + tradingEngine.current.position.positionBaseAsset.beginBalance tradingEngine.current.position.positionQuotedAsset.profitLoss.value = - tradingEngine.current.episode.balance.quotedAsset.value - - tradingEngine.previous.balance.quotedAsset.value + tradingEngine.current.episode.episodeQuotedAsset.balance.value - + tradingEngine.current.position.positionQuotedAsset.beginBalance tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 35da754cd..67ac1ef79 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -390,7 +390,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let stopRunningDate = (new Date(tradingEngine.current.candle.begin.value)).toLocaleString() if (sessionParameters.sessionBaseAsset.config.minimumBalance !== undefined) { - if (tradingEngine.current.episode.balance.baseAsset.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) { + if (tradingEngine.current.episode.episodeBaseAsset.balance.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) { const errorMessage = 'Min Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } @@ -399,7 +399,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (sessionParameters.sessionBaseAsset.config.maximumBalance !== undefined) { - if (tradingEngine.current.episode.balance.baseAsset.value >= sessionParameters.sessionBaseAsset.config.maximumBalance) { + if (tradingEngine.current.episode.episodeBaseAsset.balance.value >= sessionParameters.sessionBaseAsset.config.maximumBalance) { const errorMessage = 'Max Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } @@ -408,7 +408,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (sessionParameters.sessionQuotedAsset.config.minimumBalance !== undefined) { - if (tradingEngine.current.episode.balance.quotedAsset.value <= sessionParameters.sessionQuotedAsset.config.minimumBalance) { + if (tradingEngine.current.episode.episodeQuotedAsset.balance.value <= sessionParameters.sessionQuotedAsset.config.minimumBalance) { const errorMessage = 'Min Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } @@ -417,7 +417,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (sessionParameters.sessionQuotedAsset.config.maximumBalance !== undefined) { - if (tradingEngine.current.episode.balance.quotedAsset.value >= sessionParameters.sessionQuotedAsset.config.maximumBalance) { + if (tradingEngine.current.episode.episodeQuotedAsset.balance.value >= sessionParameters.sessionQuotedAsset.config.maximumBalance) { const errorMessage = 'Max Balance reached @ ' + stopRunningDate tradingSystem.errors.push([tradingSystem.id, errorMessage]) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) } diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 8487b3d83..a43bd3af8 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -330,8 +330,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.evalFormulas(stageNode, 'Initial Definition') /* Remember the balance we had before taking the position to later calculate profit or loss */ - tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.episode.balance.baseAsset.value - tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.episode.balance.quotedAsset.value + tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value /* Entry Position size and rate */ tradingSystem.evalFormulas(stageNode, 'Initial Targets') From 7166eac3f3147795126f1c44b7d15254ace659cd Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 8 Aug 2020 12:52:06 +0200 Subject: [PATCH 035/112] candle and distance to events --- LowFrequencyTrading/TradingEngine.js | 48 ++++++++++++------------ LowFrequencyTrading/TradingEpisode.js | 20 +++++----- LowFrequencyTrading/TradingExecution.js | 22 +++++------ LowFrequencyTrading/TradingPosition.js | 14 +++---- LowFrequencyTrading/TradingRecords.js | 6 +-- LowFrequencyTrading/TradingSimulation.js | 42 ++++++++++----------- LowFrequencyTrading/TradingStages.js | 42 ++++++++++----------- LowFrequencyTrading/TradingStrategy.js | 14 +++---- 8 files changed, 104 insertions(+), 104 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 7916f7b46..3c3255225 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -48,12 +48,12 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } function setCurrentCandle(candle) { - tradingEngine.current.candle.begin.value = candle.begin - tradingEngine.current.candle.end.value = candle.end - tradingEngine.current.candle.open.value = candle.open - tradingEngine.current.candle.close.value = candle.close - tradingEngine.current.candle.min.value = candle.min - tradingEngine.current.candle.max.value = candle.max + tradingEngine.current.episode.candle.begin.value = candle.begin + tradingEngine.current.episode.candle.end.value = candle.end + tradingEngine.current.episode.candle.open.value = candle.open + tradingEngine.current.episode.candle.close.value = candle.close + tradingEngine.current.episode.candle.min.value = candle.min + tradingEngine.current.episode.candle.max.value = candle.max } function cloneValues(originNode, destinationNode) { @@ -146,57 +146,57 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { function updateDistanceToEventsCounters() { /* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */ if ( - tradingEngine.current.distanceToEvent.triggerOn.value > 0 + tradingEngine.current.episode.distanceToEvent.triggerOn.value > 0 ) { - tradingEngine.current.distanceToEvent.triggerOn.value++ + tradingEngine.current.episode.distanceToEvent.triggerOn.value++ } if ( - tradingEngine.current.distanceToEvent.triggerOff.value > 0 + tradingEngine.current.episode.distanceToEvent.triggerOff.value > 0 ) { - tradingEngine.current.distanceToEvent.triggerOff.value++ + tradingEngine.current.episode.distanceToEvent.triggerOff.value++ } if ( - tradingEngine.current.distanceToEvent.takePosition.value > 0 + tradingEngine.current.episode.distanceToEvent.takePosition.value > 0 ) { - tradingEngine.current.distanceToEvent.takePosition.value++ + tradingEngine.current.episode.distanceToEvent.takePosition.value++ } if ( - tradingEngine.current.distanceToEvent.closePosition.value > 0 + tradingEngine.current.episode.distanceToEvent.closePosition.value > 0 ) { - tradingEngine.current.distanceToEvent.closePosition.value++ + tradingEngine.current.episode.distanceToEvent.closePosition.value++ } if ( - tradingEngine.current.distanceToEvent.nextPhase.value > 0 + tradingEngine.current.episode.distanceToEvent.nextPhase.value > 0 ) { - tradingEngine.current.distanceToEvent.nextPhase.value++ + tradingEngine.current.episode.distanceToEvent.nextPhase.value++ } if ( - tradingEngine.current.distanceToEvent.moveToPhase.value > 0 + tradingEngine.current.episode.distanceToEvent.moveToPhase.value > 0 ) { - tradingEngine.current.distanceToEvent.moveToPhase.value++ + tradingEngine.current.episode.distanceToEvent.moveToPhase.value++ } if ( - tradingEngine.current.distanceToEvent.createOrder.value > 0 + tradingEngine.current.episode.distanceToEvent.createOrder.value > 0 ) { - tradingEngine.current.distanceToEvent.createOrder.value++ + tradingEngine.current.episode.distanceToEvent.createOrder.value++ } if ( - tradingEngine.current.distanceToEvent.cancelOrder.value > 0 + tradingEngine.current.episode.distanceToEvent.cancelOrder.value > 0 ) { - tradingEngine.current.distanceToEvent.cancelOrder.value++ + tradingEngine.current.episode.distanceToEvent.cancelOrder.value++ } if ( - tradingEngine.current.distanceToEvent.closeOrder.value > 0 + tradingEngine.current.episode.distanceToEvent.closeOrder.value > 0 ) { - tradingEngine.current.distanceToEvent.closeOrder.value++ + tradingEngine.current.episode.distanceToEvent.closeOrder.value++ } } diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index b8c21f513..5dc450d8c 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -58,8 +58,8 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { tradingEngine.current.episode.status.value = 'Open' tradingEngine.current.episode.serialNumber.value = 1 tradingEngine.current.episode.identifier.value = global.UNIQUE_ID() - tradingEngine.current.episode.begin.value = tradingEngine.current.candle.begin.value - tradingEngine.current.episode.beginRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.episode.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngine.current.episode.beginRate.value = tradingEngine.current.episode.candle.close.value } } @@ -69,8 +69,8 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { function closeEpisode() { tradingEngine.current.episode.status.value = 'Closed' - tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.episode.endRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.episode.episodeBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.episode.episodeQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value } @@ -81,8 +81,8 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { function updateEnds() { if (tradingEngine.current.episode.status.value === 'Open') { - tradingEngine.current.episode.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.episode.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.episode.endRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.episode.episodeBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.episode.episodeQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value } @@ -113,7 +113,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { /* Updating Profit Loss */ tradingEngine.current.episode.episodeStatistics.profitLoss.value = - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) @@ -121,12 +121,12 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { /* Updating ROI */ tradingEngine.current.episode.episodeStatistics.ROI.value = ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value + + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value ) / ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value + + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + sessionParameters.sessionQuotedAsset.config.initialBalance ) - 1 diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index d58869724..131bb6713 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -135,7 +135,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } case 'Open': { /* Update this order properties */ - tradingEngineOrder.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value tradingEngineOrder.orderCounters.periods.value++ tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS @@ -191,13 +191,13 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngine.current.episode.episodeCounters.orders.value++ /* Initialize this */ - tradingEngine.current.distanceToEvent.createOrder.value = 1 + tradingEngine.current.episode.distanceToEvent.createOrder.value = 1 /* Create Order Procedure */ tradingEngineOrder.status.value = 'Open' tradingEngineOrder.identifier.value = global.UNIQUE_ID() - tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value - tradingEngineOrder.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value tradingEngineOrder.orderName.value = tradingSystemOrder.name tradingEngineOrder.algorithmName.value = executionAlgorithm.name @@ -205,7 +205,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE function calculateOrderRate() { /* By default this is the order rate and it is the rate that applies to Market Orders */ - tradingEngineOrder.rate.value = tradingEngine.current.candle.close.value + tradingEngineOrder.rate.value = tradingEngine.current.episode.candle.close.value /* Optional Rate Definition */ if (tradingSystemOrder.orderRate !== undefined) { @@ -458,7 +458,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.exitType.value = 'Filled' /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 } } } @@ -515,7 +515,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.exitType.value = 'Filled' /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 } if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { @@ -524,7 +524,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.exitType.value = 'Closed at the Exchange' /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 } if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { @@ -533,7 +533,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 } syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) @@ -734,7 +734,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.exitType.value = exitType /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 recalculateStageSize(tradingEngineOrder) } @@ -765,7 +765,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngineOrder.exitType.value = exitType /* Initialize this */ - tradingEngine.current.distanceToEvent.closeOrder.value = 1 + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 /* Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index dad19389b..2666b1b80 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -50,8 +50,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.status.value = 'Open' tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value tradingEngine.current.position.identifier.value = global.UNIQUE_ID() - tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value - tradingEngine.current.position.beginRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.position.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngine.current.position.beginRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.position.positionBaseAsset.beginBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.beginBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value tradingEngine.current.position.situationName.value = situationName @@ -66,7 +66,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.episode.episodeCounters.positions.value++ /* Inicializing this counter */ - tradingEngine.current.distanceToEvent.takePosition.value = 1 + tradingEngine.current.episode.distanceToEvent.takePosition.value = 1 } function closingPosition(exitType) { @@ -76,8 +76,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function closePosition() { tradingEngine.current.position.status.value = 'Closed' - tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.position.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.position.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value @@ -229,8 +229,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function updateEnds() { if (tradingEngine.current.position.status.value === 'Open') { - tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.position.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.position.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value } diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index 5402bea7f..d80bac553 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -219,8 +219,8 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { 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(tradingEngine.current.candle.end.value) - let nextDay = new Date(tradingEngine.current.candle.end.value + 1) + let currentDay = new Date(tradingEngine.current.episode.candle.end.value) + let nextDay = new Date(tradingEngine.current.episode.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. @@ -302,7 +302,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { let recordProperty = product.record.properties[j] if (recordProperty.config.codeName === 'end') { let end = record[j] - if (end >= tradingEngine.current.candle.end.value) { + if (end >= tradingEngine.current.episode.candle.end.value) { outputFile.splice(i, 1) pruneOutputFile(product, outputFile) return diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 67ac1ef79..970ddecc7 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -69,10 +69,10 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let diff = lastBegin - firstBegin let amount = diff / sessionParameters.timeFrame.config.value - tradingEngine.current.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle - if (tradingEngine.current.candle.index.value < 0 || tradingEngine.current.candle.index.value > candles.length - 1) { + tradingEngine.current.episode.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle + if (tradingEngine.current.episode.candle.index.value < 0 || tradingEngine.current.episode.candle.index.value > candles.length - 1) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.') - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.candle.index.value = ' + tradingEngine.current.candle.index.value) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.episode.candle.index.value = ' + tradingEngine.current.episode.candle.index.value) logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin) logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin) logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff) @@ -88,14 +88,14 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let diff = targetEnd - firstEnd let amount = diff / sessionParameters.timeFrame.config.value - tradingEngine.current.candle.index.value = Math.trunc(amount) - if (tradingEngine.current.candle.index.value < 0) { tradingEngine.current.candle.index.value = 0 } - if (tradingEngine.current.candle.index.value > candles.length - 1) { + tradingEngine.current.episode.candle.index.value = Math.trunc(amount) + if (tradingEngine.current.episode.candle.index.value < 0) { tradingEngine.current.episode.candle.index.value = 0 } + if (tradingEngine.current.episode.candle.index.value > 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. */ - tradingEngine.current.candle.index.value = candles.length - 1 + tradingEngine.current.episode.candle.index.value = candles.length - 1 } } @@ -103,8 +103,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } async function loop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.candle.index.value) } - let candle = candles[tradingEngine.current.candle.index.value] // This is the current candle the Simulation is working at. + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.episode.candle.index.value) } + let candle = candles[tradingEngine.current.episode.candle.index.value] // This is the current candle the Simulation is working at. if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toLocaleString()) } if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) } @@ -168,12 +168,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT return } - if (tradingEngine.current.candle.index.value + 1 < candles.length) { + if (tradingEngine.current.episode.candle.index.value + 1 < candles.length) { /* Add new records to the process output */ tradingRecordsModule.appendRecords() - tradingEngine.current.candle.index.value++ + tradingEngine.current.episode.candle.index.value++ setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. } else { @@ -217,11 +217,11 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT function heartBeat() { /* We will produce a simulation level heartbeat in order to inform the user this is running. */ - heartBeatDate = new Date(Math.trunc(tradingEngine.current.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) + heartBeatDate = new Date(Math.trunc(tradingEngine.current.episode.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) if (heartBeatDate.valueOf() !== previousHeartBeatDate) { let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.candle.index.value + ' @ ' + processingDate) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.episode.candle.index.value + ' @ ' + processingDate) } /* Telling the world we are alive and doing well */ let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime) @@ -314,15 +314,15 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT for (let i = 0; i < pArray.length; i++) { element = pArray[i] - if (tradingEngine.current.candle.end.value === element.end) { // when there is an exact match at the end we take that element + if (tradingEngine.current.episode.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 > tradingEngine.current.candle.end.value + element.end > tradingEngine.current.episode.candle.end.value ) { let previousElement = pArray[i - 1] - if (previousElement.end < tradingEngine.current.candle.end.value) { + if (previousElement.end < tradingEngine.current.episode.candle.end.value) { return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element. } else { return @@ -345,7 +345,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT */ if (bot.processingDailyFiles) { // We are processing Daily Files let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value - if (tradingEngine.current.candle.index.value === candles.length - 1) { + if (tradingEngine.current.episode.candle.index.value === candles.length - 1) { if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } return false @@ -356,7 +356,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } } else { // We are processing Market Files - if (tradingEngine.current.candle.index.value === candles.length - 1) { + if (tradingEngine.current.episode.candle.index.value === candles.length - 1) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } return false } @@ -366,11 +366,11 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT function checkInitialAndFinalDatetime() { /* Here we check that the current candle is not outside of user-defined time range at the session parameters.*/ - if (tradingEngine.current.candle.end.value < sessionParameters.timeRange.config.initialDatetime) { + if (tradingEngine.current.episode.candle.end.value < sessionParameters.timeRange.config.initialDatetime) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') } return false } - if (tradingEngine.current.candle.begin.value > sessionParameters.timeRange.config.finalDatetime) { + if (tradingEngine.current.episode.candle.begin.value > sessionParameters.timeRange.config.finalDatetime) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') } return false } @@ -387,7 +387,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT because there the balances have shifted from their resting position. */ - let stopRunningDate = (new Date(tradingEngine.current.candle.begin.value)).toLocaleString() + let stopRunningDate = (new Date(tradingEngine.current.episode.candle.begin.value)).toLocaleString() if (sessionParameters.sessionBaseAsset.config.minimumBalance !== undefined) { if (tradingEngine.current.episode.episodeBaseAsset.balance.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) { diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index a43bd3af8..640feae4d 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -196,7 +196,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingStrategyModule.openStrategy(j, situation.name, strategy.name) /* Initialize this */ - tradingEngine.current.distanceToEvent.triggerOn.value = 1 + tradingEngine.current.episode.distanceToEvent.triggerOn.value = 1 announcementsModule.makeAnnoucements(triggerStage.triggerOn) announcementsModule.makeAnnoucements(triggerStage) @@ -243,7 +243,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.highlights.push(triggerStage.triggerOff.id) tradingSystem.highlights.push(triggerStage.id) - tradingEngine.current.distanceToEvent.triggerOff.value = 1 + tradingEngine.current.episode.distanceToEvent.triggerOff.value = 1 announcementsModule.makeAnnoucements(triggerStage.triggerOff) changeStageStatus('Trigger Stage', 'Closed') tradingStrategyModule.closeStrategy('Trigger Off') @@ -826,11 +826,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( ( tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Above' && - tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value + tradingEngine.current.episode.candle.max.value >= tradingEngine.current.position.stopLoss.value ) || ( tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Below' && - tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value + tradingEngine.current.episode.candle.min.value <= tradingEngine.current.position.stopLoss.value ) ) { logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.') @@ -846,11 +846,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( ( tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Below' && - tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value + tradingEngine.current.episode.candle.min.value <= tradingEngine.current.position.takeProfit.value ) || ( tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Above' && - tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value + tradingEngine.current.episode.candle.max.value >= tradingEngine.current.position.takeProfit.value ) ) { logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.') @@ -1010,8 +1010,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingStrategyModule.closeStrategy('Position Closed') /* Distance to Events Updates */ - tradingEngine.current.distanceToEvent.closePosition.value = 1 - tradingEngine.current.distanceToEvent.triggerOff.value = 1 + tradingEngine.current.episode.distanceToEvent.closePosition.value = 1 + tradingEngine.current.episode.distanceToEvent.triggerOff.value = 1 } } @@ -1076,34 +1076,34 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function openStage(stage) { /* Recording the opening at the Trading Engine Data Structure */ - stage.begin.value = tradingEngine.current.candle.begin.value - stage.beginRate.value = tradingEngine.current.candle.close.value - stage.end.value = tradingEngine.current.candle.end.value + stage.begin.value = tradingEngine.current.episode.candle.begin.value + stage.beginRate.value = tradingEngine.current.episode.candle.close.value + stage.end.value = tradingEngine.current.episode.candle.end.value } function closeStage(stage) { /* Recording the closing at the Trading Engine Data Structure */ - stage.end.value = tradingEngine.current.candle.end.value - stage.endRate.value = tradingEngine.current.candle.close.value + stage.end.value = tradingEngine.current.episode.candle.end.value + stage.endRate.value = tradingEngine.current.episode.candle.close.value } } function updateEnds() { if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { - tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategyTriggerStage.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyTriggerStage.endRate.value = tradingEngine.current.episode.candle.close.value } if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { - tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategyOpenStage.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyOpenStage.endRate.value = tradingEngine.current.episode.candle.close.value } if (tradingEngine.current.strategyManageStage.status.value === 'Open') { - tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategyManageStage.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyManageStage.endRate.value = tradingEngine.current.episode.candle.close.value } if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { - tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategyCloseStage.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyCloseStage.endRate.value = tradingEngine.current.episode.candle.close.value } } diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index 4f48686b3..c3ec62c47 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -33,9 +33,9 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng tradingEngine.current.strategy.status.value = 'Open' tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value tradingEngine.current.strategy.identifier.value = global.UNIQUE_ID() - tradingEngine.current.strategy.begin.value = tradingEngine.current.candle.begin.value - tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategy.beginRate.value = tradingEngine.current.candle.min.value + tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategy.beginRate.value = tradingEngine.current.episode.candle.min.value tradingEngine.current.strategy.index.value = index tradingEngine.current.strategy.situationName.value = situationName @@ -48,8 +48,8 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng function closeStrategy(exitType) { tradingEngine.current.strategy.status.value = 'Closed' tradingEngine.current.strategy.exitType.value = exitType - tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value + tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.episode.candle.min.value /* Now that the strategy is closed, it is the right time to move this strategy from current to last at the Trading Engine data structure. */ @@ -58,8 +58,8 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng function updateEnds() { if (tradingEngine.current.strategy.status.value === 'Open') { - tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value - tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.close.value + tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.episode.candle.close.value } } From a297a366a65260f0dd36a3574a1ce90b133e984b Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 8 Aug 2020 15:13:59 +0200 Subject: [PATCH 036/112] TransactionOrders Module --- LowFrequencyTrading/TradingExecution.js | 793 +-------------------- LowFrequencyTrading/TradingOrders.js | 901 ++++++++++++++++++++++++ LowFrequencyTrading/TradingStages.js | 67 +- 3 files changed, 919 insertions(+), 842 deletions(-) create mode 100644 LowFrequencyTrading/TradingOrders.js diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 131bb6713..95a929bcf 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -1,10 +1,11 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingEngineModule) { /* - The Trading Execution modules manages the execution of orders against the exchanges. + The Trading Execution modules manages the execution nodes and execution algoritms. */ const MODULE_NAME = 'Trading Execution' let thisObject = { + mantain: mantain, runExecution: runExecution, initialize: initialize, finalize: finalize @@ -14,11 +15,8 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE let tradingSystem let sessionParameters - const EXCHANGE_API_MODULE = require('./ExchangeAPI.js') - let exchangeAPIModule = EXCHANGE_API_MODULE.newExchangeAPI(bot, logger) - - const ANNOUNCEMENTS_MODULE = require('./Announcements.js') - let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger) + const TRADING_ORDERS_MODULE = require('./TradingOrders.js') + let tradingOrdersModule = TRADING_ORDERS_MODULE.newTradingOrders(bot, logger, tradingEngineModule) return thisObject @@ -27,8 +25,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngine = bot.simulationState.tradingEngine sessionParameters = bot.SESSION.parameters - exchangeAPIModule.initialize() - announcementsModule.initialize() + tradingOrdersModule.initialize() } function finalize() { @@ -36,19 +33,18 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingEngine = undefined sessionParameters = undefined - exchangeAPIModule.finalize() - exchangeAPIModule = undefined + tradingOrdersModule.finalize() + tradingOrdersModule = undefined + } - announcementsModule.finalize() - announcementsModule = undefined + function mantain() { + tradingOrdersModule.mantain() } async function runExecution( executionNode, - stageIsClosing, tradingEngineStage ) { - try { /* Trading Engine Stage Validations */ if (tradingEngineStage.stageBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset === undefined', tradingEngineStage) } @@ -67,774 +63,13 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE async function checkExecutionAlgorithms(executionNode) { for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { let executionAlgorithm = executionNode.executionAlgorithms[i] - await checkOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) - await checkOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) - await checkOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) - await checkOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) + await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) + await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) + await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) + await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) } } - async function checkOrders(orders, executionAlgorithm, executionNode) { - for (let i = 0; i < orders.length; i++) { - - let tradingSystemOrder = orders[i] - - /* Trading System Validations */ - if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } - if (tradingSystemOrder.referenceParent === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder) } - - let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) - - /* Trading Engine Order Validations */ - if (tradingEngineOrder.serialNumber === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.serialNumber === undefined', tradingEngineOrder) } - if (tradingEngineOrder.identifier === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.identifier === undefined', tradingEngineOrder) } - if (tradingEngineOrder.exchangeId === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.exchangeId === undefined', tradingEngineOrder) } - if (tradingEngineOrder.begin === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.begin === undefined', tradingEngineOrder) } - if (tradingEngineOrder.end === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.end === undefined', tradingEngineOrder) } - if (tradingEngineOrder.rate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate === undefined', tradingEngineOrder) } - if (tradingEngineOrder.status === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.status === undefined', tradingEngineOrder) } - if (tradingEngineOrder.algorithmName === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.algorithmName === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderCounters === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderCounters.periods === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters.periods === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderStatistics.days === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.days === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.percentageFilled === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderStatistics.actualRate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.actualRate === undefined', tradingEngineOrder) } - - if (tradingEngineOrder.orderBaseAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.size === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.sizeFilled === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.feesPaid === undefined', tradingEngineOrder) } - - if (tradingEngineOrder.orderQuotedAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.size === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined', tradingEngineOrder) } - if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.feesPaid === undefined', tradingEngineOrder) } - - switch (tradingEngineOrder.status.value) { - case 'Not Open': { - { - /* When the stage is closing we can not create new orders */ - if (stageIsClosing === true) { continue } - /* - Check if we can create an order based on the config value for spawnMultipleOrders. - Trading System Orders that cannot spawn more than one Trading Engine Order needs to check if - at the Trading Engine Order the lock is Open or Closed. - */ - if (tradingSystemOrder.config.spawnMultipleOrders !== true) { - if (tradingEngineOrder.identifier.value === 'Closed') { - continue - } - } - /* Check if we need to Create this Order */ - let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) - if (situationName !== undefined) { - - /* Open a new order */ - await tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) - } - } - break - } - case 'Open': { - /* Update this order properties */ - tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value - tradingEngineOrder.orderCounters.periods.value++ - tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS - - /* Simulate Events that happens at the Exchange, if needed. */ - simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) - - /* Check Events that happens at the Exchange, if needed. */ - await checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) - - /* - In the previous steps, we might have discovered that the order was cancelled - at the exchange, or filled, so the order might still not be Open. - If the stage is closing or the order is not Open, we wont be cancelling orders - based on defined events. - */ - if (stageIsClosing !== true && tradingEngineOrder.status.value === 'Open') { - - /* Check if we need to Cancel this Order */ - let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) - if (situationName !== undefined) { - - /* Simulate Order Cancelation, if needed. */ - simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') - - /* Cancel the order at the Exchange, if needed. */ - await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') - } - } - } - } - } - } - - async function tryToOpenOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { - - calculateOrderRate() - calculateOrderSize() - - /* Check Size: We are not going to create Orders which size is equal or less to zero. */ - if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) { return } - - /* Place Order at the Exchange, if needed. */ - let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) - if (result !== true) { return } - - /* Update Stage Placed Size */ - tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value + tradingEngineOrder.orderBaseAsset.size.value - tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10) - tradingEngineStage.stageQuotedAsset.sizePlaced.value = tradingEngineStage.stageQuotedAsset.sizePlaced.value + tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) - - /* Updating Episode Counters */ - tradingEngine.current.episode.episodeCounters.orders.value++ - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.createOrder.value = 1 - - /* Create Order Procedure */ - tradingEngineOrder.status.value = 'Open' - tradingEngineOrder.identifier.value = global.UNIQUE_ID() - tradingEngineOrder.begin.value = tradingEngine.current.episode.candle.begin.value - tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value - tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value - tradingEngineOrder.orderName.value = tradingSystemOrder.name - tradingEngineOrder.algorithmName.value = executionAlgorithm.name - tradingEngineOrder.situationName.value = situationName - - function calculateOrderRate() { - /* By default this is the order rate and it is the rate that applies to Market Orders */ - tradingEngineOrder.rate.value = tradingEngine.current.episode.candle.close.value - - /* Optional Rate Definition */ - if (tradingSystemOrder.orderRate !== undefined) { - if (tradingSystemOrder.orderRate.formula !== undefined) { - tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id) - - if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingEngineOrder) } - if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingEngineOrder) } - if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingEngineOrder) } - - tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) - } - } - } - - function calculateOrderSize() { - /* Validate that this config exists */ - if (executionAlgorithm.config.percentageOfStageTargetSize === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.config.percentageOfStageTargetSize === undefined', executionAlgorithm) } - if (isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true', executionAlgorithm) } - - let algorithmSizeInBaseAsset = tradingEngineStage.stageBaseAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100 - let algorithmSizeInQuotedAsset = tradingEngineStage.stageQuotedAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100 - - /* Validate that this config exists */ - if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } - if (isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true', tradingSystemOrder) } - - /* Size in Base Asset */ - tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 - - /* Size in Quoted Asset */ - tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 - - /* Check that the Size calculated would not surpass Stage Target Size */ - if ( - tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value > - tradingEngineStage.stageBaseAsset.targetSize.value - ) { - tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value - } - if ( - tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value > - tradingEngineStage.stageQuotedAsset.targetSize.value - ) { - tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value - } - - tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) - tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) - } - - async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return true - } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return true - } - } - - let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) - - if (orderId !== undefined) { - tradingEngineOrder.exchangeId.value = orderId - return true - } - } - } - - function simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - break - } - case 'Live Trading Session': { - return - } - case 'Fordward Testing Session': { - return - } - case 'Paper Trading Session': { - break - } - } - - /* Filter by what is defined at the Strategy */ - if (tradingSystemOrder.simulatedExchangeEvents === undefined) { - badDefinitionUnhandledException(undefined, 'tradingSystemOrder.simulatedExchangeEvents === undefined', tradingSystemOrder) - } - - let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value - let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value - let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value - let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - - actualRateSimulation() - feesPaidSimulation() - percentageFilledSimulation() - sizeFilledSimulation() - - doTheAccounting( - tradingSystemOrder, - tradingEngineOrder, - previousBaseAssetSizeFilled, - previousQuotedAssetSizeFilled, - previousBaseAssetFeesPaid, - previousQuotedAssetFeesPaid - ) - - /* If the Stage is Closing and this order is still open, we need to cancel it now */ - if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { - simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') - } - - function actualRateSimulation() { - /* Actual Rate Simulation */ - let calculatedBasedOnTradingSystem = false - - /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { - /* Calculate this only once for this order */ - if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { - tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) - if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { - calculatedBasedOnTradingSystem = true - } - } - } - } - - /* Based on the Session Parameters Definition */ - if (calculatedBasedOnTradingSystem === false) { - switch (tradingEngineOrder.type) { - case 'Market Order': { - /* Actual Rate is simulated based on the Session Paremeters */ - let slippageAmount = tradingEngineOrder.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100 - switch (tradingSystemOrder.type) { - case 'Market Sell Order': { - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount - break - } - case 'Market Buy Order': { - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount - break - } - } - break - } - case 'Limit Order': { - /* In Limit Orders the actual rate is the rate of the order, there is no slippage */ - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - break - } - } - } - tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - } - - function feesPaidSimulation() { - /* Fees Paid Simulation */ - let calculatedBasedOnTradingSystem = false - - /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { - if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 - - calculatedBasedOnTradingSystem = true - } - - if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 - - calculatedBasedOnTradingSystem = true - } - } - } - - /* Based on the Session Parameters Definition */ - if (calculatedBasedOnTradingSystem === false) { - /* Fees are simulated based on the Session Paremeters */ - switch (tradingEngineOrder.type) { - case 'Market Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 - - break - } - case 'Limit Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 - - break - } - } - } - tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) - tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) - } - - function percentageFilledSimulation() { - /* Order Filling Simulation */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { - - /* Percentage Filled */ - let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 - if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { - percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value - } - tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - - /* Check if we need to close */ - if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Filled' - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - } - } - } - } - - function sizeFilledSimulation() { - /* Size Filled */ - tradingEngineOrder.orderBaseAsset.sizeFilled.value = - (tradingEngineOrder.orderBaseAsset.size.value - tradingEngineOrder.orderBaseAsset.feesPaid.value) * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = - (tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value) * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) - } - } - - async function checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return true - } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return true - } - } - - let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - - if (order === undefined) { return } - - const AT_EXCHANGE_STATUS = { - OPEN: 'open', - CLOSED: 'closed', - CANCELLED: 'canceled' - } - - /* Status Checks */ - if (order.remaining === 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Filled' - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - } - if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Closed at the Exchange' - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - } - if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - } - - syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) - - /* Forced Cancellation Check */ - if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { - await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') - } - } - - function syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) { - - let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value - let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value - let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value - let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - - /* Actual Rate Calculation */ - tradingEngineOrder.orderStatistics.actualRate.value = order.price - tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - - /* Fees Paid Calculation */ - /* - As a response from the exchange we can not always get the fees. - For that reason we need to estimate them base of the information that we do have. - CCXT provides order.amount which represents the size we set for the order minus the - fees taken by the exchange, all denominated in Base Asset. - In this way if we substract to the order size this order.amount - we can get the fees. All this is denominated in base asset because that is how CCXT works. - The fees then can be estimated in Quoted Asset using the Actual Rate. - */ - tradingEngineOrder.orderBaseAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.size.value - order.amount - tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) - tradingEngineOrder.orderQuotedAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.feesPaid.value * tradingEngineOrder.orderStatistics.actualRate.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) - - /* Percentage Filled Calculation */ - tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - - /* Size Filled Calculation */ - /* - CCXT returns order.filled with an amount denominated in Base Asset. We will - take it from there for our Order Base Asset. For our Order Quoted Asset we - will use the field order.cost. - */ - tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled - tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = order.cost - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) - - doTheAccounting( - tradingSystemOrder, - tradingEngineOrder, - previousBaseAssetSizeFilled, - previousQuotedAssetSizeFilled, - previousBaseAssetFeesPaid, - previousQuotedAssetFeesPaid - ) - } - - function doTheAccounting( - tradingSystemOrder, - tradingEngineOrder, - previousBaseAssetSizeFilled, - previousQuotedAssetSizeFilled, - previousBaseAssetFeesPaid, - previousQuotedAssetFeesPaid - ) { - - updateStageAssets() - updateBalances() - - function updateStageAssets() { - /* Stage Base Asset: Undo the previous accounting */ - tradingEngineStage.stageBaseAsset.sizeFilled.value = - tradingEngineStage.stageBaseAsset.sizeFilled.value - - previousBaseAssetSizeFilled - - tradingEngineStage.stageBaseAsset.feesPaid.value = - tradingEngineStage.stageBaseAsset.feesPaid.value - - previousBaseAssetFeesPaid - - /* Stage Base Asset: Account the current filling and fees */ - tradingEngineStage.stageBaseAsset.sizeFilled.value = - tradingEngineStage.stageBaseAsset.sizeFilled.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value - - tradingEngineStage.stageBaseAsset.feesPaid.value = - tradingEngineStage.stageBaseAsset.feesPaid.value + - tradingEngineOrder.orderBaseAsset.feesPaid.value - - /* Stage Quote Asset: Undo the previous accounting */ - tradingEngineStage.stageQuotedAsset.sizeFilled.value = - tradingEngineStage.stageQuotedAsset.sizeFilled.value - - previousQuotedAssetSizeFilled - - tradingEngineStage.stageQuotedAsset.feesPaid.value = - tradingEngineStage.stageQuotedAsset.feesPaid.value - - previousQuotedAssetFeesPaid - - /* Stage Quote Asset: Account the current filling and fees */ - tradingEngineStage.stageQuotedAsset.sizeFilled.value = - tradingEngineStage.stageQuotedAsset.sizeFilled.value + - tradingEngineOrder.orderQuotedAsset.sizeFilled.value - - tradingEngineStage.stageQuotedAsset.feesPaid.value = - tradingEngineStage.stageQuotedAsset.feesPaid.value + - tradingEngineOrder.orderQuotedAsset.feesPaid.value - - tradingEngineStage.stageBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizeFilled.value, 10) - tradingEngineStage.stageBaseAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageBaseAsset.feesPaid.value, 10) - - tradingEngineStage.stageQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizeFilled.value, 10) - tradingEngineStage.stageQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.feesPaid.value, 10) - } - - function updateBalances() { - /* Balances Update */ - switch (true) { - case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { - - /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.episode.episodeBaseAsset.balance.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value - - previousBaseAssetSizeFilled - - /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.episode.episodeBaseAsset.balance.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value - - /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value + - previousQuotedAssetSizeFilled + - previousQuotedAssetFeesPaid - - /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value - - tradingEngineOrder.orderQuotedAsset.sizeFilled.value - - tradingEngineOrder.orderQuotedAsset.feesPaid.value - break - } - case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { - - /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.episode.episodeBaseAsset.balance.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value + - previousBaseAssetSizeFilled + - previousBaseAssetFeesPaid - - /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.episode.episodeBaseAsset.balance.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value - - tradingEngineOrder.orderBaseAsset.sizeFilled.value - - tradingEngineOrder.orderBaseAsset.feesPaid.value - - /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value - - previousQuotedAssetSizeFilled - - /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value + - tradingEngineOrder.orderQuotedAsset.sizeFilled.value - break - } - } - tradingEngine.current.episode.episodeBaseAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.balance.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.balance.value, 10) - } - - } - - function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - break - } - case 'Live Trading Session': { - return - } - case 'Fordward Testing Session': { - return - } - case 'Paper Trading Session': { - break - } - } - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = exitType - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - - recalculateStageSize(tradingEngineOrder) - } - - async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return - } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return - } - } - - /* Check if we can cancel the order at the Exchange. */ - let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) - if (result === true) { - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = exitType - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - - /* - Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. - To sync our accounting, we need to check the order one last time and if it changed, fix it. - */ - - let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - - if (order === undefined) { return } - - syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) - - recalculateStageSize() - } - } - - function recalculateStageSize(tradingEngineOrder) { - /* - Since the order is Cancelled, we need to adjust the stage size. Remember that the Stage Size - accumulates for each asset, the order size placed at the exchange. A cancelation means that - only the part filled can be considered placed, so we need to substract from the stage size - the remainder. To achieve this with the information we currently have, we are going first - to unaccount the order size, and the account only the sizeFilled + the feesPaid. - */ - tradingEngineStage.stageBaseAsset.size.value = - tradingEngineStage.stageBaseAsset.size.value - - tradingEngineOrder.orderBaseAsset.size.value - tradingEngineStage.stageQuotedAsset.size.value = - tradingEngineStage.stageQuotedAsset.size.value - - tradingEngineOrder.orderQuotedAsset.size.value - - tradingEngineStage.stageBaseAsset.size.value = - tradingEngineStage.stageBaseAsset.size.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value + - tradingEngineOrder.orderBaseAsset.feesPaid.value - tradingEngineStage.stageQuotedAsset.size.value = - tradingEngineStage.stageQuotedAsset.size.value + - tradingEngineOrder.orderQuotedAsset.sizeFilled.value + - tradingEngineOrder.orderQuotedAsset.feesPaid.value - - tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) - tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) - } - - function checkOrderEvent(event, order, executionAlgorithm, executionNode) { - if (event !== undefined) { - for (let k = 0; k < event.situations.length; k++) { - let situation = event.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = tradingSystem.checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(event.id) - tradingSystem.highlights.push(order.id) - tradingSystem.highlights.push(executionAlgorithm.id) - tradingSystem.highlights.push(executionNode.id) - - announcementsModule.makeAnnoucements(event) - return situation.name // if the event is triggered, we return the name of the situation that passed - } - } - } - } } catch (err) { if (typeof err === 'string' || err instanceof String) { logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js new file mode 100644 index 000000000..04bf46db3 --- /dev/null +++ b/LowFrequencyTrading/TradingOrders.js @@ -0,0 +1,901 @@ +exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineModule) { + /* + The Trading Orders modules manages the execution of orders against the exchanges. + */ + const MODULE_NAME = 'Trading Orders' + + let thisObject = { + mantain: mantain, + checkOrders: checkOrders, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + let tradingSystem + let sessionParameters + + const EXCHANGE_API_MODULE = require('./ExchangeAPI.js') + let exchangeAPIModule = EXCHANGE_API_MODULE.newExchangeAPI(bot, logger) + + const ANNOUNCEMENTS_MODULE = require('./Announcements.js') + let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger) + + return thisObject + + function initialize() { + tradingSystem = bot.simulationState.tradingSystem + tradingEngine = bot.simulationState.tradingEngine + sessionParameters = bot.SESSION.parameters + + exchangeAPIModule.initialize() + announcementsModule.initialize() + } + + function finalize() { + tradingSystem = undefined + tradingEngine = undefined + sessionParameters = undefined + + exchangeAPIModule.finalize() + exchangeAPIModule = undefined + + announcementsModule.finalize() + announcementsModule = undefined + } + + function mantain() { + + if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return } + + let stageNode + let executionNode + + stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + executionNode = stageNode.openExecution + mantainExecutionNode(executionNode, tradingEngine.current.strategyOpenStage.status.value) + + stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + executionNode = stageNode.closeExecution + mantainExecutionNode(executionNode, tradingEngine.current.strategyCloseStage.status.value) + + function mantainExecutionNode(executionNode, stageStatus) { + + mantainExecutionAlgorithms(executionNode) + + function mantainExecutionAlgorithms(executionNode) { + for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { + let executionAlgorithm = executionNode.executionAlgorithms[i] + mantainOrders(executionAlgorithm.marketBuyOrders) + mantainOrders(executionAlgorithm.marketSellOrders) + mantainOrders(executionAlgorithm.limitBuyOrders) + mantainOrders(executionAlgorithm.limitSellOrders) + } + } + + function mantainOrders(orders) { + for (let i = 0; i < orders.length; i++) { + let tradingSystemOrder = orders[i] + if (tradingSystemOrder.referenceParent === undefined) { continue } + let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) + if (tradingEngineOrder.status === undefined) { continue } + + updateCounters(tradingEngineOrder) + updateEnds(tradingEngineOrder) + updateStatistics(tradingEngineOrder) + resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus) + } + } + } + } + + async function checkOrders(tradingEngineStage, orders, executionAlgorithm, executionNode) { + for (let i = 0; i < orders.length; i++) { + + let tradingSystemOrder = orders[i] + let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) + + definitionValidations(tradingSystemOrder, tradingEngineOrder) + + switch (tradingEngineOrder.status.value) { + case 'Not Open': { + { + /* When the stage is closing we can not create new orders */ + if (tradingEngineStage.status.value === 'Closing') { continue } + /* + Check if we can create an order based on the config value for spawnMultipleOrders. + Trading System Orders that cannot spawn more than one Trading Engine Order needs to check if + at the Trading Engine Order the lock is Open or Closed. + */ + if (tradingSystemOrder.config.spawnMultipleOrders !== true) { + if (tradingEngineOrder.identifier.value === 'Closed') { + continue + } + } + /* Check if we need to Create this Order */ + let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (situationName !== undefined) { + + /* Open a new order */ + await tryToOpenOrder(tradingEngineStage, executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) + } + } + break + } + case 'Open': { + /* Simulate Events that happens at the Exchange, if needed. */ + simulateExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) + + /* Check Events that happens at the Exchange, if needed. */ + await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) + + /* + In the previous steps, we might have discovered that the order was cancelled + at the exchange, or filled, so the order might still not be Open. + If the stage is closing or the order is not Open, we wont be cancelling orders + based on defined events. + */ + if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { + + /* Check if we need to Cancel this Order */ + let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (situationName !== undefined) { + + /* Simulate Order Cancelation, if needed. */ + simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + + /* Cancel the order at the Exchange, if needed. */ + await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + } + } + } + } + } + } + + function definitionValidations(tradingSystemOrder, tradingEngineOrder) { + /* Trading System Validations */ + if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } + if (tradingSystemOrder.referenceParent === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder) } + + /* Trading Engine Order Validations */ + if (tradingEngineOrder.serialNumber === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.serialNumber === undefined', tradingEngineOrder) } + if (tradingEngineOrder.identifier === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.identifier === undefined', tradingEngineOrder) } + if (tradingEngineOrder.exchangeId === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.exchangeId === undefined', tradingEngineOrder) } + if (tradingEngineOrder.begin === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.begin === undefined', tradingEngineOrder) } + if (tradingEngineOrder.end === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.end === undefined', tradingEngineOrder) } + if (tradingEngineOrder.rate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate === undefined', tradingEngineOrder) } + if (tradingEngineOrder.status === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.status === undefined', tradingEngineOrder) } + if (tradingEngineOrder.algorithmName === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.algorithmName === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderCounters === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderCounters.periods === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters.periods === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderStatistics.days === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.days === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.percentageFilled === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderStatistics.actualRate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.actualRate === undefined', tradingEngineOrder) } + + if (tradingEngineOrder.orderBaseAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.size === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.sizeFilled === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.feesPaid === undefined', tradingEngineOrder) } + + if (tradingEngineOrder.orderQuotedAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.size === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined', tradingEngineOrder) } + if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.feesPaid === undefined', tradingEngineOrder) } + + } + + async function tryToOpenOrder(tradingEngineStage, executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { + + calculateOrderRate() + calculateOrderSize() + + /* Check Size: We are not going to create Orders which size is equal or less to zero. */ + if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) { return } + + /* Place Order at the Exchange, if needed. */ + let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) + if (result !== true) { return } + + /* Update Stage Placed Size */ + tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10) + tradingEngineStage.stageQuotedAsset.sizePlaced.value = tradingEngineStage.stageQuotedAsset.sizePlaced.value + tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) + + /* Updating Episode Counters */ + tradingEngine.current.episode.episodeCounters.orders.value++ + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.createOrder.value = 1 + + /* Create Order Procedure */ + tradingEngineOrder.status.value = 'Open' + tradingEngineOrder.identifier.value = global.UNIQUE_ID() + tradingEngineOrder.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value + tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value + tradingEngineOrder.orderName.value = tradingSystemOrder.name + tradingEngineOrder.algorithmName.value = executionAlgorithm.name + tradingEngineOrder.situationName.value = situationName + + function calculateOrderRate() { + /* By default this is the order rate and it is the rate that applies to Market Orders */ + tradingEngineOrder.rate.value = tradingEngine.current.episode.candle.close.value + + /* Optional Rate Definition */ + if (tradingSystemOrder.orderRate !== undefined) { + if (tradingSystemOrder.orderRate.formula !== undefined) { + tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id) + + if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingEngineOrder) } + if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingEngineOrder) } + if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingEngineOrder) } + + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + } + } + } + + function calculateOrderSize() { + /* Validate that this config exists */ + if (executionAlgorithm.config.percentageOfStageTargetSize === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.config.percentageOfStageTargetSize === undefined', executionAlgorithm) } + if (isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true', executionAlgorithm) } + + let algorithmSizeInBaseAsset = tradingEngineStage.stageBaseAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100 + let algorithmSizeInQuotedAsset = tradingEngineStage.stageQuotedAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100 + + /* Validate that this config exists */ + if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } + if (isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true', tradingSystemOrder) } + + /* Size in Base Asset */ + tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 + + /* Size in Quoted Asset */ + tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 + + /* Check that the Size calculated would not surpass Stage Target Size */ + if ( + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value > + tradingEngineStage.stageBaseAsset.targetSize.value + ) { + tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value + } + if ( + tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value > + tradingEngineStage.stageQuotedAsset.targetSize.value + ) { + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value + } + + tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) + tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) + } + + async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return true + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return true + } + } + + let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) + + if (orderId !== undefined) { + tradingEngineOrder.exchangeId.value = orderId + return true + } + } + } + + function simulateExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + break + } + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { + break + } + } + + /* Filter by what is defined at the Strategy */ + if (tradingSystemOrder.simulatedExchangeEvents === undefined) { + badDefinitionUnhandledException(undefined, 'tradingSystemOrder.simulatedExchangeEvents === undefined', tradingSystemOrder) + } + + let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value + let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value + let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value + let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value + + actualRateSimulation() + feesPaidSimulation() + percentageFilledSimulation() + sizeFilledSimulation() + + doTheAccounting( + tradingEngineStage, + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) + + /* If the Stage is Closing and this order is still open, we need to cancel it now */ + if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') { + simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') + } + + function actualRateSimulation() { + /* Actual Rate Simulation */ + let calculatedBasedOnTradingSystem = false + + /* Based on the Trading System Definition */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { + /* Calculate this only once for this order */ + if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { + tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { + calculatedBasedOnTradingSystem = true + } + } + } + } + + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { + switch (tradingEngineOrder.type) { + case 'Market Order': { + /* Actual Rate is simulated based on the Session Paremeters */ + let slippageAmount = tradingEngineOrder.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100 + switch (tradingSystemOrder.type) { + case 'Market Sell Order': { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount + break + } + case 'Market Buy Order': { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount + break + } + } + break + } + case 'Limit Order': { + /* In Limit Orders the actual rate is the rate of the order, there is no slippage */ + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + break + } + } + } + tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + } + + function feesPaidSimulation() { + /* Fees Paid Simulation */ + let calculatedBasedOnTradingSystem = false + + /* Based on the Trading System Definition */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { + if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + + calculatedBasedOnTradingSystem = true + } + + if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + + calculatedBasedOnTradingSystem = true + } + } + } + + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { + /* Fees are simulated based on the Session Paremeters */ + switch (tradingEngineOrder.type) { + case 'Market Order': { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 + + break + } + case 'Limit Order': { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 + + break + } + } + } + tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) + } + + function percentageFilledSimulation() { + /* Order Filling Simulation */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { + + /* Percentage Filled */ + let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 + if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { + percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + } + tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + + /* Check if we need to close */ + if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Filled' + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + } + } + } + } + + function sizeFilledSimulation() { + /* Size Filled */ + tradingEngineOrder.orderBaseAsset.sizeFilled.value = + (tradingEngineOrder.orderBaseAsset.size.value - tradingEngineOrder.orderBaseAsset.feesPaid.value) * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = + (tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value) * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) + } + } + + async function checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return true + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return true + } + } + + let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) + + if (order === undefined) { return } + + const AT_EXCHANGE_STATUS = { + OPEN: 'open', + CLOSED: 'closed', + CANCELLED: 'canceled' + } + + /* Status Checks */ + if (order.remaining === 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Filled' + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + } + if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Closed at the Exchange' + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + } + if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + } + + syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + + /* Forced Cancellation Check */ + if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') { + await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Closing Stage') + } + } + + function syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) { + + let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value + let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value + let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value + let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value + + /* Actual Rate Calculation */ + tradingEngineOrder.orderStatistics.actualRate.value = order.price + tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + + /* Fees Paid Calculation */ + /* + As a response from the exchange we can not always get the fees. + For that reason we need to estimate them base of the information that we do have. + CCXT provides order.amount which represents the size we set for the order minus the + fees taken by the exchange, all denominated in Base Asset. + In this way if we substract to the order size this order.amount + we can get the fees. All this is denominated in base asset because that is how CCXT works. + The fees then can be estimated in Quoted Asset using the Actual Rate. + */ + tradingEngineOrder.orderBaseAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.size.value - order.amount + tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.feesPaid.value * tradingEngineOrder.orderStatistics.actualRate.value + tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) + + /* Percentage Filled Calculation */ + tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + + /* Size Filled Calculation */ + /* + CCXT returns order.filled with an amount denominated in Base Asset. We will + take it from there for our Order Base Asset. For our Order Quoted Asset we + will use the field order.cost. + */ + tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled + tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = order.cost + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) + + doTheAccounting( + tradingEngineStage, + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) + } + + function doTheAccounting( + tradingEngineStage, + tradingSystemOrder, + tradingEngineOrder, + previousBaseAssetSizeFilled, + previousQuotedAssetSizeFilled, + previousBaseAssetFeesPaid, + previousQuotedAssetFeesPaid + ) { + + updateStageAssets() + updateBalances() + + function updateStageAssets() { + /* Stage Base Asset: Undo the previous accounting */ + tradingEngineStage.stageBaseAsset.sizeFilled.value = + tradingEngineStage.stageBaseAsset.sizeFilled.value - + previousBaseAssetSizeFilled + + tradingEngineStage.stageBaseAsset.feesPaid.value = + tradingEngineStage.stageBaseAsset.feesPaid.value - + previousBaseAssetFeesPaid + + /* Stage Base Asset: Account the current filling and fees */ + tradingEngineStage.stageBaseAsset.sizeFilled.value = + tradingEngineStage.stageBaseAsset.sizeFilled.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value + + tradingEngineStage.stageBaseAsset.feesPaid.value = + tradingEngineStage.stageBaseAsset.feesPaid.value + + tradingEngineOrder.orderBaseAsset.feesPaid.value + + /* Stage Quote Asset: Undo the previous accounting */ + tradingEngineStage.stageQuotedAsset.sizeFilled.value = + tradingEngineStage.stageQuotedAsset.sizeFilled.value - + previousQuotedAssetSizeFilled + + tradingEngineStage.stageQuotedAsset.feesPaid.value = + tradingEngineStage.stageQuotedAsset.feesPaid.value - + previousQuotedAssetFeesPaid + + /* Stage Quote Asset: Account the current filling and fees */ + tradingEngineStage.stageQuotedAsset.sizeFilled.value = + tradingEngineStage.stageQuotedAsset.sizeFilled.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + + tradingEngineStage.stageQuotedAsset.feesPaid.value = + tradingEngineStage.stageQuotedAsset.feesPaid.value + + tradingEngineOrder.orderQuotedAsset.feesPaid.value + + tradingEngineStage.stageBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizeFilled.value, 10) + tradingEngineStage.stageBaseAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageBaseAsset.feesPaid.value, 10) + + tradingEngineStage.stageQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizeFilled.value, 10) + tradingEngineStage.stageQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.feesPaid.value, 10) + } + + function updateBalances() { + /* Balances Update */ + switch (true) { + case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { + + /* Balance Base Asset: Undo the previous accounting */ + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - + previousBaseAssetSizeFilled + + /* Balance Base Asset: Account the current filling and fees */ + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value + + /* Balance Quoted Asset: Undo the previous accounting */ + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value + + previousQuotedAssetSizeFilled + + previousQuotedAssetFeesPaid + + /* Balance Quoted Asset: Account the current filling and fees */ + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - + tradingEngineOrder.orderQuotedAsset.sizeFilled.value - + tradingEngineOrder.orderQuotedAsset.feesPaid.value + break + } + case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { + + /* Balance Base Asset: Undo the previous accounting */ + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value + + previousBaseAssetSizeFilled + + previousBaseAssetFeesPaid + + /* Balance Base Asset: Account the current filling and fees */ + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - + tradingEngineOrder.orderBaseAsset.sizeFilled.value - + tradingEngineOrder.orderBaseAsset.feesPaid.value + + /* Balance Quoted Asset: Undo the previous accounting */ + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - + previousQuotedAssetSizeFilled + + /* Balance Quoted Asset: Account the current filling and fees */ + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + break + } + } + tradingEngine.current.episode.episodeBaseAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.balance.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.balance.value, 10) + } + + } + + function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + break + } + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { + break + } + } + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = exitType + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + + recalculateStageSize(tradingEngineStage, tradingEngineOrder) + } + + async function exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return + } + } + + /* Check if we can cancel the order at the Exchange. */ + let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) + if (result === true) { + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = exitType + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + + /* + Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. + To sync our accounting, we need to check the order one last time and if it changed, fix it. + */ + + let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) + + if (order === undefined) { return } + + syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + + recalculateStageSize(tradingEngineStage) + } + } + + function recalculateStageSize(tradingEngineStage, tradingEngineOrder) { + /* + Since the order is Cancelled, we need to adjust the stage size. Remember that the Stage Size + accumulates for each asset, the order size placed at the exchange. A cancelation means that + only the part filled can be considered placed, so we need to substract from the stage size + the remainder. To achieve this with the information we currently have, we are going first + to unaccount the order size, and the account only the sizeFilled + the feesPaid. + */ + tradingEngineStage.stageBaseAsset.size.value = + tradingEngineStage.stageBaseAsset.size.value - + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageQuotedAsset.size.value = + tradingEngineStage.stageQuotedAsset.size.value - + tradingEngineOrder.orderQuotedAsset.size.value + + tradingEngineStage.stageBaseAsset.size.value = + tradingEngineStage.stageBaseAsset.size.value + + tradingEngineOrder.orderBaseAsset.sizeFilled.value + + tradingEngineOrder.orderBaseAsset.feesPaid.value + tradingEngineStage.stageQuotedAsset.size.value = + tradingEngineStage.stageQuotedAsset.size.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + + tradingEngineOrder.orderQuotedAsset.feesPaid.value + + tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) + tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) + } + + function checkOrderEvent(event, order, executionAlgorithm, executionNode) { + if (event !== undefined) { + for (let k = 0; k < event.situations.length; k++) { + let situation = event.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + passed = tradingSystem.checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(event.id) + tradingSystem.highlights.push(order.id) + tradingSystem.highlights.push(executionAlgorithm.id) + tradingSystem.highlights.push(executionNode.id) + + announcementsModule.makeAnnoucements(event) + return situation.name // if the event is triggered, we return the name of the situation that passed + } + } + } + } + + function updateEnds(tradingEngineOrder) { + if (tradingEngineOrder.status.value === 'Open') { + tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value + } + } + + function updateCounters(tradingEngineOrder) { + if (tradingEngineOrder.status.value === 'Open') { + tradingEngineOrder.orderCounters.periods.value++ + } + } + + function updateStatistics(tradingEngineOrder) { + tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + tradingEngineOrder.orderStatistics.days.value = global.PRECISE(tradingEngineOrder.orderStatistics.days.value, 10) + } + + function resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus) { + if (tradingEngineOrder.status.value === 'Closed') { + /* We reset the order data structure inside the Trading Engine to its initial value */ + tradingEngineOrder.initialize(tradingEngineOrder) + if (tradingSystemOrder.config.spawnMultipleOrders !== true) { + /* + We close the lock so as to prevent this data structure to be used again during this same stage execution. + */ + if (stageStatus === 'Open') { + tradingEngineOrder.lock.value = 'Closed' + } + } + } + } + + function badDefinitionUnhandledException(err, message, node) { + tradingSystem.errors.push([node.id, message]) + + logger.write(MODULE_NAME, "[ERROR] -> " + message); + logger.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name); + logger.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type); + logger.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config)); + if (err !== undefined) { + logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack); + } + throw 'It is not safe to continue with a Definition Error like this. Please fix the problem and try again.' + } +} + diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 640feae4d..8a64ac788 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -86,66 +86,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function mantain() { tradingPositionModule.mantain() tradingStrategyModule.mantain() - mantainStages() - mantainOrders() + tradingExecutionModule.mantain() - function mantainStages() { - resetTradingEngineDataStructure() - updateCounters() - updateEnds() - } - - function mantainOrders() { - /* Reset All Exchange Orders that are on Closed status, to their initial value */ - if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return } - - let stageNode - let executionNode - - stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - executionNode = stageNode.openExecution - resetExecution(executionNode, tradingEngine.current.strategyOpenStage.status.value) - - stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage - executionNode = stageNode.closeExecution - resetExecution(executionNode, tradingEngine.current.strategyCloseStage.status.value) - - function resetExecution(executionNode, stageStatus) { - - resetExecutionAlgorithms(executionNode) - - function resetExecutionAlgorithms(executionNode) { - for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { - let executionAlgorithm = executionNode.executionAlgorithms[i] - resetOrders(executionAlgorithm.marketBuyOrders) - resetOrders(executionAlgorithm.marketSellOrders) - resetOrders(executionAlgorithm.limitBuyOrders) - resetOrders(executionAlgorithm.limitSellOrders) - } - } - - function resetOrders(orders) { - for (let i = 0; i < orders.length; i++) { - let tradingSystemOrder = orders[i] - if (tradingSystemOrder.referenceParent === undefined) { continue } - let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) - if (tradingEngineOrder.status === undefined) { continue } - if (tradingEngineOrder.status.value === 'Closed') { - /* We reset the order data structure inside the Trading Engine to its initial value */ - tradingEngineOrder.initialize(tradingEngineOrder) - if (tradingSystemOrder.config.spawnMultipleOrders !== true) { - /* - We close the lock so as to prevent this data structure to be used again during this same stage execution. - */ - if (stageStatus === 'Open') { - tradingEngineOrder.lock.value = 'Closed' - } - } - } - } - } - } - } + resetTradingEngineDataStructure() + updateCounters() + updateEnds() } function runTriggerStage() { @@ -344,7 +289,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, - false, tradingEngine.current.strategyOpenStage ) @@ -368,7 +312,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, - false, tradingEngine.current.strategyOpenStage ) /* @@ -429,7 +372,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, - true, tradingEngine.current.strategyOpenStage ) @@ -899,7 +841,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM await tradingExecutionModule.runExecution( executionNode, - false, tradingEngine.current.strategyCloseStage ) From a44c92954a3c56b0f3daa062f9f74e5f41382480 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Aug 2020 09:47:27 +0200 Subject: [PATCH 037/112] removed cross references at tradingEngine --- LowFrequencyTrading/TradingEngine.js | 2 +- LowFrequencyTrading/TradingEpisode.js | 4 ++-- LowFrequencyTrading/TradingOrders.js | 9 +++++++-- LowFrequencyTrading/TradingPosition.js | 2 +- LowFrequencyTrading/TradingSimulation.js | 4 ++-- LowFrequencyTrading/TradingStages.js | 4 ++-- LowFrequencyTrading/TradingStrategy.js | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 3c3255225..292b58dfc 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -9,6 +9,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { getNodeById: getNodeById, cloneValues: cloneValues, setCurrentCandle: setCurrentCandle, + initializeNode: initializeNode, initialize: initialize, finalize: finalize } @@ -108,7 +109,6 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { node.value = node.config.initialValue } } - node.initialize = initializeNode // This will allow anyone to initialize this node and its children. /* Now we go down through all this node children */ let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 5dc450d8c..a3207a285 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -1,4 +1,4 @@ -exports.newTradingEpisode = function newTradingEpisode(bot, logger) { +exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngineModule) { /* This module packages all functions related to Episodes. */ @@ -76,7 +76,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger) { } function resetEpisode() { - tradingEngine.current.episode.initialize(tradingEngine.current.episode) + tradingEngineModule.initializeNode(tradingEngine.current.episode) } function updateEnds() { diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 04bf46db3..609f7d987 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -78,7 +78,12 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let tradingSystemOrder = orders[i] if (tradingSystemOrder.referenceParent === undefined) { continue } let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) - if (tradingEngineOrder.status === undefined) { continue } + try { + if (tradingEngineOrder.status === undefined) { continue } + } catch (err) { + + } + updateCounters(tradingEngineOrder) updateEnds(tradingEngineOrder) @@ -873,7 +878,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM function resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus) { if (tradingEngineOrder.status.value === 'Closed') { /* We reset the order data structure inside the Trading Engine to its initial value */ - tradingEngineOrder.initialize(tradingEngineOrder) + tradingEngineModule.initializeNode(tradingEngineOrder) if (tradingSystemOrder.config.spawnMultipleOrders !== true) { /* We close the lock so as to prevent this data structure to be used again during this same stage execution. diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 2666b1b80..a9d621fd3 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -238,7 +238,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function resetTradingEngineDataStructure() { if (tradingEngine.current.position.status.value === 'Closed') { - tradingEngine.current.position.initialize(tradingEngine.current.position) + tradingEngine.initializeNode(tradingEngine.current.position) } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 970ddecc7..acd5d239d 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -47,7 +47,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingSystemModule.initialize() const TRADING_EPISODE_MODULE = require('./TradingEpisode.js') - let tradingEpisodeModule = TRADING_EPISODE_MODULE.newTradingEpisode(bot, logger) + let tradingEpisodeModule = TRADING_EPISODE_MODULE.newTradingEpisode(bot, logger, tradingEngineModule) tradingEpisodeModule.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. */ @@ -177,7 +177,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. } else { - closeEpisode('All Candles Processed') + updateEpisode('All Candles Processed') } } diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 8a64ac788..1f19fd31b 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -269,7 +269,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM let executionNode = stageNode.openExecution /* Reset the Exchange Orders data structure to its initial value */ - tradingEngine.exchangeOrders.initialize(tradingEngine.exchangeOrders) + tradingEngineModule.initializeNode(tradingEngine.exchangeOrders) tradingSystem.evalConditions(stageNode, 'Initial Definition') tradingSystem.evalFormulas(stageNode, 'Initial Definition') @@ -1066,7 +1066,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } function resetStage(stage) { - stage.initialize(stage) + tradingEngineModule.initializeNode(stage) } } diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index c3ec62c47..87216fc29 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -65,7 +65,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng function resetTradingEngineDataStructure() { if (tradingEngine.current.strategy.status.value === 'Closed') { - tradingEngine.current.strategy.initialize(tradingEngine.current.strategy) + tradingEngineModule.initializeNode(tradingEngine.current.strategy) } } From e0fa02b38cca0f687b5dd834a8f6404e0668fb2b Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Aug 2020 10:46:14 +0200 Subject: [PATCH 038/112] Logs Cleaned --- Commons.js | 2 +- DataDependencies.js | 20 +++++++------------ DataSet.js | 3 --- FileStorage.js | 3 --- IndicatorOutput.js | 4 ---- LowFrequencyTrading/TradingOrders.js | 2 +- ProcessExecutionEvents.js | 4 ---- Root.js | 29 ++++------------------------ StatusDependencies.js | 7 ------- StatusReport.js | 3 --- 10 files changed, 13 insertions(+), 64 deletions(-) diff --git a/Commons.js b/Commons.js index f3b4cc853..3f5642205 100644 --- a/Commons.js +++ b/Commons.js @@ -75,7 +75,7 @@ return } if (outputDatasetNode.referenceParent.parentNode.dataBuilding === undefined) { - logger.write(MODULE_NAME, "[WARN] start -> Product Definition " + outputDatasetNode.referenceParent.parentNode.name + " without a Data Building Procedure. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + logger.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) { logger.write(MODULE_NAME, "[ERROR] start -> Dataset witn no codeName defined. Product Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); diff --git a/DataDependencies.js b/DataDependencies.js index 918def0bf..44287c682 100644 --- a/DataDependencies.js +++ b/DataDependencies.js @@ -4,12 +4,12 @@ const LOG_FILE_CONTENT = false; const MODULE_NAME = "Data Dependencies"; - let bot = BOT + let bot = BOT let thisObject = { nodeArray: undefined, dataSetsModulesArray: [], - isItADepenency: isItADepenency, + isItADepenency: isItADepenency, initialize: initialize, finalize: finalize }; @@ -21,9 +21,6 @@ function initialize(callBackFunction) { try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - /* Basic Valdidations */ if (bot.processNode.referenceParent.processDependencies !== undefined) { if (bot.processNode.referenceParent.processDependencies.dataDependencies !== undefined) { @@ -51,7 +48,7 @@ if (bot.DEPENDENCY_FILTER !== undefined) { for (let i = 0; i < bot.DEPENDENCY_FILTER.length; i++) { let key = bot.DEPENDENCY_FILTER[i] - filter.set(key, true) + filter.set(key, true) } } @@ -70,7 +67,7 @@ function onInitilized(err) { if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err = "+ JSON.stringify(err)); + logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err = " + JSON.stringify(err)); alreadyCalledBack = true; callBackFunction(err); @@ -81,9 +78,6 @@ } function addDataSet() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> addDataSet -> Entering function."); } - addCount++; thisObject.dataSetsModulesArray.push(dataSetModule); @@ -99,14 +93,14 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } function finalize() { for (let i = 0; i < thisObject.dataSetsModulesArray.length; i++) { - let dataSetModule = thisObject.dataSetsModulesArray[i] + let dataSetModule = thisObject.dataSetsModulesArray[i] dataSetModule.finalize() } thisObject.dataSetsModulesArray = undefined @@ -115,7 +109,7 @@ } function isItADepenency(timeFrame, product) { - let key = timeFrame + '-' + product + let key = timeFrame + '-' + product return filter.get(key) } diff --git a/DataSet.js b/DataSet.js index bb7f789e3..fe188ba16 100644 --- a/DataSet.js +++ b/DataSet.js @@ -23,9 +23,6 @@ exports.newDataSet = function newDataSet(BOT, logger) { function initialize(dataDependency, callBackFunction) { try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - thisObject.node = dataDependency.referenceParent; logger.fileName = MODULE_NAME + "." + thisObject.node.type + "." + thisObject.node.name + "." + thisObject.node.id; diff --git a/FileStorage.js b/FileStorage.js index f47b86545..9b9245883 100644 --- a/FileStorage.js +++ b/FileStorage.js @@ -31,9 +31,6 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious) function recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious) { - - logger.write(MODULE_NAME, '[INFO] FileStorage -> getTextFile -> Entering Function.') - let fileDoesNotExist = false /* Choose path for either bots or data */ diff --git a/IndicatorOutput.js b/IndicatorOutput.js index 3faa4052e..0ae431043 100644 --- a/IndicatorOutput.js +++ b/IndicatorOutput.js @@ -24,14 +24,10 @@ } function initialize(callBackFunction) { - try { - logger.fileName = MODULE_NAME; logger.initialize(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - callBackFunction(global.DEFAULT_OK_RESPONSE); } catch (err) { diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 609f7d987..530277879 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -81,7 +81,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM try { if (tradingEngineOrder.status === undefined) { continue } } catch (err) { - + console.log(err.stack) } diff --git a/ProcessExecutionEvents.js b/ProcessExecutionEvents.js index b304be111..c45f64ddd 100644 --- a/ProcessExecutionEvents.js +++ b/ProcessExecutionEvents.js @@ -29,11 +29,7 @@ return thisObject; function initialize(callBackFunction) { - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - let name = 'Not Depends on any Process' logger.fileName = MODULE_NAME + "." + name; diff --git a/Root.js b/Root.js index 06b1fe77b..209ae43ab 100644 --- a/Root.js +++ b/Root.js @@ -56,50 +56,29 @@ /* Global control of logging. */ global.LOG_CONTROL = { - "Assistant": { - logInfo: true, - logWarnings: false, - logErrors: true, - logContent: false, - intensiveLogging: false - }, - "Exchange API": { - logInfo: true, - logWarnings: false, - logErrors: true, - logContent: false, - intensiveLogging: false - }, "Status Report": { - logInfo: true, + logInfo: false, logWarnings: false, logErrors: true, logContent: false, intensiveLogging: false }, "Dataset": { - logInfo: true, - logWarnings: false, - logErrors: true, - logContent: false, - intensiveLogging: false - }, - "Context": { - logInfo: true, + logInfo: false, logWarnings: false, logErrors: true, logContent: false, intensiveLogging: false }, "Process Execution Events": { - logInfo: true, + logInfo: false, logWarnings: false, logErrors: true, logContent: false, intensiveLogging: false }, "Process Output": { - logInfo: true, + logInfo: false, logWarnings: false, logErrors: true, logContent: false, diff --git a/StatusDependencies.js b/StatusDependencies.js index 21314c4c8..2b3615e32 100644 --- a/StatusDependencies.js +++ b/StatusDependencies.js @@ -18,11 +18,7 @@ return thisObject; function initialize(callBackFunction) { - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - /* Basic Valdidations */ if (bot.processNode.referenceParent.processDependencies !== undefined) { if (bot.processNode.referenceParent.processDependencies.statusDependencies !== undefined) { @@ -88,9 +84,6 @@ switch (err.message) { case global.DEFAULT_OK_RESPONSE.message: { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> onLoad -> Execution finished well. -> Status Dependency = " + JSON.stringify(statusDependency.dependency)) } - addReport(); return; } diff --git a/StatusReport.js b/StatusReport.js index 4da076767..d39a254fb 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -39,10 +39,7 @@ return thisObject; function initialize(pStatusDependencyNode, callBackFunction) { - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - statusDependencyNode = pStatusDependencyNode; logger.fileName = MODULE_NAME + "." + statusDependencyNode.type + "." + statusDependencyNode.name + "." + statusDependencyNode.id; From be561875ace20fa4156b6cb1d98bb0b57268b8fe Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Aug 2020 11:10:27 +0200 Subject: [PATCH 039/112] Daily Files Running --- LowFrequencyTrading/TradingEngine.js | 40 +++++++++++++++++++++++++- LowFrequencyTrading/TradingPosition.js | 2 +- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 292b58dfc..5bbce94c2 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -25,6 +25,8 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { tradingEngine = bot.simulationState.tradingEngine sessionParameters = bot.SESSION.parameters + initializeNodeMap(tradingEngine) + if (bot.FIRST_EXECUTION === true) { /* Here we will go through all the nodes in the Trading Engine hiriarchy and @@ -98,11 +100,47 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } } - function initializeNode(node) { + 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 nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== 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 < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[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) { diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index a9d621fd3..076c262e8 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -238,7 +238,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function resetTradingEngineDataStructure() { if (tradingEngine.current.position.status.value === 'Closed') { - tradingEngine.initializeNode(tradingEngine.current.position) + tradingEngineModule.initializeNode(tradingEngine.current.position) } } From 2b6aa4c81613accfd73b07d97c067456e4768a37 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Aug 2020 17:17:56 +0200 Subject: [PATCH 040/112] Update TradingRecords.js --- LowFrequencyTrading/TradingRecords.js | 54 ++++++++++++++++++--------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index d80bac553..d1adacb73 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -89,7 +89,9 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { */ let productRootNode = productRoot[index] let record = scanRecordDefinition(product, productRootNode, index) - persistIndividualRecord(record, product, outputDatasetArray) + if (record !== undefined) { + persistIndividualRecord(record, product, outputDatasetArray) + } } } else { /* @@ -101,7 +103,9 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { */ let productRootNode = productRoot let record = scanRecordDefinition(product, productRootNode) - persistIndividualRecord(record, product, outputDatasetArray) + if (record !== undefined) { + persistIndividualRecord(record, product, outputDatasetArray) + } } } } @@ -192,6 +196,13 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { 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) } @@ -213,27 +224,36 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { let propertyValue = record[j] if (bot.processingDailyFiles) { - /* - 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(tradingEngine.current.episode.candle.end.value) - let nextDay = new Date(tradingEngine.current.episode.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 (product.config.doNotCutObjectInDays !== true) { + /* + By default we will cut objects in days. */ - if (propertyValue === product.config.propertyValueThatClosesObject) { + if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { outputDatasetArray.push(record) } } else { /* - When we are not at the end of the day, we will save the object normally, like in market files. + 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. */ - if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { - outputDatasetArray.push(record) + let currentDay = new Date(tradingEngine.current.episode.candle.end.value) + let nextDay = new Date(tradingEngine.current.episode.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) + } } } } From 9edbd6916940885ac7babd6452ff900330dd25e9 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Aug 2020 18:04:21 +0200 Subject: [PATCH 041/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 530277879..a51039a9c 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -98,9 +98,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM for (let i = 0; i < orders.length; i++) { let tradingSystemOrder = orders[i] + tradingSystemValidations(tradingSystemOrder) let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) - - definitionValidations(tradingSystemOrder, tradingEngineOrder) + tradingEngineValidations(tradingEngineOrder) switch (tradingEngineOrder.status.value) { case 'Not Open': { @@ -158,11 +158,13 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } - function definitionValidations(tradingSystemOrder, tradingEngineOrder) { + function tradingSystemValidations(tradingSystemOrder) { /* Trading System Validations */ if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } if (tradingSystemOrder.referenceParent === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder) } + } + function tradingEngineValidations(tradingEngineOrder) { /* Trading Engine Order Validations */ if (tradingEngineOrder.serialNumber === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.serialNumber === undefined', tradingEngineOrder) } if (tradingEngineOrder.identifier === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.identifier === undefined', tradingEngineOrder) } @@ -185,7 +187,6 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (tradingEngineOrder.orderQuotedAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.size === undefined', tradingEngineOrder) } if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined', tradingEngineOrder) } if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.feesPaid === undefined', tradingEngineOrder) } - } async function tryToOpenOrder(tradingEngineStage, executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { From a3414ccb7ded24c9891e23ef22d24521b66b0fdd Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Aug 2020 20:04:45 +0200 Subject: [PATCH 042/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 66 +++++++++++++++++++--------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index a51039a9c..21085fbd6 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -459,28 +459,54 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } function percentageFilledSimulation() { - /* Order Filling Simulation */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { - - /* Percentage Filled */ - let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 - if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { - percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + /* + Order Filling Simulation: the way we are going to handle this is the following. + For buy orders we are going to check if the price was below the order rate, and + for sell orders we are going to check if the price was above the order rate. + + If we have a partial filling defined we are going to use it to estimate how much + of the order we will consider filled. + */ + let orderWasHit + switch (tradingSystemOrder.type) { + case 'Limit Buy Order': { + if (tradingEngine.current.episode.candle.min.value <= tradingEngineOrder.rate.value) { + orderWasHit = true } - tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - - /* Check if we need to close */ - if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) { - - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Filled' - - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + break + } + case 'Limit Sell Order': { + if (tradingEngine.current.episode.candle.max.value >= tradingEngineOrder.rate.value) { + orderWasHit = true } + break + } + } + if (orderWasHit === true) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { + + /* Percentage Filled */ + let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 + if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { + percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + } + tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + } + } else { + /* We will assume that 100% of the order was filled */ + tradingEngineOrder.orderStatistics.percentageFilled.value = 100 + } + /* Check if we need to close the order */ + if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Filled' + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 } } } From ab2fc2966c9e2a6e423c6c33a8b38e5c38002b8d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 9 Aug 2020 21:13:57 +0200 Subject: [PATCH 043/112] Update TradingRecords.js --- LowFrequencyTrading/TradingRecords.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index d1adacb73..c24940b16 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -90,6 +90,11 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { 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 trading engine data structure it belongs. + */ + record.push(index) persistIndividualRecord(record, product, outputDatasetArray) } } From 2c6a6a8d1ce8512a17a5f53bb0195a8f114ba4d7 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 10 Aug 2020 13:48:45 +0200 Subject: [PATCH 044/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 21085fbd6..835427233 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -147,7 +147,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (situationName !== undefined) { /* Simulate Order Cancelation, if needed. */ - simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') /* Cancel the order at the Exchange, if needed. */ await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') @@ -349,7 +349,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* If the Stage is Closing and this order is still open, we need to cancel it now */ if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') { - simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') + simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Closing Stage') } function actualRateSimulation() { @@ -760,7 +760,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } - function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + function simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, exitType) { /* Filter by Session Type */ switch (bot.SESSION.type) { @@ -833,30 +833,30 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM function recalculateStageSize(tradingEngineStage, tradingEngineOrder) { /* - Since the order is Cancelled, we need to adjust the stage size. Remember that the Stage Size - accumulates for each asset, the order size placed at the exchange. A cancelation means that + Since the order is Cancelled, we need to adjust the stage sizePlaced. Remember that the Stage + Size Placed accumulates for each asset, the order size placed at the exchange. A cancelation means that only the part filled can be considered placed, so we need to substract from the stage size the remainder. To achieve this with the information we currently have, we are going first to unaccount the order size, and the account only the sizeFilled + the feesPaid. */ - tradingEngineStage.stageBaseAsset.size.value = - tradingEngineStage.stageBaseAsset.size.value - + tradingEngineStage.stageBaseAsset.sizePlaced.value = + tradingEngineStage.stageBaseAsset.sizePlaced.value - tradingEngineOrder.orderBaseAsset.size.value - tradingEngineStage.stageQuotedAsset.size.value = - tradingEngineStage.stageQuotedAsset.size.value - + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value - tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineStage.stageBaseAsset.size.value = - tradingEngineStage.stageBaseAsset.size.value + + tradingEngineStage.stageBaseAsset.sizePlaced.value = + tradingEngineStage.stageBaseAsset.sizePlaced.value + tradingEngineOrder.orderBaseAsset.sizeFilled.value + tradingEngineOrder.orderBaseAsset.feesPaid.value - tradingEngineStage.stageQuotedAsset.size.value = - tradingEngineStage.stageQuotedAsset.size.value + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value + tradingEngineOrder.orderQuotedAsset.sizeFilled.value + tradingEngineOrder.orderQuotedAsset.feesPaid.value - tradingEngineStage.stageBaseAsset.size.value = global.PRECISE(tradingEngineStage.stageBaseAsset.size.value, 10) - tradingEngineStage.stageQuotedAsset.size.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.size.value, 10) + tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10) + tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) } function checkOrderEvent(event, order, executionAlgorithm, executionNode) { From 3ba4aae0e880c7a4c61540c17b9ef95468310f96 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 10 Aug 2020 16:09:18 +0200 Subject: [PATCH 045/112] Independent Reset --- LowFrequencyTrading/TradingEngine.js | 5 +++ LowFrequencyTrading/TradingEpisode.js | 6 ++- LowFrequencyTrading/TradingExecution.js | 5 +++ LowFrequencyTrading/TradingOrders.js | 55 +++++++++++++++--------- LowFrequencyTrading/TradingPosition.js | 8 +++- LowFrequencyTrading/TradingSimulation.js | 37 +++++++++++++--- LowFrequencyTrading/TradingStages.js | 12 +++++- LowFrequencyTrading/TradingStrategy.js | 8 +++- LowFrequencyTrading/TradingSystem.js | 25 +++++------ 9 files changed, 115 insertions(+), 46 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 5bbce94c2..a230f5d4d 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -6,6 +6,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { const MODULE_NAME = 'Trading Engine' let thisObject = { mantain: mantain, + reset: reset, getNodeById: getNodeById, cloneValues: cloneValues, setCurrentCandle: setCurrentCandle, @@ -46,6 +47,10 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { updateDistanceToEventsCounters() } + function reset() { + + } + function getNodeById(NodeId) { return nodesMap.get(NodeId) } diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index a3207a285..a654de03a 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -5,6 +5,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin const MODULE_NAME = 'Trading Episode' let thisObject = { mantain: mantain, + reset: reset, openEpisode: openEpisode, updateExitType: updateExitType, closeEpisode: closeEpisode, @@ -33,12 +34,15 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin } function mantain() { - resetTradingEngineDataStructure() updateCounters() updateStatistics() updateEnds() } + function reset() { + resetTradingEngineDataStructure() + } + function openEpisode() { /* This function is called each time the simulation starts. That does not mean the Episode diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 95a929bcf..eb3cb4bdf 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -6,6 +6,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE let thisObject = { mantain: mantain, + reset: reset, runExecution: runExecution, initialize: initialize, finalize: finalize @@ -41,6 +42,10 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE tradingOrdersModule.mantain() } + function reset() { + tradingOrdersModule.reset() + } + async function runExecution( executionNode, tradingEngineStage diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 835427233..cb38120c4 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -6,6 +6,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let thisObject = { mantain: mantain, + reset: reset, checkOrders: checkOrders, initialize: initialize, finalize: finalize @@ -45,6 +46,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } function mantain() { + mantainReset(true, false) + } + + function reset() { + mantainReset(false, true) + } + + function mantainReset(mantain, reset) { if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return } @@ -53,42 +62,46 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage executionNode = stageNode.openExecution - mantainExecutionNode(executionNode, tradingEngine.current.strategyOpenStage.status.value) + processExecutionNode(executionNode, tradingEngine.current.strategyOpenStage.status.value) stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage executionNode = stageNode.closeExecution - mantainExecutionNode(executionNode, tradingEngine.current.strategyCloseStage.status.value) + processExecutionNode(executionNode, tradingEngine.current.strategyCloseStage.status.value) - function mantainExecutionNode(executionNode, stageStatus) { + function processExecutionNode(executionNode, stageStatus) { - mantainExecutionAlgorithms(executionNode) + preocessExecutionAlgorithms(executionNode) - function mantainExecutionAlgorithms(executionNode) { + function preocessExecutionAlgorithms(executionNode) { for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { let executionAlgorithm = executionNode.executionAlgorithms[i] - mantainOrders(executionAlgorithm.marketBuyOrders) - mantainOrders(executionAlgorithm.marketSellOrders) - mantainOrders(executionAlgorithm.limitBuyOrders) - mantainOrders(executionAlgorithm.limitSellOrders) + processOrders(executionAlgorithm.marketBuyOrders) + processOrders(executionAlgorithm.marketSellOrders) + processOrders(executionAlgorithm.limitBuyOrders) + processOrders(executionAlgorithm.limitSellOrders) } } - function mantainOrders(orders) { + function processOrders(orders) { for (let i = 0; i < orders.length; i++) { let tradingSystemOrder = orders[i] - if (tradingSystemOrder.referenceParent === undefined) { continue } + if (tradingSystemOrder.referenceParent === undefined) { + badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder) + } let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) - try { - if (tradingEngineOrder.status === undefined) { continue } - } catch (err) { - console.log(err.stack) + + if (tradingEngineOrder.status === undefined) { + badDefinitionUnhandledException(undefined, 'tradingEngineOrder.status === undefined', tradingEngineOrder) } - - updateCounters(tradingEngineOrder) - updateEnds(tradingEngineOrder) - updateStatistics(tradingEngineOrder) - resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus) + if (mantain === true) { + updateCounters(tradingEngineOrder) + updateEnds(tradingEngineOrder) + updateStatistics(tradingEngineOrder) + } + if (reset === true) { + resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus) + } } } } @@ -216,7 +229,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Create Order Procedure */ tradingEngineOrder.status.value = 'Open' tradingEngineOrder.identifier.value = global.UNIQUE_ID() - tradingEngineOrder.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngineOrder.begin.value = tradingEngine.current.episode.candle.end.value tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value tradingEngineOrder.orderName.value = tradingSystemOrder.name diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 076c262e8..86016d7f4 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -5,6 +5,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng const MODULE_NAME = 'Trading Position' let thisObject = { mantain: mantain, + reset: reset, openPosition: openPosition, closingPosition: closingPosition, closePosition: closePosition, @@ -39,18 +40,21 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } function mantain() { - resetTradingEngineDataStructure() updateCounters() updateEnds() } + function reset() { + resetTradingEngineDataStructure() + } + function openPosition(situationName) { /* Recording the opening at the Trading Engine Data Structure */ tradingEngine.current.position.status.value = 'Open' tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value tradingEngine.current.position.identifier.value = global.UNIQUE_ID() - tradingEngine.current.position.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngine.current.position.begin.value = tradingEngine.current.episode.candle.end.value tradingEngine.current.position.beginRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.position.positionBaseAsset.beginBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.beginBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index acd5d239d..db5133c52 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -137,16 +137,41 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT /* The chart was recalculated based on the current candle. */ tradingSystemModule.updateChart(chart) - /* Do the stuff needed previous to the run */ + /* + Do the stuff needed previous to the run like + Episode Counters and Statistics update + */ tradingSystemModule.mantain() - - /* Episode Counters and Statistics update */ tradingEpisodeModule.mantain() - - /* Run your maintaince procedure */ tradingEngineModule.mantain() - /* Run one cycle of the Trading System*/ + /* Reset Data Structures */ + tradingSystemModule.reset() + tradingEpisodeModule.reset() + tradingEngineModule.reset() + + /* + Run the first cycle of the Trading 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. + */ + await tradingSystemModule.run() + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords() + + /* Reset Data Structures */ + tradingSystemModule.reset() + tradingEpisodeModule.reset() + tradingEngineModule.reset() + + /* + Run the second cycle of the Trading System. During this second run + some new orders might be created at slots freed up during the first + run. This allows for example for an 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. + */ await tradingSystemModule.run() controlLoop() diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 1f19fd31b..e6735e3b9 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -6,6 +6,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM let thisObject = { updateChart: updateChart, mantain: mantain, + reset: reset, runTriggerStage: runTriggerStage, runOpenStage: runOpenStage, runManageStage: runManageStage, @@ -88,11 +89,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingStrategyModule.mantain() tradingExecutionModule.mantain() - resetTradingEngineDataStructure() updateCounters() updateEnds() } + function reset() { + tradingPositionModule.reset() + tradingStrategyModule.reset() + tradingExecutionModule.reset() + + resetTradingEngineDataStructure() + } + function runTriggerStage() { /* We check if we will be triggering on, off or taking position. @@ -1017,7 +1025,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function openStage(stage) { /* Recording the opening at the Trading Engine Data Structure */ - stage.begin.value = tradingEngine.current.episode.candle.begin.value + stage.begin.value = tradingEngine.current.episode.candle.end.value stage.beginRate.value = tradingEngine.current.episode.candle.close.value stage.end.value = tradingEngine.current.episode.candle.end.value } diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index 87216fc29..5225f705d 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -5,6 +5,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng const MODULE_NAME = 'Trading Strategy' let thisObject = { mantain: mantain, + reset: reset, openStrategy: openStrategy, closeStrategy: closeStrategy, initialize: initialize, @@ -24,16 +25,19 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng } function mantain() { - resetTradingEngineDataStructure() updateCounters() updateEnds() } + function reset() { + resetTradingEngineDataStructure() + } + function openStrategy(index, situationName, strategyName) { tradingEngine.current.strategy.status.value = 'Open' tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value tradingEngine.current.strategy.identifier.value = global.UNIQUE_ID() - tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.candle.end.value tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value tradingEngine.current.strategy.beginRate.value = tradingEngine.current.episode.candle.min.value diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index b84db7f45..ae6c839a5 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -6,6 +6,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM const MODULE_NAME = 'Trading System' let thisObject = { mantain: mantain, + reset: reset, run: run, updateChart: updateChart, initialize: initialize, @@ -97,19 +98,19 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } function mantain() { - reset() - tradingStagesModule.mantain() + tradingSystem.highlights = [] + tradingSystem.errors = [] + tradingSystem.values = [] + tradingSystem.status = [] + tradingSystem.progress = [] + tradingSystem.running = [] + tradingSystem.announcements = [] - function reset() { - /* This function helps reset data structures at every cycle of the simulation loop/=. */ - tradingSystem.highlights = [] - tradingSystem.errors = [] - tradingSystem.values = [] - tradingSystem.status = [] - tradingSystem.progress = [] - tradingSystem.running = [] - tradingSystem.announcements = [] - } + tradingStagesModule.mantain() + } + + function reset() { + tradingStagesModule.reset() } function updateChart(pChart) { From 8c375127602e9852f9dfaf6a78a77702ec3f5f94 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 10 Aug 2020 18:56:25 +0200 Subject: [PATCH 046/112] Cycles --- LowFrequencyTrading/TradingEngine.js | 26 ++++++++++++++++++++++++ LowFrequencyTrading/TradingSimulation.js | 2 ++ LowFrequencyTrading/TradingSystem.js | 12 +++++------ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index a230f5d4d..4ae374756 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -10,6 +10,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { getNodeById: getNodeById, cloneValues: cloneValues, setCurrentCandle: setCurrentCandle, + setCurrentCycle: setCurrentCycle, initializeNode: initializeNode, initialize: initialize, finalize: finalize @@ -64,6 +65,31 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { tradingEngine.current.episode.candle.max.value = candle.max } + function setCurrentCycle(cycle) { + switch (cycle) { + case 'First': { + tradingEngine.current.episode.cycle.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngine.current.episode.cycle.end.value = + tradingEngine.current.episode.candle.begin.value + + ( + tradingEngine.current.episode.candle.end.value - + tradingEngine.current.episode.candle.begin.value + 1 + ) / 2 - 1 + break + } + case 'Second': { + tradingEngine.current.episode.cycle.begin.value = + tradingEngine.current.episode.candle.begin.value + + ( + tradingEngine.current.episode.candle.end.value - + tradingEngine.current.episode.candle.begin.value + 1 + ) / 2 + tradingEngine.current.episode.cycle.end.value = tradingEngine.current.episode.candle.end.value + break + } + } + } + function cloneValues(originNode, destinationNode) { if (originNode === undefined) { return } if (destinationNode === undefined) { return } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index db5133c52..934172f40 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -155,6 +155,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT give some room so that orders can be canceled or filled and we can write those records into the output memory. */ + tradingEngineModule.setCurrentCycle('First') await tradingSystemModule.run() /* Add new records to the process output */ @@ -172,6 +173,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT first run, and the same Limit Order definition to spawn a new order without the need to wait until the next candle. */ + tradingEngineModule.setCurrentCycle('Second') await tradingSystemModule.run() controlLoop() diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index ae6c839a5..83148dbad 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -98,6 +98,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } function mantain() { + tradingStagesModule.mantain() + } + + function reset() { + tradingStagesModule.reset() + tradingSystem.highlights = [] tradingSystem.errors = [] tradingSystem.values = [] @@ -105,12 +111,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingSystem.progress = [] tradingSystem.running = [] tradingSystem.announcements = [] - - tradingStagesModule.mantain() - } - - function reset() { - tradingStagesModule.reset() } function updateChart(pChart) { From f9dfe244400272cbf5ab6cecfb6657803dcd1451 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 11 Aug 2020 13:28:43 +0200 Subject: [PATCH 047/112] Orders Ends with cycle --- LowFrequencyTrading/TradingEngine.js | 21 +++++++++++---------- LowFrequencyTrading/TradingOrders.js | 18 ++++++++++++++++-- LowFrequencyTrading/TradingRecords.js | 10 +++++++++- LowFrequencyTrading/TradingSimulation.js | 8 ++++---- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 4ae374756..159e5d392 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -68,23 +68,24 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { function setCurrentCycle(cycle) { switch (cycle) { case 'First': { - tradingEngine.current.episode.cycle.begin.value = tradingEngine.current.episode.candle.begin.value + tradingEngine.current.episode.cycle.begin.value = + tradingEngine.current.episode.candle.begin.value + + sessionParameters.timeFrame.config.value tradingEngine.current.episode.cycle.end.value = tradingEngine.current.episode.candle.begin.value + - ( - tradingEngine.current.episode.candle.end.value - - tradingEngine.current.episode.candle.begin.value + 1 - ) / 2 - 1 + sessionParameters.timeFrame.config.value + + sessionParameters.timeFrame.config.value / 4 - 1 break } case 'Second': { tradingEngine.current.episode.cycle.begin.value = tradingEngine.current.episode.candle.begin.value + - ( - tradingEngine.current.episode.candle.end.value - - tradingEngine.current.episode.candle.begin.value + 1 - ) / 2 - tradingEngine.current.episode.cycle.end.value = tradingEngine.current.episode.candle.end.value + sessionParameters.timeFrame.config.value + + sessionParameters.timeFrame.config.value / 4 + tradingEngine.current.episode.cycle.end.value = + tradingEngine.current.episode.candle.begin.value + + sessionParameters.timeFrame.config.value + + sessionParameters.timeFrame.config.value * 2 / 4 - 1 break } } diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index cb38120c4..24819f706 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -164,6 +164,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Cancel the order at the Exchange, if needed. */ await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + + updateEndsWithCycle(tradingEngineOrder) } } } @@ -229,8 +231,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Create Order Procedure */ tradingEngineOrder.status.value = 'Open' tradingEngineOrder.identifier.value = global.UNIQUE_ID() - tradingEngineOrder.begin.value = tradingEngine.current.episode.candle.end.value - tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value + tradingEngineOrder.begin.value = tradingEngine.current.episode.cycle.begin.value + tradingEngineOrder.end.value = tradingEngine.current.episode.cycle.end.value tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value tradingEngineOrder.orderName.value = tradingSystemOrder.name tradingEngineOrder.algorithmName.value = executionAlgorithm.name @@ -520,6 +522,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + + updateEndsWithCycle(tradingEngineOrder) } } } @@ -576,6 +580,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + + updateEndsWithCycle(tradingEngineOrder) } if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { @@ -585,6 +591,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + + updateEndsWithCycle(tradingEngineOrder) } if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { @@ -594,6 +602,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + + updateEndsWithCycle(tradingEngineOrder) } syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) @@ -904,6 +914,10 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } + function updateEndsWithCycle(tradingEngineOrder) { + tradingEngineOrder.end.value = tradingEngine.current.episode.cycle.end.value + } + function updateCounters(tradingEngineOrder) { if (tradingEngineOrder.status.value === 'Open') { tradingEngineOrder.orderCounters.periods.value++ diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index c24940b16..d2dc16ed1 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -31,7 +31,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { outputDatasetsMap = undefined } - function appendRecords() { + function appendRecords(cycle) { /* 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. @@ -53,6 +53,14 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { } function persistRecords() { + /* + There are a few products configured to be saved only at an specific cycle. + */ + if (product.config.saveAtCycle !== undefined) { + if (cycle !== product.config.saveAtCycle) { + return + } + } /* Clean the file from information of previous executions */ pruneOutputFile(product, outputDatasetArray) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 934172f40..9827d3a9d 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -159,7 +159,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT await tradingSystemModule.run() /* Add new records to the process output */ - tradingRecordsModule.appendRecords() + tradingRecordsModule.appendRecords('First') /* Reset Data Structures */ tradingSystemModule.reset() @@ -198,7 +198,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (tradingEngine.current.episode.candle.index.value + 1 < candles.length) { /* Add new records to the process output */ - tradingRecordsModule.appendRecords() + tradingRecordsModule.appendRecords('Second') tradingEngine.current.episode.candle.index.value++ @@ -213,7 +213,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingEpisodeModule.closeEpisode() /* Add new records to the process output */ - tradingRecordsModule.appendRecords() + tradingRecordsModule.appendRecords('Second') afterLoop() } @@ -222,7 +222,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingEpisodeModule.updateExitType(exitType) /* Add new records to the process output */ - tradingRecordsModule.appendRecords() + tradingRecordsModule.appendRecords('Second') afterLoop() } From 99f26cc0144afbbfb92acc894702bb8baae2e0bd Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 11 Aug 2020 20:20:45 +0200 Subject: [PATCH 048/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 24819f706..ca57a2380 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -348,9 +348,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value actualRateSimulation() - feesPaidSimulation() percentageFilledSimulation() sizeFilledSimulation() + feesPaidSimulation() doTheAccounting( tradingEngineStage, @@ -422,7 +422,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * + tradingEngineOrder.orderBaseAsset.sizeFilled.value * tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 calculatedBasedOnTradingSystem = true @@ -431,7 +431,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * + tradingEngineOrder.orderQuotedAsset.sizeFilled.value * tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 calculatedBasedOnTradingSystem = true @@ -446,11 +446,11 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM case 'Market Order': { tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * + tradingEngineOrder.orderBaseAsset.sizeFilled.value * bot.SESSION.parameters.feeStructure.config.taker / 100 tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * + tradingEngineOrder.orderQuotedAsset.sizeFilled.value * bot.SESSION.parameters.feeStructure.config.taker / 100 break @@ -458,11 +458,11 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM case 'Limit Order': { tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * + tradingEngineOrder.orderBaseAsset.sizeFilled.value * bot.SESSION.parameters.feeStructure.config.maker / 100 tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * + tradingEngineOrder.orderQuotedAsset.sizeFilled.value * bot.SESSION.parameters.feeStructure.config.maker / 100 break From 3d351bfb8646d751ed43257d2e07c5be24164442 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 12 Aug 2020 16:25:34 +0200 Subject: [PATCH 049/112] bug fixed --- LowFrequencyTrading/TradingRecords.js | 7 ++++++- LowFrequencyTrading/TradingSimulation.js | 7 ++++++- TaskServer.sln | 25 ------------------------ server.js | 2 ++ 4 files changed, 14 insertions(+), 27 deletions(-) delete mode 100644 TaskServer.sln diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index d2dc16ed1..274a60380 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -337,7 +337,12 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { let end = record[j] if (end >= tradingEngine.current.episode.candle.end.value) { outputFile.splice(i, 1) - pruneOutputFile(product, outputFile) + /* + 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) return } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 9827d3a9d..6a8e98a75 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -202,7 +202,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingEngine.current.episode.candle.index.value++ - setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. + /* + This will execute the next loop 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(loop) } else { updateEpisode('All Candles Processed') } diff --git a/TaskServer.sln b/TaskServer.sln deleted file mode 100644 index a21837e88..000000000 --- a/TaskServer.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Project", "Project.njsproj", "{AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9128DF06-8CA5-497C-8BF7-B924571C5394} - EndGlobalSection -EndGlobal diff --git a/server.js b/server.js index 80d7a1c9e..4aa0957d2 100644 --- a/server.js +++ b/server.js @@ -1,3 +1,5 @@ + + /* Callbacks default responses. */ global.DEFAULT_OK_RESPONSE = { result: "Ok", From 8334709e3080f868a96b2565474cb5c91f7a94af Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 12 Aug 2020 20:30:52 +0200 Subject: [PATCH 050/112] Code Tided --- LowFrequencyTrading/TradingOrders.js | 50 +++-- LowFrequencyTrading/TradingPosition.js | 62 ++++--- LowFrequencyTrading/TradingSimulation.js | 1 + LowFrequencyTrading/TradingStages.js | 227 +++++++++++------------ 4 files changed, 180 insertions(+), 160 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index ca57a2380..d03b913ef 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -268,24 +268,42 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } if (isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true', tradingSystemOrder) } - /* Size in Base Asset */ - tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 + /* + The Size calculation depends on how the user defined the size of the position. + The user could have defined the size of the position in Base Asset or Quoted Asset. + */ + switch (tradingEngineStage.stageDefinedIn.value) { + case 'Base Asset': { + /* Size in Base Asset */ + tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 - /* Size in Quoted Asset */ - tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 + /* Check that the Size calculated would not surpass Stage Target Size */ + if ( + tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value > + tradingEngineStage.stageBaseAsset.targetSize.value + ) { + tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value + } - /* Check that the Size calculated would not surpass Stage Target Size */ - if ( - tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value > - tradingEngineStage.stageBaseAsset.targetSize.value - ) { - tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value - } - if ( - tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value > - tradingEngineStage.stageQuotedAsset.targetSize.value - ) { - tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value + /* Size in Quoted Asset */ + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value + break + } + case 'Quoted Asset': { + /* Size in Quoted Asset */ + tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 + + if ( + tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value > + tradingEngineStage.stageQuotedAsset.targetSize.value + ) { + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value + } + + /* Size in Base Asset */ + tradingEngineOrder.orderBaseAsset.size.value = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value + break + } } tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 86016d7f4..43b85e7a5 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -112,37 +112,37 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.takeProfit.takeProfitStage.value = stage } - function initialTargets(stageNode) { + function initialTargets(tradingSystemStageNode, tradingEngineStageNode) { - if (stageNode.initialTargets === undefined) { + if (tradingSystemStageNode.initialTargets === undefined) { const message = 'Stage without Initial Targets node. Add one please.' - badDefinitionUnhandledException(undefined, message, stageNode) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode) } setTargetRate() setTargetSize() function setTargetRate() { - if (stageNode.initialTargets.targetRate === undefined) { + if (tradingSystemStageNode.initialTargets.targetRate === undefined) { const message = 'Target Rate Node not Found. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets) } - if (stageNode.initialTargets.targetRate.formula === undefined) { + if (tradingSystemStageNode.initialTargets.targetRate.formula === undefined) { const message = 'Formula of Target Rate Node not Found. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate) } - let value = tradingSystem.formulas.get(stageNode.initialTargets.targetRate.formula.id) + let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetRate.formula.id) if (value === undefined) { const message = 'Target Rate can not be undefined. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate.formula) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula) } if (isNaN(value)) { const message = 'Target Rate must be a number. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate.formula) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula) } - switch (stageNode.type) { + switch (tradingSystemStageNode.type) { case 'Open Stage': { tradingEngine.current.position.entryTargetRate.value = global.PRECISE(value, 10) break @@ -157,27 +157,27 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng function setTargetSize() { /* Basic Validation */ if ( - stageNode.initialTargets.targetSizeInBaseAsset !== undefined && - stageNode.initialTargets.targetSizeInQuotedAsset !== undefined + tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined && + tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined ) { const message = 'Only Target Size In Base Asset or Target Size In Quoted Asset is allowed. Remove one of them please.' - badDefinitionUnhandledException(undefined, message, stageNode) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode) } /* Position In Base Asset */ - if (stageNode.initialTargets.targetSizeInBaseAsset !== undefined) { - if (stageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) { - let value = tradingSystem.formulas.get(stageNode.initialTargets.targetSizeInBaseAsset.formula.id) + if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined) { + if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) { + let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula.id) if (value === undefined) { const message = 'Target Size In Base Asset cannot be undefined. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset.formula) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula) } if (value === undefined) { const message = 'Target Size In Base Asset cannot be zero. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset.formula) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula) } - switch (stageNode.type) { + switch (tradingSystemStageNode.type) { case 'Open Stage': { tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = global.PRECISE(value, 10) tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = @@ -191,25 +191,28 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng break } } + + /* Remember how the end user defined this stage. */ + tradingEngineStageNode.stageDefinedIn.value = 'Base Asset' } else { const message = 'You need to specify a Formula for this.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset) } } /* Position In Quoted Asset */ - if (stageNode.initialTargets.targetSizeInQuotedAsset !== undefined) { - if (stageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) { - let value = tradingSystem.formulas.get(stageNode.initialTargets.targetSizeInQuotedAsset.formula.id) + if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined) { + if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) { + let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula.id) if (value === undefined) { const message = 'Target Size In Quoted Asset cannot be undefined. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula) } if (value === undefined) { const message = 'Target Size In Quoted Asset cannot be zero. Fix this please.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula) } - switch (stageNode.type) { + switch (tradingSystemStageNode.type) { case 'Open Stage': { tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = global.PRECISE(value, 10) tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = @@ -223,9 +226,12 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng break } } + + /* Remember how the end user defined this stage. */ + tradingEngineStageNode.stageDefinedIn.value = 'Quoted Asset' } else { const errorText = 'You need to specify a Formula for this.' - badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula) + badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula) } } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 6a8e98a75..46ab10021 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -197,6 +197,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (tradingEngine.current.episode.candle.index.value + 1 < candles.length) { + console.log('Processing Candle: ' + tradingEngine.current.episode.candle.index.value) /* Add new records to the process output */ tradingRecordsModule.appendRecords('Second') diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index e6735e3b9..a38245d34 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -273,27 +273,27 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') { /* This procedure is intended to run only once */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = stageNode.openExecution + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let executionNode = tradingSystemStage.openExecution /* Reset the Exchange Orders data structure to its initial value */ tradingEngineModule.initializeNode(tradingEngine.exchangeOrders) - tradingSystem.evalConditions(stageNode, 'Initial Definition') - tradingSystem.evalFormulas(stageNode, 'Initial Definition') + tradingSystem.evalConditions(tradingSystemStage, 'Initial Definition') + tradingSystem.evalFormulas(tradingSystemStage, 'Initial Definition') /* Remember the balance we had before taking the position to later calculate profit or loss */ tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value /* Entry Position size and rate */ - tradingSystem.evalFormulas(stageNode, 'Initial Targets') - tradingPositionModule.initialTargets(stageNode) + tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets') + tradingPositionModule.initialTargets(tradingSystemStage, tradingEngine.current.strategyOpenStage) initializeStageTargetSize() /* Check Execution at open stage node */ - tradingSystem.evalConditions(stageNode, 'Open Execution') - tradingSystem.evalFormulas(stageNode, 'Open Execution') + tradingSystem.evalConditions(tradingSystemStage, 'Open Execution') + tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution') await tradingExecutionModule.runExecution( executionNode, @@ -311,55 +311,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM While the Open Stage is Open, we do our regular stuff: place orders and check what happened to the orders already placed. */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = stageNode.openExecution + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let executionNode = tradingSystemStage.openExecution /* Every Loop Cycle Activity */ - tradingSystem.evalConditions(stageNode, 'Open Execution') - tradingSystem.evalFormulas(stageNode, 'Open Execution') + tradingSystem.evalConditions(tradingSystemStage, 'Open Execution') + tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution') await tradingExecutionModule.runExecution( executionNode, tradingEngine.current.strategyOpenStage ) - /* - The Open is finished when the placedSize reaches the targetSize in any of the - stage assets, and the fillSize + feesPaid reaches the placedSize also in any - of the stage Assets. - This can happens at any time when we update the sizeFilled and the feesPaid values - when we see at the exchange that orders were filled. - */ - if ( - ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize - - || - - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced >= - tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize - ) - && - ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value - - || - - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value - ) - ) { - changeStageStatus('Open Stage', 'Closed', 'Position Size Filled') - } else { - /* Check the Close Stage Event */ - tradingSystem.evalConditions(stageNode, 'Close Stage Event') - if (checkStopStageEvent(stageNode) === true) { - changeStageStatus('Open Stage', 'Closing', 'Close Stage Event') - } - } + checkIfStageNeedsToBeClosed(tradingEngine.current.strategyOpenStage, tradingSystemStage, 'Open Stage') } /* Closing Status Procedure */ @@ -368,15 +331,15 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM During the closing stage status, we do not place new orders, just check if the ones placed were filled, and cancel the ones not filled. */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = stageNode.openExecution + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let executionNode = tradingSystemStage.openExecution /* Check if there are unfilled orders, we will check if they were executed, and cancel the ones that were not. */ - tradingSystem.evalConditions(stageNode, 'Open Execution') - tradingSystem.evalFormulas(stageNode, 'Open Execution') + tradingSystem.evalConditions(tradingSystemStage, 'Open Execution') + tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution') await tradingExecutionModule.runExecution( executionNode, @@ -384,24 +347,33 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM ) /* - The Closing is finished when the fillSize + feesPaid reaches the ordersSize. + The Closing is finished when the sizeFilled + feesPaid reaches the sizePlaced. This can happens either because we update the sizeFilled value when we see - at the exchange that orders were filled, or we reduce the ordersSize when + at the exchange that orders were filled, or we reduce the sizePlaced when we cancel not yet filled orders. Here it does not matter the targetSize since this status represent a force closure of this stage. */ - if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value - - || - - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value - ) { - changeStageStatus('Open Stage', 'Closed') + switch (tradingEngine.current.strategyOpenStage.stageDefinedIn.value) { + case 'Base Asset': { + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value + ) { + changeStageStatus('Open Stage', 'Closed') + } + break + } + case 'Quoted Asset': { + if ( + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value + ) { + changeStageStatus('Open Stage', 'Closed') + } + break + } } } @@ -435,9 +407,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' || tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage') { - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - tradingSystem.evalConditions(stageNode, 'Initial Definition') - tradingSystem.evalFormulas(stageNode, 'Initial Definition') + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + tradingSystem.evalConditions(tradingSystemStage, 'Initial Definition') + tradingSystem.evalFormulas(tradingSystemStage, 'Initial Definition') } /* Stop Loss Management */ @@ -825,11 +797,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM the total filled size of the Open Stage. By skiping execution now we allow the open stage to get a final value for sizeFilled that we can use. */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage /* Exit Position size and rate */ - tradingSystem.evalFormulas(stageNode, 'Initial Targets') - tradingPositionModule.initialTargets(stageNode) + tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets') + tradingPositionModule.initialTargets(tradingSystemStage, tradingEngine.current.strategyCloseStage) initializeStageTargetSize() changeStageStatus('Close Stage', 'Open') @@ -841,55 +813,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM /* This will happen as long as the Close Stage is Open. */ - let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage - let executionNode = stageNode.closeExecution + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + let executionNode = tradingSystemStage.closeExecution - tradingSystem.evalConditions(stageNode, 'Close Execution') - tradingSystem.evalFormulas(stageNode, 'Close Execution') + tradingSystem.evalConditions(tradingSystemStage, 'Close Execution') + tradingSystem.evalFormulas(tradingSystemStage, 'Close Execution') await tradingExecutionModule.runExecution( executionNode, tradingEngine.current.strategyCloseStage ) - /* - The Open is finished when the placedSize reaches the targetSize in any of the - stage assets, and the fillSize + feesPaid reaches the placedSize also in any - of the stage Assets. - This can happens at any time when we update the sizeFilled and the feesPaid values - when we see at the exchange that orders were filled. - */ - if ( - ( - tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced >= - tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize - - || - - tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced >= - tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize - ) - && - ( - tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced.value - - || - - tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizeFilled.value + - tradingEngine.current.strategyCloseStage.stageQuotedAsset.feesPaid.value >= - tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced.value - ) - ) { - changeStageStatus('Close Stage', 'Closed', 'Size Placed Filled') - } else { - /* Check the Close Stage Event */ - tradingSystem.evalConditions(stageNode, 'Close Stage Event') - if (checkStopStageEvent(stageNode) === true) { - changeStageStatus('Close Stage', 'Closing', 'Close Stage Event') - } - } + checkIfStageNeedsToBeClosed(tradingEngine.current.strategyCloseStage, tradingSystemStage, 'Close Stage') } /* Exit Position Validation */ @@ -965,6 +900,66 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } } + function checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, stageName) { + /* + The Stage is closed when the placedSize reaches the targetSize, and the fillSize + + feesPaid reaches the placedSize. This depends on how the user defined the stage: + either on Base Asset or Quoted Asset. + This can happens at any time when we update the sizeFilled and the feesPaid values + when we see at the exchange that orders were filled. + */ + switch (tradingEngineStage.stageDefinedIn.value) { + case 'Base Asset': { + if ( + ( + tradingEngineStage.stageBaseAsset.sizePlaced >= + tradingEngineStage.stageBaseAsset.targetSize + ) + && + ( + tradingEngineStage.stageBaseAsset.sizeFilled.value + + tradingEngineStage.stageBaseAsset.feesPaid.value >= + tradingEngineStage.stageBaseAsset.sizePlaced.value + ) + ) { + positionFilled() + } else { + checkCloseStageEvent() + } + break + } + case 'Quoted Asset': { + if ( + ( + tradingEngineStage.stageQuotedAsset.sizePlaced >= + tradingEngineStage.stageQuotedAsset.targetSize + ) + && + ( + tradingEngineStage.stageQuotedAsset.sizeFilled.value + + tradingEngineStage.stageQuotedAsset.feesPaid.value >= + tradingEngineStage.stageQuotedAsset.sizePlaced.value + ) + ) { + positionFilled() + } else { + checkCloseStageEvent() + } + break + } + } + function positionFilled() { + changeStageStatus(stageName, 'Closed', 'Position Size Filled') + } + function checkCloseStageEvent() { + /* Check the Close Stage Event */ + tradingSystem.evalConditions(tradingSystemStage, 'Close Stage Event') + if (checkStopStageEvent(tradingSystemStage) === true) { + changeStageStatus(stageName, 'Closing', 'Close Stage Event') + } + } + } + function checkStopStageEvent(stage) { /* Check the Close Stage Event. */ From f0e661d13f5ee1687ccacd72f7a184da050df772 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 13 Aug 2020 13:18:38 +0200 Subject: [PATCH 051/112] Before Romoving Initial Definition --- LowFrequencyTrading/TradingPosition.js | 4 ++++ LowFrequencyTrading/TradingStages.js | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 43b85e7a5..1dbc6cc98 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -71,6 +71,10 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* Inicializing this counter */ tradingEngine.current.episode.distanceToEvent.takePosition.value = 1 + + /* Remember the balance we had before taking the position to later calculate profit or loss */ + tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value } function closingPosition(exitType) { diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index a38245d34..7961ac29b 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -282,10 +282,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.evalConditions(tradingSystemStage, 'Initial Definition') tradingSystem.evalFormulas(tradingSystemStage, 'Initial Definition') - /* Remember the balance we had before taking the position to later calculate profit or loss */ - tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value - tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value - /* Entry Position size and rate */ tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets') tradingPositionModule.initialTargets(tradingSystemStage, tradingEngine.current.strategyOpenStage) From c35c715d6513662dd7da266cb3558ffb3c8ef267 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 13 Aug 2020 14:59:28 +0200 Subject: [PATCH 052/112] Initial Definition removed --- LowFrequencyTrading/TradingPosition.js | 8 +- LowFrequencyTrading/TradingStages.js | 450 ++++++++++++------------- 2 files changed, 212 insertions(+), 246 deletions(-) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 1dbc6cc98..fc1cebcfe 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -60,11 +60,9 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.positionQuotedAsset.beginBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value tradingEngine.current.position.situationName.value = situationName - /* Initializing Stop and Take Profit Stage / Phase */ - tradingEngine.current.position.stopLoss.stopLossStage.value = 'Open Stage' - tradingEngine.current.position.takeProfit.takeProfitStage.value = 'Open Stage' - tradingEngine.current.position.stopLoss.stopLossPhase.value = 0 - tradingEngine.current.position.takeProfit.takeProfitPhase.value = 0 + /* Initializing Stop and Take Phase */ + tradingEngine.current.position.stopLoss.stopLossPhase.value = 1 + tradingEngine.current.position.takeProfit.takeProfitPhase.value = 1 /* Updating Episode Counters */ tradingEngine.current.episode.episodeCounters.positions.value++ diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 7961ac29b..b2a598110 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -259,116 +259,118 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } async function runOpenStage() { - /* Abort Open Stage Check */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') { - /* - if the Close stage is opened while the open stage is still open that means that - we need to stop placing orders, check what happened to the orders already placed, - and cancel all unfilled limit orders. We call this the Closing status of the Open Stage. - */ - changeStageStatus('Open Stage', 'Closing') + + checkIfWeNeedToAbortTheStage() + runWhenStatusIsOpening() + await runWhenStatusIsOpen() + await runWhenStatusIsClosing() + + function checkIfWeNeedToAbortTheStage() { + /* Abort Open Stage Check */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') { + /* + if the Close stage is opened while the open stage is still open that means that + we need to stop placing orders, check what happened to the orders already placed, + and cancel all unfilled limit orders. We call this the Closing status of the Open Stage. + */ + changeStageStatus('Open Stage', 'Closing') + } } - /* Opening Status Procedure */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') { + function runWhenStatusIsOpening() { + /* Opening Status Procedure */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') { - /* This procedure is intended to run only once */ - let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = tradingSystemStage.openExecution + /* This procedure is intended to run only once */ + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let tradingEngineStage = tradingEngine.current.strategyOpenStage - /* Reset the Exchange Orders data structure to its initial value */ - tradingEngineModule.initializeNode(tradingEngine.exchangeOrders) + /* Reset the Exchange Orders data structure to its initial value */ + tradingEngineModule.initializeNode(tradingEngine.exchangeOrders) - tradingSystem.evalConditions(tradingSystemStage, 'Initial Definition') - tradingSystem.evalFormulas(tradingSystemStage, 'Initial Definition') + /* Entry Position size and rate */ + tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets') + tradingPositionModule.initialTargets(tradingSystemStage, tradingEngineStage) + initializeStageTargetSize() - /* Entry Position size and rate */ - tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets') - tradingPositionModule.initialTargets(tradingSystemStage, tradingEngine.current.strategyOpenStage) - initializeStageTargetSize() - - /* Check Execution at open stage node */ - tradingSystem.evalConditions(tradingSystemStage, 'Open Execution') - tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution') - - await tradingExecutionModule.runExecution( - executionNode, - tradingEngine.current.strategyOpenStage - ) - - /* From here on, the state is officialy Open */ - changeStageStatus('Open Stage', 'Open') - return + /* From here on, the stage is officialy Open */ + changeStageStatus('Open Stage', 'Open') + } } - /* Open Status Procedure */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { - /* - While the Open Stage is Open, we do our regular stuff: place orders and check - what happened to the orders already placed. - */ - let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = tradingSystemStage.openExecution + async function runWhenStatusIsOpen() { + /* Open Status Procedure */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { + /* + While the Open Stage is Open, we do our regular stuff: place orders and check + what happened to the orders already placed. + */ + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let tradingEngineStage = tradingEngine.current.strategyOpenStage + let executionNode = tradingSystemStage.openExecution - /* Every Loop Cycle Activity */ - tradingSystem.evalConditions(tradingSystemStage, 'Open Execution') - tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution') + /* Evaluate conditions and formulas so they are ready during the execution run */ + tradingSystem.evalConditions(executionNode, 'Open Execution') + tradingSystem.evalFormulas(executionNode, 'Open Execution') - await tradingExecutionModule.runExecution( - executionNode, - tradingEngine.current.strategyOpenStage - ) - checkIfStageNeedsToBeClosed(tradingEngine.current.strategyOpenStage, tradingSystemStage, 'Open Stage') + await tradingExecutionModule.runExecution( + executionNode, + tradingEngineStage + ) + checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, 'Open Stage') + } } - /* Closing Status Procedure */ - if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') { - /* - During the closing stage status, we do not place new orders, just check - if the ones placed were filled, and cancel the ones not filled. - */ - let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - let executionNode = tradingSystemStage.openExecution + async function runWhenStatusIsClosing() { + /* Closing Status Procedure */ + if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') { + /* + During the closing stage status, we do not place new orders, just check + if the ones placed were filled, and cancel the ones not filled. + */ + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + let executionNode = tradingSystemStage.openExecution - /* - Check if there are unfilled orders, we will check if they were executed, - and cancel the ones that were not. - */ - tradingSystem.evalConditions(tradingSystemStage, 'Open Execution') - tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution') + /* + Check if there are unfilled orders, we will check if they were executed, + and cancel the ones that were not. + */ + tradingSystem.evalConditions(tradingSystemStage, 'Open Execution') + tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution') - await tradingExecutionModule.runExecution( - executionNode, - tradingEngine.current.strategyOpenStage - ) + await tradingExecutionModule.runExecution( + executionNode, + tradingEngine.current.strategyOpenStage + ) - /* - The Closing is finished when the sizeFilled + feesPaid reaches the sizePlaced. - This can happens either because we update the sizeFilled value when we see - at the exchange that orders were filled, or we reduce the sizePlaced when - we cancel not yet filled orders. Here it does not matter the targetSize since - this status represent a force closure of this stage. - */ - switch (tradingEngine.current.strategyOpenStage.stageDefinedIn.value) { - case 'Base Asset': { - if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value - ) { - changeStageStatus('Open Stage', 'Closed') + /* + The Closing is finished when the sizeFilled + feesPaid reaches the sizePlaced. + This can happens either because we update the sizeFilled value when we see + at the exchange that orders were filled, or we reduce the sizePlaced when + we cancel not yet filled orders. Here it does not matter the targetSize since + this status represent a force closure of this stage. + */ + switch (tradingEngine.current.strategyOpenStage.stageDefinedIn.value) { + case 'Base Asset': { + if ( + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value + ) { + changeStageStatus('Open Stage', 'Closed') + } + break } - break - } - case 'Quoted Asset': { - if ( - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value - ) { - changeStageStatus('Open Stage', 'Closed') + case 'Quoted Asset': { + if ( + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + + tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value + ) { + changeStageStatus('Open Stage', 'Closed') + } + break } - break } } } @@ -386,57 +388,50 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } function runManageStage() { - /* Opening Status Procedure */ - if (tradingEngine.current.strategyManageStage.status.value === 'Opening') { - /* We jump the management at the loop cycle where the position is being taken. */ - changeStageStatus('Manage Stage', 'Open') - return + + runWhenStatusIsOpening() + runWhenStatusIsOpen() + + function runWhenStatusIsOpening() { + /* Opening Status Procedure */ + if (tradingEngine.current.strategyManageStage.status.value === 'Opening') { + + /* Now we switch to the Open status. */ + changeStageStatus('Manage Stage', 'Open') + } } - /* Open Status Procedure */ - if (tradingEngine.current.strategyManageStage.status.value === 'Open') { - let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let manageStage = strategy.manageStage - tradingSystem.evalConditions(manageStage, 'Manage Stage') - tradingSystem.evalFormulas(manageStage, 'Manage Stage') + function runWhenStatusIsOpen() { + /* Open Status Procedure */ + if (tradingEngine.current.strategyManageStage.status.value === 'Open') { + let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] + let manageStage = strategy.manageStage - if ( - tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' || - tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage') { - let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage - tradingSystem.evalConditions(tradingSystemStage, 'Initial Definition') - tradingSystem.evalFormulas(tradingSystemStage, 'Initial Definition') + /* Evaluate all the stage conditions and formulas to have them ready */ + tradingSystem.evalConditions(manageStage, 'Manage Stage') + tradingSystem.evalFormulas(manageStage, 'Manage Stage') + + /* Stop Loss Management */ + checkStopPhasesEvents() + calculateStopLoss() + calculateStopLossPosition() + + /* Take Profit Management */ + checkTakeProfitPhaseEvents() + calculateTakeProfit() + calculateTakeProfitPosition() + + /* Checking if Stop or Take Profit were hit */ + checkStopLossOrTakeProfitWasHit() } - - /* Stop Loss Management */ - checkStopPhasesEvents() - calculateStopLoss() - calculateStopLossPosition() - - /* Take Profit Management */ - checkTakeProfitPhaseEvents() - calculateTakeProfit() - calculateTakeProfitPosition() - - /* Checking if Stop or Take Profit were hit */ - checkStopLossOrTakeProfitWasHit() } function calculateStopLoss() { let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let openStage = strategy.openStage let manageStage = strategy.manageStage let phase - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialStopLoss !== undefined) { - phase = openStage.initialDefinition.initialStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value] - } - } - } - - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { + if (manageStage !== undefined) { if (manageStage.managedStopLoss !== undefined) { phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1] } @@ -456,19 +451,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function calculateTakeProfit() { let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let openStage = strategy.openStage let manageStage = strategy.manageStage let phase - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialTakeProfit !== undefined) { - phase = openStage.initialDefinition.initialTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value] - } - } - } - - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { + if (manageStage !== undefined) { if (manageStage.managedTakeProfit !== undefined) { phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1] } @@ -489,7 +475,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function calculateStopLossPosition() { /* The position of the Stop Loss (above or below the price) is needed in order to know - later if the price hit the Stop Loss or not at every Simulation cycle. When we get + later if the price hit the Stop Loss or not at every Simulation candle. When we get the first values of the simulation of the Stop Loss we check if it is above or below the Position Rate, and we assign the values Above or Below to it. */ @@ -505,7 +491,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function calculateTakeProfitPosition() { /* The position of the Take Profit (above or below the price) is needed in order to know - later if the price hit the Take Profit or not at every Simulation cycle. When we get + later if the price hit the Take Profit or not at every Simulation candle. When we get the first values of the simulation of the Take Profits we check if it is above or below the Position Rates, and we assign the values Above or Below to it. */ @@ -520,28 +506,13 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function checkStopPhasesEvents() { let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value] - let openStage = strategy.openStage let manageStage = strategy.manageStage let parentNode let phaseIndex let phase let stopLoss - let stage - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) { - stage = openStage - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialStopLoss !== undefined) { - parentNode = openStage.initialDefinition - phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - stopLoss = openStage.initialDefinition.initialStopLoss - phase = stopLoss.phases[phaseIndex] - } - } - } - - if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) { - stage = manageStage + if (manageStage !== undefined) { if (manageStage.managedStopLoss !== undefined) { parentNode = manageStage phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1 @@ -572,7 +543,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.highlights.push(phase.id) tradingSystem.highlights.push(stopLoss.id) tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) + tradingSystem.highlights.push(manageStage.id) tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage') @@ -602,7 +573,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.highlights.push(phase.id) tradingSystem.highlights.push(stopLoss.id) tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) + tradingSystem.highlights.push(manageStage.id) let moveToPhase = moveToPhaseEvent.referenceParent if (moveToPhase !== undefined) { @@ -633,22 +604,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM let phaseIndex let phase let takeProfit - let stage - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) { - stage = openStage - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.initialTakeProfit !== undefined) { - parentNode = openStage.initialDefinition - phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - takeProfit = openStage.initialDefinition.initialTakeProfit - phase = takeProfit.phases[phaseIndex] - } - } - } - - if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) { - stage = manageStage + if (manageStage !== undefined) { if (manageStage.managedTakeProfit !== undefined) { parentNode = manageStage phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1 @@ -679,7 +636,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.highlights.push(phase.id) tradingSystem.highlights.push(takeProfit.id) tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) + tradingSystem.highlights.push(manageStage.id) tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage') @@ -709,7 +666,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.highlights.push(phase.id) tradingSystem.highlights.push(takeProfit.id) tradingSystem.highlights.push(parentNode.id) - tradingSystem.highlights.push(stage.id) + tradingSystem.highlights.push(manageStage.id) let moveToPhase = moveToPhaseEvent.referenceParent if (moveToPhase !== undefined) { @@ -785,68 +742,81 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } async function runCloseStage() { - /* Opening Status Procedure */ - if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') { + + runWhenStatusIsOpening() + await runWhenStatusIsOpen() + + function runWhenStatusIsOpening() { + /* Opening Status Procedure */ + if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') { + /* + This will happen only once, as soon as the Take Profit or Stop was hit. + */ + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + + /* Exit Position size and rate */ + tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets') + tradingPositionModule.initialTargets(tradingSystemStage, tradingEngine.current.strategyCloseStage) + + initializeStageTargetSize() + changeStageStatus('Close Stage', 'Open') + } + } + + async function runWhenStatusIsOpen() { /* - This will happen only once, as soon as the Take Profit or Stop was hit. - We wil not be placing orders at this time because we do not know - the total filled size of the Open Stage. By skiping execution now - we allow the open stage to get a final value for sizeFilled that we can use. + The following code will run only when the Open Stage is closed. + The reason for that is that we need the final filled size of the Open Stage + to be known before we start placing orders at the Close Stage. */ - let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + if (tradingEngine.current.strategyCloseStage.status.value === 'Open' && tradingEngine.current.strategyOpenStage.status.value === 'Closed') { + /* + This will happen as long as the Close Stage is Open. + */ + let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + let tradingEngineStage = tradingEngine.current.strategyCloseStage + let executionNode = tradingSystemStage.closeExecution - /* Exit Position size and rate */ - tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets') - tradingPositionModule.initialTargets(tradingSystemStage, tradingEngine.current.strategyCloseStage) + tradingSystem.evalConditions(tradingSystemStage, 'Close Execution') + tradingSystem.evalFormulas(tradingSystemStage, 'Close Execution') - initializeStageTargetSize() - changeStageStatus('Close Stage', 'Open') - return - } + await tradingExecutionModule.runExecution( + executionNode, + tradingEngine.current.strategyCloseStage + ) - /* Open Status Procedure */ - if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { - /* - This will happen as long as the Close Stage is Open. + checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, 'Close Stage') + } + + /* + Exit Position Validation: we need al the previous stages to be either closed + or at their initial default value. The last is becasue after Closed objects are + initialized to their defualts. */ - let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage - let executionNode = tradingSystemStage.closeExecution + if ( + ( + tradingEngine.current.strategyTriggerStage.status.value === 'Closed' || + tradingEngine.current.strategyTriggerStage.status.value === tradingEngine.current.strategyTriggerStage.status.config.initialValue + ) + && + ( + tradingEngine.current.strategyOpenStage.status.value === 'Closed' || + tradingEngine.current.strategyOpenStage.status.value === tradingEngine.current.strategyOpenStage.status.config.initialValue + ) + && + ( + tradingEngine.current.strategyManageStage.status.value === 'Closed' || + tradingEngine.current.strategyManageStage.status.value === tradingEngine.current.strategyManageStage.status.config.initialValue + ) + && + ( + tradingEngine.current.strategyCloseStage.status.value === 'Closed' + ) + ) { - tradingSystem.evalConditions(tradingSystemStage, 'Close Execution') - tradingSystem.evalFormulas(tradingSystemStage, 'Close Execution') - - await tradingExecutionModule.runExecution( - executionNode, - tradingEngine.current.strategyCloseStage - ) - - checkIfStageNeedsToBeClosed(tradingEngine.current.strategyCloseStage, tradingSystemStage, 'Close Stage') - } - - /* Exit Position Validation */ - if ( - ( - tradingEngine.current.strategyTriggerStage.status.value === 'Closed' || - tradingEngine.current.strategyTriggerStage.status.value === tradingEngine.current.strategyTriggerStage.status.config.initialValue - ) - && - ( - tradingEngine.current.strategyOpenStage.status.value === 'Closed' || - tradingEngine.current.strategyOpenStage.status.value === tradingEngine.current.strategyOpenStage.status.config.initialValue - ) - && - ( - tradingEngine.current.strategyManageStage.status.value === 'Closed' || - tradingEngine.current.strategyManageStage.status.value === tradingEngine.current.strategyManageStage.status.config.initialValue - ) - && - ( - tradingEngine.current.strategyCloseStage.status.value === 'Closed' - ) - ) { - - /* Exiting Everything now. */ - exitPositionAndStrategy() + /* Closing Everything now. */ + closePositionAndStrategy() + } } function initializeStageTargetSize() { @@ -860,8 +830,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value, 10) } - function exitPositionAndStrategy() { - + function closePositionAndStrategy() { /* Taking Position Snapshot */ if (bot.SESSION.type === 'Backtesting Session') { if (sessionParameters.snapshots !== undefined) { @@ -920,7 +889,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM ) { positionFilled() } else { - checkCloseStageEvent() + checkCloseStageEvent(tradingSystemStage) } break } @@ -939,7 +908,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM ) { positionFilled() } else { - checkCloseStageEvent() + checkCloseStageEvent(tradingSystemStage) } break } @@ -947,7 +916,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function positionFilled() { changeStageStatus(stageName, 'Closed', 'Position Size Filled') } - function checkCloseStageEvent() { + function checkCloseStageEvent(tradingSystemStage) { /* Check the Close Stage Event */ tradingSystem.evalConditions(tradingSystemStage, 'Close Stage Event') if (checkStopStageEvent(tradingSystemStage) === true) { @@ -957,7 +926,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } function checkStopStageEvent(stage) { - /* Check the Close Stage Event. */ let stopStageEvent = stage.stopStageEvent if (stopStageEvent !== undefined) { From 01f36099cec9e18a635e56c5d24b8780d9773cf6 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 14 Aug 2020 10:25:11 +0200 Subject: [PATCH 053/112] More fixes --- LowFrequencyTrading/TradingEngine.js | 6 +++--- LowFrequencyTrading/TradingSimulation.js | 1 - LowFrequencyTrading/TradingStages.js | 26 ++++++------------------ 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 159e5d392..865713d7f 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -74,18 +74,18 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { tradingEngine.current.episode.cycle.end.value = tradingEngine.current.episode.candle.begin.value + sessionParameters.timeFrame.config.value + - sessionParameters.timeFrame.config.value / 4 - 1 + sessionParameters.timeFrame.config.value / 2 - 1 break } case 'Second': { tradingEngine.current.episode.cycle.begin.value = tradingEngine.current.episode.candle.begin.value + sessionParameters.timeFrame.config.value + - sessionParameters.timeFrame.config.value / 4 + sessionParameters.timeFrame.config.value / 2 tradingEngine.current.episode.cycle.end.value = tradingEngine.current.episode.candle.begin.value + sessionParameters.timeFrame.config.value + - sessionParameters.timeFrame.config.value * 2 / 4 - 1 + sessionParameters.timeFrame.config.value * 4 / 4 - 1 break } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 46ab10021..6a8e98a75 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -197,7 +197,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (tradingEngine.current.episode.candle.index.value + 1 < candles.length) { - console.log('Processing Candle: ' + tradingEngine.current.episode.candle.index.value) /* Add new records to the process output */ tradingRecordsModule.appendRecords('Second') diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index b2a598110..2c2abf525 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -876,16 +876,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM switch (tradingEngineStage.stageDefinedIn.value) { case 'Base Asset': { if ( - ( - tradingEngineStage.stageBaseAsset.sizePlaced >= - tradingEngineStage.stageBaseAsset.targetSize - ) - && - ( - tradingEngineStage.stageBaseAsset.sizeFilled.value + - tradingEngineStage.stageBaseAsset.feesPaid.value >= - tradingEngineStage.stageBaseAsset.sizePlaced.value - ) + tradingEngineStage.stageBaseAsset.sizeFilled.value + + tradingEngineStage.stageBaseAsset.feesPaid.value >= + tradingEngineStage.stageBaseAsset.targetSize ) { positionFilled() } else { @@ -895,16 +888,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } case 'Quoted Asset': { if ( - ( - tradingEngineStage.stageQuotedAsset.sizePlaced >= - tradingEngineStage.stageQuotedAsset.targetSize - ) - && - ( - tradingEngineStage.stageQuotedAsset.sizeFilled.value + - tradingEngineStage.stageQuotedAsset.feesPaid.value >= - tradingEngineStage.stageQuotedAsset.sizePlaced.value - ) + tradingEngineStage.stageQuotedAsset.sizeFilled.value + + tradingEngineStage.stageQuotedAsset.feesPaid.value >= + tradingEngineStage.stageQuotedAsset.targetSize ) { positionFilled() } else { From 42cbff258e3252e3fdc8bb1e837e1a0d856204a2 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 14 Aug 2020 14:06:48 +0200 Subject: [PATCH 054/112] Update TradingEpisode.js --- LowFrequencyTrading/TradingEpisode.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index a654de03a..b5112fb56 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -136,12 +136,12 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) - /* Updating Anualized Rate Of Return */ - tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = + /* Updating Annualized Rate Of Return */ + tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = tradingEngine.current.episode.episodeStatistics.ROI.value / tradingEngine.current.episode.episodeStatistics.days.value * 365 - tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10) /* Updating Hit or Fail */ if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { @@ -201,17 +201,17 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) - /* Updating Anualized Rate Of Return */ - tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = + /* Updating Annualized Rate Of Return */ + tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = tradingEngine.current.episode.episodeBaseAsset.ROI.value / tradingEngine.current.episode.episodeStatistics.days.value * 365 - tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = + tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = tradingEngine.current.episode.episodeQuotedAsset.ROI.value / tradingEngine.current.episode.episodeStatistics.days.value * 365 - tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value, 10) /* Updating Hit or Fail */ if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) { From b701c561a78a2ea72cecf2e524f5bcc0810245d3 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 14 Aug 2020 18:31:15 +0200 Subject: [PATCH 055/112] Many Bugs Fixed --- LowFrequencyTrading/TradingOrders.js | 145 ++++++++++++++----------- LowFrequencyTrading/TradingPosition.js | 6 +- LowFrequencyTrading/TradingStages.js | 34 +++--- Session.js | 14 +++ 4 files changed, 119 insertions(+), 80 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index d03b913ef..464f25a09 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -367,8 +367,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM actualRateSimulation() percentageFilledSimulation() - sizeFilledSimulation() feesPaidSimulation() + sizeFilledSimulation() doTheAccounting( tradingEngineStage, @@ -430,67 +430,6 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) } - function feesPaidSimulation() { - /* Fees Paid Simulation */ - let calculatedBasedOnTradingSystem = false - - /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { - if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.sizeFilled.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 - - calculatedBasedOnTradingSystem = true - } - - if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.sizeFilled.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 - - calculatedBasedOnTradingSystem = true - } - } - } - - /* Based on the Session Parameters Definition */ - if (calculatedBasedOnTradingSystem === false) { - /* Fees are simulated based on the Session Paremeters */ - switch (tradingEngineOrder.type) { - case 'Market Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.sizeFilled.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.sizeFilled.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 - - break - } - case 'Limit Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.sizeFilled.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.sizeFilled.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 - - break - } - } - } - tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) - tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) - } - function percentageFilledSimulation() { /* Order Filling Simulation: the way we are going to handle this is the following. @@ -514,6 +453,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } break } + case 'Market Buy Order': { + orderWasHit = true + break + } + case 'Market Sell Order': { + orderWasHit = true + break + } } if (orderWasHit === true) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { @@ -546,6 +493,80 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } + function feesPaidSimulation() { + /* + The way the fess paid is calculated is as follows: From the order 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 session parameters definitions. + */ + let calculatedBasedOnTradingSystem = false + + /* Based on the Trading System Definition */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { + if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + calculatedBasedOnTradingSystem = true + } + + if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + calculatedBasedOnTradingSystem = true + } + } + } + + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { + /* Fees are simulated based on the Session Paremeters */ + switch (tradingEngineOrder.type) { + case 'Market Order': { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.taker / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + break + } + case 'Limit Order': { + + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + bot.SESSION.parameters.feeStructure.config.maker / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + break + } + } + } + tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) + } + function sizeFilledSimulation() { /* Size Filled */ tradingEngineOrder.orderBaseAsset.sizeFilled.value = diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index fc1cebcfe..15214c844 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -104,14 +104,12 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.takeProfit.value = formulas.get(formulaId) } - function updateStopLoss(phase, stage) { + function updateStopLoss(phase) { tradingEngine.current.position.stopLoss.stopLossPhase.value = phase - tradingEngine.current.position.stopLoss.stopLossStage.value = stage } - function updateTakeProfit(phase, stage) { + function updateTakeProfit(phase) { tradingEngine.current.position.takeProfit.takeProfitPhase.value = phase - tradingEngine.current.position.takeProfit.takeProfitStage.value = stage } function initialTargets(tradingSystemStageNode, tradingEngineStageNode) { diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 2c2abf525..79007509b 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -545,7 +545,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.highlights.push(parentNode.id) tradingSystem.highlights.push(manageStage.id) - tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage') + tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1) announcementsModule.makeAnnoucements(nextPhaseEvent) return // only one event can pass at the time @@ -579,7 +579,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if (moveToPhase !== undefined) { for (let q = 0; q < stopLoss.phases.length; q++) { if (stopLoss.phases[q].id === moveToPhase.id) { - tradingPositionModule.updateStopLoss(q + 1, 'Manage Stage') + tradingPositionModule.updateStopLoss(q + 1) } } } else { @@ -638,7 +638,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingSystem.highlights.push(parentNode.id) tradingSystem.highlights.push(manageStage.id) - tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage') + tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1) announcementsModule.makeAnnoucements(nextPhaseEvent) return // only one event can pass at the time @@ -672,7 +672,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if (moveToPhase !== undefined) { for (let q = 0; q < takeProfit.phases.length; q++) { if (takeProfit.phases[q].id === moveToPhase.id) { - tradingPositionModule.updateTakeProfit(q + 1, 'Manage Stage') + tradingPositionModule.updateTakeProfit(q + 1) } } } else { @@ -769,7 +769,13 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM The reason for that is that we need the final filled size of the Open Stage to be known before we start placing orders at the Close Stage. */ - if (tradingEngine.current.strategyCloseStage.status.value === 'Open' && tradingEngine.current.strategyOpenStage.status.value === 'Closed') { + if ( + tradingEngine.current.strategyCloseStage.status.value === 'Open' && + ( + tradingEngine.current.strategyOpenStage.status.value === 'Closed' || + tradingEngine.current.strategyOpenStage.status.value == tradingEngine.current.strategyOpenStage.status.config.initialValue + ) + ) { /* This will happen as long as the Close Stage is Open. */ @@ -878,7 +884,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( tradingEngineStage.stageBaseAsset.sizeFilled.value + tradingEngineStage.stageBaseAsset.feesPaid.value >= - tradingEngineStage.stageBaseAsset.targetSize + tradingEngineStage.stageBaseAsset.targetSize.value ) { positionFilled() } else { @@ -890,7 +896,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM if ( tradingEngineStage.stageQuotedAsset.sizeFilled.value + tradingEngineStage.stageQuotedAsset.feesPaid.value >= - tradingEngineStage.stageQuotedAsset.targetSize + tradingEngineStage.stageQuotedAsset.targetSize.value ) { positionFilled() } else { @@ -970,33 +976,33 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM function openStage(stage) { /* Recording the opening at the Trading Engine Data Structure */ - stage.begin.value = tradingEngine.current.episode.candle.end.value + stage.begin.value = tradingEngine.current.episode.cycle.begin.value stage.beginRate.value = tradingEngine.current.episode.candle.close.value - stage.end.value = tradingEngine.current.episode.candle.end.value + stage.end.value = tradingEngine.current.episode.cycle.end.value } function closeStage(stage) { /* Recording the closing at the Trading Engine Data Structure */ - stage.end.value = tradingEngine.current.episode.candle.end.value + stage.end.value = tradingEngine.current.episode.cycle.end.value stage.endRate.value = tradingEngine.current.episode.candle.close.value } } function updateEnds() { if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') { - tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.episode.cycle.end.value tradingEngine.current.strategyTriggerStage.endRate.value = tradingEngine.current.episode.candle.close.value } if (tradingEngine.current.strategyOpenStage.status.value === 'Open') { - tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.episode.cycle.end.value tradingEngine.current.strategyOpenStage.endRate.value = tradingEngine.current.episode.candle.close.value } if (tradingEngine.current.strategyManageStage.status.value === 'Open') { - tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.episode.cycle.end.value tradingEngine.current.strategyManageStage.endRate.value = tradingEngine.current.episode.candle.close.value } if (tradingEngine.current.strategyCloseStage.status.value === 'Open') { - tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.episode.candle.end.value + tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.episode.cycle.end.value tradingEngine.current.strategyCloseStage.endRate.value = tradingEngine.current.episode.candle.close.value } } diff --git a/Session.js b/Session.js index 6f627e326..58e944b15 100644 --- a/Session.js +++ b/Session.js @@ -160,6 +160,20 @@ finalDatetime: (new Date()).valueOf() } } + } else { + /* Check that we received valid dates */ + if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()) { + let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters, errorMessage) + return false + } + if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf()) { + let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters, errorMessage) + return false + } } /* Session Type Forced Values */ From a6505c3f406a0c60e7378cf813bae2ae9dae47be Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 14 Aug 2020 19:24:53 +0200 Subject: [PATCH 056/112] Update TradingPosition.js --- LowFrequencyTrading/TradingPosition.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 15214c844..a132b2c78 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -52,7 +52,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* Recording the opening at the Trading Engine Data Structure */ tradingEngine.current.position.status.value = 'Open' - tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value + tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value + 1 tradingEngine.current.position.identifier.value = global.UNIQUE_ID() tradingEngine.current.position.begin.value = tradingEngine.current.episode.candle.end.value tradingEngine.current.position.beginRate.value = tradingEngine.current.episode.candle.close.value From 932f867260b1bc587812258e201b99db32067af0 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 15 Aug 2020 09:08:24 +0200 Subject: [PATCH 057/112] bug fixes --- LowFrequencyTrading/TradingEngine.js | 59 ------------------------ LowFrequencyTrading/TradingEpisode.js | 58 +++++++++++++++++++++++ LowFrequencyTrading/TradingSimulation.js | 4 +- LowFrequencyTrading/TradingStages.js | 12 +++++ 4 files changed, 73 insertions(+), 60 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 865713d7f..c0ed6d7d2 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -45,7 +45,6 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } function mantain() { - updateDistanceToEventsCounters() } function reset() { @@ -212,63 +211,5 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } } } - - function updateDistanceToEventsCounters() { - /* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */ - if ( - tradingEngine.current.episode.distanceToEvent.triggerOn.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.triggerOn.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.triggerOff.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.triggerOff.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.takePosition.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.takePosition.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.closePosition.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.closePosition.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.nextPhase.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.nextPhase.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.moveToPhase.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.moveToPhase.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.createOrder.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.createOrder.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.cancelOrder.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.cancelOrder.value++ - } - - if ( - tradingEngine.current.episode.distanceToEvent.closeOrder.value > 0 - ) { - tradingEngine.current.episode.distanceToEvent.closeOrder.value++ - } - } - } diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index b5112fb56..5276ba5bc 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -37,6 +37,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin updateCounters() updateStatistics() updateEnds() + updateDistanceToEventsCounters() } function reset() { @@ -225,4 +226,61 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail' } } + + function updateDistanceToEventsCounters() { + /* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */ + if ( + tradingEngine.current.episode.distanceToEvent.triggerOn.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.triggerOn.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.triggerOff.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.triggerOff.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.takePosition.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.takePosition.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.closePosition.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.closePosition.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.nextPhase.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.nextPhase.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.moveToPhase.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.moveToPhase.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.createOrder.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.createOrder.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.cancelOrder.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.cancelOrder.value++ + } + + if ( + tradingEngine.current.episode.distanceToEvent.closeOrder.value > 0 + ) { + tradingEngine.current.episode.distanceToEvent.closeOrder.value++ + } + } } \ No newline at end of file diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 6a8e98a75..c63f6988e 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -110,6 +110,9 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) } tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading 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() if (firstLoopExecution === true) { tradingEpisodeModule.openEpisode() @@ -131,7 +134,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT return } - heartBeat() positionChartAtCurrentCandle() /* The chart was recalculated based on the current candle. */ diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 79007509b..063cb977a 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -548,6 +548,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1) announcementsModule.makeAnnoucements(nextPhaseEvent) + + /* Reset this counter */ + tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1 return // only one event can pass at the time } } @@ -588,6 +591,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } announcementsModule.makeAnnoucements(moveToPhaseEvent) + + /* Reset this counter */ + tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1 return // only one event can pass at the time } } @@ -641,6 +647,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1) announcementsModule.makeAnnoucements(nextPhaseEvent) + + /* Reset this counter */ + tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1 return // only one event can pass at the time } } @@ -681,6 +690,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } announcementsModule.makeAnnoucements(moveToPhaseEvent) + + /* Reset this counter */ + tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1 return // only one event can pass at the time } } From 97a6469bcc99e0db21b227ddd95cebd5e5dd8dd4 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 15 Aug 2020 19:47:17 +0200 Subject: [PATCH 058/112] Precision bug --- LowFrequencyTrading/TradingExecution.js | 2 +- LowFrequencyTrading/TradingOrders.js | 84 ++++++++++++++----------- LowFrequencyTrading/TradingPosition.js | 2 +- LowFrequencyTrading/TradingStages.js | 14 +++-- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index eb3cb4bdf..aff93d2a2 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -95,7 +95,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE if (err !== undefined) { logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack); } - throw 'It is not safe to continue with a Definition Error like this. Please fix the problem and try again.' + throw 'Please fix the problem and try again.' } } diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 464f25a09..8cb20db59 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -355,11 +355,6 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } - /* Filter by what is defined at the Strategy */ - if (tradingSystemOrder.simulatedExchangeEvents === undefined) { - badDefinitionUnhandledException(undefined, 'tradingSystemOrder.simulatedExchangeEvents === undefined', tradingSystemOrder) - } - let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value @@ -390,13 +385,15 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let calculatedBasedOnTradingSystem = false /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { - /* Calculate this only once for this order */ - if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { - tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) - if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { - calculatedBasedOnTradingSystem = true + if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { + /* Calculate this only once for this order */ + if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { + tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { + calculatedBasedOnTradingSystem = true + } } } } @@ -463,18 +460,29 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } if (orderWasHit === true) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { + let calculatedBasedOnTradingSystem = false - /* Percentage Filled */ - let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 - if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { - percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + /* Based on the Trading System Definition */ + if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { + + /* Percentage Filled */ + let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 + if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { + percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + } + tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + calculatedBasedOnTradingSystem = true } - tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) } - } else { + } + /* + If there is no definition for this at the trading system we will assume orders + are 100% filled every single time. + */ + if (calculatedBasedOnTradingSystem === false) { /* We will assume that 100% of the order was filled */ tradingEngineOrder.orderStatistics.percentageFilled.value = 100 } @@ -505,26 +513,28 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let calculatedBasedOnTradingSystem = false /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { - if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { + if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { + if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - calculatedBasedOnTradingSystem = true - } + calculatedBasedOnTradingSystem = true + } - if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { + if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - calculatedBasedOnTradingSystem = true + calculatedBasedOnTradingSystem = true + } } } } @@ -993,7 +1003,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (err !== undefined) { logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack); } - throw 'It is not safe to continue with a Definition Error like this. Please fix the problem and try again.' + throw 'Please fix the problem and try again.' } } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index a132b2c78..d815cad3b 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -347,6 +347,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng if (err !== undefined) { logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack); } - throw 'It is not safe to continue with a Definition Error like this. Please fix the problem and try again.' + throw 'Please fix the problem and try again.' } } \ No newline at end of file diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 063cb977a..9bead0bd2 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -894,8 +894,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM switch (tradingEngineStage.stageDefinedIn.value) { case 'Base Asset': { if ( - tradingEngineStage.stageBaseAsset.sizeFilled.value + - tradingEngineStage.stageBaseAsset.feesPaid.value >= + global.PRECISE( + tradingEngineStage.stageBaseAsset.sizeFilled.value + + tradingEngineStage.stageBaseAsset.feesPaid.value + , 10) + >= tradingEngineStage.stageBaseAsset.targetSize.value ) { positionFilled() @@ -906,8 +909,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } case 'Quoted Asset': { if ( - tradingEngineStage.stageQuotedAsset.sizeFilled.value + - tradingEngineStage.stageQuotedAsset.feesPaid.value >= + global.PRECISE( + tradingEngineStage.stageQuotedAsset.sizeFilled.value + + tradingEngineStage.stageQuotedAsset.feesPaid.value + , 10) + >= tradingEngineStage.stageQuotedAsset.targetSize.value ) { positionFilled() From a88b94066f6a4ef5183e5c9bf6ce43439be5f03d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 16 Aug 2020 16:20:27 +0200 Subject: [PATCH 059/112] Testing Paper Trading --- Session.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Session.js b/Session.js index 58e944b15..17f9cb6ec 100644 --- a/Session.js +++ b/Session.js @@ -22,9 +22,9 @@ /* Set the folderName for early logging */ if (bot.processNode.session.config.folderName === undefined) { - bot.SESSION.folderName = bot.SESSION.id + bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.SESSION.id } else { - bot.SESSION.folderName = bot.processNode.session.config.folderName + "-" + bot.SESSION.id + bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName } /* Check if there is a session */ @@ -125,14 +125,18 @@ } function setUpSessionFolderName() { - /* Set the folderName for logging, reports, context and data output */ + /* + 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 (bot.SESSION.config !== undefined) { config = bot.SESSION.config if (config.folderName === undefined) { - bot.SESSION.folderName = bot.SESSION.id + bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.SESSION.id } else { - bot.SESSION.folderName = config.folderName + "-" + bot.SESSION.id + bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName } } } @@ -162,11 +166,13 @@ } } else { /* Check that we received valid dates */ - if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()) { - let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date." - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) - bot.sessionError(bot.SESSION.parameters, errorMessage) - return false + if (bot.SESSION.type === 'Backtesting Session') { + if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()) { + let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters, errorMessage) + return false + } } if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf()) { let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date." From ded7998d0bd391089379dc7cef5d5c621faae778 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 17 Aug 2020 13:32:52 +0200 Subject: [PATCH 060/112] Statistics at every candle --- FileStorage.js | 4 +- LowFrequencyTrading/TradingEpisode.js | 216 ++++++++++++----------- LowFrequencyTrading/TradingPosition.js | 156 ++++++++-------- LowFrequencyTrading/TradingSimulation.js | 28 ++- LowFrequencyTrading/TradingStages.js | 9 +- LowFrequencyTrading/TradingSystem.js | 3 + 6 files changed, 219 insertions(+), 197 deletions(-) diff --git a/FileStorage.js b/FileStorage.js index 9b9245883..7cb2bf26b 100644 --- a/FileStorage.js +++ b/FileStorage.js @@ -4,7 +4,7 @@ const path = require('path') exports.newFileStorage = function newFileStorage(logger, host, port) { const MODULE_NAME = 'FileStorage' - const MAX_RETRY = 30 + const MAX_RETRY = 10 const FAST_RETRY_TIME_IN_MILISECONDS = 500 const SLOW_RETRY_TIME_IN_MILISECONDS = 2000 @@ -160,7 +160,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } function retry() { - console.log(new Date(), ' Retry #: ' + currentRetryGetTextFile, fileLocation) + console.log(new Date(), 'Read File Retry #: ' + currentRetryGetTextFile, fileLocation) if (currentRetryGetTextFile < MAX_RETRY) { currentRetryGetTextFile++ logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> retry -> Will try to read the file again -> Retry #: ' + currentRetryGetTextFile) diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 5276ba5bc..96b6e5adc 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -9,7 +9,6 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin openEpisode: openEpisode, updateExitType: updateExitType, closeEpisode: closeEpisode, - calculateResults: calculateResults, calculateStatistics: calculateStatistics, initialize: initialize, finalize: finalize @@ -114,118 +113,123 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin global.ONE_DAY_IN_MILISECONDS } - function calculateStatistics() { + function calculateStatistics() { + calculateEpisodeStatistics() + calculateAssetsStatistics() - /* Updating Profit Loss */ - tradingEngine.current.episode.episodeStatistics.profitLoss.value = - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - - tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) - - /* Updating ROI */ - tradingEngine.current.episode.episodeStatistics.ROI.value = - ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + + function calculateEpisodeStatistics() { + /* Updating Profit Loss */ + tradingEngine.current.episode.episodeStatistics.profitLoss.value = tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + - sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - ) / ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + + + tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.current.episode.episodeStatistics.ROI.value = + ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) / ( + sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + + sessionParameters.sessionQuotedAsset.config.initialBalance + ) - 1 + + tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) + + /* Updating Annualized Rate Of Return */ + tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = + tradingEngine.current.episode.episodeStatistics.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 + + tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' + } else { + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail' + } + } + + function calculateAssetsStatistics() { + + /* Updating Hits & Fails */ + if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeBaseAsset.hits.value++ + } else { + tradingEngine.current.episode.episodeBaseAsset.fails.value++ + } + if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeQuotedAsset.hits.value++ + } else { + tradingEngine.current.episode.episodeQuotedAsset.fails.value++ + } + + /* Updating Profit Loss */ + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - + sessionParameters.sessionBaseAsset.config.initialBalance + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - sessionParameters.sessionQuotedAsset.config.initialBalance - ) - 1 - - tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) - - /* Updating Annualized Rate Of Return */ - tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = - tradingEngine.current.episode.episodeStatistics.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 - - tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10) - - /* Updating Hit or Fail */ - if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { - tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' - } else { - tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail' + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10) + + /* Updating ROI */ + tradingEngine.current.episode.episodeBaseAsset.ROI.value = + (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value) / + sessionParameters.sessionBaseAsset.config.initialBalance - 1 + + tradingEngine.current.episode.episodeQuotedAsset.ROI.value = + (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value) / + sessionParameters.sessionQuotedAsset.config.initialBalance - 1 + + tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10) + + /* Updating Hit Ratio */ + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = + tradingEngine.current.episode.episodeBaseAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value + + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = + tradingEngine.current.episode.episodeQuotedAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value + + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) + + /* Updating Annualized Rate Of Return */ + tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = + tradingEngine.current.episode.episodeBaseAsset.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 + + tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = + tradingEngine.current.episode.episodeQuotedAsset.ROI.value / + tradingEngine.current.episode.episodeStatistics.days.value * 365 + + tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Fail' + } + if (tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail' + } } } - function calculateResults() { - - /* Updating Hits & Fails */ - if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeBaseAsset.hits.value++ - } else { - tradingEngine.current.episode.episodeBaseAsset.fails.value++ - } - if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeQuotedAsset.hits.value++ - } else { - tradingEngine.current.episode.episodeQuotedAsset.fails.value++ - } - - /* Updating Profit Loss */ - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value - - sessionParameters.sessionBaseAsset.config.initialBalance - - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value - - sessionParameters.sessionQuotedAsset.config.initialBalance - - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10) - - /* Updating ROI */ - tradingEngine.current.episode.episodeBaseAsset.ROI.value = - (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value) / - sessionParameters.sessionBaseAsset.config.initialBalance - 1 - - tradingEngine.current.episode.episodeQuotedAsset.ROI.value = - (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value) / - sessionParameters.sessionQuotedAsset.config.initialBalance - 1 - - tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10) - - /* Updating Hit Ratio */ - tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = - tradingEngine.current.episode.episodeBaseAsset.hits.value / - tradingEngine.current.episode.episodeCounters.positions.value - - tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = - tradingEngine.current.episode.episodeQuotedAsset.hits.value / - tradingEngine.current.episode.episodeCounters.positions.value - - tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) - - /* Updating Annualized Rate Of Return */ - tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = - tradingEngine.current.episode.episodeBaseAsset.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 - - tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = - tradingEngine.current.episode.episodeQuotedAsset.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 - - tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value, 10) - - /* Updating Hit or Fail */ - if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Fail' - } - if (tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail' - } - } function updateDistanceToEventsCounters() { /* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */ diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index d815cad3b..c5606741a 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -6,6 +6,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng let thisObject = { mantain: mantain, reset: reset, + calculateStatistics: calculateStatistics, openPosition: openPosition, closingPosition: closingPosition, closePosition: closePosition, @@ -87,9 +88,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value - /* Position Statistics & Results */ - updateStatistics() - updateResults() /* Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure. */ @@ -258,82 +256,86 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } } - function updateStatistics() { + function calculateStatistics() { + calculatePositionStatistics() + calculateAssetsStatistics() - /* Profit Loss Calculation */ - tradingEngine.current.position.positionStatistics.profitLoss.value = - ( - tradingEngine.current.episode.episodeBaseAsset.balance.value * tradingEngine.current.position.endRate.value + - tradingEngine.current.episode.episodeQuotedAsset.balance.value - ) - - ( - tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + - tradingEngine.current.position.positionQuotedAsset.beginBalance - ) - tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) - - /* ROI Calculation */ - tradingEngine.current.position.positionStatistics.ROI.value = - ( - tradingEngine.current.position.positionStatistics.profitLoss.value - ) - * 100 / - ( - tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + - tradingEngine.current.position.positionQuotedAsset.beginBalance - ) - tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) - - /* Hit Fail Calculation */ - if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { - tradingEngine.current.position.positionStatistics.hitFail.value = 'Hit' - } else { - tradingEngine.current.position.positionStatistics.hitFail.value = 'Fail' + function calculatePositionStatistics() { + /* Profit Loss Calculation */ + tradingEngine.current.position.positionStatistics.profitLoss.value = + ( + tradingEngine.current.episode.episodeBaseAsset.balance.value * tradingEngine.current.position.endRate.value + + tradingEngine.current.episode.episodeQuotedAsset.balance.value + ) - + ( + tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + + tradingEngine.current.position.positionQuotedAsset.beginBalance + ) + tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) + + /* ROI Calculation */ + tradingEngine.current.position.positionStatistics.ROI.value = + ( + tradingEngine.current.position.positionStatistics.profitLoss.value + ) + * 100 / + ( + tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + + tradingEngine.current.position.positionQuotedAsset.beginBalance + ) + tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) + + /* Hit Fail Calculation */ + if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { + tradingEngine.current.position.positionStatistics.hitFail.value = 'Hit' + } else { + tradingEngine.current.position.positionStatistics.hitFail.value = 'Fail' + } + + /* Days Calculation */ + tradingEngine.current.position.positionStatistics.days.value = + tradingEngine.current.position.positionCounters.periods.value * + sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + + tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10) } - - /* Days Calculation */ - tradingEngine.current.position.positionStatistics.days.value = - tradingEngine.current.position.positionCounters.periods.value * - sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS - - tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10) - } - - function updateResults() { - /* Profit Loss Calculation */ - tradingEngine.current.position.positionBaseAsset.profitLoss.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value - - tradingEngine.current.position.positionBaseAsset.beginBalance - - tradingEngine.current.position.positionQuotedAsset.profitLoss.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value - - tradingEngine.current.position.positionQuotedAsset.beginBalance - - tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) - tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) - - /* ROI Calculation */ - tradingEngine.current.position.positionBaseAsset.ROI.value = - tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionBaseAsset.entryTargetSize.value - - tradingEngine.current.position.positionQuotedAsset.ROI.value = - tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value - - tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) - tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) - - /* Hit Fail Calculation */ - if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { - tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail' - } - if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { - tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail' + + function calculateAssetsStatistics() { + /* Profit Loss Calculation */ + tradingEngine.current.position.positionBaseAsset.profitLoss.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - + tradingEngine.current.position.positionBaseAsset.beginBalance + + tradingEngine.current.position.positionQuotedAsset.profitLoss.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - + tradingEngine.current.position.positionQuotedAsset.beginBalance + + tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) + tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) + + /* ROI Calculation */ + tradingEngine.current.position.positionBaseAsset.ROI.value = + tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / + tradingEngine.current.position.positionBaseAsset.entryTargetSize.value + + tradingEngine.current.position.positionQuotedAsset.ROI.value = + tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / + tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value + + tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) + tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) + + /* Hit Fail Calculation */ + if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail' + } + if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail' + } } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index c63f6988e..cd3ef2ae6 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -119,8 +119,18 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT firstLoopExecution = false } + if (checkInitialDatetime() === false) { + controlLoop() + return + } + + if (checkFinalDatetime() === false) { + closeEpisode('Final Datetime Reached') + return + } + if (checkLastCandle() === false) { - closeEpisode('Last Candle Reached') + controlLoop() return } @@ -129,11 +139,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT return } - if (checkInitialAndFinalDatetime() === false) { - controlLoop() - return - } - positionChartAtCurrentCandle() /* The chart was recalculated based on the current candle. */ @@ -211,7 +216,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT */ setImmediate(loop) } else { - updateEpisode('All Candles Processed') + updateEpisode('All Available Candles Processed') } } @@ -398,12 +403,17 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT return true } - function checkInitialAndFinalDatetime() { - /* Here we check that the current candle is not outside of user-defined time range at the session parameters.*/ + function checkInitialDatetime() { + /* Here we check that the current candle is not before the initial datetime defined at the session parameters.*/ if (tradingEngine.current.episode.candle.end.value < sessionParameters.timeRange.config.initialDatetime) { if (FULL_LOG === true) { logger.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 current candle is not after of the user-defined final datetime at the session parameters.*/ if (tradingEngine.current.episode.candle.begin.value > sessionParameters.timeRange.config.finalDatetime) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') } return false diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 9bead0bd2..1b5090964 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -7,6 +7,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM updateChart: updateChart, mantain: mantain, reset: reset, + calculateStatistics: calculateStatistics, runTriggerStage: runTriggerStage, runOpenStage: runOpenStage, runManageStage: runManageStage, @@ -101,6 +102,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM resetTradingEngineDataStructure() } + function calculateStatistics() { + tradingEpisodeModule.calculateStatistics() + tradingPositionModule.calculateStatistics() + } + function runTriggerStage() { /* We check if we will be triggering on, off or taking position. @@ -870,9 +876,6 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM /* Close the Position */ tradingPositionModule.closePosition() - tradingEpisodeModule.calculateResults() - tradingEpisodeModule.calculateStatistics() - /* Close the Strategy */ tradingStrategyModule.closeStrategy('Position Closed') diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 83148dbad..b938b9a31 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -131,6 +131,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Run the Close Stage */ await tradingStagesModule.runCloseStage() + + tradingStagesModule.calculateStatistics() + } catch (err) { /* If an error ocurred during execution, it was alreeady logged and From d398f1cbb40fd85d46576f4d89d5e3fa7c54d365 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 18 Aug 2020 11:16:35 +0200 Subject: [PATCH 061/112] Trading Process RE-implemented --- FileStorage.js | 43 +- LowFrequencyTrading/TradingEpisode.js | 137 ++-- LowFrequencyTrading/TradingPosition.js | 90 ++- LowFrequencyTrading/TradingProcess.js | 847 +++++++++++-------------- StatusReport.js | 22 +- 5 files changed, 565 insertions(+), 574 deletions(-) diff --git a/FileStorage.js b/FileStorage.js index 7cb2bf26b..575df8a7d 100644 --- a/FileStorage.js +++ b/FileStorage.js @@ -17,6 +17,8 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } let thisObject = { + asyncGetTextFile: asyncGetTextFile, + asyncCreateTextFile: asyncCreateTextFile, getTextFile: getTextFile, createTextFile: createTextFile, deleteTextFile: deleteTextFile @@ -24,6 +26,41 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { return thisObject + async function asyncGetTextFile(filePath, noRetry, canUsePrevious) { + /* This function allows its caller to work with async / await instead of callbacks */ + let promise = new Promise((resolve, reject) => { + + getTextFile(filePath, onFileRead, noRetry, canUsePrevious) + function onFileRead(err, text) { + + let response = { + err: err, + text: text + } + resolve(response) + } + }) + + return promise + } + + async function asyncCreateTextFile(filePath, fileContent, keepPrevious, noTemp) { + /* This function allows its caller to work with async / await instead of callbacks */ + let promise = new Promise((resolve, reject) => { + + createTextFile(filePath, fileContent, onFileWriten, keepPrevious, noTemp) + function onFileWriten(err) { + + let response = { + err: err + } + resolve(response) + } + }) + + return promise + } + function getTextFile(filePath, callBackFunction, noRetry, canUsePrevious) { let currentRetryGetTextFile = 0 @@ -216,12 +253,12 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { */ const fs = require('fs') if (noTemp === true) { - fs.writeFile(fileLocation, fileContent, onFileWritenn) + fs.writeFile(fileLocation, fileContent, onFileWriten) } else { - fs.writeFile(fileLocation + '.tmp', fileContent, onFileWritenn) + fs.writeFile(fileLocation + '.tmp', fileContent, onFileWriten) } - function onFileWritenn(err) { + function onFileWriten(err) { let retryTimeToUse = FAST_RETRY_TIME_IN_MILISECONDS if (currentRetryWriteTextFile > MAX_RETRY - 2) { retryTimeToUse = SLOW_RETRY_TIME_IN_MILISECONDS diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 96b6e5adc..f6d544188 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -113,46 +113,10 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin global.ONE_DAY_IN_MILISECONDS } - function calculateStatistics() { - calculateEpisodeStatistics() - calculateAssetsStatistics() + function calculateStatistics() { - function calculateEpisodeStatistics() { - /* Updating Profit Loss */ - tradingEngine.current.episode.episodeStatistics.profitLoss.value = - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - - tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) - - /* Updating ROI */ - tradingEngine.current.episode.episodeStatistics.ROI.value = - ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + - sessionParameters.sessionQuotedAsset.config.initialBalance + - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - ) / ( - sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.episode.candle.close.value + - sessionParameters.sessionQuotedAsset.config.initialBalance - ) - 1 - - tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) - - /* Updating Annualized Rate Of Return */ - tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = - tradingEngine.current.episode.episodeStatistics.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 - - tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10) - - /* Updating Hit or Fail */ - if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { - tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' - } else { - tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail' - } - } + calculateAssetsStatistics() + calculateEpisodeStatistics() function calculateAssetsStatistics() { @@ -180,14 +144,18 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10) - /* Updating ROI */ + /* + Updating ROI + + https://www.investopedia.com/articles/basics/10/guide-to-calculating-roi.asp + */ tradingEngine.current.episode.episodeBaseAsset.ROI.value = - (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value) / - sessionParameters.sessionBaseAsset.config.initialBalance - 1 + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value / + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * 100 tradingEngine.current.episode.episodeQuotedAsset.ROI.value = - (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value) / - sessionParameters.sessionQuotedAsset.config.initialBalance - 1 + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value / + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value * 100 tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10) tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10) @@ -204,14 +172,30 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) - /* Updating Annualized Rate Of Return */ + /* + Updating Annualized Rate Of Return + + https://www.investopedia.com/terms/a/annualized-rate.asp + */ tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = - tradingEngine.current.episode.episodeBaseAsset.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 + Math.pow( + ( + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value + ) / tradingEngine.current.episode.episodeBaseAsset.beginBalance.value + , + (365 / tradingEngine.current.episode.episodeStatistics.days.value) + ) - 1 tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = - tradingEngine.current.episode.episodeQuotedAsset.ROI.value / - tradingEngine.current.episode.episodeStatistics.days.value * 365 + Math.pow( + ( + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) / tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + , + (365 / tradingEngine.current.episode.episodeStatistics.days.value) + ) - 1 tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value, 10) tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value, 10) @@ -228,6 +212,61 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail' } } + + function calculateEpisodeStatistics() { + /* Updating Profit Loss */ + tradingEngine.current.episode.episodeStatistics.profitLoss.value = + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + + tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) + + /* + Updating ROI + + https://www.investopedia.com/articles/basics/10/guide-to-calculating-roi.asp + */ + tradingEngine.current.episode.episodeStatistics.ROI.value = + ( + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.endRate.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) / ( + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + ) * 100 + + tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) + + /* + Updating Annualized Rate Of Return + + https://www.investopedia.com/terms/a/annualized-rate.asp + */ + tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = + Math.pow( + ( + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) / + ( + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + ) + , + (365 / tradingEngine.current.episode.episodeStatistics.days.value) + ) - 1 + + tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10) + + /* Updating Hit or Fail */ + if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' + } else { + tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail' + } + } } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index c5606741a..98c131fa2 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -257,20 +257,56 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } function calculateStatistics() { - calculatePositionStatistics() + calculateAssetsStatistics() + calculatePositionStatistics() + + function calculateAssetsStatistics() { + /* Profit Loss Calculation */ + tradingEngine.current.position.positionBaseAsset.profitLoss.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - + tradingEngine.current.position.positionBaseAsset.beginBalance + + tradingEngine.current.position.positionQuotedAsset.profitLoss.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - + tradingEngine.current.position.positionQuotedAsset.beginBalance + + tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) + tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) + + /* ROI Calculation */ + tradingEngine.current.position.positionBaseAsset.ROI.value = + tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / + tradingEngine.current.position.positionBaseAsset.sizeFilled.value + + tradingEngine.current.position.positionQuotedAsset.ROI.value = + tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / + tradingEngine.current.position.positionQuotedAsset.sizeFilled.value + + tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) + tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) + + /* Hit Fail Calculation */ + if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail' + } + if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit' + } else { + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail' + } + } function calculatePositionStatistics() { /* Profit Loss Calculation */ tradingEngine.current.position.positionStatistics.profitLoss.value = ( - tradingEngine.current.episode.episodeBaseAsset.balance.value * tradingEngine.current.position.endRate.value + - tradingEngine.current.episode.episodeQuotedAsset.balance.value - ) - - ( - tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + - tradingEngine.current.position.positionQuotedAsset.beginBalance - ) + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.position.endRate.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) + tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) /* ROI Calculation */ @@ -299,44 +335,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10) } - - function calculateAssetsStatistics() { - /* Profit Loss Calculation */ - tradingEngine.current.position.positionBaseAsset.profitLoss.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value - - tradingEngine.current.position.positionBaseAsset.beginBalance - - tradingEngine.current.position.positionQuotedAsset.profitLoss.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value - - tradingEngine.current.position.positionQuotedAsset.beginBalance - - tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) - tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) - - /* ROI Calculation */ - tradingEngine.current.position.positionBaseAsset.ROI.value = - tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionBaseAsset.entryTargetSize.value - - tradingEngine.current.position.positionQuotedAsset.ROI.value = - tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value - - tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) - tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) - - /* Hit Fail Calculation */ - if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { - tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail' - } - if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { - tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit' - } else { - tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail' - } - } } function badDefinitionUnhandledException(err, message, node) { diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 7e66fe1e9..48ca5e79b 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -4,7 +4,6 @@ After execution, will save the time range and status report of the process. */ const FULL_LOG = true; - const LOG_FILE_CONTENT = false; const MODULE_NAME = "Trading Process"; const GMT_SECONDS = ':00.000 GMT+0000'; @@ -61,7 +60,7 @@ thisObject = undefined } - function start(callBackFunction) { + async function start(callBackFunction) { try { let currentTimeFrame let currentTimeFrameLabel @@ -77,7 +76,53 @@ let previousDay; // Holds the date of the previous day relative to the processing date. - getContextVariables(); + getContextVariables() + await processSingleFiles() + await processMarketFiles() + + /* + 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. We are also not going to loop + through days if we are processing market files. + */ + + if (currentTimeFrame) { + /* We are processing Market Files */ + + let chart = {} + buildCharts(chart) + + await generateOutput(chart) + await writeProcessFiles() + + checkIfSessionMustStop() + + } else { + /* We are processing Daily Files */ + + /* + Go back one day to start well when we advance time at the begining of the loop. + */ + bot.tradingProcessDate = new Date(contextVariables.lastFile.valueOf() - global.ONE_DAY_IN_MILISECONDS); + do { + if (advanceTime() === false) {break} + if (checkStopTaskGracefully() === false) {break} + if (checkStopProcessing() === false) {break} + + await processDailyFiles() + + let chart = {} + buildCharts(chart) + + await generateOutput(chart) + await writeProcessFiles() + } + while (true) + } + + callBackFunction(global.DEFAULT_OK_RESPONSE) function getContextVariables() { try { @@ -96,13 +141,13 @@ if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; + return } if (statusReport.status === "Status Report is corrupt.") { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. "); callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; + return } thisReport = statusReport.file; @@ -116,7 +161,7 @@ } logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message); callBackFunction(customOK); - return; + return } contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf()); @@ -128,7 +173,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> You need to UPDATE your datasets in order to run a " + bot.SESSION.type) } callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + return } @@ -138,13 +183,13 @@ if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; + return } if (statusReport.status === "Status Report is corrupt.") { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process."); callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; + return } thisReport = statusReport.file; @@ -155,11 +200,10 @@ if (bot.RESUME !== true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); } startFromBegining(); - return; + return } contextVariables.lastFile = new Date(thisReport.lastFile); - processSingleFiles(); - return; + return } else { /* @@ -169,14 +213,13 @@ */ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); } startFromBegining(); - return; + return } function startFromBegining() { contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS); if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); } - processSingleFiles(); } } catch (err) { @@ -189,242 +232,158 @@ } } - function processSingleFiles() { - let dependencyIndex = 0; + async function processSingleFiles() { dataFiles = new Map(); - dependencyLoopBody(); - function dependencyLoopBody() { + for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) { let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; - getFile(); + + if (datasetModule.node.config.codeName !== "Single-File") { + continue + } - function getFile() { - if (datasetModule.node.config.codeName !== "Single-File") { - dependencyControlLoop(); - return - } + if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) { + continue + } - if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) { - dependencyControlLoop(); - return - } + let fileName = "Data.json"; + let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName; - let fileName = "Data.json"; - let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName; - datasetModule.getTextFile(filePath, fileName, onFileReceived); + /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ + let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + onFileReceived(response.err, response.text) - function onFileReceived(err, text) { - try { - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> text = " + text); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - if (err.message === 'File does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message); - callBackFunction(err); - } - return; + function onFileReceived(err, text) { + try { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + if (err.message === 'File does not exist.') { + logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } else { + logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> onFileReceived -> err = " + err.message); + callBackFunction(err); } - - let dataFile = JSON.parse(text); - dataFiles.set(dependency.id, dataFile); - - dependencyControlLoop(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } + + let dataFile = JSON.parse(text); + dataFiles.set(dependency.id, dataFile); + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> onFileReceived -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } - function dependencyControlLoop() { - dependencyIndex++ - if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - dependencyLoopBody() - } else { - let mapKey = "Single Files" - multiPeriodDataFiles.set(mapKey, dataFiles) - processMarketFiles() - } - } + let mapKey = "Single Files" + multiPeriodDataFiles.set(mapKey, dataFiles) } - function processMarketFiles() { + async function processMarketFiles() { /* - We do market files first since if the simulation is run on daily files, there will be a loop to getch each daus files and we do not need - that loop to reload market files. + We do market files first since if the simulation is run on daily files, there will + be a loop to get each of those files and we do not need that loop to reload market files. + + We will iterate through all posible timeFrames. */ - let n; - timeFramesLoop(); - - function timeFramesLoop() { - /* - We will iterate through all posible timeFrames. - */ - n = 0 // loop Variable representing each possible period as defined at the timeFrames array. - timeFramesLoopBody(); - } - - function timeFramesLoopBody() { + for (let n = 0; n < global.marketFilesPeriods.length; n++) { const timeFrame = global.marketFilesPeriods[n][0]; const timeFrameLabel = global.marketFilesPeriods[n][1]; - let dependencyIndex = 0; dataFiles = new Map(); if (bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel) { currentTimeFrame = global.marketFilesPeriods[n][0]; currentTimeFrameLabel = global.marketFilesPeriods[n][1]; } - dependencyLoopBody(); - - function dependencyLoopBody() { + for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) { let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; - getFile(); - function getFile() { - if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") { - dependencyControlLoop(); - return + if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") { + continue + } + + if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { + if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { + continue } + } - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { - if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { - dependencyControlLoop(); + let fileName = "Data.json"; + let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel; + + /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ + let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + onFileReceived(response.err, response.text) + + function onFileReceived(err, text) { + try { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + if (err.message === 'File does not exist.') { + logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } else { + logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> onFileReceived -> err = " + err.message); + callBackFunction(err); + } return } + + let dataFile = JSON.parse(text); + let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record) + dataFiles.set(dependency.id, trimmedDataFile); } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> onFileReceived -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } - let fileName = "Data.json"; - let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel; - datasetModule.getTextFile(filePath, fileName, onFileReceived); - - function onFileReceived(err, text) { - try { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - if (err.message === 'File does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message); - callBackFunction(err); - } - return; - } - - let dataFile = JSON.parse(text); - let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record) - dataFiles.set(dependency.id, trimmedDataFile); - - dependencyControlLoop(); + function trimDataFile(dataFile, recordDefinition) { + /* + Here we will discard all the records in a file that are outside of the current time range. + */ + let beginIndex + let endIndex + let result = [] + for (let i = 0; i < recordDefinition.properties.length; i++) { + let property = recordDefinition.properties[i] + if (property.config.codeName === 'begin') { + beginIndex = i } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + if (property.config.codeName === 'end') { + endIndex = i } } + for (let i = 0; i < dataFile.length; i++) { + let dataRecord = dataFile[i] + let begin = dataRecord[beginIndex] + let end = dataRecord[endIndex] + if (end < bot.SESSION.parameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element + if (begin > bot.SESSION.parameters.timeRange.config.finalDatetime) { continue } + result.push(dataRecord) + } + return result } } - function dependencyControlLoop() { - dependencyIndex++; - if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - dependencyLoopBody(); - } else { - let mapKey = global.marketFilesPeriods[n][1]; - multiPeriodDataFiles.set(mapKey, dataFiles) - timeFramesControlLoop(); - } - } - } - - function timeFramesControlLoop() { - n++; - if (n < global.marketFilesPeriods.length) { - timeFramesLoopBody(); - } else { - processDailyFiles(); - } + let mapKey = global.marketFilesPeriods[n][1]; + multiPeriodDataFiles.set(mapKey, dataFiles) } } - function processDailyFiles() { - let n = 0; - if (currentTimeFrame) { - buildCharts() - return - } - bot.tradingProcessDate = new Date(contextVariables.lastFile.valueOf() - global.ONE_DAY_IN_MILISECONDS); // Go back one day to start well when we advance time at the begining of the loop. - advanceTime(); - - function advanceTime() { - bot.tradingProcessDate = new Date(bot.tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS); - previousDay = new Date(bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> bot.tradingProcessDate = " + bot.tradingProcessDate); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> previousDay = " + previousDay); } - - /* Validation that we are not going past the user defined end date. */ - if (bot.tradingProcessDate.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { - - const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); } - - bot.SESSION.stop(logText) - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - - /* Validation that we are not going past the head of the market. */ - if (bot.tradingProcessDate.valueOf() > contextVariables.dateEndOfMarket.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1) { - - const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> " + logText); } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - - checkStopTaskGracefully(); - } - - function checkStopTaskGracefully() { - /* Validation that we dont need to stop. */ - if (global.STOP_TASK_GRACEFULLY === true) { - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - checkStopProcessing(); - } - - function checkStopProcessing() { - /* Validation that we dont need to stop. */ - if (bot.STOP_SESSION === true) { - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - timeFramesLoop(); - } - - function timeFramesLoop() { - /* Telling the world we are alive and doing well */ - let processingDate = bot.tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); - bot.processHeartBeat(processingDate) - /* - We will iterate through all posible timeFrames. - */ - n = 0 // loop Variable representing each possible period as defined at the timeFrames array. - timeFramesLoopBody(); - } - - function timeFramesLoopBody() { + async function processDailyFiles() { + /* Telling the world we are alive and doing well and which date we are processing right now. */ + let processingDate = bot.tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); + bot.processHeartBeat(processingDate) + + /* + We will iterate through all posible timeFrames. + */ + for (let n = 0; n < global.dailyFilePeriods.length; n++) { const timeFrame = global.dailyFilePeriods[n][0]; const timeFrameLabel = global.dailyFilePeriods[n][1]; @@ -435,8 +394,7 @@ if (period === timeFrameLabel) { validPeriod = true } } if (validPeriod === false) { - timeFramesControlLoop(); - return; + continue } } @@ -445,101 +403,114 @@ currentTimeFrameLabel = global.dailyFilePeriods[n][1]; } - let dependencyIndex = 0; dataFiles = new Map(); - dependencyLoopBody(); - function dependencyLoopBody() { + for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) { let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; let file - getFile() - function getFile() { - if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") { - dependencyControlLoop(); - return + if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") { + continue + } + + if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { + if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { + continue } + } - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { - if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { - dependencyControlLoop(); + let dateForPath = bot.tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); + let filePath + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; + } else { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; + } + let fileName = "Data.json"; + + /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ + let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + onFileReceived(response.err, response.text) + + function onFileReceived(err, text) { + try { + if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { + logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); return } - } - - let dateForPath = bot.tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); - let filePath - if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; - } else { - filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; - } - let fileName = "Data.json"; - - datasetModule.getTextFile(filePath, fileName, onFileReceived); - - function onFileReceived(err, text) { - try { - if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> Not Ok -> err = " + err.code); - callBackFunction(err); - return; - } - - file = JSON.parse(text); - dataFiles.set(dependency.id, file); - dependencyControlLoop(); + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> onFileReceived -> Not Ok -> err = " + err.code); + callBackFunction(err); + return } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + + file = JSON.parse(text); + dataFiles.set(dependency.id, file); + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> onFileReceived -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } - function dependencyControlLoop() { - dependencyIndex++; - - if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - dependencyLoopBody(); - } else { - let mapKey = global.dailyFilePeriods[n][1]; - multiPeriodDataFiles.set(mapKey, dataFiles) - timeFramesControlLoop(); - } - } + let mapKey = global.dailyFilePeriods[n][1]; + multiPeriodDataFiles.set(mapKey, dataFiles) } - function timeFramesControlLoop() { - n++; - if (n < global.dailyFilePeriods.length) { - timeFramesLoopBody(); - } else { - n = 0; - if (currentTimeFrame !== undefined) { - buildCharts(advanceTime); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesControlLoop -> Time Frame not Recognized. Can not continue."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } + if (currentTimeFrame !== undefined) { + buildCharts(advanceTime); + } else { + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> Time Frame not Recognized. Can not continue."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function buildCharts(advanceTime) { + function advanceTime() { + bot.tradingProcessDate = new Date(bot.tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS); + previousDay = new Date(bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> bot.tradingProcessDate = " + bot.tradingProcessDate); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> previousDay = " + previousDay); } + + /* Validation that we are not going past the user defined end date. */ + if (bot.tradingProcessDate.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { + const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> " + logText); } + + bot.SESSION.stop(logText) + return false + } + + /* Validation that we are not going past the head of the market. */ + if (bot.tradingProcessDate.valueOf() > contextVariables.dateEndOfMarket.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1) { + const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> " + logText); } + return false + } + } + + function checkStopTaskGracefully() { + /* Validation that we dont need to stop. */ + if (global.STOP_TASK_GRACEFULLY === true) { + return false + } + } + + function checkStopProcessing() { + /* Validation that we dont need to stop. */ + if (bot.STOP_SESSION === true) { + return false + } + } + + function buildCharts(chart) { const COMMONS = require('../Commons.js') let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE) - let chart = {} let mainDependency = {} /* The first phase here is about checking that we have everything we need at the definition level. */ @@ -586,222 +557,130 @@ let propertyName = 'atAnyTimeFrame' chart[propertyName] = products } + } + async function generateOutput(chart) { tradingOutputModule.start( chart, currentTimeFrame, currentTimeFrameLabel, - bot.tradingProcessDate, - onOutputGenerated); + bot.tradingProcessDate + ) - function onOutputGenerated(err) { - try { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - callBackFunction(err); - return; + bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution. + } + + async function writeProcessFiles() { + + await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) + await writeDataRanges(currentTimeFrameLabel) + + if (currentTimeFrame > global.dailyFilePeriods[0][0]) { + await writeMarketStatusReport(currentTimeFrameLabel) + } else { + await writeDailyStatusReport(bot.tradingProcessDate, currentTimeFrameLabel) + } + + async function writeDataRanges(currentTimeFrameLabel) { + for (let outputDatasetIndex = 0; outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; outputDatasetIndex++) { + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + await writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel, controlLoop); + } + + async function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel) { + let dataRange = { + begin: pBegin.valueOf(), + end: pEnd.valueOf() + global.ONE_DAY_IN_MILISECONDS } - - bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution. - - writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel, onTimeFrameFilesWritten) - function onTimeFrameFilesWritten() { - writeDataRanges(currentTimeFrameLabel, onDataRangesWritten); + + let fileContent = JSON.stringify(dataRange); + let fileName = '/Data.Range.json'; + let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName; + + let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n') + + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) } - - function onDataRangesWritten(err) { - try { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack); - callBackFunction(err); - return; - } - - if (currentTimeFrame > global.dailyFilePeriods[0][0]) { - writeMarketStatusReport(currentTimeFrameLabel, onMarketStatusReport) - } else { - writeDailyStatusReport(bot.tradingProcessDate, currentTimeFrameLabel, onDailyStatusReport); - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onWritten -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + + /* + Raise the event that the Data Range was Updated. + */ + let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let event = { + dateRange: dataRange + } + + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) + } + } + + async function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) { + + for (let outputDatasetIndex = 0; outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; outputDatasetIndex++) { + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + await writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily') + await writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Market') + + async function writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, processType) { + + let timeFramesArray = [] + timeFramesArray.push(currentTimeFrameLabel) + + let fileContent = JSON.stringify(timeFramesArray) + let fileName = '/Time.Frames.json'; + + let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + processType + fileName; + + let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n') + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) } } - - function onDailyStatusReport() { - advanceTime(); - } - - function onMarketStatusReport() { - /* This is where we check if we need reached the user defined end datetime. */ - const now = new Date() - if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { - const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> onMarketStatusReport -> " + logText); } - - bot.SESSION.stop('because it just finished.') - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - if (bot.SESSION.type === 'Backtesting Session') { - bot.SESSION.stop('Backtesting Session over Market Files Finished.') - } - callBackFunction(global.DEFAULT_OK_RESPONSE); - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> buildCharts -> onOutputGenerated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - - function writeDataRanges(currentTimeFrameLabel, callBack) { - let outputDatasetIndex = -1; - controlLoop(); - - function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; - writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel, controlLoop); - } - - function controlLoop() { - outputDatasetIndex++; - if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { - productLoopBody(); - } else { - callBack(global.DEFAULT_OK_RESPONSE); - } - } - } - - function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel, callBack) { - let dataRange = { - begin: pBegin.valueOf(), - end: pEnd.valueOf() + global.ONE_DAY_IN_MILISECONDS - }; - - let fileContent = JSON.stringify(dataRange); - let fileName = '/Data.Range.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName; - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack); - callBack(err); - return; - } - - if (LOG_FILE_CONTENT === true) { - logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent); - } - - let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset - let event = { - dateRange: dataRange - } - - global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) - - callBack(global.DEFAULT_OK_RESPONSE); - } - } - - function writeDailyStatusReport(lastFileDate, currentTimeFrameLabel, callBack) { - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName - let thisReport = statusDependencies.statusReports.get(reportKey); - - thisReport.file.lastExecution = bot.currentDaytime; - thisReport.file.lastFile = lastFileDate; - thisReport.file.simulationState = bot.simulationState; - thisReport.file.timeFrames = currentTimeFrameLabel - thisReport.save(callBack); - } - - function writeMarketStatusReport(currentTimeFrameLabel, callBack) { - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName - let thisReport = statusDependencies.statusReports.get(reportKey); - - thisReport.file.lastExecution = bot.processDatetime; - thisReport.file.simulationState = bot.simulationState; - thisReport.file.timeFrames = currentTimeFrameLabel - - logger.newInternalLoop(bot.codeName, bot.process, bot.tradingProcessDate); - thisReport.save(callBack); - } - - function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel, callBack) { - let outputDatasetIndex = -1; - controlLoop() - - function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; - writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily', dailyFileWritten) - - function dailyFileWritten() { - writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Market', controlLoop) } } - function controlLoop() { - outputDatasetIndex++ - if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { - productLoopBody() - } else { - callBack() - } + async function writeDailyStatusReport(lastFileDate, currentTimeFrameLabel) { + let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName + let thisReport = statusDependencies.statusReports.get(reportKey) + + thisReport.file.lastExecution = bot.currentDaytime + thisReport.file.lastFile = lastFileDate + thisReport.file.simulationState = bot.simulationState + thisReport.file.timeFrames = currentTimeFrameLabel + await thisReport.asyncSave() + } + + async function writeMarketStatusReport(currentTimeFrameLabel) { + let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName + let thisReport = statusDependencies.statusReports.get(reportKey); + + thisReport.file.lastExecution = bot.processDatetime + thisReport.file.simulationState = bot.simulationState + thisReport.file.timeFrames = currentTimeFrameLabel + + logger.newInternalLoop(bot.codeName, bot.process, bot.tradingProcessDate) + await thisReport.asyncSave() } } - function writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, processType, callBack) { - - let timeFramesArray = [] - timeFramesArray.push(currentTimeFrameLabel) - - let fileContent = JSON.stringify(timeFramesArray) - let fileName = '/Time.Frames.json'; - - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + processType + fileName; - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) - - function onFileCreated(err) { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> writeTimeFramesFile -> onFileCreated -> err = " + err.stack) - callBack(err) - return - } - callBack() - } - } - - function trimDataFile(dataFile, recordDefinition) { + function checkIfSessionMustStop() { /* - Here we will discard all the records in a file that are outside of the current time range. + The natural way for the session to stop is when we reached the + user defined end datetime. */ - let beginIndex - let endIndex - let result = [] - for (let i = 0; i < recordDefinition.properties.length; i++) { - let property = recordDefinition.properties[i] - if (property.config.codeName === 'begin') { - beginIndex = i - } - if (property.config.codeName === 'end') { - endIndex = i - } + const now = new Date() + if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { + const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeProcessFiles -> onMarketStatusReport -> " + logText); } + + bot.SESSION.stop('because it just finished.') + return } - for (let i = 0; i < dataFile.length; i++) { - let dataRecord = dataFile[i] - let begin = dataRecord[beginIndex] - let end = dataRecord[endIndex] - if (end < bot.SESSION.parameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element - if (begin > bot.SESSION.parameters.timeRange.config.finalDatetime) { continue } - result.push(dataRecord) + if (bot.SESSION.type === 'Backtesting Session') { + bot.SESSION.stop('Backtesting Session over Market Files Finished.') + return } - return result } } catch (err) { @@ -814,4 +693,22 @@ str = str.toString(); return str.length < max ? pad(" " + str, max) : str; } -}; + + async function asyncGetDatasetFile(datasetModule, filePath, fileName) { + + let promise = new Promise((resolve, reject) => { + + datasetModule.getTextFile(filePath, fileName, onFileReceived); + function onFileReceived(err, text) { + + let response = { + err: err, + text: text + } + resolve(response) + } + }); + + return promise + } +} diff --git a/StatusReport.js b/StatusReport.js index d39a254fb..2600fca26 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -20,6 +20,7 @@ finalize: finalize, load: load, save: save, + asyncSave: asyncSave, status: undefined }; @@ -343,4 +344,23 @@ } } -}; + async function asyncSave() { + + let fileName = "Status.Report.json"; + let filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process; + + filePath += '/' + fileName + let fileContent = JSON.stringify(thisObject.file); + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated, true); + let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n', true) + + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) + } + + /* All good, lets emit the event that means data has been updated. */ + let processOutput = PROCESS_OUTPUT.newProcessOutput(bot, logger) + processOutput.raiseEvents(thisObject.file.lastFile, thisObject.file.timeFrames, callBackFunction); + } +} From 68f767e15a358e0b0bf39d547195ba4d20f3c29e Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 18 Aug 2020 14:12:15 +0200 Subject: [PATCH 062/112] Async Await Implementation finished --- LowFrequencyTrading/TradingOutput.js | 149 +++++------------- LowFrequencyTrading/TradingPosition.js | 8 +- LowFrequencyTrading/TradingProcess.js | 20 +-- LowFrequencyTrading/TradingSimulation.js | 191 ++++++++++------------- MultiPeriodDaily.js | 2 +- ProcessOutput.js | 69 +++++++- StatusReport.js | 3 +- 7 files changed, 210 insertions(+), 232 deletions(-) diff --git a/LowFrequencyTrading/TradingOutput.js b/LowFrequencyTrading/TradingOutput.js index 0abe8c40a..5973fcb96 100644 --- a/LowFrequencyTrading/TradingOutput.js +++ b/LowFrequencyTrading/TradingOutput.js @@ -3,7 +3,6 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL 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 FULL_LOG = true const MODULE_NAME = 'Trading Bot' let thisObject = { @@ -24,21 +23,13 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL commons = undefined } - function initialize(callBackFunction) { - try { - logger.fileName = MODULE_NAME - logger.initialize() + function initialize() { - callBackFunction(global.DEFAULT_OK_RESPONSE) - } catch (err) { - logger.write(MODULE_NAME, '[ERROR] initialize -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - } } - function start(chart, timeFrame, timeFrameLabel, currentDay, callBackFunction) { + async function start(chart, timeFrame, timeFrameLabel, currentDay) { try { - bot.processingDailyFiles + if (timeFrame > global.dailyFilePeriods[0][0]) { bot.processingDailyFiles = false } else { @@ -51,10 +42,6 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets let market = bot.market - let totalFilesToBeRead = 0 - let totalFilesRead = 0 - let totalFilesToBeWritten = 0 - let totalFilesWritten = 0 let outputDatasetsMap = new Map() if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { @@ -63,21 +50,27 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL */ bot.simulationState.tradingEngine = bot.TRADING_ENGINE bot.simulationState.tradingSystem = bot.TRADING_SYSTEM - initializeOutputs() + await initializeOutputs() } else { - readFiles() + await readFiles() } + + await tradingSimulation.runSimulation( + chart, + outputDatasetsMap, + writeFiles + ) return - function initializeOutputs() { + async function initializeOutputs() { if (bot.processingDailyFiles) { - initializeDailyFiles() + await initializeDailyFiles() } else { - initializeMarketFiles() + await initializeMarketFiles() } } - function initializeDailyFiles() { + async function initializeDailyFiles() { for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] let dataset = outputDatasetNode.referenceParent @@ -86,10 +79,9 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL outputDatasetsMap.set(dataset.parentNode.config.codeName, []) } } - runSimulation() } - function initializeMarketFiles() { + async function initializeMarketFiles() { for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] let dataset = outputDatasetNode.referenceParent @@ -98,23 +90,22 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL outputDatasetsMap.set(dataset.parentNode.config.codeName, []) } } - runSimulation() } - function readFiles() { + async function readFiles() { /* This bot have an output of files that it generates. At every call to the bot, it needs to read the previously generated files in order to later append more information after the execution is over. Here in this function we are going to read those output files and get them ready for appending content during the simulation. */ if (bot.processingDailyFiles) { - readDailyFiles() + await readDailyFiles() } else { - readMarketFiles() + await readMarketFiles() } } - function readMarketFiles() { + async function readMarketFiles() { for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] let dataset = outputDatasetNode.referenceParent @@ -124,12 +115,12 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL let fileName = 'Data.json' let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel - readOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName) } } } - function readDailyFiles() { + async function readDailyFiles() { for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] let dataset = outputDatasetNode.referenceParent @@ -140,70 +131,38 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL let fileName = 'Data.json' let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath - readOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName) } } } - function readOutputFile(fileName, filePath, productName) { - totalFilesToBeRead++ + async function readOutputFile(fileName, filePath, productName) { filePath += '/' + fileName - fileStorage.getTextFile(filePath, onFileRead, true) - function onFileRead(err, text) { - try { + let response = await fileStorage.asyncGetTextFile(filePath, true) - if (err.message === 'File does not exist.') { - outputDatasetsMap.set(productName, []) - anotherFileRead() - return - } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath) - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) - - callBackFunction(err) - return - } - - outputDatasetsMap.set(productName, JSON.parse(text)) - anotherFileRead() - } catch (err) { - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - } + if (response.err.message === 'File does not exist.') { + outputDatasetsMap.set(productName, []) + return } - } - - function anotherFileRead() { - totalFilesRead++ - if (totalFilesRead === totalFilesToBeRead) { - runSimulation() + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) } + outputDatasetsMap.set(productName, JSON.parse(text)) } - function runSimulation() { - tradingSimulation.runSimulation( - chart, - outputDatasetsMap, - writeFiles, - callBackFunction) - } - - function writeFiles() { + async function writeFiles() { /* The output of files which were appended with information during the simulation execution, now needs to be saved. */ if (bot.processingDailyFiles) { - writeDailyFiles() + await writeDailyFiles() } else { - writeMarketFiles() + await writeMarketFiles() } } - function writeMarketFiles() { + async function writeMarketFiles() { for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] let dataset = outputDatasetNode.referenceParent @@ -213,12 +172,12 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL let fileName = 'Data.json' let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel - writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName) } } } - function writeDailyFiles() { + async function writeDailyFiles() { for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] let dataset = outputDatasetNode.referenceParent @@ -229,47 +188,25 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL let fileName = 'Data.json' let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath - writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName) + await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName) } } } - function writeOutputFile(fileName, filePath, productName) { - totalFilesToBeWritten++ + async function writeOutputFile(fileName, filePath, productName) { filePath += '/' + fileName let fileContent = JSON.stringify(outputDatasetsMap.get(productName)) - fileStorage.createTextFile(filePath, fileContent, onFileCreated) + let response = await fileStorage.asyncCreateTextFile(filePath, fileContent) - function onFileCreated(err) { - try { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath) - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) - - callBackFunction(err) - return - } - - anotherFileWritten() - } catch (err) { - logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - } - } - } - - function anotherFileWritten() { - totalFilesWritten++ - if (totalFilesWritten === totalFilesToBeWritten) { - callBackFunction(global.DEFAULT_OK_RESPONSE) + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) } } } catch (err) { logger.write(MODULE_NAME, '[ERROR] start -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) + throw(global.DEFAULT_FAIL_RESPONSE) } } } diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 98c131fa2..a147149fb 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -256,7 +256,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } } - function calculateStatistics() { + function calculateStatistics(tradingEngineStageNode) { calculateAssetsStatistics() calculatePositionStatistics() @@ -277,11 +277,11 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* ROI Calculation */ tradingEngine.current.position.positionBaseAsset.ROI.value = tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value tradingEngine.current.position.positionQuotedAsset.ROI.value = tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / - tradingEngine.current.position.positionQuotedAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) @@ -306,7 +306,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.position.endRate.value + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value ) - + tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) /* ROI Calculation */ diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 48ca5e79b..68ee99d0c 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -31,16 +31,18 @@ function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) { try { - logger.fileName = MODULE_NAME; - logger.initialize(); + logger.fileName = MODULE_NAME + logger.initialize() - statusDependencies = pStatusDependencies; - dataDependenciesModule = pDataDependencies; - processConfig = pProcessConfig; + statusDependencies = pStatusDependencies + dataDependenciesModule = pDataDependencies + processConfig = pProcessConfig let TRADING_OUTPUT_MODULE = require("./TradingOutput") - tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE); - tradingOutputModule.initialize(callBackFunction); + tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE) + tradingOutputModule.initialize() + + callBackFunction(global.DEFAULT_OK_RESPONSE) } catch (err) { logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); @@ -560,7 +562,7 @@ } async function generateOutput(chart) { - tradingOutputModule.start( + await tradingOutputModule.start( chart, currentTimeFrame, currentTimeFrameLabel, @@ -584,7 +586,7 @@ async function writeDataRanges(currentTimeFrameLabel) { for (let outputDatasetIndex = 0; outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; outputDatasetIndex++) { let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; - await writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel, controlLoop); + await writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel); } async function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel) { diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index cd3ef2ae6..65cbf0e61 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -19,11 +19,11 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT thisObject = undefined } - function runSimulation( + async function runSimulation( chart, outputDatasetsMap, - callback, - callBackFunction) { + writeFiles, + ) { try { let tradingSystem = bot.simulationState.tradingSystem @@ -58,51 +58,54 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let heartBeatDate let previousHeartBeatDate let firstLoopExecution = true + + /* + Loop Initialization + */ + if (bot.FIRST_EXECUTION === false && tradingEngine.last.candle !== undefined) { + /* Estimate the Initial Candle based on the last candle saved at tradingEngine */ + let firstBegin = candles[0].begin + let lastBegin = tradingEngine.last.candle.begin + let diff = lastBegin - firstBegin + let amount = diff / sessionParameters.timeFrame.config.value - initializeLoop() + tradingEngine.current.episode.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle + if (tradingEngine.current.episode.candle.index.value < 0 || tradingEngine.current.episode.candle.index.value > candles.length - 1) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.') + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.episode.candle.index.value = ' + tradingEngine.current.episode.candle.index.value) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount) - function initializeLoop() { - if (bot.FIRST_EXECUTION === false && tradingEngine.last.candle !== undefined) { - /* Estimate the Initial Candle based on the last candle saved at tradingEngine */ - let firstBegin = candles[0].begin - let lastBegin = tradingEngine.last.candle.begin - let diff = lastBegin - firstBegin - let amount = diff / sessionParameters.timeFrame.config.value - - tradingEngine.current.episode.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle - if (tradingEngine.current.episode.candle.index.value < 0 || tradingEngine.current.episode.candle.index.value > candles.length - 1) { - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.') - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.episode.candle.index.value = ' + tradingEngine.current.episode.candle.index.value) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount) - - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - return - } - } else { - /* Estimate Initial Candle based on the timeRage 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 - - tradingEngine.current.episode.candle.index.value = Math.trunc(amount) - if (tradingEngine.current.episode.candle.index.value < 0) { tradingEngine.current.episode.candle.index.value = 0 } - if (tradingEngine.current.episode.candle.index.value > 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. - */ - tradingEngine.current.episode.candle.index.value = candles.length - 1 - } + throw(global.DEFAULT_FAIL_RESPONSE) + return } + } else { + /* Estimate Initial Candle based on the timeRage 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 - loop() + tradingEngine.current.episode.candle.index.value = Math.trunc(amount) + if (tradingEngine.current.episode.candle.index.value < 0) { tradingEngine.current.episode.candle.index.value = 0 } + if (tradingEngine.current.episode.candle.index.value > 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. + */ + tradingEngine.current.episode.candle.index.value = candles.length - 1 + } } - async function loop() { + /* + Main Simulation Loop + */ + let breakLoop = false + for( let i = tradingEngine.current.episode.candle.index.value; i < candles.length; i++) { + + tradingEngine.current.episode.candle.index.value = i if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.episode.candle.index.value) } let candle = candles[tradingEngine.current.episode.candle.index.value] // This is the current candle the Simulation is working at. @@ -120,23 +123,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (checkInitialDatetime() === false) { - controlLoop() - return - } - - if (checkFinalDatetime() === false) { - closeEpisode('Final Datetime Reached') - return - } - - if (checkLastCandle() === false) { - controlLoop() - return - } - - if (checkMinimunAndMaximunBalance() === false) { - closeEpisode('Min or Max Balance Reached') - return + continue } positionChartAtCurrentCandle() @@ -183,75 +170,61 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingEngineModule.setCurrentCycle('Second') await tradingSystemModule.run() - controlLoop() - } - - function controlLoop() { /* Checking if we should continue processing this loop or not. */ if (bot.STOP_SESSION === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') updateEpisode('Session Stopped') - return + breakLoop = true } if (global.STOP_TASK_GRACEFULLY === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') updateEpisode('Task Stopped') - return + breakLoop = true } - if (tradingEngine.current.episode.candle.index.value + 1 < candles.length) { + if (checkFinalDatetime() === false) { + closeEpisode('Final Datetime Reached') + breakLoop = true + } - /* Add new records to the process output */ - tradingRecordsModule.appendRecords('Second') - - tradingEngine.current.episode.candle.index.value++ - - /* - This will execute the next loop 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(loop) - } else { + if (checkLastCandle() === false) { updateEpisode('All Available Candles Processed') + breakLoop = true } + + if (checkMinimunAndMaximunBalance() === false) { + closeEpisode('Min or Max Balance Reached') + breakLoop = true + } + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords('Second') + + if (breakLoop === true) {break} } + tradingEngineModule.finalize() + tradingSystemModule.finalize() + tradingRecordsModule.finalize() + tradingEpisodeModule.finalize() + + tradingEngineModule = undefined + tradingSystemModule = undefined + tradingRecordsModule = undefined + tradingEpisodeModule = undefined + + await writeFiles() + function closeEpisode(exitType) { tradingEpisodeModule.updateExitType(exitType) tradingEpisodeModule.closeEpisode() - - /* Add new records to the process output */ - tradingRecordsModule.appendRecords('Second') - - afterLoop() } function updateEpisode(exitType) { tradingEpisodeModule.updateExitType(exitType) - - /* Add new records to the process output */ - tradingRecordsModule.appendRecords('Second') - - afterLoop() - } - - function afterLoop() { - - tradingEngineModule.finalize() - tradingSystemModule.finalize() - tradingRecordsModule.finalize() - tradingEpisodeModule.finalize() - - tradingEngineModule = undefined - tradingSystemModule = undefined - tradingRecordsModule = undefined - tradingEpisodeModule = undefined - - callback() } function heartBeat() { @@ -373,7 +346,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } catch (err) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName) logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) + throw(global.DEFAULT_FAIL_RESPONSE) } } @@ -384,7 +357,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT */ if (bot.processingDailyFiles) { // We are processing Daily Files let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value - if (tradingEngine.current.episode.candle.index.value === candles.length - 1) { + if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } return false @@ -395,7 +368,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } } else { // We are processing Market Files - if (tradingEngine.current.episode.candle.index.value === candles.length - 1) { + if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } return false } @@ -413,8 +386,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } function checkFinalDatetime() { - /* Here we check that the current candle is not after of the user-defined final datetime at the session parameters.*/ - if (tradingEngine.current.episode.candle.begin.value > sessionParameters.timeRange.config.finalDatetime) { + /* Here we check that the next candle is not after of the user-defined final datetime at the session parameters.*/ + if (tradingEngine.current.episode.candle.begin.value + sessionParameters.timeFrame.config.value > sessionParameters.timeRange.config.finalDatetime) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') } return false } @@ -474,7 +447,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } catch (err) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) + throw(global.DEFAULT_FAIL_RESPONSE) } } } diff --git a/MultiPeriodDaily.js b/MultiPeriodDaily.js index b3d0899ce..7ae3cc3b5 100644 --- a/MultiPeriodDaily.js +++ b/MultiPeriodDaily.js @@ -490,7 +490,7 @@ function writeDataRange(pBegin, pEnd, productCodeName, callBack) { let dataRange = { begin: pBegin.valueOf(), - end: pEnd.valueOf() + end: pEnd.valueOf() + ONE_DAY_IN_MILISECONDS }; let fileContent = JSON.stringify(dataRange) let fileName = '/Data.Range.json'; diff --git a/ProcessOutput.js b/ProcessOutput.js index d9ee5a6ba..71d7cdc81 100644 --- a/ProcessOutput.js +++ b/ProcessOutput.js @@ -9,7 +9,8 @@ let bot = BOT; let thisObject = { - raiseEvents, raiseEvents + raiseEvents: raiseEvents, + asyncRaiseEvents: asyncRaiseEvents }; return thisObject; @@ -99,4 +100,70 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } + + function asyncRaiseEvents(lastFile, timeFrames) { + + if (bot.processNode.referenceParent.processOutput !== undefined) { + if (bot.processNode.referenceParent.processOutput.outputDatasets !== undefined) { + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + + for (let j = 0; j < outputDatasets.length; j++) { + let outputDataset = outputDatasets[j] + + /* Some validations to verify that everything is in place. */ + if (outputDataset.referenceParent !== undefined) { + if (outputDataset.referenceParent.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + if (outputDataset.referenceParent.parentNode !== undefined) { + if (outputDataset.referenceParent.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Product not attached to a Bot. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Dataset Definition not attached to a Product. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Output Dataset with no reference to a Dataset Definition. Output Dataset = " + JSON.stringify(outputDataset)); + throw(global.DEFAULT_FAIL_RESPONSE); + } + + /* All good, lets emit the event. */ + + let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName + let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName + let product = outputDataset.referenceParent.parentNode.config.codeName + let dataset = outputDataset.referenceParent.config.codeName + + key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let event = { + lastFile: lastFile, + timeFrames: timeFrames + } + global.EVENT_SERVER_CLIENT.createEventHandler(key, 'Dataset Updated') + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Dataset Updated', event) + } + } + } + } }; diff --git a/StatusReport.js b/StatusReport.js index 2600fca26..b442e3aa9 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -352,7 +352,6 @@ filePath += '/' + fileName let fileContent = JSON.stringify(thisObject.file); - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated, true); let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n', true) if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { @@ -361,6 +360,6 @@ /* All good, lets emit the event that means data has been updated. */ let processOutput = PROCESS_OUTPUT.newProcessOutput(bot, logger) - processOutput.raiseEvents(thisObject.file.lastFile, thisObject.file.timeFrames, callBackFunction); + processOutput.asyncRaiseEvents(thisObject.file.lastFile, thisObject.file.timeFrames) } } From 98484dc7fa8e7d1b2f551760760b62776c63ba19 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 18 Aug 2020 18:04:08 +0200 Subject: [PATCH 063/112] Several bug fixes --- LowFrequencyTrading/TradingOutput.js | 4 ++-- LowFrequencyTrading/TradingRecords.js | 8 ++++---- LowFrequencyTrading/TradingSimulation.js | 3 ++- Session.js | 26 +++++++++++++----------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/LowFrequencyTrading/TradingOutput.js b/LowFrequencyTrading/TradingOutput.js index 5973fcb96..25312ea36 100644 --- a/LowFrequencyTrading/TradingOutput.js +++ b/LowFrequencyTrading/TradingOutput.js @@ -54,7 +54,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL } else { await readFiles() } - + await tradingSimulation.runSimulation( chart, outputDatasetsMap, @@ -148,7 +148,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { throw(response.err) } - outputDatasetsMap.set(productName, JSON.parse(text)) + outputDatasetsMap.set(productName, JSON.parse(response.text)) } async function writeFiles() { diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index 274a60380..277c8e565 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -63,7 +63,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { } /* Clean the file from information of previous executions */ - pruneOutputFile(product, outputDatasetArray) + pruneOutputFile(product, outputDatasetArray, tradingEngine.current.episode.candle.end.value) /* Clean Open Records */ if (product.config.saveAsObjects === true) { @@ -321,7 +321,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { return value } - function pruneOutputFile(product, outputFile) { + 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. @@ -335,14 +335,14 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { let recordProperty = product.record.properties[j] if (recordProperty.config.codeName === 'end') { let end = record[j] - if (end >= tradingEngine.current.episode.candle.end.value) { + 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) + setImmediate(pruneOutputFile, product, outputFile, currentEnd) return } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 65cbf0e61..4be2c96af 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -95,7 +95,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT 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. */ - tradingEngine.current.episode.candle.index.value = candles.length - 1 + bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.') + return } } diff --git a/Session.js b/Session.js index 17f9cb6ec..daed3a114 100644 --- a/Session.js +++ b/Session.js @@ -38,8 +38,8 @@ bot.sessionKey = bot.processNode.session.name + '-' + bot.processNode.session.type + '-' + bot.processNode.session.id global.SESSION_MAP.set(bot.sessionKey, bot.sessionKey) - global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, runSession) - global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, stopSession) + global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, onSessionRan) + global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, onSessionStopped) /* Errors sent to the UI */ bot.sessionError = sessionError @@ -50,7 +50,7 @@ callBackFunction(global.DEFAULT_OK_RESPONSE) return - function runSession(message) { + function onSessionRan(message) { try { if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { return } // This happens when the UI is reloaded, the session was running and tries to run it again. @@ -99,19 +99,21 @@ socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is starting.") } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> runSession -> err = " + err.stack); + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionRan -> err = " + err.stack); } } + function onSessionStopped() { + stopSession('Session Stopped From the User Interface.') + } + function stopSession(commandOrigin) { - try { - if (commandOrigin === undefined) { commandOrigin = ' from the User Interface.' } - socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin) - socialBotsModule.finalize() - bot.STOP_SESSION = true - } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> stopSession -> err = " + err.stack); - } + + socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin) + socialBotsModule.finalize() + bot.STOP_SESSION = true + sessionError(bot.SESSION, commandOrigin) + } function setUpAppSchema() { From 06ac5bc746d4b1fefd26a6553774445a8cff46c4 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 19 Aug 2020 16:58:45 +0200 Subject: [PATCH 064/112] Bug Fixed with Initial Candle --- LowFrequencyTrading/TradingEngine.js | 1 + LowFrequencyTrading/TradingEpisode.js | 1 - LowFrequencyTrading/TradingOrders.js | 8 +- LowFrequencyTrading/TradingPosition.js | 4 +- LowFrequencyTrading/TradingRecords.js | 8 +- LowFrequencyTrading/TradingSimulation.js | 191 +++++++++++------------ 6 files changed, 108 insertions(+), 105 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index c0ed6d7d2..1b75ee82f 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -65,6 +65,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } function setCurrentCycle(cycle) { + tradingEngine.current.episode.cycle.value = cycle switch (cycle) { case 'First': { tradingEngine.current.episode.cycle.begin.value = diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index f6d544188..6483b8e30 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -269,7 +269,6 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin } } - function updateDistanceToEventsCounters() { /* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */ if ( diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 8cb20db59..bb82dde04 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -117,7 +117,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM switch (tradingEngineOrder.status.value) { case 'Not Open': { - { + { + /* During the First cycle we can not create new orders. That is reserved for the Second cycle. */ + if (tradingEngine.current.episode.cycle.value === 'First') {continue} /* When the stage is closing we can not create new orders */ if (tradingEngineStage.status.value === 'Closing') { continue } /* @@ -147,13 +149,15 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Check Events that happens at the Exchange, if needed. */ await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) + /* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */ + if (tradingEngine.current.episode.cycle.value === 'Second') {continue} /* In the previous steps, we might have discovered that the order was cancelled at the exchange, or filled, so the order might still not be Open. If the stage is closing or the order is not Open, we wont be cancelling orders based on defined events. */ - if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { + if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { /* Check if we need to Cancel this Order */ let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index a147149fb..d534663bc 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -277,11 +277,11 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng /* ROI Calculation */ tradingEngine.current.position.positionBaseAsset.ROI.value = tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value tradingEngine.current.position.positionQuotedAsset.ROI.value = tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index 277c8e565..d5f4fdae3 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -31,7 +31,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { outputDatasetsMap = undefined } - function appendRecords(cycle) { + 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. @@ -57,13 +57,13 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { There are a few products configured to be saved only at an specific cycle. */ if (product.config.saveAtCycle !== undefined) { - if (cycle !== product.config.saveAtCycle) { + if (tradingEngine.current.episode.cycle.value !== product.config.saveAtCycle) { return } } /* Clean the file from information of previous executions */ - pruneOutputFile(product, outputDatasetArray, tradingEngine.current.episode.candle.end.value) + //pruneOutputFile(product, outputDatasetArray, tradingEngine.current.episode.candle.end.value) /* Clean Open Records */ if (product.config.saveAsObjects === true) { @@ -72,6 +72,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) { /* Remove Open Records */ spliceOpenRecords(j, product, outputDatasetArray) + break } } } @@ -103,6 +104,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { brach of the trading engine data structure it belongs. */ record.push(index) + record.push('"' + tradingEngine.current.episode.cycle.value + '"') persistIndividualRecord(record, product, outputDatasetArray) } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 4be2c96af..72cb3d97e 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -60,37 +60,19 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let firstLoopExecution = true /* - Loop Initialization + Estimation of the Initial Candle to Process in this Run. */ - if (bot.FIRST_EXECUTION === false && tradingEngine.last.candle !== undefined) { - /* Estimate the Initial Candle based on the last candle saved at tradingEngine */ - let firstBegin = candles[0].begin - let lastBegin = tradingEngine.last.candle.begin - let diff = lastBegin - firstBegin - let amount = diff / sessionParameters.timeFrame.config.value - - tradingEngine.current.episode.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle - if (tradingEngine.current.episode.candle.index.value < 0 || tradingEngine.current.episode.candle.index.value > candles.length - 1) { - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.') - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.episode.candle.index.value = ' + tradingEngine.current.episode.candle.index.value) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff) - logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount) - - throw(global.DEFAULT_FAIL_RESPONSE) - return - } - } else { + let initialCandle + if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { /* Estimate Initial Candle based on the timeRage 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 - tradingEngine.current.episode.candle.index.value = Math.trunc(amount) - if (tradingEngine.current.episode.candle.index.value < 0) { tradingEngine.current.episode.candle.index.value = 0 } - if (tradingEngine.current.episode.candle.index.value > candles.length - 1) { + 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. @@ -98,15 +80,40 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.') return } + } else { + { + /* + In this case we already have last candle index. Now we need to continue + at the next candle, so we add 1. This works well for Market Files, + but for Daily Files we need to consider that we we turn into a new date + a we get a new file with candles statting at index 0. + */ + if (bot.processingDailyFiles === true) { + /* + We are going to test if adding 1 gets us a valid candle, it means that + the candle dataset did not change since the lasst execution, otherwise + it means it changed and we need to start again from 0. + */ + let candle = candles[tradingEngine.current.episode.candle.index.value + 1] + if (candle === undefined) { + initialCandle = 0 + } else { + initialCandle = tradingEngine.current.episode.candle.index.value + 1 + } + } else { + initialCandle = tradingEngine.current.episode.candle.index.value + 1 + } + } } - /* Main Simulation Loop */ + console.log('initialCandle = ' + initialCandle) let breakLoop = false - for( let i = tradingEngine.current.episode.candle.index.value; i < candles.length; i++) { + for( let i = initialCandle; i < candles.length; i++) { tradingEngine.current.episode.candle.index.value = i + console.log('index = ' + tradingEngine.current.episode.candle.index.value) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.episode.candle.index.value) } let candle = candles[tradingEngine.current.episode.candle.index.value] // This is the current candle the Simulation is working at. @@ -124,6 +131,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } if (checkInitialDatetime() === false) { + console.log(' Esta candle esta dentras del Initial Datetime') continue } @@ -134,76 +142,78 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT /* Do the stuff needed previous to the run like - Episode Counters and Statistics update + Episode Counters and Statistics update. Mantaince is done + once per simulation candle. */ tradingSystemModule.mantain() tradingEpisodeModule.mantain() tradingEngineModule.mantain() - /* Reset Data Structures */ - tradingSystemModule.reset() - tradingEpisodeModule.reset() - tradingEngineModule.reset() - /* Run the first cycle of the Trading 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. + 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. */ tradingEngineModule.setCurrentCycle('First') - await tradingSystemModule.run() - - /* Add new records to the process output */ - tradingRecordsModule.appendRecords('First') - - /* Reset Data Structures */ - tradingSystemModule.reset() - tradingEpisodeModule.reset() - tradingEngineModule.reset() - + await runCycle() /* Run the second cycle of the Trading System. During this second run some new orders might be created at slots freed up during the first - run. This allows for example for an Limit Order to be cancelled during the + 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. + without the need to wait until the next candle. Orders can not be cancelled + during the second cycle. */ tradingEngineModule.setCurrentCycle('Second') - await tradingSystemModule.run() + await runCycle() - /* Checking if we should continue processing this loop or not. */ - if (bot.STOP_SESSION === true) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } - console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') - updateEpisode('Session Stopped') - breakLoop = true + async function runCycle() { + + console.log(new Date(), 'Function runCycle', ' Cycle = ' + tradingEngine.current.episode.cycle.value ) + + /* Reset Data Structures */ + tradingSystemModule.reset() + tradingEpisodeModule.reset() + tradingEngineModule.reset() + + await tradingSystemModule.run() + + /* Checking if we should continue processing this loop or not. */ + if (bot.STOP_SESSION === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } + console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') + updateEpisode('Session Stopped') + breakLoop = true + } + + if (global.STOP_TASK_GRACEFULLY === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } + console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') + updateEpisode('Task Stopped') + breakLoop = true + } + + if (checkFinalDatetime() === false) { + closeEpisode('Final Datetime Reached') + breakLoop = true + } + + if (checkLastCandle() === false) { + updateEpisode('All Available Candles Processed') + breakLoop = true + } + + if (checkMinimunAndMaximunBalance() === false) { + closeEpisode('Min or Max Balance Reached') + breakLoop = true + } + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords() } - if (global.STOP_TASK_GRACEFULLY === true) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } - console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') - updateEpisode('Task Stopped') - breakLoop = true - } - - if (checkFinalDatetime() === false) { - closeEpisode('Final Datetime Reached') - breakLoop = true - } - - if (checkLastCandle() === false) { - updateEpisode('All Available Candles Processed') - breakLoop = true - } - - if (checkMinimunAndMaximunBalance() === false) { - closeEpisode('Min or Max Balance Reached') - breakLoop = true - } - - /* Add new records to the process output */ - tradingRecordsModule.appendRecords('Second') - if (breakLoop === true) {break} } @@ -353,28 +363,15 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT function checkLastCandle() { /* - We skip the candle at the head of the market because it has not closed yet. The procedure to determine if we are at the head of the market is different - when we are processing Market Files than when we are processing Daily Files. TODO Check that the procedure is good for Beta 6 + Before reaching the last candle of the loop, we need to check that the next candle it is not the last candle. + We skip the candle at the head of the market because it has not closed yet. + For the special case of Daily Files, this means that the last candle of each day will never be processed. */ - if (bot.processingDailyFiles) { // We are processing Daily Files - let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value - if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { - if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } - return false - /* - Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators. - if we skip these two periods, then there will be a hole in the file since the last period will be missing. - */ - } - } - } else { // We are processing Market Files - if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') } - return false - } + if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { + return false + } else { + return true } - return true } function checkInitialDatetime() { From 09f189541d04f48a1bab925b7c1e0ca05b83269d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 20 Aug 2020 11:21:32 +0200 Subject: [PATCH 065/112] bug fixes --- LowFrequencyTrading/TradingOrders.js | 27 +++++++++++++++++---------- LowFrequencyTrading/TradingProcess.js | 19 +++++++++---------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index bb82dde04..488ebc65c 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -243,20 +243,27 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineOrder.situationName.value = situationName function calculateOrderRate() { - /* By default this is the order rate and it is the rate that applies to Market Orders */ - tradingEngineOrder.rate.value = tradingEngine.current.episode.candle.close.value /* Optional Rate Definition */ - if (tradingSystemOrder.orderRate !== undefined) { - if (tradingSystemOrder.orderRate.formula !== undefined) { - tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id) + if (tradingSystemOrder.type === 'Limit Buy Order' || tradingSystemOrder.type === 'Limit Sell Order') { + if (tradingSystemOrder.orderRate === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.orderRate === undefined', tradingSystemOrder) } + if (tradingSystemOrder.orderRate.formula === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.orderRate.formula === undefined', tradingSystemOrder) } + + /* Extract the rate value from the user-defined formula */ + tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id) - if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingEngineOrder) } - if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingEngineOrder) } - if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingEngineOrder) } + /* Final rate validations */ + if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingSystemOrder) } + if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingSystemOrder) } + if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingSystemOrder) } - tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) - } + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + } else { + /* + For Market Orders, the rate is irrelevant, since it is not sent to the Exchange. + We store at this field the last know price as a reference. + */ + tradingEngineOrder.rate.value = tradingEngine.current.episode.candle.close.value } } diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 68ee99d0c..971060a52 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -82,6 +82,11 @@ await processSingleFiles() await processMarketFiles() + /* + This is the Data Structure used at the Simulation with all indicator data. + We start creating it right here. + */ + let chart = {} /* 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 @@ -89,11 +94,9 @@ we alreaady set a value to currentTimeFrame. We are also not going to loop through days if we are processing market files. */ - if (currentTimeFrame) { /* We are processing Market Files */ - let chart = {} buildCharts(chart) await generateOutput(chart) @@ -115,7 +118,6 @@ await processDailyFiles() - let chart = {} buildCharts(chart) await generateOutput(chart) @@ -407,6 +409,10 @@ dataFiles = new Map(); + /* + We will iterate through all dependencies, in order to load the + files that later will end up at the chart data structure. + */ for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) { let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; @@ -462,13 +468,6 @@ let mapKey = global.dailyFilePeriods[n][1]; multiPeriodDataFiles.set(mapKey, dataFiles) } - - if (currentTimeFrame !== undefined) { - buildCharts(advanceTime); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> Time Frame not Recognized. Can not continue."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } } function advanceTime() { From 7d519868b70e482c520e9ce5670e2f0aba6cc8c2 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 20 Aug 2020 14:56:23 +0200 Subject: [PATCH 066/112] Handling of Process Date re implemented --- LowFrequencyTrading/TradingOutput.js | 7 +- LowFrequencyTrading/TradingProcess.js | 265 ++++++++++------------- LowFrequencyTrading/TradingSimulation.js | 25 +++ Session.js | 7 +- 4 files changed, 148 insertions(+), 156 deletions(-) diff --git a/LowFrequencyTrading/TradingOutput.js b/LowFrequencyTrading/TradingOutput.js index 25312ea36..ac3a6ca72 100644 --- a/LowFrequencyTrading/TradingOutput.js +++ b/LowFrequencyTrading/TradingOutput.js @@ -27,7 +27,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL } - async function start(chart, timeFrame, timeFrameLabel, currentDay) { + async function start(chart, timeFrame, timeFrameLabel, tradingProcessDate) { try { if (timeFrame > global.dailyFilePeriods[0][0]) { @@ -41,7 +41,6 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES) let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets - let market = bot.market let outputDatasetsMap = new Map() if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { @@ -127,7 +126,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL if (dataset.config.type === 'Daily Files') { - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2); let fileName = 'Data.json' let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath @@ -184,7 +183,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL if (dataset.config.type === 'Daily Files') { - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2); let fileName = 'Data.json' let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 971060a52..6932a8e56 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -66,19 +66,31 @@ try { let currentTimeFrame let currentTimeFrameLabel - let market = bot.market; /* Context Variables */ - let contextVariables = { lastFile: undefined, // Datetime of the last file files sucessfully 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. }; - let previousDay; // Holds the date of the previous day relative to the processing date. - getContextVariables() + + /* Initializing the Trading Process Date */ + if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { + /* + This funcion is going to be called many times by the Trading 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. + */ + bot.simulationState.tradingEngine.current.episode.processDate.value = bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf() + } + + /* This is the Date that is going to be used across the execution of this Trading Process */ + let tradingProcessDate = new Date(bot.simulationState.tradingEngine.current.episode.processDate.value) + await processSingleFiles() await processMarketFiles() @@ -102,19 +114,20 @@ await generateOutput(chart) await writeProcessFiles() - checkIfSessionMustStop() - } else { /* We are processing Daily Files */ - - /* - Go back one day to start well when we advance time at the begining of the loop. - */ - bot.tradingProcessDate = new Date(contextVariables.lastFile.valueOf() - global.ONE_DAY_IN_MILISECONDS); do { - if (advanceTime() === false) {break} + if (checkStopHeadOfTheMarket() === false) {break} if (checkStopTaskGracefully() === false) {break} if (checkStopProcessing() === false) {break} + + /* + We update the Trading 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 Trading Process Level. + */ + tradingProcessDate = new Date(bot.simulationState.tradingEngine.current.episode.processDate.value) await processDailyFiles() @@ -126,19 +139,23 @@ while (true) } + checkIfSessionMustStop() + /* + Everything worked as expected. We return an OK code and wait for + the Bot Loop to call us again later. + */ callBackFunction(global.DEFAULT_OK_RESPONSE) function getContextVariables() { try { let thisReport; - let reportKey; let statusReport; /* We are going to use the start date as beging of market date. */ contextVariables.dateBeginOfMarket = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime) /* - Here we get the status report from the bot who knows which is the end of the market. + Here we get the status report from the bot who knows which is the end of the market. */ statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point") @@ -170,17 +187,6 @@ contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf()); - /* Validation that the data is not up-to-date. */ - if (bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS > contextVariables.dateEndOfMarket.valueOf() && bot.SESSION.type !== "Backtesting Session") { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> " + "Head of the market is @ " + contextVariables.dateEndOfMarket); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> You need to UPDATE your datasets in order to run a " + bot.SESSION.type) } - - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - - } - /* Finally we get our own Status Report. */ statusReport = statusDependencies.reportsByMainUtility.get("Self Reference") @@ -256,29 +262,13 @@ /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) - onFileReceived(response.err, response.text) - function onFileReceived(err, text) { - try { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - if (err.message === 'File does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> onFileReceived -> err = " + err.message); - callBackFunction(err); - } - return - } - - let dataFile = JSON.parse(text); - dataFiles.set(dependency.id, dataFile); - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) } + + let dataFile = JSON.parse(response.text); + dataFiles.set(dependency.id, dataFile); } let mapKey = "Single Files" @@ -321,30 +311,14 @@ /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) - onFileReceived(response.err, response.text) - function onFileReceived(err, text) { - try { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - if (err.message === 'File does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - } else { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> onFileReceived -> err = " + err.message); - callBackFunction(err); - } - return - } - - let dataFile = JSON.parse(text); - let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record) - dataFiles.set(dependency.id, trimmedDataFile); - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) } + + let dataFile = JSON.parse(response.text); + let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record) + dataFiles.set(dependency.id, trimmedDataFile); function trimDataFile(dataFile, recordDefinition) { /* @@ -381,8 +355,8 @@ async function processDailyFiles() { /* Telling the world we are alive and doing well and which date we are processing right now. */ - let processingDate = bot.tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); - bot.processHeartBeat(processingDate) + let processingDateString = tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(tradingProcessDate.getUTCDate(), 2); + bot.processHeartBeat(processingDateString) /* We will iterate through all posible timeFrames. @@ -416,7 +390,6 @@ for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) { let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; - let file if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") { continue @@ -428,7 +401,7 @@ } } - let dateForPath = bot.tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2); + let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2); let filePath if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; @@ -439,30 +412,13 @@ /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) - onFileReceived(response.err, response.text) - - function onFileReceived(err, text) { - try { - if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { - logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.'); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return - } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> onFileReceived -> Not Ok -> err = " + err.code); - callBackFunction(err); - return - } - - file = JSON.parse(text); - dataFiles.set(dependency.id, file); - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> onFileReceived -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { + throw(response.err) } + + let dataFile = JSON.parse(response.text); + dataFiles.set(dependency.id, dataFile); } let mapKey = global.dailyFilePeriods[n][1]; @@ -470,26 +426,14 @@ } } - function advanceTime() { - bot.tradingProcessDate = new Date(bot.tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS); - previousDay = new Date(bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> bot.tradingProcessDate = " + bot.tradingProcessDate); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> previousDay = " + previousDay); } - - /* Validation that we are not going past the user defined end date. */ - if (bot.tradingProcessDate.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { - const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> " + logText); } - - bot.SESSION.stop(logText) - return false - } - - /* Validation that we are not going past the head of the market. */ - if (bot.tradingProcessDate.valueOf() > contextVariables.dateEndOfMarket.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1) { - const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> " + logText); } + 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 (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) { return false } } @@ -565,7 +509,7 @@ chart, currentTimeFrame, currentTimeFrameLabel, - bot.tradingProcessDate + tradingProcessDate ) bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution. @@ -574,24 +518,28 @@ async function writeProcessFiles() { await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) - await writeDataRanges(currentTimeFrameLabel) + await writeDataRanges() if (currentTimeFrame > global.dailyFilePeriods[0][0]) { await writeMarketStatusReport(currentTimeFrameLabel) } else { - await writeDailyStatusReport(bot.tradingProcessDate, currentTimeFrameLabel) + await writeDailyStatusReport(currentTimeFrameLabel) } - async function writeDataRanges(currentTimeFrameLabel) { - for (let outputDatasetIndex = 0; outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; outputDatasetIndex++) { + async function writeDataRanges() { + for ( + let outputDatasetIndex = 0; + outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; + outputDatasetIndex++ + ) { let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; - await writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel); + await writeDataRange(productCodeName); } - async function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel) { + async function writeDataRange(productCodeName) { let dataRange = { - begin: pBegin.valueOf(), - end: pEnd.valueOf() + global.ONE_DAY_IN_MILISECONDS + begin: contextVariables.dateBeginOfMarket.valueOf(), + end: tradingProcessDate + global.ONE_DAY_IN_MILISECONDS } let fileContent = JSON.stringify(dataRange); @@ -618,12 +566,16 @@ async function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) { - for (let outputDatasetIndex = 0; outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; outputDatasetIndex++) { + for ( + let outputDatasetIndex = 0; + outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; + outputDatasetIndex++ + ) { let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; - await writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily') - await writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Market') + await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily') + await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Market') - async function writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, processType) { + async function writeTimeFramesFile(currentTimeFrameLabel, productCodeName, processType) { let timeFramesArray = [] timeFramesArray.push(currentTimeFrameLabel) @@ -641,12 +593,12 @@ } } - async function writeDailyStatusReport(lastFileDate, currentTimeFrameLabel) { + async function writeDailyStatusReport( currentTimeFrameLabel) { let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName let thisReport = statusDependencies.statusReports.get(reportKey) thisReport.file.lastExecution = bot.currentDaytime - thisReport.file.lastFile = lastFileDate + thisReport.file.lastFile = tradingProcessDate thisReport.file.simulationState = bot.simulationState thisReport.file.timeFrames = currentTimeFrameLabel await thisReport.asyncSave() @@ -660,33 +612,54 @@ thisReport.file.simulationState = bot.simulationState thisReport.file.timeFrames = currentTimeFrameLabel - logger.newInternalLoop(bot.codeName, bot.process, bot.tradingProcessDate) + logger.newInternalLoop(bot.codeName, bot.process, tradingProcessDate) await thisReport.asyncSave() } } function checkIfSessionMustStop() { - /* - The natural way for the session to stop is when we reached the - user defined end datetime. - */ - const now = new Date() - if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { - const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeProcessFiles -> onMarketStatusReport -> " + logText); } - bot.SESSION.stop('because it just finished.') - return - } if (bot.SESSION.type === 'Backtesting Session') { - bot.SESSION.stop('Backtesting Session over Market Files Finished.') - return + /* + Backtests needs only one execution of this process to complete. + */ + bot.SESSION.stop('Backtesting Session Finished.') + } else { + /* + The natural way for the session to stop is when we reached the + user defined end datetime. + */ + const now = new Date() + if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { + const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeProcessFiles -> onMarketStatusReport -> " + logText); } + + bot.SESSION.stop('Final Datetime Reached.') + } } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + /* An unhandled exception occured. in this case we return Fail and log the stack. */ + if (err.stack) { + logger.write(MODULE_NAME, "[ERROR] start -> Unhandled Exception. Will Abort this process. err = " + err.stack); + callBackFunction(global.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.') { + logger.write(MODULE_NAME, "[ERROR] File not Found. Will Retry the Process Loop.") + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return + } + + /* Some other handled exception occured. We return Fail and move on. */ + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> Handled Exception. Will Abort this process. err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 72cb3d97e..023008b31 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -368,8 +368,33 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT For the special case of Daily Files, this means that the last candle of each day will never be processed. */ if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { + /* + 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 we are at the last candle of + the day, we will advance current process day. By doing so, during the next execution, the + simulation will receive the candles and indicators files of the next day. + */ + if (bot.processingDailyFiles) { + let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value + if (tradingEngine.current.episode.candle.index.value + 1 === candlesPerDay - 1) { + /* + Here we found that the next candle of the Simulation is the last candle of the day. + It is time to move to the next day so as to receive next, the indicator files from + the next day. At the same time we will reset the index to be ready to real next time + the first candle of the new set we receive. + */ + tradingEngine.current.episode.candle.index.value = 0 + tradingEngine.current.episode.processDate.value = + tradingEngine.current.episode.processDate.value + global.ONE_DAY_IN_MILISECONDS + } + } + + /* We reached the head of the market */ + tradingEngine.current.episode.headOfTheMarket.value = true return false } else { + /* Wd did not reach the head of the market */ + tradingEngine.current.episode.headOfTheMarket.value = false return true } } diff --git a/Session.js b/Session.js index daed3a114..ccc5d4e3d 100644 --- a/Session.js +++ b/Session.js @@ -67,6 +67,7 @@ setUpAppSchema() setUpSessionFolderName() + /* We validate all parameters received and complete some that might be missing if needed. */ if (checkParemeters() === false) { return } socialBotsModule.initialize() @@ -310,7 +311,6 @@ } function startBackTesting(message) { - bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) // Here we initialize the date of the process. return true } @@ -319,8 +319,6 @@ if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> Key 'codeName' or 'secret' not provided. Plese check that and try again."); } return false } - - bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) pProcessConfig.liveWaitTime = bot.SESSION.parameters.timeFrame.config.value return true } @@ -332,8 +330,6 @@ return false } - bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) - /* Reduce the balance */ let balancePercentage = 1 // This is the default value @@ -356,7 +352,6 @@ } function startPaperTrading(message) { - bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) pProcessConfig.normalWaitTime = bot.SESSION.parameters.timeFrame.config.value return true } From 2116dd35fe9ea8c15b9e1a093d4b9ddf0f65e475 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 20 Aug 2020 17:53:41 +0200 Subject: [PATCH 067/112] Working again --- LowFrequencyTrading/TradingOutput.js | 11 +- LowFrequencyTrading/TradingProcess.js | 37 ++++-- LowFrequencyTrading/TradingRecords.js | 1 - LowFrequencyTrading/TradingSimulation.js | 155 ++++++++++++----------- TradingBot.js | 51 +------- 5 files changed, 119 insertions(+), 136 deletions(-) diff --git a/LowFrequencyTrading/TradingOutput.js b/LowFrequencyTrading/TradingOutput.js index ac3a6ca72..47bb6ce6b 100644 --- a/LowFrequencyTrading/TradingOutput.js +++ b/LowFrequencyTrading/TradingOutput.js @@ -1,4 +1,4 @@ -exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE) { +exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineModule, UTILITIES, FILE_STORAGE) { /* 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. @@ -38,17 +38,12 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL /* Preparing everything for the Simulation */ const TRADING_SIMULATION = require('./TradingSimulation.js') - let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES) + let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES) let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets let outputDatasetsMap = new Map() if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { - /* - Here is where the Trading Engine and Trading Systems received are moved to the simulation state. - */ - bot.simulationState.tradingEngine = bot.TRADING_ENGINE - bot.simulationState.tradingSystem = bot.TRADING_SYSTEM await initializeOutputs() } else { await readFiles() @@ -59,6 +54,8 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL outputDatasetsMap, writeFiles ) + + tradingSimulation.finalize() return async function initializeOutputs() { diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 6932a8e56..f0ca70066 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -1,4 +1,4 @@ -exports.newTradingProcess = function newTradingProcess(bot, logger, UTILITIES, TRADING_OUTPUT_MODULE, USER_BOT_COMMONS) { +exports.newTradingProcess = function newTradingProcess(bot, logger, UTILITIES) { /* 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. @@ -20,11 +20,15 @@ let dataFiles = new Map(); let multiPeriodDataFiles = new Map(); - let tradingOutputModule; - const FILE_STORAGE = require('../FileStorage.js'); let fileStorage = FILE_STORAGE.newFileStorage(logger); + const TRADING_ENGINE_MODULE = require('./TradingEngine.js') + let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) + + let TRADING_OUTPUT_MODULE = require("./TradingOutput") + let tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, tradingEngineModule, UTILITIES, FILE_STORAGE) + let processConfig; return thisObject; @@ -38,8 +42,6 @@ dataDependenciesModule = pDataDependencies processConfig = pProcessConfig - let TRADING_OUTPUT_MODULE = require("./TradingOutput") - tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE) tradingOutputModule.initialize() callBackFunction(global.DEFAULT_OK_RESPONSE) @@ -51,12 +53,17 @@ } function finalize() { + + tradingEngineModule.finalize() + tradingEngineModule = undefined + + tradingOutputModule.finalize() + tradingOutputModule = undefined + dataFiles = undefined multiPeriodDataFiles = undefined statusDependencies = undefined dataDependenciesModule = undefined - tradingOutputModule.finalize() - tradingOutputModule = undefined fileStorage = undefined processConfig = undefined thisObject = undefined @@ -76,6 +83,17 @@ getContextVariables() + if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { + /* + Here is where the Trading Engine and Trading Systems received are moved to the simulation state. + */ + bot.simulationState.tradingEngine = bot.TRADING_ENGINE + bot.simulationState.tradingSystem = bot.TRADING_SYSTEM + } + + /* We set up the Trading Engine Module. */ + tradingEngineModule.initialize() + /* Initializing the Trading Process Date */ if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { /* @@ -117,7 +135,6 @@ } else { /* We are processing Daily Files */ do { - if (checkStopHeadOfTheMarket() === false) {break} if (checkStopTaskGracefully() === false) {break} if (checkStopProcessing() === false) {break} @@ -135,6 +152,8 @@ await generateOutput(chart) await writeProcessFiles() + + if (checkStopHeadOfTheMarket() === false) {break} } while (true) } @@ -434,8 +453,10 @@ again. */ if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) { + console.log('YES at the head of the market') return false } + console.log('NOT at the head of the market') } function checkStopTaskGracefully() { diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index d5f4fdae3..fd019fbcf 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -104,7 +104,6 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { brach of the trading engine data structure it belongs. */ record.push(index) - record.push('"' + tradingEngine.current.episode.cycle.value + '"') persistIndividualRecord(record, product, outputDatasetArray) } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 023008b31..f52b1a617 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -1,4 +1,4 @@ -exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) { +exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES) { /* This Module represents the trading simulacion. Escentially a loop through a set of candles and the execution at each loop cycle of the Trading System Protocol. @@ -23,7 +23,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT chart, outputDatasetsMap, writeFiles, - ) { + ) { try { let tradingSystem = bot.simulationState.tradingSystem @@ -34,10 +34,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) } /* These are the Modules we will need to run the Simulation */ - const TRADING_ENGINE_MODULE = require('./TradingEngine.js') - let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) - tradingEngineModule.initialize() - const TRADING_RECORDS_MODULE = require('./TradingRecords.js') let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger) tradingRecordsModule.initialize(outputDatasetsMap) @@ -58,12 +54,11 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let heartBeatDate let previousHeartBeatDate let firstLoopExecution = true - /* Estimation of the Initial Candle to Process in this Run. */ - let initialCandle - if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { + let initialCandle + if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { /* Estimate Initial Candle based on the timeRage configured for the session. */ let firstEnd = candles[0].end let targetEnd = sessionParameters.timeRange.config.initialDatetime @@ -77,40 +72,38 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT 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. */ - bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.') - return + bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.') + return } } else { - { - /* - In this case we already have last candle index. Now we need to continue - at the next candle, so we add 1. This works well for Market Files, - but for Daily Files we need to consider that we we turn into a new date - a we get a new file with candles statting at index 0. - */ - if (bot.processingDailyFiles === true) { - /* - We are going to test if adding 1 gets us a valid candle, it means that - the candle dataset did not change since the lasst execution, otherwise - it means it changed and we need to start again from 0. - */ - let candle = candles[tradingEngine.current.episode.candle.index.value + 1] - if (candle === undefined) { - initialCandle = 0 - } else { - initialCandle = tradingEngine.current.episode.candle.index.value + 1 - } - } else { - initialCandle = tradingEngine.current.episode.candle.index.value + 1 - } - } + + /* + 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 = tradingEngine.current.episode.candle.index.value } /* Main Simulation Loop */ - console.log('initialCandle = ' + initialCandle) - let breakLoop = false - for( let i = initialCandle; i < candles.length; i++) { + console.log('initialCandle = ' + initialCandle) + /* 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. + */ + tradingEngine.current.episode.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++) { tradingEngine.current.episode.candle.index.value = i console.log('index = ' + tradingEngine.current.episode.candle.index.value) @@ -121,7 +114,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) } tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading 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() @@ -157,7 +150,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT the second cycle without spending real time at the order book. */ tradingEngineModule.setCurrentCycle('First') - await runCycle() + await runCycle() + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords() + + if (breakLoop === true) { break } /* Run the second cycle of the Trading System. During this second run some new orders might be created at slots freed up during the first @@ -167,11 +165,22 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT during the second cycle. */ tradingEngineModule.setCurrentCycle('Second') - await runCycle() + await runCycle() + + /* + We check if we need to stop before appending the records so that the stop + reason is also propery recorded. + */ + checkIfWeNeedToStop() + + /* Add new records to the process output */ + tradingRecordsModule.appendRecords() + + if (breakLoop === true) { break } async function runCycle() { - console.log(new Date(), 'Function runCycle', ' Cycle = ' + tradingEngine.current.episode.cycle.value ) + console.log(new Date(), 'Function runCycle', ' Cycle = ' + tradingEngine.current.episode.cycle.value) /* Reset Data Structures */ tradingSystemModule.reset() @@ -179,18 +188,18 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingEngineModule.reset() await tradingSystemModule.run() + } + function checkIfWeNeedToStop() { /* Checking if we should continue processing this loop or not. */ if (bot.STOP_SESSION === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } - console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') updateEpisode('Session Stopped') breakLoop = true } if (global.STOP_TASK_GRACEFULLY === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } - console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') updateEpisode('Task Stopped') breakLoop = true } @@ -200,7 +209,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT breakLoop = true } - if (checkLastCandle() === false) { + if (checkNextCandle() === false) { updateEpisode('All Available Candles Processed') breakLoop = true } @@ -209,20 +218,13 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT closeEpisode('Min or Max Balance Reached') breakLoop = true } - - /* Add new records to the process output */ - tradingRecordsModule.appendRecords() } - - if (breakLoop === true) {break} } - tradingEngineModule.finalize() tradingSystemModule.finalize() tradingRecordsModule.finalize() tradingEpisodeModule.finalize() - tradingEngineModule = undefined tradingSystemModule = undefined tradingRecordsModule = undefined tradingEpisodeModule = undefined @@ -357,44 +359,55 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } catch (err) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName) logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack) - throw(global.DEFAULT_FAIL_RESPONSE) + throw (global.DEFAULT_FAIL_RESPONSE) } } - function checkLastCandle() { + function checkNextCandle() { /* Before reaching the last candle of the loop, we need to check that the next candle it is not the last candle. We skip the candle at the head of the market because it has not closed yet. For the special case of Daily Files, this means that the last candle of each day will never be processed. */ - if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { + if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { /* 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 we are at the last candle of - the day, we will advance current process day. By doing so, during the next execution, the + 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. */ - if (bot.processingDailyFiles) { - let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value - if (tradingEngine.current.episode.candle.index.value + 1 === candlesPerDay - 1) { - /* - Here we found that the next candle of the Simulation is the last candle of the day. - It is time to move to the next day so as to receive next, the indicator files from - the next day. At the same time we will reset the index to be ready to real next time - the first candle of the new set we receive. - */ - tradingEngine.current.episode.candle.index.value = 0 - tradingEngine.current.episode.processDate.value = - tradingEngine.current.episode.processDate.value + global.ONE_DAY_IN_MILISECONDS - } + let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value + if ( + bot.processingDailyFiles && + tradingEngine.current.episode.candle.index.value + 1 === candlesPerDay - 1 + ) { + /* + Here we found that the next candle of the Simulation is the last candle of the day. + It is time to move to the next day so as to receive next, 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 set we receive. The first candle of the next day starts + at index 0, so we will position the index now at zero. + */ + tradingEngine.current.episode.candle.index.value = 0 + tradingEngine.current.episode.processDate.value = + tradingEngine.current.episode.processDate.value + global.ONE_DAY_IN_MILISECONDS + return false } - /* We reached the head of the market */ + /* + 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. + */ tradingEngine.current.episode.headOfTheMarket.value = true + tradingEngine.current.episode.candle.index.value++ return false } else { + /* Wd did not reach the head of the market */ tradingEngine.current.episode.headOfTheMarket.value = false + tradingEngine.current.episode.candle.index.value++ return true } } @@ -470,7 +483,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } catch (err) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack) - throw(global.DEFAULT_FAIL_RESPONSE) + throw (global.DEFAULT_FAIL_RESPONSE) } } } diff --git a/TradingBot.js b/TradingBot.js index 8b16f955e..bab1df523 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -3,9 +3,6 @@ const MODULE_NAME = "Trading Bot"; const FULL_LOG = true; - let USER_BOT_MODULE; - let USER_BOT_COMMONS; - const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/TradingProcess.js'); const FILE_STORAGE = require('./FileStorage.js'); const SESSION = require(global.ROOT_DIR + 'Session'); @@ -33,53 +30,9 @@ processConfig = pProcessConfig; if (bot.definedByUI === true) { /* The code of the bot is defined at the UI. No need to load a file with the code. */ - session.initialize(processConfig, onSessionInitialized) - - function onSessionInitialized(err) { - callBackFunction(err); - } - return + session.initialize(processConfig, callBackFunction) } - let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName - filePath += "/User.Bot.js" - - fileStorage.getTextFile(filePath, onBotDownloaded); - - function onBotDownloaded(err, text) { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message); - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - - USER_BOT_MODULE = {} - USER_BOT_MODULE.newUserBot = eval(text); // Use this for production - - filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo; - filePath += "/Commons.js" - - fileStorage.getTextFile(filePath, onCommonsDownloaded); - - function onCommonsDownloaded(err, text) { - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.code || err.message); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - - USER_BOT_COMMONS = {} - USER_BOT_COMMONS.newCommons = eval(text); // Use this for production - - session.initialize(onSessionInitialized) - - function onSessionInitialized(err) { - callBackFunction(err); - } - } - } } catch (err) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -337,7 +290,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); } switch (processConfig.framework.name) { case 'Low-Frequency-Trading-Process': { - processFramework = TRADING_PROCESS_MODULE.newTradingProcess(bot, logger, UTILITIES, USER_BOT_MODULE, USER_BOT_COMMONS); + processFramework = TRADING_PROCESS_MODULE.newTradingProcess(bot, logger, UTILITIES); intitializeProcessFramework(); break; } From a9a317545debefbc5d7f2aeff6304472560468ed Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 21 Aug 2020 08:50:01 +0200 Subject: [PATCH 068/112] Datetime related bug fixes --- LowFrequencyTrading/TradingProcess.js | 23 +++++++++++++++-------- LowFrequencyTrading/TradingSimulation.js | 20 ++++++++++++-------- Session.js | 5 +++++ server.js | 5 +++++ 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index f0ca70066..94d35921e 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -103,11 +103,19 @@ 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. */ - bot.simulationState.tradingEngine.current.episode.processDate.value = bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf() + bot.simulationState.tradingEngine.current.episode.processDate.value = global.REMOVE_TIME(bot.SESSION.parameters.timeRange.config.initialDatetime).valueOf() } - /* This is the Date that is going to be used across the execution of this Trading Process */ - let tradingProcessDate = new Date(bot.simulationState.tradingEngine.current.episode.processDate.value) + /* + This is the Date that is going to be used across the execution of this Trading Process. + We need this because it has a different life cycle than the processData stored at the + Trading Engine data structure. This date has to remain the same during the whole execution + of the Trading Process until the end, inclusind the writting of Data Ranges and Status Reports. + The processDate of the Trading 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 tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value) await processSingleFiles() await processMarketFiles() @@ -144,8 +152,8 @@ 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 Trading Process Level. */ - tradingProcessDate = new Date(bot.simulationState.tradingEngine.current.episode.processDate.value) - + tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value) + await processDailyFiles() buildCharts(chart) @@ -171,8 +179,7 @@ let statusReport; /* We are going to use the start date as beging of market date. */ - contextVariables.dateBeginOfMarket = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime) - + contextVariables.dateBeginOfMarket = global.REMOVE_TIME(bot.SESSION.parameters.timeRange.config.initialDatetime) /* Here we get the status report from the bot who knows which is the end of the market. */ @@ -560,7 +567,7 @@ async function writeDataRange(productCodeName) { let dataRange = { begin: contextVariables.dateBeginOfMarket.valueOf(), - end: tradingProcessDate + global.ONE_DAY_IN_MILISECONDS + end: tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS } let fileContent = JSON.stringify(dataRange); diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index f52b1a617..bbd13f85a 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -365,27 +365,31 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin function checkNextCandle() { /* - Before reaching the last candle of the loop, we need to check that the next candle it is not the last candle. - We skip the candle at the head of the market because it has not closed yet. - For the special case of Daily Files, this means that the last candle of each day will never be processed. + 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. */ - if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) { + if (tradingEngine.current.episode.candle.index.value === candles.length - 1) { /* 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. + + 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.) */ let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value if ( bot.processingDailyFiles && - tradingEngine.current.episode.candle.index.value + 1 === candlesPerDay - 1 + tradingEngine.current.episode.candle.index.value + 1 + 1 === candlesPerDay ) { /* - Here we found that the next candle of the Simulation is the last candle of the day. - It is time to move to the next day so as to receive next, the indicator files from + 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 set we receive. The first candle of the next day starts + 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. */ tradingEngine.current.episode.candle.index.value = 0 diff --git a/Session.js b/Session.js index ccc5d4e3d..42c7e84e7 100644 --- a/Session.js +++ b/Session.js @@ -209,6 +209,11 @@ } 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 (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined) { bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault diff --git a/server.js b/server.js index 4aa0957d2..7717daf28 100644 --- a/server.js +++ b/server.js @@ -45,6 +45,11 @@ global.PRECISE = function (floatNumber, precision) { return this.parseFloat(floatNumber.toFixed(precision)) } +global.REMOVE_TIME = function (datetime) { + let date = new Date(datetime) + return new Date(date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + " " + "00:00" + GMT_SECONDS); +} + process.on('uncaughtException', function (err) { console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message) console.log('[ERROR] Task Server -> server -> uncaughtException -> err.stack = ' + err.stack) From 41eaf19dae06afc6ff257f4386719e38ec175c3d Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 21 Aug 2020 10:13:37 +0200 Subject: [PATCH 069/112] bug fixed --- LowFrequencyTrading/TradingOrders.js | 1 + server.js | 1 + 2 files changed, 2 insertions(+) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 488ebc65c..8d0d68fb3 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -304,6 +304,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Size in Quoted Asset */ tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100 + /* Check that the Size calculated would not surpass Stage Target Size */ if ( tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value > tradingEngineStage.stageQuotedAsset.targetSize.value diff --git a/server.js b/server.js index 7717daf28..2d0f3225f 100644 --- a/server.js +++ b/server.js @@ -46,6 +46,7 @@ global.PRECISE = function (floatNumber, precision) { } global.REMOVE_TIME = function (datetime) { + const GMT_SECONDS = ':00.000 GMT+0000'; let date = new Date(datetime) return new Date(date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + " " + "00:00" + GMT_SECONDS); } From 7918f2445c51f0fb93e6c79354c25a08678632b5 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 21 Aug 2020 11:38:56 +0200 Subject: [PATCH 070/112] Execution Started Event --- IndicatorBot.js | 2 +- LowFrequencyTrading/TradingProcess.js | 2 -- LowFrequencyTrading/TradingSimulation.js | 6 ---- ProcessExecutionEvents.js | 45 ++++++++++++++++++------ SensorBot.js | 2 +- Session.js | 2 -- TradingBot.js | 38 +++++++++++--------- 7 files changed, 58 insertions(+), 39 deletions(-) diff --git a/IndicatorBot.js b/IndicatorBot.js index dbe4b844f..df704b925 100644 --- a/IndicatorBot.js +++ b/IndicatorBot.js @@ -145,7 +145,7 @@ function initializeProcessExecutionEvents() { try { processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) - processExecutionEvents.initialize(onInizialized); + processExecutionEvents.initialize(processConfig, onInizialized); function onInizialized(err) { try { diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 94d35921e..a8f4ff95a 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -460,10 +460,8 @@ again. */ if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) { - console.log('YES at the head of the market') return false } - console.log('NOT at the head of the market') } function checkStopTaskGracefully() { diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index bbd13f85a..7a663d595 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -86,7 +86,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin /* Main Simulation Loop */ - console.log('initialCandle = ' + initialCandle) /* We are going to use this to exit the loop if needed. */ let breakLoop = false @@ -106,7 +105,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin for (let i = initialCandle; i < candles.length - 1; i++) { tradingEngine.current.episode.candle.index.value = i - console.log('index = ' + tradingEngine.current.episode.candle.index.value) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.episode.candle.index.value) } let candle = candles[tradingEngine.current.episode.candle.index.value] // This is the current candle the Simulation is working at. @@ -124,7 +122,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin } if (checkInitialDatetime() === false) { - console.log(' Esta candle esta dentras del Initial Datetime') continue } @@ -179,9 +176,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin if (breakLoop === true) { break } async function runCycle() { - - console.log(new Date(), 'Function runCycle', ' Cycle = ' + tradingEngine.current.episode.cycle.value) - /* Reset Data Structures */ tradingSystemModule.reset() tradingEpisodeModule.reset() diff --git a/ProcessExecutionEvents.js b/ProcessExecutionEvents.js index c45f64ddd..c642e9a06 100644 --- a/ProcessExecutionEvents.js +++ b/ProcessExecutionEvents.js @@ -1,12 +1,9 @@ exports.newProcessExecutionEvents = function newProcessExecutionEvents(BOT, logger) { - /* - - Here we manage the process execution events. This allow us to emit events when the process starts and finishes. Also to wait for a dependent event + Here we manage the process execution events. This allow us to emit events + when the process starts and finishes. Also to wait for a dependent event of another process. - */ - const MODULE_NAME = "Process Execution Events"; logger.fileName = MODULE_NAME @@ -28,7 +25,7 @@ return thisObject; - function initialize(callBackFunction) { + function initialize(processConfig, callBackFunction) { try { let name = 'Not Depends on any Process' logger.fileName = MODULE_NAME + "." + name; @@ -42,22 +39,50 @@ let market = bot.market.baseAsset + '/' + bot.market.quotedAsset currentProcessKey = bot.processNode.referenceParent.name + "-" + bot.processNode.referenceParent.type + "-" + bot.processNode.referenceParent.id + "-" + bot.exchange + "-" + market + /* + A process can depend for its own execution on another process to finish, and + this can be achieved in two different ways. The first one is by referencing + the Execution Started Event node from a Process Definition node to a Execution + Ended Event node of another Process Definition. The second way is when there is + a Execution Started Event node at a Process Instance. This allows the user to + override the definition created by the Data Mine for an specific task. + */ + + /* + Here we check if we can find this relationship at the Data Mine level. + */ if (bot.processNode.referenceParent.executionStartedEvent !== undefined) { if (bot.processNode.referenceParent.executionStartedEvent.referenceParent !== undefined) { if (bot.processNode.referenceParent.executionStartedEvent.referenceParent.parentNode !== undefined) { processThisDependsOn = bot.processNode.referenceParent.executionStartedEvent.referenceParent.parentNode name = processThisDependsOn.name - if (processThisDependsOn.config.monthlyInstances === true) { - logger.fileName = MODULE_NAME + "." + name; - } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " depends on " + name); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) + { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the Data Mine depends on " + name); } + } + } + } + + /* + Here we check if at the task level if there is an overriding configuration. + */ + if (bot.processNode.executionStartedEvent !== undefined) { + if (bot.processNode.executionStartedEvent.referenceParent !== undefined) { + if (bot.processNode.executionStartedEvent.referenceParent.parentNode !== undefined) { + processThisDependsOn = bot.processNode.executionStartedEvent.referenceParent.parentNode + + name = processThisDependsOn.name + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) + { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the User's Task depends on " + name); } } } } if (processThisDependsOn !== undefined) { + /* We need to remember that this process waits for another process in order to start. */ + processConfig.waitsForExecutionFinishedEvent = true + /* We need to find at which network node is running the process that we need to hear from when it finished. */ let network = global.TASK_NETWORK diff --git a/SensorBot.js b/SensorBot.js index d8f8aaa32..78cec7d95 100644 --- a/SensorBot.js +++ b/SensorBot.js @@ -148,7 +148,7 @@ function initializeProcessExecutionEvents() { try { processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) - processExecutionEvents.initialize(onInizialized); + processExecutionEvents.initialize(processConfig, onInizialized); function onInizialized(err) { try { diff --git a/Session.js b/Session.js index 42c7e84e7..79f5cb9b0 100644 --- a/Session.js +++ b/Session.js @@ -324,7 +324,6 @@ if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> Key 'codeName' or 'secret' not provided. Plese check that and try again."); } return false } - pProcessConfig.liveWaitTime = bot.SESSION.parameters.timeFrame.config.value return true } @@ -357,7 +356,6 @@ } function startPaperTrading(message) { - pProcessConfig.normalWaitTime = bot.SESSION.parameters.timeFrame.config.value return true } diff --git a/TradingBot.js b/TradingBot.js index bab1df523..6204a524a 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -131,7 +131,7 @@ function initializeProcessExecutionEvents() { try { processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger) - processExecutionEvents.initialize(onInizialized); + processExecutionEvents.initialize(processConfig, onInizialized); function onInizialized(err) { try { @@ -560,23 +560,27 @@ break case 'Normal': { let waitTime - switch (bot.SESSION.type) { - case 'Live Trading Session': { - waitTime = processConfig.liveTradingWaitTime + if (processConfig.waitsForExecutionFinishedEvent === true) { + waitTime = 0 + } else { + switch (bot.SESSION.type) { + case 'Live Trading Session': { + waitTime = bot.SESSION.parameters.timeFrame.config.value + break + } + case 'Fordward Tessting Session': { + waitTime = bot.SESSION.parameters.timeFrame.config.value + break + } + case 'Paper Trading Session': { + waitTime = bot.SESSION.parameters.timeFrame.config.value + break + } + case 'Backtesting Session': { + waitTime = 0 + break + } } - break - case 'Fordward Tessting Session': { - waitTime = processConfig.fordwardTestingWaitTime - } - break - case 'Paper Trading Session': { - waitTime = processConfig.paperTradingWaitTime - } - break - case 'Backtesting Session': { - waitTime = processConfig.backtestingWaitTime - } - break } if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s."); } From ecb599b0e7a4bf9626cc44cc66f20cebdb9d921a Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 21 Aug 2020 14:16:15 +0200 Subject: [PATCH 071/112] refactoring --- LowFrequencyTrading/TradingEpisode.js | 4 ++-- LowFrequencyTrading/TradingPosition.js | 4 ++-- LowFrequencyTrading/TradingStages.js | 8 ++++---- LowFrequencyTrading/TradingSystem.js | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 6483b8e30..032ae654e 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -9,7 +9,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin openEpisode: openEpisode, updateExitType: updateExitType, closeEpisode: closeEpisode, - calculateStatistics: calculateStatistics, + cycleBasedStatistics: cycleBasedStatistics, initialize: initialize, finalize: finalize } @@ -113,7 +113,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin global.ONE_DAY_IN_MILISECONDS } - function calculateStatistics() { + function cycleBasedStatistics() { calculateAssetsStatistics() calculateEpisodeStatistics() diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index d534663bc..57f67a0d2 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -6,7 +6,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng let thisObject = { mantain: mantain, reset: reset, - calculateStatistics: calculateStatistics, + cycleBasedStatistics: cycleBasedStatistics, openPosition: openPosition, closingPosition: closingPosition, closePosition: closePosition, @@ -256,7 +256,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } } - function calculateStatistics(tradingEngineStageNode) { + function cycleBasedStatistics(tradingEngineStageNode) { calculateAssetsStatistics() calculatePositionStatistics() diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 1b5090964..8a2a8ad31 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -7,7 +7,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM updateChart: updateChart, mantain: mantain, reset: reset, - calculateStatistics: calculateStatistics, + cycleBasedStatistics: cycleBasedStatistics, runTriggerStage: runTriggerStage, runOpenStage: runOpenStage, runManageStage: runManageStage, @@ -102,9 +102,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM resetTradingEngineDataStructure() } - function calculateStatistics() { - tradingEpisodeModule.calculateStatistics() - tradingPositionModule.calculateStatistics() + function cycleBasedStatistics() { + tradingEpisodeModule.cycleBasedStatistics() + tradingPositionModule.cycleBasedStatistics() } function runTriggerStage() { diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index b938b9a31..5ee168a04 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -132,7 +132,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Run the Close Stage */ await tradingStagesModule.runCloseStage() - tradingStagesModule.calculateStatistics() + tradingStagesModule.cycleBasedStatistics() } catch (err) { /* From 3fbde34e89c3ed9281fca4a85e7bc43d9013339f Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 21 Aug 2020 14:20:50 +0200 Subject: [PATCH 072/112] Update ExchangeAPI.js --- LowFrequencyTrading/ExchangeAPI.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index b94740fe3..4586bb79c 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -124,7 +124,7 @@ return order.id } catch (err) { tradingSystem.errors.push([tradingSystemOrder.id, err.message]) - logError("getOrder -> Error = " + err.message); + logError("createOrder -> Error = " + err.message); } } @@ -133,13 +133,13 @@ let orderId = tradingEngineOrder.exchangeId.value /* Basic Logging */ - logInfo("getOrder -> Entering function. orderId = " + orderId); + logInfo("cancelOrder -> Entering function. orderId = " + orderId); const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset /* Basic Validations */ if (exchange.has['fetchOrder'] === false) { - logError("getOrder -> Exchange does not support fetchOrder command."); + logError("cancelOrder -> Exchange does not support fetchOrder command."); return } @@ -148,7 +148,7 @@ return true } catch (err) { tradingSystem.errors.push([tradingSystemOrder.id, err.message]) - logError("getOrder -> Error = " + err.message); + logError("cancelOrder -> Error = " + err.message); } } From 1d536e235bf347fa5aae81be8a0a5171c7173da5 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 21 Aug 2020 15:07:57 +0200 Subject: [PATCH 073/112] bug fixed --- LowFrequencyTrading/TradingExecution.js | 2 ++ LowFrequencyTrading/TradingOrders.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index aff93d2a2..7e6619ec3 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -76,6 +76,8 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } } catch (err) { + tradingSystem.errors.push([executionNode.id, err.message]) + if (typeof err === 'string' || err instanceof String) { logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err) } diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 8d0d68fb3..92546f452 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -911,7 +911,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) - recalculateStageSize(tradingEngineStage) + recalculateStageSize(tradingEngineStage, tradingEngineOrder) } } From e1a80c5a4681de9bff3db5ed7a4a4747c3f0245c Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 22 Aug 2020 17:15:50 +0200 Subject: [PATCH 074/112] Bug Fixes --- LowFrequencyTrading/TradingEpisode.js | 127 ++++----- LowFrequencyTrading/TradingOrders.js | 366 ++++++++++++++++--------- LowFrequencyTrading/TradingPosition.js | 62 +++-- LowFrequencyTrading/TradingStages.js | 16 +- 4 files changed, 339 insertions(+), 232 deletions(-) diff --git a/LowFrequencyTrading/TradingEpisode.js b/LowFrequencyTrading/TradingEpisode.js index 032ae654e..76bb7a8d3 100644 --- a/LowFrequencyTrading/TradingEpisode.js +++ b/LowFrequencyTrading/TradingEpisode.js @@ -117,33 +117,20 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin calculateAssetsStatistics() calculateEpisodeStatistics() - + function calculateAssetsStatistics() { - - /* Updating Hits & Fails */ - if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeBaseAsset.hits.value++ - } else { - tradingEngine.current.episode.episodeBaseAsset.fails.value++ - } - if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { - tradingEngine.current.episode.episodeQuotedAsset.hits.value++ - } else { - tradingEngine.current.episode.episodeQuotedAsset.fails.value++ - } - /* Updating Profit Loss */ tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = tradingEngine.current.episode.episodeBaseAsset.balance.value - sessionParameters.sessionBaseAsset.config.initialBalance - + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value - sessionParameters.sessionQuotedAsset.config.initialBalance - + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10) tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10) - + /* Updating ROI @@ -151,55 +138,57 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin */ tradingEngine.current.episode.episodeBaseAsset.ROI.value = tradingEngine.current.episode.episodeBaseAsset.profitLoss.value / - tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * 100 - + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * 100 + tradingEngine.current.episode.episodeQuotedAsset.ROI.value = tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value / - tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value * 100 - + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value * 100 + tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10) tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10) - + /* Updating Hit Ratio */ - tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = - tradingEngine.current.episode.episodeBaseAsset.hits.value / - tradingEngine.current.episode.episodeCounters.positions.value - - tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = - tradingEngine.current.episode.episodeQuotedAsset.hits.value / - tradingEngine.current.episode.episodeCounters.positions.value - - tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) - tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) - + if (tradingEngine.current.episode.episodeCounters.positions.value > 0) { + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = + tradingEngine.current.episode.episodeBaseAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value + + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = + tradingEngine.current.episode.episodeQuotedAsset.hits.value / + tradingEngine.current.episode.episodeCounters.positions.value + + tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10) + tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10) + } + /* Updating Annualized Rate Of Return https://www.investopedia.com/terms/a/annualized-rate.asp */ tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = - Math.pow( - ( - tradingEngine.current.episode.episodeBaseAsset.beginBalance.value + - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value - ) / tradingEngine.current.episode.episodeBaseAsset.beginBalance.value - , - (365 / tradingEngine.current.episode.episodeStatistics.days.value) + Math.pow( + ( + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value + ) / tradingEngine.current.episode.episodeBaseAsset.beginBalance.value + , + (365 / tradingEngine.current.episode.episodeStatistics.days.value) ) - 1 - + tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = - Math.pow( - ( - tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - ) / tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value - , - (365 / tradingEngine.current.episode.episodeStatistics.days.value) + Math.pow( + ( + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) / tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + , + (365 / tradingEngine.current.episode.episodeStatistics.days.value) ) - 1 - + tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value, 10) tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value, 10) - + /* Updating Hit or Fail */ if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) { tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit' @@ -218,9 +207,9 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeStatistics.profitLoss.value = tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - + tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10) - + /* Updating ROI @@ -234,32 +223,32 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value ) * 100 - + tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10) - + /* Updating Annualized Rate Of Return https://www.investopedia.com/terms/a/annualized-rate.asp */ tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = - Math.pow( - ( - tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + - tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + - tradingEngine.current.episode.episodeBaseAsset.profitLoss.value + - tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - ) / - ( - tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + - tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value - ) - , - (365 / tradingEngine.current.episode.episodeStatistics.days.value) + Math.pow( + ( + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value + + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value + ) / + ( + tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value + + tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value + ) + , + (365 / tradingEngine.current.episode.episodeStatistics.days.value) ) - 1 - + tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10) - + /* Updating Hit or Fail */ if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) { tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit' diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 92546f452..9bbec13b2 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -117,9 +117,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM switch (tradingEngineOrder.status.value) { case 'Not Open': { - { + { /* During the First cycle we can not create new orders. That is reserved for the Second cycle. */ - if (tradingEngine.current.episode.cycle.value === 'First') {continue} + if (tradingEngine.current.episode.cycle.value === 'First') { continue } /* When the stage is closing we can not create new orders */ if (tradingEngineStage.status.value === 'Closing') { continue } /* @@ -150,14 +150,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) /* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */ - if (tradingEngine.current.episode.cycle.value === 'Second') {continue} + if (tradingEngine.current.episode.cycle.value === 'Second') { continue } /* In the previous steps, we might have discovered that the order was cancelled at the exchange, or filled, so the order might still not be Open. If the stage is closing or the order is not Open, we wont be cancelling orders based on defined events. */ - if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { + if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { /* Check if we need to Cancel this Order */ let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) @@ -248,7 +248,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (tradingSystemOrder.type === 'Limit Buy Order' || tradingSystemOrder.type === 'Limit Sell Order') { if (tradingSystemOrder.orderRate === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.orderRate === undefined', tradingSystemOrder) } if (tradingSystemOrder.orderRate.formula === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.orderRate.formula === undefined', tradingSystemOrder) } - + /* Extract the rate value from the user-defined formula */ tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id) @@ -257,7 +257,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingSystemOrder) } if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingSystemOrder) } - tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) } else { /* For Market Orders, the rate is irrelevant, since it is not sent to the Exchange. @@ -367,6 +367,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } + let previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value @@ -416,7 +417,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM switch (tradingEngineOrder.type) { case 'Market Order': { /* Actual Rate is simulated based on the Session Paremeters */ - let slippageAmount = tradingEngineOrder.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100 + let slippageAmount = tradingEngineOrder.rate.value * sessionParameters.slippage.config.positionRate / 100 switch (tradingSystemOrder.type) { case 'Market Sell Order': { tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount @@ -437,6 +438,24 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + + /* + Recalculate the Quouted Asset Size based on the newly calculated Actual Rate. + */ + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.orderStatistics.actualRate.value + tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) + /* + Recaulculate the Size Placed Based on the newly calculated Quoted Asset Size. + */ + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value - + previousQuotedAssetSize + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value + + tradingEngineOrder.orderQuotedAsset.size.value + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) } function percentageFilledSimulation() { @@ -463,13 +482,13 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM break } case 'Market Buy Order': { - orderWasHit = true + orderWasHit = true break } case 'Market Sell Order': { orderWasHit = true - break - } + break + } } if (orderWasHit === true) { let calculatedBasedOnTradingSystem = false @@ -515,88 +534,47 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM function feesPaidSimulation() { /* - The way the fess paid is calculated is as follows: From the order size we apply the % of fee, + The way the Fess Paid are calculated is as follows: From the order 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 session parameters definitions. */ - let calculatedBasedOnTradingSystem = false /* Based on the Trading System Definition */ if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { - if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - calculatedBasedOnTradingSystem = true - } - - if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) { - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - calculatedBasedOnTradingSystem = true - } + applyFeePercentage(tradingEngineOrder, tradingSystemOrder, tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage) + return } } } - /* Based on the Session Parameters Definition */ - if (calculatedBasedOnTradingSystem === false) { - /* Fees are simulated based on the Session Paremeters */ - switch (tradingEngineOrder.type) { - case 'Market Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - bot.SESSION.parameters.feeStructure.config.taker / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - break - } - case 'Limit Order': { - - tradingEngineOrder.orderBaseAsset.feesPaid.value = - tradingEngineOrder.orderBaseAsset.size.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - tradingEngineOrder.orderQuotedAsset.feesPaid.value = - tradingEngineOrder.orderQuotedAsset.size.value * - bot.SESSION.parameters.feeStructure.config.maker / 100 * - tradingEngineOrder.orderStatistics.percentageFilled.value / 100 - - break - } + /* + Another way to simulate the Fees Paid is by using the Session Paremeters configuration for Fees. + */ + switch (tradingEngineOrder.type) { + case 'Market Order': { + applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.taker) + break + } + case 'Limit Order': { + applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.maker) + break } } - tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) - tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) } function sizeFilledSimulation() { /* Size Filled */ tradingEngineOrder.orderBaseAsset.sizeFilled.value = - (tradingEngineOrder.orderBaseAsset.size.value - tradingEngineOrder.orderBaseAsset.feesPaid.value) * + tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 tradingEngineOrder.orderQuotedAsset.sizeFilled.value = - (tradingEngineOrder.orderQuotedAsset.size.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value) * + tradingEngineOrder.orderQuotedAsset.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) @@ -667,7 +645,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM updateEndsWithCycle(tradingEngineOrder) } - syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + /* + If the order happens to be at least partially filled, there is a syncronization work + we need to do, that includes discovering which is the Actual Rate the order is being filled, + the Fees Paid and many other thing we need to account for. + */ + if (order.filled > 0) { + syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + } /* Forced Cancellation Check */ if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') { @@ -677,44 +662,16 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM function syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) { + let previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - /* Actual Rate Calculation */ - tradingEngineOrder.orderStatistics.actualRate.value = order.price - tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - - /* Fees Paid Calculation */ - /* - As a response from the exchange we can not always get the fees. - For that reason we need to estimate them base of the information that we do have. - CCXT provides order.amount which represents the size we set for the order minus the - fees taken by the exchange, all denominated in Base Asset. - In this way if we substract to the order size this order.amount - we can get the fees. All this is denominated in base asset because that is how CCXT works. - The fees then can be estimated in Quoted Asset using the Actual Rate. - */ - tradingEngineOrder.orderBaseAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.size.value - order.amount - tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) - tradingEngineOrder.orderQuotedAsset.feesPaid.value = tradingEngineOrder.orderBaseAsset.feesPaid.value * tradingEngineOrder.orderStatistics.actualRate.value - tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) - - /* Percentage Filled Calculation */ - tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) - tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - - /* Size Filled Calculation */ - /* - CCXT returns order.filled with an amount denominated in Base Asset. We will - take it from there for our Order Base Asset. For our Order Quoted Asset we - will use the field order.cost. - */ - tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled - tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10) - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = order.cost - tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) + actualRateCalculation() + percentageFilledCalculation() + feesPaidCalculation() + sizeFilledCalculation() doTheAccounting( tradingEngineStage, @@ -725,6 +682,108 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM previousBaseAssetFeesPaid, previousQuotedAssetFeesPaid ) + + return + + function actualRateCalculation() { + /* + 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 trading engine inside the exchange would match out 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. + */ + tradingEngineOrder.orderStatistics.actualRate.value = order.average + } else { + /* + Otherwise we use the order.price. + */ + tradingEngineOrder.orderStatistics.actualRate.value = order.price + } + + tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + /* + Now we know the Actual Rate at which the order was filled. Since the actual rate + might not be the same as the Rate we defined for the order, we need to syncronize + the Order Size for Quoted Asset since it was calculated with the Order Size that we + now know it is not the once really used at the exchange. We will recalculate the + Order Size in Quoted Asset and not in Base Asset, since the Order Size in Base Asset + is the one we sent to the exchange, so that is fixed, regardless of how we initially + got the Order Size in Base Asset, eigher from direct imput from the user or calculated + from the Order Size in Quoted Asset input it by the user. So here we go. + */ + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.orderStatistics.actualRate.value + tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) + /* + Great, this up-to-date order size in Base Asset and the Actual Rate can now be used for example to calculate + the orders fees in Quoted Assets, and it is perfectly accurate. The next problem + we need to fix is that the sizePlaced for Quoted Asset that we already have calculated + was based on the Order Rate, not the Actual Rate, so we need to fix that by anuncounting + the previous size and accounting the new one, all this only for Quoted Asset. + */ + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value - + previousQuotedAssetSize + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value + + tradingEngineOrder.orderQuotedAsset.size.value + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) + } + + function percentageFilledCalculation() { + /* Percentage Filled Calculation */ + tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) + } + + function feesPaidCalculation() { + /* + The exchange fees are taken from the Base Asset or the Quoted Asset depending if we + are buying or selling. Within the order information received we can not see the fees paid so we need to + calculate them by ourselves. To do this it is needed to be used the Session Fees Parameters. + */ + switch (tradingEngineOrder.type) { + case 'Market Order': { + applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.taker) + break + } + case 'Limit Order': { + applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.maker) + break + } + } + } + + function sizeFilledCalculation() { + /* + 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 account for the fees, which is exactly what we need in this case. + */ + tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled + tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.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. Since we know the Size in Quoted Asset thet + is already adjusted with the Actual Rate and the Percentage Filled, we will use all + this in this formula to get the Size Filled in Quoted Asset. + */ + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = + tradingEngineOrder.orderQuotedAsset.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10) + } } function doTheAccounting( @@ -787,61 +846,72 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM function updateBalances() { /* Balances Update */ switch (true) { + /* + Let's remember that for Buy orders, the fees are paid in Base Asset. So we + actually don't receive the whole Size Filled in Base Asset, but the Size + Filled minus the Fees Paid. For the case of Quoted Asset, we actually + need to susbstract the whole Size Filled from the balance. + */ case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { /* Balance Base Asset: Undo the previous accounting */ tradingEngine.current.episode.episodeBaseAsset.balance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value - - previousBaseAssetSizeFilled - - /* Balance Base Asset: Account the current filling and fees */ - tradingEngine.current.episode.episodeBaseAsset.balance.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value - - /* Balance Quoted Asset: Undo the previous accounting */ - tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value + - previousQuotedAssetSizeFilled + - previousQuotedAssetFeesPaid - - /* Balance Quoted Asset: Account the current filling and fees */ - tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value - - tradingEngineOrder.orderQuotedAsset.sizeFilled.value - - tradingEngineOrder.orderQuotedAsset.feesPaid.value - break - } - case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { - - /* Balance Base Asset: Undo the previous accounting */ - tradingEngine.current.episode.episodeBaseAsset.balance.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value + previousBaseAssetSizeFilled + previousBaseAssetFeesPaid /* Balance Base Asset: Account the current filling and fees */ tradingEngine.current.episode.episodeBaseAsset.balance.value = - tradingEngine.current.episode.episodeBaseAsset.balance.value - + tradingEngine.current.episode.episodeBaseAsset.balance.value + tradingEngineOrder.orderBaseAsset.sizeFilled.value - tradingEngineOrder.orderBaseAsset.feesPaid.value /* Balance Quoted Asset: Undo the previous accounting */ tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value - + tradingEngine.current.episode.episodeQuotedAsset.balance.value + previousQuotedAssetSizeFilled /* Balance Quoted Asset: Account the current filling and fees */ tradingEngine.current.episode.episodeQuotedAsset.balance.value = - tradingEngine.current.episode.episodeQuotedAsset.balance.value + + tradingEngine.current.episode.episodeQuotedAsset.balance.value - tradingEngineOrder.orderQuotedAsset.sizeFilled.value break } + /* + For Sell orders, the fees are being paid in Quote Asset, so we never + acutally receive the whole Filled Size in Quote Asset, but the Filled + Size minus the Fees Paid. But we do expend the whole Filled Size in + Base Asset. + */ + case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { + + /* Balance Base Asset: Undo the previous accounting */ + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value + + previousBaseAssetSizeFilled + + /* Balance Base Asset: Account the current filling and fees */ + tradingEngine.current.episode.episodeBaseAsset.balance.value = + tradingEngine.current.episode.episodeBaseAsset.balance.value - + tradingEngineOrder.orderBaseAsset.sizeFilled.value + + /* Balance Quoted Asset: Undo the previous accounting */ + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value - + previousQuotedAssetSizeFilled + + previousQuotedAssetFeesPaid + + /* Balance Quoted Asset: Account the current filling and fees */ + tradingEngine.current.episode.episodeQuotedAsset.balance.value = + tradingEngine.current.episode.episodeQuotedAsset.balance.value + + tradingEngineOrder.orderQuotedAsset.sizeFilled.value - + tradingEngineOrder.orderQuotedAsset.feesPaid.value + break + } } tradingEngine.current.episode.episodeBaseAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.balance.value, 10) tradingEngine.current.episode.episodeQuotedAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.balance.value, 10) } - } function simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, exitType) { @@ -926,23 +996,51 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value - tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value = tradingEngineStage.stageQuotedAsset.sizePlaced.value - tradingEngineOrder.orderQuotedAsset.size.value tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value + - tradingEngineOrder.orderBaseAsset.sizeFilled.value + - tradingEngineOrder.orderBaseAsset.feesPaid.value + tradingEngineOrder.orderBaseAsset.sizeFilled.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value = tradingEngineStage.stageQuotedAsset.sizePlaced.value + - tradingEngineOrder.orderQuotedAsset.sizeFilled.value + - tradingEngineOrder.orderQuotedAsset.feesPaid.value + tradingEngineOrder.orderQuotedAsset.sizeFilled.value tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10) tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) } + function applyFeePercentage(tradingEngineOrder, tradingSystemOrder, feePercentage) { + switch (true) { + case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { + /* + In this case the fees are taken from the Base Asset you receive. + */ + tradingEngineOrder.orderBaseAsset.feesPaid.value = + tradingEngineOrder.orderBaseAsset.size.value * + feePercentage / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + break + } + case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { + /* + In this case the fees are taken from the Quoted Asset you receive. + */ + tradingEngineOrder.orderQuotedAsset.feesPaid.value = + tradingEngineOrder.orderQuotedAsset.size.value * + feePercentage / 100 * + tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + break + } + } + + tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10) + tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) + } + function checkOrderEvent(event, order, executionAlgorithm, executionNode) { if (event !== undefined) { for (let k = 0; k < event.situations.length; k++) { diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index 57f67a0d2..8476a4685 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -6,7 +6,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng let thisObject = { mantain: mantain, reset: reset, - cycleBasedStatistics: cycleBasedStatistics, + cycleBasedStatistics: cycleBasedStatistics, openPosition: openPosition, closingPosition: closingPosition, closePosition: closePosition, @@ -92,6 +92,22 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure. */ tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position) + + cycleBasedStatistics() + + /* Updating Hits & Fails */ + if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeBaseAsset.hits.value++ + } + if (tradingEngine.current.position.positionBaseAsset.profitLoss.value < 0) { + tradingEngine.current.episode.episodeBaseAsset.fails.value++ + } + if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { + tradingEngine.current.episode.episodeQuotedAsset.hits.value++ + } + if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value < 0) { + tradingEngine.current.episode.episodeQuotedAsset.fails.value++ + } } function applyStopLossFormula(formulas, formulaId) { @@ -256,47 +272,55 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng } } - function cycleBasedStatistics(tradingEngineStageNode) { + function cycleBasedStatistics() { calculateAssetsStatistics() calculatePositionStatistics() - + function calculateAssetsStatistics() { /* Profit Loss Calculation */ tradingEngine.current.position.positionBaseAsset.profitLoss.value = tradingEngine.current.episode.episodeBaseAsset.balance.value - tradingEngine.current.position.positionBaseAsset.beginBalance - + tradingEngine.current.position.positionQuotedAsset.profitLoss.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value - tradingEngine.current.position.positionQuotedAsset.beginBalance - + tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) - + /* ROI Calculation */ tradingEngine.current.position.positionBaseAsset.ROI.value = tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value - + tradingEngine.current.position.positionQuotedAsset.ROI.value = tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value - + tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) - + /* Hit Fail Calculation */ if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit' - } else { + } + if (tradingEngine.current.position.positionBaseAsset.ROI.value < 0) { tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail' } + if (tradingEngine.current.position.positionBaseAsset.ROI.value === 0) { + tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Even' + } if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit' - } else { + } + if (tradingEngine.current.position.positionQuotedAsset.ROI.value < 0) { tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail' } + if (tradingEngine.current.position.positionQuotedAsset.ROI.value === 0) { + tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Even' + } } function calculatePositionStatistics() { @@ -305,10 +329,10 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng ( tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.position.endRate.value + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value - ) + ) tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) - + /* ROI Calculation */ tradingEngine.current.position.positionStatistics.ROI.value = ( @@ -320,19 +344,23 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng tradingEngine.current.position.positionQuotedAsset.beginBalance ) tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) - + /* Hit Fail Calculation */ if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { tradingEngine.current.position.positionStatistics.hitFail.value = 'Hit' - } else { + } + if (tradingEngine.current.position.positionStatistics.ROI.value < 0) { tradingEngine.current.position.positionStatistics.hitFail.value = 'Fail' } - + if (tradingEngine.current.position.positionStatistics.ROI.value === 0) { + tradingEngine.current.position.positionStatistics.hitFail.value = 'Even' + } + /* Days Calculation */ tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS - + tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10) } } diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index 8a2a8ad31..ff01988e9 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -359,8 +359,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM switch (tradingEngine.current.strategyOpenStage.stageDefinedIn.value) { case 'Base Asset': { if ( - tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value >= tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value ) { changeStageStatus('Open Stage', 'Closed') @@ -369,8 +368,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } case 'Quoted Asset': { if ( - tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value + - tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >= + tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value >= tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value ) { changeStageStatus('Open Stage', 'Closed') @@ -897,10 +895,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM switch (tradingEngineStage.stageDefinedIn.value) { case 'Base Asset': { if ( - global.PRECISE( - tradingEngineStage.stageBaseAsset.sizeFilled.value + - tradingEngineStage.stageBaseAsset.feesPaid.value - , 10) + tradingEngineStage.stageBaseAsset.sizeFilled.value >= tradingEngineStage.stageBaseAsset.targetSize.value ) { @@ -912,10 +907,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } case 'Quoted Asset': { if ( - global.PRECISE( - tradingEngineStage.stageQuotedAsset.sizeFilled.value + - tradingEngineStage.stageQuotedAsset.feesPaid.value - , 10) + tradingEngineStage.stageQuotedAsset.sizeFilled.value >= tradingEngineStage.stageQuotedAsset.targetSize.value ) { From dd14181e3e07bdebae73cf9f23be8dde7a65042c Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 23 Aug 2020 09:38:44 +0200 Subject: [PATCH 075/112] Update TradingProcess.js --- LowFrequencyTrading/TradingProcess.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index a8f4ff95a..b8c971057 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -349,6 +349,10 @@ function trimDataFile(dataFile, recordDefinition) { /* Here we will discard all the records in a file that are outside of the current time range. + We will include the las element previous to the begining of the time range. This is needed + because during the simulation, the current period is not the open one, but the previous to + the open, and if we do not include the previous to the initial datetime there will be no + current objects at the begining of the simulation for many time frames. */ let beginIndex let endIndex @@ -366,7 +370,7 @@ let dataRecord = dataFile[i] let begin = dataRecord[beginIndex] let end = dataRecord[endIndex] - if (end < bot.SESSION.parameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element + if (end + timeFrame < bot.SESSION.parameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element if (begin > bot.SESSION.parameters.timeRange.config.finalDatetime) { continue } result.push(dataRecord) } From f5e43e8ad140b39c740a1422c3ef3996a24ab38e Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 24 Aug 2020 21:32:48 +0200 Subject: [PATCH 076/112] Heartbeats Improved --- LowFrequencyTrading/Snapshots.js | 9 +++- LowFrequencyTrading/TradingSimulation.js | 62 ++++++++++++++++++------ 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/LowFrequencyTrading/Snapshots.js b/LowFrequencyTrading/Snapshots.js index 1f4559fb3..50440f924 100644 --- a/LowFrequencyTrading/Snapshots.js +++ b/LowFrequencyTrading/Snapshots.js @@ -236,8 +236,13 @@ exports.newSnapshots = function newSnapshots(bot, logger) { snapshots.headers.push(key) } - let value = eval(instruction) - values.push(value) + try { + let value = eval(instruction) + values.push(value) + } catch (err) { + tradingSystem.errors.push([nodeWithCode.id, 'Instruction ' + instruction + ' evalueates with error ' + err.message]) + values.push(0) + } } } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 7a663d595..737e7722c 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -235,27 +235,57 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin } function heartBeat() { - /* We will produce a simulation level heartbeat in order to inform the user this is running. */ - heartBeatDate = new Date(Math.trunc(tradingEngine.current.episode.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) - if (heartBeatDate.valueOf() !== previousHeartBeatDate) { - let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) + 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. */ - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.episode.candle.index.value + ' @ ' + processingDate) } + heartBeatDate = new Date(Math.trunc(tradingEngine.current.episode.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) - /* Telling the world we are alive and doing well */ - let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime) - let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime) + let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime) + let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime) - let currentDateString = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) - let currentDate = new Date(heartBeatDate) - let percentage = global.getPercentage(fromDate, currentDate, lastDate) - bot.processHeartBeat(currentDateString, percentage) + let currentDateString = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) + let currentDate = new Date(heartBeatDate) + let percentage = global.getPercentage(fromDate, currentDate, lastDate) - if (global.areEqualDates(currentDate, new Date()) === false) { - logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage) + /* + Theere are a few tasks that we need to do only when the date changes, + otherwise it would be suboptimal. + */ + if (heartBeatDate.valueOf() !== previousHeartBeatDate) { + let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.episode.candle.index.value + ' @ ' + processingDate) } + + /* Logging to console and disk */ + if (global.areEqualDates(currentDate, new Date()) === false) { + logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage) + } + + /* Date only hearbeat */ + if (sessionParameters.heartbeats.config.date === true && sessionParameters.heartbeats.config.candleIndex === false) { + hartbeatText = hartbeatText + currentDateString + bot.processHeartBeat(hartbeatText, percentage) + return + } + } + previousHeartBeatDate = heartBeatDate.valueOf() + + /* + 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 # ' + tradingEngine.current.episode.candle.index.value + bot.processHeartBeat(hartbeatText, percentage) + } } } - previousHeartBeatDate = heartBeatDate.valueOf() } function positionChartAtCurrentCandle() { @@ -377,7 +407,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value if ( bot.processingDailyFiles && - tradingEngine.current.episode.candle.index.value + 1 + 1 === candlesPerDay + tradingEngine.current.episode.candle.index.value + 1 + 1 === candlesPerDay ) { /* Here we found that the next candle of the dataset is the last candle of the day. From f90465a82c42c1c64e7e96855cc053211c6b7395 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 25 Aug 2020 11:32:43 +0200 Subject: [PATCH 077/112] Update TradingSimulation.js --- LowFrequencyTrading/TradingSimulation.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 737e7722c..ec2b3d4e6 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -103,7 +103,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin that still can change. So effectively will be processing all closed candles. */ for (let i = initialCandle; i < candles.length - 1; i++) { - tradingEngine.current.episode.candle.index.value = i if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.episode.candle.index.value) } let candle = candles[tradingEngine.current.episode.candle.index.value] // This is the current candle the Simulation is working at. @@ -254,6 +253,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin otherwise it would be suboptimal. */ if (heartBeatDate.valueOf() !== previousHeartBeatDate) { + previousHeartBeatDate = heartBeatDate.valueOf() + let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2) if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.episode.candle.index.value + ' @ ' + processingDate) } @@ -270,8 +271,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin return } } - previousHeartBeatDate = heartBeatDate.valueOf() - + /* 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. @@ -392,17 +392,17 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin 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 (tradingEngine.current.episode.candle.index.value === candles.length - 1) { + if (tradingEngine.current.episode.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. - - 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.) */ let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value if ( From 64a3fd41a6dcd53634d4ecb1bd0da27e229726dd Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 25 Aug 2020 14:25:31 +0200 Subject: [PATCH 078/112] Bug Fixed --- LowFrequencyTrading/TradingProcess.js | 37 +++++++++++++----------- LowFrequencyTrading/TradingSimulation.js | 7 +++++ Session.js | 1 - 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index b8c971057..5258f1adc 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -134,7 +134,10 @@ */ if (currentTimeFrame) { /* 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. + */ buildCharts(chart) await generateOutput(chart) @@ -153,14 +156,26 @@ the date calculated by the Simulation is applied at the Trading Process Level. */ tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value) - await processDailyFiles() - + /* + 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. + */ buildCharts(chart) - + /* + The process of generating the output includes the trading simulation. + */ await generateOutput(chart) await writeProcessFiles() - + /* + If for any reason the session was stopped, we will break this loop and exit the process. + */ + if (bot.STOP_SESSION === 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) @@ -654,18 +669,6 @@ Backtests needs only one execution of this process to complete. */ bot.SESSION.stop('Backtesting Session Finished.') - } else { - /* - The natural way for the session to stop is when we reached the - user defined end datetime. - */ - const now = new Date() - if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) { - const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + "."; - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeProcessFiles -> onMarketStatusReport -> " + logText); } - - bot.SESSION.stop('Final Datetime Reached.') - } } } } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index ec2b3d4e6..7937c46f5 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -189,27 +189,34 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin if (FULL_LOG === true) { logger.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 (global.STOP_TASK_GRACEFULLY === true) { if (FULL_LOG === true) { logger.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 + bot.SESSION.stop('Final Datetime Reached') + return } if (checkNextCandle() === false) { updateEpisode('All Available Candles Processed') breakLoop = true + return } if (checkMinimunAndMaximunBalance() === false) { closeEpisode('Min or Max Balance Reached') breakLoop = true + bot.SESSION.stop('Min or Max Balance Reached') + return } } } diff --git a/Session.js b/Session.js index 79f5cb9b0..0e74351ad 100644 --- a/Session.js +++ b/Session.js @@ -114,7 +114,6 @@ socialBotsModule.finalize() bot.STOP_SESSION = true sessionError(bot.SESSION, commandOrigin) - } function setUpAppSchema() { From ebd657f35e71df68adea71b3fa7442564916abf3 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 25 Aug 2020 15:24:05 +0200 Subject: [PATCH 079/112] Update TradingSimulation.js --- LowFrequencyTrading/TradingSimulation.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 7937c46f5..eb70d24ac 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -148,6 +148,15 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin tradingEngineModule.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. + */ + checkIfWeNeedToStop() + /* Add new records to the process output */ tradingRecordsModule.appendRecords() @@ -163,12 +172,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin tradingEngineModule.setCurrentCycle('Second') await runCycle() - /* - We check if we need to stop before appending the records so that the stop - reason is also propery recorded. - */ - checkIfWeNeedToStop() - /* Add new records to the process output */ tradingRecordsModule.appendRecords() From 591b6abd51401cdf8efbed2a8eacf16b3dcf06d1 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 25 Aug 2020 16:19:57 +0200 Subject: [PATCH 080/112] Bug Fixed --- LowFrequencyTrading/TradingOutput.js | 30 ++++++++++++++++++++++++--- LowFrequencyTrading/TradingProcess.js | 7 ++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/LowFrequencyTrading/TradingOutput.js b/LowFrequencyTrading/TradingOutput.js index 47bb6ce6b..37b7bad6e 100644 --- a/LowFrequencyTrading/TradingOutput.js +++ b/LowFrequencyTrading/TradingOutput.js @@ -43,10 +43,34 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets let outputDatasetsMap = new Map() - if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { - await initializeOutputs() + if (bot.processingDailyFiles === true) { + /* + For Daily Files, we are going to initialize the outputs at the first execution + if we are not resuming, but also at every following execution except when + we are at the head of the market, because at the head of the market we need to + append more data to the same output files that already exists. + */ + if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { + await initializeOutputs() + } else { + if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) { + await readFiles() + } else { + await initializeOutputs() + } + } } else { - await readFiles() + /* + Form Market Files, we are only going to initialize the outputs at the first run + and if we are not resuming execution. Subsequent runs means that we are at the + head of the market. Remember that backtests finishes the session once the reach + the final datetime. + */ + if (bot.FIRST_EXECUTION === true && bot.RESUME === false) { + await initializeOutputs() + } else { + await readFiles() + } } await tradingSimulation.runSimulation( diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 5258f1adc..7e0a2e418 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -557,7 +557,12 @@ tradingProcessDate ) - bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution. + /* + 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. + */ + bot.FIRST_EXECUTION = false + bot.RESUME = false } async function writeProcessFiles() { From f30868e72ecdf5d043f1eef03976e9154efb616c Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 26 Aug 2020 16:37:05 +0200 Subject: [PATCH 081/112] Bug Fixes --- LowFrequencyTrading/TradingOrders.js | 58 +++++++++++++++++++--------- LowFrequencyTrading/TradingStages.js | 7 +++- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 9bbec13b2..e7258a578 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -151,25 +151,23 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */ if (tradingEngine.current.episode.cycle.value === 'Second') { continue } - /* - In the previous steps, we might have discovered that the order was cancelled - at the exchange, or filled, so the order might still not be Open. - If the stage is closing or the order is not Open, we wont be cancelling orders - based on defined events. + + /* Check if we need to cancel the order */ + await checkCancelOrderEvent(tradingEngineStage, executionAlgorithm, executionNode, tradingEngineOrder, tradingSystemOrder) + /* + If by this time the order is closed, we need to clone it and get the close + to the Last node at the Trading Engine data structure. */ - if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { - - /* Check if we need to Cancel this Order */ - let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) - if (situationName !== undefined) { - - /* Simulate Order Cancelation, if needed. */ - simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') - - /* Cancel the order at the Exchange, if needed. */ - await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') - - updateEndsWithCycle(tradingEngineOrder) + if (tradingEngineOrder.status.value === 'Closed') { + switch (tradingEngineOrder.type) { + case 'Market Order': { + tradingEngineModule.cloneValues(tradingEngineOrder, tradingEngine.last.marketOrders) + break + } + case 'Limit Order': { + tradingEngineModule.cloneValues(tradingEngineOrder, tradingEngine.last.limitOrders) + break + } } } } @@ -177,6 +175,30 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } + async function checkCancelOrderEvent(tradingEngineStage, executionAlgorithm, executionNode, tradingEngineOrder, tradingSystemOrder) { + /* + In the previous steps, we might have discovered that the order was cancelled + at the exchange, or filled, so the order might still not be Open. + If the stage is closing or the order is not Open, we wont be cancelling orders + based on defined events. + */ + if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { + + /* Check if we need to Cancel this Order */ + let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (situationName !== undefined) { + + /* Simulate Order Cancelation, if needed. */ + simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + + /* Cancel the order at the Exchange, if needed. */ + await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + + updateEndsWithCycle(tradingEngineOrder) + } + } + } + function tradingSystemValidations(tradingSystemOrder) { /* Trading System Validations */ if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) } diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index ff01988e9..df7848a91 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -891,11 +891,14 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM either on Base Asset or Quoted Asset. This can happens at any time when we update the sizeFilled and the feesPaid values when we see at the exchange that orders were filled. + Note that the comparison is made with a Rounding Factor in order to avoid rounding problems. */ + const ROUNDING_ERROR_CORRECTION_FACTOR = 1.0000001 + switch (tradingEngineStage.stageDefinedIn.value) { case 'Base Asset': { if ( - tradingEngineStage.stageBaseAsset.sizeFilled.value + tradingEngineStage.stageBaseAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR >= tradingEngineStage.stageBaseAsset.targetSize.value ) { @@ -907,7 +910,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM } case 'Quoted Asset': { if ( - tradingEngineStage.stageQuotedAsset.sizeFilled.value + tradingEngineStage.stageQuotedAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR >= tradingEngineStage.stageQuotedAsset.targetSize.value ) { From 75808d305e1ff86c8f4730561b13c694349b2dde Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 27 Aug 2020 12:42:38 +0200 Subject: [PATCH 082/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 129 +++++++++++++++++---------- 1 file changed, 84 insertions(+), 45 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index e7258a578..8910e8f9c 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -419,65 +419,104 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Actual Rate Simulation */ let calculatedBasedOnTradingSystem = false - /* Based on the Trading System Definition */ - if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { - /* Calculate this only once for this order */ - if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { - tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) - if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { - calculatedBasedOnTradingSystem = true + basedOnTradingSystem() + basedOnSessionParameters() + considerBestMatchWithOrderBook() + recalculateQuotedAsset() + recalculateSizePlaced() + + function basedOnTradingSystem() { + /* Based on the Trading System Definition */ + if (tradingSystemOrder.simulatedExchangeEvents !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { + /* Calculate this only once for this order */ + if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { + tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) { + calculatedBasedOnTradingSystem = true + } } } } } } - /* Based on the Session Parameters Definition */ - if (calculatedBasedOnTradingSystem === false) { - switch (tradingEngineOrder.type) { - case 'Market Order': { - /* Actual Rate is simulated based on the Session Paremeters */ - let slippageAmount = tradingEngineOrder.rate.value * sessionParameters.slippage.config.positionRate / 100 - switch (tradingSystemOrder.type) { - case 'Market Sell Order': { - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount - break - } - case 'Market Buy Order': { - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount - break + function basedOnSessionParameters() { + /* Based on the Session Parameters Definition */ + if (calculatedBasedOnTradingSystem === false) { + switch (tradingEngineOrder.type) { + case 'Market Order': { + /* Actual Rate is simulated based on the Session Paremeters */ + let slippageAmount = tradingEngineOrder.rate.value * sessionParameters.slippage.config.positionRate / 100 + switch (tradingSystemOrder.type) { + case 'Market Sell Order': { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount + break + } + case 'Market Buy Order': { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount + break + } } + break + } + case 'Limit Order': { + /* In Limit Orders the actual rate is the rate of the order, there is no slippage */ + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + break } - break - } - case 'Limit Order': { - /* In Limit Orders the actual rate is the rate of the order, there is no slippage */ - tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - break } } } - tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - /* - Recalculate the Quouted Asset Size based on the newly calculated Actual Rate. - */ - tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.orderStatistics.actualRate.value - tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) - /* - Recaulculate the Size Placed Based on the newly calculated Quoted Asset Size. - */ - tradingEngineStage.stageQuotedAsset.sizePlaced.value = - tradingEngineStage.stageQuotedAsset.sizePlaced.value - - previousQuotedAssetSize + function considerBestMatchWithOrderBook() { + /* + Until here we have got the 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. + */ + switch (true) { + case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { + if (tradingEngineOrder.orderStatistics.actualRate.value > tradingEngine.current.episode.candle.max.value) { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.max.value + } + break + } + case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': { + if (tradingEngineOrder.orderStatistics.actualRate.value < tradingEngine.current.episode.candle.min.value) { + tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.min.value + } + break + } + } + tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + } - tradingEngineStage.stageQuotedAsset.sizePlaced.value = - tradingEngineStage.stageQuotedAsset.sizePlaced.value + - tradingEngineOrder.orderQuotedAsset.size.value + function recalculateQuotedAsset() { + /* + Recalculate the Quouted Asset Size based on the newly calculated Actual Rate. + */ + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.orderStatistics.actualRate.value + tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) + } - tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) + function recalculateSizePlaced(){ + /* + Recaulculate the Size Placed Based on the newly calculated Quoted Asset Size. + */ + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value - + previousQuotedAssetSize + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value + + tradingEngineOrder.orderQuotedAsset.size.value + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) + } } function percentageFilledSimulation() { From 8fabc3f60d3dc6be3b6faead61295a098c38f102 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 27 Aug 2020 15:27:23 +0200 Subject: [PATCH 083/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 8910e8f9c..fcdffdf73 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -128,7 +128,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM at the Trading Engine Order the lock is Open or Closed. */ if (tradingSystemOrder.config.spawnMultipleOrders !== true) { - if (tradingEngineOrder.identifier.value === 'Closed') { + if (tradingEngineOrder.lock.value === 'Closed') { continue } } From 8e15a7dd81af2ea093e17bb8f34a274535c468cf Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 28 Aug 2020 14:11:12 +0200 Subject: [PATCH 084/112] Update ProcessExecutionEvents.js --- ProcessExecutionEvents.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/ProcessExecutionEvents.js b/ProcessExecutionEvents.js index c642e9a06..7c300dad2 100644 --- a/ProcessExecutionEvents.js +++ b/ProcessExecutionEvents.js @@ -86,6 +86,16 @@ /* We need to find at which network node is running the process that we need to hear from when it finished. */ let network = global.TASK_NETWORK + /* + Notice that the first task that matches the dependency is going to be considered. + This means that if the same task, with the same process, pointing to the same + process definition in the same data mine, with the same exchange and market + configured, is at two different network nodes at the same time, the current task + is going to depend on the first one found, not necesarily the one running at the + same network node. If you need to control witch task specifically to depend on + and need to exactly the same task to exist, you should split the network in two + different networks, each one with unique tasks so as to avoid confussion. + */ for (let i = 0; i < network.networkNodes.length; i++) { let networkNode = network.networkNodes[i] if (networkNode.dataMining !== undefined) { @@ -111,13 +121,20 @@ /* We found where the task that runs the process definition we are waiting for is located on the network. */ thisObject.networkNode = networkNode - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { + logger.write(MODULE_NAME, + "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + + " -> host = " + networkNode.config.host + + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.config.host, networkNode.config.webSocketsPort) eventServerClient.initialize(onConnected) function onConnected() { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { + logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + + " -> host = " + networkNode.config.host + + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } callBackFunction(global.DEFAULT_OK_RESPONSE); return From 7ccd4865582ef12e48a6a9f8ff25eb8997262e05 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 28 Aug 2020 22:47:47 +0200 Subject: [PATCH 085/112] Bug Fixed --- LowFrequencyTrading/TradingSimulation.js | 22 +++++++++++++--------- Session.js | 4 ++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index eb70d24ac..89255ae3f 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -121,6 +121,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin } if (checkInitialDatetime() === false) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Before the Initia Date Time @ ' + (new Date(candle.begin)).toLocaleString()) } continue } @@ -155,7 +156,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin had the chance to check for the status of placed orders or even cancel the ones that needed cancellation. */ - checkIfWeNeedToStop() + checkIfWeNeedToStopBetweenCycles() /* Add new records to the process output */ tradingRecordsModule.appendRecords() @@ -172,6 +173,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin tradingEngineModule.setCurrentCycle('Second') await runCycle() + checkIfWeNeedToStopAfterBothCycles() + /* Add new records to the process output */ tradingRecordsModule.appendRecords() @@ -186,8 +189,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin await tradingSystemModule.run() } - function checkIfWeNeedToStop() { - /* Checking if we should continue processing this loop or not. */ + function checkIfWeNeedToStopBetweenCycles() { if (bot.STOP_SESSION === true) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } updateEpisode('Session Stopped') @@ -209,12 +211,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin return } - if (checkNextCandle() === false) { - updateEpisode('All Available Candles Processed') - breakLoop = true - return - } - if (checkMinimunAndMaximunBalance() === false) { closeEpisode('Min or Max Balance Reached') breakLoop = true @@ -222,6 +218,14 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin return } } + + function checkIfWeNeedToStopAfterBothCycles() { + if (checkNextCandle() === false) { + updateEpisode('All Available Candles Processed') + breakLoop = true + return + } + } } tradingSystemModule.finalize() diff --git a/Session.js b/Session.js index 0e74351ad..135b2cc68 100644 --- a/Session.js +++ b/Session.js @@ -185,7 +185,7 @@ } /* Session Type Forced Values */ - let today = new Date((new Date()).valueOf()) + let today = (new Date()).valueOf() let aYearAgo = today - global.ONE_YEAR_IN_MILISECONDS let aYearFromNow = today + global.ONE_YEAR_IN_MILISECONDS switch (bot.SESSION.type) { @@ -214,7 +214,7 @@ */ /* Initial Datetime */ - if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined) { + if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined || bot.SESSION.parameters.timeRange.config.allowStartingFromThePast !== true ) { bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault } else { bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf() From 5e16b3e49a44c429921d233c209e6766c6da4f68 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 29 Aug 2020 13:37:51 +0200 Subject: [PATCH 086/112] Warniings & Infos --- LowFrequencyTrading/TradingSimulation.js | 9 +++++++-- LowFrequencyTrading/TradingSystem.js | 4 ++++ Session.js | 15 ++++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 89255ae3f..fe81e4a1e 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -104,8 +104,9 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin */ for (let i = initialCandle; i < candles.length - 1; i++) { tradingEngine.current.episode.candle.index.value = i - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.episode.candle.index.value) } - let candle = candles[tradingEngine.current.episode.candle.index.value] // This is the current candle the Simulation is working at. + + /* This is the current candle the Simulation is working at. */ + let candle = candles[tradingEngine.current.episode.candle.index.value] if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toLocaleString()) } if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) } @@ -186,6 +187,10 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin tradingEpisodeModule.reset() tradingEngineModule.reset() + let infoMessage = 'Processing candle # ' + tradingEngine.current.episode.candle.index.value + ' @ the ' + tradingEngine.current.episode.cycle.value + ' cycle.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> ' + infoMessage) } + tradingSystem.infos.push([tradingSystem.id, infoMessage]) + await tradingSystemModule.run() } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 5ee168a04..479ed7b66 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -87,6 +87,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingSystem.highlights = undefined tradingSystem.errors = undefined + tradingSystem.warnings = undefined + tradingSystem.infos = undefined tradingSystem.values = undefined tradingSystem.status = undefined tradingSystem.progress = undefined @@ -106,6 +108,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingSystem.highlights = [] tradingSystem.errors = [] + tradingSystem.warnings = [] + tradingSystem.infos = [] tradingSystem.values = [] tradingSystem.status = [] tradingSystem.progress = [] diff --git a/Session.js b/Session.js index 135b2cc68..7ad26a9b2 100644 --- a/Session.js +++ b/Session.js @@ -214,10 +214,19 @@ */ /* Initial Datetime */ - if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined || bot.SESSION.parameters.timeRange.config.allowStartingFromThePast !== true ) { - bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault + if (bot.SESSION.type === 'Backtesting Session') { + if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined ) { + bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault + } else { + bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf() + } } else { - bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf() + /* Non backtest session can start from the past only if explicitly configured that way */ + if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined || bot.SESSION.parameters.timeRange.config.allowStartingFromThePast !== true ) { + bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault + } else { + bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf() + } } /* Final Datetime */ From 3c74c2feeafef1bd44802fb384cba0e6c2af3089 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 29 Aug 2020 16:46:00 +0200 Subject: [PATCH 087/112] online Warnings and Info --- IndicatorBot.js | 22 +-------------------- SensorBot.js | 22 +-------------------- Session.js | 48 ++++++++++++++++++++++++++++++++++++++++++++-- StatusReport.js | 2 +- TradingBot.js | 22 +-------------------- server.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 66 deletions(-) diff --git a/IndicatorBot.js b/IndicatorBot.js index df704b925..bbc8d1290 100644 --- a/IndicatorBot.js +++ b/IndicatorBot.js @@ -81,9 +81,6 @@ try { let fixedTimeLoopIntervalHandle; - /* Errors sent to the UI */ - bot.processError = processError - /* Heartbeats sent to the UI */ bot.processHeartBeat = processHeartBeat @@ -763,26 +760,9 @@ global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) } - function processError(node, errorMessage) { - let event - if (node !== undefined) { - event = { - nodeName: node.name, - nodeType: node.type, - nodeId: node.id, - errorMessage: errorMessage - } - } else { - event = { - errorMessage: errorMessage - } - } - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Error', event) - } - function processStopped() { if (global.unexpectedError !== undefined) { - processError(undefined, "An unexpected error caused the Process to stop.") + global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.") } else { global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') } diff --git a/SensorBot.js b/SensorBot.js index 78cec7d95..84a6d821a 100644 --- a/SensorBot.js +++ b/SensorBot.js @@ -85,9 +85,6 @@ try { let fixedTimeLoopIntervalHandle; - /* Errors sent to the UI */ - bot.processError = processError - /* Heartbeats sent to the UI */ bot.processHeartBeat = processHeartBeat @@ -571,26 +568,9 @@ global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) } - function processError(node, errorMessage) { - let event - if (node !== undefined) { - event = { - nodeName: node.name, - nodeType: node.type, - nodeId: node.id, - errorMessage: errorMessage - } - } else { - event = { - errorMessage: errorMessage - } - } - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Error', event) - } - function processStopped() { if (global.unexpectedError !== undefined) { - processError(undefined, "An unexpected error caused the Process to stop.") + global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.") } else { global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') } diff --git a/Session.js b/Session.js index 7ad26a9b2..211c38946 100644 --- a/Session.js +++ b/Session.js @@ -41,8 +41,10 @@ global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, onSessionRan) global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, onSessionStopped) - /* Errors sent to the UI */ + /* Connect this here so that it is accesible from other places */ bot.sessionError = sessionError + bot.sessionWarning = sessionWarning + bot.sessionInfo = sessionInfo /* Heartbeats sent to the UI */ bot.sessionHeartBeat = sessionHeartBeat @@ -113,7 +115,7 @@ socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin) socialBotsModule.finalize() bot.STOP_SESSION = true - sessionError(bot.SESSION, commandOrigin) + sessionInfo(bot.SESSION, commandOrigin) } function setUpAppSchema() { @@ -421,6 +423,48 @@ } } + function sessionWarning(node, warningMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + warningMessage: warningMessage + } + } else { + event = { + warningMessage: warningMessage + } + } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Warning', event) + + if (global.STOP_TASK_GRACEFULLY === true) { + bot.STOP_SESSION = true + } + } + + function sessionInfo(node, infoMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + infoMessage: infoMessage + } + } else { + event = { + infoMessage: infoMessage + } + } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Info', event) + + if (global.STOP_TASK_GRACEFULLY === true) { + bot.STOP_SESSION = true + } + } + } catch (err) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); diff --git a/StatusReport.js b/StatusReport.js index b442e3aa9..50cf8c2a1 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -83,7 +83,7 @@ function validationFailed(errorInNode, errorMessage) { let nodeString = JSON.stringify(errorInNode) logger.write(MODULE_NAME, "[ERROR] initialize -> " + errorMessage + ' -> nodeString = ' + nodeString) - bot.processError(errorInNode, errorMessage) + bot.global.PROCESS_ERROR(bot.processKey, errorInNode, errorMessage) callBackFunction(global.DEFAULT_FAIL_RESPONSE); } diff --git a/TradingBot.js b/TradingBot.js index 6204a524a..4f1e65487 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -45,9 +45,6 @@ let fixedTimeLoopIntervalHandle; bot.STOP_SESSION = true; - /* Errors sent to the UI */ - bot.processError = processError - /* Heartbeats sent to the UI */ bot.processHeartBeat = processHeartBeat @@ -654,26 +651,9 @@ global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event) } - function processError(node, errorMessage) { - let event - if (node !== undefined) { - event = { - nodeName: node.name, - nodeType: node.type, - nodeId: node.id, - errorMessage: errorMessage - } - } else { - event = { - errorMessage: errorMessage - } - } - global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Error', event) - } - function processStopped() { if (global.unexpectedError !== undefined) { - processError(undefined, "An unexpected error caused the Process to stop.") + global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.") } else { global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped') } diff --git a/server.js b/server.js index 2d0f3225f..f3473bda2 100644 --- a/server.js +++ b/server.js @@ -51,6 +51,57 @@ global.REMOVE_TIME = function (datetime) { return new Date(date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + " " + "00:00" + GMT_SECONDS); } +global.PROCESS_ERROR = function (processKey, node, errorMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + errorMessage: errorMessage + } + } else { + event = { + errorMessage: errorMessage + } + } + global.EVENT_SERVER_CLIENT.raiseEvent(processKey, 'Error', event) +} + +global.PROCESS_WARNING = function (processKey, node, warningMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + warningMessage: warningMessage + } + } else { + event = { + warningMessage: warningMessage + } + } + global.EVENT_SERVER_CLIENT.raiseEvent(processKey, 'Warning', event) +} + +global.PROCESS_INFO = function (processKey, node, infoMessage) { + let event + if (node !== undefined) { + event = { + nodeName: node.name, + nodeType: node.type, + nodeId: node.id, + infoMessage: infoMessage + } + } else { + event = { + infoMessage: infoMessage + } + } + global.EVENT_SERVER_CLIENT.raiseEvent(processKey, 'Info', event) +} + process.on('uncaughtException', function (err) { console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message) console.log('[ERROR] Task Server -> server -> uncaughtException -> err.stack = ' + err.stack) From cdb7bb14f8b39f2e378957a56dca94ac16ce6ce5 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 30 Aug 2020 16:03:27 +0200 Subject: [PATCH 088/112] APP SCHEMA from session to task --- LowFrequencyTrading/Snapshots.js | 2 +- LowFrequencyTrading/TradingEngine.js | 6 +++--- LowFrequencyTrading/TradingSystem.js | 2 +- Session.js | 12 ------------ server.js | 20 ++++++++++++++++++-- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/LowFrequencyTrading/Snapshots.js b/LowFrequencyTrading/Snapshots.js index 50440f924..6f1b02c5a 100644 --- a/LowFrequencyTrading/Snapshots.js +++ b/LowFrequencyTrading/Snapshots.js @@ -124,7 +124,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) { } /* Now we go down through all this node children */ - let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type) if (nodeDefinition === undefined) { return } if (nodeDefinition.properties !== undefined) { diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 1b75ee82f..b9dc6f889 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -99,7 +99,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { 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 nodeDefinition = bot.APP_SCHEMA_MAP.get(originNode.type) + let nodeDefinition = global.APP_SCHEMA_MAP.get(originNode.type) if (nodeDefinition === undefined) { return } if (nodeDefinition.properties !== undefined) { @@ -138,7 +138,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { nodesMap.set(node.id, node) /* Now we go down through all this node children */ - let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type) if (nodeDefinition === undefined) { return } if (nodeDefinition.properties !== undefined) { @@ -181,7 +181,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } /* Now we go down through all this node children */ - let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type) if (nodeDefinition === undefined) { return } if (nodeDefinition.properties !== undefined) { diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 479ed7b66..36b6b40e4 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -182,7 +182,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } /* Now we go down through all this node children */ - let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type) if (nodeDefinition === undefined) { return } if (nodeDefinition.properties !== undefined) { diff --git a/Session.js b/Session.js index 211c38946..88682d775 100644 --- a/Session.js +++ b/Session.js @@ -57,7 +57,6 @@ if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { return } // This happens when the UI is reloaded, the session was running and tries to run it again. /* We are going to run the Definition comming at the event. */ - bot.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema) bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem) bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine) bot.SESSION = JSON.parse(message.event.session) @@ -66,7 +65,6 @@ bot.FIRST_EXECUTION = true bot.SESSION.stop = stopSession // stop function - setUpAppSchema() setUpSessionFolderName() /* We validate all parameters received and complete some that might be missing if needed. */ @@ -118,16 +116,6 @@ sessionInfo(bot.SESSION, commandOrigin) } - function setUpAppSchema() { - /* Setup the APP_SCHEMA_MAP based on the APP_SCHEMA_ARRAY */ - bot.APP_SCHEMA_MAP = new Map() - for (let i = 0; i < bot.APP_SCHEMA_ARRAY.length; i++) { - let nodeDefinition = bot.APP_SCHEMA_ARRAY[i] - let key = nodeDefinition.type - bot.APP_SCHEMA_MAP.set(key, nodeDefinition) - } - } - function setUpSessionFolderName() { /* The session object is overwritten when the session is run. For that reason we diff --git a/server.js b/server.js index f3473bda2..02cd778c6 100644 --- a/server.js +++ b/server.js @@ -102,6 +102,10 @@ global.PROCESS_INFO = function (processKey, node, infoMessage) { global.EVENT_SERVER_CLIENT.raiseEvent(processKey, 'Info', event) } +global.NODE_BRANCH_TO_ARRAY = function (node, nodeType) { + +} + process.on('uncaughtException', function (err) { console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message) console.log('[ERROR] Task Server -> server -> uncaughtException -> err.stack = ' + err.stack) @@ -230,7 +234,8 @@ function preLoader() { global.EVENT_SERVER_CLIENT.listenToEvent('Task Server - ' + taskId, 'Run Task', undefined, 'Task Server - ' + taskId, undefined, eventReceived) global.EVENT_SERVER_CLIENT.raiseEvent('Task Manager - ' + taskId, 'Nodejs Process Ready for Task') function eventReceived(message) { - global.TASK_NODE = message + global.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema) + setUpAppSchema() global.TASK_NODE = JSON.parse(message.event.taskDefinition) global.TASK_NETWORK = JSON.parse(message.event.networkDefinition) bootLoader() @@ -245,7 +250,8 @@ function preLoader() { //console.log('[INFO] Task Server -> server -> preLoader -> Waiting for event to start debugging...') global.EVENT_SERVER_CLIENT.listenToEvent('Task Server', 'Debug Task Started', undefined, 'Task Server', undefined, startDebugging) function startDebugging(message) { - global.TASK_NODE = message + global.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema) + setUpAppSchema() global.TASK_NODE = JSON.parse(message.event.taskDefinition) global.TASK_NETWORK = JSON.parse(message.event.networkDefinition) bootLoader() @@ -255,6 +261,16 @@ function preLoader() { console.log('[ERROR] Task Server -> server -> preLoader -> global.TASK_NODE = ' + JSON.stringify(global.TASK_NODE).substring(0, 1000)) } } + + function setUpAppSchema() { + /* Setup the APP_SCHEMA_MAP based on the APP_SCHEMA_ARRAY */ + global.APP_SCHEMA_MAP = new Map() + for (let i = 0; i < global.APP_SCHEMA_ARRAY.length; i++) { + let nodeDefinition = global.APP_SCHEMA_ARRAY[i] + let key = nodeDefinition.type + global.APP_SCHEMA_MAP.set(key, nodeDefinition) + } + } } function bootLoader() { From f1a14df0d05835ee751901c15c2bedc89326c0d2 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 30 Aug 2020 19:23:55 +0200 Subject: [PATCH 089/112] Output Dataset Folders Implemented --- IndicatorOutput.js | 2 +- LowFrequencyTrading/TradingOutput.js | 2 +- LowFrequencyTrading/TradingProcess.js | 11 +- LowFrequencyTrading/TradingRecords.js | 3 +- MultiPeriodDaily.js | 5 +- MultiPeriodMarket.js | 5 +- ProcessOutput.js | 181 +++++++++++++------------- StatusReport.js | 2 +- server.js | 31 +++++ 9 files changed, 135 insertions(+), 107 deletions(-) diff --git a/IndicatorOutput.js b/IndicatorOutput.js index 0ae431043..988acda77 100644 --- a/IndicatorOutput.js +++ b/IndicatorOutput.js @@ -56,7 +56,7 @@ let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset') if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ diff --git a/LowFrequencyTrading/TradingOutput.js b/LowFrequencyTrading/TradingOutput.js index 37b7bad6e..1f582d090 100644 --- a/LowFrequencyTrading/TradingOutput.js +++ b/LowFrequencyTrading/TradingOutput.js @@ -40,7 +40,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM const TRADING_SIMULATION = require('./TradingSimulation.js') let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES) - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset') let outputDatasetsMap = new Map() if (bot.processingDailyFiles === true) { diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 7e0a2e418..40f6dce13 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -507,7 +507,7 @@ let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset') if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ @@ -566,6 +566,7 @@ } async function writeProcessFiles() { + let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset') await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) await writeDataRanges() @@ -579,10 +580,10 @@ async function writeDataRanges() { for ( let outputDatasetIndex = 0; - outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; + outputDatasetIndex < outputDatasets.length; outputDatasetIndex++ ) { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; await writeDataRange(productCodeName); } @@ -618,10 +619,10 @@ for ( let outputDatasetIndex = 0; - outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; + outputDatasetIndex < outputDatasets.length; outputDatasetIndex++ ) { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily') await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Market') diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index fd019fbcf..f3de8553c 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -37,7 +37,8 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { 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 = bot.processNode.referenceParent.processOutput.outputDatasets + let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset') + for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] let dataset = outputDatasetNode.referenceParent diff --git a/MultiPeriodDaily.js b/MultiPeriodDaily.js index 7ae3cc3b5..cdc4a892b 100644 --- a/MultiPeriodDaily.js +++ b/MultiPeriodDaily.js @@ -469,17 +469,18 @@ } function writeDataRanges(callBack) { + let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset') let outputDatasetIndex = -1; controlLoop() function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; writeDataRange(contextVariables.dateBeginOfMarket, bot.multiPeriodDailyProcessDatetime, productCodeName, controlLoop) } function controlLoop() { outputDatasetIndex++ - if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { + if (outputDatasetIndex < outputDatasets.length) { productLoopBody() } else { callBack(global.DEFAULT_OK_RESPONSE) diff --git a/MultiPeriodMarket.js b/MultiPeriodMarket.js index 02a35a702..76ba55b28 100644 --- a/MultiPeriodMarket.js +++ b/MultiPeriodMarket.js @@ -141,17 +141,18 @@ } function writeTimeFramesFiles(callBack) { + let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset') let outputDatasetIndex = -1; controlLoop() function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; + let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; writeTimeFramesFile(productCodeName, controlLoop) } function controlLoop() { outputDatasetIndex++ - if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { + if (outputDatasetIndex < outputDatasets.length) { productLoopBody() } else { callBack() diff --git a/ProcessOutput.js b/ProcessOutput.js index 71d7cdc81..3496ffe15 100644 --- a/ProcessOutput.js +++ b/ProcessOutput.js @@ -16,96 +16,11 @@ return thisObject; function raiseEvents(lastFile, timeFrames, callBackFunction) { - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] raiseEvents -> Entering function."); } - /* We will reaise the events for the datasets impacted by the process that just finished. */ if (bot.processNode.referenceParent.processOutput !== undefined) { - if (bot.processNode.referenceParent.processOutput.outputDatasets !== undefined) { - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets - - for (let j = 0; j < outputDatasets.length; j++) { - let outputDataset = outputDatasets[j] - - /* Some validations to verify that everything is in place. */ - if (outputDataset.referenceParent !== undefined) { - if (outputDataset.referenceParent.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (outputDataset.referenceParent.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Product not attached to a Bot. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Dataset Definition not attached to a Product. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Output Dataset with no reference to a Dataset Definition. Output Dataset = " + JSON.stringify(outputDataset)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - /* All good, lets emit the event. */ - - let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName - let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName - let product = outputDataset.referenceParent.parentNode.config.codeName - let dataset = outputDataset.referenceParent.config.codeName - - key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset - let event = { - lastFile: lastFile, - timeFrames: timeFrames - } - global.EVENT_SERVER_CLIENT.createEventHandler(key, 'Dataset Updated') - global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Dataset Updated', event) - } - } - } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function asyncRaiseEvents(lastFile, timeFrames) { - - if (bot.processNode.referenceParent.processOutput !== undefined) { - if (bot.processNode.referenceParent.processOutput.outputDatasets !== undefined) { - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset') for (let j = 0; j < outputDatasets.length; j++) { let outputDataset = outputDatasets[j] @@ -114,38 +29,46 @@ if (outputDataset.referenceParent !== undefined) { if (outputDataset.referenceParent.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } if (outputDataset.referenceParent.parentNode !== undefined) { if (outputDataset.referenceParent.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } } else { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } } else { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Product not attached to a Bot. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } } else { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Dataset Definition not attached to a Product. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } } else { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Output Dataset with no reference to a Dataset Definition. Output Dataset = " + JSON.stringify(outputDataset)); - throw(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } /* All good, lets emit the event. */ @@ -164,6 +87,76 @@ global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Dataset Updated', event) } } + + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function asyncRaiseEvents(lastFile, timeFrames) { + + if (bot.processNode.referenceParent.processOutput !== undefined) { + let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset') + + for (let j = 0; j < outputDatasets.length; j++) { + let outputDataset = outputDatasets[j] + + /* Some validations to verify that everything is in place. */ + if (outputDataset.referenceParent !== undefined) { + if (outputDataset.referenceParent.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + if (outputDataset.referenceParent.parentNode !== undefined) { + if (outputDataset.referenceParent.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Product not attached to a Bot. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Dataset Definition not attached to a Product. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + } else { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Output Dataset with no reference to a Dataset Definition. Output Dataset = " + JSON.stringify(outputDataset)); + throw (global.DEFAULT_FAIL_RESPONSE); + } + + /* All good, lets emit the event. */ + + let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName + let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName + let product = outputDataset.referenceParent.parentNode.config.codeName + let dataset = outputDataset.referenceParent.config.codeName + + key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let event = { + lastFile: lastFile, + timeFrames: timeFrames + } + global.EVENT_SERVER_CLIENT.createEventHandler(key, 'Dataset Updated') + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Dataset Updated', event) + } } } }; diff --git a/StatusReport.js b/StatusReport.js index 50cf8c2a1..39d116fd5 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -83,7 +83,7 @@ function validationFailed(errorInNode, errorMessage) { let nodeString = JSON.stringify(errorInNode) logger.write(MODULE_NAME, "[ERROR] initialize -> " + errorMessage + ' -> nodeString = ' + nodeString) - bot.global.PROCESS_ERROR(bot.processKey, errorInNode, errorMessage) + global.PROCESS_ERROR(bot.processKey, errorInNode, errorMessage) callBackFunction(global.DEFAULT_FAIL_RESPONSE); } diff --git a/server.js b/server.js index 02cd778c6..3e6bb2824 100644 --- a/server.js +++ b/server.js @@ -103,7 +103,38 @@ global.PROCESS_INFO = function (processKey, node, infoMessage) { } global.NODE_BRANCH_TO_ARRAY = function (node, nodeType) { + let resultArray = [] + scanNodeBranch(node, nodeType) + return resultArray + function scanNodeBranch(startingNode) { + let nodeDefinition = global.APP_SCHEMA_MAP.get(startingNode.type) + if (nodeDefinition === undefined) { return } + + if (startingNode.type === nodeType) { + resultArray.push(startingNode) + return + } + + if (nodeDefinition.properties === undefined) { return } + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + scanNodeBranch(startingNode[property.name]) + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + for (let m = 0; m < startingNodePropertyArray.length; m++) { + scanNodeBranch(startingNodePropertyArray[m]) + } + break + } + } + } + } } process.on('uncaughtException', function (err) { From e12a21f127d485171a0dd82f75daa98216f152fe Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 1 Sep 2020 12:48:53 +0200 Subject: [PATCH 090/112] Data Mining Running Again --- DataSet.js | 79 +++++-------------------------- server.js | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 67 deletions(-) diff --git a/DataSet.js b/DataSet.js index fe188ba16..7dce95fe8 100644 --- a/DataSet.js +++ b/DataSet.js @@ -79,77 +79,22 @@ exports.newDataSet = function newDataSet(BOT, logger) { let network = global.TASK_NETWORK let datasetProductDefinition = thisObject.node.parentNode - for (let i = 0; i < network.networkNodes.length; i++) { - let networkNode = network.networkNodes[i] - if (networkNode.dataStorage !== undefined) { - if (networkNode.dataStorage.sessionIndependentData !== undefined) { - for (let j = 0; j < networkNode.dataStorage.sessionIndependentData.exchangeDataProducts.length; j++) { - let exchangeDataProduct = networkNode.dataStorage.sessionIndependentData.exchangeDataProducts[j] - for (let k = 0; k < exchangeDataProduct.singleMarketData.length; k++) { - let singleMarketData = exchangeDataProduct.singleMarketData[k] - if (singleMarketData.referenceParent !== undefined) { - let market = singleMarketData.referenceParent - let currentProcessMarket = bot.processNode.marketReference.referenceParent + let nodeArray = global.NODE_MESH_TO_PATH_ARRAY(network, datasetProductDefinition.id) + let networkNode = global.FIND_NODE_IN_NODE_ARRAY(nodeArray, 'Network Node') - if (currentProcessMarket.id === market.id) { - for (let m = 0; m < singleMarketData.dataProducts.length; m++) { - let dataProduct = singleMarketData.dataProducts[m] - if (dataProduct.referenceParent !== undefined) { - let productDefinition = dataProduct.referenceParent - if (datasetProductDefinition.id === productDefinition.id) { - - /* We found where the data is located on the network. */ - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return - } - } - } - } - } - } - } - } - if (networkNode.dataStorage.sessionBasedData !== undefined) { - for (let j = 0; j < networkNode.dataStorage.sessionBasedData.exchangeDataProducts.length; j++) { - let exchangeDataProduct = networkNode.dataStorage.sessionBasedData.exchangeDataProducts[j] - for (let s = 0; s < exchangeDataProduct.sessionReferences.length; s++) { - let sessionReference = exchangeDataProduct.sessionReferences[s] - let singleMarketData = sessionReference.singleMarketData - if (singleMarketData.referenceParent !== undefined) { - let market = singleMarketData.referenceParent - let currentProcessMarket = bot.processNode.marketReference.referenceParent - - if (currentProcessMarket.id === market.id) { - for (let m = 0; m < singleMarketData.dataProducts.length; m++) { - let dataProduct = singleMarketData.dataProducts[m] - if (dataProduct.referenceParent !== undefined) { - let productDefinition = dataProduct.referenceParent - if (datasetProductDefinition.id === productDefinition.id) { - - /* We found where the data is located on the network. */ - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return - } - } - } - } - } - } - } - } - } + if (networkNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] initialize -> Network Node not found.") + logger.write(MODULE_NAME, "[ERROR] initialize -> Initialization Failed because we could not find where the data of this dataset is located within the network. Check the logs for more info."); + logger.write(MODULE_NAME, "[ERROR] initialize -> Could not find where " + datasetProductDefinition.name + " for " + bot.exchange + " " + bot.market.baseAsset + "/" + bot.market.quotedAsset + " is stored within the network."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return } - logger.write(MODULE_NAME, "[ERROR] initialize -> Initialization Failed because we could not find where the data of this dataset is located within the network. Check the logs for more info."); - logger.write(MODULE_NAME, "[ERROR] initialize -> Could not find where " + datasetProductDefinition.name + " for " + bot.exchange + " " + bot.market.baseAsset + "/" + bot.market.quotedAsset + " is stored within the network."); + /* We found where the data is located on the network. */ + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); + callBackFunction(global.DEFAULT_OK_RESPONSE); } catch (err) { logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); diff --git a/server.js b/server.js index 3e6bb2824..0b014d460 100644 --- a/server.js +++ b/server.js @@ -137,6 +137,140 @@ global.NODE_BRANCH_TO_ARRAY = function (node, nodeType) { } } +global.FIND_NODE_IN_NODE_MESH = function (node, nodeType) { + /* + This function scans a node mesh for a certain node type and + returns the first instance found. + */ + let nodeFound + scanNodeMesh(node, nodeType) + return nodeFound + + function scanNodeMesh(startingNode) { + if (startingNode === undefined) { return } + if (nodeFound !== undefined) { return } + + let nodeDefinition = APP_SCHEMA_MAP.get(startingNode.type) + if (nodeDefinition === undefined) { return } + + if (startingNode.type === nodeType) { + nodeFound = startingNode + return + } + + /* We scan through this node children */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + scanNodeMesh(startingNode[property.name]) + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + scanNodeMesh(startingNodePropertyArray[m]) + } + } + break + } + } + } + } + /* We scan parents nodes. */ + if (startingNode.parentNode !== undefined) { + scanNodeMesh(startingNode.parentNode) + } + /* We scan reference parents too. */ + if (startingNode.referenceParent !== undefined) { + scanNodeMesh(startingNode.referenceParent) + } + } +} + +global.NODE_MESH_TO_PATH_ARRAY = function (node, nodeId) { + /* + This function scans a node mesh for a certain node if and + returns an array with the path within that mesh to the + requested node. + */ + let nodeArray = [] + scanNodeMesh(node) + return nodeArray + + function scanNodeMesh(startingNode) { + if (startingNode === undefined) { return } + + let nodeDefinition = APP_SCHEMA_MAP.get(startingNode.type) + if (nodeDefinition === undefined) { return } + + if (startingNode.id === nodeId) { + nodeArray.push(startingNode) + return + } + + /* We scan through this node children */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + scanNodeMesh(startingNode[property.name]) + if (nodeArray.length > 0) { + nodeArray.unshift(startingNode) + return + } + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + scanNodeMesh(startingNodePropertyArray[m]) + if (nodeArray.length > 0) { + nodeArray.unshift(startingNode) + return + } + } + } + break + } + } + } + } + /* We scan parents nodes. */ + if (startingNode.parentNode !== undefined) { + scanNodeMesh(startingNode.parentNode) + if (nodeArray.length > 0) { + nodeArray.unshift(startingNode) + return + } + } + /* We scan reference parents too. */ + if (startingNode.referenceParent !== undefined) { + scanNodeMesh(startingNode.referenceParent) + if (nodeArray.length > 0) { + nodeArray.unshift(startingNode) + return + } + } + } +} + +global.FIND_NODE_IN_NODE_ARRAY = function (nodeArray, nodeType) { + for (let i=0; i < nodeArray.length; i++) { + let node = nodeArray[i] + if (node.type === nodeType) { + return node + } + } +} + + process.on('uncaughtException', function (err) { console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message) console.log('[ERROR] Task Server -> server -> uncaughtException -> err.stack = ' + err.stack) From 9dc4f972e772529d72957e875c78b97463c5d2e9 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 1 Sep 2020 14:32:11 +0200 Subject: [PATCH 091/112] Backtesting working again --- Commons.js | 21 ++++++++++++------- IndicatorOutput.js | 7 +++++-- ProcessOutput.js | 52 ++++++++++++++++++++++++++++++---------------- server.js | 6 ++++-- 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/Commons.js b/Commons.js index 3f5642205..a93cd8d85 100644 --- a/Commons.js +++ b/Commons.js @@ -95,26 +95,31 @@ return } - if (outputDatasetNode.referenceParent.parentNode.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + let botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Indicator Bot') + if (botNode === undefined) { + botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Trading Bot') + } + if (botNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = " ); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); + if (botNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined." ); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); + let dataMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Data Mine') + if (dataMineNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine."); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode.parentNode)); + if (dataMineNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined." ); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } diff --git a/IndicatorOutput.js b/IndicatorOutput.js index 988acda77..1f8d20691 100644 --- a/IndicatorOutput.js +++ b/IndicatorOutput.js @@ -130,8 +130,11 @@ contextSummary.dataset = outputDatasetNode.referenceParent.config.codeName contextSummary.product = outputDatasetNode.referenceParent.parentNode.config.codeName - contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName - contextSummary.dataMine = outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.config.codeName + + let botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Indicator Bot') + contextSummary.bot = botNode.config.codeName + let dataMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Data Mine') + contextSummary.dataMine = dataMineNode.config.codeName let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, currentDay, callBackFunction) commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, timeFrameLabel, currentDay, callBackFunction) diff --git a/ProcessOutput.js b/ProcessOutput.js index 3496ffe15..5ac15c864 100644 --- a/ProcessOutput.js +++ b/ProcessOutput.js @@ -25,6 +25,14 @@ for (let j = 0; j < outputDatasets.length; j++) { let outputDataset = outputDatasets[j] + let botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Indicator Bot') + if (botNode === undefined) { + botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Trading Bot') + } + if (botNode === undefined) { + botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Sensor Bot') + } + /* Some validations to verify that everything is in place. */ if (outputDataset.referenceParent !== undefined) { if (outputDataset.referenceParent.config.codeName === undefined) { @@ -38,20 +46,20 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + if (botNode !== undefined) { + if (botNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(botNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); + if (botNode.parentNode !== undefined) { + if (botNode.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(botNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(botNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } @@ -73,8 +81,8 @@ /* All good, lets emit the event. */ - let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName - let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName + let dataMine = botNode.parentNode.config.codeName + let botName = botNode.config.codeName let product = outputDataset.referenceParent.parentNode.config.codeName let dataset = outputDataset.referenceParent.config.codeName @@ -104,6 +112,14 @@ for (let j = 0; j < outputDatasets.length; j++) { let outputDataset = outputDatasets[j] + let botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Indicator Bot') + if (botNode === undefined) { + botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Trading Bot') + } + if (botNode === undefined) { + botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Sensor Bot') + } + /* Some validations to verify that everything is in place. */ if (outputDataset.referenceParent !== undefined) { if (outputDataset.referenceParent.config.codeName === undefined) { @@ -115,18 +131,18 @@ logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); throw (global.DEFAULT_FAIL_RESPONSE); } - if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + if (botNode !== undefined) { + if (botNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(botNode)); throw (global.DEFAULT_FAIL_RESPONSE); } - if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); + if (botNode.parentNode !== undefined) { + if (botNode.parentNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(botNode.parentNode)); throw (global.DEFAULT_FAIL_RESPONSE); } } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(botNode)); throw (global.DEFAULT_FAIL_RESPONSE); } } else { @@ -144,8 +160,8 @@ /* All good, lets emit the event. */ - let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName - let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName + let dataMine = botNode.parentNode.config.codeName + let botName = botNode.config.codeName let product = outputDataset.referenceParent.parentNode.config.codeName let dataset = outputDataset.referenceParent.config.codeName diff --git a/server.js b/server.js index 0b014d460..b72ed1361 100644 --- a/server.js +++ b/server.js @@ -127,8 +127,10 @@ global.NODE_BRANCH_TO_ARRAY = function (node, nodeType) { break case 'array': { let startingNodePropertyArray = startingNode[property.name] - for (let m = 0; m < startingNodePropertyArray.length; m++) { - scanNodeBranch(startingNodePropertyArray[m]) + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + scanNodeBranch(startingNodePropertyArray[m]) + } } break } From f22a7f9c17c458b73ae03ca2779a494e75290682 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 1 Sep 2020 20:57:10 +0200 Subject: [PATCH 092/112] Fixes the Live Trading problem when the size is not in sync with the exchange --- LowFrequencyTrading/TradingOrders.js | 53 ++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index fcdffdf73..1231cf853 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -503,7 +503,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) } - function recalculateSizePlaced(){ + function recalculateSizePlaced() { /* Recaulculate the Size Placed Based on the newly calculated Quoted Asset Size. */ @@ -723,12 +723,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM function syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) { + let previousBaseAssetSize = tradingEngineOrder.orderBaseAsset.size.value let previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value + actualSizeCalculation() actualRateCalculation() percentageFilledCalculation() feesPaidCalculation() @@ -746,6 +748,51 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM return + function actualSizeCalculation() { + /* + When we submit the order to the exchange, we do it specifying the order size + in Base Asset. The exchange might take that size or might change it a little + bit if for example, it does not accept as many decimals as we are sending. + If we identify the situation in which the size accepted is different than the + size we have accounted for, we need to make several adjustments so that the + accounting syncronizes with reality. + */ + if (tradingEngineOrder.orderBaseAsset.size.value === order.amount) { return } + /* + We will sync the Base Asset size, and recalculate the Quoted Asset size too. + */ + tradingEngineOrder.orderBaseAsset.size.value = order.amount + tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value + + tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10) + tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) + /* + Finaly we need to unaccount the previous size placed and correctly account + for the the new sizes we know have. + */ + tradingEngineStage.stageBaseAsset.sizePlaced.value = + tradingEngineStage.stageBaseAsset.sizePlaced.value - + previousBaseAssetSize + + tradingEngineStage.stageBaseAsset.sizePlaced.value = + tradingEngineStage.stageBaseAsset.sizePlaced.value + + tradingEngineOrder.orderBaseAsset.size.value + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value - + previousQuotedAssetSize + + tradingEngineStage.stageQuotedAsset.sizePlaced.value = + tradingEngineStage.stageQuotedAsset.sizePlaced.value + + tradingEngineOrder.orderQuotedAsset.size.value + + tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10) + tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) + + previousBaseAssetSize = tradingEngineOrder.orderBaseAsset.size.value + previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value + } + function actualRateCalculation() { /* Actual Rate Calculation: Let's remember that the exchange may not take the rate @@ -772,9 +819,11 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + + if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) {return} /* Now we know the Actual Rate at which the order was filled. Since the actual rate - might not be the same as the Rate we defined for the order, we need to syncronize + is not the same as the Rate we defined for the order, we need to syncronize the Order Size for Quoted Asset since it was calculated with the Order Size that we now know it is not the once really used at the exchange. We will recalculate the Order Size in Quoted Asset and not in Base Asset, since the Order Size in Base Asset From ee641865bdd125aa7277bcc3a97c749d6f008fad Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 2 Sep 2020 11:23:36 +0200 Subject: [PATCH 093/112] Logging Improved --- LowFrequencyTrading/ExchangeAPI.js | 6 +- LowFrequencyTrading/TradingOrders.js | 108 ++++++++++++++++----------- LowFrequencyTrading/TradingSystem.js | 4 +- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index 4586bb79c..29fbc0100 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -66,7 +66,7 @@ let order = await exchange.fetchOrder(orderId, symbol) return order } catch (err) { - tradingSystem.errors.push([tradingSystemOrder.id, err.message]) + tradingSystem.errors.push([tradingSystemOrder.id, "getOrder -> Error = " + err.message]) logError("getOrder -> Error = " + err.message); } } @@ -123,7 +123,7 @@ let order = await (exchange.createOrder(symbol, type, side, amount, price)) return order.id } catch (err) { - tradingSystem.errors.push([tradingSystemOrder.id, err.message]) + tradingSystem.errors.push([tradingSystemOrder.id, "createOrder -> Error = " + err.message]) logError("createOrder -> Error = " + err.message); } } @@ -147,7 +147,7 @@ let order = await exchange.cancelOrder(orderId, symbol) return true } catch (err) { - tradingSystem.errors.push([tradingSystemOrder.id, err.message]) + tradingSystem.errors.push([tradingSystemOrder.id, "cancelOrder -> Error = " + err.message]) logError("cancelOrder -> Error = " + err.message); } } diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 1231cf853..ddd7d8c67 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -129,11 +129,12 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM */ if (tradingSystemOrder.config.spawnMultipleOrders !== true) { if (tradingEngineOrder.lock.value === 'Closed') { + tradingSystem.infos.push('Order ' + tradingSystemOrder.name + ' skipped because lock was closed.') continue } } /* Check if we need to Create this Order */ - let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + let situationName = await checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) if (situationName !== undefined) { /* Open a new order */ @@ -143,14 +144,23 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM break } case 'Open': { + /* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */ + if (tradingEngine.current.episode.cycle.value === 'Second') { continue } + /* Simulate Events that happens at the Exchange, if needed. */ simulateExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) /* Check Events that happens at the Exchange, if needed. */ - await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) + let allGood = await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) - /* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */ - if (tradingEngine.current.episode.cycle.value === 'Second') { continue } + if (allGood !== true) { + /* + For some reason we could not check the order at the exchange, so we will not even check if we + need to cancel it, since we could end up with inconsisten information at the accounting level. + */ + tradingSystem.warnings.push(tradingSystemOrder.id, 'Skipping Cancel Event Checking because cheking the Exchange Events failed.') + return + } /* Check if we need to cancel the order */ await checkCancelOrderEvent(tradingEngineStage, executionAlgorithm, executionNode, tradingEngineOrder, tradingSystemOrder) @@ -185,7 +195,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') { /* Check if we need to Cancel this Order */ - let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + let situationName = await checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) if (situationName !== undefined) { /* Simulate Order Cancelation, if needed. */ @@ -194,7 +204,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Cancel the order at the Exchange, if needed. */ await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event') - updateEndsWithCycle(tradingEngineOrder) + await updateEndsWithCycle(tradingEngineOrder) } } } @@ -232,15 +242,21 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM async function tryToOpenOrder(tradingEngineStage, executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) { - calculateOrderRate() - calculateOrderSize() + await calculateOrderRate() + await calculateOrderSize() /* Check Size: We are not going to create Orders which size is equal or less to zero. */ - if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) { return } + if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) { + tradingSystem.warnings.push(tradingSystemOrder.id, 'Could not open this order because its size would be zero.') + return + } /* Place Order at the Exchange, if needed. */ let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) - if (result !== true) { return } + if (result !== true) { + tradingSystem.warnings.push(tradingSystemOrder.id, 'Could not open this order because something failed placing the order at the Exchange.') + return + } /* Update Stage Placed Size */ tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value + tradingEngineOrder.orderBaseAsset.size.value @@ -264,7 +280,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineOrder.algorithmName.value = executionAlgorithm.name tradingEngineOrder.situationName.value = situationName - function calculateOrderRate() { + async function calculateOrderRate() { /* Optional Rate Definition */ if (tradingSystemOrder.type === 'Limit Buy Order' || tradingSystemOrder.type === 'Limit Sell Order') { @@ -289,7 +305,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } - function calculateOrderSize() { + async function calculateOrderSize() { /* Validate that this config exists */ if (executionAlgorithm.config.percentageOfStageTargetSize === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.config.percentageOfStageTargetSize === undefined', executionAlgorithm) } if (isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true', executionAlgorithm) } @@ -663,7 +679,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - if (order === undefined) { return } + if (order === undefined) { return false } const AT_EXCHANGE_STATUS = { OPEN: 'open', @@ -681,7 +697,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - updateEndsWithCycle(tradingEngineOrder) + await updateEndsWithCycle(tradingEngineOrder) } if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { @@ -692,7 +708,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - updateEndsWithCycle(tradingEngineOrder) + await updateEndsWithCycle(tradingEngineOrder) } if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { @@ -703,7 +719,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - updateEndsWithCycle(tradingEngineOrder) + await updateEndsWithCycle(tradingEngineOrder) } /* @@ -712,16 +728,18 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM the Fees Paid and many other thing we need to account for. */ if (order.filled > 0) { - syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) } /* Forced Cancellation Check */ if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') { await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Closing Stage') } + + return true } - function syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) { + async function syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) { let previousBaseAssetSize = tradingEngineOrder.orderBaseAsset.size.value let previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value @@ -730,13 +748,13 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value - actualSizeCalculation() - actualRateCalculation() - percentageFilledCalculation() - feesPaidCalculation() - sizeFilledCalculation() + await actualSizeCalculation() + await actualRateCalculation() + await percentageFilledCalculation() + await feesPaidCalculation() + await sizeFilledCalculation() - doTheAccounting( + await doTheAccounting( tradingEngineStage, tradingSystemOrder, tradingEngineOrder, @@ -748,11 +766,11 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM return - function actualSizeCalculation() { + async function actualSizeCalculation() { /* When we submit the order to the exchange, we do it specifying the order size in Base Asset. The exchange might take that size or might change it a little - bit if for example, it does not accept as many decimals as we are sending. + bit if for example, if it does not accept as many decimals as we are sending. If we identify the situation in which the size accepted is different than the size we have accounted for, we need to make several adjustments so that the accounting syncronizes with reality. @@ -768,7 +786,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10) /* Finaly we need to unaccount the previous size placed and correctly account - for the the new sizes we know have. + for the the new sizes we now have. */ tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value - @@ -793,7 +811,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value } - function actualRateCalculation() { + async function actualRateCalculation() { /* 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. @@ -820,7 +838,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) {return} + if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return } /* 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 @@ -851,13 +869,13 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) } - function percentageFilledCalculation() { + async function percentageFilledCalculation() { /* Percentage Filled Calculation */ tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) } - function feesPaidCalculation() { + async function feesPaidCalculation() { /* The exchange fees are taken from the Base Asset or the Quoted Asset depending if we are buying or selling. Within the order information received we can not see the fees paid so we need to @@ -865,17 +883,17 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM */ switch (tradingEngineOrder.type) { case 'Market Order': { - applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.taker) + await applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.taker) break } case 'Limit Order': { - applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.maker) + await applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.maker) break } } } - function sizeFilledCalculation() { + async function sizeFilledCalculation() { /* 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 @@ -896,7 +914,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } - function doTheAccounting( + async function doTheAccounting( tradingEngineStage, tradingSystemOrder, tradingEngineOrder, @@ -906,10 +924,10 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM previousQuotedAssetFeesPaid ) { - updateStageAssets() - updateBalances() + await updateStageAssets() + await updateBalances() - function updateStageAssets() { + async function updateStageAssets() { /* Stage Base Asset: Undo the previous accounting */ tradingEngineStage.stageBaseAsset.sizeFilled.value = tradingEngineStage.stageBaseAsset.sizeFilled.value - @@ -953,7 +971,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineStage.stageQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.feesPaid.value, 10) } - function updateBalances() { + async function updateBalances() { /* Balances Update */ switch (true) { /* @@ -1089,13 +1107,13 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (order === undefined) { return } - syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) - recalculateStageSize(tradingEngineStage, tradingEngineOrder) + await recalculateStageSize(tradingEngineStage, tradingEngineOrder) } } - function recalculateStageSize(tradingEngineStage, tradingEngineOrder) { + async function recalculateStageSize(tradingEngineStage, tradingEngineOrder) { /* Since the order is Cancelled, we need to adjust the stage sizePlaced. Remember that the Stage Size Placed accumulates for each asset, the order size placed at the exchange. A cancelation means that @@ -1123,7 +1141,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10) } - function applyFeePercentage(tradingEngineOrder, tradingSystemOrder, feePercentage) { + async function applyFeePercentage(tradingEngineOrder, tradingSystemOrder, feePercentage) { switch (true) { case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': { /* @@ -1151,7 +1169,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10) } - function checkOrderEvent(event, order, executionAlgorithm, executionNode) { + async function checkOrderEvent(event, order, executionAlgorithm, executionNode) { if (event !== undefined) { for (let k = 0; k < event.situations.length; k++) { let situation = event.situations[k] @@ -1183,7 +1201,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM } } - function updateEndsWithCycle(tradingEngineOrder) { + async function updateEndsWithCycle(tradingEngineOrder) { tradingEngineOrder.end.value = tradingEngine.current.episode.cycle.end.value } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 36b6b40e4..05248d1be 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -230,7 +230,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } try { - logger.write(MODULE_NAME, '[INFO] evalCondition -> code = ' + code) + logger.write(MODULE_NAME, '[INFO] evalCondition -> ' + node.name + ' -> code = ' + code) value = eval(code) } catch (err) { /* @@ -270,7 +270,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM let error try { - logger.write(MODULE_NAME, '[INFO] evalFormula -> code = ' + node.code) + logger.write(MODULE_NAME, '[INFO] evalFormula -> ' + node.name + ' -> code = ' + node.code) value = eval(node.code) } catch (err) { /* From ac7890b13ae830b785e23b32ac7d998f32bdc47e Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 2 Sep 2020 17:31:50 +0200 Subject: [PATCH 094/112] Logging IMproved --- LowFrequencyTrading/ExchangeAPI.js | 12 +++++++++--- LowFrequencyTrading/TradingOrders.js | 23 ++++++++++++++--------- LowFrequencyTrading/TradingStages.js | 2 +- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index 29fbc0100..ad6533687 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -50,11 +50,11 @@ async function getOrder(tradingSystemOrder, tradingEngineOrder) { let orderId = tradingEngineOrder.exchangeId.value + const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset /* Basic Logging */ - logInfo("getOrder -> Entering function. orderId = " + orderId); - - const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset + logInfo("getOrder -> orderId = " + orderId) + logInfo("getOrder -> symbol = " + symbol) /* Basic Validations */ if (exchange.has['fetchOrder'] === false) { @@ -64,6 +64,8 @@ try { let order = await exchange.fetchOrder(orderId, symbol) + + logInfo("getOrder -> Response from the Exchange: " + JSON.stringify(order)); return order } catch (err) { tradingSystem.errors.push([tradingSystemOrder.id, "getOrder -> Error = " + err.message]) @@ -121,6 +123,8 @@ 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) { tradingSystem.errors.push([tradingSystemOrder.id, "createOrder -> Error = " + err.message]) @@ -145,6 +149,8 @@ try { let order = await exchange.cancelOrder(orderId, symbol) + + logInfo("cancelOrder -> Response from the Exchange: " + JSON.stringify(order)); return true } catch (err) { tradingSystem.errors.push([tradingSystemOrder.id, "cancelOrder -> Error = " + err.message]) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index ddd7d8c67..a40021d99 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -129,7 +129,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM */ if (tradingSystemOrder.config.spawnMultipleOrders !== true) { if (tradingEngineOrder.lock.value === 'Closed') { - tradingSystem.infos.push('Order ' + tradingSystemOrder.name + ' skipped because lock was closed.') + tradingSystem.infos.push(['Order ' + tradingSystemOrder.name + ' skipped because lock was closed.']) continue } } @@ -158,7 +158,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM For some reason we could not check the order at the exchange, so we will not even check if we need to cancel it, since we could end up with inconsisten information at the accounting level. */ - tradingSystem.warnings.push(tradingSystemOrder.id, 'Skipping Cancel Event Checking because cheking the Exchange Events failed.') + if (tradingSystemOrder.cancelOrderEvent !== undefined) { + tradingSystem.warnings.push([tradingSystemOrder.cancelOrderEvent.id, 'Skipping Cancel Event Checking because cheking the order at the exchange failed.']) + } return } @@ -246,16 +248,16 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM await calculateOrderSize() /* Check Size: We are not going to create Orders which size is equal or less to zero. */ - if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) { - tradingSystem.warnings.push(tradingSystemOrder.id, 'Could not open this order because its size would be zero.') - return + if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) { + tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not open this order because its size would be zero.']) + return } /* Place Order at the Exchange, if needed. */ let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) - if (result !== true) { - tradingSystem.warnings.push(tradingSystemOrder.id, 'Could not open this order because something failed placing the order at the Exchange.') - return + if (result !== true) { + tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not open this order because something failed placing the order at the Exchange.']) + return } /* Update Stage Placed Size */ @@ -679,7 +681,10 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - if (order === undefined) { return false } + if (order === undefined) { + tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not verify the status of this order at the exchange.']) + return false + } const AT_EXCHANGE_STATUS = { OPEN: 'open', diff --git a/LowFrequencyTrading/TradingStages.js b/LowFrequencyTrading/TradingStages.js index df7848a91..b3465bf18 100644 --- a/LowFrequencyTrading/TradingStages.js +++ b/LowFrequencyTrading/TradingStages.js @@ -893,7 +893,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM when we see at the exchange that orders were filled. Note that the comparison is made with a Rounding Factor in order to avoid rounding problems. */ - const ROUNDING_ERROR_CORRECTION_FACTOR = 1.0000001 + const ROUNDING_ERROR_CORRECTION_FACTOR = 1.001 switch (tradingEngineStage.stageDefinedIn.value) { case 'Base Asset': { From 8d3b4750ef3ec60b0fc13b1a8d90d57dc3e7f08a Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 3 Sep 2020 09:55:28 +0200 Subject: [PATCH 095/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index a40021d99..04c320e0f 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -719,8 +719,17 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Close this Order */ tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' + /* + We must be carefull here not to overide an already defined exitType. It can happen + for instance that the order was cancellerd from the bot, but veryfing the cancellation + was not possible because of a connection to the exchange problem. In that case + the exit type was defined but the order was kept open until the verification could be done. + */ + if (tradingEngineOrder.exitType.value === tradingEngineOrder.exitType.config.initialValue) { + tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' + } + /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 @@ -1096,12 +1105,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Check if we can cancel the order at the Exchange. */ let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder) if (result === true) { - /* Close this Order */ - tradingEngineOrder.status.value = 'Closed' - tradingEngineOrder.exitType.value = exitType - /* Initialize this */ - tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + /* At this point we know which is the exit type for this order */ + tradingEngineOrder.exitType.value = exitType /* Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. @@ -1110,7 +1116,21 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) - if (order === undefined) { return } + if (order === undefined) { + tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not verify the status of this order at the exchange, and syncronize the accounting.']) + return false + } + + /* + Close this Order. Note that we are not closing the order until we have the exchange + response with the order details that we can use to syncronize with our accoounting. + Otherwise if the connection to the exchange fails, we would have a closed order not + accounted in any way. + */ + tradingEngineOrder.status.value = 'Closed' + + /* Initialize this */ + tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) From ba4f229b43f1b513b27ccadb039939ab4d3a26b0 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 4 Sep 2020 17:48:00 +0200 Subject: [PATCH 096/112] Debugging Improved --- LowFrequencyTrading/TradingOrders.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index 04c320e0f..d09b2cd60 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -607,6 +607,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 updateEndsWithCycle(tradingEngineOrder) + tradingSystem.infos.push([tradingSystemOrder.id, 'percentageFilledSimulation -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) } } } @@ -703,6 +704,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 await updateEndsWithCycle(tradingEngineOrder) + tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) } if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { @@ -714,6 +716,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 await updateEndsWithCycle(tradingEngineOrder) + tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) } if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { @@ -734,6 +737,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 await updateEndsWithCycle(tradingEngineOrder) + tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) } /* @@ -1081,6 +1085,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + await updateEndsWithCycle(tradingEngineOrder) + tradingSystem.infos.push([tradingSystemOrder.id, 'simulateCancelOrder -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) + recalculateStageSize(tradingEngineStage, tradingEngineOrder) } @@ -1132,6 +1139,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 + await updateEndsWithCycle(tradingEngineOrder) + tradingSystem.infos.push([tradingSystemOrder.id, 'exchangeCancelOrder -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) + await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) await recalculateStageSize(tradingEngineStage, tradingEngineOrder) From 4944b0b459eeda3dc6d5c7dd0cdf572f66d3e470 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 5 Sep 2020 11:49:31 +0200 Subject: [PATCH 097/112] Update TradingOrders.js --- LowFrequencyTrading/TradingOrders.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/LowFrequencyTrading/TradingOrders.js b/LowFrequencyTrading/TradingOrders.js index d09b2cd60..76d1cabe0 100644 --- a/LowFrequencyTrading/TradingOrders.js +++ b/LowFrequencyTrading/TradingOrders.js @@ -695,7 +695,6 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Status Checks */ if (order.remaining === 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { - /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = 'Filled' @@ -705,24 +704,29 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM await updateEndsWithCycle(tradingEngineOrder) tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) + + await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + return } - if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { + if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) { /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = 'Closed at the Exchange' - /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 await updateEndsWithCycle(tradingEngineOrder) tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) - } - if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { + await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + await recalculateStageSize(tradingEngineStage, tradingEngineOrder) + return + } + + if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { /* Close this Order */ tradingEngineOrder.status.value = 'Closed' - /* We must be carefull here not to overide an already defined exitType. It can happen for instance that the order was cancellerd from the bot, but veryfing the cancellation @@ -732,12 +736,15 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM if (tradingEngineOrder.exitType.value === tradingEngineOrder.exitType.config.initialValue) { tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' } - /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 await updateEndsWithCycle(tradingEngineOrder) tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) + + await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) + await recalculateStageSize(tradingEngineStage, tradingEngineOrder) + return } /* @@ -749,7 +756,10 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) } - /* Forced Cancellation Check */ + /* + Forced Cancellation Check: Here we check if we need to cancel this order because the + stage is closing. + */ if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') { await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Closing Stage') } @@ -1085,7 +1095,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM /* Initialize this */ tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1 - await updateEndsWithCycle(tradingEngineOrder) + updateEndsWithCycle(tradingEngineOrder) tradingSystem.infos.push([tradingSystemOrder.id, 'simulateCancelOrder -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value]) recalculateStageSize(tradingEngineStage, tradingEngineOrder) From 96c297735a92145544678c868edc76ffdf3330f3 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 10 Sep 2020 12:28:44 +0200 Subject: [PATCH 098/112] Update Session.js --- Session.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Session.js b/Session.js index 88682d775..3d823a2ca 100644 --- a/Session.js +++ b/Session.js @@ -241,6 +241,12 @@ return false } bot.SESSION.parameters.timeFrame.config.value = getTimeFrameFromLabel(bot.SESSION.parameters.timeFrame.config.label) + if (bot.SESSION.parameters.timeFrame.config.value === undefined) { + let errorMessage = "Config error: label value not recognized. Try 01-min or 01-hs for example." + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) + bot.sessionError(bot.SESSION.parameters.timeFrame, errorMessage) + return false + } /* Session Base Asset */ if (bot.SESSION.parameters.sessionBaseAsset === undefined) { From 40172877e1e3287a049d769284b5a0504a82e8d0 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 10 Sep 2020 22:10:26 +0200 Subject: [PATCH 099/112] IMPORTANT --- DebugLog.js | 2 +- LowFrequencyTrading/TradingSimulation.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DebugLog.js b/DebugLog.js index e7c874e9b..a1ef69a37 100644 --- a/DebugLog.js +++ b/DebugLog.js @@ -101,7 +101,7 @@ exports.newDebugLog = function newDebugLog() { writeLog(); /* This is the implementation of the mechanism to auto-mantain logs. */ - if (contentToPersist.indexOf('[ERROR]') < 0 && contentToPersist.indexOf('[PERSIST]') < 0) { + if (contentToPersist.indexOf('[ERROR]') < 0 && contentToPersist.indexOf('[PERSIST]') < 0 && contentToPersist.indexOf('[IMPORTANT]') < 0) { thisObject.bot.LOGS_TO_DELETE_QUEUE.push(filePath + '/' + fileName) } if (thisObject.bot.LOGS_TO_DELETE_QUEUE.length > thisObject.bot.DELETE_QUEUE_SIZE) { diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index fe81e4a1e..0ee2844a7 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -73,6 +73,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin meaning that the dataSet needs to be updated with more up-to-date data. */ bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.') + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Data is not up-to-date enough. Stopping the Session now. ' ) } return } } else { @@ -213,6 +214,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin closeEpisode('Final Datetime Reached') breakLoop = true bot.SESSION.stop('Final Datetime Reached') + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Final Datetime Reached. Stopping the Session now. ' ) } return } @@ -220,6 +222,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin closeEpisode('Min or Max Balance Reached') breakLoop = true bot.SESSION.stop('Min or Max Balance Reached') + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Min or Max Balance Reached. Stopping the Session now. ' ) } return } } From 6fc350a8fdbb52201a4dd11676e53ef2d4b21ce3 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 11 Sep 2020 15:19:24 +0200 Subject: [PATCH 100/112] Data Dependencies finished --- DataDependencies.js | 6 ++---- IndicatorOutput.js | 3 ++- LowFrequencyTrading/TradingProcess.js | 3 ++- LowFrequencyTrading/TradingSimulation.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DataDependencies.js b/DataDependencies.js index 44287c682..88e57be8e 100644 --- a/DataDependencies.js +++ b/DataDependencies.js @@ -19,13 +19,11 @@ return thisObject; function initialize(callBackFunction) { - try { /* Basic Valdidations */ if (bot.processNode.referenceParent.processDependencies !== undefined) { - if (bot.processNode.referenceParent.processDependencies.dataDependencies !== undefined) { - thisObject.nodeArray = bot.processNode.referenceParent.processDependencies.dataDependencies - } else { + thisObject.nodeArray = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency') + if (thisObject.nodeArray.length === 0) { logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> It is not possible to not have data dependencies at all."); callBackFunction(global.DEFAULT_OK_RESPONSE) return diff --git a/IndicatorOutput.js b/IndicatorOutput.js index 1f8d20691..d7f258b6b 100644 --- a/IndicatorOutput.js +++ b/IndicatorOutput.js @@ -53,7 +53,8 @@ } /* The first phase here is about checking that we have everything we need at the definition level. */ - let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency') + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset') diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 40f6dce13..0a4c584ec 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -504,7 +504,8 @@ let mainDependency = {} /* The first phase here is about checking that we have everything we need at the definition level. */ - let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency') + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset') diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 0ee2844a7..7b74f53a8 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -316,7 +316,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin 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. */ - let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency') /* Finding the Current Element on Market Files */ if (bot.processingDailyFiles) { From 66898ca2d276a00a19d937836225ead664ad8342 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 12 Sep 2020 09:54:28 +0200 Subject: [PATCH 101/112] Update TradingSimulation.js --- LowFrequencyTrading/TradingSimulation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 7b74f53a8..a1af9db6e 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -229,7 +229,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin function checkIfWeNeedToStopAfterBothCycles() { if (checkNextCandle() === false) { - updateEpisode('All Available Candles Processed') + updateEpisode('All Candles Processed') breakLoop = true return } From 2d1fe61aafc974e4329b319ca6e86a2e1b88297f Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 12 Sep 2020 14:22:52 +0200 Subject: [PATCH 102/112] Session Stopping --- LowFrequencyTrading/TradingProcess.js | 107 +++++++++++++------------- Session.js | 24 ++++-- TradingBot.js | 1 + 3 files changed, 72 insertions(+), 60 deletions(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 0a4c584ec..5960be476 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -124,7 +124,7 @@ This is the Data Structure used at the Simulation with all indicator data. We start creating it right here. */ - let chart = {} + let chart = {} /* 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 @@ -146,8 +146,8 @@ } else { /* We are processing Daily Files */ do { - if (checkStopTaskGracefully() === false) {break} - if (checkStopProcessing() === false) {break} + if (checkStopTaskGracefully() === false) { break } + if (checkStopProcessing() === false) { break } /* We update the Trading Process Date with the date calculated at the simulation. @@ -170,15 +170,15 @@ /* If for any reason the session was stopped, we will break this loop and exit the process. */ - if (bot.STOP_SESSION === true) {break} + if (bot.STOP_SESSION === 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) + if (checkStopHeadOfTheMarket() === false) { break } + } + while (true) } checkIfSessionMustStop() @@ -289,7 +289,7 @@ for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) { let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]; - + if (datasetModule.node.config.codeName !== "Single-File") { continue } @@ -302,10 +302,10 @@ let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName; /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ - let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { - throw(response.err) + throw (response.err) } let dataFile = JSON.parse(response.text); @@ -351,12 +351,12 @@ let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel; /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ - let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { - throw(response.err) + throw (response.err) } - + let dataFile = JSON.parse(response.text); let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record) dataFiles.set(dependency.id, trimmedDataFile); @@ -402,7 +402,7 @@ /* Telling the world we are alive and doing well and which date we are processing right now. */ let processingDateString = tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(tradingProcessDate.getUTCDate(), 2); bot.processHeartBeat(processingDateString) - + /* We will iterate through all posible timeFrames. */ @@ -456,10 +456,10 @@ let fileName = "Data.json"; /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ - let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) - + let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { - throw(response.err) + throw (response.err) } let dataFile = JSON.parse(response.text); @@ -471,7 +471,7 @@ } } - function checkStopHeadOfTheMarket() { + 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 / @@ -556,13 +556,13 @@ currentTimeFrame, currentTimeFrameLabel, tradingProcessDate - ) + ) /* 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. */ - bot.FIRST_EXECUTION = false + bot.FIRST_EXECUTION = false bot.RESUME = false } @@ -571,7 +571,7 @@ await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) await writeDataRanges() - + if (currentTimeFrame > global.dailyFilePeriods[0][0]) { await writeMarketStatusReport(currentTimeFrameLabel) } else { @@ -580,30 +580,30 @@ async function writeDataRanges() { for ( - let outputDatasetIndex = 0; - outputDatasetIndex < outputDatasets.length; - outputDatasetIndex++ - ) { + let outputDatasetIndex = 0; + outputDatasetIndex < outputDatasets.length; + outputDatasetIndex++ + ) { let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; await writeDataRange(productCodeName); } - + async function writeDataRange(productCodeName) { let dataRange = { begin: contextVariables.dateBeginOfMarket.valueOf(), end: tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS } - + let fileContent = JSON.stringify(dataRange); let fileName = '/Data.Range.json'; let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName; - + let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n') - + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { - throw(response.err) + throw (response.err) } - + /* Raise the event that the Data Range was Updated. */ @@ -611,59 +611,59 @@ let event = { dateRange: dataRange } - + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) } } - + async function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) { - + for ( - let outputDatasetIndex = 0; - outputDatasetIndex < outputDatasets.length; - outputDatasetIndex++ - ) { + let outputDatasetIndex = 0; + outputDatasetIndex < outputDatasets.length; + outputDatasetIndex++ + ) { let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; - await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily') - await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Market') - + await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily') + await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Market') + async function writeTimeFramesFile(currentTimeFrameLabel, productCodeName, processType) { - + let timeFramesArray = [] timeFramesArray.push(currentTimeFrameLabel) - + let fileContent = JSON.stringify(timeFramesArray) let fileName = '/Time.Frames.json'; - + let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + processType + fileName; - + let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n') if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { - throw(response.err) + throw (response.err) } } } } - async function writeDailyStatusReport( currentTimeFrameLabel) { + async function writeDailyStatusReport(currentTimeFrameLabel) { let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName let thisReport = statusDependencies.statusReports.get(reportKey) - + thisReport.file.lastExecution = bot.currentDaytime thisReport.file.lastFile = tradingProcessDate thisReport.file.simulationState = bot.simulationState thisReport.file.timeFrames = currentTimeFrameLabel await thisReport.asyncSave() } - + async function writeMarketStatusReport(currentTimeFrameLabel) { let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName let thisReport = statusDependencies.statusReports.get(reportKey); - + thisReport.file.lastExecution = bot.processDatetime thisReport.file.simulationState = bot.simulationState thisReport.file.timeFrames = currentTimeFrameLabel - + logger.newInternalLoop(bot.codeName, bot.process, tradingProcessDate) await thisReport.asyncSave() } @@ -675,6 +675,7 @@ /* Backtests needs only one execution of this process to complete. */ + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] checkIfSessionMustStop -> Backtesting Session Finished. Stopping the Session now. ') } bot.SESSION.stop('Backtesting Session Finished.') } } @@ -714,14 +715,14 @@ datasetModule.getTextFile(filePath, fileName, onFileReceived); function onFileReceived(err, text) { - + let response = { err: err, text: text } resolve(response) } - }); + }); return promise } diff --git a/Session.js b/Session.js index 3d823a2ca..64b4e0c30 100644 --- a/Session.js +++ b/Session.js @@ -54,7 +54,11 @@ function onSessionRan(message) { try { - if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { return } // This happens when the UI is reloaded, the session was running and tries to run it again. + /* This happens when the UI is reloaded, the session was running and tries to run it again. */ + if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { + parentLogger.write(MODULE_NAME, "[WARN] onSessionRan -> Event receive to run the Session while it was already running. ") + return + } /* We are going to run the Definition comming at the event. */ bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem) @@ -96,6 +100,7 @@ bot.STOP_SESSION = false } else { bot.STOP_SESSION = true + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] onSessionRan -> Stopping the Session now. ') } } socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is starting.") @@ -109,10 +114,11 @@ } function stopSession(commandOrigin) { - + socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin) socialBotsModule.finalize() bot.STOP_SESSION = true + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] stopSession -> Stopping the Session now. ') } sessionInfo(bot.SESSION, commandOrigin) } @@ -128,7 +134,7 @@ if (config.folderName === undefined) { bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.SESSION.id } else { - bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName + bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName } } } @@ -159,14 +165,14 @@ } else { /* Check that we received valid dates */ if (bot.SESSION.type === 'Backtesting Session') { - if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()) { + if (isNaN(new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()) { let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date." parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) bot.sessionError(bot.SESSION.parameters, errorMessage) return false } } - if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf()) { + if (isNaN(new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf()) { let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date." parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage) bot.sessionError(bot.SESSION.parameters, errorMessage) @@ -205,14 +211,14 @@ /* Initial Datetime */ if (bot.SESSION.type === 'Backtesting Session') { - if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined ) { + if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined) { bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault } else { bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf() } } else { /* Non backtest session can start from the past only if explicitly configured that way */ - if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined || bot.SESSION.parameters.timeRange.config.allowStartingFromThePast !== true ) { + if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined || bot.SESSION.parameters.timeRange.config.allowStartingFromThePast !== true) { bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault } else { bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf() @@ -393,6 +399,7 @@ if (global.STOP_TASK_GRACEFULLY === true) { bot.STOP_SESSION = true + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionHeartBeat -> Stopping the Session now. ') } } } @@ -414,6 +421,7 @@ if (global.STOP_TASK_GRACEFULLY === true) { bot.STOP_SESSION = true + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionError -> Stopping the Session now. ') } } } @@ -435,6 +443,7 @@ if (global.STOP_TASK_GRACEFULLY === true) { bot.STOP_SESSION = true + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionWarning -> Stopping the Session now. ') } } } @@ -456,6 +465,7 @@ if (global.STOP_TASK_GRACEFULLY === true) { bot.STOP_SESSION = true + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionInfo -> Stopping the Session now. ') } } } diff --git a/TradingBot.js b/TradingBot.js index 4f1e65487..e8c03c194 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -44,6 +44,7 @@ /* Some initial values*/ let fixedTimeLoopIntervalHandle; bot.STOP_SESSION = true; + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] run -> Stopping the Session now. ') } /* Heartbeats sent to the UI */ bot.processHeartBeat = processHeartBeat From a9502d345b52d52420821bc8cec12af94a427a00 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 13 Sep 2020 14:57:25 +0200 Subject: [PATCH 103/112] Raw Data Working Again --- IndicatorBot.js | 4 +- ProcessExecutionEvents.js | 82 ++++++++++++------------ Root.js | 24 +++---- SensorBot.js | 4 +- StatusReport.js | 129 ++++++++++++++++++++++++++------------ server.js | 56 ++++++++++------- 6 files changed, 177 insertions(+), 122 deletions(-) diff --git a/IndicatorBot.js b/IndicatorBot.js index bbc8d1290..cd513dee4 100644 --- a/IndicatorBot.js +++ b/IndicatorBot.js @@ -36,7 +36,7 @@ } /* This bot is not ready for taking its code from the UI, then we need to load it from its repo. */ - let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName + let filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo + "/" + pProcessConfig.codeName filePath += "/User.Bot.js" fileStorage.getTextFile(filePath, onBotDownloaded); @@ -54,7 +54,7 @@ USER_BOT_MODULE = {} USER_BOT_MODULE.newUserBot = eval(text); // TODO This needs to be changed function - filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo; + filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo filePath += "/Commons.js" fileStorage.getTextFile(filePath, onCommonsDownloaded); diff --git a/ProcessExecutionEvents.js b/ProcessExecutionEvents.js index 7c300dad2..9b94c0365 100644 --- a/ProcessExecutionEvents.js +++ b/ProcessExecutionEvents.js @@ -57,8 +57,7 @@ processThisDependsOn = bot.processNode.referenceParent.executionStartedEvent.referenceParent.parentNode name = processThisDependsOn.name - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) - { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the Data Mine depends on " + name); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the Data Mine depends on " + name); } } } } @@ -72,8 +71,7 @@ processThisDependsOn = bot.processNode.executionStartedEvent.referenceParent.parentNode name = processThisDependsOn.name - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) - { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the User's Task depends on " + name); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the User's Task depends on " + name); } } } } @@ -99,48 +97,50 @@ for (let i = 0; i < network.networkNodes.length; i++) { let networkNode = network.networkNodes[i] if (networkNode.dataMining !== undefined) { - for (let j = 0; j < networkNode.dataMining.exchangeTasks.length; j++) { - let exchangeTasks = networkNode.dataMining.exchangeTasks[j] - for (let k = 0; k < exchangeTasks.taskManagers.length; k++) { - let taskManager = exchangeTasks.taskManagers[k] - for (let m = 0; m < taskManager.tasks.length; m++) { - let task = taskManager.tasks[m] - if (task.bot !== undefined) { - for (let n = 0; n < task.bot.processes.length; n++) { - let process = task.bot.processes[n] - if (process.marketReference !== undefined) { - if (process.marketReference.referenceParent !== undefined) { - let market = process.marketReference.referenceParent - let currentProcessMarket = bot.processNode.marketReference.referenceParent + for (let j = 0; j < networkNode.dataMining.exchangeDataTasks.length; j++) { + let exchangeTasks = networkNode.dataMining.exchangeDataTasks[j] + for (let p = 0; p < exchangeTasks.marketDataTasks.length; p++) { + let marketTasks = exchangeTasks.marketDataTasks[p] + if (marketTasks.referenceParent === undefined) { continue } + if (marketTasks.referenceParent.id !== global.MARKET_NODE.id) { continue } + for (let q = 0; q < marketTasks.dataMineTasks.length; q++) { + let mineTasks = marketTasks.dataMineTasks[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) { + for (let n = 0; n < task.bot.processes.length; n++) { + let process = task.bot.processes[n] + if (process.referenceParent !== undefined) { + let processDefinition = process.referenceParent + if (processThisDependsOn.id === processDefinition.id) { + /* + We found where the task that runs the process definition + we are waiting for is located on the network. + */ + thisObject.networkNode = networkNode + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { + logger.write(MODULE_NAME, + "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + + " -> host = " + networkNode.config.host + + ' -> port = ' + networkNode.config.webSocketsPort + '.'); + } - if (currentProcessMarket.id === market.id) { - if (process.referenceParent !== undefined) { - let processDefinition = process.referenceParent - if (processThisDependsOn.id === processDefinition.id) { + eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.config.host, networkNode.config.webSocketsPort) + eventServerClient.initialize(onConnected) - /* We found where the task that runs the process definition we are waiting for is located on the network. */ - - thisObject.networkNode = networkNode - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { - logger.write(MODULE_NAME, - "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + - " -> host = " + networkNode.config.host + - ' -> port = ' + networkNode.config.webSocketsPort + '.'); } - - eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.config.host, networkNode.config.webSocketsPort) - eventServerClient.initialize(onConnected) - - function onConnected() { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { - logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + - " -> host = " + networkNode.config.host + - ' -> port = ' + networkNode.config.webSocketsPort + '.'); } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return + function onConnected() { + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { + logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + + " -> host = " + networkNode.config.host + + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } + + callBackFunction(global.DEFAULT_OK_RESPONSE); return } + return } } } diff --git a/Root.js b/Root.js index 209ae43ab..10ac254d5 100644 --- a/Root.js +++ b/Root.js @@ -140,7 +140,7 @@ const FILE_STORAGE = require('./FileStorage.js'); let fileStorage = FILE_STORAGE.newFileStorage(); - let filePath = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName + '/bots/' + global.TASK_NODE.bot.config.repo + '/this.bot.config.json'; + let filePath = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName + '/bots/' + processInstance.referenceParent.parentNode.config.repo + '/this.bot.config.json'; fileStorage.getTextFile(filePath, onFileReceived); @@ -183,11 +183,11 @@ /* Simplifying the access to basic info */ botConfig.dataMine = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName - botConfig.exchange = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode.name - botConfig.exchangeNode = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode + botConfig.exchange = global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.parentNode.parentNode.name + botConfig.exchangeNode = global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.parentNode.parentNode botConfig.market = { - baseAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent.config.codeName, - quotedAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent.config.codeName + baseAsset: global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName, + quotedAsset: global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName } botConfig.uiStartDate = global.TASK_NODE.bot.config.startDate botConfig.config = global.TASK_NODE.bot.config @@ -210,16 +210,12 @@ botConfig.marketTimeFrames = global.TASK_NODE.bot.timeFramesFilter.config.marketTimeFrames } - if (botConfig.processNode) { - if (botConfig.processNode.marketReference) { - if (botConfig.processNode.marketReference.keyReference !== undefined) { - if (botConfig.processNode.marketReference.keyReference.referenceParent !== undefined) { - let key = botConfig.processNode.marketReference.keyReference.referenceParent + if (global.TASK_NODE.keyReference !== undefined) { + if (global.TASK_NODE.keyReference.referenceParent !== undefined) { + let key = global.TASK_NODE.keyReference.referenceParent - botConfig.KEY = key.config.codeName - botConfig.SECRET = key.config.secret - } - } + botConfig.KEY = key.config.codeName + botConfig.SECRET = key.config.secret } } diff --git a/SensorBot.js b/SensorBot.js index 84a6d821a..13e5c251d 100644 --- a/SensorBot.js +++ b/SensorBot.js @@ -34,7 +34,7 @@ return } - let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName + let filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo + "/" + pProcessConfig.codeName filePath += "/User.Bot.js" fileStorage.getTextFile(filePath, onBotDownloaded); @@ -56,7 +56,7 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } - filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo; + filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo; filePath += "/Commons.js" fileStorage.getTextFile(filePath, onCommonsDownloaded); diff --git a/StatusReport.js b/StatusReport.js index 39d116fd5..21089be6d 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -20,7 +20,7 @@ finalize: finalize, load: load, save: save, - asyncSave: asyncSave, + asyncSave: asyncSave, status: undefined }; @@ -111,50 +111,97 @@ for (let i = 0; i < network.networkNodes.length; i++) { let networkNode = network.networkNodes[i] - if (checkThisBranch(networkNode.dataMining) === true) { return } - if (checkThisBranch(networkNode.testingEnvironment) === true) { return } - if (checkThisBranch(networkNode.productionEnvironment) === true) { return } + if (checkThisDataBranch(networkNode.dataMining) === true) { return } + if (checkThisTradingBranch(networkNode.testingEnvironment) === true) { return } + if (checkThisTradingBranch(networkNode.productionEnvironment) === true) { return } - function checkThisBranch(branch) { + function checkThisDataBranch(branch) { if (branch === undefined) { return } - for (let j = 0; j < branch.exchangeTasks.length; j++) { - let exchangeTasks = branch.exchangeTasks[j] - for (let k = 0; k < exchangeTasks.taskManagers.length; k++) { - let taskManager = exchangeTasks.taskManagers[k] - for (let m = 0; m < taskManager.tasks.length; m++) { - let task = taskManager.tasks[m] - if (task.bot !== undefined) { - for (let n = 0; n < task.bot.processes.length; n++) { - let process = task.bot.processes[n] - if (process.marketReference !== undefined) { - if (process.marketReference.referenceParent !== undefined) { - let market = process.marketReference.referenceParent - let currentProcessMarket = bot.processNode.marketReference.referenceParent - if (currentProcessMarket.id === market.id) { - if (process.referenceParent !== undefined) { - let processDefinition = process.referenceParent - if (processThisDependsOn.id === processDefinition.id) { - if (process.type === 'Trading Process Instance') { - if (process.session !== undefined) { - if (bot.processNode.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.networkNode = networkNode - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return true + for (let j = 0; j < branch.exchangeDataTasks.length; j++) { + let exchangeTasks = branch.exchangeDataTasks[j] + for (let p = 0; p < exchangeTasks.marketDataTasks.length; p++) { + let marketTasks = exchangeTasks.marketDataTasks[p] + if (marketTasks.referenceParent === undefined) { continue } + if (marketTasks.referenceParent.id !== global.MARKET_NODE.id) { continue } + for (let q = 0; q < marketTasks.dataMineTasks.length; q++) { + let mineTasks = marketTasks.dataMineTasks[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 } + 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 === 'Trading Process Instance') { + if (process.session !== undefined) { + if (bot.processNode.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.networkNode = networkNode + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } + + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); + callBackFunction(global.DEFAULT_OK_RESPONSE); + return true + } + } + } + } + } + } + } + } + + function checkThisTradingBranch(branch) { + if (branch === undefined) { return } + for (let j = 0; j < branch.exchangeTradingTasks.length; j++) { + let exchangeTasks = branch.exchangeTradingTasks[j] + for (let p = 0; p < exchangeTasks.marketTradingTasks.length; p++) { + let marketTasks = exchangeTasks.marketTradingTasks[p] + if (marketTasks.referenceParent === undefined) { continue } + if (marketTasks.referenceParent.id !== global.MARKET_NODE.id) { continue } + for (let q = 0; q < marketTasks.tradingMineTasks.length; q++) { + let mineTasks = marketTasks.tradingMineTasks[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 } + 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 === 'Trading Process Instance') { + if (process.session !== undefined) { + if (bot.processNode.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.networkNode = networkNode + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } + + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); + callBackFunction(global.DEFAULT_OK_RESPONSE); + return true } } } @@ -355,7 +402,7 @@ let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n', true) if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { - throw(response.err) + throw (response.err) } /* All good, lets emit the event that means data has been updated. */ diff --git a/server.js b/server.js index b72ed1361..c081bb175 100644 --- a/server.js +++ b/server.js @@ -482,73 +482,85 @@ function bootLoader() { /* Validate that the minimun amount of input required are defined. */ - if (global.TASK_NODE.bot.processes[processIndex].marketReference === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Process Instance without a Market Reference. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex])); + if (global.TASK_NODE.parentNode === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Task without a Task Manager. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex])); continue } - if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Market Reference without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference) + ", bot = " + global.TASK_NODE.bot.name); + if (global.TASK_NODE.parentNode.parentNode === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Task Manager without Mine Tasks. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex])); continue } - if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Market without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent)); + if (global.TASK_NODE.parentNode.parentNode.parentNode === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Mine Tasks without Market Tasks. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex])); continue } - if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Exchange Markets without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode)); + if (global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Market Tasks without a Market. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex])); continue } - if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Market without a Base Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent)); + global.MARKET_NODE = global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent + + if (global.MARKET_NODE.parentNode === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Market without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE)); continue } - if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Market without a Quoted Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent)); + if (global.MARKET_NODE.parentNode.parentNode === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Exchange Markets without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE.parentNode)); continue } - if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Base Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset)); + if (global.MARKET_NODE.baseAsset === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Market without a Base Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE)); continue } - if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Quoted Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset)); + if (global.MARKET_NODE.quotedAsset === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Market without a Quoted Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE)); + continue + } + + if (global.MARKET_NODE.baseAsset.referenceParent === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Base Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE.baseAsset)); + continue + } + + if (global.MARKET_NODE.quotedAsset.referenceParent === undefined) { + console.log("[ERROR] Task Server -> server -> bootLoader -> Quoted Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE.quotedAsset)); continue } if (global.TASK_NODE.bot.processes[processIndex].referenceParent === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Process Instance without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex])); + console.log("[ERROR] Task Server -> server -> bootLoader -> Process Instance without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex])); continue } if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Process Definition without parent Bot Definition. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent)); + console.log("[ERROR] Task Server -> server -> bootLoader -> Process Definition without parent Bot Definition. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent)); continue } if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Bot Definition without parent Data Mine. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode)); + console.log("[ERROR] Task Server -> server -> bootLoader -> Bot Definition without parent Data Mine. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode)); continue } if (global.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Process Definition without a codeName defined. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent)); + console.log("[ERROR] Task Server -> server -> bootLoader -> Process Definition without a codeName defined. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent)); continue } if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.config.codeName === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Bot Definition without a codeName defined. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode)); + console.log("[ERROR] Task Server -> server -> bootLoader -> Bot Definition without a codeName defined. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode)); continue } if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName === undefined) { - console.log("[WARN] Task Server -> server -> bootLoader -> Data Mine without a codeName defined. -> Data Mine Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode)); + console.log("[ERROR] Task Server -> server -> bootLoader -> Data Mine without a codeName defined. -> Data Mine Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode)); continue } From 4a884a74c99b14e1dd0e2cc7bba8f298c0948c05 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sun, 13 Sep 2020 20:05:17 +0200 Subject: [PATCH 104/112] Trading mine --- Commons.js | 35 ++++++++++++++++++++++------------- DataSet.js | 2 +- ProcessOutput.js | 4 ++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Commons.js b/Commons.js index a93cd8d85..050791f24 100644 --- a/Commons.js +++ b/Commons.js @@ -96,33 +96,42 @@ } let botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Indicator Bot') - if (botNode === undefined) { + if (botNode === undefined) { botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Trading Bot') } if (botNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = " ); + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = "); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } if (botNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined." ); + logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined."); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } let dataMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Data Mine') if (dataMineNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - if (dataMineNode.config.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined." ); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } + let tradingMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Trading Mine') + if (tradingMineNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine or a Trading Mine."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } else { + if (tradingMineNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Trading Mine witn no codeName defined."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + } + } else { + if (dataMineNode.config.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + } } return true } diff --git a/DataSet.js b/DataSet.js index 7dce95fe8..5d1527055 100644 --- a/DataSet.js +++ b/DataSet.js @@ -64,7 +64,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { } if (thisObject.node.parentNode.parentNode.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode)); + logger.write(MODULE_NAME, "[ERROR] initialize -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } diff --git a/ProcessOutput.js b/ProcessOutput.js index 5ac15c864..9f6f0ee71 100644 --- a/ProcessOutput.js +++ b/ProcessOutput.js @@ -59,7 +59,7 @@ return } } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(botNode)); + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(botNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } @@ -142,7 +142,7 @@ throw (global.DEFAULT_FAIL_RESPONSE); } } else { - logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(botNode)); + logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(botNode)); throw (global.DEFAULT_FAIL_RESPONSE); } } else { From 78040472d1adc54bfadb36137e7227b58ce7783e Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 21 Sep 2020 09:36:51 +0200 Subject: [PATCH 105/112] Bug fixed --- DataDependencies.js | 10 +++++++--- LowFrequencyTrading/TradingProcess.js | 6 ++++++ LowFrequencyTrading/TradingSimulation.js | 1 + server.js | 13 +++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/DataDependencies.js b/DataDependencies.js index 88e57be8e..552f6a64f 100644 --- a/DataDependencies.js +++ b/DataDependencies.js @@ -28,6 +28,13 @@ callBackFunction(global.DEFAULT_OK_RESPONSE) return } + /* + We will filter ourt 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. + */ + thisObject.nodeArray = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(thisObject.nodeArray) + } else { logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> It is not possible to not have process dependencies, which means not data dependencies."); callBackFunction(global.DEFAULT_OK_RESPONSE) @@ -57,9 +64,7 @@ let addCount = 0; for (let i = 0; i < thisObject.nodeArray.length; i++) { - let dataSetModule = DATA_SET.newDataSet(BOT, logger); - dataSetModule.initialize(thisObject.nodeArray[i], onInitilized); function onInitilized(err) { @@ -71,7 +76,6 @@ callBackFunction(err); return; } - addDataSet(); } diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 5960be476..b6298e6f0 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -505,6 +505,12 @@ /* The first phase here is about checking that we have everything we need at the definition level. */ let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency') + /* + We will filter ourt 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. + */ + dataDependencies = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(dataDependencies) if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index a1af9db6e..186f2872e 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -317,6 +317,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin For that reason we do the following processing, to have at the chart data structure the current objects of each indicator / time frame. */ let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency') + dataDependencies = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(dataDependencies) /* Finding the Current Element on Market Files */ if (bot.processingDailyFiles) { diff --git a/server.js b/server.js index c081bb175..754888678 100644 --- a/server.js +++ b/server.js @@ -272,6 +272,19 @@ global.FIND_NODE_IN_NODE_ARRAY = function (nodeArray, nodeType) { } } +global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY = function (nodeArray) { + let filteredNodeArray = [] + for (let i = 0; i < nodeArray.length; i++) { + let arrayItem = nodeArray[i] + if (arrayItem.referenceParent === undefined) { + continue + } else { + filteredNodeArray.push(arrayItem) + } + } + return filteredNodeArray +} + process.on('uncaughtException', function (err) { console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message) From 8b32b910dad70efe7da61f2db4f223d4f356d832 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 23 Sep 2020 15:02:19 +0200 Subject: [PATCH 106/112] dependencies improved --- DataDependencies.js | 38 ++++++++++++++++++++++++++++++-------- DataSet.js | 10 +++++----- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/DataDependencies.js b/DataDependencies.js index 552f6a64f..54eb5916d 100644 --- a/DataDependencies.js +++ b/DataDependencies.js @@ -60,14 +60,27 @@ /* For each dependency declared at the nodeArray, we will initialize a DataSet as part of this initialization process. */ - let alreadyCalledBack = false; - let addCount = 0; + let alreadyCalledBack = false + let addCount = 0 + let skipCount = 0 + + /* + The current nodeArray that we have includes all the dependencies daclared in the Data Mine / Trading 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 + this task is running, that is storing the data associated to this dependency. The way we use to filter this + out is by analizyng the response of the initialization call to the DataSet module. If it did not fail but + it was not initialized, then we will assume this happened because the data could not be located and we will + assume the user did this on purpose and we will filter out this dependency by not putting it on this new array. + */ + let newNodeArray = [] for (let i = 0; i < thisObject.nodeArray.length; i++) { let dataSetModule = DATA_SET.newDataSet(BOT, logger); dataSetModule.initialize(thisObject.nodeArray[i], onInitilized); - function onInitilized(err) { + function onInitilized(err, wasInitialized) { if (err.result !== global.DEFAULT_OK_RESPONSE.result) { logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err = " + JSON.stringify(err)); @@ -76,17 +89,26 @@ callBackFunction(err); return; } - addDataSet(); + + if (wasInitialized === true) { + addCount++; + newNodeArray.push(thisObject.nodeArray[i]) + addDataSet() + } else { + skipCount++ + } + checkIfWeAreDone() } - function addDataSet() { - addCount++; - + function addDataSet() { thisObject.dataSetsModulesArray.push(dataSetModule); + } - if (addCount === thisObject.nodeArray.length) { + function checkIfWeAreDone() { + if (addCount + skipCount === thisObject.nodeArray.length) { if (alreadyCalledBack === false) { alreadyCalledBack = true + thisObject.nodeArray = newNodeArray callBackFunction(global.DEFAULT_OK_RESPONSE); return; } diff --git a/DataSet.js b/DataSet.js index 5d1527055..5b43b7bbf 100644 --- a/DataSet.js +++ b/DataSet.js @@ -83,10 +83,10 @@ exports.newDataSet = function newDataSet(BOT, logger) { let networkNode = global.FIND_NODE_IN_NODE_ARRAY(nodeArray, 'Network Node') if (networkNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] initialize -> Network Node not found.") - logger.write(MODULE_NAME, "[ERROR] initialize -> Initialization Failed because we could not find where the data of this dataset is located within the network. Check the logs for more info."); - logger.write(MODULE_NAME, "[ERROR] initialize -> Could not find where " + datasetProductDefinition.name + " for " + bot.exchange + " " + bot.market.baseAsset + "/" + bot.market.quotedAsset + " is stored within the network."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + logger.write(MODULE_NAME, "[WARN] initialize -> Network Node not found.") + logger.write(MODULE_NAME, "[WARN] initialize -> Initialization Failed because we could not find where the data of this dataset is located within the network. Check the logs for more info."); + logger.write(MODULE_NAME, "[WARN] initialize -> Could not find where " + datasetProductDefinition.name + " for " + bot.exchange + " " + bot.market.baseAsset + "/" + bot.market.quotedAsset + " is stored within the network."); + callBackFunction(global.DEFAULT_OK_RESPONSE, false); return } @@ -94,7 +94,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); - callBackFunction(global.DEFAULT_OK_RESPONSE); + callBackFunction(global.DEFAULT_OK_RESPONSE, true); } catch (err) { logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); From 00e9ee45e311293861120f13e52d55d326da7a8e Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 25 Sep 2020 20:20:29 +0200 Subject: [PATCH 107/112] Session Syncro --- LowFrequencyTrading/TradingProcess.js | 4 +++- Session.js | 29 ++++++++++++++++++++------- TradingBot.js | 10 ++++----- server.js | 12 +++++++++++ 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index b6298e6f0..5a37df7c6 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -146,6 +146,8 @@ } else { /* We are processing Daily Files */ do { + global.EMIT_SESSION_STATUS (bot.SESSION_STATUS, bot.sessionKey) + if (checkStopTaskGracefully() === false) { break } if (checkStopProcessing() === false) { break } @@ -401,7 +403,7 @@ async function processDailyFiles() { /* Telling the world we are alive and doing well and which date we are processing right now. */ let processingDateString = tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(tradingProcessDate.getUTCDate(), 2); - bot.processHeartBeat(processingDateString) + bot.processHeartBeat(processingDateString, undefined, "Running...") /* We will iterate through all posible timeFrames. diff --git a/Session.js b/Session.js index 64b4e0c30..926fbcc09 100644 --- a/Session.js +++ b/Session.js @@ -38,8 +38,9 @@ bot.sessionKey = bot.processNode.session.name + '-' + bot.processNode.session.type + '-' + bot.processNode.session.id global.SESSION_MAP.set(bot.sessionKey, bot.sessionKey) - global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, onSessionRan) - global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, onSessionStopped) + global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Session Status', undefined, bot.sessionKey, undefined, onSessionStatus) + global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, onSessionRun) + global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, onSessionStop) /* Connect this here so that it is accesible from other places */ bot.sessionError = sessionError @@ -52,11 +53,25 @@ callBackFunction(global.DEFAULT_OK_RESPONSE) return - function onSessionRan(message) { + function onSessionStatus() { + if (bot.SESSION_STATUS === 'Running') { + let event = { + status: 'Session Runnning' + } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Status Response', event) + } else { + let event = { + status: 'Not Session Runnning' + } + global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, '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 (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { - parentLogger.write(MODULE_NAME, "[WARN] onSessionRan -> Event receive to run the Session while it was already running. ") + parentLogger.write(MODULE_NAME, "[WARN] onSessionRun -> Event receive to run the Session while it was already running. ") return } @@ -100,16 +115,16 @@ bot.STOP_SESSION = false } else { bot.STOP_SESSION = true - if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] onSessionRan -> Stopping the Session now. ') } + if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] onSessionRun -> Stopping the Session now. ') } } socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is starting.") } catch (err) { - parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionRan -> err = " + err.stack); + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionRun -> err = " + err.stack); } } - function onSessionStopped() { + function onSessionStop() { stopSession('Session Stopped From the User Interface.') } diff --git a/TradingBot.js b/TradingBot.js index e8c03c194..9d4d48ee7 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -74,7 +74,7 @@ let nextWaitTime; /* We tell the UI that we are running. */ - processHeartBeat() + processHeartBeat(undefined, undefined, "Running...") /* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */ const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities'); @@ -94,14 +94,14 @@ /* Checking if we need to need to emit any event */ if (bot.SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Running') bot.SESSION_STATUS = 'Running' } if (bot.SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) { - global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped') bot.SESSION_STATUS = 'Stopped' - } + } + + global.EMIT_SESSION_STATUS (bot.SESSION_STATUS, bot.sessionKey) /* Checking if we should process this loop or not.*/ if (bot.STOP_SESSION === true) { @@ -523,7 +523,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); } /* We show we reached the end of the loop. */ - processHeartBeat() + processHeartBeat(undefined, undefined, "Running...") /* Here we check if we must stop the loop gracefully. */ shallWeStop(onStop, onContinue); diff --git a/server.js b/server.js index 754888678..3ff27cf3f 100644 --- a/server.js +++ b/server.js @@ -285,6 +285,18 @@ global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY = function (nod return filteredNodeArray } +global.EMIT_SESSION_STATUS = function (status, key) { + switch(status) { + case 'Running': { + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Running') + break + } + case 'Stopped': { + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Stopped') + break + } + } +} process.on('uncaughtException', function (err) { console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message) From 2ca5bbe7be6e8725acfda00bb404e420b1cf92d6 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Tue, 29 Sep 2020 15:37:38 +0200 Subject: [PATCH 108/112] Resuming Session --- LowFrequencyTrading/TradingProcess.js | 10 +++++----- Session.js | 27 +++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 5a37df7c6..0562fd592 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -116,7 +116,7 @@ Here is the point where we sync one and the other. */ let tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value) - + console.log("1 tradingProcessDate ", tradingProcessDate) await processSingleFiles() await processMarketFiles() @@ -147,10 +147,6 @@ /* We are processing Daily Files */ do { global.EMIT_SESSION_STATUS (bot.SESSION_STATUS, bot.sessionKey) - - if (checkStopTaskGracefully() === false) { break } - if (checkStopProcessing() === false) { break } - /* We update the Trading Process Date with the date calculated at the simulation. We will use this date to load indicator and output files. After that we will @@ -158,6 +154,10 @@ the date calculated by the Simulation is applied at the Trading Process Level. */ tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value) + console.log("2 tradingProcessDate ", tradingProcessDate) + if (checkStopTaskGracefully() === false) { break } + if (checkStopProcessing() === false) { break } + await processDailyFiles() /* With all the indicators data files loaded, we will build the chart object diff --git a/Session.js b/Session.js index 926fbcc09..6a06be524 100644 --- a/Session.js +++ b/Session.js @@ -41,6 +41,7 @@ global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Session Status', undefined, bot.sessionKey, undefined, onSessionStatus) global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, onSessionRun) global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, onSessionStop) + global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Resume Session', undefined, bot.sessionKey, undefined, onSessionResume) /* Connect this here so that it is accesible from other places */ bot.sessionError = sessionError @@ -71,7 +72,7 @@ try { /* This happens when the UI is reloaded, the session was running and tries to run it again. */ if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { - parentLogger.write(MODULE_NAME, "[WARN] onSessionRun -> Event receive to run the Session while it was already running. ") + parentLogger.write(MODULE_NAME, "[WARN] onSessionRun -> Event received to run the Session while it was already running. ") return } @@ -80,7 +81,7 @@ bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine) bot.SESSION = JSON.parse(message.event.session) bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter) - bot.RESUME = message.event.resume + bot.RESUME = false bot.FIRST_EXECUTION = true bot.SESSION.stop = stopSession // stop function @@ -128,6 +129,28 @@ stopSession('Session Stopped From the User Interface.') } + function onSessionResume(message) { + try { + if (bot.SESSION.stop === undefined) { + parentLogger.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 (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { + parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session while it was already running. ") + return + } + + bot.RESUME = true + bot.STOP_SESSION = false + + socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is resuming.") + } catch (err) { + parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionResume -> err = " + err.stack); + } + } + function stopSession(commandOrigin) { socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin) From 8265464598cde7a5cac40965c6d919ebc4cf7cb2 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 30 Sep 2020 08:52:48 +0200 Subject: [PATCH 109/112] Update ExchangeAPI.js --- LowFrequencyTrading/ExchangeAPI.js | 1 + 1 file changed, 1 insertion(+) diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index ad6533687..58197db90 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -35,6 +35,7 @@ 'timeout': 30000, 'enableRateLimit': true, verbose: false, + 'adjustForTimeDifference': True, options: options } exchange = new exchangeClass(exchangeConstructorParams) From 683de5ebaae9ed3ae7aa385b5da857ef37368354 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 30 Sep 2020 13:23:34 +0200 Subject: [PATCH 110/112] Update ExchangeAPI.js --- LowFrequencyTrading/ExchangeAPI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index 58197db90..a6d314c43 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -35,7 +35,7 @@ 'timeout': 30000, 'enableRateLimit': true, verbose: false, - 'adjustForTimeDifference': True, + 'adjustForTimeDifference': true, options: options } exchange = new exchangeClass(exchangeConstructorParams) From 5a5c5c365c2d02630baa96b33842f21a85a108ba Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Wed, 30 Sep 2020 15:32:17 +0200 Subject: [PATCH 111/112] Dynamic Indicators --- LowFrequencyTrading/TradingSystem.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 05248d1be..d90c558ae 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -17,6 +17,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM let tradingSystem let tradingEngine let sessionParameters + let dynamicIndicators const TRADING_STAGES_MODULE = require('./TradingStages.js') let tradingStagesModule = TRADING_STAGES_MODULE.newTradingStages(bot, logger, tradingEngineModule) @@ -122,8 +123,26 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingStagesModule.updateChart(pChart) } + function buildDynamicIndicators() { + if (tradingSystem.dynamicIndicators !== undefined) { + dynamicIndicators = {} + /* Eval Dynamic Indicators */ + tradingSystem.evalFormulas(tradingSystem.dynamicIndicators, 'Indicator Function') + + for (let i = 0; i < tradingSystem.dynamicIndicators.indicatorFunctions.length; i++) { + let indicatorFunction = tradingSystem.dynamicIndicators.indicatorFunctions[i] + if (indicatorFunction.formula === undefined) { return} + if (indicatorFunction.config.codeName === undefined) { return} + dynamicIndicators[indicatorFunction.config.codeName] = tradingSystem.formulas.get(indicatorFunction.formula.id) + } + } + } + async function run() { try { + /* Dynamic Indicators */ + buildDynamicIndicators() + /* Run the Trigger Stage */ tradingStagesModule.runTriggerStage() @@ -230,7 +249,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } try { - logger.write(MODULE_NAME, '[INFO] evalCondition -> ' + node.name + ' -> code = ' + code) + logger.write(MODULE_NAME, '[INFO] evalCondition -> ' + node.name + ' -> code = ' + code) value = eval(code) } catch (err) { /* @@ -270,7 +289,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM let error try { - logger.write(MODULE_NAME, '[INFO] evalFormula -> ' + node.name + ' -> code = ' + node.code) + logger.write(MODULE_NAME, '[INFO] evalFormula -> ' + node.name + ' -> code = ' + node.code) value = eval(node.code) } catch (err) { /* From fae16e87e00ee552650ce5304dd0075b0b846030 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 2 Oct 2020 12:21:58 +0200 Subject: [PATCH 112/112] Bug fixed Live Trading --- FileStorage.js | 2 +- LowFrequencyTrading/TradingProcess.js | 67 +++++++++++++++++++++++---- Session.js | 8 ++-- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/FileStorage.js b/FileStorage.js index 575df8a7d..8cc7baac9 100644 --- a/FileStorage.js +++ b/FileStorage.js @@ -197,7 +197,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } function retry() { - console.log(new Date(), 'Read File Retry #: ' + currentRetryGetTextFile, fileLocation) + console.log(new Date(), '[WARN] Read File Retry #: ' + currentRetryGetTextFile, fileLocation) if (currentRetryGetTextFile < MAX_RETRY) { currentRetryGetTextFile++ logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> retry -> Will try to read the file again -> Retry #: ' + currentRetryGetTextFile) diff --git a/LowFrequencyTrading/TradingProcess.js b/LowFrequencyTrading/TradingProcess.js index 0562fd592..bddb9883a 100644 --- a/LowFrequencyTrading/TradingProcess.js +++ b/LowFrequencyTrading/TradingProcess.js @@ -116,10 +116,13 @@ Here is the point where we sync one and the other. */ let tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value) - console.log("1 tradingProcessDate ", tradingProcessDate) await processSingleFiles() - await processMarketFiles() + if (await processMarketFiles() === false) { + bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run') + callBackFunction(global.DEFAULT_RETRY_RESPONSE) + return + } /* This is the Data Structure used at the Simulation with all indicator data. We start creating it right here. @@ -138,15 +141,25 @@ 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. */ + bot.sessionHeartBeat(undefined, undefined, 'Waking up') buildCharts(chart) - await generateOutput(chart) - await writeProcessFiles() + if (checkThereAreCandles(chart) === true) { + bot.sessionHeartBeat(undefined, undefined, 'Running') + await generateOutput(chart) + bot.sessionHeartBeat(undefined, undefined, 'Saving') + await writeProcessFiles() + bot.sessionHeartBeat(undefined, undefined, 'Sleeping') + } else { + bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date') + callBackFunction(global.DEFAULT_RETRY_RESPONSE) + return + } } else { /* We are processing Daily Files */ do { - global.EMIT_SESSION_STATUS (bot.SESSION_STATUS, bot.sessionKey) + global.EMIT_SESSION_STATUS(bot.SESSION_STATUS, bot.sessionKey) /* We update the Trading Process Date with the date calculated at the simulation. We will use this date to load indicator and output files. After that we will @@ -154,11 +167,16 @@ the date calculated by the Simulation is applied at the Trading Process Level. */ tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value) - console.log("2 tradingProcessDate ", tradingProcessDate) + if (checkStopTaskGracefully() === false) { break } if (checkStopProcessing() === false) { break } - await processDailyFiles() + bot.sessionHeartBeat(undefined, undefined, 'Waking up') + if (await processDailyFiles() === false) { + bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run') + callBackFunction(global.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. @@ -167,8 +185,17 @@ /* The process of generating the output includes the trading simulation. */ - await generateOutput(chart) - await writeProcessFiles() + if (checkThereAreCandles(chart) === true) { + bot.sessionHeartBeat(undefined, undefined, 'Running') + await generateOutput(chart) + bot.sessionHeartBeat(undefined, undefined, 'Saving') + await writeProcessFiles() + bot.sessionHeartBeat(undefined, undefined, 'Sleeping') + } else { + bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date') + callBackFunction(global.DEFAULT_RETRY_RESPONSE) + return + } /* If for any reason the session was stopped, we will break this loop and exit the process. */ @@ -179,6 +206,7 @@ into indicators, and eventually a new execution of this process. */ if (checkStopHeadOfTheMarket() === false) { break } + } while (true) } @@ -190,6 +218,18 @@ */ callBackFunction(global.DEFAULT_OK_RESPONSE) + function checkThereAreCandles(chart) { + let sessionParameters = bot.SESSION.parameters + let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '') + let candles = chart[propertyName].candles + + if (candles.length === 0) { + return false + } else { + return true + } + } + function getContextVariables() { try { let thisReport; @@ -355,6 +395,10 @@ /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + if (response.err.message === 'File does not exist.') { + return false + } + if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { throw (response.err) } @@ -398,6 +442,7 @@ let mapKey = global.marketFilesPeriods[n][1]; multiPeriodDataFiles.set(mapKey, dataFiles) } + return true } async function processDailyFiles() { @@ -460,6 +505,9 @@ /* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */ let response = await asyncGetDatasetFile(datasetModule, filePath, fileName) + if (response.err.message === 'File does not exist.') { + return false + } if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) { throw (response.err) } @@ -471,6 +519,7 @@ let mapKey = global.dailyFilePeriods[n][1]; multiPeriodDataFiles.set(mapKey, dataFiles) } + return true } function checkStopHeadOfTheMarket() { diff --git a/Session.js b/Session.js index 6a06be524..9b8c82b67 100644 --- a/Session.js +++ b/Session.js @@ -197,7 +197,7 @@ type: 'Time Range', config: { initialDatetime: (new Date()).valueOf(), - finalDatetime: (new Date()).valueOf() + finalDatetime: (new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS } } } else { @@ -428,10 +428,12 @@ } } - function sessionHeartBeat(processingDate) { + function sessionHeartBeat(processingDate, percentage, status) { let event = { seconds: (new Date()).getSeconds(), - processingDate: processingDate + processingDate: processingDate, + percentage: percentage, + status: status } global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Heartbeat', event)