From b308132ad8aff686a7f2cce26ee7f8cbcec370bb Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 4 Jun 2020 13:55:09 +0200 Subject: [PATCH] More Modules --- Trading/Announcements.js | 22 ++ Trading/Snapshots.js | 73 ++++ Trading/TradingEngine.js | 124 ++++++ Trading/TradingExecution.js | 326 ++++++++++++++++ Trading/TradingRecords.js | 163 ++++++++ Trading/TradingSimulation.js | 704 +++-------------------------------- Trading/TradingSystem.js | 15 +- 7 files changed, 772 insertions(+), 655 deletions(-) create mode 100644 Trading/Announcements.js create mode 100644 Trading/Snapshots.js create mode 100644 Trading/TradingExecution.js create mode 100644 Trading/TradingRecords.js diff --git a/Trading/Announcements.js b/Trading/Announcements.js new file mode 100644 index 000000000..e8db3bb7a --- /dev/null +++ b/Trading/Announcements.js @@ -0,0 +1,22 @@ +exports.newAnnouncements = function newAnnouncements(bot, logger) { + + const MODULE_NAME = 'Announcements' + + let thisObject = { + initialize: initialize, + finalize: finalize + } + + let tradingEngine + + return thisObject + + function initialize() { + tradingEngine = bot.TRADING_ENGINE + } + + function finalize() { + tradingEngine = undefined + } + +} \ No newline at end of file diff --git a/Trading/Snapshots.js b/Trading/Snapshots.js new file mode 100644 index 000000000..8a220817e --- /dev/null +++ b/Trading/Snapshots.js @@ -0,0 +1,73 @@ +exports.newSnapshots = function newSnapshots(bot, logger) { + + const MODULE_NAME = 'Snapshots' + + let thisObject = { + manageSnapshots: manageSnapshots, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + + return thisObject + + function initialize() { + tradingEngine = bot.TRADING_ENGINE + } + + function finalize() { + tradingEngine = undefined + } + + function manageSnapshots() { + /* Snapshots Management (before we generate the trade record and delete that info) */ + if (saveAsLastTriggerOnSnapshot === true) { + snapshots.lastTriggerOn = snapshotDataRecord + saveAsLastTriggerOnSnapshot = false + } + + if (saveAsLastTakePositionSnapshot === true) { + snapshots.lastTakePosition = snapshotDataRecord + saveAsLastTakePositionSnapshot = false + } + + if (addToSnapshots === true) { + let closeValues = [ + tradingEngine.episode.positionCounters.positions.value, // Position Number + (new Date(candle.begin)).toISOString(), // Datetime + tradingSystem.strategies[tradingEngine.current.strategy.index.value].name, // Strategy Name + tradingEngine.current.strategy.situationName.value, // Trigger On Situation + tradingEngine.current.position.situationName.value, // Take Position Situation + hitOrFial(), // Result + tradingEngine.last.position.ROI.value, // ROI + exitType() // Exit Type + ] + + function hitOrFial() { + if (tradingEngine.last.position.ROI.value > 0) { return 'HIT' } else { return 'FAIL' } + } + + function exitType() { + switch (tradingEngine.current.position.exitType.value) { + case 1: return 'Stop' + case 2: return 'Take Profit' + } + } + + + if (positionedAtYesterday === false) { + snapshots.triggerOn.push(closeValues.concat(snapshots.lastTriggerOn)) + snapshots.takePosition.push(closeValues.concat(snapshots.lastTakePosition)) + } + snapshots.lastTriggerOn = undefined + snapshots.lastTakePosition = undefined + addToSnapshots = false + } + + let closeHeaders = ['Trade Number', 'Close Datetime', 'Strategy', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type'] + if (snapshots.headers === undefined) { + snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshotLoopHeaders))) + } + } +} \ No newline at end of file diff --git a/Trading/TradingEngine.js b/Trading/TradingEngine.js index d856b0c38..3d05fe4ee 100644 --- a/Trading/TradingEngine.js +++ b/Trading/TradingEngine.js @@ -5,6 +5,10 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { let thisObject = { updatePositionCounters: updatePositionCounters, updateDistanceToEventsCounters: updateDistanceToEventsCounters, + getReadyToTakePosition: getReadyToTakePosition, + takePosition: takePosition, + getReadyToClosePosition: getReadyToClosePosition, + closePosition: closePosition, initialize: initialize, finalize: finalize } @@ -115,5 +119,125 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) { tradingEngine.current.distanceToEvent.closePosition.value++ } } + + function getReadyToTakePosition(candle) { + /* Inicializing this counter */ + tradingEngine.current.distanceToEvent.takePosition.value = 1 + + /* Position size and rate */ + tradingEngine.current.position.size.value = getPositionSize() + tradingEngine.current.position.rate.value = getPositionRate() + + /* We take what was calculated at the formula and apply the slippage. */ + let slippageAmount = tradingEngine.current.position.rate.value * bot.VALUES_TO_USE.slippage.positionRate / 100 + + if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + tradingEngine.current.position.rate.value = tradingEngine.current.position.rate.value - slippageAmount + } else { + tradingEngine.current.position.rate.value = tradingEngine.current.position.rate.value + slippageAmount + } + + /* Update the trade record information. */ + tradingEngine.current.position.begin.value = candle.begin + tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value + } + + function takePosition() { + calculateTakeProfit() // TODO: Check if this is really necesary + calculateStopLoss() // TODO: Check if this is really necesary + + tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value + + tradingEngine.last.position.profitLoss.value = 0 + tradingEngine.last.position.ROI.value = 0 + + let feePaid = 0 + + if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + feePaid = tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value * bot.VALUES_TO_USE.feeStructure.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.VALUES_TO_USE.feeStructure.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 getReadyToClosePosition() { + /* Inicializing this counter */ + tradingEngine.current.distanceToEvent.closePosition.value = 1 + + /* Position size and rate */ + let strategy = tradingSystem.strategies[tradingEngine.current.strategy.index.value] + } + + function closePosition() { + tradingEngine.episode.positionCounters.positions.value++ + + let feePaid = 0 + + if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + + feePaid = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value * bot.VALUES_TO_USE.feeStructure.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.VALUES_TO_USE.feeStructure.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 + } + + if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + tradingEngine.last.position.profitLoss.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.baseAsset.value + tradingEngine.last.position.ROI.value = tradingEngine.last.position.profitLoss.value * 100 / tradingEngine.current.position.size.value + if (isNaN(tradingEngine.last.position.ROI.value)) { tradingEngine.last.position.ROI.value = 0 } + tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - sessionParameters.sessionBaseAsset.config.initialBalance + } else { + tradingEngine.last.position.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.previous.balance.quotedAsset.value + tradingEngine.last.position.ROI.value = tradingEngine.last.position.profitLoss.value * 100 / tradingEngine.current.position.size.value + if (isNaN(tradingEngine.last.position.ROI.value)) { tradingEngine.last.position.ROI.value = 0 } + tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - sessionParameters.sessionQuotedAsset.config.initialBalance + } + + tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.last.position.ROI.value + + if (tradingEngine.last.position.profitLoss.value > 0) { + tradingEngine.episode.episodeCounters.hits.value++ + } else { + tradingEngine.episode.episodeCounters.fails.value++ + } + + if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionBaseAsset.config.initialBalance - 1 + tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.positionCounters.positions.value + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days * 365 + } else { + tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionQuotedAsset.config.initialBalance - 1 + tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.positionCounters.positions.value + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days * 365 + } + + addRecords() + + tradingEngine.current.position.stopLoss.value = 0 + tradingEngine.current.position.takeProfit.value = 0 + + tradingEngine.current.position.rate.value = 0 + tradingEngine.current.position.size.value = 0 + + timerToCloseStage = candle.begin + tradingEngine.current.position.stopLoss.stopLossStage.value = 'No Stage' + tradingEngine.current.position.takeProfit.takeProfitStage.value = 'No Stage' + tradingEngine.current.position.stopLoss.stopLossPhase.value = -1 + tradingEngine.current.position.takeProfit.takeProfitPhase.value = -1 + + } } diff --git a/Trading/TradingExecution.js b/Trading/TradingExecution.js new file mode 100644 index 000000000..2df00a402 --- /dev/null +++ b/Trading/TradingExecution.js @@ -0,0 +1,326 @@ +exports.newTradingExecution = function newTradingExecution(bot, logger) { + + const MODULE_NAME = 'Trading Execution' + + let thisObject = { + takePosition: takePosition, + closePosition: closePosition, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + + return thisObject + + function initialize() { + + tradingEngine = bot.TRADING_ENGINE + + } + + function finalize() { + tradingEngine = undefined + } + + function takePosition() { + /* Check if we need to execute. */ + if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not. */ + /* Check that we are in LIVE MODE */ + if (bot.startMode === 'Live') { + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> Taking a Position in Live Mode.') + 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) + + /* 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 + } + } + break + } + case 'Position Closed': { // Waiting for a confirmation that the position was closed. + if (strategy.openStage !== undefined) { + if (strategy.openStage.openExecution !== undefined) { + putOpeningOrder() + return + } + } + break + } + case 'Taking Position': { // Waiting for a confirmation that the position was taken. + if (FULL_LOG === true) { 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. + if (FULL_LOG === true) { 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 { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not trading live.') } + } + } else { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not the last closed candle.') } + } + + } + + function putOpeningOrder() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function.') } + + /* 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.valueOf()) { + if (FULL_LOG === true) { 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 + } + } + + /* 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.valueOf()) { + if (FULL_LOG === true) { 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 + } + } + + /* 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 <= variable.executionContext.periods) { + if (FULL_LOG === true) { 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() + 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()) / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) + let processDay = new Date(Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) + if (today.valueOf() !== processDay.valueOf()) { + if (FULL_LOG === true) { 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.') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> today = ' + today) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = ' + processDay) } + takePositionAtSimulation() + return + } + } + + let orderPrice + let amountA + let amountB + let orderSide + + if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + 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 + } + + if (FULL_LOG === true) { 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) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function.') } + + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') } + variable.executionContext = { + status: 'In a Position', + periods: tradingEngine.episode.episodeCounters.periods, + amountA: amountA, + amountB: amountB, + orderId: order.id + } + takePositionAtSimulation() + return + } + case global.DEFAULT_FAIL_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') } + if (FULL_LOG === true) { 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: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) } + strategy.openStage.openExecution.error = err.message + afterLoop() + return + } + } + if (FULL_LOG === true) { 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() { + if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not. */ + /* Check that we are in LIVE MODE */ + if (bot.startMode === 'Live') { + /* 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. + if (FULL_LOG === true) { 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': { // + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed.') } + break + } + } + } + } + } + } + + function putClosingOrder() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> putClosingOrder -> Entering function.') } + + /* 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 <= variable.executionContext.periods) { + if (FULL_LOG === true) { 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 (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + 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 + } + + if (FULL_LOG === true) { 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) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function.') } + + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') } + variable.executionContext = { + status: 'Position Closed', + periods: tradingEngine.episode.episodeCounters.periods, + amountA: amountA, + amountB: amountB, + orderId: order.id + } + closePositionAtSimulation() + return + } + case global.DEFAULT_FAIL_RESPONSE.result: { + if (FULL_LOG === true) { 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: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') } + strategy.closeStage.closeExecution.error = err.message + afterLoop() + return + } + } + if (FULL_LOG === true) { 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/Trading/TradingRecords.js b/Trading/TradingRecords.js new file mode 100644 index 000000000..222c0786d --- /dev/null +++ b/Trading/TradingRecords.js @@ -0,0 +1,163 @@ +exports.newTradingRecords = function newTradingRecords(bot, logger) { + + const MODULE_NAME = 'Trading Records' + + let thisObject = { + addSimulationRecord: addSimulationRecord, + addConditionsRecord: addConditionsRecord, + addStrategyRecord: addStrategyRecord, + addPositionRecord: addPositionRecord, + initialize: initialize, + finalize: finalize + } + + let tradingEngine + + return thisObject + + function initialize() { + tradingEngine = bot.TRADING_ENGINE + } + + function finalize() { + tradingEngine = undefined + } + + function addSimulationRecord() { + /* Simulation Record */ + let simulationRecord + + if (tradingEngine.current.balance.baseAsset.value === Infinity) { + tradingEngine.current.balance.baseAsset.value = Number.MAX_SAFE_INTEGER + } + + if (tradingEngine.current.balance.quotedAsset.value === Infinity) { + tradingEngine.current.balance.quotedAsset.value = Number.MAX_SAFE_INTEGER + } + + simulationRecord = [ + candle.begin, + candle.end, + tradingEngine.current.balance.baseAsset.value, + tradingEngine.current.balance.quotedAsset.value, + tradingEngine.episode.episodeStatistics.profitLoss.value, + tradingEngine.last.position.profitLoss.value, + tradingEngine.current.position.stopLoss.value, + tradingEngine.episode.positionCounters.positions.value, + tradingEngine.episode.episodeCounters.hits.value, + tradingEngine.episode.episodeCounters.fails.value, + tradingEngine.episode.episodeStatistics.hitRatio.value, + tradingEngine.episode.episodeStatistics.ROI.value, + tradingEngine.episode.episodeCounters.periods, + tradingEngine.episode.episodeStatistics.days, + tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value, + tradingEngine.current.position.rate.value, + tradingEngine.last.position.ROI.value, + tradingEngine.current.position.takeProfit.value, + tradingEngine.current.position.stopLoss.stopLossPhase.value, + tradingEngine.current.position.takeProfit.takeProfitPhase.value, + tradingEngine.current.position.size.value, + sessionParameters.sessionBaseAsset.config.initialBalance, + sessionParameters.sessionBaseAsset.config.minimumBalance, + sessionParameters.sessionBaseAsset.config.maximumBalance, + sessionParameters.sessionQuotedAsset.config.initialBalance, + sessionParameters.sessionQuotedAsset.config.minimumBalance, + sessionParameters.sessionQuotedAsset.config.maximumBalance, + '"' + sessionParameters.sessionBaseAsset.name + '"', + '"' + sessionParameters.sessionQuotedAsset.name + '"', + '"' + bot.market.marketBaseAsset + '"', + '"' + bot.market.marketQuotedAsset + '"', + tradingEngine.current.position.positionCounters.periods.value, + tradingEngine.current.position.positionStatistics.days.value + ] + + recordsArray.push(simulationRecord) + } + + function addConditionsRecord() { + /* Prepare the information for the Conditions File */ + let conditionsRecord = [ + candle.begin, + candle.end, + tradingEngine.current.strategy.index.value, + tradingEngine.current.position.stopLoss.stopLossPhase.value, + tradingEngine.current.position.takeProfit.takeProfitPhase.value, + conditionsValues, + formulasErrors, + formulasValues + ] + + conditionsArray.push(conditionsRecord) + } + + function addStrategyRecord() { + /* + Lets see if there will be an open strategy ... + Except if we are at the head of the market (remember we skipped the last candle for not being closed.) + */ + if (tradingEngine.current.strategy.begin.value !== 0 && tradingEngine.current.strategy.end.value === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { + tradingEngine.current.strategy.status.value = 'Open' + tradingEngine.current.strategy.end.value = candle.end + } + + /* Prepare the information for the Strategies File */ + if (tradingEngine.current.strategy.begin.value !== 0 && tradingEngine.current.strategy.end.value !== 0) { + let strategyRecord = [ + tradingEngine.current.strategy.begin.value, + tradingEngine.current.strategy.end.value, + tradingEngine.current.strategy.status.value, + tradingEngine.current.strategy.index.value, + tradingEngine.current.strategy.beginRate.value, + tradingEngine.current.strategy.endRate.value, + '"' + tradingEngine.current.strategy.situationName.value + '"', + '"' + tradingEngine.current.strategy.strategyName.value + '"' + ] + + strategiesArray.push(strategyRecord) + + inializeCurrentStrategy() + } + } + + function addPositionRecord() { + /* + Lets see if there will be an open trade ... + Except if we are at the head of the market (remember we skipped the last candle for not being closed.) + */ + if (tradingEngine.current.position.begin.value !== 0 && + tradingEngine.current.position.end.value === 0 && + currentCandleIndex === candles.length - 2 && + lastCandle.end !== lastInstantOfTheDay) { + + /* This means the trade is open */ + tradingEngine.current.position.status.value = 2 + tradingEngine.current.position.end.value = candle.end + tradingEngine.current.position.endRate.value = candle.close + + /* Here we will calculate the ongoing ROI */ + if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { + tradingEngine.current.position.positionStatistics.ROI.value = (tradingEngine.current.position.rate.value - candle.close) / tradingEngine.current.position.rate.value * 100 + } else { + tradingEngine.current.position.positionStatistics.ROI.value = (candle.close - tradingEngine.current.position.rate.value) / tradingEngine.current.position.rate.value * 100 + } + } + + /* Prepare the information for the Positions File */ + if (tradingEngine.current.position.begin.value !== 0 && tradingEngine.current.position.end.value !== 0) { + let positionRecord = [ + tradingEngine.current.position.begin.value, + tradingEngine.current.position.end.value, + tradingEngine.current.position.status.value, + tradingEngine.current.position.positionStatistics.ROI.value, + tradingEngine.current.position.beginRate.value, + tradingEngine.current.position.endRate.value, + tradingEngine.current.position.exitType.value, + '"' + tradingEngine.current.position.situationName.value + '"' + ] + + positionsArray.push(positionRecord) + + initializeCurrentPosition() + } + } +} \ No newline at end of file diff --git a/Trading/TradingSimulation.js b/Trading/TradingSimulation.js index 609191d13..51b721e32 100644 --- a/Trading/TradingSimulation.js +++ b/Trading/TradingSimulation.js @@ -84,6 +84,26 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) tradingEngineModule.initialize() + const TRADING_ENGINE_MODULE = require('./TradingExecution.js') + let tradingExecutionModule = TRADING_ENGINE_MODULE.newTradingExecution(bot, logger) + tradingExecutionModule.initialize() + + const TRADING_RECORDS_MODULE = require('./TradingRecords.js') + let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger) + tradingRecordsModule.initialize() + + const SNAPSHOTS_MODULE = require('./Snapshots.js') + let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger) + snapshotsModule.initialize() + + const ANNOUNCEMENTS_MODULE = require('./Announcements.js') + let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger) + announcementsModule.initialize() + + const TRADING_SYSTEM_MODULE = require('./TradingSystem.js') + let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger) + tradingSystemModule.initialize() + /* Main Array and Maps */ let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '') let candles = chart[propertyName].candles @@ -292,9 +312,9 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } - const TRADING_SYSTEM_MODULE = require('./TradingSystem.js') - let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger) - tradingSystemModule.initialize(chart, candle) + + tradingSystemModule.setCandle(candle) + tradingSystemModule.setChart(chart) tradingSystemModule.evalConditions() tradingSystemModule.evalFormulas() @@ -373,455 +393,26 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT closePositionNow = tradingSystemModule.checkStopLossOrTakeProfitWasHit() } - tradingSystemModule.finalize() + /* Taking a Position */ if ( takePositionNow === true ) { takePositionNow = false - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Entering code block.') } - - /* Inicializing this counter */ - tradingEngine.current.distanceToEvent.takePosition.value = 1 - - /* Position size and rate */ - tradingEngine.current.position.size.value = getPositionSize() - tradingEngine.current.position.rate.value = getPositionRate() - - /* We take what was calculated at the formula and apply the slippage. */ - let slippageAmount = tradingEngine.current.position.rate.value * bot.VALUES_TO_USE.slippage.positionRate / 100 - - if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - tradingEngine.current.position.rate.value = tradingEngine.current.position.rate.value - slippageAmount - } else { - tradingEngine.current.position.rate.value = tradingEngine.current.position.rate.value + slippageAmount - } - - if (bot.startMode === 'Live') { - logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> Taking a Position in Live Mode.') - 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) - } - - /* Update the trade record information. */ - tradingEngine.current.position.begin.value = candle.begin - tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value - - /* Check if we need to execute. */ - if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not. */ - /* Check that we are in LIVE MODE */ - if (bot.startMode === 'Live') { - /* 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 - } - } - break - } - case 'Position Closed': { // Waiting for a confirmation that the position was closed. - if (strategy.openStage !== undefined) { - if (strategy.openStage.openExecution !== undefined) { - putOpeningOrder() - return - } - } - break - } - case 'Taking Position': { // Waiting for a confirmation that the position was taken. - if (FULL_LOG === true) { 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. - if (FULL_LOG === true) { 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 { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not trading live.') } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionNow -> Not the last closed candle.') } - } - - takePositionAtSimulation() - - return - - function putOpeningOrder() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> Entering function.') } - - /* 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.valueOf()) { - if (FULL_LOG === true) { 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 - } - } - - /* 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.valueOf()) { - if (FULL_LOG === true) { 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 - } - } - - /* 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 <= variable.executionContext.periods) { - if (FULL_LOG === true) { 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() - 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()) / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - let processDay = new Date(Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - if (today.valueOf() !== processDay.valueOf()) { - if (FULL_LOG === true) { 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.') } - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> today = ' + today) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> processDay = ' + processDay) } - takePositionAtSimulation() - return - } - } - - let orderPrice - let amountA - let amountB - let orderSide - - if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - 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 - } - - if (FULL_LOG === true) { 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) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Entering function.') } - - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') } - variable.executionContext = { - status: 'In a Position', - periods: tradingEngine.episode.episodeCounters.periods, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - takePositionAtSimulation() - return - } - case global.DEFAULT_FAIL_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_FAIL_RESPONSE ') } - if (FULL_LOG === true) { 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: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') } - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> Message = ' + err.message) } - strategy.openStage.openExecution.error = err.message - afterLoop() - return - } - } - if (FULL_LOG === true) { 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 takePositionAtSimulation() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionAtSimulation -> Entering function.') } - - /* Continue with the simulation */ - calculateTakeProfit() - calculateStopLoss() - - tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value - - tradingEngine.last.position.profitLoss.value = 0 - tradingEngine.last.position.ROI.value = 0 - - let feePaid = 0 - - if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - feePaid = tradingEngine.current.position.size.value * tradingEngine.current.position.rate.value * bot.VALUES_TO_USE.feeStructure.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.VALUES_TO_USE.feeStructure.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 - } - - addRecords() - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionAtSimulation -> Exiting Loop Body after taking position at simulation.') } - controlLoop() - return - } + tradingEngineModule.getReadyToTakePosition(candle) + tradingExecutionModule.takePosition() + tradingEngineModule.takePosition() } - if (closePositionNow === true) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Entering code block.') } - + /* Closing a Position */ + if ( + closePositionNow === true + ) { closePositionNow = false - - /* Inicializing this counter */ - tradingEngine.current.distanceToEvent.closePosition.value = 1 - - /* Position size and rate */ - let strategy = tradingSystem.strategies[tradingEngine.current.strategy.index.value] - - if (currentCandleIndex > candles.length - 10) { /* Only at the last candles makes sense to check if we are in live mode or not. */ - /* Check that we are in LIVE MODE */ - if (bot.startMode === 'Live') { - /* 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. - if (FULL_LOG === true) { 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': { // - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Exiting code block because status is Position Closed.') } - break - } - } - } - } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Not within the last 10 candles.') } - } - - closePositionAtSimulation() - return - - function putClosingOrder() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> putClosingOrder -> Entering function.') } - - /* 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 <= variable.executionContext.periods) { - if (FULL_LOG === true) { 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 (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - 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 - } - - if (FULL_LOG === true) { 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) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> Entering function.') } - - try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putClosingOrder -> onOrderCreated -> DEFAULT_OK_RESPONSE ') } - variable.executionContext = { - status: 'Position Closed', - periods: tradingEngine.episode.episodeCounters.periods, - amountA: amountA, - amountB: amountB, - orderId: order.id - } - closePositionAtSimulation() - return - } - case global.DEFAULT_FAIL_RESPONSE.result: { - if (FULL_LOG === true) { 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: { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderCreated -> DEFAULT_RETRY_RESPONSE ') } - strategy.closeStage.closeExecution.error = err.message - afterLoop() - return - } - } - if (FULL_LOG === true) { 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 - } - } - } - - function closePositionAtSimulation() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> closePositionAtSimulation -> Entering function.') } - - tradingEngine.episode.positionCounters.positions.value++ - - let feePaid = 0 - - if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - - feePaid = tradingEngine.current.balance.quotedAsset.value / tradingEngine.current.position.endRate.value * bot.VALUES_TO_USE.feeStructure.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.VALUES_TO_USE.feeStructure.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 - } - - if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - tradingEngine.last.position.profitLoss.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.baseAsset.value - tradingEngine.last.position.ROI.value = tradingEngine.last.position.profitLoss.value * 100 / tradingEngine.current.position.size.value - if (isNaN(tradingEngine.last.position.ROI.value)) { tradingEngine.last.position.ROI.value = 0 } - tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - sessionParameters.sessionBaseAsset.config.initialBalance - } else { - tradingEngine.last.position.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.previous.balance.quotedAsset.value - tradingEngine.last.position.ROI.value = tradingEngine.last.position.profitLoss.value * 100 / tradingEngine.current.position.size.value - if (isNaN(tradingEngine.last.position.ROI.value)) { tradingEngine.last.position.ROI.value = 0 } - tradingEngine.episode.episodeStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - sessionParameters.sessionQuotedAsset.config.initialBalance - } - - tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.last.position.ROI.value - - if (tradingEngine.last.position.profitLoss.value > 0) { - tradingEngine.episode.episodeCounters.hits.value++ - } else { - tradingEngine.episode.episodeCounters.fails.value++ - } - - if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionBaseAsset.config.initialBalance - 1 - tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.positionCounters.positions.value - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days * 365 - } else { - tradingEngine.episode.episodeStatistics.ROI.value = (sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.episode.episodeStatistics.profitLoss.value) / sessionParameters.sessionQuotedAsset.config.initialBalance - 1 - tradingEngine.episode.episodeStatistics.hitRatio.value = tradingEngine.episode.episodeCounters.hits.value / tradingEngine.episode.positionCounters.positions.value - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value = tradingEngine.episode.episodeStatistics.ROI.value / tradingEngine.episode.episodeStatistics.days * 365 - } - - addRecords() - - tradingEngine.current.position.stopLoss.value = 0 - tradingEngine.current.position.takeProfit.value = 0 - - tradingEngine.current.position.rate.value = 0 - tradingEngine.current.position.size.value = 0 - - timerToCloseStage = candle.begin - tradingEngine.current.position.stopLoss.stopLossStage.value = 'No Stage' - tradingEngine.current.position.takeProfit.takeProfitStage.value = 'No Stage' - tradingEngine.current.position.stopLoss.stopLossPhase.value = -1 - tradingEngine.current.position.takeProfit.takeProfitPhase.value = -1 - - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> closePositionAtSimulation -> Exiting Loop Body after closing position at simulation.') } - controlLoop() - return - } + tradingEngineModule.getReadyToClosePosition(candle) + tradingExecutionModule.closePosition() + tradingEngineModule.closePosition() } /* Closing the Closing Stage */ @@ -843,218 +434,23 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT } } - /* Not a buy or sell condition */ + snapshotsModule.manageSnapshots() + tradingRecordsModule.addSimulationRecord() + tradingRecordsModule.addConditionsRecord() + tradingRecordsModule.addStrategyRecord() + tradingRecordsModule.addPositionRecord() + + /* We will remmember the last candle processed, so that if it is the last one of this run we can use it to continue from there next time. */ + tradingEngine.last.candle.begin = candle.begin + tradingEngine.last.candle.end = candle.end + + /* After everything at the simulation level was done, we will do the annoucements that are pending. */ + makeAnnoucements() + - addRecords() - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Exiting Loop Body after adding a record.') } controlLoop() return - function addRecords() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> addRecords -> Entering function.') } - - manageSnapshots() - addSimulationRecord() - addConditionsRecord() - addStrategyRecord() - addPositionRecord() - - /* We will remmember the last candle processed, so that if it is the last one of this run we can use it to continue from there next time. */ - tradingEngine.last.candle.begin = candle.begin - tradingEngine.last.candle.end = candle.end - - /* After everything at the simulation level was done, we will do the annoucements that are pending. */ - makeAnnoucements() - - function manageSnapshots() { - /* Snapshots Management (before we generate the trade record and delete that info) */ - if (saveAsLastTriggerOnSnapshot === true) { - snapshots.lastTriggerOn = snapshotDataRecord - saveAsLastTriggerOnSnapshot = false - } - - if (saveAsLastTakePositionSnapshot === true) { - snapshots.lastTakePosition = snapshotDataRecord - saveAsLastTakePositionSnapshot = false - } - - if (addToSnapshots === true) { - let closeValues = [ - tradingEngine.episode.positionCounters.positions.value, // Position Number - (new Date(candle.begin)).toISOString(), // Datetime - tradingSystem.strategies[tradingEngine.current.strategy.index.value].name, // Strategy Name - tradingEngine.current.strategy.situationName.value, // Trigger On Situation - tradingEngine.current.position.situationName.value, // Take Position Situation - hitOrFial(), // Result - tradingEngine.last.position.ROI.value, // ROI - exitType() // Exit Type - ] - - function hitOrFial() { - if (tradingEngine.last.position.ROI.value > 0) { return 'HIT' } else { return 'FAIL' } - } - - function exitType() { - switch (tradingEngine.current.position.exitType.value) { - case 1: return 'Stop' - case 2: return 'Take Profit' - } - } - - - if (positionedAtYesterday === false) { - snapshots.triggerOn.push(closeValues.concat(snapshots.lastTriggerOn)) - snapshots.takePosition.push(closeValues.concat(snapshots.lastTakePosition)) - } - snapshots.lastTriggerOn = undefined - snapshots.lastTakePosition = undefined - addToSnapshots = false - } - - let closeHeaders = ['Trade Number', 'Close Datetime', 'Strategy', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type'] - if (snapshots.headers === undefined) { - snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshotLoopHeaders))) - } - } - - function addSimulationRecord() { - /* Simulation Record */ - let simulationRecord - - if (tradingEngine.current.balance.baseAsset.value === Infinity) { - tradingEngine.current.balance.baseAsset.value = Number.MAX_SAFE_INTEGER - } - - if (tradingEngine.current.balance.quotedAsset.value === Infinity) { - tradingEngine.current.balance.quotedAsset.value = Number.MAX_SAFE_INTEGER - } - - simulationRecord = [ - candle.begin, - candle.end, - tradingEngine.current.balance.baseAsset.value, - tradingEngine.current.balance.quotedAsset.value, - tradingEngine.episode.episodeStatistics.profitLoss.value, - tradingEngine.last.position.profitLoss.value, - tradingEngine.current.position.stopLoss.value, - tradingEngine.episode.positionCounters.positions.value, - tradingEngine.episode.episodeCounters.hits.value, - tradingEngine.episode.episodeCounters.fails.value, - tradingEngine.episode.episodeStatistics.hitRatio.value, - tradingEngine.episode.episodeStatistics.ROI.value, - tradingEngine.episode.episodeCounters.periods, - tradingEngine.episode.episodeStatistics.days, - tradingEngine.episode.episodeStatistics.anualizedRateOfReturn.value, - tradingEngine.current.position.rate.value, - tradingEngine.last.position.ROI.value, - tradingEngine.current.position.takeProfit.value, - tradingEngine.current.position.stopLoss.stopLossPhase.value, - tradingEngine.current.position.takeProfit.takeProfitPhase.value, - tradingEngine.current.position.size.value, - sessionParameters.sessionBaseAsset.config.initialBalance, - sessionParameters.sessionBaseAsset.config.minimumBalance, - sessionParameters.sessionBaseAsset.config.maximumBalance, - sessionParameters.sessionQuotedAsset.config.initialBalance, - sessionParameters.sessionQuotedAsset.config.minimumBalance, - sessionParameters.sessionQuotedAsset.config.maximumBalance, - '"' + sessionParameters.sessionBaseAsset.name + '"', - '"' + sessionParameters.sessionQuotedAsset.name + '"', - '"' + bot.market.marketBaseAsset + '"', - '"' + bot.market.marketQuotedAsset + '"', - tradingEngine.current.position.positionCounters.periods.value, - tradingEngine.current.position.positionStatistics.days.value - ] - - recordsArray.push(simulationRecord) - } - - function addConditionsRecord() { - /* Prepare the information for the Conditions File */ - let conditionsRecord = [ - candle.begin, - candle.end, - tradingEngine.current.strategy.index.value, - tradingEngine.current.position.stopLoss.stopLossPhase.value, - tradingEngine.current.position.takeProfit.takeProfitPhase.value, - conditionsValues, - formulasErrors, - formulasValues - ] - - conditionsArray.push(conditionsRecord) - } - - function addStrategyRecord() { - /* - Lets see if there will be an open strategy ... - Except if we are at the head of the market (remember we skipped the last candle for not being closed.) - */ - if (tradingEngine.current.strategy.begin.value !== 0 && tradingEngine.current.strategy.end.value === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { - tradingEngine.current.strategy.status.value = 'Open' - tradingEngine.current.strategy.end.value = candle.end - } - - /* Prepare the information for the Strategies File */ - if (tradingEngine.current.strategy.begin.value !== 0 && tradingEngine.current.strategy.end.value !== 0) { - let strategyRecord = [ - tradingEngine.current.strategy.begin.value, - tradingEngine.current.strategy.end.value, - tradingEngine.current.strategy.status.value, - tradingEngine.current.strategy.index.value, - tradingEngine.current.strategy.beginRate.value, - tradingEngine.current.strategy.endRate.value, - '"' + tradingEngine.current.strategy.situationName.value + '"', - '"' + tradingEngine.current.strategy.strategyName.value + '"' - ] - - strategiesArray.push(strategyRecord) - - inializeCurrentStrategy() - } - } - - function addPositionRecord() { - /* - Lets see if there will be an open trade ... - Except if we are at the head of the market (remember we skipped the last candle for not being closed.) - */ - if (tradingEngine.current.position.begin.value !== 0 && - tradingEngine.current.position.end.value === 0 && - currentCandleIndex === candles.length - 2 && - lastCandle.end !== lastInstantOfTheDay) { - - /* This means the trade is open */ - tradingEngine.current.position.status.value = 2 - tradingEngine.current.position.end.value = candle.end - tradingEngine.current.position.endRate.value = candle.close - - /* Here we will calculate the ongoing ROI */ - if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) { - tradingEngine.current.position.positionStatistics.ROI.value = (tradingEngine.current.position.rate.value - candle.close) / tradingEngine.current.position.rate.value * 100 - } else { - tradingEngine.current.position.positionStatistics.ROI.value = (candle.close - tradingEngine.current.position.rate.value) / tradingEngine.current.position.rate.value * 100 - } - } - - /* Prepare the information for the Positions File */ - if (tradingEngine.current.position.begin.value !== 0 && tradingEngine.current.position.end.value !== 0) { - let positionRecord = [ - tradingEngine.current.position.begin.value, - tradingEngine.current.position.end.value, - tradingEngine.current.position.status.value, - tradingEngine.current.position.positionStatistics.ROI.value, - tradingEngine.current.position.beginRate.value, - tradingEngine.current.position.endRate.value, - tradingEngine.current.position.exitType.value, - '"' + tradingEngine.current.position.situationName.value + '"' - ] - - positionsArray.push(positionRecord) - - initializeCurrentPosition() - } - } - } function checkAnnouncements(node, value) { /* @@ -1231,8 +627,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> afterLoop -> Entering function.') } tradingEngineModule.finalize() + tradingExecutionModule.finalize() + tradingSystemModule.finalize() + tradingRecordsModule.finalize() + snapshotsModule.finalize() + announcementsModule.finalize() - if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> callback -> recordsArray.length = ' + recordsArray.length) } callback(tradingSystem, snapshots.headers, snapshots.triggerOn, snapshots.takePosition) } diff --git a/Trading/TradingSystem.js b/Trading/TradingSystem.js index fa01e77e6..b9b766155 100644 --- a/Trading/TradingSystem.js +++ b/Trading/TradingSystem.js @@ -4,6 +4,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { const FULL_LOG = true let thisObject = { + setCandle: setCandle, + setChart: setChart, evalConditions: evalConditions, evalFormulas: evalFormulas, checkTriggerOn: checkTriggerOn, @@ -22,6 +24,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { let chart let candle + let tradingSystem let tradingEngine @@ -35,9 +38,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { return thisObject - function initialize(pChart, pCandle) { - chart = pChart - candle = pCandle + function initialize() { tradingSystem = bot.TRADING_SYSTEM tradingEngine = bot.TRADING_ENGINE } @@ -57,6 +58,14 @@ exports.newTradingSystem = function newTradingSystem(bot, logger) { formulasErrors = undefined } + function setCandle(pCandle) { + candle = pCandle // We need candle to be a local object accessible from conditions and formulas. + } + + function setChart(pChart) { + chart = pChart // We need chat to be a local object accessible from conditions and formulas. + } + function evalConditions() { evalNode(bot.TRADING_SYSTEM, 'Conditions') }