diff --git a/Commons.js b/Commons.js index 70200a529..fcc9ded0b 100644 --- a/Commons.js +++ b/Commons.js @@ -3,6 +3,7 @@ const FULL_LOG = true; const LOG_FILE_CONTENT = false; const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; + const ONE_MIN_IN_MILISECONDS = 60 * 1000; const MODULE_NAME = "Commons"; @@ -55,6 +56,11 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } + if (outputDatasetNode.referenceParent.parentNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Dataset not a child of a Product Definition. Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } if (outputDatasetNode.referenceParent.parentNode.code.singularVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -284,7 +290,8 @@ let yesterday = {} let system = { // These are the available system variables to be used in User Code and Formulas timeFrame: timeFrame, - ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS + ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS, + ONE_MIN_IN_MILISECONDS: ONE_MIN_IN_MILISECONDS } let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. let results = [] diff --git a/TradingBot.js b/TradingBot.js index 72cc5ef03..bc34ed945 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -1,137 +1,134 @@ -exports.newTradingBot = function newTradingBot(bot, logger, UTILITIES, FILE_STORAGE) { +exports.newTradingBot = function newTradingBot (bot, logger, UTILITIES, FILE_STORAGE) { + const FULL_LOG = true + const LOG_FILE_CONTENT = false + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000 - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; + const MODULE_NAME = 'Trading Bot' - const MODULE_NAME = "Trading Bot"; + let thisObject = { + initialize: initialize, + finalize: finalize, + start: start + } - let thisObject = { - initialize: initialize, - finalize: finalize, - start: start - }; + let utilities = UTILITIES.newCloudUtilities(logger) + let fileStorage = FILE_STORAGE.newFileStorage(logger) - let utilities = UTILITIES.newCloudUtilities(logger); - let fileStorage = FILE_STORAGE.newFileStorage(logger); + const COMMONS = require('./Commons.js') + let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE) - const COMMONS = require('./Commons.js'); - let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE); + let exchangeAPI - let exchangeAPI; + return thisObject - return thisObject; + function finalize () { + thisObject = undefined + utilities = undefined + fileStorage = undefined + commons = undefined + } - function finalize() { - thisObject = undefined - utilities = undefined - fileStorage = undefined - commons = undefined + function initialize (pExchangeAPI, callBackFunction) { + try { + logger.fileName = MODULE_NAME + logger.initialize() + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] initialize -> Entering function.') } + + exchangeAPI = pExchangeAPI + callBackFunction(global.DEFAULT_OK_RESPONSE) + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] initialize -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) } + } - function initialize(pExchangeAPI, callBackFunction) { + function start (multiPeriodDataFiles, timeFrame, timeFrameLabel, currentDay, variable, callBackFunction) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Entering function.') } - try { - - logger.fileName = MODULE_NAME; - logger.initialize(); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - exchangeAPI = pExchangeAPI; - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function start(multiPeriodDataFiles, timeFrame, timeFrameLabel, currentDay, variable, callBackFunction) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } - - let chart = {} - let mainDependency = {} + let chart = {} + 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 - if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) {return} + let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets - if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + 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. */ - for (let j = 0; j < global.marketFilesPeriods.length; j++) { + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + let timeFrameLabel = marketFilesPeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) + let products = {} - let timeFrameLabel = marketFilesPeriods[j][1] - let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) - let products = {} + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Inflating Data Files for timeFrame = ' + timeFrameLabel) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files for timeFrame = " + timeFrameLabel); } + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + let propertyName = 'at' + timeFrameLabel.replace('-', '') + chart[propertyName] = products + } + } - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - chart[propertyName] = products - } - } + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + let timeFrameLabel = global.dailyFilePeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) + let products = {} - for (let j = 0; j < global.dailyFilePeriods.length; j++) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Inflating Data Files for timeFrame = ' + timeFrameLabel) } - let timeFrameLabel = global.dailyFilePeriods[j][1] - let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) - let products = {} + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files for timeFrame = " + timeFrameLabel); } - - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - chart[propertyName] = products - } - } + let propertyName = 'at' + timeFrameLabel.replace('-', '') + chart[propertyName] = products + } + } /* Single Files */ - let dataFiles = multiPeriodDataFiles.get('Single Files') - let products = {} + let dataFiles = multiPeriodDataFiles.get('Single Files') + let products = {} - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files from Single Files."); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Inflating Data Files from Single Files.') } - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - let propertyName = 'atAnyTimeFrame'; - chart[propertyName] = products - } + let propertyName = 'atAnyTimeFrame' + chart[propertyName] = products + } /* Simulation */ - - const TRADING_SIMULATION = require('./TradingSimulation.js'); - let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES); - let market = bot.market; + const TRADING_SIMULATION = require('./TradingSimulation.js') + let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES) - const SIMULATED_RECORDS_FOLDER_NAME = "Trading-Simulation"; - const CONDITIONS_FOLDER_NAME = "Simulation-Conditions"; - const STRATEGIES_FOLDER_NAME = "Simulation-Strategies"; - const TRADES_FOLDER_NAME = "Simulation-Trades"; + let market = bot.market - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; + const SIMULATED_RECORDS_FOLDER_NAME = 'Trading-Simulation' + const CONDITIONS_FOLDER_NAME = 'Simulation-Conditions' + const STRATEGIES_FOLDER_NAME = 'Simulation-Strategies' + const TRADES_FOLDER_NAME = 'Simulation-Trades' + const SNAPSHOTS_FOLDER_NAME = 'Snapshots' - let recordsArray - let conditionsArray - let strategiesArray - let tradesArray + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000 - let tradingSystem = {}; + let recordsArray + let conditionsArray + let strategiesArray + let tradesArray - tradingSimulation.runSimulation( + let snapshotHeaders + let triggerOnSnapshot + let takePositionSnapshot + + let tradingSystem = {} + + tradingSimulation.runSimulation( chart, dataDependencies, timeFrame, @@ -142,709 +139,768 @@ writeFiles, callBackFunction) - function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray) { + function writeFiles (pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray, pSnapshotHeaders, pTriggerOnSnapshot, pTakePositionSnapshot) { + tradingSystem = pTradingSystem + recordsArray = pRecordsArray + conditionsArray = pConditionsArray + strategiesArray = pStrategiesArray + tradesArray = pTradesArray - tradingSystem = pTradingSystem - recordsArray = pRecordsArray - conditionsArray = pConditionsArray - strategiesArray = pStrategiesArray - tradesArray = pTradesArray + snapshotHeaders = pSnapshotHeaders + triggerOnSnapshot = pTriggerOnSnapshot + takePositionSnapshot = pTakePositionSnapshot - if (timeFrame > global.dailyFilePeriods[0][0]) { - writeMarketFiles() - } else { - writeDailyFiles() - } + if (timeFrame > global.dailyFilePeriods[0][0]) { + writeMarketFiles() + } else { + writeDailyFiles() + } + } + + function writeMarketFiles () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> Entering function.') } + + writeRecordsFile() + + function writeRecordsFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeRecordsFile -> Entering function.') } + + let separator = '' + let fileRecordCounter = 0 + + let fileContent = '' + + for (let i = 0; i < recordsArray.length; i++) { + let record = recordsArray[i] + + fileContent = fileContent + separator + '[' + + record.begin + ',' + + record.end + ',' + + record.balanceBaseAsset + ',' + + record.balanceQuotedAsset + ',' + + record.accumulatedProfitLoss + ',' + + record.lastTradeProfitLoss + ',' + + record.stopLoss + ',' + + record.tradesCount + ',' + + record.hits + ',' + + record.fails + ',' + + record.hitRatio + ',' + + record.ROI + ',' + + record.periods + ',' + + record.days + ',' + + record.anualizedRateOfReturn + ',' + + record.positionRate + ',' + + record.lastTradeROI + ',' + + record.strategy + ',' + + record.takeProfit + ',' + + record.stopLossPhase + ',' + + record.takeProfitPhase + ',' + + record.positionSize + ',' + + record.initialBalanceA + ',' + + record.minimumBalanceA + ',' + + record.maximumBalanceA + ',' + + record.initialBalanceB + ',' + + record.minimumBalanceB + ',' + + record.maximumBalanceB + ',' + + record.baseAsset + ',' + + record.quotedAsset + ',' + + record.marketBaseAsset + ',' + + record.marketQuotedAsset + ',' + + record.positionPeriods + ',' + + record.positionDays + ',' + + record.distanceToLastTriggerOn + ',' + + record.distanceToLastTriggerOff + ',' + + record.distanceToLastTakePosition + ',' + + record.distanceToLastClosePosition + ']' + + if (separator === '') { separator = ',' } + + fileRecordCounter++ } - function writeMarketFiles() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> Entering function."); } + fileContent = '[' + fileContent + ']' - writeRecordsFile(); + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SIMULATED_RECORDS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName - function writeRecordsFile() { + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) - try { + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> fileContent = ' + fileContent) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> Entering function."); } + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < recordsArray.length; i++) { - - let record = recordsArray[i]; - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.balanceBaseAsset + "," + - record.balanceQuotedAsset + "," + - record.accumulatedProfitLoss + "," + - record.lastTradeProfitLoss + "," + - record.stopLoss + "," + - record.tradesCount + "," + - record.hits + "," + - record.fails + "," + - record.hitRatio + "," + - record.ROI + "," + - record.periods + "," + - record.days + "," + - record.anualizedRateOfReturn + "," + - record.positionRate + "," + - record.lastTradeROI + "," + - record.strategy + "," + - record.takeProfit + "," + - record.stopLossPhase + "," + - record.takeProfitPhase + "," + - record.positionSize + "," + - record.initialBalanceA + "," + - record.minimumBalanceA + "," + - record.maximumBalanceA + "," + - record.initialBalanceB + "," + - record.minimumBalanceB + "," + - record.maximumBalanceB + "," + - record.baseAsset + "," + - record.quotedAsset + "," + - record.marketBaseAsset + "," + - record.marketQuotedAsset + "," + - record.positionPeriods + "," + - record.positionDays + "," + - record.distanceToLastTriggerOn + "," + - record.distanceToLastTriggerOff + "," + - record.distanceToLastTakePosition + "," + - record.distanceToLastClosePosition + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeConditionsFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + callBackFunction(err) + return } - function writeConditionsFile() { + writeConditionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } - try { + function writeConditionsFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeConditionsFile -> Entering function.') } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> Entering function."); } + let separator = '' + let fileRecordCounter = 0 - let separator = ""; - let fileRecordCounter = 0; + let fileContent = '' - let fileContent = ""; + for (let i = 0; i < conditionsArray.length; i++) { + let record = conditionsArray[i] - for (let i = 0; i < conditionsArray.length; i++) { + let conditions = '' + let conditionsSeparator = '' - let record = conditionsArray[i]; + for (let j = 0; j < record.length - 3; j++) { + conditions = conditions + conditionsSeparator + record[j] + if (conditionsSeparator === '') { conditionsSeparator = ',' } + } - let conditions = ""; - let conditionsSeparator = ""; + conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']' // The last item contains an Array of condition values. + conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']' // The last item contains an Array of formulaErrors. + conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']' // The last item contains an Array of formulaValues. - for (let j = 0; j < record.length - 3; j++) { - conditions = conditions + conditionsSeparator + record[j]; - if (conditionsSeparator === "") { conditionsSeparator = ","; } - } + fileContent = fileContent + separator + '[' + conditions + ']' - conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']'; // The last item contains an Array of condition values. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']'; // The last item contains an Array of formulaErrors. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']'; // The last item contains an Array of formulaValues. - - fileContent = fileContent + separator + '[' + conditions + ']'; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - fileContent = "[" + JSON.stringify(tradingSystem) + "," + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeStrategiesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeStrategiesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < strategiesArray.length; i++) { - let record = strategiesArray[i]; - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.number + "," + - record.beginRate + "," + - record.endRate + "," + - '"' + record.triggerOnSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeTradesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeTradesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < tradesArray.length; i++) { - - let record = tradesArray[i]; - if (record.stopRate === undefined) { record.stopRate = 0 } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.lastTradeROI + "," + - record.beginRate + "," + - record.endRate + "," + - record.exitType + "," + - '"' + record.takePositionSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } + if (separator === '') { separator = ',' } + fileRecordCounter++ } - function writeDailyFiles() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> Entering function."); } + fileContent = '[' + fileContent + ']' + fileContent = '[' + JSON.stringify(tradingSystem) + ',' + fileContent + ']' - writeRecordsFile(); + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + CONDITIONS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName - function writeRecordsFile() { + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) - try { + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> fileContent = ' + fileContent) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> Entering function."); } + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < recordsArray.length; i++) { - - let record = recordsArray[i]; - - /* Will only add to the file the records of the current day */ - - if (record.begin < currentDay.valueOf()) { continue; } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.balanceBaseAsset + "," + - record.balanceQuotedAsset + "," + - record.accumulatedProfitLoss + "," + - record.lastTradeProfitLoss + "," + - record.stopLoss + "," + - record.tradesCount + "," + - record.hits + "," + - record.fails + "," + - record.hitRatio + "," + - record.ROI + "," + - record.periods + "," + - record.days + "," + - record.anualizedRateOfReturn + "," + - record.positionRate + "," + - record.lastTradeROI + "," + - record.strategy + "," + - record.takeProfit + "," + - record.stopLossPhase + "," + - record.takeProfitPhase + "," + - record.positionSize + "," + - record.initialBalanceA + "," + - record.minimumBalanceA + "," + - record.maximumBalanceA + "," + - record.initialBalanceB + "," + - record.minimumBalanceB + "," + - record.maximumBalanceB + "," + - record.baseAsset + "," + - record.quotedAsset + "," + - record.marketBaseAsset + "," + - record.marketQuotedAsset + "," + - record.positionPeriods + "," + - record.positionDays + "," + - record.distanceToLastTriggerOn + "," + - record.distanceToLastTriggerOff + "," + - record.distanceToLastTakePosition + "," + - record.distanceToLastClosePosition + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeConditionsFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + callBackFunction(err) + return } - function writeConditionsFile() { + writeStrategiesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } - try { + function writeStrategiesFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeStrategiesFile -> Entering function.') } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> Entering function."); } + let separator = '' + let fileRecordCounter = 0 - let separator = ""; - let fileRecordCounter = 0; + let fileContent = '' - let fileContent = ""; + for (let i = 0; i < strategiesArray.length; i++) { + let record = strategiesArray[i] - for (let i = 0; i < conditionsArray.length; i++) { + fileContent = fileContent + separator + '[' + + record.begin + ',' + + record.end + ',' + + record.status + ',' + + record.number + ',' + + record.beginRate + ',' + + record.endRate + ',' + + '"' + record.triggerOnSituation + '"' + ',' + + '"' + record.name + '"' + ']' - let record = conditionsArray[i]; + if (separator === '') { separator = ',' } - /* Will only add to the file the records of the current day */ + fileRecordCounter++ + } - if (record.begin < currentDay.valueOf()) { continue; } + fileContent = '[' + fileContent + ']' - let conditions = ""; - let conditionsSeparator = ""; + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + STRATEGIES_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName - /* Will only add to the file the records of the current day */ + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) - if (record.begin < currentDay.valueOf()) { continue; } + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> fileContent = ' + fileContent) } - for (let j = 0; j < record.length - 3; j++) { - conditions = conditions + conditionsSeparator + record[j]; - if (conditionsSeparator === "") { conditionsSeparator = ","; } - } + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) - conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']'; // The last item contains an Array of condition values. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']'; // The last item contains an Array of formulaErrors. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']'; // The last item contains an Array of formulaValues. - - fileContent = fileContent + separator + '[' + conditions + ']'; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - fileContent = "[" + JSON.stringify(tradingSystem) + "," + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeStrategiesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + callBackFunction(err) + return } - function writeStrategiesFile() { + writeTradesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } - try { + function writeTradesFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeTradesFile -> Entering function.') } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> Entering function."); } + let separator = '' + let fileRecordCounter = 0 - let separator = ""; - let fileRecordCounter = 0; + let fileContent = '' - let fileContent = ""; + for (let i = 0; i < tradesArray.length; i++) { + let record = tradesArray[i] + if (record.stopRate === undefined) { record.stopRate = 0 } - for (let i = 0; i < strategiesArray.length; i++) { - let record = strategiesArray[i]; + fileContent = fileContent + separator + '[' + + record.begin + ',' + + record.end + ',' + + record.status + ',' + + record.lastTradeROI + ',' + + record.beginRate + ',' + + record.endRate + ',' + + record.exitType + ',' + + '"' + record.takePositionSituation + '"' + ']' + + if (separator === '') { separator = ',' } + + fileRecordCounter++ + } + + fileContent = '[' + fileContent + ']' + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + TRADES_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeSnapshotFiles() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeTradesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeSnapshotFiles () { + writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish) + + function onFinish () { + writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction) + } + } + + function writeSnapshotFile (snapshotArray, pFileName, callBack) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeSnapshotFile -> Entering function.') } + + let fileRecordCounter = 0 + + let fileContent = '' + let separator = '\r\n' + + parseRecord(snapshotHeaders) + + for (let i = 0; i < snapshotArray.length; i++) { + let record = snapshotArray[i] + parseRecord(record) + fileRecordCounter++ + } + + function parseRecord (record) { + for (let j = 0; j < record.length; j++) { + let property = record[j] + + fileContent = fileContent + '' + property + if (j !== record.length - 1) { + fileContent = fileContent + ',' + } + } + fileContent = fileContent + separator + } + + fileContent = '' + fileContent + '' + + let fileName = pFileName + '.csv' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SNAPSHOTS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + callBack(global.DEFAULT_OK_RESPONSE) + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function writeDailyFiles () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> Entering function.') } + + writeRecordsFile() + + function writeRecordsFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeRecordsFile -> Entering function.') } + + let separator = '' + let fileRecordCounter = 0 + + let fileContent = '' + + for (let i = 0; i < recordsArray.length; i++) { + let record = recordsArray[i] + + /* Will only add to the file the records of the current day */ + + if (record.begin < currentDay.valueOf()) { continue } + + fileContent = fileContent + separator + '[' + + record.begin + ',' + + record.end + ',' + + record.balanceBaseAsset + ',' + + record.balanceQuotedAsset + ',' + + record.accumulatedProfitLoss + ',' + + record.lastTradeProfitLoss + ',' + + record.stopLoss + ',' + + record.tradesCount + ',' + + record.hits + ',' + + record.fails + ',' + + record.hitRatio + ',' + + record.ROI + ',' + + record.periods + ',' + + record.days + ',' + + record.anualizedRateOfReturn + ',' + + record.positionRate + ',' + + record.lastTradeROI + ',' + + record.strategy + ',' + + record.takeProfit + ',' + + record.stopLossPhase + ',' + + record.takeProfitPhase + ',' + + record.positionSize + ',' + + record.initialBalanceA + ',' + + record.minimumBalanceA + ',' + + record.maximumBalanceA + ',' + + record.initialBalanceB + ',' + + record.minimumBalanceB + ',' + + record.maximumBalanceB + ',' + + record.baseAsset + ',' + + record.quotedAsset + ',' + + record.marketBaseAsset + ',' + + record.marketQuotedAsset + ',' + + record.positionPeriods + ',' + + record.positionDays + ',' + + record.distanceToLastTriggerOn + ',' + + record.distanceToLastTriggerOff + ',' + + record.distanceToLastTakePosition + ',' + + record.distanceToLastClosePosition + ']' + + if (separator === '') { separator = ',' } + + fileRecordCounter++ + } + + fileContent = '[' + fileContent + ']' + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SIMULATED_RECORDS_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeConditionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeConditionsFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeConditionsFile -> Entering function.') } + + let separator = '' + let fileRecordCounter = 0 + + let fileContent = '' + + for (let i = 0; i < conditionsArray.length; i++) { + let record = conditionsArray[i] + + /* Will only add to the file the records of the current day */ + + if (record.begin < currentDay.valueOf()) { continue } + + let conditions = '' + let conditionsSeparator = '' + + /* Will only add to the file the records of the current day */ + + if (record.begin < currentDay.valueOf()) { continue } + + for (let j = 0; j < record.length - 3; j++) { + conditions = conditions + conditionsSeparator + record[j] + if (conditionsSeparator === '') { conditionsSeparator = ',' } + } + + conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']' // The last item contains an Array of condition values. + conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']' // The last item contains an Array of formulaErrors. + conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']' // The last item contains an Array of formulaValues. + + fileContent = fileContent + separator + '[' + conditions + ']' + + if (separator === '') { separator = ',' } + + fileRecordCounter++ + } + + fileContent = '[' + fileContent + ']' + fileContent = '[' + JSON.stringify(tradingSystem) + ',' + fileContent + ']' + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + CONDITIONS_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeStrategiesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeStrategiesFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeStrategiesFile -> Entering function.') } + + let separator = '' + let fileRecordCounter = 0 + + let fileContent = '' + + for (let i = 0; i < strategiesArray.length; i++) { + let record = strategiesArray[i] /* Will only add to the file the records of the current day. In this case since objects can span more than one day, we add all of the objects that ends at the current date. */ - if (record.end < currentDay.valueOf()) { continue; } + if (record.end < currentDay.valueOf()) { continue } - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.number + "," + - record.beginRate + "," + - record.endRate + "," + - '"' + record.triggerOnSituation + '"' + "]"; + fileContent = fileContent + separator + '[' + + record.begin + ',' + + record.end + ',' + + record.status + ',' + + record.number + ',' + + record.beginRate + ',' + + record.endRate + ',' + + '"' + record.triggerOnSituation + '"' + ',' + + '"' + record.name + '"' + ']' - if (separator === "") { separator = ","; } + if (separator === '') { separator = ',' } - fileRecordCounter++; + fileRecordCounter++ + } - } + fileContent = '[' + fileContent + ']' - fileContent = "[" + fileContent + "]"; + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + STRATEGIES_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> fileContent = ' + fileContent) } - function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeTradesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + callBackFunction(err) + return } - function writeTradesFile() { + writeTradesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } - try { + function writeTradesFile () { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeTradesFile -> Entering function.') } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> Entering function."); } + let separator = '' + let fileRecordCounter = 0 - let separator = ""; - let fileRecordCounter = 0; + let fileContent = '' - let fileContent = ""; - - for (let i = 0; i < tradesArray.length; i++) { - - let record = tradesArray[i]; + for (let i = 0; i < tradesArray.length; i++) { + let record = tradesArray[i] /* Will only add to the file the records of the current day. In this case since objects can span more than one day, we add all of the objects that ends at the current date. */ - if (record.end < currentDay.valueOf()) { continue; } - if (record.stopRate === undefined) { record.stopRate = 0 } + if (record.end < currentDay.valueOf()) { continue } + if (record.stopRate === undefined) { record.stopRate = 0 } - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.lastTradeROI + "," + - record.beginRate + "," + - record.endRate + "," + - record.exitType + "," + - '"' + record.takePositionSituation + '"' + "]"; + fileContent = fileContent + separator + '[' + + record.begin + ',' + + record.end + ',' + + record.status + ',' + + record.lastTradeROI + ',' + + record.beginRate + ',' + + record.endRate + ',' + + record.exitType + ',' + + '"' + record.takePositionSituation + '"' + ']' - if (separator === "") { separator = ","; } + if (separator === '') { separator = ',' } - fileRecordCounter++; + fileRecordCounter++ + } - } + fileContent = '[' + fileContent + ']' - fileContent = "[" + fileContent + "]"; + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + TRADES_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> fileContent = ' + fileContent) } - function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + callBackFunction(err) + return } + writeSnapshotFiles() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeTradesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + + function writeSnapshotFiles () { + writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish) + + function onFinish () { + writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction) + } } + + function writeSnapshotFile (snapshotArray, pFileName, callBack) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeSnapshotFile -> Entering function.') } + + let fileRecordCounter = 0 + + let fileContent = '' + let separator = '\r\n' + + parseRecord(snapshotHeaders) + + for (let i = 0; i < snapshotArray.length; i++) { + let record = snapshotArray[i] + parseRecord(record) + fileRecordCounter++ + } + + function parseRecord (record) { + for (let j = 0; j < record.length; j++) { + let property = record[j] + + fileContent = fileContent + '' + property + if (j !== record.length - 1) { + fileContent = fileContent + ',' + } + } + fileContent = fileContent + separator + } + + fileContent = '' + fileContent + '' + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = pFileName + '.csv' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SNAPSHOTS_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated (err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + callBack(global.DEFAULT_OK_RESPONSE) + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) } -}; + } +} diff --git a/TradingSimulation.js b/TradingSimulation.js index 7884a8079..d79e83e98 100644 --- a/TradingSimulation.js +++ b/TradingSimulation.js @@ -1,26 +1,25 @@ -exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) { +exports.newTradingSimulation = function newTradingSimulation (bot, logger, UTILITIES) { + const FULL_LOG = true + const LOG_FILE_CONTENT = false + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000 + const MODULE_NAME = 'Trading Simulation -> ' + bot.SESSION.name - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; - const MODULE_NAME = "Trading Simulation -> " + bot.SESSION.name; - - const GMT_SECONDS = ':00.000 GMT+0000'; + const GMT_SECONDS = ':00.000 GMT+0000' - let thisObject = { - finalize: finalize, - runSimulation: runSimulation - }; + let thisObject = { + finalize: finalize, + runSimulation: runSimulation + } - let utilities = UTILITIES.newCloudUtilities(bot, logger); + let utilities = UTILITIES.newCloudUtilities(bot, logger) - return thisObject; + return thisObject - function finalize() { - thisObject = undefined - } + function finalize () { + thisObject = undefined + } - function runSimulation( + function runSimulation ( chart, dataDependencies, timeFrame, @@ -30,1489 +29,1468 @@ exchangeAPI, callback, callBackFunction) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> Entering function.') } + let processingDailyFiles + if (timeFrame > global.dailyFilePeriods[0][0]) { + processingDailyFiles = false + } else { + processingDailyFiles = true + } - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> Entering function."); } - let processingDailyFiles - if (timeFrame > global.dailyFilePeriods[0][0]) { - processingDailyFiles = false - } else { - processingDailyFiles = true - } + let recordsArray = [] + let conditionsArray = [] + let strategiesArray = [] + let tradesArray = [] - let recordsArray = []; - let conditionsArray = []; - let strategiesArray = []; - let tradesArray = []; + let tradingSystem = bot.TRADING_SYSTEM - let tradingSystem = bot.TRADING_SYSTEM + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.initialDatetime = ' + bot.VALUES_TO_USE.timeRange.initialDatetime) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.finalDatetime = ' + bot.VALUES_TO_USE.timeRange.finalDatetime) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.initialDatetime = " + bot.VALUES_TO_USE.timeRange.initialDatetime); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.finalDatetime = " + bot.VALUES_TO_USE.timeRange.finalDatetime); } - - let timerToCloseStage = 0 + let timerToCloseStage = 0 /* Stop Loss Management */ - const MIN_STOP_LOSS_VALUE = 1 // 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 + + 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 */ - const MIN_TAKE_PROFIT_VALUE = 1 // 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 + + 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 /* Variables for this executioin only */ - let takePositionNow = false - let closePositionNow = false + let takePositionNow = false + let closePositionNow = false /* In some cases we need to know if we are positioned at the last candle of the calendar day, for that we need these variables. */ - let lastInstantOfTheDay = 0 - if (currentDay) { - lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1; - } + let lastInstantOfTheDay = 0 + if (currentDay) { + lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1 + } - if (variable.isInitialized !== true) { + if (variable.isInitialized !== true) { + variable.isInitialized = true - variable.isInitialized = true + variable.episode = {} // An Episode represents each execution of the Simulation + variable.episode.count = {} // Everything in here means the total count since the begining of the episode. + variable.episode.stat = {} // Everything in here means the statistics calculated since the begining of the episode. - variable.episode = {} // An Episode represents each execution of the Simulation - variable.episode.count = {} // Everything in here means the total count since the begining of the episode. - variable.episode.stat = {} // Everything in here means the statistics calculated since the begining of the episode. + variable.current = {} // Everything in here means that is happening right now. + variable.current.balance = {} // Balances at the current candle of the Simulation. + variable.current.distance = {} // Everything in here means the distance, measured in periods to whatever happened in the past, usually events. + variable.current.distance.toEvent = {} // Distance to Events that already happened. + variable.current.strategy = {} // Everything in here are variables related to the current strategy being executed. + variable.current.position = {} // Everything in here are variables related to the current position being executed. - variable.current = {} // Everything in here means that is happening right now. - variable.current.balance = {} // Balances at the current candle of the Simulation. - variable.current.distance = {} // Everything in here means the distance, measured in periods to whatever happened in the past, usually events. - variable.current.distance.toEvent = {} // Distance to Events that already happened. - variable.current.strategy = {} // Everything in here are variables related to the current strategy being executed. - variable.current.position = {} // Everything in here are variables related to the current position being executed. + variable.current.balance.baseAsset = bot.VALUES_TO_USE.initialBalanceA + variable.current.balance.quotedAsset = bot.VALUES_TO_USE.initialBalanceB + variable.current.distance.toEvent.triggerOn = 0 + variable.current.distance.toEvent.triggerOff = 0 + variable.current.distance.toEvent.takePosition = 0 + variable.current.distance.toEvent.closePosition = 0 - variable.current.balance.baseAsset = bot.VALUES_TO_USE.initialBalanceA - variable.current.balance.quotedAsset = bot.VALUES_TO_USE.initialBalanceB - variable.current.distance.toEvent.triggerOn = 0 - variable.current.distance.toEvent.triggerOff = 0 - variable.current.distance.toEvent.takePosition = 0 - variable.current.distance.toEvent.closePosition = 0 + variable.last = {} // Everything in here means that it already happened. + variable.last.position = {} // Las values of variables at the last position. - variable.last = {} // Everything in here means that it already happened. - variable.last.position = {} // Las values of variables at the last position. + variable.last.position.profitLoss = 0 + variable.last.position.ROI = 0 - variable.last.position.profitLoss = 0 - variable.last.position.ROI = 0 + variable.previous = {} // Everything in here means the the value that was current before a new current value was set. + variable.previous.balance = {} // Balances values previous to the current ones. - variable.previous = {} // Everything in here means the the value that was current before a new current value was set. - variable.previous.balance = {} // Balances values previous to the current ones. + variable.previous.balance.baseAsset = 0 + variable.previous.balance.quotedAsset = 0 - variable.previous.balance.baseAsset = 0 - variable.previous.balance.quotedAsset = 0 + variable.episode.parameters = {} // Parameters give you access to information received by the Simulation as parameters. + variable.episode.parameters.initial = {} // Everything in here means the status at the begining of the episode received as a parameter of the Sumulation. + variable.episode.parameters.minimun = {} // Everything in here means the minimun in the whole episode received as a parameter of the Sumulation. + variable.episode.parameters.maximun = {} // Everything in here means the maximun in the whole episode received as a parameter of the Sumulation. + variable.episode.parameters.initial.balance = {} + variable.episode.parameters.minimun.balance = {} + variable.episode.parameters.maximun.balance = {} - variable.episode.parameters = {} // Parameters give you access to information received by the Simulation as parameters. - variable.episode.parameters.initial = {} // Everything in here means the status at the begining of the episode received as a parameter of the Sumulation. - variable.episode.parameters.minimun = {} // Everything in here means the minimun in the whole episode received as a parameter of the Sumulation. - variable.episode.parameters.maximun = {} // Everything in here means the maximun in the whole episode received as a parameter of the Sumulation. - variable.episode.parameters.initial.balance = {} - variable.episode.parameters.minimun.balance = {} - variable.episode.parameters.maximun.balance = {} + variable.episode.parameters.baseAsset = bot.VALUES_TO_USE.baseAsset + variable.episode.parameters.quotedAsset = bot.VALUES_TO_USE.quotedAsset + variable.episode.parameters.marketBaseAsset = bot.market.baseAsset + variable.episode.parameters.marketQuotedAsset = bot.market.quotedAsset - variable.episode.parameters.baseAsset = bot.VALUES_TO_USE.baseAsset - variable.episode.parameters.quotedAsset = bot.VALUES_TO_USE.quotedAsset - variable.episode.parameters.marketBaseAsset = bot.market.baseAsset - variable.episode.parameters.marketQuotedAsset = bot.market.quotedAsset + variable.episode.parameters.initial.balance.baseAsset = bot.VALUES_TO_USE.initialBalanceA + variable.episode.parameters.minimum.balance.baseAsset = bot.VALUES_TO_USE.minimumBalanceA + variable.episode.parameters.maximum.balance.baseAsset = bot.VALUES_TO_USE.maximumBalanceA - variable.episode.parameters.initial.balance.baseAsset = bot.VALUES_TO_USE.initialBalanceA - variable.episode.parameters.minimum.balance.baseAsset = bot.VALUES_TO_USE.minimumBalanceA - variable.episode.parameters.maximum.balance.baseAsset = bot.VALUES_TO_USE.maximumBalanceA + variable.episode.parameters.initial.balance.quotedAsset = bot.VALUES_TO_USE.initialBalanceB + variable.episode.parameters.minimum.balance.quotedAsset = bot.VALUES_TO_USE.minimumBalanceB + variable.episode.parameters.maximum.balance.quotedAsset = bot.VALUES_TO_USE.maximumBalanceB - variable.episode.parameters.initial.balance.quotedAsset = bot.VALUES_TO_USE.initialBalanceB - variable.episode.parameters.minimum.balance.quotedAsset = bot.VALUES_TO_USE.minimumBalanceB - variable.episode.parameters.maximum.balance.quotedAsset = bot.VALUES_TO_USE.maximumBalanceB + variable.episode.count.positions = 0 + variable.episode.count.fails = 0 + variable.episode.count.hits = 0 + variable.episode.count.periods = 0 - variable.episode.count.positions = 0 - variable.episode.count.fails = 0 - variable.episode.count.hits = 0 - variable.episode.count.periods = 0 + variable.episode.stat.profitLoss = 0 + variable.episode.stat.hitRatio = 0 + variable.episode.stat.days = 0 + variable.episode.stat.ROI = 0 + variable.episode.stat.anualizedRateOfReturn = 0 - variable.episode.stat.profitLoss = 0 - variable.episode.stat.hitRatio = 0 - variable.episode.stat.days = 0 - variable.episode.stat.ROI = 0 - variable.episode.stat.anualizedRateOfReturn = 0 - - inializeCurrentStrategy() - initializeCurrentPosition() + inializeCurrentStrategy() + initializeCurrentPosition() - variable.announcements = [] - } + variable.announcements = [] + } - function inializeCurrentStrategy() { - variable.current.strategy = { - index: -1, - stage: 'No Stage', - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - situationName: '' - } - } + function inializeCurrentStrategy () { + variable.current.strategy = { + index: -1, + stage: 'No Stage', + begin: 0, + end: 0, + status: 0, + number: 0, + beginRate: 0, + endRate: 0, + situationName: '' + } + } - function initializeCurrentPosition() { - variable.current.position = { - begin: 0, - end: 0, - rate: 0, - size: 0, - stopLoss: 0, - takeProfit: 0, - status: 0, - profit: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - periods: 0, - situationName: '', - stopLossPhase: -1, - stopLossStage: 'No Stage', - takeProfitPhase: -1, - takeProfitStage: 'No Stage' - } - } + function initializeCurrentPosition () { + variable.current.position = { + begin: 0, + end: 0, + rate: 0, + size: 0, + stopLoss: 0, + takeProfit: 0, + status: 0, + profit: 0, + exitType: 0, + beginRate: 0, + endRate: 0, + periods: 0, + situationName: '', + stopLossPhase: -1, + stopLossStage: 'No Stage', + takeProfitPhase: -1, + takeProfitStage: 'No Stage' + } + } /* Main Array and Maps */ - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - let candles = chart[propertyName].candles - let currentChart = chart[propertyName] + let propertyName = 'at' + timeFrameLabel.replace('-', '') + let candles = chart[propertyName].candles + let currentChart = chart[propertyName] /* Last Candle */ - let lastCandle = candles[candles.length - 1]; + let lastCandle = candles[candles.length - 1] /* Main Simulation Loop: We go thourgh all the candles at this time period. */ - let currentCandleIndex + let currentCandleIndex /* For Loop Level heartbeat */ - let loopingDay - let previousLoopingDay + let loopingDay + let previousLoopingDay - initializeLoop() + initializeLoop() - function initializeLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> initializeLoop -> Entering function."); } + function initializeLoop () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initializeLoop -> Entering function.') } /* Estimate Initial Candle */ - let firstEnd = candles[0].end - let targetEnd = bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf() - let diff = targetEnd - firstEnd - let amount = diff / timeFrame + let firstEnd = candles[0].end + let targetEnd = bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf() + let diff = targetEnd - firstEnd + let amount = diff / timeFrame - currentCandleIndex = Math.trunc(amount) - if (currentCandleIndex < 0) { currentCandleIndex = 0 } - if (currentCandleIndex > candles.length - 1) { + currentCandleIndex = Math.trunc(amount) + if (currentCandleIndex < 0) { currentCandleIndex = 0 } + if (currentCandleIndex > candles.length - 1) { /* This will happen when the bot.VALUES_TO_USE.timeRange.initialDatetime is beyond the last candle available, meaning that the dataSet needs to be updated with more up-to-date data. */ - currentCandleIndex = candles.length - 1 - } + currentCandleIndex = candles.length - 1 + } - loop() - } + loop() + } - function loop() { + function loop () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Entering function.') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + currentCandleIndex) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Entering function."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Processing candle # " + currentCandleIndex); } + /* We are going to take snapshots of the values of indicators at key moments of the sumulation. */ - let announcementsToBeMade = [] - let candle = candles[currentCandleIndex]; + let snapshotKeys = new Map() + snapshotLoopHeaders = [] + snapshotDataRecord = [] + saveAsLastTriggerOnSnapshot = false + saveAsLastTakePositionSnapshot = false + addToSnapshots = false + + let announcementsToBeMade = [] + let candle = candles[currentCandleIndex] /* Not processing while out of user-defined time range */ - if (candle.end < bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle before the bot.VALUES_TO_USE.timeRange.initialDatetime."); } - controlLoop(); - return - } - if (candle.begin > bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle after the bot.VALUES_TO_USE.timeRange.finalDatetime."); } - afterLoop(); - return - } + if (candle.end < bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle before the bot.VALUES_TO_USE.timeRange.initialDatetime.') } + controlLoop() + return + } + if (candle.begin > bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle after the bot.VALUES_TO_USE.timeRange.finalDatetime.') } + afterLoop() + return + } /* Here we update at the chart data structure the objects for each product representing where we are currently standing at the simulation loop */ - if (processingDailyFiles) { - for (let j = 0; j < global.dailyFilePeriods.length; j++) { + if (processingDailyFiles) { + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + let mapKey = dailyFilePeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', '') + let thisChart = chart[propertyName] - let mapKey = dailyFilePeriods[j][1] - let propertyName = 'at' + mapKey.replace('-', ''); - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Daily') { continue } - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], candle, 'Daily' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement - } - } - } + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Daily') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName + let currentElement = getElement(thisChart[pluralVariableName], candle, 'Daily' + '-' + mapKey + '-' + pluralVariableName) + thisChart[singularVariableName] = currentElement + } + } + } /* Finding the Current Element on Market Files */ - for (let j = 0; j < global.marketFilesPeriods.length; j++) { + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + let mapKey = marketFilesPeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', '') + let thisChart = chart[propertyName] - let mapKey = marketFilesPeriods[j][1] - let propertyName = 'at' + mapKey.replace('-', ''); - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Market') {continue} - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], candle, 'Market' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement - } - } + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Market') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName + let currentElement = getElement(thisChart[pluralVariableName], candle, 'Market' + '-' + mapKey + '-' + pluralVariableName) + thisChart[singularVariableName] = currentElement + } + } /* Finding the Current Element on Single Files */ - function isItInside(elementWithTimestamp, elementWithBeginEnd) { - if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) { - return true - } else { - return false - } - } + function isItInside (elementWithTimestamp, elementWithBeginEnd) { + if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) { + return true + } else { + return false + } + } - let propertyName = 'atAnyTimeFrame' - let thisChart = chart[propertyName] + let propertyName = 'atAnyTimeFrame' + let thisChart = chart[propertyName] - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Single-File') { continue } - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let elementArray = thisChart[pluralVariableName] - let currentElement - if (elementArray !== undefined) { - currentElement = elementArray[elementArray.length - 1] - } - thisChart[singularVariableName] = currentElement - } + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.code.codeName !== 'Single-File') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName + let elementArray = thisChart[pluralVariableName] + let currentElement + if (elementArray !== undefined) { + currentElement = elementArray[elementArray.length - 1] + } + thisChart[singularVariableName] = currentElement + } /* While we are processing the previous day. */ - let positionedAtYesterday = false - if (currentDay) { - positionedAtYesterday = (candle.end < currentDay.valueOf()) - } + let positionedAtYesterday = false + if (currentDay) { + positionedAtYesterday = (candle.end < currentDay.valueOf()) + } - 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()) } + 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()) } - let ticker = { - bid: candle.close, - ask: candle.close, - last: candle.close - } + let ticker = { + bid: candle.close, + ask: candle.close, + last: candle.close + } /* We will produce a simulation level heartbeat in order to inform the user this is running. */ - loopingDay = new Date(Math.trunc(candle.begin / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - if (loopingDay.valueOf() !== previousLoopingDay) { + loopingDay = new Date(Math.trunc(candle.begin / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) + if (loopingDay.valueOf() !== previousLoopingDay) { + let processingDate = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2) - let processingDate = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Simulation " + bot.sessionKey + " Loop # " + currentCandleIndex + " @ " + processingDate) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + currentCandleIndex + ' @ ' + processingDate) } /* Telling the world we are alive and doing well */ - let fromDate = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) - let lastDate = new Date(bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) + let fromDate = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) + let lastDate = new Date(bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) - let currentDateString = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2); - let currentDate = new Date(loopingDay) - let percentage = global.getPercentage(fromDate, currentDate, lastDate) - bot.processHeartBeat(currentDateString, percentage) + let currentDateString = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2) + let currentDate = new Date(loopingDay) + let percentage = global.getPercentage(fromDate, currentDate, lastDate) + bot.processHeartBeat(currentDateString, percentage) - if (global.areEqualDates(currentDate, new Date()) === false) { - logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage); - } - } - previousLoopingDay = loopingDay.valueOf() + if (global.areEqualDates(currentDate, new Date()) === false) { + logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage) + } + } + previousLoopingDay = loopingDay.valueOf() - variable.episode.count.periods++; - variable.episode.stat.days = variable.episode.count.periods * timeFrame / ONE_DAY_IN_MILISECONDS; - - if (processingDailyFiles) { + variable.episode.count.periods++ + variable.episode.stat.days = variable.episode.count.periods * timeFrame / ONE_DAY_IN_MILISECONDS + if (processingDailyFiles) { /* We skip the candle at the head of the market because currentCandleIndex has not closed yet. */ - let candlesPerDay = ONE_DAY_IN_MILISECONDS / timeFrame - if (currentCandleIndex === candles.length - 1) { - if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { - /*We are at the head of the market, thus we skip the last candle because it has not close yet. */ - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet."); } - controlLoop(); - return + let candlesPerDay = ONE_DAY_IN_MILISECONDS / timeFrame + if (currentCandleIndex === candles.length - 1) { + if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { + /* We are at the head of the market, thus we skip the last candle because it has not close yet. */ + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet.') } + controlLoop() + return /* 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 variable.episode.count.periods, then there will be a hole in the file since the last period will be missing. */ - } - } + } + } + } else { // We are processing Market Files + if (currentCandleIndex === candles.length - 1) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet.') } + controlLoop() + return + } + } - } else { // We are processing Market Files - if (currentCandleIndex === candles.length - 1) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet."); } - controlLoop(); - return - } - } - - let conditions = new Map; // Here we store the conditions values that will be use in the simulator for decision making. - let formulas = new Map; - let conditionsArrayRecord = []; // These are the records that will be saved in a file for the plotter to consume. - let conditionsArrayValues = []; // Here we store the conditions values that will be written on file for the plotter. - let formulasErrors = []; // Here we store the errors produced by all phase formulas. - let formulasValues = []; // Here we store the values produced by all phase formulas. + let conditions = new Map() // Here we store the conditions values that will be use in the simulator for decision making. + let formulas = new Map() + let conditionsArrayRecord = [] // These are the records that will be saved in a file for the plotter to consume. + let conditionsArrayValues = [] // Here we store the conditions values that will be written on file for the plotter. + let formulasErrors = [] // Here we store the errors produced by all phase formulas. + let formulasValues = [] // Here we store the values produced by all phase formulas. /* We define and evaluate all conditions to be used later during the simulation loop. */ - conditionsArrayRecord.push(candle.begin); - conditionsArrayRecord.push(candle.end); + conditionsArrayRecord.push(candle.begin) + conditionsArrayRecord.push(candle.end) - for (let j = 0; j < tradingSystem.strategies.length; j++) { + for (let j = 0; j < tradingSystem.strategies.length; j++) { + let strategy = tradingSystem.strategies[j] - let strategy = tradingSystem.strategies[j]; - - let positionSize = 0 - let positionRate = 0 + let positionSize = 0 + let positionRate = 0 /* Continue with the rest of the formulas and conditions */ - let triggerStage = strategy.triggerStage + let triggerStage = strategy.triggerStage - if (triggerStage !== undefined) { + if (triggerStage !== undefined) { + if (triggerStage.triggerOn !== undefined) { + for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { + let situation = triggerStage.triggerOn.situations[k] - if (triggerStage.triggerOn !== undefined) { + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m - for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } - let situation = triggerStage.triggerOn.situations[k]; + if (triggerStage.triggerOff !== undefined) { + for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { + let situation = triggerStage.triggerOff.situations[k] - for (let m = 0; m < situation.conditions.length; m++) { + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m; + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } + if (triggerStage.takePosition !== undefined) { + for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { + let situation = triggerStage.takePosition.situations[k] - if (triggerStage.triggerOff !== undefined) { + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m - for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } - let situation = triggerStage.triggerOff.situations[k]; + let openStage = strategy.openStage - for (let m = 0; m < situation.conditions.length; m++) { + if (openStage !== undefined) { + /* Default Values */ + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + positionSize = variable.current.balance.baseAsset + positionRate = candle.close + } else { + positionSize = variable.current.balance.quotedAsset + positionRate = candle.close + } - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m; + let initialDefinition = openStage.initialDefinition - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - if (triggerStage.takePosition !== undefined) { - - for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { - - let situation = triggerStage.takePosition.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - - let openStage = strategy.openStage - - if (openStage !== undefined) { - - /* Default Values*/ + if (initialDefinition !== undefined) { + if (variable.current.position.size !== 0) { + positionSize = variable.current.position.size + } else { + if (initialDefinition.positionSize !== undefined) { + if (initialDefinition.positionSize.formula !== undefined) { + try { + let code = initialDefinition.positionSize.formula.code + positionSize = eval(code) + addCodeToSnapshot(code) + } catch (err) { + initialDefinition.positionSize.formula.error = err.message + } + if (isNaN(positionSize)) { if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - positionSize = variable.current.balance.baseAsset; - positionRate = candle.close; - } else { - positionSize = variable.current.balance.quotedAsset; - positionRate = candle.close; - } + positionSize = variable.current.balance.baseAsset + } else { + positionSize = variable.current.balance.quotedAsset + } + } else { + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + if (positionSize > variable.current.balance.baseAsset) { positionSize = variable.current.balance.baseAsset } + } else { + if (positionSize > variable.current.balance.quotedAsset) { positionSize = variable.current.balance.quotedAsset } + } + } + } + } + } - let initialDefinition = openStage.initialDefinition + if (variable.current.position.rate !== 0) { + positionRate = variable.current.position.rate + } else { + if (initialDefinition.positionRate !== undefined) { + if (initialDefinition.positionRate.formula !== undefined) { + try { + let code = initialDefinition.positionRate.formula.code + positionRate = eval(initialDefinition.positionRate.formula.code) + addCodeToSnapshot(code) + } catch (err) { + initialDefinition.positionRate.formula.error = err.message + } + if (isNaN(positionRate)) { + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + positionRate = candle.close + } else { + positionRate = candle.close + } + } + } + } + } - if (initialDefinition !== undefined) { - - if (variable.current.position.size !== 0) { - positionSize = variable.current.position.size - } else { - if (initialDefinition.positionSize !== undefined) { - if (initialDefinition.positionSize.formula !== undefined) { - try { - positionSize = eval(initialDefinition.positionSize.formula.code); - } catch (err) { - initialDefinition.positionSize.formula.error = err.message - } - if (isNaN(positionSize)) { - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - positionSize = variable.current.balance.baseAsset; - } else { - positionSize = variable.current.balance.quotedAsset; - } - } else { - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - if (positionSize > variable.current.balance.baseAsset) { positionSize = variable.current.balance.baseAsset } - } else { - if (positionSize > variable.current.balance.quotedAsset) { positionSize = variable.current.balance.quotedAsset } - } - } - } - } - } - - if (variable.current.position.rate !== 0) { - positionRate = variable.current.position.rate - } else { - if (initialDefinition.positionRate !== undefined) { - if (initialDefinition.positionRate.formula !== undefined) { - try { - positionRate = eval(initialDefinition.positionRate.formula.code); - } catch (err) { - initialDefinition.positionRate.formula.error = err.message - } - if (isNaN(positionRate)) { - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - positionRate = candle.close; - } else { - positionRate = candle.close; - } - } - } - } - } - - if (initialDefinition.stopLoss !== undefined) { - - for (let p = 0; p < initialDefinition.stopLoss.phases.length; p++) { - - let phase = initialDefinition.stopLoss.phases[p]; + if (initialDefinition.stopLoss !== undefined) { + for (let p = 0; p < initialDefinition.stopLoss.phases.length; p++) { + let phase = initialDefinition.stopLoss.phases[p] /* Evaluate Formula */ - let formulaValue - let formulaError = '' + let formulaValue + let formulaError = '' - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaError = "Formula evaluates to Infinity." - formulaValue = MAX_STOP_LOSS_VALUE - if (stopLossStage === 'Open Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaError = 'Formula evaluates to Infinity.' + formulaValue = MAX_STOP_LOSS_VALUE + if (stopLossStage === 'Open Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { /* We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_STOP_LOSS_VALUE) { - formulaValue = MIN_STOP_LOSS_VALUE - if (stopLossStage === 'Open Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_STOP_LOSS_VALUE) { + formulaValue = MIN_STOP_LOSS_VALUE + if (stopLossStage === 'Open Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p; - formulas.set(key, formulaValue) - } + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + formulas.set(key, formulaValue) + } /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - let situation = nextPhaseEvent.situations[k]; + let situation = nextPhaseEvent.situations[k] - for (let m = 0; m < situation.conditions.length; m++) { + for (let m = 0; m < situation.conditions.length; m++) { - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { + 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++) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - let situation = moveToPhaseEvent.situations[k]; + let situation = moveToPhaseEvent.situations[k] - for (let m = 0; m < situation.conditions.length; m++) { + for (let m = 0; m < situation.conditions.length; m++) { - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } - if (initialDefinition.takeProfit !== undefined) { - - for (let p = 0; p < initialDefinition.takeProfit.phases.length; p++) { - - let phase = initialDefinition.takeProfit.phases[p]; + if (initialDefinition.takeProfit !== undefined) { + for (let p = 0; p < initialDefinition.takeProfit.phases.length; p++) { + let phase = initialDefinition.takeProfit.phases[p] /* Evaluate Formula */ - let formulaValue - let formulaError = '' + let formulaValue + let formulaError = '' - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaValue = MAX_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Open Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaValue = MAX_TAKE_PROFIT_VALUE + if (takeProfitStage === 'Open Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { /* We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_TAKE_PROFIT_VALUE) { - formulaValue = MIN_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Open Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_TAKE_PROFIT_VALUE) { + formulaValue = MIN_TAKE_PROFIT_VALUE + if (takeProfitStage === 'Open Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p; - formulas.set(key, formulaValue) - } + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + formulas.set(key, formulaValue) + } /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - let situation = nextPhaseEvent.situations[k]; + let situation = nextPhaseEvent.situations[k] - for (let m = 0; m < situation.conditions.length; m++) { + for (let m = 0; m < situation.conditions.length; m++) { - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { + 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++) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - let situation = moveToPhaseEvent.situations[k]; + let situation = moveToPhaseEvent.situations[k] - for (let m = 0; m < situation.conditions.length; m++) { + for (let m = 0; m < situation.conditions.length; m++) { - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } + } - strategy.positionSize = positionSize - strategy.positionRate = positionRate - } + strategy.positionSize = positionSize + strategy.positionRate = positionRate + } - let manageStage = strategy.manageStage + let manageStage = strategy.manageStage - if (manageStage !== undefined) { - - if (manageStage.stopLoss !== undefined) { - - for (let p = 0; p < manageStage.stopLoss.phases.length; p++) { - - let phase = manageStage.stopLoss.phases[p]; + if (manageStage !== undefined) { + if (manageStage.stopLoss !== undefined) { + for (let p = 0; p < manageStage.stopLoss.phases.length; p++) { + let phase = manageStage.stopLoss.phases[p] /* Evaluate Formula */ - let formulaValue - let formulaError = '' + let formulaValue + let formulaError = '' - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaError = "" - formulaValue = MAX_STOP_LOSS_VALUE - if (stopLossStage === 'Manage Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaError = '' + formulaValue = MAX_STOP_LOSS_VALUE + if (stopLossStage === 'Manage Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { /* We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_STOP_LOSS_VALUE) { - formulaValue = MIN_STOP_LOSS_VALUE - if (stopLossStage === 'Manage Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_STOP_LOSS_VALUE) { + formulaValue = MIN_STOP_LOSS_VALUE + if (stopLossStage === 'Manage Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p; - formulas.set(key, formulaValue) - } + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + formulas.set(key, formulaValue) + } /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] - let situation = nextPhaseEvent.situations[k]; + for (let m = 0; m < situation.conditions.length; m++) { - for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { + 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++) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - let situation = moveToPhaseEvent.situations[k]; + let situation = moveToPhaseEvent.situations[k] - for (let m = 0; m < situation.conditions.length; m++) { + for (let m = 0; m < situation.conditions.length; m++) { - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } - if (manageStage.takeProfit !== undefined) { - - for (let p = 0; p < manageStage.takeProfit.phases.length; p++) { - - let phase = manageStage.takeProfit.phases[p]; + if (manageStage.takeProfit !== undefined) { + for (let p = 0; p < manageStage.takeProfit.phases.length; p++) { + let phase = manageStage.takeProfit.phases[p] /* Evaluate Formula */ - let formulaValue - let formulaError = '' + let formulaValue + let formulaError = '' - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaError = "Formula evaluates to Infinity." - formulaValue = MAX_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Manage Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaError = 'Formula evaluates to Infinity.' + formulaValue = MAX_TAKE_PROFIT_VALUE + if (takeProfitStage === 'Manage Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { /* We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_TAKE_PROFIT_VALUE) { - formulaValue = MIN_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Manage Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_TAKE_PROFIT_VALUE) { + formulaValue = MIN_TAKE_PROFIT_VALUE + if (takeProfitStage === 'Manage Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p; - formulas.set(key, formulaValue) - } + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + formulas.set(key, formulaValue) + } /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] - let situation = nextPhaseEvent.situations[k]; + for (let m = 0; m < situation.conditions.length; m++) { - for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { + 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++) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - let situation = moveToPhaseEvent.situations[k]; + let situation = moveToPhaseEvent.situations[k] - for (let m = 0; m < situation.conditions.length; m++) { + for (let m = 0; m < situation.conditions.length; m++) { - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - } + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } + } - function newCondition(key, node, chart) { + function newCondition (key, node, chart) { + let condition + let value - let condition; - let value - - try { - value = eval(node.code); - } catch (err) { + try { + let code = node.code + value = eval(code) + addCodeToSnapshot(code) + } catch (err) { /* One possible error is that the conditions references a .previous that is undefined. For this reason and others, we will simply set the value to false. */ - value = false + value = false - if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || + if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || node.code.indexOf('chart') > -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. */ - } else { - node.error = err.message + " @ " + (new Date(candle.begin)).toLocaleString() - } - } + } else { + node.error = err.message + ' @ ' + (new Date(candle.begin)).toLocaleString() + } + } - condition = { - key: key, - value: value - }; + condition = { + key: key, + value: value + } - conditions.set(condition.key, condition); + conditions.set(condition.key, condition) - if (condition.value) { - conditionsArrayValues.push(1); - } else { - conditionsArrayValues.push(0); - } - } - } + if (condition.value) { + conditionsArrayValues.push(1) + } else { + conditionsArrayValues.push(0) + } + } + } /* Trigger On Conditions */ - if ( + if ( variable.current.strategy.stage === 'No Stage' && variable.current.strategy.index === -1 ) { - let minimumBalance - let maximumBalance - let balance + let minimumBalance + let maximumBalance + let balance - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - balance = variable.current.balance.baseAsset - minimumBalance = variable.episode.parameters.minimum.balance.baseAsset - maximumBalance = variable.episode.parameters.maximum.balance.baseAsset - } else { - balance = variable.current.balance.quotedAsset - minimumBalance = variable.episode.parameters.minimum.balance.quotedAsset - maximumBalance = variable.episode.parameters.maximum.balance.quotedAsset - } - - let stopRunningDate = new Date(candle.begin) - if (balance <= minimumBalance) { - tradingSystem.error = "Min Balance @ " + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> " + tradingSystem.error ); } - afterLoop() - return - } + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + balance = variable.current.balance.baseAsset + minimumBalance = variable.episode.parameters.minimum.balance.baseAsset + maximumBalance = variable.episode.parameters.maximum.balance.baseAsset + } else { + balance = variable.current.balance.quotedAsset + minimumBalance = variable.episode.parameters.minimum.balance.quotedAsset + maximumBalance = variable.episode.parameters.maximum.balance.quotedAsset + } - if (balance >= maximumBalance) { - tradingSystem.error = "Max Balance @ " + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> " + tradingSystem.error); } - afterLoop() - return - } + let stopRunningDate = new Date(candle.begin) + if (balance <= minimumBalance) { + tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> ' + tradingSystem.error) } + afterLoop() + return + } + + if (balance >= maximumBalance) { + tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> ' + tradingSystem.error) } + afterLoop() + return + } /* Here we need to pick a strategy, or if there is not suitable strategy for the current market conditions, we pass until the next period. - + To pick a new strategy we will evaluate what we call the trigger on. 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 condition to enter all over again. - + */ - for (let j = 0; j < tradingSystem.strategies.length; j++) { + for (let j = 0; j < tradingSystem.strategies.length; j++) { + if ( + strategyStage !== 'No Stage' || + currentStrategyIndex !== -1 + ) { continue } - let strategy = tradingSystem.strategies[j]; + let strategy = tradingSystem.strategies[j] - let triggerStage = strategy.triggerStage + let triggerStage = strategy.triggerStage - if (triggerStage !== undefined) { + 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 + } - if (triggerStage.triggerOn !== undefined) { + for (let m = 0; m < situation.conditions.length; m++) { - for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m - let situation = triggerStage.triggerOn.situations[k]; - let passed = true; + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } - for (let m = 0; m < situation.conditions.length; m++) { + if (value === false) { passed = false } + } - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m; + if (passed) { - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } + variable.current.strategy.stage = 'Trigger Stage' + checkAnnouncements(triggerStage) - if (value === false) { passed = false; } - } + variable.current.strategy.index = j + variable.current.strategy.begin = candle.begin + variable.current.strategy.beginRate = candle.min + variable.current.strategy.endRate = candle.min // In case the strategy does not get exited + variable.current.strategy.situationName = situation.name - if (passed) { + variable.current.distance.toEvent.triggerOn = 1 - variable.current.strategy.stage = 'Trigger Stage'; - checkAnnouncements(triggerStage) + checkAnnouncements(triggerStage.triggerOn) + saveAsLastTriggerOnSnapshot = true - variable.current.strategy.index = j; - variable.current.strategy.begin = candle.begin; - variable.current.strategy.beginRate = candle.min; - variable.current.strategy.endRate = candle.min; // In case the strategy does not get exited - variable.current.strategy.situationName = situation.name - - variable.current.distance.toEvent.triggerOn = 1; - - checkAnnouncements(triggerStage.triggerOn) - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Switching to Trigger Stage because conditions at Trigger On Event were met."); } - break; - } - } - } - } - } - + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Switching to Trigger Stage because conditions at Trigger On Event were met.') } + break + } } + } + } + } + } /* Trigger Off Condition */ - if (variable.current.strategy.stage === 'Trigger Stage') { + if (variable.current.strategy.stage === 'Trigger Stage') { + let strategy = tradingSystem.strategies[strategyIndex] - let strategy = tradingSystem.strategies[strategyIndex]; + let triggerStage = strategy.triggerStage - let triggerStage = strategy.triggerStage - - 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 = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = variable.current.strategy.index + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - variable.current.strategy.number = variable.current.strategy.index - variable.current.strategy.end = candle.end; - variable.current.strategy.endRate = candle.min; - variable.current.strategy.status = 1; // This means the strategy is closed, i.e. that has a begin and end. - variable.current.strategy.stage = 'No Stage'; - variable.current.strategy.index = -1; - - variable.current.distance.toEvent.triggerOff = 1; - - checkAnnouncements(triggerStage.triggerOff) - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Switching to No Stage because conditions at the Trigger Off Event were met."); } - break; - } - } - } - } + 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 } + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = variable.current.strategy.index + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + variable.current.strategy.number = variable.current.strategy.index + variable.current.strategy.end = candle.end + variable.current.strategy.endRate = candle.min + variable.current.strategy.status = 1 // This means the strategy is closed, i.e. that has a begin and end. + variable.current.strategy.stage = 'No Stage' + variable.current.strategy.index = -1 + + variable.current.distance.toEvent.triggerOff = 1 + + checkAnnouncements(triggerStage.triggerOff) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Switching to No Stage because conditions at the Trigger Off Event were met.') } + break + } + } + } + } + } + /* Take Position Condition */ - if (variable.current.strategy.stage === 'Trigger Stage') { + if (variable.current.strategy.stage === 'Trigger Stage') { + let strategy = tradingSystem.strategies[strategyIndex] - let strategy = tradingSystem.strategies[strategyIndex]; + let triggerStage = strategy.triggerStage - let triggerStage = strategy.triggerStage - - 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 = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = variable.current.strategy.index + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - variable.current.strategy.stage = 'Open Stage'; - checkAnnouncements(strategy.openStage) - - variable.current.position.stopLossStage = 'Open Stage'; - variable.current.position.takeProfitStage = 'Open Stage'; - variable.current.position.stopLossPhase = 0; - variable.current.position.takeProfitPhase = 0; - - takePositionNow = true - variable.current.position.situationName = situation.name - - checkAnnouncements(triggerStage.takePosition) - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Conditions at the Take Position Event were met."); } - break; - } - } - } - } + 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 } + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = variable.current.strategy.index + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + variable.current.strategy.stage = 'Open Stage' + checkAnnouncements(strategy.openStage) + + variable.current.position.stopLossStage = 'Open Stage' + variable.current.position.takeProfitStage = 'Open Stage' + variable.current.position.stopLossPhase = 0 + variable.current.position.takeProfitPhase = 0 + + takePositionNow = true + variable.current.position.situationName = situation.name + + checkAnnouncements(triggerStage.takePosition) + saveAsLastTakePositionSnapshot = true + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Conditions at the Take Position Event were met.') } + break + } + } + } + } + } + /* Stop Loss Management */ - if ( + if ( (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') && takePositionNow !== true ) { + checkStopPhases() + calculateStopLoss() + } - checkStopPhases() - calculateStopLoss(); + function checkStopPhases () { + let strategy = tradingSystem.strategies[strategyIndex] - } + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let j = variable.current.strategy.index + let stageKey + let initialDefinitionKey = '' + let p - function checkStopPhases() { + if (stopLossStage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.stopLoss !== undefined) { + parentNode = openStage.initialDefinition + initialDefinitionKey = '-' + 'initialDefinition' + stageKey = 'openStage' + p = variable.current.position.stopLossPhase + } + } + } - let strategy = tradingSystem.strategies[strategyIndex]; + if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.stopLoss !== undefined) { + parentNode = manageStage + stageKey = 'manageStage' + p = variable.current.position.stopLossPhase - 1 + } + } - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let j = variable.current.strategy.index - let stageKey - let initialDefinitionKey = '' - let p - - if (stopLossStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.stopLoss !== undefined) { - parentNode = openStage.initialDefinition - initialDefinitionKey = '-' + 'initialDefinition' - stageKey = 'openStage' - p = variable.current.position.stopLossPhase - } - } - } - - if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.stopLoss !== undefined) { - parentNode = manageStage - stageKey = 'manageStage' - p = variable.current.position.stopLossPhase - 1 - } - } - - if (parentNode !== undefined) { - let phase = parentNode.stopLoss.phases[p]; + if (parentNode !== undefined) { + let phase = parentNode.stopLoss.phases[p] /* Check the next Phase Event. */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { + 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 + } - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m - let situation = nextPhaseEvent.situations[k]; - let passed = true; + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } - for (let m = 0; m < situation.conditions.length; m++) { + if (value === false) { passed = false } + } - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; + if (passed) { + variable.current.position.stopLossPhase++ + variable.current.position.stopLossStage = 'Manage Stage' + if (takeProfitPhase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Take Profit') + } - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - variable.current.position.stopLossPhase++; - variable.current.position.stopLossStage = 'Manage Stage' - if (takeProfitPhase > 0) { - variable.current.strategy.stage = 'Manage Stage' - checkAnnouncements(manageStage, 'Take Profit') - } - - checkAnnouncements(nextPhaseEvent) - return; - } - } - } + checkAnnouncements(nextPhaseEvent) + return + } + } + } /* 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 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 + } - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + for (let m = 0; m < situation.conditions.length; m++) { - let situation = moveToPhaseEvent.situations[k]; - let passed = true; + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m - for (let m = 0; m < situation.conditions.length; m++) { + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; + if (value === false) { passed = false } + } - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } + if (passed) { - if (value === false) { passed = false; } - } + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < parentNode.stopLoss.phases.length; q++) { + if (parentNode.stopLoss.phases[q].id === moveToPhase.id) { + variable.current.position.stopLossPhase = q + 1 + } + } + } else { + moveToPhaseEvent.error = 'This Node needs to reference a Phase.' + continue + } - if (passed) { + variable.current.position.stopLossStage = 'Manage Stage' + if (takeProfitPhase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Take Profit') + } - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < parentNode.stopLoss.phases.length; q++) { - if (parentNode.stopLoss.phases[q].id === moveToPhase.id) { - variable.current.position.stopLossPhase = q + 1 - } - } - } else { - moveToPhaseEvent.error = "This Node needs to reference a Phase." - continue - } - - variable.current.position.stopLossStage = 'Manage Stage' - if (takeProfitPhase > 0) { - variable.current.strategy.stage = 'Manage Stage' - checkAnnouncements(manageStage, 'Take Profit') - } - - checkAnnouncements(moveToPhaseEvent) - return; - } - } - } - } - } + checkAnnouncements(moveToPhaseEvent) + return + } } + } + } + } + } - function calculateStopLoss() { + function calculateStopLoss () { + let strategy = tradingSystem.strategies[strategyIndex] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + let key - let strategy = tradingSystem.strategies[strategyIndex]; - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - let key + if (stopLossStage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.stopLoss !== undefined) { + phase = openStage.initialDefinition.stopLoss.phases[stopLossPhase] + key = variable.current.strategy.index + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + (stopLossPhase) + } + } + } - if (stopLossStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.stopLoss !== undefined) { - phase = openStage.initialDefinition.stopLoss.phases[stopLossPhase]; - key = variable.current.strategy.index + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + (stopLossPhase); - } - } - } + if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.stopLoss !== undefined) { + phase = manageStage.stopLoss.phases[stopLossPhase - 1] + key = variable.current.strategy.index + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + (stopLossPhase - 1) + } + } - if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.stopLoss !== undefined) { - phase = manageStage.stopLoss.phases[stopLossPhase - 1]; - key = variable.current.strategy.index + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + (stopLossPhase - 1); - } - } + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = variable.current.position.stopLoss - if (phase !== undefined) { - if (phase.formula !== undefined) { - let previousValue = variable.current.position.stopLoss + variable.current.position.stopLoss = formulas.get(key) - variable.current.position.stopLoss = formulas.get(key) - - if (stopLoss !== previousValue) { - checkAnnouncements(phase, variable.current.position.stopLoss) - } - } - } - } + if (stopLoss !== previousValue) { + checkAnnouncements(phase, variable.current.position.stopLoss) + } + } + } + } /* Take Profit Management */ - if ( + if ( (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') && takePositionNow !== true ) { + checkTakeProfitPhases() + calculateTakeProfit() + } - checkTakeProfitPhases(); - calculateTakeProfit(); + function checkTakeProfitPhases () { + let strategy = tradingSystem.strategies[strategyIndex] - } + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let j = variable.current.strategy.index + let stageKey + let initialDefinitionKey = '' + let p - function checkTakeProfitPhases() { + if (takeProfitStage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.takeProfit !== undefined) { + parentNode = openStage.initialDefinition + initialDefinitionKey = '-' + 'initialDefinition' + stageKey = 'openStage' + p = variable.current.position.takeProfitPhase + } + } + } - let strategy = tradingSystem.strategies[strategyIndex]; + if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.takeProfit !== undefined) { + parentNode = manageStage + stageKey = 'manageStage' + p = variable.current.position.takeProfitPhase - 1 + } + } - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let j = variable.current.strategy.index - let stageKey - let initialDefinitionKey = '' - let p - - if (takeProfitStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.takeProfit !== undefined) { - parentNode = openStage.initialDefinition - initialDefinitionKey = '-' + 'initialDefinition' - stageKey = 'openStage' - p = variable.current.position.takeProfitPhase - } - } - } - - if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.takeProfit !== undefined) { - parentNode = manageStage - stageKey = 'manageStage' - p = variable.current.position.takeProfitPhase - 1 - } - } - - if (parentNode !== undefined) { - let phase = parentNode.takeProfit.phases[p]; - if (phase === undefined) {return} // trying to jump to a phase that does not exists. + if (parentNode !== undefined) { + let phase = parentNode.takeProfit.phases[p] + 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) { + 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 + } - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m - let situation = nextPhaseEvent.situations[k]; - let passed = true; + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } - for (let m = 0; m < situation.conditions.length; m++) { + if (value === false) { passed = false } + } - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; + if (passed) { + variable.current.position.takeProfitPhase++ + variable.current.position.takeProfitStage = 'Manage Stage' + if (stopLossPhase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Stop') + } - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - variable.current.position.takeProfitPhase++; - variable.current.position.takeProfitStage = 'Manage Stage' - if (stopLossPhase > 0) { - variable.current.strategy.stage = 'Manage Stage' - checkAnnouncements(manageStage, 'Stop') - } - - checkAnnouncements(nextPhaseEvent) - return; - } - } - } + checkAnnouncements(nextPhaseEvent) + return + } + } + } /* 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 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 + } - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + for (let m = 0; m < situation.conditions.length; m++) { - let situation = moveToPhaseEvent.situations[k]; - let passed = true; + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m - for (let m = 0; m < situation.conditions.length; m++) { + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; + if (value === false) { passed = false } + } - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } + if (passed) { - if (value === false) { passed = false; } - } + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < parentNode.takeProfit.phases.length; q++) { + if (parentNode.takeProfit.phases[q].id === moveToPhase.id) { + variable.current.position.takeProfitPhase = q + 1 + } + } + } else { + moveToPhaseEvent.error = 'This Node needs to reference a Phase.' + continue + } - if (passed) { + variable.current.position.takeProfitStage = 'Manage Stage' + if (stopLossPhase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Stop') + } - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < parentNode.takeProfit.phases.length; q++) { - if (parentNode.takeProfit.phases[q].id === moveToPhase.id) { - variable.current.position.takeProfitPhase = q + 1 - } - } - } else { - moveToPhaseEvent.error = "This Node needs to reference a Phase." - continue - } - - variable.current.position.takeProfitStage = 'Manage Stage' - if (stopLossPhase > 0) { - variable.current.strategy.stage = 'Manage Stage' - checkAnnouncements(manageStage, 'Stop') - } - - checkAnnouncements(moveToPhaseEvent) - return; - } - } - } - } - } + checkAnnouncements(moveToPhaseEvent) + return + } } + } + } + } + } - function calculateTakeProfit() { + function calculateTakeProfit () { + let strategy = tradingSystem.strategies[strategyIndex] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + let key - let strategy = tradingSystem.strategies[strategyIndex]; - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - let key + if (takeProfitStage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.takeProfit !== undefined) { + phase = openStage.initialDefinition.takeProfit.phases[takeProfitPhase] + key = variable.current.strategy.index + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + (takeProfitPhase) + } + } + } - if (takeProfitStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.takeProfit !== undefined) { - phase = openStage.initialDefinition.takeProfit.phases[takeProfitPhase]; - key = variable.current.strategy.index + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + (takeProfitPhase); - } - } - } + if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.takeProfit !== undefined) { + phase = manageStage.takeProfit.phases[takeProfitPhase - 1] + key = variable.current.strategy.index + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + (takeProfitPhase - 1) + } + } - if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.takeProfit !== undefined) { - phase = manageStage.takeProfit.phases[takeProfitPhase - 1]; - key = variable.current.strategy.index + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + (takeProfitPhase - 1); - } - } + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = variable.current.position.stopLoss - if (phase !== undefined) { - if (phase.formula !== undefined) { + variable.current.position.takeProfit = formulas.get(key) - let previousValue = variable.current.position.stopLoss - - variable.current.position.takeProfit = formulas.get(key) - - if (takeProfit !== previousValue) { - checkAnnouncements(phase, variable.current.position.takeProfit) - } - } - } - } + if (takeProfit !== previousValue) { + checkAnnouncements(phase, variable.current.position.takeProfit) + } + } + } + } /* Keeping Position Counters Up-to-date */ - if ( + if ( (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') ) { + if (takePositionNow === true) { + variable.current.position.periods = 0 + } - if (takePositionNow === true) { - variable.current.position.periods = 0 - } - - variable.current.position.periods++; - variable.positionDays = variable.current.position.periods * timeFrame / ONE_DAY_IN_MILISECONDS; - - } else { - variable.current.position.periods = 0 - variable.positionDays = 0 - } + variable.current.position.periods++ + variable.positionDays = variable.current.position.periods * timeFrame / ONE_DAY_IN_MILISECONDS + } else { + variable.current.position.periods = 0 + variable.positionDays = 0 + } /* Keeping Distance Counters Up-to-date */ - if ( + if ( variable.current.distance.toEvent.triggerOn > 0 // with this we avoind counting before the first event happens. ) { - variable.current.distance.toEvent.triggerOn++; - } + variable.current.distance.toEvent.triggerOn++ + } - if ( + if ( variable.current.distance.toEvent.triggerOff > 0 // with this we avoind counting before the first event happens. ) { - variable.current.distance.toEvent.triggerOff++; - } + variable.current.distance.toEvent.triggerOff++ + } - if ( + if ( variable.current.distance.toEvent.takePosition > 0 // with this we avoind counting before the first event happens. ) { - variable.current.distance.toEvent.takePosition++; - } + variable.current.distance.toEvent.takePosition++ + } - if ( + if ( variable.current.distance.toEvent.closePosition > 0 // with this we avoind counting before the first event happens. ) { - variable.current.distance.toEvent.closePosition++; - } + variable.current.distance.toEvent.closePosition++ + } /* Checking if Stop or Take Profit were hit */ - if ( + if ( (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') && takePositionNow !== true ) { - let strategy = tradingSystem.strategies[strategyIndex]; + let strategy = tradingSystem.strategies[strategyIndex] /* 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 ((variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset && candle.max >= variable.current.position.stopLoss) || (variable.episode.parameters.baseAsset !== variable.episode.parameters.marketBaseAsset && candle.min <= variable.current.position.stopLoss)) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Stop Loss was hit."); } + if ((variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset && candle.max >= variable.current.position.stopLoss) || (variable.episode.parameters.baseAsset !== variable.episode.parameters.marketBaseAsset && candle.min <= variable.current.position.stopLoss)) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Stop Loss was hit.') } /* Hit Point Validation @@ -1520,47 +1498,46 @@ If we take the stop loss value at those situation would be a huge distortion of facts. */ - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - if (stopLoss < candle.min) { - variable.current.position.stopLoss = candle.min - } - } else { - if (stopLoss > candle.max) { - variable.current.position.stopLoss = candle.max - } - } + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + if (stopLoss < candle.min) { + variable.current.position.stopLoss = candle.min + } + } else { + if (stopLoss > candle.max) { + variable.current.position.stopLoss = candle.max + } + } - let slippedStopLoss = variable.current.position.stopLoss + let slippedStopLoss = variable.current.position.stopLoss /* Apply the Slippage */ - let slippageAmount = slippedStopLoss * bot.VALUES_TO_USE.slippage.stopLoss / 100 + let slippageAmount = slippedStopLoss * bot.VALUES_TO_USE.slippage.stopLoss / 100 - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - slippedStopLoss = slippedStopLoss + slippageAmount - } else { - slippedStopLoss = slippedStopLoss - slippageAmount - } + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + slippedStopLoss = slippedStopLoss + slippageAmount + } else { + slippedStopLoss = slippedStopLoss - slippageAmount + } - closeRate = slippedStopLoss; + closeRate = slippedStopLoss - variable.current.strategy.stage = 'Close Stage'; - checkAnnouncements(strategy.closeStage, 'Stop') + variable.current.strategy.stage = 'Close Stage' + checkAnnouncements(strategy.closeStage, 'Stop') - variable.current.position.stopLossStage = 'No Stage'; - variable.current.position.takeProfitStage = 'No Stage'; - variable.current.position.end = candle.end; - variable.current.position.status = 1; - variable.current.position.exitType = 1; - variable.current.position.endRate = closeRate; + variable.current.position.stopLossStage = 'No Stage' + variable.current.position.takeProfitStage = 'No Stage' + variable.current.position.end = candle.end + variable.current.position.status = 1 + variable.current.position.exitType = 1 + variable.current.position.endRate = closeRate - closePositionNow = true; - } + closePositionNow = true + } /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ - if ((variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset && candle.min <= variable.current.position.takeProfit) || (variable.episode.parameters.baseAsset !== variable.episode.parameters.marketBaseAsset && candle.max >= variable.current.position.takeProfit)) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Take Profit was hit."); } + if ((variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset && candle.min <= variable.current.position.takeProfit) || (variable.episode.parameters.baseAsset !== variable.episode.parameters.marketBaseAsset && candle.max >= variable.current.position.takeProfit)) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Take Profit was hit.') } /* Hit Point Validation: @@ -1568,838 +1545,925 @@ If we take the stop loss value at those situation would be a huge distortion of facts. */ - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - if (takeProfit > candle.max) { - variable.current.position.takeProfit = candle.max - } - } else { - if (takeProfit < candle.min) { - variable.current.position.takeProfit = candle.min - } - } + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + if (takeProfit > candle.max) { + variable.current.position.takeProfit = candle.max + } + } else { + if (takeProfit < candle.min) { + variable.current.position.takeProfit = candle.min + } + } - let slippedTakeProfit = variable.current.position.takeProfit + let slippedTakeProfit = variable.current.position.takeProfit /* Apply the Slippage */ - let slippageAmount = slippedTakeProfit * bot.VALUES_TO_USE.slippage.takeProfit / 100 + let slippageAmount = slippedTakeProfit * bot.VALUES_TO_USE.slippage.takeProfit / 100 - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - slippedTakeProfit = slippedTakeProfit + slippageAmount - } else { - slippedTakeProfit = slippedTakeProfit - slippageAmount - } + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + slippedTakeProfit = slippedTakeProfit + slippageAmount + } else { + slippedTakeProfit = slippedTakeProfit - slippageAmount + } - closeRate = slippedTakeProfit; + closeRate = slippedTakeProfit - variable.current.strategy.stage = 'Close Stage'; - checkAnnouncements(strategy.closeStage, 'Take Profit') + variable.current.strategy.stage = 'Close Stage' + checkAnnouncements(strategy.closeStage, 'Take Profit') - variable.current.position.stopLossStage = 'No Stage'; - variable.current.position.takeProfitStage = 'No Stage'; + variable.current.position.stopLossStage = 'No Stage' + variable.current.position.takeProfitStage = 'No Stage' - variable.current.position.end = candle.end; - variable.current.position.status = 1; - variable.current.position.exitType = 2; - variable.current.position.endRate = closeRate; + variable.current.position.end = candle.end + variable.current.position.status = 1 + variable.current.position.exitType = 2 + variable.current.position.endRate = closeRate - closePositionNow = true; - - } - } + closePositionNow = true + addToSnapshots = true + } + } /* Taking a Position */ - if ( + if ( takePositionNow === true ) { - takePositionNow = false - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Entering code block."); } + takePositionNow = false + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Entering code block.') } /* Inicializing this counter */ - variable.current.distance.toEvent.takePosition = 1; + variable.current.distance.toEvent.takePosition = 1 /* Position size and rate */ - let strategy = tradingSystem.strategies[strategyIndex]; + let strategy = tradingSystem.strategies[strategyIndex] - variable.current.position.size = strategy.positionSize; - variable.current.position.rate = strategy.positionRate; + variable.current.position.size = strategy.positionSize + variable.current.position.rate = strategy.positionRate /* We take what was calculated at the formula and apply the slippage. */ - let slippageAmount = variable.current.position.rate * bot.VALUES_TO_USE.slippage.positionRate / 100 + let slippageAmount = variable.current.position.rate * bot.VALUES_TO_USE.slippage.positionRate / 100 - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - variable.current.position.rate = variable.current.position.rate - slippageAmount - } else { - variable.current.position.rate = variable.current.position.rate + slippageAmount - } + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.current.position.rate = variable.current.position.rate - slippageAmount + } else { + variable.current.position.rate = variable.current.position.rate + slippageAmount + } /* Update the trade record information. */ - variable.current.position.begin = candle.begin; - variable.current.position.beginRate = variable.current.position.rate; + variable.current.position.begin = candle.begin + variable.current.position.beginRate = variable.current.position.rate /* Check if we need to execute. */ - if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not.*/ + if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not. */ /* Check that we are in LIVE MODE */ - if (bot.startMode === "Live") { + if (bot.startMode === 'Live') { /* We see if we need to put the actual order at the exchange. */ - if (variable.executionContext !== undefined) { - switch (variable.executionContext.status) { - case "Without a Position": { // We need to put the order because It was not put yet. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return - } - } - break - } - case "Position Closed": { // Waiting for a confirmation that the position was closed. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return - } - } - break - } - case "Taking Position": { // Waiting for a confirmation that the position was taken. - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is Taking Position."); } - break - } - case "In a Position": { // This should mean that we already put the order at the exchange. - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is In a Position."); } - break - } - } - } else { // The context does not exist so it means we are not in a position. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return - } - } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Not trading live."); } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionNow -> Not the last closed candle."); } - } - - takePositionAtSimulation() + if (variable.executionContext !== undefined) { + switch (variable.executionContext.status) { + case 'Without a Position': { // We need to put the order because It was not put yet. + if (strategy.openStage !== undefined) { + if (strategy.openStage.openExecution !== undefined) { + putOpeningOrder() + return + } + } + break + } + case 'Position Closed': { // Waiting for a confirmation that the position was closed. + if (strategy.openStage !== undefined) { + if (strategy.openStage.openExecution !== undefined) { + putOpeningOrder() + return + } + } + break + } + case 'Taking Position': { // Waiting for a confirmation that the position was taken. + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is Taking Position.') } + break + } + case 'In a Position': { // This should mean that we already put the order at the exchange. + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is In a Position.') } + break + } + } + } else { // The context does not exist so it means we are not in a position. + if (strategy.openStage !== undefined) { + if (strategy.openStage.openExecution !== undefined) { + putOpeningOrder() return + } + } + } + } else { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not trading live.') } + } + } else { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not the last closed candle.') } + } - function putOpeningOrder() { + takePositionAtSimulation() - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function."); } + return + + function putOpeningOrder () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function.') } /* We wont take a position unless we are withing the bot.VALUES_TO_USE.timeRange.initialDatetime and the bot.VALUES_TO_USE.timeRange.finalDatetime range */ - if (bot.VALUES_TO_USE.timeRange.initialDatetime !== undefined) { - if (candle.end < bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because current candle ends before the start date. -> bot.VALUES_TO_USE.timeRange.initialDatetime = " + bot.VALUES_TO_USE.timeRange.initialDatetime); } - takePositionAtSimulation() - return; - } - } + if (bot.VALUES_TO_USE.timeRange.initialDatetime !== undefined) { + if (candle.end < bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because current candle ends before the start date. -> bot.VALUES_TO_USE.timeRange.initialDatetime = ' + bot.VALUES_TO_USE.timeRange.initialDatetime) } + takePositionAtSimulation() + return + } + } - /*We wont take a position if we are past the final datetime */ - if (bot.VALUES_TO_USE.timeRange.finalDatetime !== undefined) { - if (candle.begin > bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> putOpeningOrder -> Not placing the trade at the exchange because current candle begins after the end date. -> bot.VALUES_TO_USE.timeRange.finalDatetime = " + bot.VALUES_TO_USE.timeRange.finalDatetime); } - takePositionAtSimulation() - return; - } - } + /* We wont take a position if we are past the final datetime */ + if (bot.VALUES_TO_USE.timeRange.finalDatetime !== undefined) { + if (candle.begin > bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> putOpeningOrder -> Not placing the trade at the exchange because current candle begins after the end date. -> bot.VALUES_TO_USE.timeRange.finalDatetime = ' + bot.VALUES_TO_USE.timeRange.finalDatetime) } + takePositionAtSimulation() + return + } + } /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ - if (variable.executionContext !== undefined) { - if (variable.executionContext.periods !== undefined) { - if (variable.episode.count.periods <= variable.executionContext.periods) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because it was already placed at a previous execution."); } - takePositionAtSimulation() - return; - } - } - } + if (variable.executionContext !== undefined) { + if (variable.executionContext.periods !== undefined) { + if (variable.episode.count.periods <= variable.executionContext.periods) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because it was already placed at a previous execution.') } + takePositionAtSimulation() + return + } + } + } /* We are not going to place orders based on outdated information. The next filter prevents firing orders when backtesting. */ - if (currentDay) { - let today = new Date(Math.trunc((new Date().valueOf()) / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - let processDay = new Date(Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - if (today.valueOf() !== processDay.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because the current candle belongs to the previous day and that is considered simulation and not live trading."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> today = " + today); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = " + processDay); } - takePositionAtSimulation() - return; - } - } + if (currentDay) { + let today = new Date(Math.trunc((new Date().valueOf()) / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) + let processDay = new Date(Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) + if (today.valueOf() !== processDay.valueOf()) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because the current candle belongs to the previous day and that is considered simulation and not live trading.') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> today = ' + today) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = ' + processDay) } + takePositionAtSimulation() + return + } + } - let orderPrice - let amountA - let amountB - let orderSide - + let orderPrice + let amountA + let amountB + let orderSide - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - orderSide = "sell" + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + orderSide = 'sell' - orderPrice = variable.current.position.rate + orderPrice = variable.current.position.rate - amountA = variable.current.position.size * orderPrice - amountB = variable.current.position.size - - } else { - orderSide = "buy" + amountA = variable.current.position.size * orderPrice + amountB = variable.current.position.size + } else { + orderSide = 'buy' - orderPrice = variable.current.position.rate + orderPrice = variable.current.position.rate - amountA = variable.current.position.size - amountB = variable.current.position.size / orderPrice + amountA = variable.current.position.size + amountB = variable.current.position.size / orderPrice + } - } + variable.executionContext = { + status: 'Taking Position', + periods: variable.episode.count.periods + } - variable.executionContext = { - status: "Taking Position", - periods: variable.episode.count.periods, - } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Ready to create order.') } + exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Ready to create order."); } - exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) + function onOrderCreated (err, order) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function.') } - function onOrderCreated(err, order) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function."); } + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') } + variable.executionContext = { + status: 'In a Position', + periods: variable.episode.count.periods, + amountA: amountA, + amountB: amountB, + orderId: order.id + } + takePositionAtSimulation() + return + } + case global.DEFAULT_FAIL_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) } + strategy.openStage.openExecution.error = err.message + afterLoop() + return + } + case global.DEFAULT_RETRY_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) } + strategy.openStage.openExecution.error = err.message + afterLoop() + return + } + } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message) } + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } + } + } - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE "); } - variable.executionContext = { - status: "In a Position", - periods: variable.episode.count.periods, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - takePositionAtSimulation() - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE "); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = " + err.message); } - strategy.openStage.openExecution.error = err.message - afterLoop() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE "); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = " + err.message); } - strategy.openStage.openExecution.error = err.message - afterLoop() - return; - } - } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Unexpected Response -> Message = " + err.message); } - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } - } - - function takePositionAtSimulation() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionAtSimulation -> Entering function."); } + function takePositionAtSimulation () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionAtSimulation -> Entering function.') } /* Continue with the simulation */ - calculateTakeProfit(); - calculateStopLoss(); + calculateTakeProfit() + calculateStopLoss() - variable.previous.balance.baseAsset = variable.current.balance.baseAsset; - variable.previous.balance.quotedAsset = variable.current.balance.quotedAsset; + variable.previous.balance.baseAsset = variable.current.balance.baseAsset + variable.previous.balance.quotedAsset = variable.current.balance.quotedAsset - variable.last.position.profitLoss = 0; - variable.last.position.ROI = 0; + variable.last.position.profitLoss = 0 + variable.last.position.ROI = 0 - let feePaid = 0 + let feePaid = 0 - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + feePaid = variable.current.position.size * variable.current.position.rate * bot.VALUES_TO_USE.feeStructure.taker / 100 - feePaid = variable.current.position.size * variable.current.position.rate * bot.VALUES_TO_USE.feeStructure.taker / 100 + variable.current.balance.quotedAsset = variable.current.balance.quotedAsset + variable.current.position.size * variable.current.position.rate - feePaid + variable.current.balance.baseAsset = variable.current.balance.baseAsset - variable.current.position.size + } else { + feePaid = variable.current.position.size / variable.current.position.rate * bot.VALUES_TO_USE.feeStructure.taker / 100 - variable.current.balance.quotedAsset = variable.current.balance.quotedAsset + variable.current.position.size * variable.current.position.rate - feePaid; - variable.current.balance.baseAsset = variable.current.balance.baseAsset - variable.current.position.size; - } else { + variable.current.balance.baseAsset = variable.current.balance.baseAsset + variable.current.position.size / variable.current.position.rate - feePaid + variable.current.balance.quotedAsset = variable.current.balance.quotedAsset - variable.current.position.size + } - feePaid = variable.current.position.size / variable.current.position.rate * bot.VALUES_TO_USE.feeStructure.taker / 100 + addRecord() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionAtSimulation -> Exiting Loop Body after taking position at simulation.') } + controlLoop() + return + } + } - variable.current.balance.baseAsset = variable.current.balance.baseAsset + variable.current.position.size / variable.current.position.rate - feePaid; - variable.current.balance.quotedAsset = variable.current.balance.quotedAsset - variable.current.position.size; - } + if (closePositionNow === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Entering code block.') } - addRecord(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionAtSimulation -> Exiting Loop Body after taking position at simulation."); } - controlLoop(); - return - } - } - - if (closePositionNow === true) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Entering code block."); } - - closePositionNow = false + closePositionNow = false /* Inicializing this counter */ - variable.current.distance.toEvent.closePosition = 1; + variable.current.distance.toEvent.closePosition = 1 /* Position size and rate */ - let strategy = tradingSystem.strategies[strategyIndex]; + let strategy = tradingSystem.strategies[strategyIndex] - if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not.*/ + if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not. */ /* Check that we are in LIVE MODE */ - if (bot.startMode === "Live") { + if (bot.startMode === 'Live') { /* We see if we need to put the actual order at the exchange. */ - if (variable.executionContext !== undefined) { - switch (variable.executionContext.status) { - case "Without a Position": { // No way to close anything at the exchange. - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Without a Position."); } - break - } - case "In a Position": { // This should mean that we already put the order at the exchange. - if (strategy.closeStage !== undefined) { - if (strategy.closeStage.closeExecution !== undefined) { - putClosingOrder() - return - } - } - break - } - case "Closing Position": { // Waiting for a confirmation that the position was taken. - if (strategy.closeStage !== undefined) { - if (strategy.closeStage.closeExecution !== undefined) { - putClosingOrder() - return - } - } - break - } + if (variable.executionContext !== undefined) { + switch (variable.executionContext.status) { + case 'Without a Position': { // No way to close anything at the exchange. + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Without a Position.') } + break + } + case 'In a Position': { // This should mean that we already put the order at the exchange. + if (strategy.closeStage !== undefined) { + if (strategy.closeStage.closeExecution !== undefined) { + putClosingOrder() + return + } + } + break + } + case 'Closing Position': { // Waiting for a confirmation that the position was taken. + if (strategy.closeStage !== undefined) { + if (strategy.closeStage.closeExecution !== undefined) { + putClosingOrder() + return + } + } + break + } - case "Position Closed": { // - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed."); } - break - } - } - } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Not within the last 10 candles."); } - } + case 'Position Closed': { // + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed.') } + break + } + } + } + } + } else { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Not within the last 10 candles.') } + } - closePositionAtSimulation() - return + closePositionAtSimulation() + return - function putClosingOrder() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> putClosingOrder -> Entering function."); } + function putClosingOrder () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> putClosingOrder -> Entering function.') } /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ - if (variable.executionContext !== undefined) { - if (variable.executionContext.periods !== undefined) { - if (variable.episode.count.periods <= variable.executionContext.periods) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> Exiting function because this closing was already submited at a previous execution."); } - closePositionAtSimulation() - return; - } - } - } - - let orderPrice - let amountA - let amountB - let orderSide - - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - orderSide = "buy" - - orderPrice = ticker.last + 100; // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. - - amountA = variable.current.balance.quotedAsset - amountB = variable.current.balance.quotedAsset / orderPrice - - } else { - orderSide = "sell" - - orderPrice = ticker.last - 100; // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. - - amountA = variable.current.balance.baseAsset * orderPrice - amountB = variable.current.balance.baseAsset - - } - - variable.executionContext = { - status: "Closing Position", - periods: variable.episode.count.periods, - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> About to close position at the exchange."); } - exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) - - function onOrderCreated(err, order) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function."); } - - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE "); } - variable.executionContext = { - status: "Position Closed", - periods: variable.episode.count.periods, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - closePositionAtSimulation() - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE "); } - /* We will assume that the problem is temporary, and expect that it will work at the next execution.*/ - strategy.closeStage.closeExecution.error = err.message - afterLoop() - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE "); } - strategy.closeStage.closeExecution.error = err.message - afterLoop() - return; - } - } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Unexpected Response -> Message = " + err.message); } - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - } - } - - function closePositionAtSimulation() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> closePositionAtSimulation -> Entering function."); } - - variable.episode.count.positions++; - - let feePaid = 0 - - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - strategy.positionSize = variable.current.balance.quotedAsset / closeRate; - strategy.positionRate = closeRate; - - feePaid = variable.current.balance.quotedAsset / closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - variable.current.balance.baseAsset = variable.current.balance.baseAsset + variable.current.balance.quotedAsset / closeRate - feePaid; - variable.current.balance.quotedAsset = 0; - } else { - strategy.positionSize = variable.current.balance.baseAsset * closeRate; - strategy.positionRate = closeRate; - - feePaid = variable.current.balance.baseAsset * closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - variable.current.balance.quotedAsset = variable.current.balance.quotedAsset + variable.current.balance.baseAsset * closeRate - feePaid; - variable.current.balance.baseAsset = 0; - } - - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - variable.last.position.profitLoss = variable.current.balance.baseAsset - variable.previous.balance.baseAsset; - variable.last.position.ROI = variable.last.position.profitLoss * 100 / variable.current.position.size; - if (isNaN(lastTradeROI)) { variable.last.position.ROI = 0; } - variable.episode.stat.profitLoss = variable.current.balance.baseAsset - variable.episode.parameters.initial.balance.baseAsset; - } else { - variable.last.position.profitLoss = variable.current.balance.quotedAsset - variable.previous.balance.quotedAsset; - variable.last.position.ROI = variable.last.position.profitLoss * 100 / variable.current.position.size; - if (isNaN(lastTradeROI)) { variable.last.position.ROI = 0; } - variable.episode.stat.profitLoss = variable.current.balance.quotedAsset - variable.episode.parameters.initial.balance.quotedAsset; - } - - variable.current.position.lastTradeROI = variable.last.position.ROI; - - if (variable.last.position.profitLoss > 0) { - variable.episode.count.hits++; - } else { - variable.episode.count.fails++; - } - - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - variable.episode.stat.ROI = (variable.episode.parameters.initial.balance.baseAsset + variable.episode.stat.profitLoss) / variable.episode.parameters.initial.balance.baseAsset - 1; - variable.episode.stat.hitRatio = variable.episode.count.hits / variable.episode.count.positions; - variable.episode.stat.anualizedRateOfReturn = variable.episode.stat.ROI / variable.episode.stat.days * 365; - } else { - variable.episode.stat.ROI = (variable.episode.parameters.initial.balance.quotedAsset + variable.episode.stat.profitLoss) / variable.episode.parameters.initial.balance.quotedAsset - 1; - variable.episode.stat.hitRatio = variable.episode.count.hits / variable.episode.count.positions; - variable.episode.stat.anualizedRateOfReturn = variable.episode.stat.ROI / variable.episode.stat.days * 365; - } - - addRecord(); - - variable.current.position.stopLoss = 0; - variable.current.position.takeProfit = 0; - - variable.current.position.rate = 0; - variable.current.position.size = 0; - - timerToCloseStage = candle.begin - variable.current.position.stopLossStage = 'No Stage'; - variable.current.position.takeProfitStage = 'No Stage'; - variable.current.position.stopLossPhase = -1; - variable.current.position.takeProfitPhase = -1; - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> closePositionAtSimulation -> Exiting Loop Body after closing position at simulation."); } - controlLoop(); - return - } + if (variable.executionContext !== undefined) { + if (variable.executionContext.periods !== undefined) { + if (variable.episode.count.periods <= variable.executionContext.periods) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> Exiting function because this closing was already submited at a previous execution.') } + closePositionAtSimulation() + return } + } + } + + let orderPrice + let amountA + let amountB + let orderSide + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + orderSide = 'buy' + + orderPrice = ticker.last + 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. + + amountA = variable.current.balance.quotedAsset + amountB = variable.current.balance.quotedAsset / orderPrice + } else { + orderSide = 'sell' + + orderPrice = ticker.last - 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. + + amountA = variable.current.balance.baseAsset * orderPrice + amountB = variable.current.balance.baseAsset + } + + variable.executionContext = { + status: 'Closing Position', + periods: variable.episode.count.periods + } + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> About to close position at the exchange.') } + exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) + + function onOrderCreated (err, order) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function.') } + + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') } + variable.executionContext = { + status: 'Position Closed', + periods: variable.episode.count.periods, + amountA: amountA, + amountB: amountB, + orderId: order.id + } + closePositionAtSimulation() + return + } + case global.DEFAULT_FAIL_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') } + /* We will assume that the problem is temporary, and expect that it will work at the next execution. */ + strategy.closeStage.closeExecution.error = err.message + afterLoop() + return + } + case global.DEFAULT_RETRY_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') } + strategy.closeStage.closeExecution.error = err.message + afterLoop() + return + } + } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message) } + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } + } + } + + function closePositionAtSimulation () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> closePositionAtSimulation -> Entering function.') } + + variable.episode.count.positions++ + + let feePaid = 0 + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + strategy.positionSize = variable.current.balance.quotedAsset / closeRate + strategy.positionRate = closeRate + + feePaid = variable.current.balance.quotedAsset / closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + variable.current.balance.baseAsset = variable.current.balance.baseAsset + variable.current.balance.quotedAsset / closeRate - feePaid + variable.current.balance.quotedAsset = 0 + } else { + strategy.positionSize = variable.current.balance.baseAsset * closeRate + strategy.positionRate = closeRate + + feePaid = variable.current.balance.baseAsset * closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + variable.current.balance.quotedAsset = variable.current.balance.quotedAsset + variable.current.balance.baseAsset * closeRate - feePaid + variable.current.balance.baseAsset = 0 + } + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.last.position.profitLoss = variable.current.balance.baseAsset - variable.previous.balance.baseAsset + variable.last.position.ROI = variable.last.position.profitLoss * 100 / variable.current.position.size + if (isNaN(lastTradeROI)) { variable.last.position.ROI = 0 } + variable.episode.stat.profitLoss = variable.current.balance.baseAsset - variable.episode.parameters.initial.balance.baseAsset + } else { + variable.last.position.profitLoss = variable.current.balance.quotedAsset - variable.previous.balance.quotedAsset + variable.last.position.ROI = variable.last.position.profitLoss * 100 / variable.current.position.size + if (isNaN(lastTradeROI)) { variable.last.position.ROI = 0 } + variable.episode.stat.profitLoss = variable.current.balance.quotedAsset - variable.episode.parameters.initial.balance.quotedAsset + } + + variable.current.position.lastTradeROI = variable.last.position.ROI + + if (variable.last.position.profitLoss > 0) { + variable.episode.count.hits++ + } else { + variable.episode.count.fails++ + } + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.episode.stat.ROI = (variable.episode.parameters.initial.balance.baseAsset + variable.episode.stat.profitLoss) / variable.episode.parameters.initial.balance.baseAsset - 1 + variable.episode.stat.hitRatio = variable.episode.count.hits / variable.episode.count.positions + variable.episode.stat.anualizedRateOfReturn = variable.episode.stat.ROI / variable.episode.stat.days * 365 + } else { + variable.episode.stat.ROI = (variable.episode.parameters.initial.balance.quotedAsset + variable.episode.stat.profitLoss) / variable.episode.parameters.initial.balance.quotedAsset - 1 + variable.episode.stat.hitRatio = variable.episode.count.hits / variable.episode.count.positions + variable.episode.stat.anualizedRateOfReturn = variable.episode.stat.ROI / variable.episode.stat.days * 365 + } + + addRecord() + + variable.current.position.stopLoss = 0 + variable.current.position.takeProfit = 0 + + variable.current.position.rate = 0 + variable.current.position.size = 0 + + timerToCloseStage = candle.begin + variable.current.position.stopLossStage = 'No Stage' + variable.current.position.takeProfitStage = 'No Stage' + variable.current.position.stopLossPhase = -1 + variable.current.position.takeProfitPhase = -1 + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> closePositionAtSimulation -> Exiting Loop Body after closing position at simulation.') } + controlLoop() + return + } + } /* Closing the Closing Stage */ - if (variable.current.strategy.stage === 'Close Stage') { - if (candle.begin - 5 * 60 * 1000 > timerToCloseStage) { + if (variable.current.strategy.stage === 'Close Stage') { + if (candle.begin - 5 * 60 * 1000 > timerToCloseStage) { + variable.current.strategy.number = variable.current.strategy.index + variable.current.strategy.end = candle.end + variable.current.strategy.endRate = candle.min + variable.current.strategy.status = 1 // This means the strategy is closed, i.e. that has a begin and end. - variable.current.strategy.number = variable.current.strategy.index - variable.current.strategy.end = candle.end; - variable.current.strategy.endRate = candle.min; - variable.current.strategy.status = 1; // This means the strategy is closed, i.e. that has a begin and end. + variable.current.strategy.index = -1 + variable.current.strategy.stage = 'No Stage' - variable.current.strategy.index = -1; - variable.current.strategy.stage = 'No Stage'; + timerToCloseStage = 0 + variable.current.distance.toEvent.triggerOff = 1 - timerToCloseStage = 0 - variable.current.distance.toEvent.triggerOff = 1; - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing the Closing Stage -> Exiting Close Stage."); } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing the Closing Stage -> Waiting for timer."); } - } - } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing the Closing Stage -> Exiting Close Stage.') } + } else { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing the Closing Stage -> Waiting for timer.') } + } + } /* Not a buy or sell condition */ - addRecord(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Exiting Loop Body after adding a record."); } - controlLoop(); - return + addRecord() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Exiting Loop Body after adding a record.') } + controlLoop() + return - function addRecord() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> addRecord -> Entering function."); } - let simulationRecord; + function addRecord () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> addRecord -> Entering function.') } + let simulationRecord - if (variable.current.balance.baseAsset === Infinity) { - variable.current.balance.baseAsset = Number.MAX_SAFE_INTEGER - } + if (variable.current.balance.baseAsset === Infinity) { + variable.current.balance.baseAsset = Number.MAX_SAFE_INTEGER + } - if (variable.current.balance.quotedAsset === Infinity) { - variable.current.balance.quotedAsset = Number.MAX_SAFE_INTEGER - } + if (variable.current.balance.quotedAsset === Infinity) { + variable.current.balance.quotedAsset = Number.MAX_SAFE_INTEGER + } - simulationRecord = { - begin: candle.begin, - end: candle.end, - balanceBaseAsset: variable.current.balance.baseAsset, - balanceQuotedAsset: variable.current.balance.quotedAsset, - accumulatedProfitLoss: variable.episode.stat.profitLoss, - lastTradeProfitLoss: variable.last.position.profitLoss, - stopLoss: variable.current.position.stopLoss, - tradesCount: variable.episode.count.positions, - hits: variable.episode.count.hits, - fails: variable.episode.count.fails, - hitRatio: variable.episode.stat.hitRatio, - ROI: variable.episode.stat.ROI, - periods: variable.episode.count.periods, - days: variable.episode.stat.days, - anualizedRateOfReturn: variable.episode.stat.anualizedRateOfReturn, - positionRate: variable.current.position.rate, - lastTradeROI: variable.last.position.ROI, - strategy: variable.current.strategy.index, - takeProfit: variable.current.position.takeProfit, - stopLossPhase: variable.current.position.stopLossPhase, - takeProfitPhase: variable.current.position.takeProfitPhase, - positionSize: variable.current.position.size, - initialBalanceA: variable.episode.parameters.initial.balance.baseAsset, - minimumBalanceA: variable.episode.parameters.minimum.balance.baseAsset, - maximumBalanceA: variable.episode.parameters.maximum.balance.baseAsset, - initialBalanceB: variable.episode.parameters.initial.balance.quotedAsset, - minimumBalanceB: variable.episode.parameters.minimum.balance.quotedAsset, - maximumBalanceB: variable.episode.parameters.maximum.balance.quotedAsset, - baseAsset: '"' + variable.episode.parameters.baseAsset + '"', - quotedAsset: '"' + variable.episode.parameters.quotedAsset + '"', - marketBaseAsset: '"' + variable.episode.parameters.marketBaseAsset + '"', - marketQuotedAsset: '"' + variable.episode.parameters.marketQuotedAsset + '"' , - positionPeriods: variable.current.position.periods, - positionDays: variable.positionDays, - distanceToEventTriggerOn: variable.current.distance.toEvent.triggerOn, - distanceToEventTriggerOff: variable.current.distance.toEvent.triggerOff, - distanceToEventTakePosition: variable.current.distance.toEvent.takePosition, - distanceToEventClosePosition: variable.current.distance.toEvent.closePosition - } + simulationRecord = { + begin: candle.begin, + end: candle.end, + balanceBaseAsset: variable.current.balance.baseAsset, + balanceQuotedAsset: variable.current.balance.quotedAsset, + accumulatedProfitLoss: variable.episode.stat.profitLoss, + lastTradeProfitLoss: variable.last.position.profitLoss, + stopLoss: variable.current.position.stopLoss, + tradesCount: variable.episode.count.positions, + hits: variable.episode.count.hits, + fails: variable.episode.count.fails, + hitRatio: variable.episode.stat.hitRatio, + ROI: variable.episode.stat.ROI, + periods: variable.episode.count.periods, + days: variable.episode.stat.days, + anualizedRateOfReturn: variable.episode.stat.anualizedRateOfReturn, + positionRate: variable.current.position.rate, + lastTradeROI: variable.last.position.ROI, + strategy: variable.current.strategy.index, + takeProfit: variable.current.position.takeProfit, + stopLossPhase: variable.current.position.stopLossPhase, + takeProfitPhase: variable.current.position.takeProfitPhase, + positionSize: variable.current.position.size, + initialBalanceA: variable.episode.parameters.initial.balance.baseAsset, + minimumBalanceA: variable.episode.parameters.minimum.balance.baseAsset, + maximumBalanceA: variable.episode.parameters.maximum.balance.baseAsset, + initialBalanceB: variable.episode.parameters.initial.balance.quotedAsset, + minimumBalanceB: variable.episode.parameters.minimum.balance.quotedAsset, + maximumBalanceB: variable.episode.parameters.maximum.balance.quotedAsset, + baseAsset: '"' + variable.episode.parameters.baseAsset + '"', + quotedAsset: '"' + variable.episode.parameters.quotedAsset + '"', + marketBaseAsset: '"' + variable.episode.parameters.marketBaseAsset + '"', + marketQuotedAsset: '"' + variable.episode.parameters.marketQuotedAsset + '"', + positionPeriods: variable.current.position.periods, + positionDays: variable.positionDays, + distanceToEventTriggerOn: variable.current.distance.toEvent.triggerOn, + distanceToEventTriggerOff: variable.current.distance.toEvent.triggerOff, + distanceToEventTakePosition: variable.current.distance.toEvent.takePosition, + distanceToEventClosePosition: variable.current.distance.toEvent.closePosition + } - recordsArray.push(simulationRecord); + recordsArray.push(simulationRecord) /* Prepare the information for the Conditions File */ - conditionsArrayRecord.push(strategyIndex); - conditionsArrayRecord.push(stopLossPhase); - conditionsArrayRecord.push(takeProfitPhase); - conditionsArrayRecord.push(conditionsArrayValues); - conditionsArrayRecord.push(formulasErrors); - conditionsArrayRecord.push(formulasValues); + conditionsArrayRecord.push(strategyIndex) + conditionsArrayRecord.push(stopLossPhase) + conditionsArrayRecord.push(takeProfitPhase) + conditionsArrayRecord.push(conditionsArrayValues) + conditionsArrayRecord.push(formulasErrors) + conditionsArrayRecord.push(formulasValues) - conditionsArray.push(conditionsArrayRecord); + conditionsArray.push(conditionsArrayRecord) - /* + /* Lets see if there will be an open strategy ... Except if we are at the head of the market (remember we skipped the last candle for not being closed.) */ - if (variable.current.strategy.begin !== 0 && variable.current.strategy.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { - variable.current.strategy.status = 2; // This means the strategy is open, i.e. that has a begin but no end. - variable.current.strategy.end = candle.end - } - - /* Prepare the information for the Strategies File*/ - if (variable.current.strategy.begin !== 0 && variable.current.strategy.end !== 0) - { - let currentStrategyRecord = { - begin: variable.current.strategy.begin, - end: variable.current.strategy.end, - status: variable.current.strategy.status, - number: variable.current.strategy.number, - beginRate: variable.current.strategy.beginRate, - endRate: variable.current.strategy.endRate, - situationName: variable.current.strategy.situationName - } + if (variable.current.strategy.begin !== 0 && variable.current.strategy.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { + variable.current.strategy.status = 2 // This means the strategy is open, i.e. that has a begin but no end. + variable.current.strategy.end = candle.end + } - strategiesArray.push(currentStrategyRecord ); + /* Prepare the information for the Strategies File */ + if (variable.current.strategy.begin !== 0 && variable.current.strategy.end !== 0) { + let currentStrategyRecord = { + begin: variable.current.strategy.begin, + end: variable.current.strategy.end, + status: variable.current.strategy.status, + number: variable.current.strategy.number, + beginRate: variable.current.strategy.beginRate, + endRate: variable.current.strategy.endRate, + situationName: variable.current.strategy.situationName + } - inializeCurrentStrategy() - } + strategiesArray.push(currentStrategyRecord) - /* + inializeCurrentStrategy() + } + + /* Lets see if there will be an open trade ... Except if we are at the head of the market (remember we skipped the last candle for not being closed.) */ - if (variable.current.position.begin !== 0 && variable.current.position.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { - variable.current.position.status = 2; // This means the trade is open - variable.current.position.end = candle.end - variable.current.position.endRate = candle.close + if (variable.current.position.begin !== 0 && variable.current.position.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { + variable.current.position.status = 2 // This means the trade is open + variable.current.position.end = candle.end + variable.current.position.endRate = candle.close /* Here we will calculate the ongoing variable.episode.stat.ROI */ - if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { - variable.current.position.lastTradeROI = (variable.current.position.rate - candle.close) / variable.current.position.rate * 100 - } else { - variable.current.position.lastTradeROI = (candle.close - variable.current.position.rate) / variable.current.position.rate * 100 - } - } + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.current.position.lastTradeROI = (variable.current.position.rate - candle.close) / variable.current.position.rate * 100 + } else { + variable.current.position.lastTradeROI = (candle.close - variable.current.position.rate) / variable.current.position.rate * 100 + } + } + + /* Snapshots Management (before we generate the trade record and delete that info) */ + if (saveAsLastTriggerOnSnapshot === true) { + snapshots.lastTriggerOn = snapshotDataRecord + saveAsLastTriggerOnSnapshot = false + } + + if (saveAsLastTakePositionSnapshot === true) { + snapshots.lastTakePosition = snapshotDataRecord + saveAsLastTakePositionSnapshot = false + } + + if (addToSnapshots === true) { + let closeValues = [ + roundtrips, // Trade Number + (new Date(candle.begin)).toISOString(), // Datetime + tradingSystem.strategies[currentStrategyIndex].name, // Strategy + currentStrategy.triggerOnSituation, // Trigger On Situation + currentTrade.takePositionSituation, // Take Position Situation + hitOrFial(), // Result + lastTradeROI, // ROI + simulationRecord.type.replace(/"/g, '') // Exit Type + ] + + function hitOrFial () { + if (lastTradeROI > 0) { return 'HIT' } else { return 'FAIL' } + } + + if (positionedAtYesterday === false) { + snapshots.triggerOn.push(closeValues.concat(snapshots.lastTriggerOn)) + snapshots.takePosition.push(closeValues.concat(snapshots.lastTakePosition)) + } + snapshots.lastTriggerOn = undefined + snapshots.lastTakePosition = undefined + addToSnapshots = false + } + + let closeHeaders = ['Trade Number', 'Close Datetime', 'Strategy', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type'] + if (snapshots.headers === undefined) { + snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshotLoopHeaders))) + } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.snapshots.headers = snapshots.headers + yesterday.snapshots.triggerOn = snapshots.triggerOn + yesterday.snapshots.takePosition = snapshots.takePosition + yesterday.snapshots.lastTriggerOn = snapshots.lastTriggerOn + yesterday.snapshots.lastTakePosition = snapshots.lastTakePosition + } + } /* Prepare the information for the Trades File */ - if (variable.current.position.begin !== 0 && variable.current.position.end !== 0) { + if (variable.current.position.begin !== 0 && variable.current.position.end !== 0) { + let currentPositionRecord = { + begin: variable.current.position.begin, + end: variable.current.position.end, + status: variable.current.position.status, + lastTradeROI: variable.current.position.lastTradeROI, + exitType: variable.current.position.exitType, + beginRate: variable.current.position.beginRate, + endRate: variable.current.position.endRate, + situationName: variable.current.position.situationName + } - let currentPositionRecord = { - begin: variable.current.position.begin, - end: variable.current.position.end, - status: variable.current.position.status, - lastTradeROI: variable.current.position.lastTradeROI, - exitType: variable.current.position.exitType, - beginRate: variable.current.position.beginRate, - endRate: variable.current.position.endRate, - situationName: variable.current.position.situationName - } + tradesArray.push(currentPositionRecord) - tradesArray.push(currentPositionRecord); + initializeCurrentPosition() + } - initializeCurrentPosition() - } + /* After everything at the simulation level was done, we will do the annoucements that are pending. */ + makeAnnoucements() + } - makeAnnoucements() // After everything at the simulation level was done, we will do the annoucements that are pending. - } - - function checkAnnouncements(node, value) { + function checkAnnouncements (node, value) { /* Value is an optional parameter that represents the value that the announcement is monitoring for change (for numeric values only). If we do receive this value, we will only make the annoucement if the variance is grater than the user pre-defined value for this variance. */ - if (node.announcements !== undefined) { - for (let i = 0; i < node.announcements.length; i++) { - let announcement = node.announcements[i] - let key = node.type + "-" + announcement.name + "-" + announcement.id + if (node.announcements !== undefined) { + for (let i = 0; i < node.announcements.length; i++) { + let announcement = node.announcements[i] + let key = node.type + '-' + announcement.name + '-' + announcement.id - let lastPeriodAnnounced = -1 - let newAnnouncementRecord = {} + let lastPeriodAnnounced = -1 + let newAnnouncementRecord = {} - for (let j = 0; j < variable.announcements.length; j++) { - let announcementRecord = variable.announcements[j] - if (announcementRecord.key === key) { - lastPeriodAnnounced = announcementRecord.periods - newAnnouncementRecord = announcementRecord - break - } - } + for (let j = 0; j < variable.announcements.length; j++) { + let announcementRecord = variable.announcements[j] + if (announcementRecord.key === key) { + lastPeriodAnnounced = announcementRecord.periods + newAnnouncementRecord = announcementRecord + break + } + } - if (variable.episode.count.periods > lastPeriodAnnounced) { - - if (isNaN(value) === false) { + if (variable.episode.count.periods > lastPeriodAnnounced) { + if (isNaN(value) === false) { /* The Value Variation is what tells us how much the value already announced must change in order to annouce it again. */ - let valueVariation + let valueVariation - let code = announcement.code - valueVariation = code.valueVariation + let code = announcement.code + valueVariation = code.valueVariation - if (newAnnouncementRecord.value !== undefined && valueVariation !== undefined) { - let upperLimit = newAnnouncementRecord.value + newAnnouncementRecord.value * valueVariation / 100 - let lowerLimit = newAnnouncementRecord.value - newAnnouncementRecord.value * valueVariation / 100 - if (value > lowerLimit && value < upperLimit) { + if (newAnnouncementRecord.value !== undefined && valueVariation !== undefined) { + let upperLimit = newAnnouncementRecord.value + newAnnouncementRecord.value * valueVariation / 100 + let lowerLimit = newAnnouncementRecord.value - newAnnouncementRecord.value * valueVariation / 100 + if (value > lowerLimit && value < upperLimit) { /* There is not enough variation to announce this again. */ - return - } - } - } + return + } + } + } /* We store the announcement temporarily at an Array to differ its execution, becasue we need to evaulate its formula and at this point in time the potential variables used at the formula are still not set. */ - announcement.value = value - announcementsToBeMade.push(announcement) + announcement.value = value + announcementsToBeMade.push(announcement) /* Next, we will remmeber this announcement was already done, so that it is not announced again in further processing of the same day. */ - if (newAnnouncementRecord.periods !== undefined) { - newAnnouncementRecord.periods = variable.episode.count.periods - newAnnouncementRecord.value = value - } else { - newAnnouncementRecord = { - key: key, - periods: variable.episode.count.periods, - value: value - } - variable.announcements.push(newAnnouncementRecord) - } - } - } - } - } - - function makeAnnoucements() { - /* Here we go through all the annoucements that need to be done during this loop, and we just do them. */ - for (let i = 0; i < announcementsToBeMade.length; i++) { - announcement = announcementsToBeMade[i] - /* Here we check if there is a formula attached to the annoucement, we evaluate it to get the annoucement text. */ - let formulaValue - if (announcement.formula !== undefined) { - try { - let value = announcement.value - formulaValue = eval(announcement.formula.code); - } catch (err) { - announcement.formula.error = err.message - } - } - announcement.formulaValue = formulaValue - - if (bot.SESSION.socialBots !== undefined) { - bot.SESSION.socialBots.announce(announcement) - } - } - } - } - - - - function controlLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> controlLoop -> Entering function."); } - - /* 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.") - afterLoop() - 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() - return - } - - currentCandleIndex++ - if (currentCandleIndex < candles.length) { - setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. + if (newAnnouncementRecord.periods !== undefined) { + newAnnouncementRecord.periods = variable.episode.count.periods + newAnnouncementRecord.value = value } else { - afterLoop() + newAnnouncementRecord = { + key: key, + periods: variable.episode.count.periods, + value: value + } + variable.announcements.push(newAnnouncementRecord) } + } } + } + } - function afterLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> afterLoop -> Entering function."); } + function makeAnnoucements () { + /* Here we go through all the annoucements that need to be done during this loop, and we just do them. */ + for (let i = 0; i < announcementsToBeMade.length; i++) { + announcement = announcementsToBeMade[i] + /* Here we check if there is a formula attached to the annoucement, we evaluate it to get the annoucement text. */ + let formulaValue + if (announcement.formula !== undefined) { + try { + let value = announcement.value + formulaValue = eval(announcement.formula.code) + } catch (err) { + announcement.formula.error = err.message + } + } + announcement.formulaValue = formulaValue + + if (bot.SESSION.socialBots !== undefined) { + bot.SESSION.socialBots.announce(announcement) + } + } + } + + function addCodeToSnapshot (code) { + if (code === undefined) { return } + + try { + let instructionsArray = code.split(' ') + for (let i = 0; i < instructionsArray.length; i++) { + let instruction = instructionsArray[i] + instruction = instruction.replace('(', '') + instruction = instruction.replace(')', '') + instruction = instruction.replace(//g, '') + instruction = instruction.replace(/<=/g, '') + instruction = instruction.replace(/>=/g, '') + instruction = instruction.replace(/!=/g, '') + instruction = instruction.replace(/!==/g, '') + instruction = instruction.replace(/==/g, '') + instruction = instruction.replace(/===/g, '') + instruction = instruction.replace(/{/g, '') + instruction = instruction.replace(/}/g, '') + if (instruction.indexOf('chart') >= 0) { + let parts = instruction.split('.') + let timeFrame = parts[1] + let product = parts[2] + let property + checkPrevious(3) + function checkPrevious (index) { + property = parts[index] + if (property === 'previous') { + product = product + '.previous' + checkPrevious(index + 1) + } + } + + // Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10 + if (timeFrame !== 'atAnyTimeFrame') { + timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7) + } + let key = timeFrame + '-' + product + '-' + property + let existingKey = snapshotKeys.get(key) + + if (existingKey === undefined) { // means that at the current loop this property of this product was not used before. + snapshotKeys.set(key, key) + snapshotLoopHeaders.push(key) + + let value = eval(instruction) + snapshotDataRecord.push(value) + } + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> code = ' + code) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function controlLoop () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> Entering function.') } + + /* 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.') + afterLoop() + 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() + return + } + + currentCandleIndex++ + if (currentCandleIndex < candles.length) { + 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() + } + } + + function afterLoop () { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> afterLoop -> Entering function.') } /* Before returning we need to see if we have to record some of our counters at the variable. To do that, the condition to be met is that this execution must include all candles of the current day. */ - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> callback -> recordsArray.length = " + recordsArray.length); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> callback -> recordsArray.length = ' + recordsArray.length) } - callback(tradingSystem, recordsArray, conditionsArray, strategiesArray, tradesArray); - } + callback(tradingSystem, recordsArray, conditionsArray, strategiesArray, tradesArray, snapshots.headers, snapshots.triggerOn, snapshots.takePosition) + } - function getElement(pArray, currentCandle, datasetName) { - if (pArray === undefined) {return} - try { - let element; - for (let i = 0; i < pArray.length; i++) { - element = pArray[i]; + function getElement (pArray, currentCandle, datasetName) { + if (pArray === undefined) { return } + try { + let element + for (let i = 0; i < pArray.length; i++) { + element = pArray[i] - if (currentCandle.end === element.end) { // when there is an exact match at the end we take that element - return element - } else { - if ( + if (currentCandle.end === element.end) { // when there is an exact match at the end we take that element + return element + } else { + if ( i > 0 && element.end > currentCandle.end ) { - let previousElement = pArray[i - 1] - if (previousElement.end < currentCandle.end) { - return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element. - } else { - return - } - } - if ( + let previousElement = pArray[i - 1] + if (previousElement.end < currentCandle.end) { + return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element. + } else { + return + } + } + if ( i === pArray.length - 1 // If we reach the end of the array, then we return the last element. && element.end < currentCandle.end ) { - return element - } - } - } - return - } - 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); - } + return element + } } + } + return + } 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) } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) } -}; - - + } +} diff --git a/package-lock.json b/package-lock.json index 9da031d46..fc670f1b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "ccxt": { - "version": "1.25.80", - "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.25.80.tgz", - "integrity": "sha512-AUWagxMa8PMexRabbktWezlmVacj6L2Ep4844ebIXUOyME2Ommg7O/9/pq0F2Ne1IHcJnNJzHrMYQvkh2gBtlw==" + "version": "1.27.45", + "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.27.45.tgz", + "integrity": "sha512-4ADlbcIWDzIWjtLvsrtux5jMFj7pU4kVuiqtI2Btq6NEhQ4r/4+LirXdEFlCF6/wIU3XSHabxMd/8nu8HC1KJw==" }, "debug": { "version": "4.1.1", @@ -48,9 +48,9 @@ "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==" }, "telegraf": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.37.0.tgz", - "integrity": "sha512-V3448qwfOolBqkIc87yxjW4zMvR2P6AIF24pPTlX9WhZPwA1TF/x3nQhnWPRLtGh2SJuvDcr83iTkXPXT7Opnw==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.38.0.tgz", + "integrity": "sha512-va4VlrKWp64JrowFoZX/NPzzA6q38kvaIukVXOWFO1V+jR1G8+hCfgJy4TX8Z3rwLJzwaBEet1QhikHDRZWl3A==", "requires": { "debug": "4.1.1", "minimist": "1.2.5", @@ -66,9 +66,9 @@ "integrity": "sha512-njVv1EAhIZnmQVLocZEADYUyqA1WIXuVcDYlsp+mXua/XB0pxx+PKtMSPeZ/EE4wPWTw9h/hA9ASTT6yQelkiw==" }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" } } }