diff --git a/Trading/Announcements.js b/Trading/Announcements.js index e8db3bb7a..4f9d92f5a 100644 --- a/Trading/Announcements.js +++ b/Trading/Announcements.js @@ -3,6 +3,8 @@ exports.newAnnouncements = function newAnnouncements(bot, logger) { const MODULE_NAME = 'Announcements' let thisObject = { + checkAnnouncements: checkAnnouncements, + makeAnnoucements: makeAnnoucements, initialize: initialize, finalize: finalize } @@ -19,4 +21,91 @@ exports.newAnnouncements = function newAnnouncements(bot, logger) { tradingEngine = undefined } + 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 + + 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 + } + } + + if (tradingEngine.episode.episodeCounters.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 config = announcement.config + valueVariation = config.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) { + /* There is not enough variation to announce this again. */ + 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) + + /* 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 = tradingEngine.episode.episodeCounters.periods + newAnnouncementRecord.value = value + } else { + newAnnouncementRecord = { + key: key, + periods: tradingEngine.episode.episodeCounters.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) + } + } + } } \ No newline at end of file diff --git a/Trading/Snapshots.js b/Trading/Snapshots.js index 8a220817e..0b2b56e9e 100644 --- a/Trading/Snapshots.js +++ b/Trading/Snapshots.js @@ -4,6 +4,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) { let thisObject = { manageSnapshots: manageSnapshots, + addCodeToSnapshot: addCodeToSnapshot, initialize: initialize, finalize: finalize } @@ -70,4 +71,60 @@ exports.newSnapshots = function newSnapshots(bot, logger) { snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshotLoopHeaders))) } } + + 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 !== 'atAnyvariable.timeFrame') { + 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) + } + } } \ No newline at end of file diff --git a/Trading/TradingEngine.js b/Trading/TradingEngine.js index 3d05fe4ee..1a7c3344e 100644 --- a/Trading/TradingEngine.js +++ b/Trading/TradingEngine.js @@ -3,6 +3,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { const MODULE_NAME = 'Trading Engine' let thisObject = { + setCurrentCandle: setCurrentCandle, updatePositionCounters: updatePositionCounters, updateDistanceToEventsCounters: updateDistanceToEventsCounters, getReadyToTakePosition: getReadyToTakePosition, @@ -32,6 +33,58 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { tradingEngine = undefined } + function setCurrentCandle(candle) { + cloneValues(tradingEngine.current.candle, tradingEngine.previous.candle) + + tradingEngine.current.candle.begin.value = candle.begin + tradingEngine.current.candle.end.value = candle.end + tradingEngine.current.candle.open.value = candle.open + tradingEngine.current.candle.close.value = candle.close + tradingEngine.current.candle.min.value = candle.min + tradingEngine.current.candle.max.value = candle.max + } + + function cloneValues(originNode, destinationNode) { + if (originNode === undefined) { return } + if (destinationNode === undefined) { return } + + /* Here copy the node value */ + destinationNode.value = originNode.value + + /* Now we go down through all the children of the origin node, assuming the destination node has the same children structure*/ + let nodeDefinition = bot.APP_SCHEMA_MAP.get(originNode.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) { + cloneValues(originNode[property.name], destinationNode[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) { + let originNodePropertyArray = originNode[property.name] + let destinationNodePropertyArray = destinationNode[property.name] + for (let m = 0; m < originNodePropertyArray.length; m++) { + cloneValues(originNodePropertyArray[m], destinationNodePropertyArray[m]) + } + } + break + } + } + } + } + } + function initializeNode(node) { if (node === undefined) { return } @@ -64,7 +117,6 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { case 'array': { if (node[property.name] !== undefined) { let nodePropertyArray = node[property.name] - object[property.name] = [] for (let m = 0; m < nodePropertyArray.length; m++) { initializeNode(nodePropertyArray[m]) } diff --git a/Trading/TradingSimulation.js b/Trading/TradingSimulation.js index 51b721e32..a72167407 100644 --- a/Trading/TradingSimulation.js +++ b/Trading/TradingSimulation.js @@ -180,6 +180,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let announcementsToBeMade = [] let candle = candles[currentCandleIndex] + tradingEngineModule.setCurrentCandle(candle) + /* Not processing while out of user-defined time range */ if (candle.end < sessionParameters.timeRange.config.initialDatetime.valueOf()) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') } @@ -312,8 +314,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } - - tradingSystemModule.setCandle(candle) tradingSystemModule.setChart(chart) tradingSystemModule.evalConditions() tradingSystemModule.evalFormulas() @@ -445,156 +445,9 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingEngine.last.candle.end = candle.end /* After everything at the simulation level was done, we will do the annoucements that are pending. */ - makeAnnoucements() - + annoucementsModule.makeAnnoucements() controlLoop() - return - - - 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 - - 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 - } - } - - if (tradingEngine.episode.episodeCounters.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 config = announcement.config - valueVariation = config.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) { - /* There is not enough variation to announce this again. */ - 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) - - /* 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 = tradingEngine.episode.episodeCounters.periods - newAnnouncementRecord.value = value - } else { - newAnnouncementRecord = { - key: key, - periods: tradingEngine.episode.episodeCounters.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 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 !== 'atAnyvariable.timeFrame') { - 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() { diff --git a/Trading/TradingSystem.js b/Trading/TradingSystem.js index b9b766155..5d372763a 100644 --- a/Trading/TradingSystem.js +++ b/Trading/TradingSystem.js @@ -4,7 +4,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { const FULL_LOG = true let thisObject = { - setCandle: setCandle, setChart: setChart, evalConditions: evalConditions, evalFormulas: evalFormulas, @@ -23,7 +22,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { } let chart - let candle let tradingSystem let tradingEngine @@ -45,7 +43,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { function finalize() { chart = undefined - candle = undefined + tradingSystem = undefined tradingEngine = undefined @@ -58,10 +56,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { formulasErrors = undefined } - function setCandle(pCandle) { - candle = pCandle // We need candle to be a local object accessible from conditions and formulas. - } - function setChart(pChart) { chart = pChart // We need chat to be a local object accessible from conditions and formulas. } @@ -260,9 +254,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { checkAnnouncements(triggerStage) tradingEngine.current.strategy.index.value = j - tradingEngine.current.strategy.begin.value = candle.begin - tradingEngine.current.strategy.beginRate.value = candle.min - tradingEngine.current.strategy.endRate.value = candle.min // In case the strategy does not get exited + tradingEngine.current.strategy.begin.value = tradingEngine.current.candle.begin.value + tradingEngine.current.strategy.beginRate.value = tradingEngine.current.candle.min.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value // In case the strategy does not get exited tradingEngine.current.strategy.situationName.value = situation.name tradingEngine.current.strategy.strategyName.value = strategy.name @@ -307,8 +301,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { } if (passed) { - tradingEngine.current.strategy.end.value = candle.end - tradingEngine.current.strategy.endRate.value = candle.min + tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value + tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value tradingEngine.current.strategy.status.value = 'Closed' tradingEngine.current.strategy.stageType.value = 'No Stage' tradingEngine.current.strategy.index.value = -1 @@ -686,8 +680,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ if ( - (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset && candle.max >= tradingEngine.current.position.stopLoss.value) || - (sessionParameters.sessionBaseAsset.name !== bot.market.marketBaseAsset && candle.min <= tradingEngine.current.position.stopLoss.value) + (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset && tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value) || + (sessionParameters.sessionBaseAsset.name !== bot.market.marketBaseAsset && tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value) ) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Stop Loss was hit.') } /* @@ -698,12 +692,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { */ if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - if (tradingEngine.current.position.stopLoss.value < candle.min) { - tradingEngine.current.position.stopLoss.value = candle.min + if (tradingEngine.current.position.stopLoss.value < tradingEngine.current.candle.min.value) { + tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.min.value } } else { - if (tradingEngine.current.position.stopLoss.value > candle.max) { - tradingEngine.current.position.stopLoss.value = candle.max + if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.candle.max.value) { + tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.max.value } } @@ -725,7 +719,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { tradingEngine.current.position.stopLoss.stopLossStage.value = 'No Stage' tradingEngine.current.position.takeProfit.takeProfitStage.value = 'No Stage' - tradingEngine.current.position.end.value = candle.end + tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value tradingEngine.current.position.status.value = 1 tradingEngine.current.position.exitType.value = 1 @@ -735,8 +729,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ if ( - (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset && candle.min <= tradingEngine.current.position.takeProfit.value) || - (sessionParameters.sessionBaseAsset.name !== bot.market.marketBaseAsset && candle.max >= tradingEngine.current.position.takeProfit.value) + (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset && tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value) || + (sessionParameters.sessionBaseAsset.name !== bot.market.marketBaseAsset && tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value) ) { if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Take Profit was hit.') } /* @@ -747,12 +741,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { */ if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - if (tradingEngine.current.position.takeProfit.value > candle.max) { - tradingEngine.current.position.takeProfit.value = candle.max + if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.candle.max.value) { + tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.max.value } } else { - if (tradingEngine.current.position.takeProfit.value < candle.min) { - tradingEngine.current.position.takeProfit.value = candle.min + if (tradingEngine.current.position.takeProfit.value < tradingEngine.current.candle.min.value) { + tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.min.value } } @@ -774,7 +768,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { tradingEngine.current.position.stopLoss.stopLossStage.value = 'No Stage' tradingEngine.current.position.takeProfit.takeProfitStage.value = 'No Stage' - tradingEngine.current.position.end.value = candle.end + tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value tradingEngine.current.position.status.value = 1 tradingEngine.current.position.exitType.value = 2 @@ -806,7 +800,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { } function getPositionRate() { - const DEFAULT_VALUE = candle.close + const DEFAULT_VALUE = tradingEngine.current.candle.close.value let strategy = tradingSystem.strategies[tradingEngine.current.strategy.index.value] if (strategy.openStage === undefined) return { DEFAULT_VALUE }