diff --git a/TradingBot.js b/TradingBot.js index 1a2852487..c0af44f59 100644 --- a/TradingBot.js +++ b/TradingBot.js @@ -121,6 +121,7 @@ const CONDITIONS_FOLDER_NAME = "Simulation-Conditions"; const STRATEGIES_FOLDER_NAME = "Simulation-Strategies"; const TRADES_FOLDER_NAME = "Simulation-Trades"; + const SNAPSHOTS_FOLDER_NAME = "Snapshots"; const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; @@ -129,6 +130,10 @@ let strategiesArray let tradesArray + let snapshotHeaders + let triggerOnSnapshot + let takePositionSnapshot + let tradingSystem = {}; tradingSimulation.runSimulation( @@ -142,7 +147,7 @@ writeFiles, callBackFunction) - function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray) { + function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray, pSnapshotHeaders, pTriggerOnSnapshot, pTakePositionSnapshot) { tradingSystem = pTradingSystem recordsArray = pRecordsArray @@ -150,11 +155,16 @@ strategiesArray = pStrategiesArray tradesArray = pTradesArray + snapshotHeaders = pSnapshotHeaders + triggerOnSnapshot = pTriggerOnSnapshot + takePositionSnapshot = pTakePositionSnapshot + if (timeFrame > global.dailyFilePeriods[0][0]) { writeMarketFiles() } else { writeDailyFiles() } + } function writeMarketFiles() { @@ -474,7 +484,7 @@ } - callBackFunction(global.DEFAULT_OK_RESPONSE); + writeSnapshotFiles(); } catch (err) { @@ -489,97 +499,52 @@ } } - } + function writeSnapshotFiles() { - function writeDailyFiles() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> Entering function."); } + writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish) - writeRecordsFile(); + function onFinish() { + writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction) + } + + } - /* TODO : NEXT function is a work in progress, not used today */ - function writeFinalResults() { + function writeSnapshotFile(snapshotArray, pFileName, callBack) { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> Entering function."); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeSnapshotFile -> Entering function."); } - if ( // We only write this file if we are at the head of the market. - Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS - !== - Math.trunc((new Date()).valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS - ) { - writeRecordsFile(); - return; - } - - let separator = ""; let fileRecordCounter = 0; let fileContent = ""; + let separator = "\r\n"; - 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.type + "," + - record.marketRate + "," + - record.amount + "," + - record.balanceA + "," + - record.balanceB + "," + - record.profit + "," + - record.lastTradeProfitLoss + "," + - record.stopLoss + "," + - record.roundtrips + "," + - record.hits + "," + - record.fails + "," + - record.hitRatio + "," + - record.ROI + "," + - record.periods + "," + - record.days + "," + - record.anualizedRateOfReturn + "," + - record.positionRate + "," + - record.lastTradeROI + "," + - record.strategy + "," + - record.strategyStageNumber + "," + - record.takeProfit + "," + - record.stopLossPhase + "," + - record.takeProfitPhase + "," + - JSON.stringify(record.executionRecord) + "," + - 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 = ","; } + 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 + "]"; + fileContent = "" + fileContent + ""; - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel; + 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); @@ -588,35 +553,42 @@ try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> fileContent = " + fileContent); } + 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 -> writeDailyFiles -> writeFinalResults -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); + 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; } - writeRecordsFile(); + callBack(global.DEFAULT_OK_RESPONSE); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> err = " + err.stack); + 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 -> writeDailyFiles -> writeFinalResults -> err = " + err.stack); + 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 { diff --git a/TradingSimulation.js b/TradingSimulation.js index 1e924e89c..9fc408d30 100644 --- a/TradingSimulation.js +++ b/TradingSimulation.js @@ -39,6 +39,10 @@ } else { processingDailyFiles = true } + + let snapshotHeaders + let triggerOnSnapshot = []; + let takePositionSnapshot = []; let recordsArray = []; let conditionsArray = []; @@ -500,6 +504,14 @@ 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 snapshotKeys = new Map() + snapshotLoopHeaders = [] + snapshotDataRecord = [] + addToTriggerOnSnapshot = false + addToTakePositionSnapshot = false + let announcementsToBeMade = [] let candle = candles[currentCandleIndex]; @@ -767,7 +779,9 @@ if (initialDefinition.positionSize !== undefined) { if (initialDefinition.positionSize.formula !== undefined) { try { - positionSize = eval(initialDefinition.positionSize.formula.code); + let code = initialDefinition.positionSize.formula.code + positionSize = eval(code); + addCodeToSnapshot(code) } catch (err) { initialDefinition.positionSize.formula.error = err.message } @@ -794,7 +808,9 @@ 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 } @@ -821,7 +837,9 @@ if (phase.formula !== undefined) { try { - formulaValue = eval(phase.formula.code); + let code = phase.formula.code + formulaValue = eval(code); + addCodeToSnapshot(code) if (formulaValue === Infinity) { formulaError = "Formula evaluates to Infinity." formulaValue = MAX_STOP_LOSS_VALUE @@ -910,7 +928,9 @@ if (phase.formula !== undefined) { try { - formulaValue = eval(phase.formula.code); + let code = phase.formula.code + formulaValue = eval(code); + addCodeToSnapshot(code) if (formulaValue === Infinity) { formulaValue = MAX_TAKE_PROFIT_VALUE if (takeProfitStage === 'Open Stage') { @@ -1007,7 +1027,9 @@ if (phase.formula !== undefined) { try { - formulaValue = eval(phase.formula.code); + let code = phase.formula.code + formulaValue = eval(code); + addCodeToSnapshot(code) if (formulaValue === Infinity) { formulaError = "" formulaValue = MAX_STOP_LOSS_VALUE @@ -1096,7 +1118,9 @@ if (phase.formula !== undefined) { try { - formulaValue = eval(phase.formula.code); + let code = phase.formula.code + formulaValue = eval(code); + addCodeToSnapshot(code) if (formulaValue === Infinity) { formulaError = "Formula evaluates to Infinity." formulaValue = MAX_TAKE_PROFIT_VALUE @@ -1181,7 +1205,9 @@ let value try { - value = eval(node.code); + 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 @@ -1319,6 +1345,7 @@ } checkAnnouncements(triggerStage.triggerOn) + addToTriggerOnSnapshot = true if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Switching to Trigger Stage because conditions at Trigger On Event were met."); } break; @@ -1452,6 +1479,7 @@ currentTrade.takePositionSituation = situation.name checkAnnouncements(triggerStage.takePosition) + addToTakePositionSnapshot = true if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Conditions at the Take Position Event were met."); } break; @@ -2186,6 +2214,7 @@ } takePositionAtSimulation() + return function putOpeningOrder() { @@ -2914,6 +2943,21 @@ } } + /* Add to Snapshot */ + if (addToTakePositionSnapshot === true) { + takePositionSnapshot.push(snapshotDataRecord) + addToTakePositionSnapshot = false + } + + if (addToTriggerOnSnapshot === true) { + triggerOnSnapshot.push(snapshotDataRecord) + addToTriggerOnSnapshot = false + } + + if (snapshotHeaders === undefined) { + snapshotHeaders = JSON.parse(JSON.stringify(snapshotLoopHeaders)) + } + makeAnnoucements() // After everything at the simulation level was done, we will do the annoucements that are pending. } @@ -3005,10 +3049,63 @@ } } } + + 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, '') + 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."); } @@ -3114,7 +3211,7 @@ 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, snapshotHeaders, triggerOnSnapshot, takePositionSnapshot); } function getElement(pArray, currentCandle, datasetName) { @@ -3156,6 +3253,7 @@ } } } + 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==" } } }