diff --git a/LowFrequencyTrading/ExchangeAPI.js b/LowFrequencyTrading/ExchangeAPI.js index 437b52665..7bcfd280a 100644 --- a/LowFrequencyTrading/ExchangeAPI.js +++ b/LowFrequencyTrading/ExchangeAPI.js @@ -5,10 +5,9 @@ let bot = BOT let thisObject = { - getTicker: getTicker, - getOpenOrders: getOpenOrders, getOrder: getOrder, createOrder: createOrder, + cancelOrder: cancelOrder, initialize: initialize, finalize: finalize }; @@ -53,64 +52,27 @@ exchange = undefined } - async function getTicker(callBackFunction) { - try { - logInfo("getTicker -> Entering function."); - const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset - let ticker + async function getOrder(tradingSystemOrder, tradingEngineOrder) { - if (exchange.has['fetchTicker']) { - ticker = await (exchange.fetchTicker(symbol)) - callBackFunction(global.DEFAULT_OK_RESPONSE, ticker) - } else { - logError("getTicker -> Exchange does not support fetchTicker command."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + let orderId = tradingEngineOrder.exchangeId.value - } catch (err) { - logError("getTicker -> err = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); + /* Basic Logging */ + logInfo("getOrder -> Entering function. orderId = " + orderId); + + const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset + + /* Basic Validations */ + if (exchange.has['fetchOrder'] === false) { + logError("getOrder -> Exchange does not support fetchOrder command."); + return } - } - async function getOpenOrders(market, callBackFunction) { try { - logInfo("getOpenOrders -> Entering function. market = " + JSON.stringify(market)); - - const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset - let openOrders - - if (exchange.has['fetchOpenOrders']) { - openOrders = await (exchange.fetchOpenOrders(symbol)) - callBackFunction(global.DEFAULT_OK_RESPONSE, openOrders) - } else { - logError("getOpenOrders -> Exchange does not support fetchOpenOrders command."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - - } catch (err) { - logError("getOpenOrders -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - async function getOrder(orderId, market, callBackFunction) { - try { - logInfo("getOrder -> Entering function. orderId = " + orderId); - - const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset - let order - - if (exchange.has['fetchOrder']) { - order = await (exchange.fetchOrder(orderId, symbol)) - callBackFunction(global.DEFAULT_OK_RESPONSE, order) - } else { - logError("getOrder -> Exchange does not support fetchOrder command."); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } + let order = await (exchange.fetchOrder(orderId, symbol)) + return order } catch (err) { + tradingSystem.errors.push([tradingSystemOrder.id, err.message]) logError("getOrder -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -171,6 +133,30 @@ } } + async function cancelOrder(tradingSystemOrder, tradingEngineOrder) { + + let orderId = tradingEngineOrder.exchangeId.value + + /* Basic Logging */ + logInfo("getOrder -> Entering function. orderId = " + orderId); + + const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset + + /* Basic Validations */ + if (exchange.has['fetchOrder'] === false) { + logError("getOrder -> Exchange does not support fetchOrder command."); + return + } + + try { + let order = await (exchange.cancelOrder(orderId, symbol)) + return true + } catch (err) { + tradingSystem.errors.push([tradingSystemOrder.id, err.message]) + logError("getOrder -> Error = " + err.message); + } + } + function logInfo(message) { if (LOG_INFO === true) { logger.write(MODULE_NAME, '[INFO] ' + message) } } diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index abdb8351b..7d3813664 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -5,313 +5,447 @@ exports.newTradingExecution = function newTradingExecution(bot, logger) { const MODULE_NAME = 'Trading Execution' let thisObject = { - takePosition: takePosition, - closePosition: closePosition, + checkExecution: checkExecution, initialize: initialize, finalize: finalize } let tradingEngine + let tradingSystem return thisObject function initialize() { - + tradingSystem = bot.simulationState.tradingSystem tradingEngine = bot.simulationState.tradingEngine - } function finalize() { + tradingSystem = undefined tradingEngine = undefined } - function takePosition() { - /* Check that we are in LIVE or FORWARD MODE */ - if (bot.SESSION.type === 'Live Trading Session' || bot.SESSION.type === 'Fordward Testion Session') { - logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> Taking a Position in ' + bot.SESSION.type) - logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.size.value = ' + tradingEngine.current.position.size.value) - logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.rate.value = ' + tradingEngine.current.position.rate.value) - logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> slippageAmount = ' + slippageAmount) - logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> tradingEngine.current.position.rate.value = ' + tradingEngine.current.position.rate.value) + function checkExecution(executionNode, stageIsOpening, stageIsClosing, stageSizeLimit, stageOrdersSize, stageFilledSize) { - /* We see if we need to put the actual order at the exchange. */ - if (variable.executionContext !== undefined) { - switch (variable.executionContext.status) { - case 'Without a Position': { // We need to put the order because It was not put yet. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return + /* Enforcing Precision Limit */ + stageSizeLimit.value = global.PRECISE(stageSizeLimit.value, 10) + stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) + + checkExecutionAlgorithms(executionNode) + + function checkExecutionAlgorithms(executionNode) { + for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { + let executionAlgorithm = executionNode.executionAlgorithms[i] + checkOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) + checkOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) + checkOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) + checkOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) + } + } + + function checkOrders(orders, executionAlgorithm, executionNode) { + for (let i = 0; i < orders.length; i++) { + + let tradingSystemOrder = orders[i] + + /* Trading System Validations */ + if (tradingSystemOrder.config.positionSizePercentage === undefined) { continue } + if (tradingSystemOrder.referenceParent === undefined) { continue } + + let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id) + + /* Trading Engine Validations */ + if (tradingEngineOrder.serialNumber === undefined) { continue } + if (tradingEngineOrder.identifier === undefined) { continue } + if (tradingEngineOrder.exchangeId === 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 } + if (tradingEngineOrder.orderStatistics.days === undefined) { continue } + if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { continue } + if (tradingEngineOrder.orderStatistics.actualRate === undefined) { continue } + 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) { + + /* Open a new order */ + openOrder(tradingSystemOrder, tradingEngineOrder, situationName) } } break } - case 'Position Closed': { // Waiting for a confirmation that the position was closed. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return + 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 + + /* Simulate Events that happens at the Exchange, if needed. */ + simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) + + /* Check Events that happens at the Exchange, if needed. */ + checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) + + /* If the stage is closing or the order is not still Open, we wont be cancelling orders based on defined events */ + if (stageIsClosing !== true && tradingEngineOrder.status.value === 'Open') { + + /* Check if we need to Cancel this Order */ + let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) + if (situationName !== undefined) { + simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') + exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event') } } - break - } - case 'Taking Position': { // Waiting for a confirmation that the position was taken. - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is Taking Position.') - break - } - case 'In a Position': { // This should mean that we already put the order at the exchange. - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Exiting code block because status is In a Position.') - break - } - } - } else { // The context does not exist so it means we are not in a position. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return } } } - } else { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not trading live.') } - } + function openOrder(tradingSystemOrder, tradingEngineOrder, situationName) { + /* Order Size Calculation */ + tradingEngineOrder.size.value = tradingSystem.formulas.get(executionAlgorithm.positionSize.formula.id) * tradingSystemOrder.config.positionSizePercentage / 100 + if (stageOrdersSize.value + tradingEngineOrder.size.value > stageSizeLimit.value) { + /* We reduce the size to the remaining size of the position. */ + tradingEngineOrder.size.value = stageSizeLimit.value - stageOrdersSize.value + tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) + } - function putOpeningOrder() { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function.') + /* Check Order Size */ + if (tradingEngineOrder.size.value <= 0) { return } - /* We wont take a position unless we are withing the sessionParameters.timeRange.config.initialDatetime and the sessionParameters.timeRange.config.finalDatetime range */ - if (sessionParameters.timeRange.config.initialDatetime !== undefined) { - if (candle.end < sessionParameters.timeRange.config.initialDatetime) { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because current candle ends before the start date. -> sessionParameters.timeRange.config.initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) - takePositionAtSimulation() - return + /* Place Order at the Exchange */ + if (createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) !== true) { return } + + /* Updating Episode Counters */ + tradingEngine.episode.episodeCounters.orders.value++ + + /* Initialize this */ + tradingEngine.current.distanceToEvent.createOrder.value = 1 + + /* Create Order Procedure */ + tradingEngineOrder.status.value = 'Open' + tradingEngineOrder.identifier.value = global.UNIQUE_ID() + tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value + tradingEngineOrder.end.value = tradingEngine.current.candle.end.value + tradingEngineOrder.serialNumber.value = tradingEngine.episode.episodeCounters.orders.value + 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 = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id) + tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10) + } + } + + /* Update Stage Orders Size */ + stageOrdersSize.value = stageOrdersSize.value + tradingEngineOrder.size.value + stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + } + + function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return true + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return true + } + } + + let orderId = exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) + + if (orderId !== undefined) { + tradingEngineOrder.exchangeId.value = orderId + return true } } - /* We wont take a position if we are past the final datetime */ - if (sessionParameters.timeRange.config.finalDatetime !== undefined) { - if (candle.begin > sessionParameters.timeRange.config.finalDatetime) { - logger.write(MODULE_NAME, '[INFO] runSimulation -> putOpeningOrder -> Not placing the trade at the exchange because current candle begins after the end date. -> sessionParameters.timeRange.config.finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) - takePositionAtSimulation() - return + function simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + break + } + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { + break + } + } + + /* Filter by what is defined at the Strategy */ + if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } + + /* Partial Fill Simulation */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { + + /* Percentage Filled Calculation */ + let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 + if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { + percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value + } + 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 */ + stageFilledSize.value = stageFilledSize.value + tradingEngineOrder.size.value * percentageFilled / 100 + stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) + + if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { + simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') + } + } + } + + /* Actual Rate Simulation */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { + if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { + tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + } + } + } + + /* Fees Paid Simulation */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { + if (tradingEngineOrder.orderStatistics.feesPaid.value === tradingEngineOrder.orderStatistics.feesPaid.config.initialValue) { + tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 + } + } } } - /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ - if (variable.executionContext !== undefined) { - if (variable.executionContext.periods !== undefined) { - if (tradingEngine.episode.episodeCounters.periods.value <= variable.executionContext.periods) { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because it was already placed at a previous execution.') - takePositionAtSimulation() + function checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return true + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { + return true + } + } + + let order = exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) + + if (order === undefined) { return } + + const AT_EXCHANGE_STATUS = { + OPEN: 'open', + CLOSED: 'closed', + CANCELLED: 'canceled' + } + + /* Status Checks */ + if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100 && order.status === AT_EXCHANGE_STATUS.CLOSED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Filled' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 + } + if (tradingEngineOrder.orderStatistics.percentageFilled.value < 100 && order.status === AT_EXCHANGE_STATUS.CLOSED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Closed at the Exchange' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 + } + if (order.status === AT_EXCHANGE_STATUS.CANCELLED) { + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = 'Cancelled at the Exchange' + + /* Initialize this */ + tradingEngine.current.distanceToEvent.closeOrder.value = 1 + } + + syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) + + /* Forced Cancellation Check */ + if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { + exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') + } + } + + function syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) { + /* Percentage Filled */ + let currentPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value + let percentageFilled = order.filled * 100 / tradingEngineOrder.size + percentageFilled = global.PRECISE(percentageFilled.value, 10) + tradingEngineOrder.orderStatistics.percentageFilled.value = percentageFilled + + /* Filled Size Calculation */ + stageFilledSize.value = stageFilledSize.value - tradingEngineOrder.size.value * currentPercentageFilled / 100 // First remove the current filled size + stageFilledSize.value = stageFilledSize.value + tradingEngineOrder.size.value * percentageFilled / 100 // Second add the new filled size + stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) + + /* Actual Rate Simulation */ + tradingEngineOrder.orderStatistics.actualRate.value = order.cost / order.filled + tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) + + /* Fees Paid Simulation */ + tradingEngineOrder.orderStatistics.feesPaid.value = order.fee.rate + tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10) + } + + function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + break + } + case 'Live Trading Session': { + return + } + case 'Fordward Testing Session': { + return + } + case 'Paper Trading Session': { + break + } + } + + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = exitType + + /* 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 + stageOrdersSize.value = stageOrdersSize.value - unfilledSize + stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + } + + function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { + + /* Filter by Session Type */ + switch (bot.SESSION.type) { + case 'Backtesting Session': { + return + } + case 'Live Trading Session': { + break + } + case 'Fordward Testing Session': { + break + } + case 'Paper Trading Session': { return } } - } - /* We are not going to place orders based on outdated information. The next filter prevents firing orders when backtesting. */ - if (currentDay) { - let today = new Date(Math.trunc((new Date().valueOf()) / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) - let processDay = new Date(Math.trunc(currentDay.valueOf() / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS) - if (today.valueOf() !== processDay.valueOf()) { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because the current candle belongs to the previous day and that is considered simulation and not live trading.') - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> today = ' + today) - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = ' + processDay) - takePositionAtSimulation() - return + /* Check if we can cancel the order at the Exchange. */ + if (exchangeAPIModule.exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder) === true) { + /* Close this Order */ + tradingEngineOrder.status.value = 'Closed' + tradingEngineOrder.exitType.value = exitType + + /* 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 + stageOrdersSize.value = stageOrdersSize.value - unfilledSize + stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) + + /* + Perhaps the order was filled a bit more between the last time we checked and when it was cancelled. + To sync our accounting, we need to check the order one last time and if it changed, fix it. + */ + + let order = exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder) + + if (order === undefined) { return } + + const AT_EXCHANGE_STATUS = { + OPEN: 'open', + CLOSED: 'closed', + CANCELLED: 'canceled' + } + + syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) } } - let orderPrice - let amountA - let amountB - let orderSide - - if (bot.sessionAndMarketBaseAssetsAreEqual) { - orderSide = 'sell' - - orderPrice = tradingEngine.current.position.rate.value - - amountA = tradingEngine.current.position.size.value * orderPrice - amountB = tradingEngine.current.position.size.value - } else { - orderSide = 'buy' - - orderPrice = tradingEngine.current.position.rate.value - - amountA = tradingEngine.current.position.size.value - amountB = tradingEngine.current.position.size.value / orderPrice - } - - variable.executionContext = { - status: 'Taking Position', - periods: tradingEngine.episode.episodeCounters.periods.value - } - - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Ready to create order.') - exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) - - function onOrderCreated(err, order) { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function.') - - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') - variable.executionContext = { - status: 'In a Position', - periods: tradingEngine.episode.episodeCounters.periods.value, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - takePositionAtSimulation() - return + function checkOrderEvent(event, order, executionAlgorithm, executionNode) { + if (event !== undefined) { + for (let k = 0; k < event.situations.length; k++) { + let situation = event.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true } - case global.DEFAULT_FAIL_RESPONSE.result: { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') - logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) - strategy.openStage.openExecution.error = err.message - afterLoop() - return - } - case global.DEFAULT_RETRY_RESPONSE.result: { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') - logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) - strategy.openStage.openExecution.error = err.message - afterLoop() - return + + passed = checkConditions(situation, passed) + + tradingSystem.values.push([situation.id, passed]) + if (passed) { + tradingSystem.highlights.push(situation.id) + tradingSystem.highlights.push(event.id) + tradingSystem.highlights.push(order.id) + tradingSystem.highlights.push(executionAlgorithm.id) + tradingSystem.highlights.push(executionNode.id) + + announcementsModule.makeAnnoucements(event) + return situation.name // if the event is triggered, we return the name of the situation that passed } } - logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - return - } catch (err) { - logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - return - } - } - } - - function closePosition() { - /* Check that we are in LIVE MODE */ - if (bot.SESSION.type === 'Live Trading Session' || bot.SESSION.type === 'Fordward Testion Session') { - /* We see if we need to put the actual order at the exchange. */ - if (variable.executionContext !== undefined) { - switch (variable.executionContext.status) { - case 'Without a Position': { // No way to close anything at the exchange. - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Without a Position.') - break - } - case 'In a Position': { // This should mean that we already put the order at the exchange. - if (strategy.closeStage !== undefined) { - if (strategy.closeStage.closeExecution !== undefined) { - putClosingOrder() - return - } - } - break - } - case 'Closing Position': { // Waiting for a confirmation that the position was taken. - if (strategy.closeStage !== undefined) { - if (strategy.closeStage.closeExecution !== undefined) { - putClosingOrder() - return - } - } - break - } - - case 'Position Closed': { // - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed.') - break - } - } - } - } - } - - function putClosingOrder() { - /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ - if (variable.executionContext !== undefined) { - if (variable.executionContext.periods !== undefined) { - if (tradingEngine.episode.episodeCounters.periods.value <= variable.executionContext.periods) { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> Exiting function because this closing was already submited at a previous execution.') - closePositionAtSimulation() - return - } - } - } - - let orderPrice - let amountA - let amountB - let orderSide - - if (bot.sessionAndMarketBaseAssetsAreEqual) { - orderSide = 'buy' - - orderPrice = ticker.last + 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. - - amountA = tradingEngine.current.balance.quotedAsset.value - amountB = tradingEngine.current.balance.quotedAsset.value / orderPrice - } else { - orderSide = 'sell' - - orderPrice = ticker.last - 100 // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. - - amountA = tradingEngine.current.balance.baseAsset.value * orderPrice - amountB = tradingEngine.current.balance.baseAsset.value - } - - variable.executionContext = { - status: 'Closing Position', - periods: tradingEngine.episode.episodeCounters.periods.value - } - - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> About to close position at the exchange.') - exchangeAPI.createOrder(bot.market, orderSide, orderPrice, amountA, amountB, onOrderCreated) - - function onOrderCreated(err, order) { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function.') - - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') - variable.executionContext = { - status: 'Position Closed', - periods: tradingEngine.episode.episodeCounters.periods.value, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - closePositionAtSimulation() - return - } - case global.DEFAULT_FAIL_RESPONSE.result: { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') - /* We will assume that the problem is temporary, and expect that it will work at the next execution. */ - strategy.closeStage.closeExecution.error = err.message - afterLoop() - return - } - case global.DEFAULT_RETRY_RESPONSE.result: { - logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') - strategy.closeStage.closeExecution.error = err.message - afterLoop() - return - } - } - logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Unexpected Response -> Message = ' + err.message) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - return - } catch (err) { - logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> err = ' + err.stack) - callBackFunction(global.DEFAULT_FAIL_RESPONSE) - return } } } diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index b990c0afb..27c4ae05c 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -23,8 +23,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM let tradingEngine let sessionParameters - let conditions = new Map() - let formulas = new Map() + tradingSystem.conditions = new Map() + tradingSystem.formulas = new Map() const EXCHANGE_API_MODULE = require('./ExchangeAPI.js') let exchangeAPIModule = EXCHANGE_API_MODULE.newExchangeAPI(bot, logger) @@ -80,8 +80,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM chart = undefined - conditions = undefined - formulas = undefined + tradingSystem.conditions = undefined + tradingSystem.formulas = undefined tradingSystem.highlights = undefined tradingSystem.errors = undefined @@ -270,7 +270,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } - conditions.set(node.id, value) + tradingSystem.conditions.set(node.id, value) if (value === true) { tradingSystem.highlights.push(node.id) @@ -311,7 +311,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } - formulas.set(node.id, value) + tradingSystem.formulas.set(node.id, value) if (error !== undefined) { tradingSystem.errors.push([node.id, error]) @@ -524,7 +524,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') - checkExecution(executionNode, true, false, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) + tradingExecutionModule.checkExecution(executionNode, true, false, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) /* From here on, the state is officially Open */ tradingStrategyModule.updateStageStatus('Open Stage', 'Open') @@ -544,7 +544,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') - checkExecution(executionNode, false, false, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) + tradingExecutionModule.checkExecution(executionNode, false, false, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) /* The Open is finished when the fillSize reaches the Position Size. @@ -572,7 +572,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') - checkExecution(executionNode, false, true, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) + tradingExecutionModule.checkExecution(executionNode, false, true, tradingEngine.current.position.size, tradingEngine.current.position.openStageOrdersSize, tradingEngine.current.position.openStageFilledSize) } /* @@ -609,7 +609,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (strategy.openStage.initialDefinition.positionSize === undefined) return DEFAULT_VALUE if (strategy.openStage.initialDefinition.positionSize.formula === undefined) return DEFAULT_VALUE - let value = formulas.get(strategy.openStage.initialDefinition.positionSize.formula.id) + let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionSize.formula.id) if (value === undefined) return DEFAULT_VALUE return value } @@ -623,7 +623,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (strategy.openStage.initialDefinition.positionRate === undefined) return DEFAULT_VALUE if (strategy.openStage.initialDefinition.positionRate.formula === undefined) return DEFAULT_VALUE - let value = formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id) + let value = tradingSystem.formulas.get(strategy.openStage.initialDefinition.positionRate.formula.id) if (value === undefined) return DEFAULT_VALUE return value } @@ -958,7 +958,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM evalConditions(stageNode, 'Close Execution') evalFormulas(stageNode, 'Close Execution') - checkExecution(executionNode, false, false, tradingEngine.current.position.openStageFilledSize, tradingEngine.current.position.closeStageOrdersSize, tradingEngine.current.position.closeStageFilledSize) + tradingExecutionModule.checkExecution(executionNode, false, false, tradingEngine.current.position.openStageFilledSize, tradingEngine.current.position.closeStageOrdersSize, tradingEngine.current.position.closeStageFilledSize) /* Close the Stage Validation */ if (tradingEngine.current.position.closeStageFilledSize.value === tradingEngine.current.position.openStageFilledSize.value) { @@ -1071,7 +1071,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (phase !== undefined) { if (phase.formula !== undefined) { let previousValue = tradingEngine.current.position.stopLoss.value - tradingPositionModule.applyStopLossFormula(formulas, phase.formula.id) + tradingPositionModule.applyStopLossFormula(tradingSystem.formulas, phase.formula.id) if (tradingEngine.current.position.stopLoss.value !== previousValue) { announcementsModule.makeAnnoucements(phase) @@ -1103,7 +1103,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM if (phase !== undefined) { if (phase.formula !== undefined) { let previousValue = tradingEngine.current.position.takeProfit.value - tradingPositionModule.applyTakeProfitFormula(formulas, phase.formula.id) + tradingPositionModule.applyTakeProfitFormula(tradingSystem.formulas, phase.formula.id) if (tradingEngine.current.position.takeProfit.value !== previousValue) { announcementsModule.makeAnnoucements(phase) @@ -1112,305 +1112,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM } } - function checkExecution(executionNode, stageIsOpening, stageIsClosing, stageSizeLimit, stageOrdersSize, stageFilledSize) { - - /* Enforcing Precision Limit */ - stageSizeLimit.value = global.PRECISE(stageSizeLimit.value, 10) - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) - - checkExecutionAlgorithms(executionNode) - - function checkExecutionAlgorithms(executionNode) { - for (let i = 0; i < executionNode.executionAlgorithms.length; i++) { - let executionAlgorithm = executionNode.executionAlgorithms[i] - checkOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode) - checkOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode) - checkOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode) - checkOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode) - } - } - - function checkOrders(orders, executionAlgorithm, executionNode) { - for (let i = 0; i < orders.length; i++) { - let tradingSystemOrder = orders[i] - - /* Basic Validations */ - if (tradingSystemOrder.config.positionSizePercentage === undefined) { continue } - if (tradingSystemOrder.referenceParent === undefined) { continue } - - 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 } - if (tradingEngineOrder.orderStatistics.days === undefined) { continue } - if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { continue } - if (tradingEngineOrder.orderStatistics.actualRate === undefined) { continue } - 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.value) { - /* We reduce the size to the remaining size of the position. */ - tradingEngineOrder.size.value = stageSizeLimit.value - stageOrdersSize.value - tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10) - } - - if (tradingEngineOrder.size.value <= 0) { continue } - - /* Place Order at the Exchange */ - if (placeOrderAtExchange(tradingSystemOrder, tradingEngineOrder) !== true) { continue } - - /* Updating Episode Counters */ - tradingEngine.episode.episodeCounters.orders.value++ - - /* Initialize this */ - tradingEngine.current.distanceToEvent.createOrder.value = 1 - - /* Create Order Procedure */ - tradingEngineOrder.status.value = 'Open' - tradingEngineOrder.identifier.value = global.UNIQUE_ID() - tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value - tradingEngineOrder.end.value = tradingEngine.current.candle.end.value - tradingEngineOrder.serialNumber.value = tradingEngine.episode.episodeCounters.orders.value - 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 Stage Orders Size */ - stageOrdersSize.value = stageOrdersSize.value + tradingEngineOrder.size.value - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - } - } - 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] - let situationName = checkOrderEvent(moveOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode) - 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 - } - } - } - } - } - } - - /* Simulate Events that happens at the Exchange, if needed. */ - simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) - } - } - } - } - - function placeOrderAtExchange(tradingSystemOrder, tradingEngineOrder) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - return true - } - case 'Live Trading Session': { - break - } - case 'Fordward Testing Session': { - break - } - case 'Paper Trading Session': { - return true - } - } - - let orderId = exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder) - - if (orderId !== undefined) { - tradingEngineOrder.exchangeId.value = orderId - return true - } - } - - function simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) { - - /* Filter by Session Type */ - switch (bot.SESSION.type) { - case 'Backtesting Session': { - break - } - case 'Live Trading Session': { - return - } - case 'Fordward Testing Session': { - return - } - case 'Paper Trading Session': { - break - } - } - - /* Filter by what is defined at the Strategy */ - if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return } - - /* Partial Fill Simulation */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { - - /* Percentage Filled Calculation */ - let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100 - if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) { - percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value - } - 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 */ - stageFilledSize.value = stageFilledSize.value + tradingEngineOrder.size.value * percentageFilled / 100 - stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) - - 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 - stageOrdersSize.value = stageOrdersSize.value - unfilledSize - stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - } - } - } - - /* Actual Rate Simulation */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { - if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { - tradingEngineOrder.orderStatistics.actualRate.value = formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) - } - } - } - - /* Fees Paid Simulation */ - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) { - if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) { - if (tradingEngineOrder.orderStatistics.feesPaid.value === tradingEngineOrder.orderStatistics.feesPaid.config.initialValue) { - tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 - } - } - } - } - - function checkOrderEvent(event, order, executionAlgorithm, executionNode) { - if (event !== undefined) { - for (let k = 0; k < event.situations.length; k++) { - let situation = event.situations[k] - let passed - if (situation.conditions.length > 0) { - passed = true - } - - passed = checkConditions(situation, passed) - - tradingSystem.values.push([situation.id, passed]) - if (passed) { - tradingSystem.highlights.push(situation.id) - tradingSystem.highlights.push(event.id) - tradingSystem.highlights.push(order.id) - tradingSystem.highlights.push(executionAlgorithm.id) - tradingSystem.highlights.push(executionNode.id) - - announcementsModule.makeAnnoucements(event) - return situation.name // if the event is triggered, we return the name of the situation that passed - } - } - } - } - } - function isItInside(elementWithTimestamp, elementWithBeginEnd) { /* The function is to allow in Conditions and Formulas to easily know when an element with timestamp (like the ones of single files) are inside the time range @@ -1429,8 +1130,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM let condition = situation.conditions[m] let value = false - if (conditions.get(condition.id) !== undefined) { - value = conditions.get(condition.id) + if (tradingSystem.conditions.get(condition.id) !== undefined) { + value = tradingSystem.conditions.get(condition.id) } if (value === true) { tradingSystem.highlights.push(condition.id)