diff --git a/LowFrequencyTrading/TradingExecution.js b/LowFrequencyTrading/TradingExecution.js index 2c6b6afe3..4cd6a6912 100644 --- a/LowFrequencyTrading/TradingExecution.js +++ b/LowFrequencyTrading/TradingExecution.js @@ -48,7 +48,6 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* Enforcing Precision Limit */ stageSizeLimit.value = global.PRECISE(stageSizeLimit.value, 10) stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10) - stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) await checkExecutionAlgorithms(executionNode) @@ -223,41 +222,18 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE /* 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') - } - } - } + let previousSizeFilled = tradingEngineOrder.orderStatistics.sizeFilled.value + let previousPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value + let previousAmountReceived = tradingEngineOrder.orderStatistics.amountReceived.value + let previousFeesPaid = tradingEngineOrder.orderStatistics.feesPaid.value /* Actual Rate Simulation */ if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) { if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) { + /* Calculate this only once for this order */ if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) { tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id) + tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) } } } @@ -267,8 +243,59 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE 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 + tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10) } } + } else { + /* Fees are simulated based on the Session Paremeters */ + switch (tradingEngineOrder.type) { + case 'Market Order': { + tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * bot.SESSION.parameters.feeStructure.config.taker / 100 + break + } + case 'Limit Order': { + tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * bot.SESSION.parameters.feeStructure.config.maker / 100 + break + } + } + } + + /* Partial Fill Simulation */ + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) { + if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) { + + /* Percentage Filled */ + 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 + + /* Check if we need to close */ + 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 + } + } + } + + /* Size Filled */ + tradingEngineOrder.orderStatistics.sizeFilled.value = tradingEngineOrder.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + tradingEngineOrder.orderStatistics.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.sizeFilled.value, 10) + + /* Amount Received */ + tradingEngineOrder.orderStatistics.amountReceived.value = tradingEngineOrder.orderStatistics.sizeFilled.value * tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate + tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) + + doTheAccounting(previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid) + + if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') { + simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage') } } @@ -338,24 +365,74 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE } function syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) { + + let previousSizeFilled = tradingEngineOrder.orderStatistics.sizeFilled.value + let previousPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value + let previousAmountReceived = tradingEngineOrder.orderStatistics.amountReceived.value + let previousFeesPaid = tradingEngineOrder.orderStatistics.feesPaid.value + + /* Fees Paid */ + tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value - order.amount + tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10) + /* Percentage Filled */ - let currentPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value - let percentageFilled = order.filled * 100 / (order.filled + order.remaining) - percentageFilled = global.PRECISE(percentageFilled, 10) - tradingEngineOrder.orderStatistics.percentageFilled.value = percentageFilled + tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining) + tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10) - /* 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) + /* Size Filled */ + tradingEngineOrder.orderStatistics.sizeFilled.value = order.filled + tradingEngineOrder.orderStatistics.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.sizeFilled.value, 10) - /* Actual Rate Calculation */ + /* Amount Received */ + tradingEngineOrder.orderStatistics.amountReceived.value = order.cost + tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10) + + /* Actual Rate */ tradingEngineOrder.orderStatistics.actualRate.value = order.price tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10) - /* Fees Paid Calculation */ - tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value - order.amount - tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10) + doTheAccounting(previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid) + } + + function doTheAccounting(previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid) { + + /* Unaccount for the previous Stage Filled Size */ + stageFilledSize.value = stageFilledSize.value - tradingEngineOrder.size.value * previousPercentageFilled / 100 + /* Account for the current Stage Filled Size */ + stageFilledSize.value = stageFilledSize.value + tradingEngineOrder.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100 + stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10) + + /* Balances Update */ + switch (order.side) { + case 'buy': { + + /* Undo the previous accounting */ + tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - previousFeesPaid - previousSizeFilled + /* Account the current filling and fees */ + tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngineOrder.orderStatistics.sizeFilled.value + tradingEngineOrder.orderStatistics.feesPaid.value + + /* Undo the previous accounting */ + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value + previousAmountReceived + /* Account the current filling and fees */ + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngineOrder.orderStatistics.amountReceived.value + + break + } + case 'sell': { + + /* Undo the previous accounting */ + tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + previousFeesPaid + previousSizeFilled + /* Account the current filling and fees */ + tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngineOrder.orderStatistics.sizeFilled.value - tradingEngineOrder.orderStatistics.feesPaid.value + + /* Undo the previous accounting */ + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value - previousAmountReceived + /* Account the current filling and fees */ + tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngineOrder.orderStatistics.amountReceived.value + + break + } + } } function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) { diff --git a/LowFrequencyTrading/TradingSystem.js b/LowFrequencyTrading/TradingSystem.js index 7cb87893c..32f87c3b6 100644 --- a/LowFrequencyTrading/TradingSystem.js +++ b/LowFrequencyTrading/TradingSystem.js @@ -517,13 +517,14 @@ 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 + /* Reset the Exchange Orders data structure to its initial value */ + tradingEngine.exchangeOrders.initialize(tradingEngine.exchangeOrders) + evalConditions(stageNode, 'Initial Definition') evalFormulas(stageNode, 'Initial Definition') @@ -532,9 +533,11 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM calculateTakeProfit() calculateStopLoss() - takePosition() + /* Remember the balance we had before taking the position to later calculate profit or loss */ + tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - /* Check Execution in opening stage mode */ + /* Check Execution in opening stage node */ evalConditions(stageNode, 'Open Execution') evalFormulas(stageNode, 'Open Execution') @@ -573,7 +576,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM /* Closing Status Procedure */ if (tradingEngine.current.strategy.openStageStatus.value === 'Closing') { /* - During the closing status, we do not place new orders, just check + During the closing stage status, we do not place new orders, just check if the ones placed were filled, and cancel the ones not filled. */ let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage @@ -642,25 +645,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM return value } } - - function takePosition() { - tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - - let feePaid = 0 - - if (bot.sessionAndMarketBaseAssetsAreEqual) { - feePaid = tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 - - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value - feePaid - tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.current.position.size.value - } else { - feePaid = tradingEngine.current.position.size.value / tradingEngine.current.position.rate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 - - tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.current.position.size.value / tradingEngine.current.position.rate.value - feePaid - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.current.position.size.value - } - } } function manageStage() { @@ -997,18 +981,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM function exitPositionAndStrategy() { - /* New Balances Calculation */ - let feePaid = 0 - if (bot.sessionAndMarketBaseAssetsAreEqual) { - feePaid = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 - tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value - feePaid - tradingEngine.current.balance.quotedAsset.value = 0 - } else { - feePaid = tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value * bot.SESSION.parameters.feeStructure.config.taker / 100 - tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + tradingEngine.current.balance.baseAsset.value * tradingEngine.current.position.endRate.value - feePaid - tradingEngine.current.balance.baseAsset.value = 0 - } - /* Needed for Episode Statistics */ tradingPositionModule.updateStatistics()