diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index af33f924a..c8e7e4fdd 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -147,30 +147,66 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { } function updateDistanceToEventsCounters() { - /* Keeping Distance Counters Up-to-date */ + /* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */ if ( - tradingEngine.current.distanceToEvent.triggerOn.value > 0 // with this we avoind counting before the first event happens. + tradingEngine.current.distanceToEvent.triggerOn.value > 0 ) { tradingEngine.current.distanceToEvent.triggerOn.value++ } if ( - tradingEngine.current.distanceToEvent.triggerOff.value > 0 // with this we avoind counting before the first event happens. + tradingEngine.current.distanceToEvent.triggerOff.value > 0 ) { tradingEngine.current.distanceToEvent.triggerOff.value++ } if ( - tradingEngine.current.distanceToEvent.takePosition.value > 0 // with this we avoind counting before the first event happens. + tradingEngine.current.distanceToEvent.takePosition.value > 0 ) { tradingEngine.current.distanceToEvent.takePosition.value++ } if ( - tradingEngine.current.distanceToEvent.closePosition.value > 0 // with this we avoind counting before the first event happens. + tradingEngine.current.distanceToEvent.closePosition.value > 0 ) { tradingEngine.current.distanceToEvent.closePosition.value++ } + + if ( + tradingEngine.current.distanceToEvent.nextPhase.value > 0 + ) { + tradingEngine.current.distanceToEvent.nextPhase.value++ + } + + if ( + tradingEngine.current.distanceToEvent.moveToPhase.value > 0 + ) { + tradingEngine.current.distanceToEvent.moveToPhase.value++ + } + + if ( + tradingEngine.current.distanceToEvent.createOrder.value > 0 + ) { + tradingEngine.current.distanceToEvent.createOrder.value++ + } + + if ( + tradingEngine.current.distanceToEvent.moveOrder.value > 0 + ) { + tradingEngine.current.distanceToEvent.moveOrder.value++ + } + + if ( + tradingEngine.current.distanceToEvent.cancelOrder.value > 0 + ) { + tradingEngine.current.distanceToEvent.cancelOrder.value++ + } + + if ( + tradingEngine.current.distanceToEvent.closeOrder.value > 0 + ) { + tradingEngine.current.distanceToEvent.closeOrder.value++ + } } } diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index cb7ff4b0d..d79347f2d 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -101,57 +101,6 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { } } - function persistIndividualRecord(record, product, outputDatasetArray) { - - if (product.config.saveAsObjects === true) { - /* For saving objects we need to take care of a different set of rules. */ - for (let j = 0; j < product.record.properties.length; j++) { - let recordProperty = product.record.properties[j] - if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) { - let propertyValue = record[j] - - if (bot.processingDailyFiles) { - /* - When dealing with Daily Files, we need to avoid to write an open object at the last 'candle' of the day, - since the object will be duplicated on the next day. How do we know we are positioned at the last candle - of the day? Easy: the end of the candle must be 1 millisecod before the next day. That happens at any - time frame. - */ - let currentDay = new Date(tradingEngine.current.candle.end.value) - let nextDay = new Date(tradingEngine.current.candle.end.value + 1) - if (currentDay.getUTCDate() !== nextDay.getUTCDate()) { - /* - We will save the object only if it is closed, becasuse we are at the last candle of the day. - */ - if (propertyValue === product.config.propertyValueThatClosesObject) { - outputDatasetArray.push(record) - } - } else { - /* - When we are not at the end of the day, we will save the object normally, like in market files. - */ - if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { - outputDatasetArray.push(record) - } - } - } - else { - /* - For Market Files we will add a record everytime that proeprty value does not match this - */ - if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { - outputDatasetArray.push(record) - } - } - break - } - } - } else { - /* When we are not dealing with objects, we add every record to the existing file. */ - outputDatasetArray.push(record) - } - } - function scanRecordDefinition(product, productRootNode, index) { let record = [] @@ -232,6 +181,57 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { return record } + function persistIndividualRecord(record, product, outputDatasetArray) { + + if (product.config.saveAsObjects === true) { + /* For saving objects we need to take care of a different set of rules. */ + for (let j = 0; j < product.record.properties.length; j++) { + let recordProperty = product.record.properties[j] + if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) { + let propertyValue = record[j] + + if (bot.processingDailyFiles) { + /* + When dealing with Daily Files, we need to avoid to write an open object at the last 'candle' of the day, + since the object will be duplicated on the next day. How do we know we are positioned at the last candle + of the day? Easy: the end of the candle must be 1 millisecod before the next day. That happens at any + time frame. + */ + let currentDay = new Date(tradingEngine.current.candle.end.value) + let nextDay = new Date(tradingEngine.current.candle.end.value + 1) + if (currentDay.getUTCDate() !== nextDay.getUTCDate()) { + /* + We will save the object only if it is closed, becasuse we are at the last candle of the day. + */ + if (propertyValue === product.config.propertyValueThatClosesObject) { + outputDatasetArray.push(record) + } + } else { + /* + When we are not at the end of the day, we will save the object normally, like in market files. + */ + if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { + outputDatasetArray.push(record) + } + } + } + else { + /* + For Market Files we will add a record everytime that proeprty value does not match this + */ + if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) { + outputDatasetArray.push(record) + } + } + break + } + } + } else { + /* When we are not dealing with objects, we add every record to the existing file. */ + outputDatasetArray.push(record) + } + } + function safeNumericValue(value) { /* The purpose of this function is to check that value variable does not have a value that diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index 356fdb22f..98014e6fc 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -141,6 +141,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingSystemModule.reset(chart) tradingSystemModule.mantainPositions() tradingSystemModule.mantainStrategies() + tradingSystemModule.mantainOrders() tradingEngineModule.updateEpisodeCountersAndStatistics() tradingEngineModule.updateDistanceToEventsCounters() diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index b720ea95a..8a943b666 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -8,6 +8,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM reset: reset, mantainStrategies: mantainStrategies, mantainPositions: mantainPositions, + mantainOrders: mantainOrders, triggerStage: triggerStage, openStage: openStage, manageStage: manageStage, @@ -113,6 +114,50 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingPositionModule.updateEnds() } + function mantainOrders() { + /* Reset All Exchange Orders that are on Closed status, to their initial value */ + if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return } + + let stageNode + let executionNode + + stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage + executionNode = stageNode.openExecution + resetExecution(executionNode) + + stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage + executionNode = stageNode.closeExecution + resetExecution(executionNode) + + function resetExecution(executionNode) { + + resetExecutionAlgorithms(executionNode) + + function resetExecutionAlgorithms(executionNode) { + for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { + let executionAlgorithm = executionNode.executionAlgorithms[i] + resetOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) + resetOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) + resetOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) + resetOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) + } + } + + function resetOrders(orders, executionAlgorithm, executionNode) { + for (let i = 0; i < orders.length; i++) { + let tradingSystemOrder = orders[i] + if (tradingSystemOrder.referenceParent === undefined) { continue } + let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) + if (tradingEngineOrder.status === undefined) { continue } + if (tradingEngineOrder.status.value === 'Closed') { + /* We reset the order data structure inside the Trading Engine to its initial value */ + tradingEngineOrder.initialize(tradingEngineOrder) + } + } + } + } + } + function evalConditions(startingNode, descendentOfNodeType) { evalNode(startingNode, 'Conditions', descendentOfNodeType) } @@ -319,6 +364,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM tradingStrategyModule.openStrategy(j, situation.name, strategy.name) + /* Updating Episode Counters */ + tradingEngine.episode.episodeCounters.strategies.value++ + /* Initialize this */ tradingEngine.current.distanceToEvent.triggerOn.value = 1 announcementsModule.makeAnnoucements(triggerStage.triggerOn) @@ -441,6 +489,9 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Opening Status Procedure */ if (tradingEngine.current.strategy.openStageStatus.value === 'Opening') { + /* Reset the Exchange Orders data structure to its initial value */ + tradingEngine.exchangeOrders.initialize(tradingEngine.exchangeOrders) + /* This procedure is intended to run only once */ let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage let executionNode = stageNode.openExecution @@ -919,9 +970,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Exiting Everything now. */ exitPositionAndStrategy() - - /* Reset Exchange Orders */ - tradingEngine.exchangeOrders.initialize(tradingEngine.exchangeOrders) } function exitPositionAndStrategy() { @@ -1094,25 +1142,80 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (tradingEngineOrder.orderStatistics.feesPaid === undefined) { continue } switch (tradingEngineOrder.status.value) { + case 'Not Open': { + { + /* Check if we need to Create this Order */ + if (stageIsClosing === true) { continue } + let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (situationName !== undefined) { + + /* Order Size Calculation */ + tradingEngineOrder.size.value = formulas.get(executionAlgorithm.positionSize.formula.id) * tradingSystemOrder.config.positionSizePercentage / 100 + if (stageOrdersSize.value + tradingEngineOrder.size.value > stageSizeLimit) { + /* We reduce the size to the remaining size of the position. */ + tradingEngineOrder.size.value = stageSizeLimit - stageOrdersSize.value + } + + if (tradingEngineOrder.size.value <= 0) { continue } + + /* Create Order Procedure */ + tradingEngineOrder.identifier.value = global.UNIQUE_ID() + tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value + tradingEngineOrder.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.status.value = 'Open' + tradingEngineOrder.orderName.value = tradingSystemOrder.name + tradingEngineOrder.algorithmName.value = executionAlgorithm.name + tradingEngineOrder.situationName.value = situationName + + /* Order Rate Calculation */ + tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. + if (tradingSystemOrder.positionRate !== undefined) { + if (tradingSystemOrder.positionRate.formula !== undefined) { + tradingEngineOrder.rate.value = formulas.get(tradingSystemOrder.positionRate.formula.id) + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + } + } + + /* Update State Orders Size */ + stageOrdersSize.value = stageOrdersSize.value + tradingEngineOrder.size.value + stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + + /* Updating Episode Counters */ + tradingEngine.episode.episodeCounters.orders.value++ + /* Initialize this */ + tradingEngine.current.distanceToEvent.createOrder.value = 1 + } + } + break + } case 'Open': { tradingEngineOrder.end.value = tradingEngine.current.candle.end.value tradingEngineOrder.orderCounters.periods.value++ tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS + /* If the stage is closing we wont be moving or cancelling orders based on defined events */ if (stageIsClosing !== true) { + + /* Check if we need to Cancel this Order */ let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) if (situationName !== undefined) { + /* Since the order was cancelled, we remove the unfilled amount of the order from here */ let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100 stageOrdersSize.value = stageOrdersSize.value - unfilledSize stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + /* Cancel the Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = 'Cancelled by Event' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.cancelOrder.value = 1 break } + /* Check if we need to Move this Order */ if (tradingSystemOrder.moveOrderEvents !== undefined) { for (let j = 0; j < tradingSystemOrder.moveOrderEvents.length; j++) { let moveOrderEvent = tradingSystemOrder.moveOrderEvents[j] @@ -1120,9 +1223,13 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (situationName !== undefined) { if (moveOrderEvent.positionRate !== undefined) { if (moveOrderEvent.positionRate.formula !== undefined) { + /* Move Order to a different Rate */ tradingEngineOrder.rate.value = formulas.get(moveOrderEvent.positionRate.formula.id) tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + + /* Initialize this */ + tradingEngine.current.distanceToEvent.moveOrder.value = 1 break } } @@ -1131,58 +1238,18 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } + /* Simulate Events that happens at the Exchange, if needed. */ if (stageIsOpening === false) { simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) } } - break - case 'Closed': { - // Nothing to do here - } - break - default: { - if (stageIsClosing === true) { continue } - - tradingEngineOrder.status.value = 'Not Open' - let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) - if (situationName !== undefined) { - - /* Order Size Calculation */ - tradingEngineOrder.size.value = formulas.get(executionAlgorithm.positionSize.formula.id) * tradingSystemOrder.config.positionSizePercentage / 100 - if (stageOrdersSize.value + tradingEngineOrder.size.value > stageSizeLimit) { - /* We reduce the size to the remaining size of the position. */ - tradingEngineOrder.size.value = stageSizeLimit - stageOrdersSize.value - } - - if (tradingEngineOrder.size.value <= 0) { continue } - - /* Create Order Procedure */ - tradingEngineOrder.identifier.value = global.UNIQUE_ID() - tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value - tradingEngineOrder.end.value = tradingEngine.current.candle.end.value - tradingEngineOrder.status.value = 'Open' - tradingEngineOrder.orderName.value = tradingSystemOrder.name - tradingEngineOrder.algorithmName.value = executionAlgorithm.name - tradingEngineOrder.situationName.value = situationName - - /* Order Rate Calculation */ - tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate. - if (tradingSystemOrder.positionRate !== undefined) { - if (tradingSystemOrder.positionRate.formula !== undefined) { - tradingEngineOrder.rate.value = formulas.get(tradingSystemOrder.positionRate.formula.id) - tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) - } - } - - stageOrdersSize.value = stageOrdersSize.value + tradingEngineOrder.size.value - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - } - } } } } function simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ switch (bot.SESSION.type) { case 'Backtesting Session': { break @@ -1198,6 +1265,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } + /* Filter by what is defined at the Strategy */ if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } /* Partial Fill Simulation */ @@ -1211,8 +1279,13 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) { + + /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = 'Filled' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 } /* Filled Size Calculation */ @@ -1221,13 +1294,24 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { if (tradingEngineOrder.orderStatistics.percentageFilled.value === 0) { + + /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = 'Cancelled by Closing Stage' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 } if (tradingEngineOrder.orderStatistics.percentageFilled.value > 0 && tradingEngineOrder.orderStatistics.percentageFilled.value < 100) { + + /* Close this Order */ tradingEngineOrder.status.value = 'Closed' tradingEngineOrder.exitType.value = 'Partially Filled' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 } + /* Since the order was cancelled, we remove the unfilled amount of the order from here */ let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100