exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEngineModule) { /* This module packages all functions related to Positions. */ const MODULE_NAME = 'Trading Position' let thisObject = { mantain: mantain, reset: reset, cycleBasedStatistics: cycleBasedStatistics, openPosition: openPosition, closingPosition: closingPosition, closePosition: closePosition, applyStopLossFormula: applyStopLossFormula, applyTakeProfitFormula: applyTakeProfitFormula, updateStopLoss: updateStopLoss, updateTakeProfit: updateTakeProfit, initialTargets: initialTargets, updateEnds: updateEnds, resetTradingEngineDataStructure: resetTradingEngineDataStructure, updateCounters: updateCounters, initialize: initialize, finalize: finalize } let tradingEngine let tradingSystem let sessionParameters return thisObject function initialize() { tradingSystem = bot.simulationState.tradingSystem tradingEngine = bot.simulationState.tradingEngine sessionParameters = bot.TRADING_SESSION.tradingParameters } function finalize() { tradingEngine = undefined tradingSystem = undefined sessionParameters = undefined } function mantain() { updateCounters() updateEnds() } function reset() { resetTradingEngineDataStructure() } function openPosition(situationName) { /* Starting begin and end */ tradingEngine.current.position.begin.value = tradingEngine.current.episode.cycle.lastBegin.value tradingEngine.current.position.end.value = tradingEngine.current.episode.cycle.lastEnd.value /* Recording the opening at the Trading Engine Data Structure */ tradingEngine.current.position.status.value = 'Open' tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value + 1 tradingEngine.current.position.identifier.value = global.UNIQUE_ID() tradingEngine.current.position.beginRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.position.positionBaseAsset.beginBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.beginBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value tradingEngine.current.position.situationName.value = situationName /* Initializing Stop and Take Phase */ tradingEngine.current.position.stopLoss.stopLossPhase.value = 1 tradingEngine.current.position.takeProfit.takeProfitPhase.value = 1 /* Updating Episode Counters */ tradingEngine.current.episode.episodeCounters.positions.value++ /* Inicializing this counter */ tradingEngine.current.episode.distanceToEvent.takePosition.value = 1 /* Remember the balance we had before taking the position to later calculate profit or loss */ tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value } function closingPosition(exitType) { tradingEngine.current.position.status.value = 'Closing' tradingEngine.current.position.exitType.value = exitType /* By the time we figured out that the stop loss or take profit were hit, or for whatever reason the position needs to be closed, the take profit and stop loss values for the next candle have already been calculated. In order to avoid being plotted, we will put them in zero. */ tradingEngine.current.position.stopLoss.value = 0 tradingEngine.current.position.takeProfit.value = 0 } function closePosition() { tradingEngine.current.position.status.value = 'Closed' tradingEngine.current.position.end.value = tradingEngine.current.episode.cycle.lastEnd.value tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value /* Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure. */ tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position) cycleBasedStatistics() /* Updating Hits & Fails */ if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) { tradingEngine.current.episode.episodeBaseAsset.hits.value++ } if (tradingEngine.current.position.positionBaseAsset.profitLoss.value < 0) { tradingEngine.current.episode.episodeBaseAsset.fails.value++ } if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) { tradingEngine.current.episode.episodeQuotedAsset.hits.value++ } if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value < 0) { tradingEngine.current.episode.episodeQuotedAsset.fails.value++ } } function applyStopLossFormula(formulas, formulaId) { updateStopLossTakeProfitFinalValue(tradingEngine.current.position.stopLoss) tradingEngine.current.position.stopLoss.value = formulas.get(formulaId) updateStopLossTakeProfitInitialValue(tradingEngine.current.position.stopLoss) updateStopLossTakeProfitBeginEnd(tradingEngine.current.position.stopLoss) } function applyTakeProfitFormula(formulas, formulaId) { updateStopLossTakeProfitFinalValue(tradingEngine.current.position.takeProfit) tradingEngine.current.position.takeProfit.value = formulas.get(formulaId) updateStopLossTakeProfitInitialValue(tradingEngine.current.position.takeProfit) updateStopLossTakeProfitBeginEnd(tradingEngine.current.position.takeProfit) } function updateStopLossTakeProfitInitialValue(node) { /* We store the first Stop Loss or Take Profit value in a separate node. To know if it is the first one we compare its current value to the initial value. */ if (node.initialValue.value === node.initialValue.config.initialValue) { node.initialValue.value = node.value } } function updateStopLossTakeProfitFinalValue(node) { /* We set the final value just before calcualting the new value, since the new calculated value is going to be discarded if the position is closed after it is calculated, but during the same candle. */ node.finalValue.value = node.value } function updateStopLossTakeProfitBeginEnd(node) { /* Both the Stop Loss and the Take Profit have their own Begin and End. Ther reason for this is because they represent targets to be checked at the next candle, so it does not apply that they share the begin and end of the position itself. */ node.begin.value = tradingEngine.current.episode.candle.begin.value + sessionParameters.timeFrame.config.value node.end.value = tradingEngine.current.episode.candle.end.value + sessionParameters.timeFrame.config.value } function updateStopLoss(phase) { tradingEngine.current.position.stopLoss.stopLossPhase.value = phase } function updateTakeProfit(phase) { tradingEngine.current.position.takeProfit.takeProfitPhase.value = phase } function initialTargets(tradingSystemStageNode, tradingEngineStageNode) { if (tradingSystemStageNode.initialTargets === undefined) { const message = 'Stage without Initial Targets node. Add one please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode) } setTargetRate() setTargetSize() function setTargetRate() { if (tradingSystemStageNode.initialTargets.targetRate === undefined) { const message = 'Target Rate Node not Found. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets) } if (tradingSystemStageNode.initialTargets.targetRate.formula === undefined) { const message = 'Formula of Target Rate Node not Found. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate) } let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetRate.formula.id) if (value === undefined) { const message = 'Target Rate can not be undefined. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula) } if (isNaN(value)) { const message = 'Target Rate must be a number. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula) } switch (tradingSystemStageNode.type) { case 'Open Stage': { tradingEngine.current.position.entryTargetRate.value = global.PRECISE(value, 10) break } case 'Close Stage': { tradingEngine.current.position.exitTargetRate.value = global.PRECISE(value, 10) break } } } function setTargetSize() { /* Basic Validation */ if ( tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined && tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined ) { const message = 'Only Target Size In Base Asset or Target Size In Quoted Asset is allowed. Remove one of them please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode) } /* Position In Base Asset */ if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined) { if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) { let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula.id) if (value === undefined) { const message = 'Target Size In Base Asset cannot be undefined. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula) } if (value === 0) { const message = 'Target Size In Base Asset cannot be zero. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula) } switch (tradingSystemStageNode.type) { case 'Open Stage': { tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = global.PRECISE(value, 10) tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = global.PRECISE(value * tradingEngine.current.position.entryTargetRate.value, 10) break } case 'Close Stage': { tradingEngine.current.position.positionBaseAsset.exitTargetSize.value = global.PRECISE(value, 10) tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value = global.PRECISE(value * tradingEngine.current.position.exitTargetRate.value, 10) break } } /* Remember how the end user defined this stage. */ tradingEngineStageNode.stageDefinedIn.value = 'Base Asset' } else { const message = 'You need to specify a Formula for this.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset) } } /* Position In Quoted Asset */ if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined) { if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) { let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula.id) if (value === undefined) { const message = 'Target Size In Quoted Asset cannot be undefined. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula) } if (value === 0) { const message = 'Target Size In Quoted Asset cannot be zero. Fix this please.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula) } switch (tradingSystemStageNode.type) { case 'Open Stage': { tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = global.PRECISE(value, 10) tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = global.PRECISE(value / tradingEngine.current.position.entryTargetRate.value, 10) break } case 'Close Stage': { tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value = global.PRECISE(value, 10) tradingEngine.current.position.positionBaseAsset.exitTargetSize.value = global.PRECISE(value / tradingEngine.current.position.exitTargetRate.value, 10) break } } /* Remember how the end user defined this stage. */ tradingEngineStageNode.stageDefinedIn.value = 'Quoted Asset' } else { const message = 'You need to specify a Formula for this.' badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset) } } } } function updateEnds() { if (tradingEngine.current.position.status.value === 'Open') { tradingEngine.current.position.end.value = tradingEngine.current.position.end.value + sessionParameters.timeFrame.config.value tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value } } function resetTradingEngineDataStructure() { if (tradingEngine.current.position.status.value === 'Closed') { tradingEngineModule.initializeNode(tradingEngine.current.position) } } function updateCounters() { if (tradingEngine.current.position.status.value === 'Open') { tradingEngine.current.position.positionCounters.periods.value++ } } function cycleBasedStatistics() { calculateAssetsStatistics() calculatePositionStatistics() function calculateAssetsStatistics() { /* Profit Loss Calculation */ tradingEngine.current.position.positionBaseAsset.profitLoss.value = tradingEngine.current.episode.episodeBaseAsset.balance.value - tradingEngine.current.position.positionBaseAsset.beginBalance tradingEngine.current.position.positionQuotedAsset.profitLoss.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value - tradingEngine.current.position.positionQuotedAsset.beginBalance tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10) tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10) /* ROI Calculation */ tradingEngine.current.position.positionBaseAsset.ROI.value = tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 / tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value tradingEngine.current.position.positionQuotedAsset.ROI.value = tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 / tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10) tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10) /* Hit Fail Calculation */ if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) { tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit' } if (tradingEngine.current.position.positionBaseAsset.ROI.value < 0) { tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail' } if (tradingEngine.current.position.positionBaseAsset.ROI.value === 0) { tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Even' } if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) { tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit' } if (tradingEngine.current.position.positionQuotedAsset.ROI.value < 0) { tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail' } if (tradingEngine.current.position.positionQuotedAsset.ROI.value === 0) { tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Even' } } function calculatePositionStatistics() { /* Profit Loss Calculation */ tradingEngine.current.position.positionStatistics.profitLoss.value = ( tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.position.endRate.value + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value ) tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10) /* ROI Calculation */ tradingEngine.current.position.positionStatistics.ROI.value = ( tradingEngine.current.position.positionStatistics.profitLoss.value ) * 100 / ( tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value + tradingEngine.current.position.positionQuotedAsset.beginBalance ) tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10) /* Hit Fail Calculation */ if (tradingEngine.current.position.positionStatistics.ROI.value > 0) { tradingEngine.current.position.positionStatistics.hitFail.value = 'Hit' } if (tradingEngine.current.position.positionStatistics.ROI.value < 0) { tradingEngine.current.position.positionStatistics.hitFail.value = 'Fail' } if (tradingEngine.current.position.positionStatistics.ROI.value === 0) { tradingEngine.current.position.positionStatistics.hitFail.value = 'Even' } /* Days Calculation */ tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10) } } function badDefinitionUnhandledException(err, message, node) { tradingSystem.errors.push([node.id, message]) logger.write(MODULE_NAME, "[ERROR] -> " + message); logger.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name); logger.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type); logger.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config)); if (err !== undefined) { logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack); } throw 'Please fix the problem and try again.' } }