From 55c1c67e267287cee4397c7052dc5c2701d306e7 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Mon, 13 Jul 2020 14:45:15 +0200 Subject: [PATCH] Market Orders Being Saved --- LowFrequencyTrading/TradingEngine.js | 9 + LowFrequencyTrading/TradingPosition.js | 5 +- LowFrequencyTrading/TradingRecords.js | 250 +++++++++++++---------- LowFrequencyTrading/TradingSimulation.js | 2 +- LowFrequencyTrading/TradingStrategy.js | 4 +- LowFrequencyTrading/TradingSystem.js | 72 ++++--- 6 files changed, 196 insertions(+), 146 deletions(-) diff --git a/LowFrequencyTrading/TradingEngine.js b/LowFrequencyTrading/TradingEngine.js index 7a549481d..af33f924a 100644 --- a/LowFrequencyTrading/TradingEngine.js +++ b/LowFrequencyTrading/TradingEngine.js @@ -5,6 +5,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { */ const MODULE_NAME = 'Trading Engine' let thisObject = { + getNodeById: getNodeById, cloneValues: cloneValues, setCurrentCandle: setCurrentCandle, updateEpisodeCountersAndStatistics: updateEpisodeCountersAndStatistics, @@ -15,6 +16,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { let tradingEngine let sessionParameters + let nodesMap = new Map() return thisObject @@ -34,6 +36,11 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { function finalize() { tradingEngine = undefined sessionParameters = undefined + nodesMap = undefined + } + + function getNodeById(NodeId) { + return nodesMap.get(NodeId) } function setCurrentCandle(candle) { @@ -90,6 +97,8 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { function initializeNode(node) { if (node === undefined) { return } + /* Add to Node Map */ + nodesMap.set(node.id, node) /* Here we initialize the node value */ if (node.config !== undefined) { diff --git a/LowFrequencyTrading/TradingPosition.js b/LowFrequencyTrading/TradingPosition.js index ad7bc42fc..1712cd46e 100644 --- a/LowFrequencyTrading/TradingPosition.js +++ b/LowFrequencyTrading/TradingPosition.js @@ -1,4 +1,4 @@ -exports.newTradingPosition = function newTradingPosition(bot, logger) { +exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEngineModule) { /* This module packages all functions related to Positions. */ @@ -60,9 +60,6 @@ exports.newTradingPosition = function newTradingPosition(bot, logger) { /* 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. */ - const TRADING_ENGINE_MODULE = require('./TradingEngine.js') - let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) - tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position) } diff --git a/LowFrequencyTrading/TradingRecords.js b/LowFrequencyTrading/TradingRecords.js index f15526649..ca81843e4 100644 --- a/LowFrequencyTrading/TradingRecords.js +++ b/LowFrequencyTrading/TradingRecords.js @@ -45,85 +45,107 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { let outputDatasetArray = outputDatasetsMap.get(product.config.codeName) if (bot.processingDailyFiles === true && dataset.config.type === 'Daily Files') { - persistRecord() + persistRecords() } if (bot.processingDailyFiles === false && dataset.config.type === 'Market Files') { - persistRecord() + persistRecords() } - function persistRecord() { - pruneOutputFile(product, outputDatasetArray) - let record = scanRecordDefinition(product) + function persistRecords() { - 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] - /* - We check that the existing file does not contain at the end a record that - does not signal that the object is closed. - If it does, we replace it with the new one until at some point it will signal - does the object is closed. + /* Clean the file from information of previous executions */ + pruneOutputFile(product, outputDatasetArray) + + /* + The product root can be a node or a node property of type array. + */ + let productRoot = eval(product.config.nodePath) + + if (product.config.nodePathType === 'array') { + /* + This means that the configured nodePath is not pointing to a node, but to a node property that is an array. + For that reason we will assume that each element of the array is a record to be outputed + */ + for (let index = 0; index < productRoot.length; index++) { + /* + The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values. + */ + let productRootNode = productRoot[index] + let record = scanRecordDefinition(product, productRootNode, index) + persistIndividualRecord(record, product, outputDatasetArray) + } + } else { + /* + This means that the configured nodePath points to a single node, which is the one whose children constitutes + the record to be saved at the output file. + */ + /* + The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values. + */ + let productRootNode = productRoot + let record = scanRecordDefinition(product, productRootNode) + persistIndividualRecord(record, product, outputDatasetArray) + } + } + } + + 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] + + /* Remove Open Records */ + spliceOpenRecords(j, product, outputDatasetArray) + + 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 lastRecord = outputDatasetArray[outputDatasetArray.length - 1] - if (lastRecord !== undefined) { - let lastRecordValue = lastRecord[j] - if (lastRecordValue !== product.config.propertyValueThatClosesObject) { - outputDatasetArray.splice(outputDatasetArray.length - 1, 1) - } - } - if (bot.processingDailyFiles) { + let currentDay = new Date(tradingEngine.current.candle.end.value) + let nextDay = new Date(tradingEngine.current.candle.end.value + 1) + if (currentDay.getUTCDate() !== nextDay.getUTCDate()) { /* - 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. + We will save the object only if it is closed, becasuse we are at the last candle of the day. */ - 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) - } + if (propertyValue === product.config.propertyValueThatClosesObject) { + outputDatasetArray.push(record) } - } - else { + } else { /* - For Market Files we will add a record everytime that proeprty value does not match this + 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) } } - break } + 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) } + } else { + /* When we are not dealing with objects, we add every record to the existing file. */ + outputDatasetArray.push(record) } } - function scanRecordDefinition(product) { - /* - The Product Root Node is the root of the node hiriarchy branch that we need to look at - in order to fnd the node where we are going to extract the value. - */ - let productRootNode = eval(product.config.nodePath) + function scanRecordDefinition(product, productRootNode, index) { + let record = [] for (let j = 0; j < product.record.properties.length; j++) { let recordProperty = product.record.properties[j] @@ -201,64 +223,84 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) { } return record } - } - function safeNumericValue(value) { - /* - The purpose of this function is to check that value variable does not have a value that - will later break the JSON format of files where this is going to be stored at. - */ - if (value === Infinity) { - value = Number.MAX_SAFE_INTEGER + function safeNumericValue(value) { + /* + The purpose of this function is to check that value variable does not have a value that + will later break the JSON format of files where this is going to be stored at. + */ + if (value === Infinity) { + value = Number.MAX_SAFE_INTEGER + } + if (value === undefined) { + value = 0 + } + if (value === null) { + value = 0 + } + if (isNaN(value)) { + value = 0 + } + return value } - if (value === undefined) { - value = 0 - } - if (value === null) { - value = 0 - } - if (isNaN(value)) { - value = 0 - } - return value - } - function safeStringValue(value) { - /* - The purpose of this function is to check that value variable does not have a value that - will later break the JSON format of files where this is going to be stored at. - */ - if (value === undefined) { - value = '' + function safeStringValue(value) { + /* + The purpose of this function is to check that value variable does not have a value that + will later break the JSON format of files where this is going to be stored at. + */ + if (value === undefined) { + value = '' + } + if (value === null) { + value = '' + } + return value } - if (value === null) { - value = '' + + function pruneOutputFile(product, outputFile) { + if (outputFile.isPrunned === true) { return } + /* + When a session is resumed, we will be potentially reading output files belonging to a previous session execution. + For that reason we need to prune all the records that are beyond the current candle. We do not delete everything + because we might be resuming a stopped session, which is fine. + */ + for (let i = 0; i < outputFile.length; i++) { + let record = outputFile[i] + + for (let j = 0; j < product.record.properties.length; j++) { + let recordProperty = product.record.properties[j] + if (recordProperty.config.codeName === 'end') { + let end = record[j] + if (end >= tradingEngine.current.candle.end.value) { + outputFile.splice(i, 1) + pruneOutputFile(product, outputFile) + return + } + } + } + } + outputFile.isPrunned = true } - return value - } - function pruneOutputFile(product, outputFile) { - if (outputFile.isPrunned === true) { return } - /* - When a session is resumed, we will be potentially reading output files belonging to a previous session execution. - For that reason we need to prune all the records that are beyond the current candle. We do not delete everything - because we might be resuming a stopped session, which is fine. - */ - for (let i = 0; i < outputFile.length; i++) { - let record = outputFile[i] + function spliceOpenRecords(j, product, outputDatasetArray) { + /* + Before adding records to the output file, we need to remove all open records, + because we will be adding the same records later with potentially + new or updated information. + */ - for (let j = 0; j < product.record.properties.length; j++) { - let recordProperty = product.record.properties[j] - if (recordProperty.config.codeName === 'end') { - let end = record[j] - if (end >= tradingEngine.current.candle.end.value) { - outputFile.splice(i, 1) - pruneOutputFile(product, outputFile) + for (let i = 0; i < outputDatasetArray.length; i++) { + let dataRecord = outputDatasetArray[i] + if (dataRecord !== undefined) { + let dataRecordValue = dataRecord[j] + if (dataRecordValue !== product.config.propertyValueThatClosesObject) { + outputDatasetArray.splice(i, 1) + spliceOpenRecords(j, product, outputDatasetArray) return } } } } - outputFile.isPrunned = true } } \ No newline at end of file diff --git a/LowFrequencyTrading/TradingSimulation.js b/LowFrequencyTrading/TradingSimulation.js index ed061680a..d8b18737b 100644 --- a/LowFrequencyTrading/TradingSimulation.js +++ b/LowFrequencyTrading/TradingSimulation.js @@ -55,7 +55,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT tradingRecordsModule.initialize(outputDatasetsMap) const TRADING_SYSTEM_MODULE = require('./TradingSystem.js') - let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger) + let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger, tradingEngineModule) tradingSystemModule.initialize() /* Setting up the candles array: The whole simulation is based on the array of candles at the time-frame defined at the session parameters. */ diff --git a/LowFrequencyTrading/TradingStrategy.js b/LowFrequencyTrading/TradingStrategy.js index a07627cd1..9b6fa41cb 100644 --- a/LowFrequencyTrading/TradingStrategy.js +++ b/LowFrequencyTrading/TradingStrategy.js @@ -1,4 +1,4 @@ -exports.newTradingStrategy = function newTradingStrategy(bot, logger) { +exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEngineModule) { /* This module packages all functions related to Strategies. */ @@ -48,8 +48,6 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger) { /* Now that the strategy is closed, it is the right time to move this strategy from current to last at the Trading Engine data structure. */ - const TRADING_ENGINE_MODULE = require('./TradingEngine.js') - let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) tradingEngineModule.cloneValues(tradingEngine.current.strategy, tradingEngine.last.strategy) } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 3806416ab..0ea5f1562 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -1,4 +1,4 @@ -exports.newTradingSystem = function newTradingSystem(bot, logger) { +exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineModule) { /* The Trading System is the user defined set of rules compliant with the Trading Protocol that defines the trading logic to be applied during each cycle of the Simulation. @@ -26,10 +26,10 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { let formulas = new Map() const TRADING_STRATEGY_MODULE = require('./TradingStrategy.js') - let tradingStrategyModule = TRADING_STRATEGY_MODULE.newTradingStrategy(bot, logger) + let tradingStrategyModule = TRADING_STRATEGY_MODULE.newTradingStrategy(bot, logger, tradingEngineModule) const TRADING_POSITION_MODULE = require('./TradingPosition.js') - let tradingPositionModule = TRADING_POSITION_MODULE.newTradingPosition(bot, logger) + let tradingPositionModule = TRADING_POSITION_MODULE.newTradingPosition(bot, logger, tradingEngineModule) const ANNOUNCEMENTS_MODULE = require('./Announcements.js') let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger) @@ -1008,37 +1008,41 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { function checkOrders(orders, executionAlgorithm, executionNode) { for (let i = 0; i < orders.length; i++) { - let order = orders[i] + let tradingSystemOrder = orders[i] /* Basic Validations */ - if (order.referenceParent === undefined) { continue } - if (order.referenceParent.identifier === undefined) { continue } - if (order.referenceParent.begin === undefined) { continue } - if (order.referenceParent.end === undefined) { continue } - if (order.referenceParent.rate === undefined) { continue } - if (order.referenceParent.size === undefined) { continue } - if (order.referenceParent.status === undefined) { continue } - if (order.referenceParent.algorithmName === undefined) { continue } - if (order.referenceParent.orderCounters === undefined) { continue } - if (order.referenceParent.orderCounters.periods === undefined) { continue } - if (order.positionSize === undefined) { continue } - if (order.positionSize.formula === undefined) { continue } + if (tradingSystemOrder.positionSize === undefined) { continue } + if (tradingSystemOrder.positionSize.formula === undefined) { continue } + if (tradingSystemOrder.referenceParent === undefined) { continue } - switch (order.referenceParent.status.value) { + let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) + + if (tradingEngineOrder.identifier === undefined) { continue } + if (tradingEngineOrder.begin === undefined) { continue } + if (tradingEngineOrder.end === undefined) { continue } + if (tradingEngineOrder.rate === undefined) { continue } + if (tradingEngineOrder.size === undefined) { continue } + if (tradingEngineOrder.status === undefined) { continue } + if (tradingEngineOrder.algorithmName === undefined) { continue } + if (tradingEngineOrder.orderCounters === undefined) { continue } + if (tradingEngineOrder.orderCounters.periods === undefined) { continue } + + + switch (tradingEngineOrder.status.value) { case 'Open': { - order.referenceParent.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.end.value = tradingEngine.current.candle.end.value - let mustCancelOrder = checkOrderEvent(order.cancelOrderEvent, order, executionAlgorithm, executionNode) + let mustCancelOrder = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) if (mustCancelOrder === true) { // Cancel Order - order.referenceParent.status.value = 'Closed' - order.referenceParent.exitType.value = 'Cancelled' + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Cancelled' } - let mustMoveOrder = checkOrderEvent(order.moveOrderEvent, order, executionAlgorithm, executionNode) - if (mustMoveOrder === true && order.referenceParent.status.value === 'Open') { + let mustMoveOrder = checkOrderEvent(tradingSystemOrder.moveOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (mustMoveOrder === true && tradingEngineOrder.status.value === 'Open') { // Move Order - order.referenceParent.status.value = 'Open' + tradingEngineOrder.status.value = 'Open' } } break @@ -1047,18 +1051,18 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { } break default: { - order.referenceParent.status.value = 'Not Open' - let mustCreateOrder = checkOrderEvent(order.createOrderEvent, order, executionAlgorithm, executionNode) + tradingEngineOrder.status.value = 'Not Open' + let mustCreateOrder = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) if (mustCreateOrder === true) { // Create Order - order.referenceParent.identifier.value = global.UNIQUE_ID() - order.referenceParent.begin.value = tradingEngine.current.candle.begin.value - order.referenceParent.end.value = tradingEngine.current.candle.end.value - order.referenceParent.rate.value = tradingEngine.current.candle.end.value - order.referenceParent.size.value = formulas.get(order.positionSize.formula.id) - order.referenceParent.status.value = 'Open' - order.referenceParent.algorithmName.value = executionAlgorithm.name - order.referenceParent.orderCounters.periods++ + tradingEngineOrder.identifier.value = global.UNIQUE_ID() + tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value + tradingEngineOrder.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value + tradingEngineOrder.size.value = formulas.get(tradingSystemOrder.positionSize.formula.id) + tradingEngineOrder.status.value = 'Open' + tradingEngineOrder.algorithmName.value = executionAlgorithm.name + tradingEngineOrder.orderCounters.periods.value++ } } }