diff --git a/Assistant.js b/Assistant.js deleted file mode 100644 index 9a98b1cbd..000000000 --- a/Assistant.js +++ /dev/null @@ -1,1127 +0,0 @@ -exports.newAssistant = function newAssistant(BOT, logger, UTILITIES) { - - /* - This module allows trading bots to execute actions on the exchange. - */ - - const MODULE_NAME = "Assistant"; - - let bot = BOT; - - let thisObject = { - dataDependencies: undefined, - initialize: initialize, - createOrder: createOrder, - getPositions: getPositions, - getBalance: getBalance, - getAvailableBalance: getAvailableBalance, - getInitialBalance: getInitialBalance, - getProfits: getProfits, - getCombinedProfits: getCombinedProfits, - getROI: getROI, - getMarketRate: getMarketRate, - getTicker: getTicker, - sendMessage: sendMessage, - rememberThis: rememberThis, - remindMeOf: remindMeOf, - truncDecimals: truncDecimals, - sendEmail: sendEmail, - addExtraData: addExtraData - }; - - let utilities = UTILITIES.newCloudUtilities(logger); - - let openOrders = []; // These are the open positions at the exchange at the account the bot is authorized to use. - let openPositions = []; // These are the open positions the bot knows it made by itself. - - let context; - let exchangeAPI; - - let marketRate; - let ticker; - - return thisObject; - - function initialize(pContext, pExchangeAPI, pDataDependencies, callBackFunction) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - /* Save local values. */ - - context = pContext; - exchangeAPI = pExchangeAPI; - thisObject.dataDependencies = pDataDependencies; - - switch (bot.startMode) { - - case 'Live': { - getMarketRateFromExchange(); - break; - } - - case 'Backtest': { - validateExchangeSyncronicity(); - break; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] initialize -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] initialize -> bot.startMode = " + bot.startMode); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function getMarketRateFromExchange() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getMarketRateFromExchange -> Entering function."); } - - exchangeAPI.getTicker(bot.market, onTicker); - - return; - - function onTicker(err, pTicker) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getMarketRateFromExchange -> onTicker -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getMarketRateFromExchange -> onTicker -> We could not get the Market Price now."); } - - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - - ticker = pTicker; - marketRate = pTicker.last; - - context.newHistoryRecord.marketRate = marketRate; - - validateExchangeSyncronicity(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getMarketRateFromExchange -> onTicker -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getMarketRateFromExchange -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function validateExchangeSyncronicity() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> validateExchangeSyncronicity -> Entering function."); } - - /* Procedure to validate we are in sync with the exchange. */ - - getPositionsAtExchange(onDone); - - function onDone(err) { - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> validateExchangeSyncronicity -> onDone -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> validateExchangeSyncronicity -> onDone -> Execution finished well."); } - calculateProfits(); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> Retry Later. Requesting Execution Retry."); - callBackFunction(err); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - break; - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function calculateProfits() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> calculateProfits -> Entering function."); } - - /* Calculate Profits */ - - if (context.executionContext.initialBalance.baseAsset > 0) { - - context.executionContext.profits.baseAsset = (thisObject.truncDecimals(context.executionContext.balance.baseAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset); - context.executionContext.profits.baseAsset = thisObject.truncDecimals(context.executionContext.profits.baseAsset); - } - - if (context.executionContext.initialBalance.quotedAsset > 0) { - - context.executionContext.profits.quotedAsset = (thisObject.truncDecimals(context.executionContext.balance.quotedAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset); - context.executionContext.profits.quotedAsset = thisObject.truncDecimals(context.executionContext.profits.quotedAsset); - } - - context.newHistoryRecord.profitsBaseAsset = context.executionContext.profits.baseAsset; - context.newHistoryRecord.profitsQuotedAsset = context.executionContext.profits.quotedAsset; - - /* Calculate Combined Profits */ - - if (context.executionContext.initialBalance.baseAsset > 0) { - - let convertedAssetsB = (thisObject.truncDecimals(context.executionContext.balance.quotedAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset)) * marketRate; - convertedAssetsB = thisObject.truncDecimals(convertedAssetsB); - - context.executionContext.combinedProfits.baseAsset = (thisObject.truncDecimals(context.executionContext.balance.baseAsset) + convertedAssetsB - thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset) * 100; - context.executionContext.combinedProfits.baseAsset = thisObject.truncDecimals(context.executionContext.combinedProfits.baseAsset); - } - - if (context.executionContext.initialBalance.quotedAsset > 0) { - - let convertedAssetsA = (thisObject.truncDecimals(context.executionContext.balance.baseAsset) - thisObject.truncDecimals(context.executionContext.initialBalance.baseAsset)) / marketRate; - convertedAssetsA = thisObject.truncDecimals(convertedAssetsA); - - context.executionContext.combinedProfits.quotedAsset = (thisObject.truncDecimals(context.executionContext.balance.quotedAsset) + convertedAssetsA - thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset)) / thisObject.truncDecimals(context.executionContext.initialBalance.quotedAsset) * 100; - context.executionContext.combinedProfits.quotedAsset = thisObject.truncDecimals(context.executionContext.combinedProfits.quotedAsset); - } - - context.newHistoryRecord.combinedProfitsA = context.executionContext.combinedProfits.baseAsset; - context.newHistoryRecord.combinedProfitsB = context.executionContext.combinedProfits.quotedAsset; - - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> calculateProfits -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getPositionsAtExchange(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> Entering function."); } - - /* - - Here we grab all the positions at the exchange for the account we are using for trading. We will not asume all the positions - were made by this bot, but we need them all to later check if all the ones made by the bot are still there, were executed or - manually cancelled by the exchange account owner. - - */ - - switch (bot.startMode) { - - case "Live": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> Live Mode Detected."); } - exchangeAPI.getOpenOrders(bot.market, onResponse); - break; - } - - case "Backtest": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> Backtest Mode Detected."); } - let openOrders = []; // We simulate all positions were executed. - onResponse(global.DEFAULT_OK_RESPONSE, openOrders); - break; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> bot.startMode = " + bot.startMode); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function onResponse(err, pOpenOrders) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> onResponse -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> onResponse -> pOpenOrders = " + JSON.stringify(pOpenOrders)); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { // Everything went well, we have the information requested. - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositionsAtExchange -> onResponse -> Execution finished well."); } - openOrders = pOpenOrders; - ordersExecutionCheck(callBack); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> onResponse -> Retry Later. Requesting Execution Retry."); - callBack(err); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> onResponse -> Operation Failed. Aborting the process."); - callBack(err); - return; - } - break; - } - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] getPositionsAtExchange -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function ordersExecutionCheck(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> Entering function."); } - - /* - - Here we check that all the positions we know are still valid at the exchange. If they are not, we will try to take appropiate - actions. Reasons why the positions might not be there are: - - 1. The user / account owner closed the positions manually. - 2. The exchange for some reason closed the positions. In some exchanges positions have an expiration time. - 3. The orders were executed. - - Situations 1 and 2 are similar unexpected and we will stop the bot execution when we detect them. Number 3 is an - expected behaviour and we will take appropiate action. - - Also, the position might still be there but it might have been partially executed. To detect that we need to compare the - position amounts we have on file to the one we are receiving from the exchange. - - Before we begin, we have to remove all the orders that have been already closed at the last execution of the bot. - - */ - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> JSON.stringify(context.executionContext.positions) = " + JSON.stringify(context.executionContext.positions)); } - - let openPositions = []; - - for (let a = 0; a < context.executionContext.positions.length; a++) { - - if (context.executionContext.positions[a].status === "open") { - - openPositions.push(context.executionContext.positions[a]); - - } - } - - /* - - This removes all orders leaving only the not executed position. Consider that we do want executed positions to be recored on the file so as to - have a record of their execution. But at next execution these records must be deleted before the new Execution Context file is created and - this is what we do here. - - */ - - context.executionContext.positions = openPositions; - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> Removing executed positions."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> JSON.stringify(context.executionContext.positions) = " + JSON.stringify(context.executionContext.positions)); } - - /* Now we can start checking what happened at the exchange. */ - - let i = 0; - - controlLoop(); - - function loopBody() { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> Entering function."); } - - let position = context.executionContext.positions[i]; - let exchangePosition; - - for (let j = 0; j < openOrders.length; j++) { - - if (position.id === openOrders[j].id) { - - exchangePosition = openOrders[j]; - positionFound(); - return; - } - } - - positionNotFound(); - return; - - function positionFound() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> positionFound -> Entering function."); } - - /* - We update the final amountA based on what we got from the exchange. - This is needed since the values could be different from what we calculate. - */ - position.amountA = exchangePosition.amountA; - - /* - We need to know if the order was partially executed. To know that, we compare the amounts on file with the ones - received from the exchange. If they are the same, then the order is intact. Otherwise, to confirm a partial execution, - we will request the associated trades from the exchange. - */ - - if (position.amountB === exchangePosition.amountB) { - - /* Position is still there, untouched. Nothing to do here. */ - - next(); - return; - - } else { - - getPositionTradesAtExchange(position.id, confirmOrderWasPartiallyExecuted); - return; - - } - } - - function positionNotFound() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> positionNotFound -> Entering function."); } - - /* Position not found: we need to know if the order was executed. */ - - getPositionTradesAtExchange(position.id, confirmOrderWasExecuted); - - } - - function getPositionTradesAtExchange(pOrderId, innerCallBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> pOrderId = " + pOrderId); } - - /* - - Given one position, we request all the associated trades to it. - - */ - - switch (bot.startMode) { - - case "Live": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Live Mode Detected."); } - exchangeAPI.getOrder(pOrderId, bot.market, onResponse); - return; - - } - - case "Backtest": { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Backtest Mode Detected."); } - - let trades = []; - - /* We look for the position at the executionContext */ - - for (let i = 0; i < context.executionContext.positions.length; i++) { - - let thisOrder = context.executionContext.positions[i]; - - if (thisOrder.id === pOrderId) { - - let feeRate = 0.002; // Default backtesting fee simulation - - let trade = { - id: Math.trunc(Math.random(1) * 1000000), - type: thisOrder.type, - rate: thisOrder.rate.toString(), - amountA: thisObject.truncDecimals(thisOrder.amountA).toString(), - amountB: thisObject.truncDecimals(thisOrder.amountB).toString(), - fee: thisObject.truncDecimals(feeRate).toString(), - date: (new Date()).valueOf() - } - - trades.push(trade); - - let order = { - trades: trades - } - onResponse(global.DEFAULT_OK_RESPONSE, order); - - return; - } - } - - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Position not found at Executioin Context."); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> bot.startMode = " + bot.startMode); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function onResponse(err, order) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Entering function."); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { // Everything went well, we have the information requested. - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Execution finished well."); } - innerCallBack(order); - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Retry Later. Requesting Execution Retry."); - callBack(global.DEFAULT_RETRY_RESPONSE); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> Operation Failed. Aborting the process."); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - break; - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> onResponse -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> getPositionTradesAtExchange -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function confirmOrderWasExecuted(order) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> trades = " + JSON.stringify(trades)); } - - let trades = order.trades - /* - - To confirm everything is ok, we will add all the amounts on trades asociated to the order and - they must be equal to the one on file. Otherwise something very strange could have happened, - in which case we will halt the bot execution. - - */ - - let sumBaseAsset = 0; - let sumQuotedAsset = 0; - let sumRate = 0; - - for (let k = 0; k < trades.length; k++) { - let trade = trades[k]; - sumBaseAsset = sumBaseAsset + thisObject.truncDecimals(trade.amountA); - sumQuotedAsset = sumQuotedAsset + thisObject.truncDecimals(trade.amountB); - sumRate = sumRate + thisObject.truncDecimals(trade.rate); - } - - sumBaseAsset = thisObject.truncDecimals(sumBaseAsset); - sumQuotedAsset = thisObject.truncDecimals(sumQuotedAsset); - - if (position.amountB !== sumQuotedAsset) { - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> position.amountB = " + position.amountB); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> sumQuotedAsset = " + sumQuotedAsset); - - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> Cannot be confirmed that the order was executed. It must be manually cancelled by the user or cancelled by the exchange itself."); - logger.write(MODULE_NAME, "[HINT] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> If the process was abruptally cancelled and then restarted, it is possible that now is not sincronized with the exchange."); - logger.write(MODULE_NAME, "[HINT] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> In any case, to continue, you must manually delete the orders at the exchange."); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - - /* - - Confirmed that order was executed. Next thing to do is to remember the trades and change its status. - - */ - - position.status = "executed"; - - if (position.type === "sell") { - context.newHistoryRecord.sellExecRate = sumRate / trades.length; - } else { - context.newHistoryRecord.buyExecRate = sumRate / trades.length; - } - - applyTradesToContext(trades); - - let newTransaction = { - type: position.type + " executed", - position: position - }; - - context.executionContext.transactions.push(newTransaction); - - /* All done. */ - - next(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function confirmOrderWasPartiallyExecuted(trades) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> Entering function."); } - - let trades = order.trades - - /* - - To confirm everything is ok, we will add all the amounts on trades plus the remaining amounts - at the order at the exchange and they must be equal to the one on file. Otherwise an inconsistent - state has been detected, in which case we will halt the bot execution. - - */ - - let sumBaseAsset = 0; - let sumQuotedAsset = 0; - - for (let k = 0; k < trades.length; k++) { - let trade = trades[k]; - sumBaseAsset = sumBaseAsset + thisObject.truncDecimals(trade.amountA); - sumQuotedAsset = sumQuotedAsset + thisObject.truncDecimals(trade.amountB); - } - - /* To this we add the current position amounts. */ - - sumBaseAsset = sumBaseAsset + thisObject.truncDecimals(exchangePosition.amountA); - sumQuotedAsset = sumQuotedAsset + thisObject.truncDecimals(exchangePosition.amountB); - - sumBaseAsset = thisObject.truncDecimals(sumBaseAsset); - sumQuotedAsset = thisObject.truncDecimals(sumQuotedAsset); - - /* - * Next let's get the max decimal positions for the pair being trade returned by the exchange and validate that at least - * one decimal position less than the max matches. The reason for this is because there could be some - * rounding on the exchange when there are partial executed orders that will interfiere with the exact validation. - * - * IE: For Poloniex USDT/BTC the max returned decimal positions is 8, it means we will validate that the diference is less than 0.0000001 - */ - - let exchangeParam = exchangeAPI.getMaxDecimalPositions(); - let minValue = '0.' + (1).toPrecision(exchangeParam - 1).split('.').reverse().join(''); - let exchangePrecision = parseFloat(parseFloat(minValue).toFixed(exchangeParam)); - - if (Math.abs(position.amountA - sumBaseAsset) > exchangePrecision || Math.abs(position.amountB - sumQuotedAsset) > exchangePrecision) { - - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountA = " + position.amountA); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumBaseAsset = " + sumBaseAsset); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> position.amountB = " + position.amountB); - logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> sumQuotedAsset = " + sumQuotedAsset); - - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> Cannot confirm that a partial execution was done well."); - - /* There are diferences on the responses between the getPosition and getTrades that causes some issues, let's retry. */ - callBack(global.DEFAULT_RETRY_RESPONSE); - return; - } - - /* - - Confirmed that order was partially executed. Next thing to do is to remember the trades and the new position. - - */ - - position.amountA = exchangePosition.amountA; - position.amountB = exchangePosition.amountB; - position.date = (new Date(exchangePosition.date)).valueOf(); - - applyTradesToContext(trades); - - let newTransaction = { - type: position.type + " partially executed", - position: position - }; - - context.executionContext.transactions.push(newTransaction); - - /* All done. */ - - next(); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasPartiallyExecuted -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function applyTradesToContext(trades) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> loopBody -> applyTradesToContext -> Entering function."); } - - /* Here we apply the trades that already happened at the exchange to the balance and available balance of the bot. We also calculate its profits. */ - - for (k = 0; k < trades.length; k++) { - - let trade = trades[k]; - - position.trades.push(trade); - - context.newHistoryRecord.newTrades++; - - /* Calculate Balances */ - - let baseAsset = 0; - let quotedAsset = 0; - - if (trade.type === 'buy') { - - /* - - The fee received at each trade is the factor by which if we multiply asset B, we get the amount of Asset B extracted as fees by the exchange. - This happens with each individual trade. Remeber that one order can potentially be executed with 1 to many trades. - - */ - - let feeAmount = thisObject.truncDecimals(Number(trade.fee) * Number(trade.amountB)); - - baseAsset = Number(trade.amountA); - quotedAsset = Number(trade.amountB) - feeAmount; - - context.executionContext.balance.baseAsset = thisObject.truncDecimals(context.executionContext.balance.baseAsset - baseAsset); - context.executionContext.balance.quotedAsset = thisObject.truncDecimals(context.executionContext.balance.quotedAsset + quotedAsset); - - context.executionContext.availableBalance.quotedAsset = thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset + quotedAsset); - - /* Not the available balance for asset A is not affected since it was already reduced when the order was placed. */ - - } - - if (trade.type === 'sell') { - - /* - - The fee received at each trade is the factor by which if we multiply asset A, we get the amount of Asset A extracted as fees by the exchange. - This happens with each individual trade. Remeber that one order can potentially be executed with 1 to many trades. - - */ - - let feeAmount = thisObject.truncDecimals(Number(trade.fee) * Number(trade.amountA)); - - baseAsset = Number(trade.amountA) - feeAmount; - quotedAsset = Number(trade.amountB); - - context.executionContext.balance.baseAsset = thisObject.truncDecimals(context.executionContext.balance.baseAsset + baseAsset); - context.executionContext.balance.quotedAsset = thisObject.truncDecimals(context.executionContext.balance.quotedAsset - quotedAsset); - - context.executionContext.availableBalance.baseAsset = thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset + baseAsset); - - /* Not the available balance for asset B is not affected since it was already reduced when the order was placed. */ - - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> applyTradesToContext -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> loopBody -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function next() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> next -> Entering function."); } - - i++; - controlLoop(); - - } - - function controlLoop() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> controlLoop -> Entering function."); } - - if (i < context.executionContext.positions.length) { - - loopBody(); - - } else { - - final(); - - } - } - - function final() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] ordersExecutionCheck -> final -> Entering function."); } - - callBack(global.DEFAULT_OK_RESPONSE); - - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] ordersExecutionCheck -> err = " + err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function createOrder(pType, pRate, pAmountA, pAmountB, callBackFunction) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> Entering function."); } - - /* Removing extra decimals. */ - - pRate = thisObject.truncDecimals(pRate); - pAmountA = thisObject.truncDecimals(pAmountA); - pAmountB = thisObject.truncDecimals(pAmountB); - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pType = " + pType); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pRate = " + pRate); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pAmountA = " + pAmountA); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> pAmountB = " + pAmountB); } - - /* Validations that the limits are not surpassed. */ - - if (pType === 'buy') { - - if (pAmountA > thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset)) { - - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountA is grater than the Available Balance."); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountA = " + pAmountA); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> Available Balance = " + thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset)); - - let err = { - result: global.DEFAULT_FAIL_RESPONSE.result, - message: 'Not Enough Available Balance to Buy.' - }; - - callBackFunction(err); - return; - } - } - - if (pType === 'sell') { - - if (pAmountB > thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset)) { - - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountB is grater than the Available Balance."); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> pAmountB = " + pAmountB); - logger.write(MODULE_NAME, "[ERROR] createOrder -> Input Validations -> Available Balance = " + thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset)); - - let err = { - result: global.DEFAULT_FAIL_RESPONSE.result, - message: 'Not Enough Available Balance to Sell.' - }; - - callBackFunction(err); - return; - } - } - - /* All validations passed, we proceed. */ - - switch (bot.startMode) { - - case "Live": { - - exchangeAPI.createOrder(bot.market, pType, pRate, pAmountA, pAmountB, onResponse); - return; - } - - case "Backtest": { - - if (pRate !== marketRate) { - - logger.write(MODULE_NAME, "[WARNING] createOrder -> Input Validations -> createOrder Rate can is different to marketRate while in Backtesting Mode. "); - //onResponse(global.DEFAULT_FAIL_RESPONSE, positionId); - } - - let positionId = Math.trunc(Math.random(1) * 1000000); - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> Simulating Exchange Response -> orderId = " + positionId); } - onResponse(global.DEFAULT_OK_RESPONSE, positionId); - return; - } - - default: { - logger.write(MODULE_NAME, "[ERROR] createOrder -> Unexpected bot.startMode."); - logger.write(MODULE_NAME, "[ERROR] createOrder -> bot.startMode = " + bot.startMode); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return; - } - } - - function onResponse(err, order) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> onResponse -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> onResponse -> order.id = " + order.id); } - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { // Everything went well, we have the information requested. - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createOrder -> onResponse -> Execution finished well."); } - - let position = { - id: order.id, - type: pType, - rate: pRate, - amountA: pAmountA, - amountB: pAmountB, - date: (bot.processDatetime.valueOf()), - status: "open", - trades: [] - }; - - context.executionContext.positions.push(position); - - let newTransaction = { - type: "newPosition", - position: position - }; - - context.executionContext.transactions.push(newTransaction); - - context.newHistoryRecord.newPositions++; - - recalculateRateAverages(); - - /* Recalculate Available Balances */ - - if (position.type === 'buy') { - - context.executionContext.availableBalance.baseAsset = thisObject.truncDecimals(context.executionContext.availableBalance.baseAsset - pAmountA); - context.newHistoryRecord.lastBuyRate = pRate; - } - - if (position.type === 'sell') { - - context.executionContext.availableBalance.quotedAsset = thisObject.truncDecimals(context.executionContext.availableBalance.quotedAsset - pAmountB); - context.newHistoryRecord.lastSellRate = pRate; - } - - callBackFunction(global.DEFAULT_OK_RESPONSE, position); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] createOrder -> onResponse -> Retry Later. Requesting Execution Retry."); - callBackFunction(global.DEFAULT_RETRY_RESPONSE); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] createOrder -> onResponse -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - break; - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] createOrder -> onResponse -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] createOrder -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getPublicTradeHistory(baseAsset, quotedAsset, startTime, endTime, callback) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTradeHistory -> Entering function."); } - - exchangeAPI.getPublicTradeHistory(baseAsset, quotedAsset, startTime, endTime, callback); - } - - function recalculateRateAverages() { - - /* This function calculates the weighted rates of buy and sell positions. This is a way to summarize with only one rate, where the positions are. */ - - let sumBuyWeightedRates = 0; - let sumSellWeightedRates = 0; - - let sumBuyWeights = 0; - let sumSellWeights = 0; - - for (let i = 0; i < context.executionContext.positions.length; i++) { - - let position = context.executionContext.positions[i]; - - if (position.type === 'buy') { - - sumBuyWeightedRates = sumBuyWeightedRates + position.rate * position.amountB - sumBuyWeights = sumBuyWeights + position.amountB; - - } - - if (position.type === 'sell') { - - sumSellWeightedRates = sumSellWeightedRates + position.rate * position.amountB - sumSellWeights = sumSellWeights + position.amountB; - - } - - } - - context.newHistoryRecord.buyAvgRate = (sumBuyWeightedRates / sumBuyWeights || 0); - context.newHistoryRecord.sellAvgRate = (sumSellWeightedRates / sumSellWeights || 0); - } - - function getPositions() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositions -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getPositions -> JSON.stringify(context.executionContext.positions) = " + JSON.stringify(context.executionContext.positions)); } - - return JSON.parse(JSON.stringify(context.executionContext.positions)); - } - - function getBalance() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getBalance -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getBalance -> JSON.stringify(context.executionContext.balance) = " + JSON.stringify(context.executionContext.balance)); } - - return JSON.parse(JSON.stringify(context.executionContext.balance)); - } - - function getAvailableBalance() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getAvailableBalance -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getAvailableBalance -> JSON.stringify(context.executionContext.availableBalance) = " + JSON.stringify(context.executionContext.availableBalance)); } - - return JSON.parse(JSON.stringify(context.executionContext.availableBalance)); - } - - function getInitialBalance() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getInitialBalance -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getInitialBalance -> JSON.stringify(context.executionContext.initialBalance) = " + JSON.stringify(context.executionContext.initialBalance)); } - - return JSON.parse(JSON.stringify(context.executionContext.initialBalance)); - } - - function getProfits() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getProfits -> Entering function."); } - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getProfits -> JSON.stringify(context.executionContext.profits) = " + JSON.stringify(context.executionContext.profits)); } - - return JSON.parse(JSON.stringify(context.executionContext.profits)); - } - - function getCombinedProfits() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getCombinedProfits -> Entering function."); } - - let combinedProfits = { - baseAsset: context.newHistoryRecord.combinedProfitsA, - quotedAsset: context.newHistoryRecord.combinedProfitsB - } - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getCombinedProfits -> JSON.stringify(combinedProfits) = " + JSON.stringify(combinedProfits)); } - - return JSON.parse(JSON.stringify(combinedProfits)); - } - - function getROI() { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getROI -> Entering function."); } - - let ROI = { - baseAsset: (context.executionContext.balance.baseAsset - context.executionContext.initialBalance.baseAsset) / context.executionContext.initialBalance.baseAsset * 100, - quotedAsset: (context.executionContext.balance.quotedAsset - context.executionContext.initialBalance.quotedAsset) / context.executionContext.initialBalance.quotedAsset * 100 - } - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getROI -> JSON.stringify(ROI) = " + JSON.stringify(ROI)); } - - return JSON.parse(JSON.stringify(ROI)); - } - - function getMarketRate() { - return marketRate; - } - - function getTicker() { - return ticker; - } - - function sendMessage(pRelevance, pTitle, pBody) { - let runIndex = context.statusReport.runs.length - 1; - - context.newHistoryRecord.messageRelevance = pRelevance; - context.newHistoryRecord.messageTitle = bot.startMode + "." + runIndex + ": " + pTitle; - context.newHistoryRecord.messageBody = pBody; - } - - function rememberThis(pKey, pValue) { - if (context.executionContext.remember === undefined) { - context.executionContext.remember = {}; - } - context.executionContext.remember[pKey] = pValue; - } - - function remindMeOf(pKey) { - if (context.executionContext.remember === undefined) { - context.executionContext.remember = {}; - } - return context.executionContext.remember[pKey]; - } - - function sendEmail(pTitle, pBody, pTo) { - logger.write(MODULE_NAME, "[WARN] sendEmail -> Send emails is currently disabled."); - return; - } - - function addExtraData(pExtraDataArray) { - context.extraData.push(pExtraDataArray); - } - - function truncDecimals(pFloatValue, pDecimals) { - if (!pDecimals) pDecimals = 8; // Default value - return parseFloat(parseFloat(pFloatValue).toFixed(pDecimals)); - } -}; diff --git a/Commons.js b/Commons.js index 70200a529..bea993f51 100644 --- a/Commons.js +++ b/Commons.js @@ -3,6 +3,7 @@ const FULL_LOG = true; const LOG_FILE_CONTENT = false; const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; + const ONE_MIN_IN_MILISECONDS = 60 * 1000; const MODULE_NAME = "Commons"; @@ -11,7 +12,7 @@ validateOutputDatasets: validateOutputDatasets, inflateDatafiles: inflateDatafiles, dataBuildingProcedure: dataBuildingProcedure, - calculationsProcedure: calculationsProcedure, + calculationsProcedure: calculationsProcedure, generateFileContent: generateFileContent, writeFile: writeFile }; @@ -24,15 +25,15 @@ function validateDataDependencies(dataDependencies, callBackFunction) { for (let i = 0; i < dataDependencies.length; i++) { - let dataDependencyNode = dataDependencies[i] + let dataDependencyNode = dataDependencies[i] /* Basic validations to see if we have everything we need. */ - if (dataDependencyNode.referenceParent.parentNode.code.singularVariableName === undefined) { + if (dataDependencyNode.referenceParent.parentNode.config.singularVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (dataDependencyNode.referenceParent.parentNode.code.pluralVariableName === undefined) { + if (dataDependencyNode.referenceParent.parentNode.config.pluralVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -55,12 +56,17 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDatasetNode.referenceParent.parentNode.code.singularVariableName === undefined) { + if (outputDatasetNode.referenceParent.parentNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Dataset not a child of a Product Definition. Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (outputDatasetNode.referenceParent.parentNode.config.singularVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } - if (outputDatasetNode.referenceParent.parentNode.code.pluralVariableName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.config.pluralVariableName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -73,7 +79,7 @@ if (outputDatasetNode.referenceParent.parentNode.dataBuilding === undefined) { logger.write(MODULE_NAME, "[WARN] start -> Product Definition " + outputDatasetNode.referenceParent.parentNode.name + " without a Data Building Procedure. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); } - if (outputDatasetNode.referenceParent.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Dataset witn no codeName defined. Product Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -85,7 +91,7 @@ return } - if (outputDatasetNode.referenceParent.parentNode.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Product Definition witn no codeName defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -97,7 +103,7 @@ return } - if (outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -109,7 +115,7 @@ return } - if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { + if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -126,19 +132,19 @@ */ for (let i = 0; i < dataDependencies.length; i++) { - let dataFile + let dataFile let jsonData // Datafile converted into Json objects let inputData // Includes calculated properties let singularVariableName // name of the variable for this product let recordDefinition - let dataDependencyNode = dataDependencies[i] + let dataDependencyNode = dataDependencies[i] dataFile = dataFiles.get(dataDependencyNode.id) if (dataFile === undefined) { continue } // When a datafile is not found it might be because we are processing market or daily and at the dependency array there are both types mixed up. recordDefinition = dataDependencyNode.referenceParent.parentNode.record - singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName + singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName /* Transform the raw data into JSON objects */ jsonData = jsonifyDataFile(dataFile, recordDefinition) /* Add the calculated properties */ @@ -147,7 +153,7 @@ } else { inputData = jsonData } - products[dataDependencyNode.referenceParent.parentNode.code.pluralVariableName] = inputData + products[dataDependencyNode.referenceParent.parentNode.config.pluralVariableName] = inputData /* The main dependency is defined as the first dependency processed. */ if (mainDependency.records === undefined) { @@ -171,8 +177,8 @@ let record = {} for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { - record[property.code.codeName] = dataFile[i][j] + if (property.config.isCalculated !== true) { + record[property.config.codeName] = dataFile[i][j] } } @@ -232,13 +238,13 @@ /* For each calculated property we apply its formula*/ for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated === true) { + if (property.config.isCalculated === true) { if (property.formula !== undefined) { if (property.formula.code !== undefined) { try { let newValue = eval(property.formula.code) let currentRecord = product[variableName] - currentRecord[property.code.codeName] = newValue + currentRecord[property.config.codeName] = newValue } catch (err) { logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) @@ -271,7 +277,7 @@ interExecutionMemory, processingDailyFiles, currentDay - ) { + ) { /* This function has as an input the products object, with all the information @@ -280,11 +286,12 @@ a new set of information. */ - let lastInstantOfTheDay + let lastInstantOfTheDay let yesterday = {} let system = { // These are the available system variables to be used in User Code and Formulas timeFrame: timeFrame, - ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS + ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS, + ONE_MIN_IN_MILISECONDS: ONE_MIN_IN_MILISECONDS } let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. let results = [] @@ -306,7 +313,7 @@ let dataDependencyMap = new Map() for (let j = 0; j < dataDependecyArray.length; j++) { let record = dataDependecyArray[j] - let key = record.begin.toString() + '-' + record.end.toString() + let key = record.begin.toString() + '-' + record.end.toString() dataDependencyMap.set(key, record) } dataDependencies[dataDependencyName] = dataDependencyMap @@ -314,7 +321,7 @@ } function getElement(dependencyName, currentRecordPrimaryDataDependency) { - let key = currentRecordPrimaryDataDependency.begin.toString() + '-' + currentRecordPrimaryDataDependency.end.toString() + let key = currentRecordPrimaryDataDependency.begin.toString() + '-' + currentRecordPrimaryDataDependency.end.toString() return dataDependencies[dependencyName].get(key) } @@ -342,7 +349,7 @@ } else { /* We override the initialization, since the valid stuff is already at the Inter Execution Memory */ - variable = JSON.parse(JSON.stringify(interExecutionMemory[productName].variable)) + variable = JSON.parse(JSON.stringify(interExecutionMemory[productName].variable)) } } @@ -380,13 +387,13 @@ /* For each non-calculated property we apply its formula*/ for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { + if (property.config.isCalculated !== true) { if (property.formula !== undefined) { if (property.formula.code !== undefined) { try { let newValue = eval(property.formula.code) let currentRecord = product[variableName] - currentRecord[property.code.codeName] = newValue + currentRecord[property.config.codeName] = newValue } catch (err) { logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) @@ -467,13 +474,13 @@ let propertySeparator = "" for (let j = 0; j < recordDefinition.properties.length; j++) { let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { + if (property.config.isCalculated !== true) { fileContent = fileContent + propertySeparator - if (property.code.isString === true) { + if (property.config.isString === true) { fileContent = fileContent + '"' } - fileContent = fileContent + record[property.code.codeName] - if (property.code.isString === true) { + fileContent = fileContent + record[property.config.codeName] + if (property.config.isString === true) { fileContent = fileContent + '"' } if (propertySeparator === "") { propertySeparator = ","; } diff --git a/Context.js b/Context.js deleted file mode 100644 index bc347977b..000000000 --- a/Context.js +++ /dev/null @@ -1,551 +0,0 @@ -exports.newContext = function newContext(BOT, logger, UTILITIES) { - - let bot = BOT; - - const MODULE_NAME = "Context"; - - /* - - This module deals with keeping the context between different executions of the bot. Conceptually this context is divided in 3 main parts: - - I. The non-business information which helps the mechanisms of giving continuety to the bots execution over time. This information goes in a file - called the Status Report, and there is a module, child of this one called Status Report that deals with loading and saving the contents of that file. - - II. The business information around the bot execution, including its balance, positions at exchanges, trades executed and many other things. These - are platform data mantained on behalf of the executing bot. All these is kept in one file per execution called the Execution Context. The collection - of all these files can later reconstruct the vital business information the bot had at each moment in time. - - II. Execution of any bot cannot be guaranteed that will be on fixed interval of times. We need an index of all executions that points to the detailed files - with all the business informatino the bot had each time it executed. We call this index the Execution History and there is one file to store it. - - */ - - let thisObject = { - statusReport: undefined, // Here is the information that defines which was the last sucessfull execution and some other details. - executionHistory: [], // This is the record of bot execution. - executionContext: undefined, // Here is the business information of the last execution of this bot process. - newHistoryRecord : { - date: undefined, - buyAvgRate: 0, - sellAvgRate: 0, - lastSellRate: 0, - sellExecRate: 0, - lastBuyRate: 0, - buyExecRate: 0, - marketRate: 0, - newPositions: 0, - newTrades: 0, - movedPositions: 0, - profitsBaseAsset: 0, - profitsQuotedAsset: 0, - combinedProfitsA: 0, - combinedProfitsB: 0, - messageRelevance: 0, - messageTitle: "", - messageBody: "" - }, - initialize: initialize, - saveThemAll: saveThemAll, - extraData: [] - }; - - /* - - During the process we will create a new History Record. This will go to the Context History file which essentially mantains an - index of all the bots executions. This file will later be plotted by the bot s plotter on the timeline, allowing end users to - know where there is information related to the actions taken by the bot. - - */ - - let statusReportModule; - - /* Utilities needed. */ - - let utilities = UTILITIES.newCloudUtilities(logger); - - /* Storage account to be used here. */ - - const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage = FILE_STORAGE.newFileStorage(logger); - - let statusDependencies; - - let runIndex; // This is the index for this run and depends on the startMode. - let sessionPath = '' - - return thisObject; - - function initialize(pStatusDependencies, callBackFunction) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - statusDependencies = pStatusDependencies; - - if (bot.SESSION !== undefined) { - sessionPath = bot.SESSION.folderName + "/" - } - - /* - Here we get the positions the bot did and that are recorded at the bot storage account. We will use them through out the rest - of the process. - */ - thisObject.newHistoryRecord.date = bot.processDatetime; - - getStatusReport(onDone); - - function onDone(err) { - try { - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> onDone -> Execution finished well."); } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> Retry Later. Requesting Execution Retry."); - callBackFunction(err); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - default: { - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> Operation Failed with custom response."); - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> err = " + JSON.stringify(err)); - callBackFunction(err); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> onDone -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getStatusReport(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getStatusReport -> Entering function."); } - - let key = bot.dataMine + "-" + bot.codeName + "-" + "Context" + "-" + bot.dataSetVersion; - - statusReportModule = statusDependencies.statusReports.get(key); - thisObject.statusReport = statusReportModule.file; - - /* The context is created in 2 situations: 1) when at the bot config is set not to continue using the same context after a new execution. 2) when the status report does not exist. */ - - if (bot.hasTheBotJustStarted === true || thisObject.statusReport.runs === undefined) { - - createConext(callBack); - - } else { - - runIndex = thisObject.statusReport.runs.length - 1; - - getExecutionHistory(callBack); - - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getStatusReport -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getExecutionHistory(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionHistory -> Entering function."); } - - let fileName = "Execution.History." + bot.startMode + "." + runIndex + ".json"; - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + fileName; - - fileStorage.getTextFile(filePath, onFileReceived); - - function onFileReceived(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionHistory -> onFileReceived -> err = "+ JSON.stringify(err)); - callBack(err); - return; - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { - logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionHistory -> onFileReceived -> Content received = " + text); - } - - try { - - thisObject.executionHistory = JSON.parse(text); - - /* Move some values to the new record, in case there are no transactions that re-calculate them. */ - - thisObject.newHistoryRecord.buyAvgRate = thisObject.executionHistory[thisObject.executionHistory.length-1][1]; - thisObject.newHistoryRecord.sellAvgRate = thisObject.executionHistory[thisObject.executionHistory.length - 1][2]; - - getExecutionContext(callBack); - - } catch (err) { - - /* - - It might happen that the file content is corrupt or it does not exist. The bot can not run without an Execution Hitory, - since it is risky to ignore its own history unless it is its first execution in this mode ever. - - */ - - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionHistory -> onFileReceived -> Bot cannot execute without the Execution History. -> Err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionHistory -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function getExecutionContext(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionContext -> Entering function."); } - - let date; - - date = new Date(thisObject.statusReport.runs[runIndex].lastExecution); - thisObject.statusReport.runs[runIndex].lastExecution = bot.processDatetime; - thisObject.statusReport.runs[runIndex].endDatetime = bot.processDatetime; - - let fileName = "/Execution.Context." + bot.startMode + "." + runIndex + ".json"; - let dateForPath = date.getUTCFullYear() + '/' + utilities.pad(date.getUTCMonth() + 1, 2) + '/' + utilities.pad(date.getUTCDate(), 2) + '/' + utilities.pad(date.getUTCHours(), 2) + '/' + utilities.pad(date.getUTCMinutes(), 2); - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + dateForPath + fileName; - - fileStorage.getTextFile(filePath, onFileReceived); - - function onFileReceived(err, text) { - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> onFileReceived -> err = "+ err.stack); - callBack(err); - return; - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { - logger.write(MODULE_NAME, "[INFO] initialize -> getExecutionContext -> onFileReceived -> Content received = " + text); - } - - try { - - thisObject.executionContext = JSON.parse(text); - thisObject.executionContext.transactions = []; // We record here the transactions that happened duting this execution. - - /* Update the new History Record, in case there are no trades during this execution that updates it. */ - - thisObject.newHistoryRecord.profitsBaseAsset = thisObject.executionContext.profits.baseAsset; - thisObject.newHistoryRecord.profitsQuotedAsset = thisObject.executionContext.profits.quotedAsset; - - thisObject.newHistoryRecord.combinedProfitsA = thisObject.executionContext.combinedProfits.baseAsset; - thisObject.newHistoryRecord.combinedProfitsB = thisObject.executionContext.combinedProfits.quotedAsset; - - callBack(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - - /* - - It might happen that the file content is corrupt or it does not exist. The bot can not run without a Status Report, - since it is risky to ignore its own execution history. Except when the bot is running for the fist time in this mode, - an execution context file with the right format is needed. - - */ - - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> onFileReceived -> Response from storage. -> Text = " + text); - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> onFileReceived -> Bot cannot execute without the Execution Context. -> Err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> getExecutionContext -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - function createConext(callBack) { - - try { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> createConext -> Entering function."); } - /* - - When the bot is executed for the very first time, there are a few files that do not exist and need to be created, and that - content is what we are going to set up now. - - */ - - if (thisObject.statusReport.runs === undefined) { // This means that the Status Report does not exist. - - thisObject.statusReport.runs = []; - } - - let runContent = { - beginDatetime: bot.processDatetime, - endDatetime: bot.processDatetime, - lastExecution: bot.processDatetime - }; - - thisObject.statusReport.runs.push(runContent); - runIndex = thisObject.statusReport.runs.length - 1; - - thisObject.executionHistory = []; - - if (bot.VALUES_TO_USE.initialBalanceA === undefined || bot.VALUES_TO_USE.initialBalanceB === undefined) { throw new Error("Global Variables INITIAL_BALANCE_A and INITIAL_BALANCE_B cannot be undefined.") } - - const INITIAL_BALANCE_A = Number(bot.VALUES_TO_USE.initialBalanceB); // NOTE THAT THIS IS INVERTED BECAUSE OF POLONIEX THAT IS THE ONLY EXCHANGE SUPPORTED RIGHT NOW - const INITIAL_BALANCE_B = Number(bot.VALUES_TO_USE.initialBalanceA); - - - thisObject.executionContext = { - initialBalance: { // This is used to calculate profits. - baseAsset: INITIAL_BALANCE_A, - quotedAsset: INITIAL_BALANCE_B - }, - balance: { // This is the total balance that includes positions at the order book + funds available to be traded. - baseAsset: INITIAL_BALANCE_A, - quotedAsset: INITIAL_BALANCE_B // It starts with the initial initialBalance. - }, - availableBalance: { // This is the balance the bot has at any moment in time available to be traded (not in positions at the order book). - baseAsset: INITIAL_BALANCE_A, - quotedAsset: INITIAL_BALANCE_B // It starts with the initial initialBalance. - }, - profits: { - baseAsset: 0, - quotedAsset: 0 - }, - combinedProfits: { - baseAsset: 0, - quotedAsset: 0 - }, - positions: [], - transactions: [] - }; - - callBack(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> createConext -> err = "+ err.stack); - callBack(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function saveThemAll(callBackFunction) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> Entering function."); } - - writeExecutionContext(onDone); - - function onDone(err) { - try { - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> onDone -> Execution finished well."); } - - bot.hasTheBotJustStarted = false; - - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> onDone -> Retry Later. Requesting Execution Retry."); - callBackFunction(err); - return; - } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> onDone -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - break; - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> onDone -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeExecutionContext(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExecutionContext -> Entering function."); } - - let fileName = "/Execution.Context." + bot.startMode + "." + runIndex +".json"; - let dateForPath = bot.processDatetime.getUTCFullYear() + '/' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.processDatetime.getUTCDate(), 2) + '/' + utilities.pad(bot.processDatetime.getUTCHours(), 2) + '/' + utilities.pad(bot.processDatetime.getUTCMinutes(), 2); - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + dateForPath + fileName; - let fileContent = JSON.stringify(thisObject.executionContext); - - if(fileContent === undefined){ - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExecutionContext -> executionContext is undefined."); - callBackFunction(global.DEFAULT_ERROR_RESPONSE); - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExecutionContext -> fileContent = " + fileContent); } - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExecutionContext -> onFileCreated -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExecutionContext -> onFileCreated -> err = "+ err.stack); - callBack(err); - return; - } - - writeExucutionHistory(callBack); - return; - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExecutionContext -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeExucutionHistory(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> Entering function."); } - - let fileName = "Execution.History." + bot.startMode + "." + runIndex + ".json"; - let filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + fileName; - - let newRecord = [ - thisObject.newHistoryRecord.date.valueOf(), - thisObject.newHistoryRecord.buyAvgRate, - thisObject.newHistoryRecord.sellAvgRate, - - thisObject.newHistoryRecord.lastSellRate, - thisObject.newHistoryRecord.sellExecRate, - thisObject.newHistoryRecord.lastBuyRate, - thisObject.newHistoryRecord.buyExecRate, - - thisObject.newHistoryRecord.marketRate, - thisObject.newHistoryRecord.newPositions, - thisObject.newHistoryRecord.newTrades, - thisObject.newHistoryRecord.movedPositions, - thisObject.newHistoryRecord.profitsBaseAsset, - thisObject.newHistoryRecord.profitsQuotedAsset, - thisObject.newHistoryRecord.combinedProfitsA, - thisObject.newHistoryRecord.combinedProfitsB, - - thisObject.newHistoryRecord.messageRelevance, - thisObject.newHistoryRecord.messageTitle, - thisObject.newHistoryRecord.messageBody, - thisObject.extraData - ]; - - thisObject.executionHistory.push(newRecord); - - let fileContent = JSON.stringify(thisObject.executionHistory); - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> onFileCreated -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExucutionHistory -> onFileCreated -> err = "+ err.stack); - callBack(err); - return; - } - - if (global.LOG_CONTROL[MODULE_NAME].logContent === true) { - logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> onFileCreated -> Content written = " + fileContent); - } - - /* Here we will write the file containing the max sequence number. */ - - fileContent = runIndex; - fileName = "Execution.History." + bot.startMode + "." + "Sequence" + ".json"; - filePath = bot.filePathRoot + "/Output/" + sessionPath + "Trading-Process/" + fileName; - - fileStorage.createTextFile(filePath, fileContent + '\n', onSequenceFileCreated); - - function onSequenceFileCreated(err) { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeExucutionHistory -> onFileCreated -> onSequenceFileCreated -> Entering function."); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExucutionHistory -> onFileCreated -> onSequenceFileCreated -> err = "+ err.stack); - callBack(err); - return; - } - - writeStatusReport(callBack); - return; - } - } - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeExucutionHistory -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeStatusReport(callBack) { - - try { - - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] saveThemAll -> writeStatusReport -> Entering function."); } - - statusReportModule.file = thisObject.statusReport; - statusReportModule.save(callBack); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> writeStatusReport -> err = "+ err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] saveThemAll -> Error = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function truncDecimals(pFloatValue, pDecimals) { - - if (!pDecimals) pDecimals = 8; // Default value - - return parseFloat(parseFloat(pFloatValue).toFixed(pDecimals)); - - } - -}; diff --git a/DataSet.js b/DataSet.js index aa0c6bb1c..1a6364b7a 100644 --- a/DataSet.js +++ b/DataSet.js @@ -16,7 +16,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { /* Storage account to be used here. */ const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage + let fileStorage return thisObject; @@ -36,7 +36,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.code.codeName === undefined) { + if (thisObject.node.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Dataset witn no codeName defined -> Product Dataset = " + JSON.stringify(thisObject.node)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -48,7 +48,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.parentNode.code.codeName === undefined) { + if (thisObject.node.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Product Definition witn no codeName defined -> Product Definition = " + JSON.stringify(thisObject.node.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -60,7 +60,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.parentNode.parentNode.code.codeName === undefined) { + if (thisObject.node.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Bot witn no codeName defined. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -72,14 +72,14 @@ exports.newDataSet = function newDataSet(BOT, logger) { return } - if (thisObject.node.parentNode.parentNode.parentNode.code.codeName === undefined) { + if (thisObject.node.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(thisObject.node.parentNode.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } /* Now we will see where do we need to fetch this data from. */ - let network = global.TASK_NETWORK + let network = global.TASK_NETWORK let datasetProductDefinition = thisObject.node.parentNode for (let i = 0; i < network.networkNodes.length; i++) { @@ -102,9 +102,9 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (datasetProductDefinition.id === productDefinition.id) { /* We found where the data is located on the network. */ - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.code.host, networkNode.code.webPort); + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); callBackFunction(global.DEFAULT_OK_RESPONSE); return } @@ -133,9 +133,9 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (datasetProductDefinition.id === productDefinition.id) { /* We found where the data is located on the network. */ - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.code.host, networkNode.code.webPort); + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); callBackFunction(global.DEFAULT_OK_RESPONSE); return } @@ -155,7 +155,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { callBackFunction(global.DEFAULT_FAIL_RESPONSE); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -173,8 +173,8 @@ exports.newDataSet = function newDataSet(BOT, logger) { if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTextFile -> Entering function."); } - let dataMineCodeName = thisObject.node.parentNode.parentNode.parentNode.code.codeName - let botCodeName = thisObject.node.parentNode.parentNode.code.codeName + let dataMineCodeName = thisObject.node.parentNode.parentNode.parentNode.config.codeName + let botCodeName = thisObject.node.parentNode.parentNode.config.codeName let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + dataMineCodeName + "/" + botCodeName; let filePath = filePathRoot + "/Output/" + pFolderPath; @@ -191,7 +191,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] 'getTextFile' -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] 'getTextFile' -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -232,7 +232,7 @@ exports.newDataSet = function newDataSet(BOT, logger) { } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] 'createTextFile' -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] 'createTextFile' -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/DebugLog.js b/DebugLog.js index bcaf567a3..f3372242d 100644 --- a/DebugLog.js +++ b/DebugLog.js @@ -34,7 +34,7 @@ exports.newDebugLog = function newDebugLog() { disableLogging = pdisableLogging } catch (err) { - console.log("[ERROR] Debug Log -> initialize -> err = "+ err.stack); + console.log("[ERROR] Debug Log -> initialize -> err = " + err.stack); } } @@ -53,12 +53,12 @@ exports.newDebugLog = function newDebugLog() { date = date.getUTCFullYear() + '-' + strPad(date.getUTCMonth() + 1, 2, "0") + '-' + strPad(date.getUTCDate(), 2, "0"); console.log(new Date().toISOString() + " " + strPad(thisObject.bot.exchange, 20) + " " + strPad(thisObject.bot.market.baseAsset + '/' + thisObject.bot.market.quotedAsset, 10) + " " + strPad(pBot, 30) + " " + strPad(pProcess, 30) - + " Internal Loop # " + strPad(internalLoopCounter + 1, 8) + " " + strPad(date, 30) + " " + strPad(percentage, 10)) + + " Internal Loop # " + strPad(internalLoopCounter + 1, 8) + " " + strPad(date, 30) + " " + strPad(percentage, 10)) persist(); function strPad(str, max, fill) { - if (fill === undefined) {fill = " "} + if (fill === undefined) { fill = " " } str = str.toString(); return str.length < max ? strPad(fill + str, max) : str; } @@ -69,7 +69,7 @@ exports.newDebugLog = function newDebugLog() { /* Here we actually write the content of the in-memory log to a file */ try { - if (accumulatedLog === "[") {return} // nothing to persist at the moment. + if (accumulatedLog === "[") { return } // nothing to persist at the moment. internalLoopCounter++; @@ -82,7 +82,7 @@ exports.newDebugLog = function newDebugLog() { let filePath = thisObject.bot.filePathRoot + "/Logs/" + thisObject.bot.process + "/" if (thisObject.bot.SESSION !== undefined) { - filePath = filePath + thisObject.bot.SESSION.folderName + "/" + executionDatetime; + filePath = filePath + thisObject.bot.SESSION.folderName + "/" + executionDatetime; } else { filePath = filePath + executionDatetime; } @@ -102,32 +102,32 @@ exports.newDebugLog = function newDebugLog() { writeLog(); /* This is the implementation of the mechanism to auto-mantain logs. */ - if (contentToPersist.indexOf('[ERROR]') < 0) { + if (contentToPersist.indexOf('[ERROR]') < 0 && contentToPersist.indexOf('[PERSIST]') < 0) { thisObject.bot.LOGS_TO_DELETE_QUEUE.push(filePath + '/' + fileName) } if (thisObject.bot.LOGS_TO_DELETE_QUEUE.length > thisObject.bot.DELETE_QUEUE_SIZE) { let fileToDelete = thisObject.bot.LOGS_TO_DELETE_QUEUE[0] thisObject.bot.LOGS_TO_DELETE_QUEUE.splice(0, 1) /* Will delete this file only if it does not contains ERROR inside. */ - let fileContent = fileStorage.getTextFile(fileToDelete, onGetFile, true) + let fileContent = fileStorage.getTextFile(fileToDelete, onGetFile, true) function onGetFile(err, fileContent) { if (err.result === global.DEFAULT_OK_RESPONSE.result) { if (fileContent.indexOf("ERROR") < 0) { /* Logs will only be deleted when they contain no ERROR in them. */ fileStorage.deleteTextFile(fileToDelete); - } + } } else { fileStorage.deleteTextFile(fileToDelete); } } } - + function writeLog() { fileStorage.createTextFile(filePath + '/' + fileName, contentToPersist + '\r\n' + "]", onFileCreated); function onFileCreated(err) { if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - console.log("[ERROR] DebugLog -> persist -> onInizialized -> onFileCreated -> err = "+ err.message); + console.log("[ERROR] DebugLog -> persist -> onInizialized -> onFileCreated -> err = " + err.message); setTimeout(writeLog, 10000); // Lets retry until we make it. return; } @@ -135,7 +135,7 @@ exports.newDebugLog = function newDebugLog() { } } } catch (err) { - console.log("[ERROR] DebugLog -> persist -> err = "+ err.stack); + console.log("[ERROR] DebugLog -> persist -> err = " + err.stack); console.log("[ERROR] DebugLog -> persist -> onInizialized -> contentToPersist = " + contentToPersist); } } @@ -174,7 +174,7 @@ exports.newDebugLog = function newDebugLog() { accumulatedLog = accumulatedLog + logLine; } catch (err) { - console.log("[ERROR] DebugLog -> write -> err = "+ err.stack); + console.log("[ERROR] DebugLog -> write -> err = " + err.stack); } } diff --git a/FileStorage.js b/FileStorage.js index 22966c706..bec9ef69a 100644 --- a/FileStorage.js +++ b/FileStorage.js @@ -22,11 +22,11 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { deleteTextFile: deleteTextFile } - return thisObject + return thisObject function getTextFile(filePath, callBackFunction, noRetry, canUsePrevious) { - let currentRetryGetTextFile = 0 + let currentRetryGetTextFile = 0 recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious) @@ -77,7 +77,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { } callBackFunction(customResponse) return - } + } fileDoesNotExist = true logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> File does not exist. Retrying. ') @@ -106,7 +106,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { if (mustBeJason === false) { /* In this case there is nothing else to check. */ callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString()) - return + return } /* @@ -117,7 +117,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { /* The file was correctly read and could be parsed. */ callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString()) - return + return } catch (err) { @@ -140,11 +140,11 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> canUsePrevious -> err = ' + err.stack) callBackFunction(global.DEFAULT_FAIL_RESPONSE) return - } + } /* Returning the previous file */ callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString()) - return + return } } else { logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Could read the file, but could not parse it as it is not a valid JSON.') @@ -153,12 +153,12 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { setTimeout(retry, retryTimeToUse) return } - } + } } } catch (err) { - logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error writing file -> file = ' + fileLocation) - logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error writing file -> err = ' + err.stack) + logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error reading file -> file = ' + fileLocation) + logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error reading file -> err = ' + err.stack) retry() } @@ -252,7 +252,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> err = ' + err.stack) setTimeout(retry, retryTimeToUse) return - } + } /* Rename de Original into Previous */ fs.rename(fileLocation, fileLocation + '.Previous.json', onOriginalRenamed) @@ -266,7 +266,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> Error renaming original file -> file = ' + fileLocation) logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> Error renaming original file -> err = ' + err.stack) setTimeout(retry, retryTimeToUse) - return + return } /* Rename de Temp into Original */ @@ -282,13 +282,13 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { callBackFunction(global.DEFAULT_OK_RESPONSE) } - } + } } } } else { /* In this case, there is no need to keep a copy of the file being replaced, so we just delete and that's it. */ - fs.unlink(fileLocation , onUnlinked) + fs.unlink(fileLocation, onUnlinked) function onUnlinked(err) { let code = '' @@ -296,11 +296,11 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { code = err.code } if (code !== '' && code !== 'ENOENT') { - logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> file = ' + fileLocation ) + logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> file = ' + fileLocation) logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> err = ' + err.stack) setTimeout(retry, retryTimeToUse) return - } + } /* Rename de Temp into Original */ fs.rename(fileLocation + '.tmp', fileLocation, onTempRenamed) @@ -406,33 +406,33 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { /* Function to create folders of missing folders at any path. */ function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { - const sep = '/'; - const initDir = path.isAbsolute(targetDir) ? sep : ''; - const baseDir = isRelativeToScript ? __dirname : '.'; + const sep = '/'; + const initDir = path.isAbsolute(targetDir) ? sep : ''; + const baseDir = isRelativeToScript ? __dirname : '.'; - return targetDir.split(sep).reduce((parentDir, childDir) => { - const curDir = path.resolve(baseDir, parentDir, childDir); - try { - const fs = require('fs') - fs.mkdirSync(curDir); - } catch (err) { - if (err.code === 'EEXIST') { // curDir already exists! - return curDir; - } + return targetDir.split(sep).reduce((parentDir, childDir) => { + const curDir = path.resolve(baseDir, parentDir, childDir); + try { + const fs = require('fs') + fs.mkdirSync(curDir); + } catch (err) { + if (err.code === 'EEXIST') { // curDir already exists! + return curDir; + } - // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. - if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. - throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); - } + // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. + if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. + throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); + } - const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; - if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) { - throw err; // Throw if it's just the last created dir. - } - } + const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; + if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) { + throw err; // Throw if it's just the last created dir. + } + } - return curDir; - }, initDir); + return curDir; + }, initDir); } function getFileViaHTTP(filePath, callback, callBackFunction) { @@ -455,7 +455,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) { logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onError -> err = " + err.stack); logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onError -> Failed to fetch file via HTTP. Will retry later. "); callBackFunction(global.DEFAULT_RETRY_RESPONSE); - }) + }) function onResponse(response) { diff --git a/IndicatorBot.js b/IndicatorBot.js index 004ed077f..fc28afeae 100644 --- a/IndicatorBot.js +++ b/IndicatorBot.js @@ -65,7 +65,7 @@ if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets - if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) @@ -86,7 +86,7 @@ */ recordDefinition = outputDatasetNode.referenceParent.parentNode.record - singularVariableName = outputDatasetNode.referenceParent.parentNode.code.singularVariableName + singularVariableName = outputDatasetNode.referenceParent.parentNode.config.singularVariableName /* Check Irregular Periods */ @@ -103,7 +103,7 @@ recordDefinition, outputDatasetNode.referenceParent.parentNode.dataBuilding, singularVariableName, - outputDatasetNode.referenceParent.parentNode.code.codeName, + outputDatasetNode.referenceParent.parentNode.config.codeName, timeFrame, timeFrameLabel, resultsWithIrregularPeriods, @@ -118,14 +118,14 @@ } else { outputData = jsonData } - products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName] = outputData + products[outputDatasetNode.referenceParent.parentNode.config.pluralVariableName] = outputData } /*At the fourth and last phase, we will save the new information generated into files corresponding to each output outputDatasetNode.*/ let totalFilesWritten = 0 for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] - let outputData = products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName] + let outputData = products[outputDatasetNode.referenceParent.parentNode.config.pluralVariableName] let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution. let contextSummary = {} @@ -136,10 +136,10 @@ /* Simplifying the access to basic info */ - contextSummary.dataset = outputDatasetNode.referenceParent.code.codeName - contextSummary.product = outputDatasetNode.referenceParent.parentNode.code.codeName - contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName - contextSummary.dataMine = outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName + contextSummary.dataset = outputDatasetNode.referenceParent.config.codeName + contextSummary.product = outputDatasetNode.referenceParent.parentNode.config.codeName + contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName + contextSummary.dataMine = outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.config.codeName /* This stuff is still hardcoded and unresolved. */ contextSummary.botVersion = { diff --git a/MultiPeriod.js b/MultiPeriod.js index 217e49077..c414630a3 100644 --- a/MultiPeriod.js +++ b/MultiPeriod.js @@ -41,7 +41,7 @@ dataDependenciesModule = pDataDependencies; processConfig = pProcessConfig; - let USER_BOT_MODULE = require("./TradingBot") + let USER_BOT_MODULE = require("./Trading/TradingBot") botInstance = USER_BOT_MODULE.newTradingBot(bot, logger, UTILITIES, FILE_STORAGE); botInstance.initialize(exchangeAPI, callBackFunction); @@ -57,7 +57,7 @@ multiPeriodDataFiles = undefined statusDependencies = undefined dataDependenciesModule = undefined - botInstance.finalize() + botInstance.finalize() botInstance = undefined fileStorage = undefined processConfig = undefined @@ -71,7 +71,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } let currentTimeFrame - let currentTimeFrameLabel + let currentTimeFrameLabel let market = bot.market; let botNeverRan = true; @@ -86,7 +86,7 @@ let previousDay; // Holds the date of the previous day relative to the processing date. - let interExecutionMemoryArray = []; + let variable getContextVariables(); @@ -101,12 +101,11 @@ let statusReport; /* We are going to use the start date as beging of market date. */ - contextVariables.dateBeginOfMarket = bot.VALUES_TO_USE.timeRange.initialDatetime + contextVariables.dateBeginOfMarket = bot.VALUES_TO_USE.timeRange.initialDatetime /* Here we get the status report from the bot who knows which is the end of the market. */ - statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point") if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. @@ -138,7 +137,6 @@ contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf()); /* Validation that the data is not up-to-date. */ - if (bot.multiPeriodProcessDatetime.valueOf() - ONE_DAY_IN_MILISECONDS > contextVariables.dateEndOfMarket.valueOf() && bot.SESSION.type !== "Backtesting Session") { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> " + "Head of the market is @ " + contextVariables.dateEndOfMarket); } @@ -150,7 +148,6 @@ } /* Finally we get our own Status Report. */ - statusReport = statusDependencies.reportsByMainUtility.get("Self Reference") if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. @@ -166,26 +163,25 @@ } thisReport = statusReport.file; + variable = thisReport.variable; + if (variable === undefined) { variable = {} } // This should happen only when there is no status report if (thisReport.lastFile !== undefined) { - if (bot.hasTheBotJustStarted === true && bot.resumeExecution === false) { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); } startFromBegining(); return; } - contextVariables.lastFile = new Date(thisReport.lastFile); - interExecutionMemoryArray = thisReport.interExecutionMemoryArray; - processSingleFiles(); return; } else { - /* - In the case when there is no status report, we assume like the last processed file is the one on the date of Begining of Market. + We are here becuse either: + 1. There is no status report + 2. There is no Last File (this happens on Market Files) */ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); } startFromBegining(); @@ -197,19 +193,6 @@ contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS); if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); } - - /* - The first time the process is running is the right time to create the data structure that is going to be shared across different executions. - This data structure has one object per each timeFrame. - */ - - interExecutionMemoryArray = []; - - for (let i = 0; i < global.dailyFilePeriods.length; i++) { - let emptyObject = {}; - interExecutionMemoryArray.push(emptyObject); - } - processSingleFiles(); } @@ -237,7 +220,6 @@ function dependencyLoopBody() { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> Entering function."); } let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; @@ -249,24 +231,23 @@ try { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> Entering function."); } - if (datasetModule.node.code.codeName !== "Single-File") { + if (datasetModule.node.config.codeName !== "Single-File") { dependencyControlLoop(); return } - if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.code.singularVariableName) !== true) { + if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) { dependencyControlLoop(); return } let fileName = "Data.json"; - let filePath = datasetModule.node.parentNode.code.codeName + '/' + datasetModule.node.code.codeName; + let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName; datasetModule.getTextFile(filePath, fileName, onFileReceived); function onFileReceived(err, text) { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> Entering function."); } if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyLoopBody -> getFile -> onFileReceived -> text = " + text); } @@ -309,22 +290,16 @@ function dependencyControlLoop() { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processSingleFiles -> dependencyControlLoop -> Entering function."); } dependencyIndex++; if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - dependencyLoopBody(); - } else { - let mapKey = "Single Files" multiPeriodDataFiles.set(mapKey, dataFiles) - processMarketFiles(); - } } catch (err) { @@ -344,25 +319,17 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> Entering function."); } try { - let n; - timeFramesLoop(); function timeFramesLoop() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoop -> Entering function."); } - /* We will iterate through all posible timeFrames. */ - n = 0 // loop Variable representing each possible period as defined at the timeFrames array. - timeFramesLoopBody(); - } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoop -> err = " + err.stack); @@ -371,11 +338,8 @@ } function timeFramesLoopBody() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> Entering function."); } - const timeFrame = global.marketFilesPeriods[n][0]; const timeFrameLabel = global.marketFilesPeriods[n][1]; @@ -389,9 +353,7 @@ dependencyLoopBody(); function dependencyLoopBody() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> Entering function."); } let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; @@ -403,26 +365,24 @@ try { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> Entering function."); } - if (dependency.referenceParent.code.codeName !== "Multi-Period-Market") { + if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") { dependencyControlLoop(); return } - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.code.singularVariableName) !== true) { - if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.code.pluralVariableName === 'candles')) { + if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { + if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { dependencyControlLoop(); return } } let fileName = "Data.json"; - let filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel; + let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel; datasetModule.getTextFile(filePath, fileName, onFileReceived); function onFileReceived(err, text) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> Entering function."); } if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> text = " + text); } @@ -433,7 +393,7 @@ } else { logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.message); callBackFunction(err); - } + } return; } @@ -454,7 +414,6 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> timeFramesLoop -> dependencyLoopBody -> err = " + err.stack); @@ -463,24 +422,16 @@ } function dependencyControlLoop() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> dependencyControlLoop -> Entering function."); } dependencyIndex++; - if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - dependencyLoopBody(); - } else { - let mapKey = global.marketFilesPeriods[n][1]; multiPeriodDataFiles.set(mapKey, dataFiles) - timeFramesControlLoop(); - } } catch (err) { @@ -496,21 +447,13 @@ } function timeFramesControlLoop() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processMarketFiles -> timeFramesControlLoop -> Entering function."); } - n++; - if (n < global.marketFilesPeriods.length) { - timeFramesLoopBody(); - } else { - processDailyFiles(); - } } catch (err) { @@ -518,7 +461,6 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> err = " + err.stack); @@ -527,20 +469,14 @@ } function processDailyFiles() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> Entering function."); } - try { - let n = 0; - if (currentTimeFrame) { callTheBot() return - } - + } bot.multiPeriodProcessDatetime = new Date(contextVariables.lastFile.valueOf() - ONE_DAY_IN_MILISECONDS); // Go back one day to start well when we advance time at the begining of the loop. - advanceTime(); function advanceTime() { @@ -551,12 +487,11 @@ bot.multiPeriodProcessDatetime = new Date(bot.multiPeriodProcessDatetime.valueOf() + ONE_DAY_IN_MILISECONDS); previousDay = new Date(bot.multiPeriodProcessDatetime.valueOf() - ONE_DAY_IN_MILISECONDS); - + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> bot.multiPeriodProcessDatetime = " + bot.multiPeriodProcessDatetime); } if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> advanceTime -> previousDay = " + previousDay); } /* Validation that we are not going past the user defined end date. */ - if (bot.multiPeriodProcessDatetime.valueOf() >= bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; @@ -565,11 +500,9 @@ bot.STOP_SESSION = true callBackFunction(global.DEFAULT_OK_RESPONSE); return; - } /* Validation that we are not going past the head of the market. */ - if (bot.multiPeriodProcessDatetime.valueOf() > contextVariables.dateEndOfMarket.valueOf() + ONE_DAY_IN_MILISECONDS - 1) { const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; @@ -577,7 +510,6 @@ callBackFunction(global.DEFAULT_OK_RESPONSE); return; - } checkStopTaskGracefully(); @@ -591,20 +523,13 @@ function checkStopTaskGracefully() { try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> checkStopTaskGracefully -> Entering function."); } - /* Validation that we dont need to stop. */ - if (global.STOP_TASK_GRACEFULLY === true) { - callBackFunction(global.DEFAULT_OK_RESPONSE); return; - } - checkStopProcessing(); - } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> checkStopTaskGracefully -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -612,18 +537,13 @@ } function checkStopProcessing() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> checkStopProcessing -> Entering function."); } /* Validation that we dont need to stop. */ - if (bot.STOP_SESSION === true) { - callBackFunction(global.DEFAULT_OK_RESPONSE); return; - } timeFramesLoop(); @@ -635,23 +555,16 @@ } function timeFramesLoop() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoop -> Entering function."); } /* Telling the world we are alive and doing well */ let processingDate = bot.multiPeriodProcessDatetime.getUTCFullYear() + '-' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCDate(), 2); - bot.processHeartBeat(processingDate) - + bot.processHeartBeat(processingDate) /* - We will iterate through all posible timeFrames. - */ - n = 0 // loop Variable representing each possible period as defined at the timeFrames array. - timeFramesLoopBody(); } @@ -662,9 +575,7 @@ } function timeFramesLoopBody() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> Entering function."); } const timeFrame = global.dailyFilePeriods[n][0]; @@ -693,9 +604,7 @@ dependencyLoopBody(); function dependencyLoopBody() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> Entering function."); } let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; @@ -715,13 +624,13 @@ try { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> Entering function."); } - if (dependency.referenceParent.code.codeName !== "Multi-Period-Daily") { + if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") { dependencyControlLoop(); return } - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.code.singularVariableName) !== true) { - if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.code.pluralVariableName === 'candles')) { + if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { + if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { dependencyControlLoop(); return } @@ -730,19 +639,17 @@ let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2); let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } - let fileName = "Data.json"; + let fileName = "Data.json"; datasetModule.getTextFile(filePath, fileName, onFileReceived); function onFileReceived(err, text) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> Entering function."); } if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> text = " + text); } @@ -776,7 +683,6 @@ } previousFile = JSON.parse(text); - getCurrentFile(); } @@ -796,13 +702,13 @@ try { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> Entering function."); } - if (dependency.referenceParent.code.codeName !== "Multi-Period-Daily") { + if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") { dependencyControlLoop(); return } - if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.code.singularVariableName) !== true) { - if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.code.pluralVariableName === 'candles')) { + if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) { + if (!(bot.VALUES_TO_USE.timeFrame === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) { dependencyControlLoop(); return } @@ -810,19 +716,17 @@ let dateForPath = bot.multiPeriodProcessDatetime.getUTCFullYear() + '/' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.multiPeriodProcessDatetime.getUTCDate(), 2); let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } - let fileName = "Data.json"; + let fileName = "Data.json"; datasetModule.getTextFile(filePath, fileName, onFileReceived); function onFileReceived(err, text) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> Entering function."); } if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> text = " + text); } @@ -841,11 +745,8 @@ } currentFile = JSON.parse(text); - let dataFile = previousFile.concat(currentFile); - dataFiles.set(dependency.id, dataFile); - dependencyControlLoop(); } @@ -868,24 +769,16 @@ } function dependencyControlLoop() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> dependencyControlLoop -> Entering function."); } - dependencyIndex++; if (dependencyIndex < dataDependenciesModule.nodeArray.length) { - dependencyLoopBody(); - } else { - let mapKey = global.dailyFilePeriods[n][1]; multiPeriodDataFiles.set(mapKey, dataFiles) - timeFramesControlLoop(); - } } catch (err) { @@ -902,25 +795,17 @@ } function timeFramesControlLoop() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> timeFramesControlLoop -> Entering function."); } - n++; - if (n < global.dailyFilePeriods.length) { - timeFramesLoopBody(); - } else { - n = 0; - if (currentTimeFrame !== undefined) { callTheBot(); } else { - logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesControlLoop -> Time Frame not Recognized. Can not continue." ); + logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> timeFramesControlLoop -> Time Frame not Recognized. Can not continue."); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -932,31 +817,26 @@ } function callTheBot() { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> Entering function."); } - botInstance.start( multiPeriodDataFiles, currentTimeFrame, currentTimeFrameLabel, bot.multiPeriodProcessDatetime, - interExecutionMemoryArray[n], + variable, onBotFinished); function onBotFinished(err) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> Entering function."); } - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { callBackFunction(err); return; } - botNeverRan = false; + botNeverRan = false + bot.RESUME = true // From here on, all other loops executions must resume from where we left at this current run. if (currentTimeFrame > global.dailyFilePeriods[0][0]) { writeMarketStatusReport(onMarketStatusReport) @@ -966,9 +846,7 @@ } function onWritten(err) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> onWritten -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { @@ -978,7 +856,6 @@ } writeDailyStatusReport(bot.multiPeriodProcessDatetime, onDailyStatusReport); - } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> callTheBot -> onBotFinished -> onWritten -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -991,7 +868,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> onDailyStatusReport -> Entering function."); } /* The next run we need the process to continue at the date it finished. */ - processConfig.framework.startDate.resumeExecution = true; + processConfig.framework.startDate.resumeExecution = true; advanceTime(); } catch (err) { @@ -1005,7 +882,6 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processDailyFiles -> callTheBot -> onBotFinished -> onMarketStatusReport -> Entering function."); } /* This is where we check if we need reached the user defined end datetime. */ - const now = new Date() if (now.valueOf() >= bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { @@ -1044,23 +920,19 @@ } function writeDataRanges(currentTimeFrameLabel, callBack) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRanges -> Entering function."); } let outputDatasetIndex = -1; controlLoop(); function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.code.codeName; + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; writeDataRange(contextVariables.dateBeginOfMarket, bot.multiPeriodProcessDatetime, productCodeName, currentTimeFrameLabel, controlLoop); } function controlLoop() { - outputDatasetIndex++; - if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) { productLoopBody(); } else { @@ -1076,25 +948,20 @@ } function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel, callBack) { - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> Entering function."); } - let dataRange = { begin: pBegin.valueOf(), end: pEnd.valueOf() + ONE_DAY_IN_MILISECONDS }; let fileContent = JSON.stringify(dataRange); - let fileName = '/Data.Range.json'; let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName; fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); function onFileCreated(err) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { @@ -1107,11 +974,11 @@ logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent); } - let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset let event = { dateRange: dataRange } - + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) callBack(global.DEFAULT_OK_RESPONSE); @@ -1124,22 +991,19 @@ } function writeDailyStatusReport(lastFileDate, callBack) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyStatusReport -> Entering function."); } if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyStatusReport -> lastFileDate = " + lastFileDate); } try { - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period" + "-" + "dataSet.V1"; let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.currentDaytime; thisReport.file.lastFile = lastFileDate; - thisReport.file.interExecutionMemoryArray = interExecutionMemoryArray; + thisReport.file.variable = variable; thisReport.save(callBack); bot.hasTheBotJustStarted = false; - } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> writeDailyStatusReport -> err = " + err.stack); @@ -1148,15 +1012,13 @@ } function writeMarketStatusReport(callBack) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketStatusReport -> Entering function."); } - try { - let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period" + "-" + "dataSet.V1"; let thisReport = statusDependencies.statusReports.get(reportKey); thisReport.file.lastExecution = bot.processDatetime; + thisReport.file.variable = variable; thisReport.save(callBack); logger.newInternalLoop(bot.codeName, bot.process, bot.multiPeriodProcessDatetime); @@ -1166,15 +1028,12 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - } - catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function pad(str, max) { str = str.toString(); return str.length < max ? pad(" " + str, max) : str; diff --git a/MultiPeriodDaily.js b/MultiPeriodDaily.js index 4e384081d..096df4c62 100644 --- a/MultiPeriodDaily.js +++ b/MultiPeriodDaily.js @@ -22,7 +22,7 @@ let fileStorage = FILE_STORAGE.newFileStorage(logger); - let bootstrappingTheProcess = false + let bootstrappingTheProcess = false let processConfig; let beginingOfMarket @@ -48,7 +48,7 @@ botInstance.initialize(callBackFunction); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -248,7 +248,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack); if (err.message === "Cannot read property 'file' of undefined") { logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. "); logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys)); @@ -280,7 +280,7 @@ bot.multiPeriodDailyProcessDatetime = new Date(bot.multiPeriodDailyProcessDatetime.valueOf() + ONE_DAY_IN_MILISECONDS); previousDay = new Date(bot.multiPeriodDailyProcessDatetime.valueOf() - ONE_DAY_IN_MILISECONDS); - + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> advanceTime -> bot.multiPeriodDailyProcessDatetime = " + bot.multiPeriodDailyProcessDatetime); } if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> advanceTime -> previousDay = " + previousDay); } @@ -300,7 +300,7 @@ let currentDateString = bot.multiPeriodDailyProcessDatetime.getUTCFullYear() + '-' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCDate(), 2); let currentDate = new Date(bot.multiPeriodDailyProcessDatetime) let percentage = global.getPercentage(fromDate, currentDate, lastDate) - bot.processHeartBeat(currentDateString, percentage) + bot.processHeartBeat(currentDateString, percentage) if (global.areEqualDates(currentDate, new Date()) === false) { logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage); @@ -309,7 +309,7 @@ checkStopTaskGracefully(); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> advanceTime -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> advanceTime -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -355,7 +355,7 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -395,9 +395,9 @@ let dependency = dataDependenciesModule.nodeArray[dependencyIndex]; - if (dependency === undefined ) { + if (dependency === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> You need to add at least one Data Dependency to the process Multi Period Daily. Aborting process." ); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> You need to add at least one Data Dependency to the process Multi Period Daily. Aborting process."); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return; } @@ -422,10 +422,10 @@ let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2); let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } let fileName = "Data.json"; @@ -455,14 +455,14 @@ if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_RETRY_RESPONSE); return; } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); callBackFunction(err); return; } @@ -473,13 +473,13 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getPreviousFile -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -492,12 +492,12 @@ let dateForPath = bot.multiPeriodDailyProcessDatetime.getUTCFullYear() + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCDate(), 2); let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Daily") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel + "/" + dateForPath; + if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } - let fileName = "Data.json"; + let fileName = "Data.json"; datasetModule.getTextFile(filePath, fileName, onFileReceived); @@ -543,19 +543,19 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> Catch Error -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> Catch Error -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getCurrentFile -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -579,7 +579,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -617,19 +617,19 @@ periodsControlLoop(); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -659,7 +659,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> controlLoop -> onWritten -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = " + err.stack); callBackFunction(err); return; } @@ -667,21 +667,21 @@ writeStatusReport(bot.multiPeriodDailyProcessDatetime, advanceTime); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -696,7 +696,7 @@ controlLoop(); function productLoopBody() { - let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.code.codeName; + let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName; writeDataRange(contextVariables.dateBeginOfMarket, bot.multiPeriodDailyProcessDatetime, productCodeName, controlLoop); } @@ -712,7 +712,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRanges -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRanges -> err = " + err.stack); callBack(global.DEFAULT_FAIL_RESPONSE); } @@ -741,7 +741,7 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack); callBack(err); return; } @@ -750,7 +750,7 @@ logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent); } - let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset let event = { dateRange: dataRange } @@ -761,7 +761,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> err = " + err.stack); callBack(global.DEFAULT_FAIL_RESPONSE); } } @@ -786,14 +786,14 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/MultiPeriodMarket.js b/MultiPeriodMarket.js index 3c8b10f07..11bc7ae40 100644 --- a/MultiPeriodMarket.js +++ b/MultiPeriodMarket.js @@ -41,7 +41,7 @@ botInstance.initialize(callBackFunction); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -100,7 +100,7 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -137,13 +137,13 @@ if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> Entering function."); } let dateForPath = bot.processDatetime.getUTCFullYear() + '/' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.processDatetime.getUTCDate(), 2); - let fileName = "Data.json"; + let fileName = "Data.json"; let filePath - if (dependency.referenceParent.code.codeName === "Multi-Period-Market") { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + timeFrameLabel; + if (dependency.referenceParent.config.codeName === "Multi-Period-Market") { + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel; } else { - filePath = dependency.referenceParent.parentNode.code.codeName + '/' + dependency.referenceParent.code.codeName + "/" + dateForPath; + filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath; } datasetModule.getTextFile(filePath, fileName, onFileReceived); @@ -156,7 +156,7 @@ if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); callBackFunction(err); return; } @@ -168,20 +168,20 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> dependencyLoopBody -> getFile -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoop -> dependencyLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -205,7 +205,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> dependencyControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -242,19 +242,19 @@ periodsControlLoop(); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> onBotFinished -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } - catch(err){ - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> err = "+ err.stack); + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> callTheBot -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsLoopBody -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -278,14 +278,14 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> periodsControlLoop -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -304,23 +304,23 @@ if (global.areEqualDates(bot.processDatetime, new Date()) === false) { logger.newInternalLoop(bot.codeName, bot.process, bot.processDatetime); - } - + } + /* Telling the world we are alive and doing well */ let currentDateString = bot.processDatetime.getUTCFullYear() + '-' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.processDatetime.getUTCDate(), 2); let currentDate = new Date(bot.processDatetime) let lastDate = new Date() - bot.processHeartBeat(currentDateString, global.getPercentage(currentDate, currentDate, lastDate)) + bot.processHeartBeat(currentDateString, global.getPercentage(currentDate, currentDate, lastDate)) } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/ProcessExecutionEvents.js b/ProcessExecutionEvents.js index 8731bf55a..b304be111 100644 --- a/ProcessExecutionEvents.js +++ b/ProcessExecutionEvents.js @@ -8,7 +8,7 @@ */ const MODULE_NAME = "Process Execution Events"; - logger.fileName = MODULE_NAME + logger.fileName = MODULE_NAME let bot = BOT; @@ -43,7 +43,7 @@ return } - let market = bot.market.baseAsset + '/' + bot.market.quotedAsset + let market = bot.market.baseAsset + '/' + bot.market.quotedAsset currentProcessKey = bot.processNode.referenceParent.name + "-" + bot.processNode.referenceParent.type + "-" + bot.processNode.referenceParent.id + "-" + bot.exchange + "-" + market if (bot.processNode.referenceParent.executionStartedEvent !== undefined) { @@ -52,7 +52,7 @@ processThisDependsOn = bot.processNode.referenceParent.executionStartedEvent.referenceParent.parentNode name = processThisDependsOn.name - if (processThisDependsOn.code.monthlyInstances === true) { + if (processThisDependsOn.config.monthlyInstances === true) { logger.fileName = MODULE_NAME + "." + name; } if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " depends on " + name); } @@ -90,13 +90,13 @@ /* We found where the task that runs the process definition we are waiting for is located on the network. */ thisObject.networkNode = networkNode - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webSocketsPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connecting to Websockets Server " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } - eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.code.host, networkNode.code.webSocketsPort) + eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.config.host, networkNode.config.webSocketsPort) eventServerClient.initialize(onConnected) function onConnected() { - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webSocketsPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webSocketsPort + '.'); } callBackFunction(global.DEFAULT_OK_RESPONSE); return @@ -119,7 +119,7 @@ return } callBackFunction(global.DEFAULT_OK_RESPONSE); - + } catch (err) { logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -151,7 +151,7 @@ let extraCallerId = '-' + Math.trunc(Math.random() * 10000) + '-' - let market = bot.market.baseAsset + '/' + bot.market.quotedAsset + let market = bot.market.baseAsset + '/' + bot.market.quotedAsset let key = processThisDependsOn.name + "-" + processThisDependsOn.type + "-" + processThisDependsOn.id + "-" + bot.exchange + "-" + market let callerId = bot.dataMine + "-" + bot.codeName + "-" + bot.process + extraCallerId @@ -163,7 +163,7 @@ eventServerClient.listenToEvent(key, 'Process Execution Finished', undefined, callerId, responseCallBack, eventsCallBack) - bot.processHeartBeat(undefined, undefined, "Waiting for " + thisObject.networkNode.name + "->" + processThisDependsOn.parentNode.parentNode.name + "->" + processThisDependsOn.parentNode.code.codeName + "->" + processThisDependsOn.code.codeName) + bot.processHeartBeat(undefined, undefined, "Waiting for " + thisObject.networkNode.name + "->" + processThisDependsOn.parentNode.parentNode.name + "->" + processThisDependsOn.parentNode.config.codeName + "->" + processThisDependsOn.config.codeName) function responseCallBack(message) { if (message.result !== global.DEFAULT_OK_RESPONSE.result) { @@ -192,9 +192,9 @@ global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Started') global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Started', event) - bot.processHeartBeat(undefined, undefined,"Running...") + bot.processHeartBeat(undefined, undefined, "Running...") - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> " + currentProcessKey + " Process Execution Started because " + key + " Finished."); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> " + currentProcessKey + " Process Execution Started because " + key + " Finished."); } } callBackFunction(message.event.err); @@ -208,7 +208,7 @@ global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Started') global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Started', event) - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> " + currentProcessKey + " Process Execution Started " ); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> " + currentProcessKey + " Process Execution Started "); } callBackFunction(global.DEFAULT_OK_RESPONSE); } @@ -232,7 +232,7 @@ global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Finished') global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Finished', event) - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] finish -> " + currentProcessKey + " Process Execution Finished " ); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] finish -> " + currentProcessKey + " Process Execution Finished "); } callBackFunction(global.DEFAULT_OK_RESPONSE); diff --git a/ProcessOutput.js b/ProcessOutput.js index 7bbdfff10..2130a7cf6 100644 --- a/ProcessOutput.js +++ b/ProcessOutput.js @@ -30,29 +30,29 @@ /* Some validations to verify that everything is in place. */ if (outputDataset.referenceParent !== undefined) { - if (outputDataset.referenceParent.code.codeName === undefined) { + if (outputDataset.referenceParent.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } if (outputDataset.referenceParent.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.code.codeName === undefined) { + if (outputDataset.referenceParent.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } if (outputDataset.referenceParent.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.code.codeName === undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) { - if (outputDataset.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { + if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return - } + } } else { logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); @@ -76,10 +76,10 @@ /* All good, lets emit the event. */ - let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.code.codeName - let botName = outputDataset.referenceParent.parentNode.parentNode.code.codeName - let product = outputDataset.referenceParent.parentNode.code.codeName - let dataset = outputDataset.referenceParent.code.codeName + let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName + let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName + let product = outputDataset.referenceParent.parentNode.config.codeName + let dataset = outputDataset.referenceParent.config.codeName key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset let event = { diff --git a/Root.js b/Root.js index 3a0dec4cd..f4f3910f3 100644 --- a/Root.js +++ b/Root.js @@ -112,7 +112,7 @@ } catch (err) { - console.log(logDisplace + "Root : [ERROR] initialize -> err = " + err.stack); + console.log(logDisplace + "Root : [ERROR] initialize -> err = " + err.stack); return; } } @@ -141,19 +141,19 @@ /* Here we will check if we need to load the configuration and code of the bot from a file or we will take that from the UI. */ if ( - processInstance.referenceParent.code.framework !== undefined && - ( processInstance.referenceParent.code.framework.name === 'Multi-Period-Market' || - processInstance.referenceParent.code.framework.name === 'Multi-Period-Daily' || - processInstance.referenceParent.code.framework.name === 'Multi-Period') - ) { - botConfig = processInstance.referenceParent.parentNode.code + processInstance.referenceParent.config.framework !== undefined && + (processInstance.referenceParent.config.framework.name === 'Multi-Period-Market' || + processInstance.referenceParent.config.framework.name === 'Multi-Period-Daily' || + processInstance.referenceParent.config.framework.name === 'Multi-Period') + ) { + botConfig = processInstance.referenceParent.parentNode.config botConfig.definedByUI = true bootingBot(processIndex) return } else { getBotConfigFromFile(); return - } + } function getBotConfigFromFile() { @@ -162,7 +162,7 @@ const FILE_STORAGE = require('./FileStorage.js'); let fileStorage = FILE_STORAGE.newFileStorage(); - let filePath = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName + '/bots/' + global.TASK_NODE.bot.code.repo + '/this.bot.config.json'; + let filePath = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName + '/bots/' + global.TASK_NODE.bot.config.repo + '/this.bot.config.json'; fileStorage.getTextFile(filePath, onFileReceived); @@ -171,13 +171,13 @@ if (err.result !== global.DEFAULT_OK_RESPONSE.result) { console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> err = " + JSON.stringify(err)); console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> filePath = " + filePath); - console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> dataMine = " + global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName); + console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> dataMine = " + global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName); return; } try { botConfig = JSON.parse(text); - botConfig.repo = global.TASK_NODE.bot.code.repo; + botConfig.repo = global.TASK_NODE.bot.config.repo; bootingBot(processIndex); } catch (err) { console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> err = " + JSON.stringify(err)); @@ -195,7 +195,7 @@ try { - botConfig.process = global.TASK_NODE.bot.processes[processIndex].referenceParent.code.codeName + botConfig.process = global.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName botConfig.debug = {}; botConfig.processNode = global.TASK_NODE.bot.processes[processIndex] @@ -204,15 +204,15 @@ botConfig.DELETE_QUEUE_SIZE = 10 // This number represents how many log files can be at the queue at any point in time, which means how many logs are not still deleted. /* Simplifying the access to basic info */ - botConfig.dataMine = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName + botConfig.dataMine = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName botConfig.exchange = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode.name botConfig.exchangeNode = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode botConfig.market = { - baseAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent.code.codeName, - quotedAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent.code.codeName + baseAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent.config.codeName, + quotedAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent.config.codeName } - botConfig.uiStartDate = global.TASK_NODE.bot.code.startDate - botConfig.code = global.TASK_NODE.bot.code + botConfig.uiStartDate = global.TASK_NODE.bot.config.startDate + botConfig.config = global.TASK_NODE.bot.config /* This stuff is still hardcoded and unresolved. */ botConfig.version = { @@ -222,10 +222,10 @@ botConfig.dataSetVersion = "dataSet.V1" /* Loop Counter */ - botConfig.loopCounter = 0; + botConfig.loopCounter = 0; /* File Path Root */ - botConfig.filePathRoot = botConfig.exchange + "/" + botConfig.market.baseAsset + "-" + botConfig.market.quotedAsset + "/" + botConfig.dataMine + "/" + botConfig.codeName ; + botConfig.filePathRoot = botConfig.exchange + "/" + botConfig.market.baseAsset + "-" + botConfig.market.quotedAsset + "/" + botConfig.dataMine + "/" + botConfig.codeName; /* Process Key */ botConfig.processKey = global.TASK_NODE.bot.processes[processIndex].name + '-' + global.TASK_NODE.bot.processes[processIndex].type + '-' + global.TASK_NODE.bot.processes[processIndex].id @@ -243,14 +243,14 @@ if (botConfig.processNode.marketReference.keyInstance.referenceParent !== undefined) { let key = botConfig.processNode.marketReference.keyInstance.referenceParent - process.env.KEY = key.code.codeName - process.env.SECRET = key.code.secret + process.env.KEY = key.config.codeName + process.env.SECRET = key.config.secret } } } } - let processConfig = global.TASK_NODE.bot.processes[processIndex].referenceParent.code + let processConfig = global.TASK_NODE.bot.processes[processIndex].referenceParent.config if (processConfig.framework !== undefined) { if (processConfig.framework.name === "Multi-Period-Daily" || processConfig.framework.name === "Multi-Period-Market" || processConfig.framework.name === "Multi-Period") { @@ -283,7 +283,7 @@ if (processConfig.startMode === undefined) { // Default botConfig.hasTheBotJustStarted = true; - + switch (botConfig.type) { case 'Sensor Bot': { runSensorBot(botConfig, processConfig); @@ -475,7 +475,7 @@ pBotConfig.loopCounter = 0; let botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process; - + if (err.result === global.DEFAULT_OK_RESPONSE.result) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully."); diff --git a/Session.js b/Session.js index 9ae1f99f4..3e84808ed 100644 --- a/Session.js +++ b/Session.js @@ -5,7 +5,7 @@ const ONE_YEAR_IN_MILISECONDS = 365 * 24 * 60 * 60 * 1000 let thisObject = { - initialize: initialize + initialize: initialize } return thisObject; @@ -17,19 +17,19 @@ let telegramAPI /* Initialize this info so that everything is logged propeerly */ - + bot.SESSION = { name: bot.processNode.session.name, - id: bot.processNode.session.id + id: bot.processNode.session.id } /* Set the folderName for early logging */ - if (bot.processNode.session.code.folderName === undefined) { + if (bot.processNode.session.config.folderName === undefined) { bot.SESSION.folderName = bot.SESSION.id } else { - bot.SESSION.folderName = bot.processNode.session.code.folderName + "-" + bot.SESSION.id + bot.SESSION.folderName = bot.processNode.session.config.folderName + "-" + bot.SESSION.id } - + /* Check if there is a session */ if (bot.processNode.session === undefined) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> Cannot run without a Session."); @@ -40,7 +40,7 @@ /* Listen to event to start or stop the session. */ bot.sessionKey = bot.processNode.session.name + '-' + bot.processNode.session.type + '-' + bot.processNode.session.id global.SESSION_MAP.set(bot.sessionKey, bot.sessionKey) - + global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, runSession) global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, stopSession) @@ -53,18 +53,29 @@ if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { return } // This happens when the UI is reloaded, the session was running and tries to run it again. /* We are going to run the Definition comming at the event. */ + bot.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema) bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem) + bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine) bot.SESSION = JSON.parse(message.event.session) bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter) + bot.RESUME = message.event.resume + + /* Setup the APP_SCHEMA_MAP based on the APP_SCHEMA_ARRAY */ + bot.APP_SCHEMA_MAP = new Map() + for (let i = 0; i < bot.APP_SCHEMA_ARRAY.length; i++) { + let nodeDefinition = bot.APP_SCHEMA_ARRAY[i] + let key = nodeDefinition.type + bot.APP_SCHEMA_MAP.set(key, nodeDefinition) + } /* Set the folderName for logging, reports, context and data output */ - let code - if (bot.SESSION.code !== undefined) { - code = bot.SESSION.code - if (code.folderName === undefined) { + let config + if (bot.SESSION.config !== undefined) { + config = bot.SESSION.config + if (config.folderName === undefined) { bot.SESSION.folderName = bot.SESSION.id } else { - bot.SESSION.folderName = code.folderName + "-" + bot.SESSION.id + bot.SESSION.folderName = config.folderName + "-" + bot.SESSION.id } } @@ -133,7 +144,7 @@ } bot.resumeExecution = false; bot.hasTheBotJustStarted = true - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) + bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) return true } @@ -148,7 +159,7 @@ bot.startMode = "Live" checkDatetimes() bot.resumeExecution = false; - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) + bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) bot.hasTheBotJustStarted = true pProcessConfig.liveWaitTime = getTimeFrameFromLabel(bot.VALUES_TO_USE.timeFrame) return true @@ -165,23 +176,23 @@ bot.startMode = "Live" checkDatetimes() bot.resumeExecution = false; - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) + bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) bot.hasTheBotJustStarted = true /* Reduce the balance */ let balancePercentage = 1 // This is the default value - - if (bot.SESSION.code.balancePercentage !== undefined) { - balancePercentage = bot.SESSION.code.balancePercentage + + if (bot.SESSION.config.balancePercentage !== undefined) { + balancePercentage = bot.SESSION.config.balancePercentage } bot.VALUES_TO_USE.initialBalanceA = bot.VALUES_TO_USE.initialBalanceA * balancePercentage / 100 bot.VALUES_TO_USE.initialBalanceB = bot.VALUES_TO_USE.initialBalanceB * balancePercentage / 100 - + bot.VALUES_TO_USE.minimumBalanceA = bot.VALUES_TO_USE.minimumBalanceA * balancePercentage / 100 bot.VALUES_TO_USE.minimumBalanceB = bot.VALUES_TO_USE.minimumBalanceB * balancePercentage / 100 - + bot.VALUES_TO_USE.maximumBalanceA = bot.VALUES_TO_USE.maximumBalanceA * balancePercentage / 100 bot.VALUES_TO_USE.maximumBalanceB = bot.VALUES_TO_USE.maximumBalanceB * balancePercentage / 100 @@ -192,11 +203,11 @@ function startPaperTrading(message) { bot.startMode = "Backtest" - + checkDatetimes() bot.resumeExecution = false; bot.hasTheBotJustStarted = true - bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) + bot.multiPeriodProcessDatetime = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) pProcessConfig.normalWaitTime = getTimeFrameFromLabel(bot.VALUES_TO_USE.timeFrame) return true } @@ -270,7 +281,7 @@ } } - let tradingSystem = bot.TRADING_SYSTEM + let tradingSystem = bot.TRADING_SYSTEM if (tradingSystem !== undefined) { @@ -284,38 +295,38 @@ if (bot.SESSION.parameters.baseAsset.referenceParent !== undefined) { if (bot.SESSION.parameters.baseAsset.referenceParent.referenceParent !== undefined) { - let code = bot.SESSION.parameters.baseAsset.referenceParent.referenceParent.code + let config = bot.SESSION.parameters.baseAsset.referenceParent.referenceParent.config - if (code.codeName !== undefined) { - bot.VALUES_TO_USE.baseAsset = code.codeName; + if (config.codeName !== undefined) { + bot.VALUES_TO_USE.baseAsset = config.codeName; } - code = bot.SESSION.parameters.baseAsset.code + config = bot.SESSION.parameters.baseAsset.config if (bot.VALUES_TO_USE.baseAsset === bot.market.baseAsset) { - if (code.initialBalance !== undefined) { - bot.VALUES_TO_USE.initialBalanceA = code.initialBalance; + if (config.initialBalance !== undefined) { + bot.VALUES_TO_USE.initialBalanceA = config.initialBalance; bot.VALUES_TO_USE.initialBalanceB = 0 } - if (code.minimumBalance !== undefined) { - bot.VALUES_TO_USE.minimumBalanceA = code.minimumBalance; + if (config.minimumBalance !== undefined) { + bot.VALUES_TO_USE.minimumBalanceA = config.minimumBalance; bot.VALUES_TO_USE.minimumBalanceB = 0 } - if (code.maximumBalance !== undefined) { - bot.VALUES_TO_USE.maximumBalanceA = code.maximumBalance; + if (config.maximumBalance !== undefined) { + bot.VALUES_TO_USE.maximumBalanceA = config.maximumBalance; bot.VALUES_TO_USE.maximumBalanceB = 0 } } else { - if (code.initialBalance !== undefined) { - bot.VALUES_TO_USE.initialBalanceB = code.initialBalance; + if (config.initialBalance !== undefined) { + bot.VALUES_TO_USE.initialBalanceB = config.initialBalance; bot.VALUES_TO_USE.initialBalanceA = 0 } - if (code.minimumBalance !== undefined) { - bot.VALUES_TO_USE.minimumBalanceB = code.minimumBalance; + if (config.minimumBalance !== undefined) { + bot.VALUES_TO_USE.minimumBalanceB = config.minimumBalance; bot.VALUES_TO_USE.minimumBalanceA = 0 } - if (code.maximumBalance !== undefined) { - bot.VALUES_TO_USE.maximumBalanceB = code.maximumBalance; + if (config.maximumBalance !== undefined) { + bot.VALUES_TO_USE.maximumBalanceB = config.maximumBalance; bot.VALUES_TO_USE.maximumBalanceA = 0 } } @@ -347,10 +358,10 @@ if (bot.SESSION.parameters.quotedAsset.referenceParent !== undefined) { if (bot.SESSION.parameters.quotedAsset.referenceParent.referenceParent !== undefined) { - let code = bot.SESSION.parameters.quotedAsset.referenceParent.referenceParent.code + let config = bot.SESSION.parameters.quotedAsset.referenceParent.referenceParent.config - if (code.codeName !== undefined) { - bot.VALUES_TO_USE.quotedAsset = code.codeName; + if (config.codeName !== undefined) { + bot.VALUES_TO_USE.quotedAsset = config.codeName; } } } @@ -359,59 +370,59 @@ /* Time Frame */ if (bot.SESSION.parameters.timeFrame !== undefined) { - bot.VALUES_TO_USE.timeFrame = bot.SESSION.parameters.timeFrame.code.value + bot.VALUES_TO_USE.timeFrame = bot.SESSION.parameters.timeFrame.config.value } /* Slippage */ if (bot.SESSION.parameters.slippage !== undefined) { - if (bot.SESSION.parameters.slippage.code !== undefined) { + if (bot.SESSION.parameters.slippage.config !== undefined) { - let code = bot.SESSION.parameters.slippage.code + let config = bot.SESSION.parameters.slippage.config - if (code.positionRate !== undefined) { - bot.VALUES_TO_USE.slippage.positionRate = code.positionRate + if (config.positionRate !== undefined) { + bot.VALUES_TO_USE.slippage.positionRate = config.positionRate } - if (code.stopLoss !== undefined) { - bot.VALUES_TO_USE.slippage.stopLoss = code.stopLoss + if (config.stopLoss !== undefined) { + bot.VALUES_TO_USE.slippage.stopLoss = config.stopLoss } - if (code.takeProfit !== undefined) { - bot.VALUES_TO_USE.slippage.takeProfit = code.takeProfit + if (config.takeProfit !== undefined) { + bot.VALUES_TO_USE.slippage.takeProfit = config.takeProfit } } } /* Fee Structure */ if (bot.SESSION.parameters.feeStructure !== undefined) { - if (bot.SESSION.parameters.feeStructure.code !== undefined) { + if (bot.SESSION.parameters.feeStructure.config !== undefined) { - let code = bot.SESSION.parameters.feeStructure.code + let config = bot.SESSION.parameters.feeStructure.config - if (code.maker !== undefined) { - bot.VALUES_TO_USE.feeStructure.maker = code.maker + if (config.maker !== undefined) { + bot.VALUES_TO_USE.feeStructure.maker = config.maker } - if (code.taker !== undefined) { - bot.VALUES_TO_USE.feeStructure.taker = code.taker + if (config.taker !== undefined) { + bot.VALUES_TO_USE.feeStructure.taker = config.taker } } } /* Time Range */ if (bot.SESSION.parameters.timeRange !== undefined) { - if (bot.SESSION.parameters.timeRange.code !== undefined) { + if (bot.SESSION.parameters.timeRange.config !== undefined) { - let code = bot.SESSION.parameters.timeRange.code - if (code.initialDatetime !== undefined) { - if (isNaN(Date.parse(code.initialDatetime)) === true) { + let config = bot.SESSION.parameters.timeRange.config + if (config.initialDatetime !== undefined) { + if (isNaN(Date.parse(config.initialDatetime)) === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> runSession -> setValuesToUse -> Cannot use initialDatatime provided at Session Parameters because it is not a valid Date."); } else { - bot.VALUES_TO_USE.timeRange.initialDatetime = new Date(code.initialDatetime) + bot.VALUES_TO_USE.timeRange.initialDatetime = new Date(config.initialDatetime) } } - if (code.finalDatetime !== undefined) { - if (isNaN(Date.parse(code.finalDatetime)) === true) { + if (config.finalDatetime !== undefined) { + if (isNaN(Date.parse(config.finalDatetime)) === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> runSession -> setValuesToUse -> Cannot use finalDatetime provided at Session Parameters because it is not a valid Date."); } else { - bot.VALUES_TO_USE.timeRange.finalDatetime = new Date(code.finalDatetime) + bot.VALUES_TO_USE.timeRange.finalDatetime = new Date(config.finalDatetime) } } } @@ -428,8 +439,8 @@ for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { let socialBot = bot.SESSION.socialBots.bots[i] if (socialBot.type === "Telegram Bot") { - let code = socialBot.code - socialBot.botInstance = initializeTelegramBot(code.botToken, code.chatId) + let config = socialBot.config + socialBot.botInstance = initializeTelegramBot(config.botToken, config.chatId) } } } @@ -439,16 +450,16 @@ for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { let socialBot = bot.SESSION.socialBots.bots[i] try { - let code = announcement.code - + let config = announcement.config + if (socialBot.type === "Telegram Bot") { if (announcement.formulaValue !== undefined) { socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, announcement.formulaValue).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err)) } else { - socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, code.text).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err)) + socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, config.text).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err)) } } - + } catch (err) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> err = " + err.stack); } @@ -461,13 +472,13 @@ } function finalizeSocialBots() { - if (bot.SESSION.socialBots === undefined) {return} + if (bot.SESSION.socialBots === undefined) { return } if (bot.SESSION.socialBots.bots !== undefined) { for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) { let socialBot = bot.SESSION.socialBots.bots[i] if (socialBot.type === "Telegram Bot") { - let code = socialBot.code - socialBot.botInstance = finalizeTelegramBot(code.chatId) + let config = socialBot.config + socialBot.botInstance = finalizeTelegramBot(config.chatId) } } } @@ -488,8 +499,8 @@ telegramAPI = new Telegram(botToken) - const messge = bot.SESSION.type + " '" + bot.SESSION.name + "' was started with an initial balance of " + " " + bot.VALUES_TO_USE.initialBalanceA + " " + bot.VALUES_TO_USE.baseAsset + "." - telegramAPI.sendMessage(chatId, messge ).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeTelegramBot -> Telegram API error -> err = " + err)) + const messge = bot.SESSION.type + " '" + bot.SESSION.name + "' was started with an initial balance of " + " " + bot.VALUES_TO_USE.initialBalanceA + " " + bot.VALUES_TO_USE.baseAsset + "." + telegramAPI.sendMessage(chatId, messge).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeTelegramBot -> Telegram API error -> err = " + err)) let botInstance = { telegramBot: telegramBot, @@ -504,10 +515,10 @@ } function finalizeTelegramBot(chatId) { - const messge = bot.SESSION.type + " '" + bot.SESSION.name + "' was stopped." + const messge = bot.SESSION.type + " '" + bot.SESSION.name + "' was stopped." telegramAPI.sendMessage(chatId, messge).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initialize -> initializeTelegramBot -> Telegram API error -> err = " + err)) } - + } catch (err) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); diff --git a/StatusReport.js b/StatusReport.js index 0ae6bc288..b9865452e 100644 --- a/StatusReport.js +++ b/StatusReport.js @@ -23,7 +23,7 @@ status: undefined }; - let statusDependencyNode; + let statusDependencyNode; /* Utilities needed. */ @@ -32,7 +32,7 @@ /* Storage account to be used here. */ const FILE_STORAGE = require('./FileStorage.js'); - let fileStorage + let fileStorage let sessionPath = '' @@ -59,7 +59,7 @@ return } - if (statusDependencyNode.referenceParent.parentNode.code.codeName === undefined) { + if (statusDependencyNode.referenceParent.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Process Definition witn no codeName defined. Process Definition = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -71,7 +71,7 @@ return } - if (statusDependencyNode.referenceParent.parentNode.parentNode.code.codeName === undefined) { + if (statusDependencyNode.referenceParent.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Bot witn no codeName defined. Bot = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return @@ -83,22 +83,22 @@ return } - if (statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { + if (statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) { logger.write(MODULE_NAME, "[ERROR] initialize -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(statusDependencyNode.referenceParent.parentNode.parentNode.parentNode)); callBackFunction(global.DEFAULT_FAIL_RESPONSE); return } /* Simplifying the access to basic info */ - statusDependencyNode.bot = statusDependencyNode.referenceParent.parentNode.parentNode.code.codeName - statusDependencyNode.process = statusDependencyNode.referenceParent.parentNode.code.codeName + statusDependencyNode.bot = statusDependencyNode.referenceParent.parentNode.parentNode.config.codeName + statusDependencyNode.process = statusDependencyNode.referenceParent.parentNode.config.codeName statusDependencyNode.bottype = statusDependencyNode.referenceParent.parentNode.parentNode.type - statusDependencyNode.dataMine = statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.code.codeName + statusDependencyNode.dataMine = statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.config.codeName /* We retrieve the report main utility */ - if (statusDependencyNode.code !== undefined) { - if (statusDependencyNode.code.mainUtility !== undefined) { - thisObject.mainUtility = statusDependencyNode.code.mainUtility + if (statusDependencyNode.config !== undefined) { + if (statusDependencyNode.config.mainUtility !== undefined) { + thisObject.mainUtility = statusDependencyNode.config.mainUtility } } @@ -119,13 +119,13 @@ for (let i = 0; i < network.networkNodes.length; i++) { let networkNode = network.networkNodes[i] - + if (checkThisBranch(networkNode.dataMining) === true) { return } - if (checkThisBranch(networkNode.testingEnvironment) === true) {return} + if (checkThisBranch(networkNode.testingEnvironment) === true) { return } if (checkThisBranch(networkNode.productionEnvironment) === true) { return } function checkThisBranch(branch) { - if (branch === undefined) {return} + if (branch === undefined) { return } for (let j = 0; j < branch.exchangeTasks.length; j++) { let exchangeTasks = branch.exchangeTasks[j] for (let k = 0; k < exchangeTasks.taskManagers.length; k++) { @@ -156,9 +156,9 @@ /* We found where the task that runs the process definition this status report depends on and where it is located on the network. */ thisObject.networkNode = networkNode - if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.code.host + ' -> port = ' + networkNode.code.webPort + '.'); } + if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); } - fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.code.host, networkNode.code.webPort); + fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort); callBackFunction(global.DEFAULT_OK_RESPONSE); return true } @@ -183,7 +183,7 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -211,9 +211,9 @@ let rootPath = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + statusDependencyNode.dataMine + "/" + statusDependencyNode.bot - filePath = rootPath + "/Reports/" + sessionPath + statusDependencyNode.process ; + filePath = rootPath + "/Reports/" + sessionPath + statusDependencyNode.process; } else { - filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process ; + filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process; } filePath += '/' + fileName @@ -236,10 +236,10 @@ function onFileReceived(err, text) { - if ( err.result === global.CUSTOM_FAIL_RESPONSE.result && (err.message === 'Folder does not exist.' || err.message === 'File does not exist.') + if (err.result === global.CUSTOM_FAIL_RESPONSE.result && (err.message === 'Folder does not exist.' || err.message === 'File does not exist.') || err.code === "The specified key does not exist.") { - logger.write(MODULE_NAME, "[INFO] load -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[INFO] load -> onFileReceived -> err = " + err.message); /* In this case we can assume that this is the first execution ever of this bot.*/ @@ -255,7 +255,7 @@ } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] load -> onFileReceived -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] load -> onFileReceived -> err = " + err.message); callBackFunction(err); return; } @@ -290,7 +290,7 @@ } } catch (err) { - logger.write(MODULE_NAME, "[ERROR] load -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] load -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } @@ -328,7 +328,7 @@ if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] save -> onFileCreated -> Entering function."); } if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - logger.write(MODULE_NAME, "[ERROR] save -> onFileCreated -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] save -> onFileCreated -> err = " + err.stack); callBackFunction(err); return; } @@ -348,7 +348,7 @@ } catch (err) { - logger.write(MODULE_NAME, "[ERROR] save -> err = "+ err.stack); + logger.write(MODULE_NAME, "[ERROR] save -> err = " + err.stack); callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } diff --git a/Trading/TradingBot.js b/Trading/TradingBot.js new file mode 100644 index 000000000..37c791fc4 --- /dev/null +++ b/Trading/TradingBot.js @@ -0,0 +1,998 @@ +exports.newTradingBot = function newTradingBot(bot, logger, UTILITIES, FILE_STORAGE) { + const FULL_LOG = true + const LOG_FILE_CONTENT = false + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000 + + const MODULE_NAME = 'Trading Bot' + + let thisObject = { + initialize: initialize, + finalize: finalize, + start: start + } + + let utilities = UTILITIES.newCloudUtilities(logger) + let fileStorage = FILE_STORAGE.newFileStorage(logger) + + const COMMONS = require('../Commons.js') + let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE) + + let exchangeAPI + + return thisObject + + function finalize() { + thisObject = undefined + utilities = undefined + fileStorage = undefined + commons = undefined + } + + function initialize(pExchangeAPI, callBackFunction) { + try { + logger.fileName = MODULE_NAME + logger.initialize() + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] initialize -> Entering function.') } + + exchangeAPI = pExchangeAPI + callBackFunction(global.DEFAULT_OK_RESPONSE) + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] initialize -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function start(multiPeriodDataFiles, timeFrame, timeFrameLabel, currentDay, variable, callBackFunction) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Entering function.') } + + let chart = {} + let mainDependency = {} + + /* The first phase here is about checking that we have everything we need at the definition level. */ + let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } + + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + + /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + let timeFrameLabel = marketFilesPeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) + let products = {} + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Inflating Data Files for timeFrame = ' + timeFrameLabel) } + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + + let propertyName = 'at' + timeFrameLabel.replace('-', '') + chart[propertyName] = products + } + } + + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + let timeFrameLabel = global.dailyFilePeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) + let products = {} + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Inflating Data Files for timeFrame = ' + timeFrameLabel) } + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + + let propertyName = 'at' + timeFrameLabel.replace('-', '') + chart[propertyName] = products + } + } + + /* Single Files */ + + let dataFiles = multiPeriodDataFiles.get('Single Files') + let products = {} + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> Inflating Data Files from Single Files.') } + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) + + let propertyName = 'atAnyTimeFrame' + chart[propertyName] = products + } + + /* Simulation */ + + const TRADING_SIMULATION = require('./TradingSimulation.js') + let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES) + + let market = bot.market + + const SIMULATED_RECORDS_FOLDER_NAME = 'Trading-Simulation' + const CONDITIONS_FOLDER_NAME = 'Simulation-Conditions' + const STRATEGIES_FOLDER_NAME = 'Simulation-Strategies' + const TRADES_FOLDER_NAME = 'Simulation-Trades' + const SNAPSHOTS_FOLDER_NAME = 'Snapshots' + + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000 + + let recordsArray = [] + let conditionsArray = [] + let strategiesArray = [] + let positionsArray = [] + + let snapshotHeaders + let triggerOnSnapshot + let takePositionSnapshot + + let tradingSystem = {} + + if (bot.RESUME === true) { + readFiles() + } else { + runSimulation() + } + + function readFiles() { + + if (timeFrame > global.dailyFilePeriods[0][0]) { + readMarketFiles(runSimulation) + } else { + readDailyFiles(runSimulation) + } + } + + function readMarketFiles(callBack) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> Entering function.') } + + readRecordsFile() + + function readRecordsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readRecordsFile -> Entering function.') } + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SIMULATED_RECORDS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readRecordsFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + recordsArray = [] + readConditionsFile() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readRecordsFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readRecordsFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readRecordsFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + recordsArray = JSON.parse(text) + readConditionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readRecordsFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readRecordsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function readConditionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readConditionsFile -> Entering function.') } + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + CONDITIONS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readConditionsFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + conditionsArray = [] + readStrategiesFile() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readConditionsFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readConditionsFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readConditionsFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + conditionsArray = JSON.parse(text) + conditionsArray = conditionsArray[1] // conditionsArray[0] = a snapshot of the trading system. + readStrategiesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readConditionsFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readConditionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function readStrategiesFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readStrategiesFile -> Entering function.') } + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + STRATEGIES_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readStrategiesFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + strategiesArray = [] + readPositionsFile() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readStrategiesFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readStrategiesFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readStrategiesFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + strategiesArray = JSON.parse(text) + readPositionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readStrategiesFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readStrategiesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function readPositionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readPositionsFile -> Entering function.') } + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + TRADES_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readMarketFiles -> readPositionsFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + positionsArray = [] + callBack() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readPositionsFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readPositionsFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readPositionsFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + positionsArray = JSON.parse(text) + callBack() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readPositionsFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readPositionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function readDailyFiles(callBack) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> Entering function.') } + + readRecordsFile() + + function readRecordsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readRecordsFile -> Entering function.') } + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SIMULATED_RECORDS_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readRecordsFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + recordsArray = [] + readConditionsFile() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readRecordsFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readRecordsFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readRecordsFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + recordsArray = JSON.parse(text) + readConditionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readRecordsFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readRecordsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function readConditionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readConditionsFile -> Entering function.') } + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readConditionsFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + conditionsArray = [] + readStrategiesFile() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readConditionsFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readConditionsFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readConditionsFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + conditionsArray = JSON.parse(text) + conditionsArray = conditionsArray[1] // conditionsArray[0] = a snapshot of the trading system. + readStrategiesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readConditionsFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readConditionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function readStrategiesFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readStrategiesFile -> Entering function.') } + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + STRATEGIES_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readStrategiesFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + strategiesArray = [] + readPositionsFile() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readStrategiesFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readStrategiesFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readStrategiesFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + strategiesArray = JSON.parse(text) + readPositionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readStrategiesFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readStrategiesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function readPositionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readPositionsFile -> Entering function.') } + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + TRADES_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileRead, true) + + function onFileRead(err, text) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> readDailyFiles -> readPositionsFile -> onFileRead -> Entering function.') } + + if (err.message === 'File does not exist.') { + positionsArray = [] + callBack() + return + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readPositionsFile -> onFileRead -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readPositionsFile -> onFileRead -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readPositionsFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + positionsArray = JSON.parse(text) + callBack() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readPositionsFile -> onFileRead -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> readDailyFiles -> readPositionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function runSimulation() { + tradingSimulation.runSimulation( + chart, + dataDependencies, + timeFrame, + timeFrameLabel, + currentDay, + variable, + exchangeAPI, + recordsArray, + conditionsArray, + strategiesArray, + positionsArray, + writeFiles, + callBackFunction) + } + + function writeFiles(pTradingSystem, pSnapshotHeaders, pTriggerOnSnapshot, pTakePositionSnapshot) { + tradingSystem = pTradingSystem + + snapshotHeaders = pSnapshotHeaders + triggerOnSnapshot = pTriggerOnSnapshot + takePositionSnapshot = pTakePositionSnapshot + + if (timeFrame > global.dailyFilePeriods[0][0]) { + writeMarketFiles() + } else { + writeDailyFiles() + } + } + + function writeMarketFiles() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> Entering function.') } + + writeRecordsFile() + + function writeRecordsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeRecordsFile -> Entering function.') } + + let fileContent = JSON.stringify(recordsArray) + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SIMULATED_RECORDS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeConditionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeRecordsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeConditionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeConditionsFile -> Entering function.') } + + let fileContent = JSON.stringify(conditionsArray) + + fileContent = '[' + JSON.stringify(tradingSystem) + ',' + fileContent + ']' + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + CONDITIONS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeStrategiesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeConditionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeStrategiesFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeStrategiesFile -> Entering function.') } + + let fileContent = JSON.stringify(strategiesArray) + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + STRATEGIES_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writePositionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writePositionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writePositionsFile -> Entering function.') } + + let fileContent = JSON.stringify(positionsArray) + + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + TRADES_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writePositionsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writePositionsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writePositionsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writePositionsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writePositionsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeSnapshotFiles() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writePositionsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writePositionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeSnapshotFiles() { + writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish) + + function onFinish() { + writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction) + } + } + + function writeSnapshotFile(snapshotArray, pFileName, callBack) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeSnapshotFile -> Entering function.') } + + let fileRecordCounter = 0 + + let fileContent = '' + let separator = '\r\n' + + parseRecord(snapshotHeaders) + + for (let i = 0; i < snapshotArray.length; i++) { + let record = snapshotArray[i] + parseRecord(record) + fileRecordCounter++ + } + + function parseRecord(record) { + for (let j = 0; j < record.length; j++) { + let property = record[j] + + fileContent = fileContent + '' + property + if (j !== record.length - 1) { + fileContent = fileContent + ',' + } + } + fileContent = fileContent + separator + } + + fileContent = '' + fileContent + '' + + let fileName = pFileName + '.csv' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SNAPSHOTS_FOLDER_NAME + '/' + 'Multi-Period-Market' + '/' + timeFrameLabel + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + callBack(global.DEFAULT_OK_RESPONSE) + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeMarketFiles -> writeSnapshotFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function writeDailyFiles() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> Entering function.') } + + writeRecordsFile() + + function writeRecordsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeRecordsFile -> Entering function.') } + + let fileContent = JSON.stringify(recordsArray) + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SIMULATED_RECORDS_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeConditionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeRecordsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeConditionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeConditionsFile -> Entering function.') } + + let fileContent = JSON.stringify(conditionsArray) + + fileContent = '[' + JSON.stringify(tradingSystem) + ',' + fileContent + ']' + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + CONDITIONS_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeStrategiesFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeConditionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeStrategiesFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeStrategiesFile -> Entering function.') } + + let fileContent = JSON.stringify(strategiesArray) + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + STRATEGIES_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writePositionsFile() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writePositionsFile() { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writePositionsFile -> Entering function.') } + + let fileContent = JSON.stringify(positionsArray) + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = 'Data.json' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + TRADES_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writePositionsFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writePositionsFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writePositionsFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writePositionsFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writePositionsFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + writeSnapshotFiles() + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writePositionsFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writePositionsFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + + function writeSnapshotFiles() { + writeSnapshotFile(triggerOnSnapshot, 'Trigger-On', onFinish) + + function onFinish() { + writeSnapshotFile(takePositionSnapshot, 'Take-Position', callBackFunction) + } + } + + function writeSnapshotFile(snapshotArray, pFileName, callBack) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeSnapshotFile -> Entering function.') } + + let fileRecordCounter = 0 + + let fileContent = '' + let separator = '\r\n' + + parseRecord(snapshotHeaders) + + for (let i = 0; i < snapshotArray.length; i++) { + let record = snapshotArray[i] + parseRecord(record) + fileRecordCounter++ + } + + function parseRecord(record) { + for (let j = 0; j < record.length; j++) { + let property = record[j] + + fileContent = fileContent + '' + property + if (j !== record.length - 1) { + fileContent = fileContent + ',' + } + } + fileContent = fileContent + separator + } + + fileContent = '' + fileContent + '' + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2) + let fileName = pFileName + '.csv' + let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + SNAPSHOTS_FOLDER_NAME + '/' + 'Multi-Period-Daily' + '/' + timeFrameLabel + '/' + dateForPath + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated) + + function onFileCreated(err) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> Entering function.') } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, '[INFO] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> fileContent = ' + fileContent) } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> filePath = ' + filePath) + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> market = ' + market.baseAsset + '_' + market.quotedAsset) + + callBackFunction(err) + return + } + + callBack(global.DEFAULT_OK_RESPONSE) + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> onFileCreated -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> writeDailyFiles -> writeSnapshotFile -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] start -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } +} + diff --git a/Trading/TradingEngine.js b/Trading/TradingEngine.js new file mode 100644 index 000000000..30207da99 --- /dev/null +++ b/Trading/TradingEngine.js @@ -0,0 +1,69 @@ +exports.newTradingEngine = function newTradingEngine(bot, logger) { + + const MODULE_NAME = 'Trading Engine' + + let thisObject = { + initialize: initialize, + finalize: finalize + } + + return thisObject + + function initialize() { + /* Here we will go through all the nodes in the Trading Engine hiriarchy and apply the initial value to the value property when needed */ + + if (bot.TRADING_ENGINE.isInitialized !== true || bot.RESUME === false) { + bot.TRADING_ENGINE.isInitialized = true + initializeNode(bot.TRADING_ENGINE) + } + } + + function finalize() { + + } + + function initializeNode(node) { + if (node === undefined) { return } + + /* Here we initialize the node value */ + if (node.config !== undefined) { + if (node.config.initialValue !== undefined) { + node.value = node.config.initialValue + } + } + + /* Now we go down through all this node children */ + let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + initializeNode(node[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + object[property.name] = [] + for (let m = 0; m < nodePropertyArray.length; m++) { + initializeNode(nodePropertyArray[m]) + } + } + break + } + } + } + } + } +} + diff --git a/Trading/TradingSimulation.js b/Trading/TradingSimulation.js new file mode 100644 index 000000000..0f78d4c25 --- /dev/null +++ b/Trading/TradingSimulation.js @@ -0,0 +1,2546 @@ +exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) { + const FULL_LOG = true + const LOG_FILE_CONTENT = false + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000 + const MODULE_NAME = 'Trading Simulation -> ' + bot.SESSION.name + + const GMT_SECONDS = ':00.000 GMT+0000' + + let thisObject = { + finalize: finalize, + runSimulation: runSimulation + } + + let utilities = UTILITIES.newCloudUtilities(bot, logger) + + return thisObject + + function finalize() { + thisObject = undefined + } + + function runSimulation( + chart, + dataDependencies, + timeFrame, + timeFrameLabel, + currentDay, + variable, + exchangeAPI, + recordsArray, + conditionsArray, + strategiesArray, + positionsArray, + callback, + callBackFunction) { + try { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> Entering function.') } + let processingDailyFiles + if (timeFrame > global.dailyFilePeriods[0][0]) { + processingDailyFiles = false + } else { + processingDailyFiles = true + } + + /* Snapshots of Trigger On and Take Positions */ + let snapshots = { + headers: undefined, + triggerOn: [], + takePosition: [], + lastTriggerOn: undefined, + lastTakePosition: undefined + } + + let tradingSystem = bot.TRADING_SYSTEM + let tradingEngine = bot.TRADING_ENGINE + let sessionParameters = bot.SESSION.parameters + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> sessionParameters.timeRange.config.initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> sessionParameters.timeRange.config.finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) } + + let timerToCloseStage = 0 + + /* Stop Loss Management */ + + const MIN_STOP_LOSS_VALUE = 0.0000000001 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result. + const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER + + /* Take Profit Management */ + + const MIN_TAKE_PROFIT_VALUE = 0.0000000001 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result. + const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER + + /* Variables for this executioin only */ + let takePositionNow = false + let closePositionNow = false + + /* In some cases we need to know if we are positioned at the last candle of the calendar day, for that we need these variables. */ + let lastInstantOfTheDay = 0 + if (currentDay) { + lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1 + } + + const TRADING_ENGINE_MODULE = require('./TradingEngine.js') + let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger) + tradingEngineModule.initialize() + + if (variable.isInitialized !== true || bot.RESUME === false) { + variable.isInitialized = true + + variable.episode = {} // An Episode represents each execution of the Simulation + variable.episode.count = {} // Everything in here means the total count since the begining of the episode. + variable.episode.stat = {} // Everything in here means the statistics calculated since the begining of the episode. + + variable.current = {} // Everything in here means that is happening right now. + variable.current.balance = {} // Balances at the current candle of the Simulation. + variable.current.distance = {} // Everything in here means the distance, measured in periods to whatever happened in the past, usually events. + variable.current.distance.toEvent = {} // Distance to Events that already happened. + variable.current.strategy = {} // Everything in here are variables related to the current strategy being executed. + variable.current.position = {} // Everything in here are variables related to the current position being executed. + + variable.current.balance.baseAsset = bot.VALUES_TO_USE.initialBalanceA + variable.current.balance.quotedAsset = bot.VALUES_TO_USE.initialBalanceB + variable.current.distance.toEvent.triggerOn = 0 + variable.current.distance.toEvent.triggerOff = 0 + variable.current.distance.toEvent.takePosition = 0 + variable.current.distance.toEvent.closePosition = 0 + + variable.last = {} // Everything in here means that it already happened. + variable.last.position = {} // Las values of variables at the last position. + + variable.last.position.profitLoss = 0 + variable.last.position.ROI = 0 + + variable.previous = {} // Everything in here means the the value that was current before a new current value was set. + variable.previous.balance = {} // Balances values previous to the current ones. + + variable.previous.balance.baseAsset = 0 + variable.previous.balance.quotedAsset = 0 + + variable.episode.parameters = {} // Parameters give you access to information received by the Simulation as parameters. + variable.episode.parameters.initial = {} // Everything in here means the status at the begining of the episode received as a parameter of the Sumulation. + variable.episode.parameters.minimum = {} // Everything in here means the minimum in the whole episode received as a parameter of the Sumulation. + variable.episode.parameters.maximum = {} // Everything in here means the maximum in the whole episode received as a parameter of the Sumulation. + variable.episode.parameters.initial.balance = {} + variable.episode.parameters.minimum.balance = {} + variable.episode.parameters.maximum.balance = {} + + variable.episode.parameters.baseAsset = bot.VALUES_TO_USE.baseAsset + variable.episode.parameters.quotedAsset = bot.VALUES_TO_USE.quotedAsset + variable.episode.parameters.marketBaseAsset = bot.market.baseAsset + variable.episode.parameters.marketQuotedAsset = bot.market.quotedAsset + variable.episode.parameters.timeFrame = timeFrame // The numeric value of the time frame, in milliseconds + variable.episode.parameters.timeFrameLabel = timeFrameLabel // The alpha-numeric value of the time frame + + variable.episode.parameters.initial.balance.baseAsset = bot.VALUES_TO_USE.initialBalanceA + variable.episode.parameters.minimum.balance.baseAsset = bot.VALUES_TO_USE.minimumBalanceA + variable.episode.parameters.maximum.balance.baseAsset = bot.VALUES_TO_USE.maximumBalanceA + + variable.episode.parameters.initial.balance.quotedAsset = bot.VALUES_TO_USE.initialBalanceB + variable.episode.parameters.minimum.balance.quotedAsset = bot.VALUES_TO_USE.minimumBalanceB + variable.episode.parameters.maximum.balance.quotedAsset = bot.VALUES_TO_USE.maximumBalanceB + + variable.episode.count.positions = 0 + variable.episode.count.fails = 0 + variable.episode.count.hits = 0 + variable.episode.count.periods = 0 + + variable.episode.stat.profitLoss = 0 + variable.episode.stat.hitRatio = 0 + variable.episode.stat.days = 0 + variable.episode.stat.ROI = 0 + variable.episode.stat.anualizedRateOfReturn = 0 + + inializeCurrentStrategy() + initializeCurrentPosition() + + variable.announcements = [] + } + + function inializeCurrentStrategy() { + + variable.current.strategy.name = '' + variable.current.strategy.index = -1 + variable.current.strategy.stage = 'No Stage' + variable.current.strategy.begin = 0 + variable.current.strategy.end = 0 + variable.current.strategy.status = 0 + variable.current.strategy.number = 0 + variable.current.strategy.beginRate = 0 + variable.current.strategy.endRate = 0 + variable.current.strategy.situationName = '' + + } + + function initializeCurrentPosition() { + + variable.current.position.begin = 0 + variable.current.position.end = 0 + variable.current.position.rate = 0 + variable.current.position.size = 0 + variable.current.position.status = 0 + variable.current.position.exitType = 0 + variable.current.position.beginRate = 0 + variable.current.position.endRate = 0 + variable.current.position.situationName = '' + + variable.current.position.stat = {} + variable.current.position.stat.ROI = 0 + variable.current.position.stat.days = 0 + + variable.current.position.count = {} + variable.current.position.count.periods = 0 + + variable.current.position.stopLoss = {} + variable.current.position.stopLoss.value = 0 + variable.current.position.stopLoss.phase = -1 + variable.current.position.stopLoss.stage = 'No Stage' + + variable.current.position.takeProfit = {} + variable.current.position.takeProfit.value = 0 + variable.current.position.takeProfit.phase = -1 + variable.current.position.takeProfit.stage = 'No Stage' + + } + + /* Main Array and Maps */ + let propertyName = 'at' + variable.episode.parameters.timeFrameLabel.replace('-', '') + let candles = chart[propertyName].candles + let currentChart = chart[propertyName] + + /* Last Candle */ + let lastCandle = candles[candles.length - 1] + + /* Main Simulation Loop: We go thourgh all the candles at this time period. */ + let currentCandleIndex + + /* For Loop Level heartbeat */ + let loopingDay + let previousLoopingDay + + initializeLoop() + + function initializeLoop() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initializeLoop -> Entering function.') } + + if (bot.RESUME === true && variable.last.candle !== undefined) { + /* Estimate the Initial Candle based on the last candle saved at variable */ + let firstBegin = candles[0].begin + let lastBegin = variable.last.candle.begin + let diff = lastBegin - firstBegin + let amount = diff / variable.episode.parameters.timeFrame + + currentCandleIndex = Math.trunc(amount) + 1 // Because we need to start from the next candle + if (currentCandleIndex < 0 || currentCandleIndex > candles.length - 1) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.') + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> currentCandleIndex = ' + currentCandleIndex) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount) + + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + return + } + } else { + /* Estimate Initial Candle based on the timeRage configured for the session. */ + let firstEnd = candles[0].end + let targetEnd = sessionParameters.timeRange.config.initialDatetime.valueOf() + let diff = targetEnd - firstEnd + let amount = diff / variable.episode.parameters.timeFrame + + currentCandleIndex = Math.trunc(amount) + if (currentCandleIndex < 0) { currentCandleIndex = 0 } + if (currentCandleIndex > candles.length - 1) { + /* + This will happen when the sessionParameters.timeRange.config.initialDatetime is beyond the last candle available, + meaning that the dataSet needs to be updated with more up-to-date data. + */ + currentCandleIndex = candles.length - 1 + } + } + + loop() + } + + function loop() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Entering function.') } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + currentCandleIndex) } + + /* We are going to take snapshots of the values of indicators at key moments of the sumulation. */ + + let snapshotKeys = new Map() + snapshotLoopHeaders = [] + snapshotDataRecord = [] + saveAsLastTriggerOnSnapshot = false + saveAsLastTakePositionSnapshot = false + addToSnapshots = false + + let announcementsToBeMade = [] + let candle = candles[currentCandleIndex] + + /* Not processing while out of user-defined time range */ + if (candle.end < sessionParameters.timeRange.config.initialDatetime.valueOf()) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') } + controlLoop() + return + } + if (candle.begin > sessionParameters.timeRange.config.finalDatetime.valueOf()) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') } + afterLoop() + return + } + + /* Here we update at the chart data structure the objects for each product representing where we are currently standing at the simulation loop */ + if (processingDailyFiles) { + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + let mapKey = dailyFilePeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', '') + let thisChart = chart[propertyName] + + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName + let currentElement = getElement(thisChart[pluralVariableName], candle, 'Daily' + '-' + mapKey + '-' + pluralVariableName) + thisChart[singularVariableName] = currentElement + } + } + } + + /* Finding the Current Element on Market Files */ + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + let mapKey = marketFilesPeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', '') + let thisChart = chart[propertyName] + + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName + let currentElement = getElement(thisChart[pluralVariableName], candle, 'Market' + '-' + mapKey + '-' + pluralVariableName) + thisChart[singularVariableName] = currentElement + } + } + + /* Finding the Current Element on Single Files */ + function isItInside(elementWithTimestamp, elementWithBeginEnd) { + if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) { + return true + } else { + return false + } + } + + let propertyName = 'atAnyTimeFrame' + let thisChart = chart[propertyName] + + for (let k = 0; k < dataDependencies.length; k++) { + let dataDependencyNode = dataDependencies[k] + if (dataDependencyNode.referenceParent.config.codeName !== 'Single-File') { continue } + let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName + let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName + let elementArray = thisChart[pluralVariableName] + let currentElement + if (elementArray !== undefined) { + currentElement = elementArray[elementArray.length - 1] + } + thisChart[singularVariableName] = currentElement + } + + /* While we are processing the previous day. */ + let positionedAtYesterday = false + if (currentDay) { + positionedAtYesterday = (candle.end < currentDay.valueOf()) + } + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toLocaleString()) } + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) } + + let ticker = { + bid: candle.close, + ask: candle.close, + last: candle.close + } + + /* We will produce a simulation level heartbeat in order to inform the user this is running. */ + loopingDay = new Date(Math.trunc(candle.begin / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) + if (loopingDay.valueOf() !== previousLoopingDay) { + let processingDate = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + currentCandleIndex + ' @ ' + processingDate) } + + /* Telling the world we are alive and doing well */ + let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime.valueOf()) + let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime.valueOf()) + + let currentDateString = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2) + let currentDate = new Date(loopingDay) + let percentage = global.getPercentage(fromDate, currentDate, lastDate) + bot.processHeartBeat(currentDateString, percentage) + + if (global.areEqualDates(currentDate, new Date()) === false) { + logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage) + } + } + previousLoopingDay = loopingDay.valueOf() + + variable.episode.count.periods++ + variable.episode.stat.days = variable.episode.count.periods * variable.episode.parameters.timeFrame / ONE_DAY_IN_MILISECONDS + + if (processingDailyFiles) { + /* We skip the candle at the head of the market because currentCandleIndex has not closed yet. */ + let candlesPerDay = ONE_DAY_IN_MILISECONDS / variable.episode.parameters.timeFrame + if (currentCandleIndex === candles.length - 1) { + if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { + /* We are at the head of the market, thus we skip the last candle because it has not close yet. */ + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet.') } + controlLoop() + return + /* Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators. + if we skip these two variable.episode.count.periods, then there will be a hole in the file since the last period will be missing. */ + } + } + } else { // We are processing Market Files + if (currentCandleIndex === candles.length - 1) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet.') } + controlLoop() + return + } + } + + let conditions = new Map() // Here we store the conditions values that will be use in the simulator for decision making. + let formulas = new Map() + let conditionsValues = [] // Here we store the conditions values that will be written on file for the plotter. + let formulasErrors = [] // Here we store the errors produced by all phase formulas. + let formulasValues = [] // Here we store the values produced by all phase formulas. + + /* We define and evaluate all conditions to be used later during the simulation loop. */ + for (let j = 0; j < tradingSystem.strategies.length; j++) { + let strategy = tradingSystem.strategies[j] + + let positionSize = 0 + let positionRate = 0 + + /* Continue with the rest of the formulas and conditions */ + + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.triggerOn !== undefined) { + for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { + let situation = triggerStage.triggerOn.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + + if (triggerStage.triggerOff !== undefined) { + for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { + let situation = triggerStage.triggerOff.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + + if (triggerStage.takePosition !== undefined) { + for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { + let situation = triggerStage.takePosition.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + + let openStage = strategy.openStage + + if (openStage !== undefined) { + /* Default Values */ + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + positionSize = variable.current.balance.baseAsset + positionRate = candle.close + } else { + positionSize = variable.current.balance.quotedAsset + positionRate = candle.close + } + + let initialDefinition = openStage.initialDefinition + + if (initialDefinition !== undefined) { + if (variable.current.position.size !== 0) { + positionSize = variable.current.position.size + } else { + if (initialDefinition.positionSize !== undefined) { + if (initialDefinition.positionSize.formula !== undefined) { + try { + let code = initialDefinition.positionSize.formula.code + positionSize = eval(code) + addCodeToSnapshot(code) + } catch (err) { + initialDefinition.positionSize.formula.error = err.message + } + if (isNaN(positionSize)) { + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + positionSize = variable.current.balance.baseAsset + } else { + positionSize = variable.current.balance.quotedAsset + } + } else { + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + if (positionSize > variable.current.balance.baseAsset) { positionSize = variable.current.balance.baseAsset } + } else { + if (positionSize > variable.current.balance.quotedAsset) { positionSize = variable.current.balance.quotedAsset } + } + } + } + } + } + + if (variable.current.position.rate !== 0) { + positionRate = variable.current.position.rate + } else { + if (initialDefinition.positionRate !== undefined) { + if (initialDefinition.positionRate.formula !== undefined) { + try { + let code = initialDefinition.positionRate.formula.code + positionRate = eval(initialDefinition.positionRate.formula.code) + addCodeToSnapshot(code) + } catch (err) { + initialDefinition.positionRate.formula.error = err.message + } + if (isNaN(positionRate)) { + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + positionRate = candle.close + } else { + positionRate = candle.close + } + } + } + } + } + + if (initialDefinition.stopLoss !== undefined) { + for (let p = 0; p < initialDefinition.stopLoss.phases.length; p++) { + let phase = initialDefinition.stopLoss.phases[p] + + /* Evaluate Formula */ + let formulaValue + let formulaError = '' + + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaError = 'Formula evaluates to Infinity.' + formulaValue = MAX_STOP_LOSS_VALUE + if (variable.current.position.stopLoss.stage === 'Open Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + /* + We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. + */ + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_STOP_LOSS_VALUE) { + formulaValue = MIN_STOP_LOSS_VALUE + if (variable.current.position.stopLoss.stage === 'Open Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } + + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + formulas.set(key, formulaValue) + } + + /* next phase event */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + + let situation = nextPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + + /* move to phase events */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + + let situation = moveToPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } + + if (initialDefinition.takeProfit !== undefined) { + for (let p = 0; p < initialDefinition.takeProfit.phases.length; p++) { + let phase = initialDefinition.takeProfit.phases[p] + + /* Evaluate Formula */ + let formulaValue + let formulaError = '' + + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaValue = MAX_TAKE_PROFIT_VALUE + if (variable.current.position.takeProfit.stage === 'Open Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + /* + We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. + */ + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_TAKE_PROFIT_VALUE) { + formulaValue = MIN_TAKE_PROFIT_VALUE + if (variable.current.position.takeProfit.stage === 'Open Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } + + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + formulas.set(key, formulaValue) + } + + /* next phase event */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + + let situation = nextPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + + /* move to phase events */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + + let situation = moveToPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } + } + + strategy.positionSize = positionSize + strategy.positionRate = positionRate + } + + let manageStage = strategy.manageStage + + if (manageStage !== undefined) { + if (manageStage.stopLoss !== undefined) { + for (let p = 0; p < manageStage.stopLoss.phases.length; p++) { + let phase = manageStage.stopLoss.phases[p] + + /* Evaluate Formula */ + let formulaValue + let formulaError = '' + + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaError = '' + formulaValue = MAX_STOP_LOSS_VALUE + if (variable.current.position.stopLoss.stage === 'Manage Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + /* + We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. + */ + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_STOP_LOSS_VALUE) { + formulaValue = MIN_STOP_LOSS_VALUE + if (variable.current.position.stopLoss.stage === 'Manage Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = ' + formulaError) } + } + } + + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + formulas.set(key, formulaValue) + } + + /* next phase event */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + + let situation = nextPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + + /* move to phase events */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + + let situation = moveToPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } + + if (manageStage.takeProfit !== undefined) { + for (let p = 0; p < manageStage.takeProfit.phases.length; p++) { + let phase = manageStage.takeProfit.phases[p] + + /* Evaluate Formula */ + let formulaValue + let formulaError = '' + + if (phase.formula !== undefined) { + try { + let code = phase.formula.code + formulaValue = eval(code) + addCodeToSnapshot(code) + if (formulaValue === Infinity) { + formulaError = 'Formula evaluates to Infinity.' + formulaValue = MAX_TAKE_PROFIT_VALUE + if (variable.current.position.takeProfit.stage === 'Manage Stage') { + formulaError = 'WARNING: Formula evaluates to Infinity.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } + } catch (err) { + if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { + /* + We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. + */ + } else { + formulaError = err.message + } + } + if (isNaN(formulaValue)) { formulaValue = 0 } + if (formulaValue < MIN_TAKE_PROFIT_VALUE) { + formulaValue = MIN_TAKE_PROFIT_VALUE + if (variable.current.position.takeProfit.stage === 'Manage Stage') { + formulaError = 'WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE.' + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> loop -> manageStage.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = ' + formulaError) } + } + } + + formulasErrors.push('"' + formulaError + '"') + formulasValues.push(formulaValue) + let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + formulas.set(key, formulaValue) + } + + /* next phase event */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + + let situation = nextPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + + /* move to phase events */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + + let situation = moveToPhaseEvent.situations[k] + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m + + if (condition.javascriptCode !== undefined) { + newCondition(key, condition.javascriptCode, chart) + } + } + } + } + } + } + } + } + + function newCondition(key, node, chart) { + let condition + let value + + try { + let code = node.code + value = eval(code) + addCodeToSnapshot(code) + } catch (err) { + /* + One possible error is that the conditions references a .previous that is undefined. For this + reason and others, we will simply set the value to false. + */ + value = false + + if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || + node.code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1 + ) { + /* + We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. + */ + } else { + node.error = err.message + ' @ ' + (new Date(candle.begin)).toLocaleString() + } + } + + condition = { + key: key, + value: value + } + + conditions.set(condition.key, condition) + + if (condition.value) { + conditionsValues.push(1) + } else { + conditionsValues.push(0) + } + } + } + + /* Trigger On Conditions */ + if ( + variable.current.strategy.stage === 'No Stage' && + variable.current.strategy.index === -1 + ) { + let minimumBalance + let maximumBalance + let balance + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + balance = variable.current.balance.baseAsset + minimumBalance = variable.episode.parameters.minimum.balance.baseAsset + maximumBalance = variable.episode.parameters.maximum.balance.baseAsset + } else { + balance = variable.current.balance.quotedAsset + minimumBalance = variable.episode.parameters.minimum.balance.quotedAsset + maximumBalance = variable.episode.parameters.maximum.balance.quotedAsset + } + + let stopRunningDate = new Date(candle.begin) + if (balance <= minimumBalance) { + tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> ' + tradingSystem.error) } + afterLoop() + return + } + + if (balance >= maximumBalance) { + tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> ' + tradingSystem.error) } + afterLoop() + return + } + + /* + Here we need to pick a strategy, or if there is not suitable strategy for the current + market conditions, we pass until the next period. + + To pick a new strategy we will evaluate what we call the trigger on. Once we enter + into one strategy, we will ignore market conditions for others. However there is also + a strategy trigger off which can be hit before taking a position. If hit, we would + be outside a strategy again and looking for the condition to enter all over again. + + */ + + for (let j = 0; j < tradingSystem.strategies.length; j++) { + if ( + variable.current.strategy.stage !== 'No Stage' || + variable.current.strategy.index !== -1 + ) { continue } + + let strategy = tradingSystem.strategies[j] + + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.triggerOn !== undefined) { + for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { + let situation = triggerStage.triggerOn.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + + variable.current.strategy.stage = 'Trigger Stage' + checkAnnouncements(triggerStage) + + variable.current.strategy.index = j + variable.current.strategy.begin = candle.begin + variable.current.strategy.beginRate = candle.min + variable.current.strategy.endRate = candle.min // In case the strategy does not get exited + variable.current.strategy.situationName = situation.name + variable.current.strategy.name = strategy.name + + variable.current.distance.toEvent.triggerOn = 1 + + checkAnnouncements(triggerStage.triggerOn) + saveAsLastTriggerOnSnapshot = true + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Switching to Trigger Stage because conditions at Trigger On Event were met.') } + break + } + } + } + } + } + } + + /* Trigger Off Condition */ + if (variable.current.strategy.stage === 'Trigger Stage') { + let strategy = tradingSystem.strategies[variable.current.strategy.index] + + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.triggerOff !== undefined) { + for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { + let situation = triggerStage.triggerOff.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = variable.current.strategy.index + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + variable.current.strategy.number = variable.current.strategy.index + variable.current.strategy.end = candle.end + variable.current.strategy.endRate = candle.min + variable.current.strategy.status = 1 // This means the strategy is closed, i.e. that has a begin and end. + variable.current.strategy.stage = 'No Stage' + variable.current.strategy.index = -1 + + variable.current.distance.toEvent.triggerOff = 1 + + checkAnnouncements(triggerStage.triggerOff) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Switching to No Stage because conditions at the Trigger Off Event were met.') } + break + } + } + } + } + } + + /* Take Position Condition */ + if (variable.current.strategy.stage === 'Trigger Stage') { + let strategy = tradingSystem.strategies[variable.current.strategy.index] + + let triggerStage = strategy.triggerStage + + if (triggerStage !== undefined) { + if (triggerStage.takePosition !== undefined) { + for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { + let situation = triggerStage.takePosition.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = variable.current.strategy.index + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + variable.current.strategy.stage = 'Open Stage' + checkAnnouncements(strategy.openStage) + + variable.current.position.stopLoss.stage = 'Open Stage' + variable.current.position.takeProfit.stage = 'Open Stage' + variable.current.position.stopLoss.phase = 0 + variable.current.position.takeProfit.phase = 0 + + takePositionNow = true + variable.current.position.situationName = situation.name + + checkAnnouncements(triggerStage.takePosition) + saveAsLastTakePositionSnapshot = true + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Conditions at the Take Position Event were met.') } + break + } + } + } + } + } + + /* Stop Loss Management */ + if ( + (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') && + takePositionNow !== true + ) { + checkStopPhases() + calculateStopLoss() + } + + function checkStopPhases() { + let strategy = tradingSystem.strategies[variable.current.strategy.index] + + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let j = variable.current.strategy.index + let stageKey + let initialDefinitionKey = '' + let p + + if (variable.current.position.stopLoss.stage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.stopLoss !== undefined) { + parentNode = openStage.initialDefinition + initialDefinitionKey = '-' + 'initialDefinition' + stageKey = 'openStage' + p = variable.current.position.stopLoss.phase + } + } + } + + if (variable.current.position.stopLoss.stage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.stopLoss !== undefined) { + parentNode = manageStage + stageKey = 'manageStage' + p = variable.current.position.stopLoss.phase - 1 + } + } + + if (parentNode !== undefined) { + let phase = parentNode.stopLoss.phases[p] + + /* Check the next Phase Event. */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + variable.current.position.stopLoss.phase++ + variable.current.position.stopLoss.stage = 'Manage Stage' + if (variable.current.position.takeProfit.phase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Take Profit') + } + + checkAnnouncements(nextPhaseEvent) + return + } + } + } + + /* Check the Move to Phase Events. */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + let situation = moveToPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < parentNode.stopLoss.phases.length; q++) { + if (parentNode.stopLoss.phases[q].id === moveToPhase.id) { + variable.current.position.stopLoss.phase = q + 1 + } + } + } else { + moveToPhaseEvent.error = 'This Node needs to reference a Phase.' + continue + } + + variable.current.position.stopLoss.stage = 'Manage Stage' + if (variable.current.position.takeProfit.phase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Take Profit') + } + + checkAnnouncements(moveToPhaseEvent) + return + } + } + } + } + } + } + + function calculateStopLoss() { + let strategy = tradingSystem.strategies[variable.current.strategy.index] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + let key + + if (variable.current.position.stopLoss.stage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.stopLoss !== undefined) { + phase = openStage.initialDefinition.stopLoss.phases[variable.current.position.stopLoss.phase] + key = variable.current.strategy.index + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + (variable.current.position.stopLoss.phase) + } + } + } + + if (variable.current.position.stopLoss.stage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.stopLoss !== undefined) { + phase = manageStage.stopLoss.phases[variable.current.position.stopLoss.phase - 1] + key = variable.current.strategy.index + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + (variable.current.position.stopLoss.phase - 1) + } + } + + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = variable.current.position.stopLoss.value + + variable.current.position.stopLoss.value = formulas.get(key) + + if (variable.current.position.stopLoss.value !== previousValue) { + checkAnnouncements(phase, variable.current.position.stopLoss.value) + } + } + } + } + + /* Take Profit Management */ + if ( + (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') && + takePositionNow !== true + ) { + checkTakeProfitPhases() + calculateTakeProfit() + } + + function checkTakeProfitPhases() { + let strategy = tradingSystem.strategies[variable.current.strategy.index] + + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let parentNode + let j = variable.current.strategy.index + let stageKey + let initialDefinitionKey = '' + let p + + if (variable.current.position.takeProfit.stage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.takeProfit !== undefined) { + parentNode = openStage.initialDefinition + initialDefinitionKey = '-' + 'initialDefinition' + stageKey = 'openStage' + p = variable.current.position.takeProfit.phase + } + } + } + + if (variable.current.position.takeProfit.stage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.takeProfit !== undefined) { + parentNode = manageStage + stageKey = 'manageStage' + p = variable.current.position.takeProfit.phase - 1 + } + } + + if (parentNode !== undefined) { + let phase = parentNode.takeProfit.phases[p] + if (phase === undefined) { return } // trying to jump to a phase that does not exists. + + /* Check the next Phase Event. */ + let nextPhaseEvent = phase.nextPhaseEvent + if (nextPhaseEvent !== undefined) { + for (let k = 0; k < nextPhaseEvent.situations.length; k++) { + let situation = nextPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + variable.current.position.takeProfit.phase++ + variable.current.position.takeProfit.stage = 'Manage Stage' + if (variable.current.position.stopLoss.phase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Stop') + } + + checkAnnouncements(nextPhaseEvent) + return + } + } + } + + /* Check the Move to Phase Events. */ + for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { + let moveToPhaseEvent = phase.moveToPhaseEvents[n] + if (moveToPhaseEvent !== undefined) { + for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { + let situation = moveToPhaseEvent.situations[k] + let passed + if (situation.conditions.length > 0) { + passed = true + } + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m] + let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m + + let value = false + if (conditions.get(key) !== undefined) { + value = conditions.get(key).value + } + + if (value === false) { passed = false } + } + + if (passed) { + + let moveToPhase = moveToPhaseEvent.referenceParent + if (moveToPhase !== undefined) { + for (let q = 0; q < parentNode.takeProfit.phases.length; q++) { + if (parentNode.takeProfit.phases[q].id === moveToPhase.id) { + variable.current.position.takeProfit.phase = q + 1 + } + } + } else { + moveToPhaseEvent.error = 'This Node needs to reference a Phase.' + continue + } + + variable.current.position.takeProfit.stage = 'Manage Stage' + if (variable.current.position.stopLoss.phase > 0) { + variable.current.strategy.stage = 'Manage Stage' + checkAnnouncements(manageStage, 'Stop') + } + + checkAnnouncements(moveToPhaseEvent) + return + } + } + } + } + } + } + + function calculateTakeProfit() { + let strategy = tradingSystem.strategies[variable.current.strategy.index] + let openStage = strategy.openStage + let manageStage = strategy.manageStage + let phase + let key + + if (variable.current.position.takeProfit.stage === 'Open Stage' && openStage !== undefined) { + if (openStage.initialDefinition !== undefined) { + if (openStage.initialDefinition.takeProfit !== undefined) { + phase = openStage.initialDefinition.takeProfit.phases[variable.current.position.takeProfit.phase] + key = variable.current.strategy.index + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + (variable.current.position.takeProfit.phase) + } + } + } + + if (variable.current.position.takeProfit.stage === 'Manage Stage' && manageStage !== undefined) { + if (manageStage.takeProfit !== undefined) { + phase = manageStage.takeProfit.phases[variable.current.position.takeProfit.phase - 1] + key = variable.current.strategy.index + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + (variable.current.position.takeProfit.phase - 1) + } + } + + if (phase !== undefined) { + if (phase.formula !== undefined) { + let previousValue = variable.current.position.stopLoss.value + + variable.current.position.takeProfit.value = formulas.get(key) + + if (variable.current.position.takeProfit.value !== previousValue) { + checkAnnouncements(phase, variable.current.position.takeProfit.value) + } + } + } + } + + /* Keeping Position Counters Up-to-date */ + if ( + (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') + ) { + if (takePositionNow === true) { + variable.current.position.count.periods = 0 + } + + variable.current.position.count.periods++ + variable.current.position.stat.days = variable.current.position.count.periods * variable.episode.parameters.timeFrame / ONE_DAY_IN_MILISECONDS + } else { + variable.current.position.count.periods = 0 + variable.current.position.stat.days = 0 + } + + /* Keeping Distance Counters Up-to-date */ + if ( + variable.current.distance.toEvent.triggerOn > 0 // with this we avoind counting before the first event happens. + ) { + variable.current.distance.toEvent.triggerOn++ + } + + if ( + variable.current.distance.toEvent.triggerOff > 0 // with this we avoind counting before the first event happens. + ) { + variable.current.distance.toEvent.triggerOff++ + } + + if ( + variable.current.distance.toEvent.takePosition > 0 // with this we avoind counting before the first event happens. + ) { + variable.current.distance.toEvent.takePosition++ + } + + if ( + variable.current.distance.toEvent.closePosition > 0 // with this we avoind counting before the first event happens. + ) { + variable.current.distance.toEvent.closePosition++ + } + + /* Checking if Stop or Take Profit were hit */ + if ( + (variable.current.strategy.stage === 'Open Stage' || variable.current.strategy.stage === 'Manage Stage') && + takePositionNow !== true + ) { + let strategy = tradingSystem.strategies[variable.current.strategy.index] + + /* Checking what happened since the last execution. We need to know if the Stop Loss + or our Take Profit were hit. */ + + /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ + + if ((variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset && candle.max >= variable.current.position.stopLoss.value) || (variable.episode.parameters.baseAsset !== variable.episode.parameters.marketBaseAsset && candle.min <= variable.current.position.stopLoss.value)) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Stop Loss was hit.') } + /* + Hit Point Validation + + This prevents misscalculations when a formula places the stop loss in this case way beyond the market price. + If we take the stop loss value at those situation would be a huge distortion of facts. + */ + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + if (variable.current.position.stopLoss.value < candle.min) { + variable.current.position.stopLoss.value = candle.min + } + } else { + if (variable.current.position.stopLoss.value > candle.max) { + variable.current.position.stopLoss.value = candle.max + } + } + + let slippedStopLoss = variable.current.position.stopLoss.value + + /* Apply the Slippage */ + let slippageAmount = slippedStopLoss * bot.VALUES_TO_USE.slippage.stopLoss / 100 + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + slippedStopLoss = slippedStopLoss + slippageAmount + } else { + slippedStopLoss = slippedStopLoss - slippageAmount + } + + closeRate = slippedStopLoss + + variable.current.strategy.stage = 'Close Stage' + checkAnnouncements(strategy.closeStage, 'Stop') + + variable.current.position.stopLoss.stage = 'No Stage' + variable.current.position.takeProfit.stage = 'No Stage' + variable.current.position.end = candle.end + variable.current.position.status = 1 + variable.current.position.exitType = 1 + variable.current.position.endRate = closeRate + + closePositionNow = true + } + + /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ + + if ((variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset && candle.min <= variable.current.position.takeProfit.value) || (variable.episode.parameters.baseAsset !== variable.episode.parameters.marketBaseAsset && candle.max >= variable.current.position.takeProfit.value)) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Take Profit was hit.') } + /* + Hit Point Validation: + + This prevents misscalculations when a formula places the take profit in this case way beyond the market price. + If we take the stop loss value at those situation would be a huge distortion of facts. + */ + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + if (variable.current.position.takeProfit.value > candle.max) { + variable.current.position.takeProfit.value = candle.max + } + } else { + if (variable.current.position.takeProfit.value < candle.min) { + variable.current.position.takeProfit.value = candle.min + } + } + + let slippedTakeProfit = variable.current.position.takeProfit.value + /* Apply the Slippage */ + let slippageAmount = slippedTakeProfit * bot.VALUES_TO_USE.slippage.takeProfit / 100 + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + slippedTakeProfit = slippedTakeProfit + slippageAmount + } else { + slippedTakeProfit = slippedTakeProfit - slippageAmount + } + + closeRate = slippedTakeProfit + + variable.current.strategy.stage = 'Close Stage' + checkAnnouncements(strategy.closeStage, 'Take Profit') + + variable.current.position.stopLoss.stage = 'No Stage' + variable.current.position.takeProfit.stage = 'No Stage' + + variable.current.position.end = candle.end + variable.current.position.status = 1 + variable.current.position.exitType = 2 + variable.current.position.endRate = closeRate + + closePositionNow = true + addToSnapshots = true + } + } + + /* 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 */ + variable.current.distance.toEvent.takePosition = 1 + + /* Position size and rate */ + let strategy = tradingSystem.strategies[variable.current.strategy.index] + + variable.current.position.size = strategy.positionSize + variable.current.position.rate = strategy.positionRate + + /* We take what was calculated at the formula and apply the slippage. */ + let slippageAmount = variable.current.position.rate * bot.VALUES_TO_USE.slippage.positionRate / 100 + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.current.position.rate = variable.current.position.rate - slippageAmount + } else { + variable.current.position.rate = variable.current.position.rate + 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 -> strategy.positionSize = ' + strategy.positionSize) + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> strategy.positionRate = ' + strategy.positionRate) + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> slippageAmount = ' + slippageAmount) + logger.write(MODULE_NAME, '[PERSIST] runSimulation -> loop -> takePositionNow -> variable.current.position.rate = ' + variable.current.position.rate) + } + + /* Update the trade record information. */ + variable.current.position.begin = candle.begin + variable.current.position.beginRate = variable.current.position.rate + + /* 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 (variable.episode.count.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 (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + orderSide = 'sell' + + orderPrice = variable.current.position.rate + + amountA = variable.current.position.size * orderPrice + amountB = variable.current.position.size + } else { + orderSide = 'buy' + + orderPrice = variable.current.position.rate + + amountA = variable.current.position.size + amountB = variable.current.position.size / orderPrice + } + + variable.executionContext = { + status: 'Taking Position', + periods: variable.episode.count.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: variable.episode.count.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() + + variable.previous.balance.baseAsset = variable.current.balance.baseAsset + variable.previous.balance.quotedAsset = variable.current.balance.quotedAsset + + variable.last.position.profitLoss = 0 + variable.last.position.ROI = 0 + + let feePaid = 0 + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + feePaid = variable.current.position.size * variable.current.position.rate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + variable.current.balance.quotedAsset = variable.current.balance.quotedAsset + variable.current.position.size * variable.current.position.rate - feePaid + variable.current.balance.baseAsset = variable.current.balance.baseAsset - variable.current.position.size + } else { + feePaid = variable.current.position.size / variable.current.position.rate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + variable.current.balance.baseAsset = variable.current.balance.baseAsset + variable.current.position.size / variable.current.position.rate - feePaid + variable.current.balance.quotedAsset = variable.current.balance.quotedAsset - variable.current.position.size + } + + addRecords() + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> takePositionAtSimulation -> Exiting Loop Body after taking position at simulation.') } + controlLoop() + return + } + } + + if (closePositionNow === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing a Position -> Entering code block.') } + + closePositionNow = false + + /* Inicializing this counter */ + variable.current.distance.toEvent.closePosition = 1 + + /* Position size and rate */ + let strategy = tradingSystem.strategies[variable.current.strategy.index] + + 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 (variable.episode.count.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 (variable.episode.parameters.baseAsset === variable.episode.parameters.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 = variable.current.balance.quotedAsset + amountB = variable.current.balance.quotedAsset / 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 = variable.current.balance.baseAsset * orderPrice + amountB = variable.current.balance.baseAsset + } + + variable.executionContext = { + status: 'Closing Position', + periods: variable.episode.count.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: variable.episode.count.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.') } + + variable.episode.count.positions++ + + let feePaid = 0 + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + strategy.positionSize = variable.current.balance.quotedAsset / closeRate + strategy.positionRate = closeRate + + feePaid = variable.current.balance.quotedAsset / closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + variable.current.balance.baseAsset = variable.current.balance.baseAsset + variable.current.balance.quotedAsset / closeRate - feePaid + variable.current.balance.quotedAsset = 0 + } else { + strategy.positionSize = variable.current.balance.baseAsset * closeRate + strategy.positionRate = closeRate + + feePaid = variable.current.balance.baseAsset * closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + variable.current.balance.quotedAsset = variable.current.balance.quotedAsset + variable.current.balance.baseAsset * closeRate - feePaid + variable.current.balance.baseAsset = 0 + } + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.last.position.profitLoss = variable.current.balance.baseAsset - variable.previous.balance.baseAsset + variable.last.position.ROI = variable.last.position.profitLoss * 100 / variable.current.position.size + if (isNaN(variable.last.position.ROI)) { variable.last.position.ROI = 0 } + variable.episode.stat.profitLoss = variable.current.balance.baseAsset - variable.episode.parameters.initial.balance.baseAsset + } else { + variable.last.position.profitLoss = variable.current.balance.quotedAsset - variable.previous.balance.quotedAsset + variable.last.position.ROI = variable.last.position.profitLoss * 100 / variable.current.position.size + if (isNaN(variable.last.position.ROI)) { variable.last.position.ROI = 0 } + variable.episode.stat.profitLoss = variable.current.balance.quotedAsset - variable.episode.parameters.initial.balance.quotedAsset + } + + variable.current.position.stat.ROI = variable.last.position.ROI + + if (variable.last.position.profitLoss > 0) { + variable.episode.count.hits++ + } else { + variable.episode.count.fails++ + } + + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.episode.stat.ROI = (variable.episode.parameters.initial.balance.baseAsset + variable.episode.stat.profitLoss) / variable.episode.parameters.initial.balance.baseAsset - 1 + variable.episode.stat.hitRatio = variable.episode.count.hits / variable.episode.count.positions + variable.episode.stat.anualizedRateOfReturn = variable.episode.stat.ROI / variable.episode.stat.days * 365 + } else { + variable.episode.stat.ROI = (variable.episode.parameters.initial.balance.quotedAsset + variable.episode.stat.profitLoss) / variable.episode.parameters.initial.balance.quotedAsset - 1 + variable.episode.stat.hitRatio = variable.episode.count.hits / variable.episode.count.positions + variable.episode.stat.anualizedRateOfReturn = variable.episode.stat.ROI / variable.episode.stat.days * 365 + } + + addRecords() + + variable.current.position.stopLoss.value = 0 + variable.current.position.takeProfit.value = 0 + + variable.current.position.rate = 0 + variable.current.position.size = 0 + + timerToCloseStage = candle.begin + variable.current.position.stopLoss.stage = 'No Stage' + variable.current.position.takeProfit.stage = 'No Stage' + variable.current.position.stopLoss.phase = -1 + variable.current.position.takeProfit.phase = -1 + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> closePositionAtSimulation -> Exiting Loop Body after closing position at simulation.') } + controlLoop() + return + } + } + + /* Closing the Closing Stage */ + if (variable.current.strategy.stage === 'Close Stage') { + if (candle.begin - 5 * 60 * 1000 > timerToCloseStage) { + variable.current.strategy.number = variable.current.strategy.index + variable.current.strategy.end = candle.end + variable.current.strategy.endRate = candle.min + variable.current.strategy.status = 1 // This means the strategy is closed, i.e. that has a begin and end. + + variable.current.strategy.index = -1 + variable.current.strategy.stage = 'No Stage' + + timerToCloseStage = 0 + variable.current.distance.toEvent.triggerOff = 1 + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing the Closing Stage -> Exiting Close Stage.') } + } else { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing the Closing Stage -> Waiting for timer.') } + } + } + + /* Not a buy or sell condition */ + + 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. */ + variable.last.candle = { + begin: candle.begin, + 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 = [ + variable.episode.count.positions, // Position Number + (new Date(candle.begin)).toISOString(), // Datetime + tradingSystem.strategies[variable.current.strategy.index].name, // Strategy Name + variable.current.strategy.situationName, // Trigger On Situation + variable.current.position.situationName, // Take Position Situation + hitOrFial(), // Result + variable.last.position.ROI, // ROI + exitType() // Exit Type + ] + + function hitOrFial() { + if (variable.last.position.ROI > 0) { return 'HIT' } else { return 'FAIL' } + } + + function exitType() { + switch (variable.current.position.exitType) { + 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 (variable.current.balance.baseAsset === Infinity) { + variable.current.balance.baseAsset = Number.MAX_SAFE_INTEGER + } + + if (variable.current.balance.quotedAsset === Infinity) { + variable.current.balance.quotedAsset = Number.MAX_SAFE_INTEGER + } + + simulationRecord = [ + candle.begin, + candle.end, + variable.current.balance.baseAsset, + variable.current.balance.quotedAsset, + variable.episode.stat.profitLoss, + variable.last.position.profitLoss, + variable.current.position.stopLoss.value, + variable.episode.count.positions, + variable.episode.count.hits, + variable.episode.count.fails, + variable.episode.stat.hitRatio, + variable.episode.stat.ROI, + variable.episode.count.periods, + variable.episode.stat.days, + variable.episode.stat.anualizedRateOfReturn, + variable.current.position.rate, + variable.last.position.ROI, + variable.current.position.takeProfit.value, + variable.current.position.stopLoss.phase, + variable.current.position.takeProfit.phase, + variable.current.position.size, + variable.episode.parameters.initial.balance.baseAsset, + variable.episode.parameters.minimum.balance.baseAsset, + variable.episode.parameters.maximum.balance.baseAsset, + variable.episode.parameters.initial.balance.quotedAsset, + variable.episode.parameters.minimum.balance.quotedAsset, + variable.episode.parameters.maximum.balance.quotedAsset, + '"' + variable.episode.parameters.baseAsset + '"', + '"' + variable.episode.parameters.quotedAsset + '"', + '"' + variable.episode.parameters.marketBaseAsset + '"', + '"' + variable.episode.parameters.marketQuotedAsset + '"', + variable.current.position.count.periods, + variable.current.position.stat.days + ] + + recordsArray.push(simulationRecord) + } + + function addConditionsRecord() { + /* Prepare the information for the Conditions File */ + let conditionsRecord = [ + candle.begin, + candle.end, + variable.current.strategy.index, + variable.current.position.stopLoss.phase, + variable.current.position.takeProfit.phase, + 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 (variable.current.strategy.begin !== 0 && variable.current.strategy.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { + variable.current.strategy.status = 2 // This means the strategy is open, i.e. that has a begin but no end. + variable.current.strategy.end = candle.end + } + + /* Prepare the information for the Strategies File */ + if (variable.current.strategy.begin !== 0 && variable.current.strategy.end !== 0) { + let strategyRecord = [ + variable.current.strategy.begin, + variable.current.strategy.end, + variable.current.strategy.status, + variable.current.strategy.number, + variable.current.strategy.beginRate, + variable.current.strategy.endRate, + '"' + variable.current.strategy.situationName + '"', + '"' + variable.current.strategy.name + '"' + ] + + 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 (variable.current.position.begin !== 0 && + variable.current.position.end === 0 && + currentCandleIndex === candles.length - 2 && + lastCandle.end !== lastInstantOfTheDay) { + + /* This means the trade is open */ + variable.current.position.status = 2 + variable.current.position.end = candle.end + variable.current.position.endRate = candle.close + + /* Here we will calculate the ongoing ROI */ + if (variable.episode.parameters.baseAsset === variable.episode.parameters.marketBaseAsset) { + variable.current.position.stat.ROI = (variable.current.position.rate - candle.close) / variable.current.position.rate * 100 + } else { + variable.current.position.stat.ROI = (candle.close - variable.current.position.rate) / variable.current.position.rate * 100 + } + } + + /* Prepare the information for the Positions File */ + if (variable.current.position.begin !== 0 && variable.current.position.end !== 0) { + let positionRecord = [ + variable.current.position.begin, + variable.current.position.end, + variable.current.position.status, + variable.current.position.stat.ROI, + variable.current.position.beginRate, + variable.current.position.endRate, + variable.current.position.exitType, + '"' + variable.current.position.situationName + '"' + ] + + positionsArray.push(positionRecord) + + initializeCurrentPosition() + } + } + } + + function checkAnnouncements(node, value) { + /* + Value is an optional parameter that represents the value that the announcement is monitoring for change (for numeric values only). + If we do receive this value, we will only make the annoucement if the variance is grater than the user pre-defined value + for this variance. + */ + + if (node.announcements !== undefined) { + for (let i = 0; i < node.announcements.length; i++) { + let announcement = node.announcements[i] + let key = node.type + '-' + announcement.name + '-' + announcement.id + + let lastPeriodAnnounced = -1 + let newAnnouncementRecord = {} + + for (let j = 0; j < variable.announcements.length; j++) { + let announcementRecord = variable.announcements[j] + if (announcementRecord.key === key) { + lastPeriodAnnounced = announcementRecord.periods + newAnnouncementRecord = announcementRecord + break + } + } + + if (variable.episode.count.periods > lastPeriodAnnounced) { + if (isNaN(value) === false) { + /* The Value Variation is what tells us how much the value already announced must change in order to annouce it again. */ + let valueVariation + + let config = announcement.config + valueVariation = config.valueVariation + + if (newAnnouncementRecord.value !== undefined && valueVariation !== undefined) { + let upperLimit = newAnnouncementRecord.value + newAnnouncementRecord.value * valueVariation / 100 + let lowerLimit = newAnnouncementRecord.value - newAnnouncementRecord.value * valueVariation / 100 + if (value > lowerLimit && value < upperLimit) { + /* There is not enough variation to announce this again. */ + return + } + } + } + + /* + We store the announcement temporarily at an Array to differ its execution, becasue we need to evaulate its formula + and at this point in time the potential variables used at the formula are still not set. + */ + announcement.value = value + announcementsToBeMade.push(announcement) + + /* Next, we will remmeber this announcement was already done, so that it is not announced again in further processing of the same day. */ + if (newAnnouncementRecord.periods !== undefined) { + newAnnouncementRecord.periods = variable.episode.count.periods + newAnnouncementRecord.value = value + } else { + newAnnouncementRecord = { + key: key, + periods: variable.episode.count.periods, + value: value + } + variable.announcements.push(newAnnouncementRecord) + } + } + } + } + } + + function makeAnnoucements() { + /* Here we go through all the annoucements that need to be done during this loop, and we just do them. */ + for (let i = 0; i < announcementsToBeMade.length; i++) { + announcement = announcementsToBeMade[i] + /* Here we check if there is a formula attached to the annoucement, we evaluate it to get the annoucement text. */ + let formulaValue + if (announcement.formula !== undefined) { + try { + let value = announcement.value + formulaValue = eval(announcement.formula.code) + } catch (err) { + announcement.formula.error = err.message + } + } + announcement.formulaValue = formulaValue + + if (bot.SESSION.socialBots !== undefined) { + bot.SESSION.socialBots.announce(announcement) + } + } + } + + function addCodeToSnapshot(code) { + if (code === undefined) { return } + + try { + let instructionsArray = code.split(' ') + for (let i = 0; i < instructionsArray.length; i++) { + let instruction = instructionsArray[i] + instruction = instruction.replace('(', '') + instruction = instruction.replace(')', '') + instruction = instruction.replace(//g, '') + instruction = instruction.replace(/<=/g, '') + instruction = instruction.replace(/>=/g, '') + instruction = instruction.replace(/!=/g, '') + instruction = instruction.replace(/!==/g, '') + instruction = instruction.replace(/==/g, '') + instruction = instruction.replace(/===/g, '') + instruction = instruction.replace(/{/g, '') + instruction = instruction.replace(/}/g, '') + if (instruction.indexOf('chart') >= 0) { + let parts = instruction.split('.') + let timeFrame = parts[1] + let product = parts[2] + let property + checkPrevious(3) + function checkPrevious(index) { + property = parts[index] + if (property === 'previous') { + product = product + '.previous' + checkPrevious(index + 1) + } + } + + // Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10 + if (timeFrame !== 'atAnyvariable.timeFrame') { + timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7) + } + let key = timeFrame + '-' + product + '-' + property + let existingKey = snapshotKeys.get(key) + + if (existingKey === undefined) { // means that at the current loop this property of this product was not used before. + snapshotKeys.set(key, key) + snapshotLoopHeaders.push(key) + + let value = eval(instruction) + snapshotDataRecord.push(value) + } + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> code = ' + code) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } + + function controlLoop() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> Entering function.') } + + /* Checking if we should continue processing this loop or not. */ + if (bot.STOP_SESSION === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') } + console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') + afterLoop() + return + } + + if (global.STOP_TASK_GRACEFULLY === true) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') } + console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') + afterLoop() + return + } + + currentCandleIndex++ + if (currentCandleIndex < candles.length) { + setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. + } else { + afterLoop() + } + } + + function afterLoop() { + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> afterLoop -> Entering function.') } + + /* + Before returning we need to see if we have to record some of our counters at the variable. + To do that, the condition to be met is that this execution must include all candles of the current day. + */ + + if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> callback -> recordsArray.length = ' + recordsArray.length) } + + callback(tradingSystem, snapshots.headers, snapshots.triggerOn, snapshots.takePosition) + } + + function getElement(pArray, currentCandle, datasetName) { + if (pArray === undefined) { return } + try { + let element + for (let i = 0; i < pArray.length; i++) { + element = pArray[i] + + if (currentCandle.end === element.end) { // when there is an exact match at the end we take that element + return element + } else { + if ( + i > 0 && + element.end > currentCandle.end + ) { + let previousElement = pArray[i - 1] + if (previousElement.end < currentCandle.end) { + return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element. + } else { + return + } + } + if ( + i === pArray.length - 1 // If we reach the end of the array, then we return the last element. + && + element.end < currentCandle.end + ) { + return element + } + } + } + return + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName) + logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } + } catch (err) { + logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack) + callBackFunction(global.DEFAULT_FAIL_RESPONSE) + } + } +} + diff --git a/TradingBot.js b/TradingBot.js deleted file mode 100644 index 1a2852487..000000000 --- a/TradingBot.js +++ /dev/null @@ -1,981 +0,0 @@ -exports.newTradingBot = function newTradingBot(bot, logger, UTILITIES, FILE_STORAGE) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; - - const MODULE_NAME = "Trading Bot"; - - let thisObject = { - initialize: initialize, - finalize: finalize, - start: start - }; - - let utilities = UTILITIES.newCloudUtilities(logger); - let fileStorage = FILE_STORAGE.newFileStorage(logger); - - const COMMONS = require('./Commons.js'); - let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE); - - let exchangeAPI; - - return thisObject; - - function finalize() { - thisObject = undefined - utilities = undefined - fileStorage = undefined - commons = undefined - } - - function initialize(pExchangeAPI, callBackFunction) { - - try { - - logger.fileName = MODULE_NAME; - logger.initialize(); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - - exchangeAPI = pExchangeAPI; - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function start(multiPeriodDataFiles, timeFrame, timeFrameLabel, currentDay, interExecutionMemory, callBackFunction) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } - - let chart = {} - let mainDependency = {} - - /* The first phase here is about checking that we have everything we need at the definition level. */ - let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies - if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) {return} - - let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets - if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } - - /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ - for (let j = 0; j < global.marketFilesPeriods.length; j++) { - - let timeFrameLabel = marketFilesPeriods[j][1] - let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) - let products = {} - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files for timeFrame = " + timeFrameLabel); } - - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - chart[propertyName] = products - } - } - - for (let j = 0; j < global.dailyFilePeriods.length; j++) { - - let timeFrameLabel = global.dailyFilePeriods[j][1] - let dataFiles = multiPeriodDataFiles.get(timeFrameLabel) - let products = {} - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files for timeFrame = " + timeFrameLabel); } - - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - chart[propertyName] = products - } - } - - /* Single Files */ - - let dataFiles = multiPeriodDataFiles.get('Single Files') - let products = {} - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Inflating Data Files from Single Files."); } - - if (dataFiles !== undefined) { - commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) - - let propertyName = 'atAnyTimeFrame'; - chart[propertyName] = products - } - - /* Simulation */ - - const TRADING_SIMULATION = require('./TradingSimulation.js'); - let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES); - - let market = bot.market; - - const SIMULATED_RECORDS_FOLDER_NAME = "Trading-Simulation"; - const CONDITIONS_FOLDER_NAME = "Simulation-Conditions"; - const STRATEGIES_FOLDER_NAME = "Simulation-Strategies"; - const TRADES_FOLDER_NAME = "Simulation-Trades"; - - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; - - let recordsArray - let conditionsArray - let strategiesArray - let tradesArray - - let tradingSystem = {}; - - tradingSimulation.runSimulation( - chart, - dataDependencies, - timeFrame, - timeFrameLabel, - currentDay, - interExecutionMemory, - exchangeAPI, - writeFiles, - callBackFunction) - - function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray) { - - tradingSystem = pTradingSystem - recordsArray = pRecordsArray - conditionsArray = pConditionsArray - strategiesArray = pStrategiesArray - tradesArray = pTradesArray - - if (timeFrame > global.dailyFilePeriods[0][0]) { - writeMarketFiles() - } else { - writeDailyFiles() - } - } - - function writeMarketFiles() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> Entering function."); } - - writeRecordsFile(); - - function writeRecordsFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < recordsArray.length; i++) { - - let record = recordsArray[i]; - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.type + "," + - record.marketRate + "," + - record.amount + "," + - record.balanceA + "," + - record.balanceB + "," + - record.profit + "," + - record.lastTradeProfitLoss + "," + - record.stopLoss + "," + - record.roundtrips + "," + - record.hits + "," + - record.fails + "," + - record.hitRatio + "," + - record.ROI + "," + - record.periods + "," + - record.days + "," + - record.anualizedRateOfReturn + "," + - record.positionRate + "," + - record.lastTradeROI + "," + - record.strategy + "," + - record.strategyStageNumber + "," + - record.takeProfit + "," + - record.stopLossPhase + "," + - record.takeProfitPhase + "," + - JSON.stringify(record.executionRecord) + "," + - record.positionSize + "," + - record.initialBalanceA + "," + - record.minimumBalanceA + "," + - record.maximumBalanceA + "," + - record.initialBalanceB + "," + - record.minimumBalanceB + "," + - record.maximumBalanceB + "," + - record.baseAsset + "," + - record.quotedAsset + "," + - record.marketBaseAsset + "," + - record.marketQuotedAsset + "," + - record.positionPeriods + "," + - record.positionDays + "," + - record.distanceToLastTriggerOn + "," + - record.distanceToLastTriggerOff + "," + - record.distanceToLastTakePosition + "," + - record.distanceToLastClosePosition + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeConditionsFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeRecordsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeConditionsFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < conditionsArray.length; i++) { - - let record = conditionsArray[i]; - - let conditions = ""; - let conditionsSeparator = ""; - - for (let j = 0; j < record.length - 3; j++) { - conditions = conditions + conditionsSeparator + record[j]; - if (conditionsSeparator === "") { conditionsSeparator = ","; } - } - - conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']'; // The last item contains an Array of condition values. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']'; // The last item contains an Array of formulaErrors. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']'; // The last item contains an Array of formulaValues. - - fileContent = fileContent + separator + '[' + conditions + ']'; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - fileContent = "[" + JSON.stringify(tradingSystem) + "," + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeStrategiesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeConditionsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeStrategiesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < strategiesArray.length; i++) { - let record = strategiesArray[i]; - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.number + "," + - record.beginRate + "," + - record.endRate + "," + - '"' + record.triggerOnSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeTradesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeStrategiesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeTradesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < tradesArray.length; i++) { - - let record = tradesArray[i]; - if (record.stopRate === undefined) { record.stopRate = 0 } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.lastTradeROI + "," + - record.beginRate + "," + - record.endRate + "," + - record.exitType + "," + - '"' + record.takePositionSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeMarketFiles -> writeTradesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } - - function writeDailyFiles() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> Entering function."); } - - writeRecordsFile(); - - /* TODO : NEXT function is a work in progress, not used today */ - function writeFinalResults() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> Entering function."); } - - if ( // We only write this file if we are at the head of the market. - Math.trunc(currentDay.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS - !== - Math.trunc((new Date()).valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS - ) { - writeRecordsFile(); - return; - } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < recordsArray.length; i++) { - - let record = recordsArray[i]; - - /* Will only add to the file the records of the current day */ - - if (record.begin < currentDay.valueOf()) { continue; } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.type + "," + - record.marketRate + "," + - record.amount + "," + - record.balanceA + "," + - record.balanceB + "," + - record.profit + "," + - record.lastTradeProfitLoss + "," + - record.stopLoss + "," + - record.roundtrips + "," + - record.hits + "," + - record.fails + "," + - record.hitRatio + "," + - record.ROI + "," + - record.periods + "," + - record.days + "," + - record.anualizedRateOfReturn + "," + - record.positionRate + "," + - record.lastTradeROI + "," + - record.strategy + "," + - record.strategyStageNumber + "," + - record.takeProfit + "," + - record.stopLossPhase + "," + - record.takeProfitPhase + "," + - JSON.stringify(record.executionRecord) + "," + - record.positionSize + "," + - record.initialBalanceA + "," + - record.minimumBalanceA + "," + - record.maximumBalanceA + "," + - record.initialBalanceB + "," + - record.minimumBalanceB + "," + - record.maximumBalanceB + "," + - record.baseAsset + "," + - record.quotedAsset + "," + - record.marketBaseAsset + "," + - record.marketQuotedAsset + "," + - record.positionPeriods + "," + - record.positionDays + "," + - record.distanceToLastTriggerOn + "," + - record.distanceToLastTriggerOff + "," + - record.distanceToLastTakePosition + "," + - record.distanceToLastClosePosition + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeRecordsFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeFinalResults -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeRecordsFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < recordsArray.length; i++) { - - let record = recordsArray[i]; - - /* Will only add to the file the records of the current day */ - - if (record.begin < currentDay.valueOf()) { continue; } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.type + "," + - record.marketRate + "," + - record.amount + "," + - record.balanceA + "," + - record.balanceB + "," + - record.profit + "," + - record.lastTradeProfitLoss + "," + - record.stopLoss + "," + - record.roundtrips + "," + - record.hits + "," + - record.fails + "," + - record.hitRatio + "," + - record.ROI + "," + - record.periods + "," + - record.days + "," + - record.anualizedRateOfReturn + "," + - record.positionRate + "," + - record.lastTradeROI + "," + - record.strategy + "," + - record.strategyStageNumber + "," + - record.takeProfit + "," + - record.stopLossPhase + "," + - record.takeProfitPhase + "," + - JSON.stringify(record.executionRecord) + "," + - record.positionSize + "," + - record.initialBalanceA + "," + - record.minimumBalanceA + "," + - record.maximumBalanceA + "," + - record.initialBalanceB + "," + - record.minimumBalanceB + "," + - record.maximumBalanceB + "," + - record.baseAsset + "," + - record.quotedAsset + "," + - record.marketBaseAsset + "," + - record.marketQuotedAsset + "," + - record.positionPeriods + "," + - record.positionDays + "," + - record.distanceToLastTriggerOn + "," + - record.distanceToLastTriggerOff + "," + - record.distanceToLastTakePosition + "," + - record.distanceToLastClosePosition + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeConditionsFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeRecordsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeConditionsFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < conditionsArray.length; i++) { - - let record = conditionsArray[i]; - - /* Will only add to the file the records of the current day */ - - if (record.begin < currentDay.valueOf()) { continue; } - - let conditions = ""; - let conditionsSeparator = ""; - - /* Will only add to the file the records of the current day */ - - if (record.begin < currentDay.valueOf()) { continue; } - - for (let j = 0; j < record.length - 3; j++) { - conditions = conditions + conditionsSeparator + record[j]; - if (conditionsSeparator === "") { conditionsSeparator = ","; } - } - - conditions = conditions + conditionsSeparator + '[' + record[record.length - 3] + ']'; // The last item contains an Array of condition values. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 2] + ']'; // The last item contains an Array of formulaErrors. - conditions = conditions + conditionsSeparator + '[' + record[record.length - 1] + ']'; // The last item contains an Array of formulaValues. - - fileContent = fileContent + separator + '[' + conditions + ']'; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - fileContent = "[" + JSON.stringify(tradingSystem) + "," + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeStrategiesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeConditionsFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeStrategiesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < strategiesArray.length; i++) { - let record = strategiesArray[i]; - - /* Will only add to the file the records of the current day. In this case since objects can span more than one day, we add all of the objects that ends - at the current date. */ - - if (record.end < currentDay.valueOf()) { continue; } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.number + "," + - record.beginRate + "," + - record.endRate + "," + - '"' + record.triggerOnSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - writeTradesFile(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeStrategiesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeTradesFile() { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> Entering function."); } - - let separator = ""; - let fileRecordCounter = 0; - - let fileContent = ""; - - for (let i = 0; i < tradesArray.length; i++) { - - let record = tradesArray[i]; - - /* Will only add to the file the records of the current day. In this case since objects can span more than one day, we add all of the objects that ends - at the current date. */ - - if (record.end < currentDay.valueOf()) { continue; } - if (record.stopRate === undefined) { record.stopRate = 0 } - - fileContent = fileContent + separator + '[' + - record.begin + "," + - record.end + "," + - record.status + "," + - record.lastTradeROI + "," + - record.beginRate + "," + - record.endRate + "," + - record.exitType + "," + - '"' + record.takePositionSituation + '"' + "]"; - - if (separator === "") { separator = ","; } - - fileRecordCounter++; - - } - - fileContent = "[" + fileContent + "]"; - - let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - let fileName = 'Data.json'; - let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timeFrameLabel + "/" + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); - - callBackFunction(err); - return; - - } - - callBackFunction(global.DEFAULT_OK_RESPONSE); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeDailyFiles -> writeTradesFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } -}; - diff --git a/TradingSimulation.js b/TradingSimulation.js deleted file mode 100644 index 1e924e89c..000000000 --- a/TradingSimulation.js +++ /dev/null @@ -1,3167 +0,0 @@ -exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) { - - const FULL_LOG = true; - const LOG_FILE_CONTENT = false; - const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; - const MODULE_NAME = "Trading Simulation -> " + bot.SESSION.name; - - const GMT_SECONDS = ':00.000 GMT+0000'; - - let thisObject = { - finalize: finalize, - runSimulation: runSimulation - }; - - let utilities = UTILITIES.newCloudUtilities(bot, logger); - - return thisObject; - - function finalize() { - thisObject = undefined - } - - function runSimulation( - chart, - dataDependencies, - timeFrame, - timeFrameLabel, - currentDay, - interExecutionMemory, - exchangeAPI, - callback, - callBackFunction) { - - try { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> Entering function."); } - let processingDailyFiles - if (timeFrame > global.dailyFilePeriods[0][0]) { - processingDailyFiles = false - } else { - processingDailyFiles = true - } - - let recordsArray = []; - let conditionsArray = []; - let strategiesArray = []; - let tradesArray = []; - - let tradingSystem = bot.TRADING_SYSTEM - - /* Initial Default Values */ - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.initialDatetime = " + bot.VALUES_TO_USE.timeRange.initialDatetime); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> bot.VALUES_TO_USE.timeRange.finalDatetime = " + bot.VALUES_TO_USE.timeRange.finalDatetime); } - - let timerToCloseStage = 0 - - /* Stop Loss Management */ - - const MIN_STOP_LOSS_VALUE = 1 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result. - const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER - let stopLoss = 0; - - /* Take Profit Management */ - - const MIN_TAKE_PROFIT_VALUE = 1 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result. - const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER - let takeProfit = 0; - - /* Simulation Records */ - - let hitRatio = 0; - let ROI = 0; - let days = 0; - let positionDays = 0; - let anualizedRateOfReturn = 0; - let type = '""'; - let marketRate = 0; - let takePositionNow = false - let closePositionNow = false - - /* In some cases we need to know if we are positioned at the last candle of the calendar day, for that we need these variables. */ - - let lastInstantOfTheDay = 0 - if (currentDay) { - lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1; - } - - /* - The following counters need to survive multiple executions of the similator and keep themselves reliable. - This is challenging when the simulation is executed using Daily Files, since each execution means a new - day and these counters are meant to be kept along the whole market. - - To overcome this problem, we use the interExecutionMemory to record the values of the current execution - when finish. But there are a few details: - - 1. When the process is at the head of the market, it executes multple times at the same day. - 2. The same code serves execution from Market Files. - 3. In Daily Files we are receiving candles from the current day and previous day, so we need to take care of - not adding to the counters duplicate info when processing the day before candles. - - To overcome these challenges we record the values of the counters and variables on the interExecutionMemory only when - the day is complete and if we have a current Day. That menas that for Market Files we will never use - interExecutionMemory. - */ - - /*Needed for statistics */ - let previousBalanceBaseAsset = 0; - let previousBalanceQuotedAsset = 0; - - /* Position Management */ - let tradePositionRate = 0; - let tradePositionSize = 0; - - /* Strategy and Phase Management */ - let currentStrategyIndex = -1; - let strategyStage = 'No Stage'; - - let stopLossPhase = -1; - let stopLossStage = 'No Stage'; - - let takeProfitPhase = -1; - let takeProfitStage = 'No Stage'; - - /* These 2 objects will allow us to create separate files for each one of them. */ - - let currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '' - } - - let currentTrade = { - begin: 0, - end: 0, - status: 0, - profit: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - let balanceBaseAsset = bot.VALUES_TO_USE.initialBalanceA; - let balanceQuotedAsset = bot.VALUES_TO_USE.initialBalanceB; - - let lastTradeProfitLoss = 0; - let profit = 0; - let lastTradeROI = 0; - - let roundtrips = 0; - let fails = 0; - let hits = 0; - let periods = 0; - let positionPeriods = 0; - - let closeRate - - /* Usefull counters for conditions and formulas */ - - let distanceToLast = { - triggerOn: 0, - triggerOff: 0, - takePosition: 0, - closePosition: 0 - } - - /* Message to the Simulation Executor */ - - let orderId = 0; - let messageId = 0; - - /* Allowing these to be accesible at formulas */ - baseAsset = bot.VALUES_TO_USE.baseAsset - quotedAsset = bot.VALUES_TO_USE.quotedAsset - - let yesterday = {}; - - /* Initialization */ - - yesterday.stopLoss = 0 - yesterday.takeProfit = 0 - - yesterday.previousBalanceBaseAsset = previousBalanceBaseAsset - yesterday.previousBalanceQuotedAsset = previousBalanceQuotedAsset - - yesterday.tradePositionRate = tradePositionRate - yesterday.tradePositionSize = tradePositionSize - - yesterday.currentStrategyIndex = currentStrategyIndex - yesterday.strategyStage = strategyStage - - yesterday.stopLossPhase = stopLossPhase - yesterday.stopLossStage = stopLossStage - - yesterday.takeProfitPhase = takeProfitPhase - yesterday.takeProfitStage = takeProfitStage - - yesterday.currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '' - } - - yesterday.currentTrade = { - begin: 0, - end: 0, - status: 0, - profit: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - yesterday.lastTradeProfitLoss = 0; - yesterday.profit = 0; - yesterday.lastTradeROI = 0; - - yesterday.roundtrips = 0; - yesterday.fails = 0; - yesterday.hits = 0; - yesterday.periods = 0; - yesterday.positionPeriods = 0; - - yesterday.distanceToLast = { - triggerOn: 0, - triggerOff: 0, - takePosition: 0, - closePosition: 0 - } - - yesterday.orderId = 0; - yesterday.messageId = 0; - - yesterday.hitRatio = 0; - yesterday.ROI = 0; - yesterday.anualizedRateOfReturn = 0; - - if (interExecutionMemory.roundtrips === undefined) { // This just means that the inter execution memory was never used before. - - /* Initialize the data structure we will use inter execution. */ - - interExecutionMemory.stopLoss = 0 - interExecutionMemory.takeProfit = 0 - - interExecutionMemory.previousBalanceBaseAsset = previousBalanceBaseAsset - interExecutionMemory.previousBalanceQuotedAsset = previousBalanceQuotedAsset - - interExecutionMemory.tradePositionRate = tradePositionRate - interExecutionMemory.tradePositionSize = tradePositionSize - - interExecutionMemory.currentStrategyIndex = currentStrategyIndex - interExecutionMemory.strategyStage = strategyStage - - interExecutionMemory.stopLossPhase = stopLossPhase - interExecutionMemory.stopLossStage = stopLossStage - - interExecutionMemory.takeProfitPhase = takeProfitPhase - interExecutionMemory.takeProfitStage = takeProfitStage - - interExecutionMemory.currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '' - } - - interExecutionMemory.currentTrade = { - begin: 0, - end: 0, - status: 0, - profit: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - interExecutionMemory.balanceBaseAsset = balanceBaseAsset; - interExecutionMemory.balanceQuotedAsset = balanceQuotedAsset; - - interExecutionMemory.lastTradeProfitLoss = lastTradeProfitLoss; - interExecutionMemory.profit = profit; - interExecutionMemory.lastTradeROI = lastTradeROI; - - interExecutionMemory.roundtrips = 0; - interExecutionMemory.fails = 0; - interExecutionMemory.hits = 0; - interExecutionMemory.periods = 0; - interExecutionMemory.positionPeriods = 0; - - interExecutionMemory.distanceToLast = { - triggerOn: 0, - triggerOff: 0, - takePosition: 0, - closePosition: 0 - } - - interExecutionMemory.orderId = 0; - interExecutionMemory.messageId = 0; - - interExecutionMemory.hitRatio = 0; - interExecutionMemory.ROI = 0; - interExecutionMemory.anualizedRateOfReturn = 0; - - interExecutionMemory.announcements = [] - - } else { - - /* We get the initial values from the day previous to the candles we receive at the current execution */ - - stopLoss = interExecutionMemory.stopLoss - takeProfit = interExecutionMemory.takeProfit - - previousBalanceBaseAsset = interExecutionMemory.previousBalanceBaseAsset - previousBalanceQuotedAsset = interExecutionMemory.previousBalanceQuotedAsset - - tradePositionRate = interExecutionMemory.tradePositionRate - tradePositionSize = interExecutionMemory.tradePositionSize - - currentStrategyIndex = interExecutionMemory.currentStrategyIndex - strategyStage = interExecutionMemory.strategyStage - - stopLossPhase = interExecutionMemory.stopLossPhase - stopLossStage = interExecutionMemory.stopLossStage - - takeProfitPhase = interExecutionMemory.takeProfitPhase - takeProfitStage = interExecutionMemory.takeProfitStage - - currentStrategy = { - begin: interExecutionMemory.currentStrategy.begin, - end: interExecutionMemory.currentStrategy.end, - status: interExecutionMemory.currentStrategy.status, - number: interExecutionMemory.currentStrategy.number, - beginRate: interExecutionMemory.currentStrategy.beginRate, - endRate: interExecutionMemory.currentStrategy.endRate, - triggerOnSituation: interExecutionMemory.currentStrategy.triggerOnSituation - } - - currentTrade = { - begin: interExecutionMemory.currentTrade.begin, - end: interExecutionMemory.currentTrade.end, - status: interExecutionMemory.currentTrade.status, - profit: interExecutionMemory.currentTrade.profit, - exitType: interExecutionMemory.currentTrade.exitType, - beginRate: interExecutionMemory.currentTrade.beginRate, - endRate: interExecutionMemory.currentTrade.endRate, - takePositionSituation: interExecutionMemory.currentTrade.takePositionSituation - } - - if (currentDay) { - if (currentDay.valueOf() >= bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf() + ONE_DAY_IN_MILISECONDS) { // Only after the first day we start grabbing the balance from this memory. - - balanceBaseAsset = interExecutionMemory.balanceBaseAsset; - balanceQuotedAsset = interExecutionMemory.balanceQuotedAsset; - - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - } - } - - lastTradeProfitLoss = interExecutionMemory.lastTradeProfitLoss; - profit = interExecutionMemory.profit; - lastTradeROI = interExecutionMemory.lastTradeROI; - - roundtrips = interExecutionMemory.roundtrips; - fails = interExecutionMemory.fails; - hits = interExecutionMemory.hits; - periods = interExecutionMemory.periods; - positionPeriods = interExecutionMemory.positionPeriods; - - distanceToLast.triggerOn = interExecutionMemory.distanceToLast.triggerOn - distanceToLast.triggerOff = interExecutionMemory.distanceToLast.triggerOff - distanceToLast.takePosition = interExecutionMemory.distanceToLast.takePosition - distanceToLast.closePosition = interExecutionMemory.distanceToLast.closePosition - - orderId = interExecutionMemory.orderId; // to be deprecated - messageId = interExecutionMemory.messageId; // to be deprecated - - hitRatio = interExecutionMemory.hitRatio; - ROI = interExecutionMemory.ROI; - anualizedRateOfReturn = interExecutionMemory.anualizedRateOfReturn; - - /* For the case that any of these variables are not updated during the main loop, we need to store their value at the yesterday structure, otherwise it would be lost. */ - - yesterday.stopLoss = stopLoss - yesterday.takeProfit = takeProfit - - yesterday.previousBalanceBaseAsset = previousBalanceBaseAsset - yesterday.previousBalanceQuotedAsset = previousBalanceQuotedAsset - - yesterday.tradePositionRate = tradePositionRate - yesterday.tradePositionSize = tradePositionSize - - yesterday.currentStrategyIndex = currentStrategyIndex; - yesterday.strategyStage = strategyStage; - - yesterday.stopLossPhase = stopLossPhase; - yesterday.stopLossStage = stopLossStage; - - yesterday.takeProfitPhase = takeProfitPhase; - yesterday.takeProfitStage = takeProfitStage; - - yesterday.currentStrategy = { - begin: currentStrategy.begin, - end: currentStrategy.end, - status: currentStrategy.status, - number: currentStrategy.number, - beginRate: currentStrategy.beginRate, - endRate: currentStrategy.endRate, - triggerOnSituation: currentStrategy.triggerOnSituation - } - - yesterday.currentTrade = { - begin: currentTrade.begin, - end: currentTrade.end, - status: currentTrade.status, - profit: currentTrade.profit, - exitType: currentTrade.exitType, - beginRate: currentTrade.beginRate, - endRate: currentTrade.endRate, - takePositionSituation: currentTrade.takePositionSituation - } - - yesterday.lastTradeProfitLoss = lastTradeProfitLoss; - yesterday.profit = profit; - yesterday.lastTradeROI = lastTradeROI; - - yesterday.roundtrips = roundtrips; - yesterday.fails = fails; - yesterday.hits = hits; - yesterday.periods = periods; - yesterday.positionPeriods = positionPeriods; - - yesterday.distanceToLast.triggerOn = distanceToLast.triggerOn - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - yesterday.distanceToLast.takePosition = distanceToLast.takePosition - yesterday.distanceToLast.closePosition = distanceToLast.closePosition - - yesterday.hitRatio = hitRatio; - yesterday.ROI = ROI; - yesterday.anualizedRateOfReturn = anualizedRateOfReturn; - } - - /* Main Array and Maps */ - - let propertyName = 'at' + timeFrameLabel.replace('-', ''); - let candles = chart[propertyName].candles - let currentChart = chart[propertyName] - - /* Last Candle */ - let lastCandle = candles[candles.length - 1]; - - /* Main Simulation Loop: We go thourgh all the candles at this time period. */ - let currentCandleIndex - - /* For Loop Level heartbeat */ - let loopingDay - let previousLoopingDay - - initializeLoop() - - function initializeLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> initializeLoop -> Entering function."); } - - /* Estimate Initial Candle */ - - let firstEnd = candles[0].end - let targetEnd = bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf() - let diff = targetEnd - firstEnd - let amount = diff / timeFrame - - currentCandleIndex = Math.trunc(amount) - if (currentCandleIndex < 0) { currentCandleIndex = 0 } - if (currentCandleIndex > candles.length - 1) { - /* This will happen when the bot.VALUES_TO_USE.timeRange.initialDatetime is beyond the last candle available, meaning that the dataSet needs to be updated with more up-to-date data. */ - currentCandleIndex = candles.length - 1 - } - - loop() - } - - function loop() { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Entering function."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Processing candle # " + currentCandleIndex); } - - let announcementsToBeMade = [] - let candle = candles[currentCandleIndex]; - - /* Not processing while out of user-defined time range */ - - if (candle.end < bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle before the bot.VALUES_TO_USE.timeRange.initialDatetime."); } - controlLoop(); - return - } - if (candle.begin > bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle after the bot.VALUES_TO_USE.timeRange.finalDatetime."); } - afterLoop(); - return - } - - /* Here we update at the chart data structure the objects for each product representing where we are currently standing at the simulation loop */ - if (processingDailyFiles) { - for (let j = 0; j < global.dailyFilePeriods.length; j++) { - - let mapKey = dailyFilePeriods[j][1] - let propertyName = 'at' + mapKey.replace('-', ''); - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Daily') { continue } - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], candle, 'Daily' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement - } - } - } - - /* Finding the Current Element on Market Files */ - for (let j = 0; j < global.marketFilesPeriods.length; j++) { - - let mapKey = marketFilesPeriods[j][1] - let propertyName = 'at' + mapKey.replace('-', ''); - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Multi-Period-Market') {continue} - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let currentElement = getElement(thisChart[pluralVariableName], candle, 'Market' + '-' + mapKey + '-' + pluralVariableName) - thisChart[singularVariableName] = currentElement - } - } - - /* Finding the Current Element on Single Files */ - - function isItInside(elementWithTimestamp, elementWithBeginEnd) { - if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) { - return true - } else { - return false - } - } - - let propertyName = 'atAnyTimeFrame' - let thisChart = chart[propertyName] - - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - if (dataDependencyNode.referenceParent.code.codeName !== 'Single-File') { continue } - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let elementArray = thisChart[pluralVariableName] - let currentElement - if (elementArray !== undefined) { - currentElement = elementArray[elementArray.length - 1] - } - thisChart[singularVariableName] = currentElement - } - - /* While we are processing the previous day. */ - let positionedAtYesterday = false - if (currentDay) { - positionedAtYesterday = (candle.end < currentDay.valueOf()) - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Candle Begin @ " + (new Date(candle.begin)).toLocaleString()) } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Candle End @ " + (new Date(candle.end)).toLocaleString()) } - - let ticker = { - bid: candle.close, - ask: candle.close, - last: candle.close - } - - /* We will produce a simulation level heartbeat in order to inform the user this is running. */ - - loopingDay = new Date(Math.trunc(candle.begin / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) - if (loopingDay.valueOf() !== previousLoopingDay) { - - let processingDate = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2); - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Simulation " + bot.sessionKey + " Loop # " + currentCandleIndex + " @ " + processingDate) } - - /* Telling the world we are alive and doing well */ - let fromDate = new Date(bot.VALUES_TO_USE.timeRange.initialDatetime.valueOf()) - let lastDate = new Date(bot.VALUES_TO_USE.timeRange.finalDatetime.valueOf()) - - let currentDateString = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2); - let currentDate = new Date(loopingDay) - let percentage = global.getPercentage(fromDate, currentDate, lastDate) - bot.processHeartBeat(currentDateString, percentage) - - if (global.areEqualDates(currentDate, new Date()) === false) { - logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage); - } - } - previousLoopingDay = loopingDay.valueOf() - - /* If any of the needed data dependencies is missing for this particular candle, then we jump the candle*/ - /* - for (let k = 0; k < dataDependencies.length; k++) { - let dataDependencyNode = dataDependencies[k] - let singularVariableName = dataDependencyNode.referenceParent.parentNode.code.singularVariableName - let pluralVariableName = dataDependencyNode.referenceParent.parentNode.code.pluralVariableName - let product = currentChart[pluralVariableName] - let currentElement = currentChart[singularVariableName] - - if (currentElement === undefined) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because " + singularVariableName + " is undefined."); } - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> For some indicators it is normal to start after a few candles."); } - controlLoop(); - return - } - } - */ - - periods++; - days = periods * timeFrame / ONE_DAY_IN_MILISECONDS; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.periods = periods - } - - /* We skip the candle at the head of the market because currentCandleIndex has not closed yet. */ - let candlesPerDay = ONE_DAY_IN_MILISECONDS / timeFrame - if (currentCandleIndex === candles.length - 1) { - if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) { - /*We are at the head of the market, thus we skip the last candle because it has not close yet. */ - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet."); } - controlLoop(); - return - /* Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators. - if we skip these two periods, then there will be a hole in the file since the last period will be missing. */ - } - } - - } else { // We are processing Market Files - if (currentCandleIndex === candles.length - 1) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet."); } - controlLoop(); - return - } - } - - let conditions = new Map; // Here we store the conditions values that will be use in the simulator for decision making. - let formulas = new Map; - let conditionsArrayRecord = []; // These are the records that will be saved in a file for the plotter to consume. - let conditionsArrayValues = []; // Here we store the conditions values that will be written on file for the plotter. - let formulasErrors = []; // Here we store the errors produced by all phase formulas. - let formulasValues = []; // Here we store the values produced by all phase formulas. - - /* We define and evaluate all conditions to be used later during the simulation loop. */ - - conditionsArrayRecord.push(candle.begin); - conditionsArrayRecord.push(candle.end); - - for (let j = 0; j < tradingSystem.strategies.length; j++) { - - let strategy = tradingSystem.strategies[j]; - - let positionSize = 0 - let positionRate = 0 - - /* Continue with the rest of the formulas and conditions */ - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.triggerOn !== undefined) { - - for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { - - let situation = triggerStage.triggerOn.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - if (triggerStage.triggerOff !== undefined) { - - for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { - - let situation = triggerStage.triggerOff.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - if (triggerStage.takePosition !== undefined) { - - for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { - - let situation = triggerStage.takePosition.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - - let openStage = strategy.openStage - - if (openStage !== undefined) { - - /* Default Values*/ - if (baseAsset === bot.market.baseAsset) { - positionSize = balanceBaseAsset; - positionRate = candle.close; - } else { - positionSize = balanceQuotedAsset; - positionRate = candle.close; - } - - let initialDefinition = openStage.initialDefinition - - if (initialDefinition !== undefined) { - - if (tradePositionSize !== 0) { - positionSize = tradePositionSize - } else { - if (initialDefinition.positionSize !== undefined) { - if (initialDefinition.positionSize.formula !== undefined) { - try { - positionSize = eval(initialDefinition.positionSize.formula.code); - } catch (err) { - initialDefinition.positionSize.formula.error = err.message - } - if (isNaN(positionSize)) { - if (baseAsset === bot.market.baseAsset) { - positionSize = balanceBaseAsset; - } else { - positionSize = balanceQuotedAsset; - } - } else { - if (baseAsset === bot.market.baseAsset) { - if (positionSize > balanceBaseAsset) { positionSize = balanceBaseAsset } - } else { - if (positionSize > balanceQuotedAsset) { positionSize = balanceQuotedAsset } - } - } - } - } - } - - if (tradePositionRate !== 0) { - positionRate = tradePositionRate - } else { - if (initialDefinition.positionRate !== undefined) { - if (initialDefinition.positionRate.formula !== undefined) { - try { - positionRate = eval(initialDefinition.positionRate.formula.code); - } catch (err) { - initialDefinition.positionRate.formula.error = err.message - } - if (isNaN(positionRate)) { - if (baseAsset === bot.market.baseAsset) { - positionRate = candle.close; - } else { - positionRate = candle.close; - } - } - } - } - } - - if (initialDefinition.stopLoss !== undefined) { - - for (let p = 0; p < initialDefinition.stopLoss.phases.length; p++) { - - let phase = initialDefinition.stopLoss.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaError = "Formula evaluates to Infinity." - formulaValue = MAX_STOP_LOSS_VALUE - if (stopLossStage === 'Open Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_STOP_LOSS_VALUE) { - formulaValue = MIN_STOP_LOSS_VALUE - if (stopLossStage === 'Open Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - - if (initialDefinition.takeProfit !== undefined) { - - for (let p = 0; p < initialDefinition.takeProfit.phases.length; p++) { - - let phase = initialDefinition.takeProfit.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaValue = MAX_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Open Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_TAKE_PROFIT_VALUE) { - formulaValue = MIN_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Open Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> initialDefinition.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - } - - strategy.positionSize = positionSize - strategy.positionRate = positionRate - } - - let manageStage = strategy.manageStage - - if (manageStage !== undefined) { - - if (manageStage.stopLoss !== undefined) { - - for (let p = 0; p < manageStage.stopLoss.phases.length; p++) { - - let phase = manageStage.stopLoss.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaError = "" - formulaValue = MAX_STOP_LOSS_VALUE - if (stopLossStage === 'Manage Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.stopLoss -> MAX_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_STOP_LOSS_VALUE) { - formulaValue = MIN_STOP_LOSS_VALUE - if (stopLossStage === 'Manage Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_STOP_LOSS_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.stopLoss -> MIN_STOP_LOSS_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - - if (manageStage.takeProfit !== undefined) { - - for (let p = 0; p < manageStage.takeProfit.phases.length; p++) { - - let phase = manageStage.takeProfit.phases[p]; - - /* Evaluate Formula */ - let formulaValue - let formulaError = '' - - if (phase.formula !== undefined) { - try { - formulaValue = eval(phase.formula.code); - if (formulaValue === Infinity) { - formulaError = "Formula evaluates to Infinity." - formulaValue = MAX_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Manage Stage') { - formulaError = "WARNING: Formula evaluates to Infinity." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.takeProfit -> MAX_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - } catch (err) { - if (phase.formula.code.indexOf('previous') > 0 && err.message.indexOf('of undefined') > 0) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - formulaError = err.message - } - } - if (isNaN(formulaValue)) { formulaValue = 0; } - if (formulaValue < MIN_TAKE_PROFIT_VALUE) { - formulaValue = MIN_TAKE_PROFIT_VALUE - if (takeProfitStage === 'Manage Stage') { - formulaError = "WARNING: Formula is evaluating below the MIN_TAKE_PROFIT_VALUE." - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] runSimulation -> loop -> manageStage.takeProfit -> MIN_TAKE_PROFIT_VALUE -> formulaError = " + formulaError); } - } - } - - formulasErrors.push('"' + formulaError + '"') - formulasValues.push(formulaValue) - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p; - formulas.set(key, formulaValue) - } - - /* next phase event */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - - /* move to phase events */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; - - if (condition.javascriptCode !== undefined) { - newCondition(key, condition.javascriptCode, chart); - } - } - } - } - } - } - } - } - - function newCondition(key, node, chart) { - - let condition; - let error = '' - let value - - try { - value = eval(node.code); - } catch (err) { - /* - One possible error is that the conditions references a .previous that is undefined. For this - reason and others, we will simply set the value to false. - */ - value = false - - if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1 || - node.code.indexOf('chart') > -1 && err.message.indexOf('of undefined') > -1 - ) { - /* - We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined. - */ - } else { - node.error = err.message + " @ " + (new Date(candle.begin)).toLocaleString() - } - } - - condition = { - key: key, - value: value - }; - - conditions.set(condition.key, condition); - - if (condition.value) { - conditionsArrayValues.push(1); - } else { - conditionsArrayValues.push(0); - } - } - } - - /* Trigger On Conditions */ - if ( - strategyStage === 'No Stage' && - currentStrategyIndex === -1 - ) { - let minimumBalance - let maximumBalance - let balance - - if (baseAsset === bot.market.baseAsset) { - balance = balanceBaseAsset - minimumBalance = bot.VALUES_TO_USE.minimumBalanceA - maximumBalance = bot.VALUES_TO_USE.maximumBalanceA - } else { - balance = balanceQuotedAsset - minimumBalance = bot.VALUES_TO_USE.minimumBalanceB - maximumBalance = bot.VALUES_TO_USE.maximumBalanceB - } - - let stopRunningDate = new Date(candle.begin) - if (balance <= minimumBalance) { - tradingSystem.error = "Min Balance @ " + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> " + tradingSystem.error ); } - afterLoop() - return - } - - if (balance >= maximumBalance) { - tradingSystem.error = "Max Balance @ " + stopRunningDate.toLocaleString() - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> " + tradingSystem.error); } - afterLoop() - return - } - - /* - Here we need to pick a strategy, or if there is not suitable strategy for the current - market conditions, we pass until the next period. - - To pick a new strategy we will evaluate what we call the trigger on. Once we enter - into one strategy, we will ignore market conditions for others. However there is also - a strategy trigger off which can be hit before taking a position. If hit, we would - be outside a strategy again and looking for the condition to enter all over again. - - */ - - for (let j = 0; j < tradingSystem.strategies.length; j++) { - - let strategy = tradingSystem.strategies[j]; - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.triggerOn !== undefined) { - - for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) { - - let situation = triggerStage.triggerOn.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + 'triggerStage' + '-' + 'triggerOn' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - strategyStage = 'Trigger Stage'; - checkAnnouncements(triggerStage) - - currentStrategyIndex = j; - currentStrategy.begin = candle.begin; - currentStrategy.beginRate = candle.min; - currentStrategy.endRate = candle.min; // In case the strategy does not get exited - currentStrategy.triggerOnSituation = situation.name - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.currentStrategyIndex = currentStrategyIndex; - yesterday.currentStrategy.begin = currentStrategy.begin; - yesterday.currentStrategy.beginRate = currentStrategy.beginRate; - yesterday.currentStrategy.endRate = currentStrategy.endRate; - yesterday.currentStrategy.triggerOnSituation = currentStrategy.triggerOnSituation; - } - } - - distanceToLast.triggerOn = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOn = distanceToLast.triggerOn - } - } - - checkAnnouncements(triggerStage.triggerOn) - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Switching to Trigger Stage because conditions at Trigger On Event were met."); } - break; - } - } - } - } - } - - } - - /* Trigger Off Condition */ - if (strategyStage === 'Trigger Stage') { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.triggerOff !== undefined) { - - for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) { - - let situation = triggerStage.triggerOff.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = currentStrategyIndex + '-' + 'triggerStage' + '-' + 'triggerOff' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - currentStrategy.number = currentStrategyIndex - currentStrategy.end = candle.end; - currentStrategy.endRate = candle.min; - currentStrategy.status = 1; // This means the strategy is closed, i.e. that has a begin and end. - strategyStage = 'No Stage'; - currentStrategyIndex = -1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentStrategy.number = currentStrategy.number; - yesterday.currentStrategy.end = currentStrategy.end; - yesterday.currentStrategy.endRate = currentStrategy.endRate; - yesterday.currentStrategy.status = currentStrategy.status; - yesterday.strategyStage = strategyStage; - yesterday.currentStrategyIndex = currentStrategyIndex; - } - } - - distanceToLast.triggerOff = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - } - } - - checkAnnouncements(triggerStage.triggerOff) - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Switching to No Stage because conditions at the Trigger Off Event were met."); } - break; - } - } - } - } - } - - /* Take Position Condition */ - if (strategyStage === 'Trigger Stage') { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let triggerStage = strategy.triggerStage - - if (triggerStage !== undefined) { - - if (triggerStage.takePosition !== undefined) { - - for (let k = 0; k < triggerStage.takePosition.situations.length; k++) { - - let situation = triggerStage.takePosition.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = currentStrategyIndex + '-' + 'triggerStage' + '-' + 'takePosition' + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - type = '"Take Position"'; - - strategyStage = 'Open Stage'; - checkAnnouncements(strategy.openStage) - - stopLossStage = 'Open Stage'; - takeProfitStage = 'Open Stage'; - stopLossPhase = 0; - takeProfitPhase = 0; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - yesterday.stopLossPhase = stopLossPhase; - yesterday.takeProfitPhase = takeProfitPhase; - } - } - - takePositionNow = true - currentTrade.takePositionSituation = situation.name - - checkAnnouncements(triggerStage.takePosition) - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Conditions at the Take Position Event were met."); } - break; - } - } - } - } - } - - /* Stop Loss Management */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') && - takePositionNow !== true - ) { - - checkStopPhases() - calculateStopLoss(); - - } - - function checkStopPhases() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let j = currentStrategyIndex - let stageKey - let initialDefinitionKey = '' - let p - - if (stopLossStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.stopLoss !== undefined) { - parentNode = openStage.initialDefinition - initialDefinitionKey = '-' + 'initialDefinition' - stageKey = 'openStage' - p = stopLossPhase - } - } - } - - if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.stopLoss !== undefined) { - parentNode = manageStage - stageKey = 'manageStage' - p = stopLossPhase - 1 - } - } - - if (parentNode !== undefined) { - let phase = parentNode.stopLoss.phases[p]; - - /* Check the next Phase Event. */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - stopLossPhase++; - stopLossStage = 'Manage Stage' - if (takeProfitPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Take Profit') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLossPhase = stopLossPhase; - yesterday.stopLossStage = stopLossStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(nextPhaseEvent) - return; - } - } - } - - /* Check the Move to Phase Events. */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'stopLoss' + '-' + p + '-' + n + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < parentNode.stopLoss.phases.length; q++) { - if (parentNode.stopLoss.phases[q].id === moveToPhase.id) { - stopLossPhase = q + 1 - } - } - } else { - moveToPhaseEvent.error = "This Node needs to reference a Phase." - continue - } - - stopLossStage = 'Manage Stage' - if (takeProfitPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Take Profit') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLossPhase = stopLossPhase; - yesterday.stopLossStage = stopLossStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(moveToPhaseEvent) - return; - } - } - } - } - } - } - - function calculateStopLoss() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - let key - - if (stopLossStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.stopLoss !== undefined) { - phase = openStage.initialDefinition.stopLoss.phases[stopLossPhase]; - key = currentStrategyIndex + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'stopLoss' + '-' + (stopLossPhase); - } - } - } - - if (stopLossStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.stopLoss !== undefined) { - phase = manageStage.stopLoss.phases[stopLossPhase - 1]; - key = currentStrategyIndex + '-' + 'manageStage' + '-' + 'stopLoss' + '-' + (stopLossPhase - 1); - } - } - - if (phase !== undefined) { - if (phase.formula !== undefined) { - let previousValue = stopLoss - - stopLoss = formulas.get(key) - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - - if (stopLoss !== previousValue) { - checkAnnouncements(phase, stopLoss) - } - } - } - } - - /* Take Profit Management */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') && - takePositionNow !== true - ) { - - checkTakeProfitPhases(); - calculateTakeProfit(); - - } - - function checkTakeProfitPhases() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let parentNode - let j = currentStrategyIndex - let stageKey - let initialDefinitionKey = '' - let p - - if (takeProfitStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.takeProfit !== undefined) { - parentNode = openStage.initialDefinition - initialDefinitionKey = '-' + 'initialDefinition' - stageKey = 'openStage' - p = takeProfitPhase - } - } - } - - if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.takeProfit !== undefined) { - parentNode = manageStage - stageKey = 'manageStage' - p = takeProfitPhase - 1 - } - } - - if (parentNode !== undefined) { - let phase = parentNode.takeProfit.phases[p]; - if (phase === undefined) {return} // trying to jump to a phase that does not exists. - - /* Check the next Phase Event. */ - let nextPhaseEvent = phase.nextPhaseEvent; - if (nextPhaseEvent !== undefined) { - - for (let k = 0; k < nextPhaseEvent.situations.length; k++) { - - let situation = nextPhaseEvent.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - takeProfitPhase++; - takeProfitStage = 'Manage Stage' - if (stopLossPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Stop') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfitPhase = takeProfitPhase; - yesterday.takeProfitStage = takeProfitStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(nextPhaseEvent) - return; - } - } - } - - /* Check the Move to Phase Events. */ - for (let n = 0; n < phase.moveToPhaseEvents.length; n++) { - let moveToPhaseEvent = phase.moveToPhaseEvents[n]; - if (moveToPhaseEvent !== undefined) { - - for (let k = 0; k < moveToPhaseEvent.situations.length; k++) { - - let situation = moveToPhaseEvent.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = j + '-' + stageKey + initialDefinitionKey + '-' + 'takeProfit' + '-' + p + '-' + n + '-' + k + '-' + m; - - let value = false - if (conditions.get(key) !== undefined) { - value = conditions.get(key).value; - } - - if (value === false) { passed = false; } - } - - if (passed) { - - let moveToPhase = moveToPhaseEvent.referenceParent - if (moveToPhase !== undefined) { - for (let q = 0; q < parentNode.takeProfit.phases.length; q++) { - if (parentNode.takeProfit.phases[q].id === moveToPhase.id) { - takeProfitPhase = q + 1 - } - } - } else { - moveToPhaseEvent.error = "This Node needs to reference a Phase." - continue - } - - takeProfitStage = 'Manage Stage' - if (stopLossPhase > 0) { - strategyStage = 'Manage Stage' - checkAnnouncements(manageStage, 'Stop') - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfitPhase = takeProfitPhase; - yesterday.takeProfitStage = takeProfitStage; - yesterday.strategyStage = strategyStage; - } - } - - checkAnnouncements(moveToPhaseEvent) - return; - } - } - } - } - } - } - - function calculateTakeProfit() { - - let strategy = tradingSystem.strategies[currentStrategyIndex]; - let openStage = strategy.openStage - let manageStage = strategy.manageStage - let phase - let key - - if (takeProfitStage === 'Open Stage' && openStage !== undefined) { - if (openStage.initialDefinition !== undefined) { - if (openStage.initialDefinition.takeProfit !== undefined) { - phase = openStage.initialDefinition.takeProfit.phases[takeProfitPhase]; - key = currentStrategyIndex + '-' + 'openStage' + '-' + 'initialDefinition' + '-' + 'takeProfit' + '-' + (takeProfitPhase); - } - } - } - - if (takeProfitStage === 'Manage Stage' && manageStage !== undefined) { - if (manageStage.takeProfit !== undefined) { - phase = manageStage.takeProfit.phases[takeProfitPhase - 1]; - key = currentStrategyIndex + '-' + 'manageStage' + '-' + 'takeProfit' + '-' + (takeProfitPhase - 1); - } - } - - if (phase !== undefined) { - if (phase.formula !== undefined) { - - let previousValue = stopLoss - - takeProfit = formulas.get(key) - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - - if (takeProfit !== previousValue) { - checkAnnouncements(phase, takeProfit) - } - } - } - } - - /* Keeping Position Counters Up-to-date */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') - ) { - - if (takePositionNow === true) { - positionPeriods = 0 - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.positionPeriods = 0 - } - } - } - - positionPeriods++; - positionDays = positionPeriods * timeFrame / ONE_DAY_IN_MILISECONDS; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.positionPeriods = positionPeriods - } - } - } else { - positionPeriods = 0 - positionDays = 0 - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.positionPeriods = 0 - } - } - } - - /* Keeping Distance Counters Up-to-date */ - if ( - distanceToLast.triggerOn > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.triggerOn++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOn = distanceToLast.triggerOn - } - } - } - - if ( - distanceToLast.triggerOff > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.triggerOff++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - } - } - } - - if ( - distanceToLast.takePosition > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.takePosition++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.takePosition = distanceToLast.takePosition - } - } - } - - if ( - distanceToLast.closePosition > 0 // with this we avoind counting before the first event happens. - ) { - distanceToLast.closePosition++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.takePosition = distanceToLast.closePosition - } - } - } - - /* Checking if Stop or Take Profit were hit */ - if ( - (strategyStage === 'Open Stage' || strategyStage === 'Manage Stage') && - takePositionNow !== true - ) { - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - /* Checking what happened since the last execution. We need to know if the Stop Loss - or our Take Profit were hit. */ - - /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ - - if ((baseAsset === bot.market.baseAsset && candle.max >= stopLoss) || (baseAsset !== bot.market.baseAsset && candle.min <= stopLoss)) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Stop Loss was hit."); } - /* - Hit Point Validation - - This prevents misscalculations when a formula places the stop loss in this case way beyond the market price. - If we take the stop loss value at those situation would be a huge distortion of facts. - */ - - if (baseAsset === bot.market.baseAsset) { - if (stopLoss < candle.min) { - stopLoss = candle.min - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - } - } else { - if (stopLoss > candle.max) { - stopLoss = candle.max - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - } - } - - let slippedStopLoss = stopLoss - - /* Apply the Slippage */ - let slippageAmount = slippedStopLoss * bot.VALUES_TO_USE.slippage.stopLoss / 100 - - if (baseAsset === bot.market.baseAsset) { - slippedStopLoss = slippedStopLoss + slippageAmount - } else { - slippedStopLoss = slippedStopLoss - slippageAmount - } - - closeRate = slippedStopLoss; - - marketRate = closeRate; - type = '"Close@StopLoss"'; - strategyStage = 'Close Stage'; - checkAnnouncements(strategy.closeStage, 'Stop') - - stopLossStage = 'No Stage'; - takeProfitStage = 'No Stage'; - currentTrade.end = candle.end; - currentTrade.status = 1; - currentTrade.exitType = 1; - currentTrade.endRate = closeRate; - - closePositionNow = true; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - yesterday.currentTrade.end = currentTrade.end; - yesterday.currentTrade.status = currentTrade.status; - yesterday.currentTrade.exitType = currentTrade.exitType; - yesterday.currentTrade.endRate = currentTrade.endRate; - } - } - } - - /* Take Profit condition: Here we verify if the Take Profit was hit or not. */ - - if ((baseAsset === bot.market.baseAsset && candle.min <= takeProfit) || (baseAsset !== bot.market.baseAsset && candle.max >= takeProfit)) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Take Profit was hit."); } - /* - Hit Point Validation: - - This prevents misscalculations when a formula places the take profit in this case way beyond the market price. - If we take the stop loss value at those situation would be a huge distortion of facts. - */ - - if (baseAsset === bot.market.baseAsset) { - if (takeProfit > candle.max) { - takeProfit = candle.max - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - } - } else { - if (takeProfit < candle.min) { - takeProfit = candle.min - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - } - } - - let slippedTakeProfit = takeProfit - /* Apply the Slippage */ - let slippageAmount = slippedTakeProfit * bot.VALUES_TO_USE.slippage.takeProfit / 100 - - if (baseAsset === bot.market.baseAsset) { - slippedTakeProfit = slippedTakeProfit + slippageAmount - } else { - slippedTakeProfit = slippedTakeProfit - slippageAmount - } - - closeRate = slippedTakeProfit; - - marketRate = closeRate; - type = '"Close@TakeProfit"'; - strategyStage = 'Close Stage'; - checkAnnouncements(strategy.closeStage, 'Take Profit') - - stopLossStage = 'No Stage'; - takeProfitStage = 'No Stage'; - - currentTrade.end = candle.end; - currentTrade.status = 1; - currentTrade.exitType = 2; - currentTrade.endRate = closeRate; - - closePositionNow = true; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.strategyStage = strategyStage; - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - - yesterday.currentTrade.end = currentTrade.end; - yesterday.currentTrade.status = currentTrade.status; - yesterday.currentTrade.exitType = currentTrade.exitType; - yesterday.currentTrade.endRate = currentTrade.endRate; - } - } - } - } - - /* 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 */ - distanceToLast.takePosition = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.takePosition = distanceToLast.takePosition - } - } - - /* Position size and rate */ - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - tradePositionSize = strategy.positionSize; - tradePositionRate = strategy.positionRate; - - /* We take what was calculated at the formula and apply the slippage. */ - let slippageAmount = tradePositionRate * bot.VALUES_TO_USE.slippage.positionRate / 100 - - if (baseAsset === bot.market.baseAsset) { - tradePositionRate = tradePositionRate - slippageAmount - } else { - tradePositionRate = tradePositionRate + slippageAmount - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.tradePositionSize = tradePositionSize; - yesterday.tradePositionRate = tradePositionRate; - } - } - - /* Update the trade record information. */ - currentTrade.begin = candle.begin; - currentTrade.beginRate = tradePositionRate; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentTrade.begin = currentTrade.begin; - yesterday.currentTrade.beginRate = currentTrade.beginRate; - } - } - - /* 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 (interExecutionMemory.executionContext !== undefined) { - switch (interExecutionMemory.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 bot.VALUES_TO_USE.timeRange.initialDatetime and the bot.VALUES_TO_USE.timeRange.finalDatetime range */ - if (bot.VALUES_TO_USE.timeRange.initialDatetime !== undefined) { - if (candle.end < bot.VALUES_TO_USE.timeRange.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. -> bot.VALUES_TO_USE.timeRange.initialDatetime = " + bot.VALUES_TO_USE.timeRange.initialDatetime); } - takePositionAtSimulation() - return; - } - } - - /*We wont take a position if we are past the final datetime */ - if (bot.VALUES_TO_USE.timeRange.finalDatetime !== undefined) { - if (candle.begin > bot.VALUES_TO_USE.timeRange.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. -> bot.VALUES_TO_USE.timeRange.finalDatetime = " + bot.VALUES_TO_USE.timeRange.finalDatetime); } - takePositionAtSimulation() - return; - } - } - - /* Mechanism to avoid putting the same order over and over again at different executions of the simulation engine. */ - if (interExecutionMemory.executionContext !== undefined) { - if (interExecutionMemory.executionContext.periods !== undefined) { - if (periods <= interExecutionMemory.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 (baseAsset === bot.market.baseAsset) { - orderSide = "sell" - - orderPrice = tradePositionRate - 100 // This is going to be ingnored at the Exchange API for now since we only put market orders. - - amountA = tradePositionSize * orderPrice - amountB = tradePositionSize - - } else { - orderSide = "buy" - - orderPrice = tradePositionRate // This is going to be ingnored at the Exchange API for now since we only put market orders. - - amountA = tradePositionSize - amountB = tradePositionSize / orderPrice - - } - - interExecutionMemory.executionContext = { - status: "Taking Position", - periods: 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 "); } - interExecutionMemory.executionContext = { - status: "In a Position", - periods: 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(); - - marketRate = candle.close; - - previousBalanceBaseAsset = balanceBaseAsset; - previousBalanceQuotedAsset = balanceQuotedAsset; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.previousBalanceBaseAsset = previousBalanceBaseAsset; - yesterday.previousBalanceQuotedAsset = previousBalanceQuotedAsset; - } - } - - lastTradeProfitLoss = 0; - lastTradeROI = 0; - - let feePaid = 0 - - if (baseAsset === bot.market.baseAsset) { - - feePaid = tradePositionSize * tradePositionRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceQuotedAsset = balanceQuotedAsset + tradePositionSize * tradePositionRate - feePaid; - balanceBaseAsset = balanceBaseAsset - tradePositionSize; - } else { - - feePaid = tradePositionSize / tradePositionRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceBaseAsset = balanceBaseAsset + tradePositionSize / tradePositionRate - feePaid; - balanceQuotedAsset = balanceQuotedAsset - tradePositionSize; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - yesterday.lastTradeProfitLoss = lastTradeProfitLoss; - yesterday.lastTradeROI = lastTradeROI; - } - } - - addRecord(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> takePositionAtSimulation -> Exiting Loop Body after taking position at simulation."); } - controlLoop(); - return - } - } - - if (closePositionNow === true) { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing a Position -> Entering code block."); } - - closePositionNow = false - - /* Inicializing this counter */ - distanceToLast.closePosition = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.closePosition = distanceToLast.closePosition - } - } - - /* Position size and rate */ - let strategy = tradingSystem.strategies[currentStrategyIndex]; - - 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 (interExecutionMemory.executionContext !== undefined) { - switch (interExecutionMemory.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 (interExecutionMemory.executionContext !== undefined) { - if (interExecutionMemory.executionContext.periods !== undefined) { - if (periods <= interExecutionMemory.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 (baseAsset === bot.market.baseAsset) { - 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 = balanceQuotedAsset - amountB = balanceQuotedAsset / 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 = balanceBaseAsset * orderPrice - amountB = balanceBaseAsset - - } - - interExecutionMemory.executionContext = { - status: "Closing Position", - periods: 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 "); } - interExecutionMemory.executionContext = { - status: "Position Closed", - periods: 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."); } - - roundtrips++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.roundtrips = roundtrips - } - } - - let feePaid = 0 - - if (baseAsset === bot.market.baseAsset) { - strategy.positionSize = balanceQuotedAsset / closeRate; - strategy.positionRate = closeRate; - - feePaid = balanceQuotedAsset / closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceBaseAsset = balanceBaseAsset + balanceQuotedAsset / closeRate - feePaid; - balanceQuotedAsset = 0; - } else { - strategy.positionSize = balanceBaseAsset * closeRate; - strategy.positionRate = closeRate; - - feePaid = balanceBaseAsset * closeRate * bot.VALUES_TO_USE.feeStructure.taker / 100 - - balanceQuotedAsset = balanceQuotedAsset + balanceBaseAsset * closeRate - feePaid; - balanceBaseAsset = 0; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.balanceBaseAsset = balanceBaseAsset; - yesterday.balanceQuotedAsset = balanceQuotedAsset; - - } - } - - if (baseAsset === bot.market.baseAsset) { - lastTradeProfitLoss = balanceBaseAsset - previousBalanceBaseAsset; - lastTradeROI = lastTradeProfitLoss * 100 / tradePositionSize; - if (isNaN(lastTradeROI)) { lastTradeROI = 0; } - profit = balanceBaseAsset - bot.VALUES_TO_USE.initialBalanceA; - } else { - lastTradeProfitLoss = balanceQuotedAsset - previousBalanceQuotedAsset; - lastTradeROI = lastTradeProfitLoss * 100 / tradePositionSize; - if (isNaN(lastTradeROI)) { lastTradeROI = 0; } - profit = balanceQuotedAsset - bot.VALUES_TO_USE.initialBalanceB; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.lastTradeProfitLoss = lastTradeProfitLoss; - yesterday.profit = profit; - yesterday.lastTradeROI = lastTradeROI; - } - } - - currentTrade.lastTradeROI = lastTradeROI; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentTrade.lastTradeROI = currentTrade.lastTradeROI; - } - } - - if (lastTradeProfitLoss > 0) { - hits++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.hits = hits - } - } - - } else { - fails++; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.fails = fails - } - } - } - - if (baseAsset === bot.market.baseAsset) { - ROI = (bot.VALUES_TO_USE.initialBalanceA + profit) / bot.VALUES_TO_USE.initialBalanceA - 1; - hitRatio = hits / roundtrips; - anualizedRateOfReturn = ROI / days * 365; - } else { - ROI = (bot.VALUES_TO_USE.initialBalanceB + profit) / bot.VALUES_TO_USE.initialBalanceB - 1; - hitRatio = hits / roundtrips; - anualizedRateOfReturn = ROI / days * 365; - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.ROI = ROI; - yesterday.hitRatio = hitRatio; - yesterday.anualizedRateOfReturn = anualizedRateOfReturn; - } - } - - addRecord(); - - stopLoss = 0; - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLoss = stopLoss - } - } - takeProfit = 0; - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.takeProfit = takeProfit - } - } - - tradePositionRate = 0; - tradePositionSize = 0; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.tradePositionSize = tradePositionSize; - yesterday.tradePositionRate = tradePositionRate; - } - } - - timerToCloseStage = candle.begin - stopLossStage = 'No Stage'; - takeProfitStage = 'No Stage'; - stopLossPhase = -1; - takeProfitPhase = -1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.stopLossStage = stopLossStage; - yesterday.takeProfitStage = takeProfitStage; - yesterday.stopLossPhase = stopLossPhase; - yesterday.takeProfitPhase = takeProfitPhase; - } - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> closePositionAtSimulation -> Exiting Loop Body after closing position at simulation."); } - controlLoop(); - return - } - } - - /* Closing the Closing Stage */ - if (strategyStage === 'Close Stage') { - if (candle.begin - 5 * 60 * 1000 > timerToCloseStage) { - - currentStrategy.number = currentStrategyIndex - currentStrategy.end = candle.end; - currentStrategy.endRate = candle.min; - currentStrategy.status = 1; // This means the strategy is closed, i.e. that has a begin and end. - - if (processingDailyFiles) { - if (positionedAtYesterday) { - - yesterday.currentStrategy.number = currentStrategy.number; - yesterday.currentStrategy.end = currentStrategy.end; - yesterday.currentStrategy.endRate = currentStrategy.endRate; - yesterday.currentStrategy.status = currentStrategy.status; - } - } - - currentStrategyIndex = -1; - strategyStage = 'No Stage'; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentStrategyIndex = currentStrategyIndex; - yesterday.strategyStage = strategyStage; - } - } - - timerToCloseStage = 0 - - distanceToLast.triggerOff = 1; - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.distanceToLast.triggerOff = distanceToLast.triggerOff - } - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing the Closing Stage -> Exiting Close Stage."); } - } else { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Closing the Closing Stage -> Waiting for timer."); } - } - } - - /* Not a buy or sell condition */ - - marketRate = candle.close; - addRecord(); - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Exiting Loop Body after adding a record."); } - controlLoop(); - return - - function addRecord() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> addRecord -> Entering function."); } - - // Since we are going to write the message to a file that the Simulation Executor is going to read, we use the abbreviations. - let messageType; - let message; - let simulationRecord; - let executionRecord; - - let strategyStageNumber - switch (strategyStage) { - case 'No Stage': { - strategyStageNumber = 0 - break - } - case 'Trigger Stage': { - strategyStageNumber = 1 - break - } - case 'Open Stage': { - strategyStageNumber = 2 - break - } - case 'Manage Stage': { - strategyStageNumber = 3 - break - } - case 'Close Stage': { - strategyStageNumber = 4 - break - } - } - - if (balanceBaseAsset === Infinity) { - balanceBaseAsset = Number.MAX_SAFE_INTEGER - } - - if (balanceQuotedAsset === Infinity) { - balanceQuotedAsset = Number.MAX_SAFE_INTEGER - } - - let quotedBaseAsset = '"' + baseAsset + '"' - let quotedQuotedAsset = '"' + quotedAsset + '"' - - simulationRecord = { - begin: candle.begin, - end: candle.end, - type: type, - marketRate: marketRate, - amount: 1, - balanceA: balanceBaseAsset, - balanceB: balanceQuotedAsset, - profit: profit, - lastTradeProfitLoss: lastTradeProfitLoss, - stopLoss: stopLoss, - roundtrips: roundtrips, - hits: hits, - fails: fails, - hitRatio: hitRatio, - ROI: ROI, - periods: periods, - days: days, - anualizedRateOfReturn: anualizedRateOfReturn, - positionRate: tradePositionRate, - lastTradeROI: lastTradeROI, - strategy: currentStrategyIndex, - strategyStageNumber: strategyStageNumber, - takeProfit: takeProfit, - stopLossPhase: stopLossPhase, - takeProfitPhase: takeProfitPhase, - executionRecord: '', - positionSize: tradePositionSize, - initialBalanceA: bot.VALUES_TO_USE.initialBalanceA, - minimumBalanceA: bot.VALUES_TO_USE.minimumBalanceA, - maximumBalanceA: bot.VALUES_TO_USE.maximumBalanceA, - initialBalanceB: bot.VALUES_TO_USE.initialBalanceB, - minimumBalanceB: bot.VALUES_TO_USE.minimumBalanceB, - maximumBalanceB: bot.VALUES_TO_USE.maximumBalanceB, - baseAsset: quotedBaseAsset, - quotedAsset: quotedQuotedAsset, - marketBaseAsset: '"' + bot.market.baseAsset + '"', - marketQuotedAsset: '"' + bot.market.quotedAsset + '"' , - positionPeriods: positionPeriods, - positionDays: positionDays, - distanceToLastTriggerOn: distanceToLast.triggerOn, - distanceToLastTriggerOff: distanceToLast.triggerOff, - distanceToLastTakePosition: distanceToLast.takePosition, - distanceToLastClosePosition: distanceToLast.closePosition - } - - recordsArray.push(simulationRecord); - - type = '""'; - - /* Prepare the information for the Conditions File */ - - conditionsArrayRecord.push(currentStrategyIndex); - conditionsArrayRecord.push(strategyStageNumber); - conditionsArrayRecord.push(stopLossPhase); - conditionsArrayRecord.push(takeProfitPhase); - conditionsArrayRecord.push(conditionsArrayValues); - conditionsArrayRecord.push(formulasErrors); - conditionsArrayRecord.push(formulasValues); - - conditionsArray.push(conditionsArrayRecord); - - /* - 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 (currentStrategy.begin !== 0 && currentStrategy.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { - currentStrategy.status = 2; // This means the strategy is open, i.e. that has a begin but no end. - currentStrategy.end = candle.end - } - - /* Prepare the information for the Strategies File*/ - if (currentStrategy.begin !== 0 && currentStrategy.end !== 0) - { - strategiesArray.push(currentStrategy); - - currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '' - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentStrategy = { - begin: 0, - end: 0, - status: 0, - number: 0, - beginRate: 0, - endRate: 0, - triggerOnSituation: '' - } - } - } - } - - /* - 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 (currentTrade.begin !== 0 && currentTrade.end === 0 && currentCandleIndex === candles.length - 2 && lastCandle.end !== lastInstantOfTheDay) { - currentTrade.status = 2; // This means the trade is open - currentTrade.end = candle.end - currentTrade.endRate = candle.close - - /* Here we will calculate the ongoing ROI */ - if (baseAsset === bot.market.baseAsset) { - currentTrade.lastTradeROI = (tradePositionRate - candle.close) / tradePositionRate * 100 - } else { - currentTrade.lastTradeROI = (candle.close - tradePositionRate) / tradePositionRate * 100 - } - } - - /* Prepare the information for the Trades File */ - if (currentTrade.begin !== 0 && currentTrade.end !== 0) { - - currentTrade.profit = lastTradeProfitLoss; - - tradesArray.push(currentTrade); - - currentTrade = { - begin: 0, - end: 0, - status: 0, - lastTradeROI: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - - if (processingDailyFiles) { - if (positionedAtYesterday) { - yesterday.currentTrade = { - begin: 0, - end: 0, - status: 0, - lastTradeROI: 0, - exitType: 0, - beginRate: 0, - endRate: 0, - takePositionSituation: '' - } - } - } - } - - makeAnnoucements() // After everything at the simulation level was done, we will do the annoucements that are pending. - } - - function checkAnnouncements(node, value) { - /* - Value is an optional parameter that represents the value that the announcement is monitoring for change (for numeric values only). - If we do receive this value, we will only make the annoucement if the variance is grater than the user pre-defined value - for this variance. - */ - - if (node.announcements !== undefined) { - for (let i = 0; i < node.announcements.length; i++) { - let announcement = node.announcements[i] - let key = node.type + "-" + announcement.name + "-" + announcement.id - - let lastPeriodAnnounced = -1 - let newAnnouncementRecord = {} - - for (let j = 0; j < interExecutionMemory.announcements.length; j++) { - let announcementRecord = interExecutionMemory.announcements[j] - if (announcementRecord.key === key) { - lastPeriodAnnounced = announcementRecord.periods - newAnnouncementRecord = announcementRecord - break - } - } - - if (periods > lastPeriodAnnounced) { - - if (isNaN(value) === false) { - /* The Value Variation is what tells us how much the value already announced must change in order to annouce it again. */ - let valueVariation - - let code = announcement.code - valueVariation = code.valueVariation - - if (newAnnouncementRecord.value !== undefined && valueVariation !== undefined) { - let upperLimit = newAnnouncementRecord.value + newAnnouncementRecord.value * valueVariation / 100 - let lowerLimit = newAnnouncementRecord.value - newAnnouncementRecord.value * valueVariation / 100 - if (value > lowerLimit && value < upperLimit) { - /* There is not enough variation to announce this again. */ - return - } - } - } - - /* - We store the announcement temporarily at an Array to differ its execution, becasue we need to evaulate its formula - and at this point in time the potential variables used at the formula are still not set. - */ - announcement.value = value - announcementsToBeMade.push(announcement) - - /* Next, we will remmeber this announcement was already done, so that it is not announced again in further processing of the same day. */ - if (newAnnouncementRecord.periods !== undefined) { - newAnnouncementRecord.periods = periods - newAnnouncementRecord.value = value - } else { - newAnnouncementRecord = { - key: key, - periods: periods, - value: value - } - interExecutionMemory.announcements.push(newAnnouncementRecord) - } - } - } - } - } - - function makeAnnoucements() { - /* Here we go through all the annoucements that need to be done during this loop, and we just do them. */ - for (let i = 0; i < announcementsToBeMade.length; i++) { - announcement = announcementsToBeMade[i] - /* Here we check if there is a formula attached to the annoucement, we evaluate it to get the annoucement text. */ - let formulaValue - if (announcement.formula !== undefined) { - try { - let value = announcement.value - formulaValue = eval(announcement.formula.code); - } catch (err) { - announcement.formula.error = err.message - } - } - announcement.formulaValue = formulaValue - - if (bot.SESSION.socialBots !== undefined) { - bot.SESSION.socialBots.announce(announcement) - } - } - } - } - - - - function controlLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> controlLoop -> Entering function."); } - - /* Checking if we should continue processing this loop or not.*/ - if (bot.STOP_SESSION === true) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session."); } - console.log("[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.") - afterLoop() - return - } - - if (global.STOP_TASK_GRACEFULLY === true) { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task."); } - console.log("[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.") - afterLoop() - return - } - - currentCandleIndex++ - if (currentCandleIndex < candles.length) { - setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed. - } else { - afterLoop() - } - } - - function afterLoop() { - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> afterLoop -> Entering function."); } - - /* - Before returning we need to see if we have to record some of our counters at the interExecutionMemory. - To do that, the condition to be met is that this execution must include all candles of the current day. - */ - - if (processingDailyFiles) { - - if (lastCandle.end === lastInstantOfTheDay) { - - interExecutionMemory.stopLoss = yesterday.stopLoss - interExecutionMemory.takeProfit = yesterday.takeProfit - - interExecutionMemory.previousBalanceBaseAsset = yesterday.previousBalanceBaseAsset - interExecutionMemory.previousBalanceQuotedAsset = yesterday.previousBalanceQuotedAsset - - interExecutionMemory.tradePositionRate = yesterday.tradePositionRate - interExecutionMemory.tradePositionSize = yesterday.tradePositionSize - - interExecutionMemory.currentStrategyIndex = yesterday.currentStrategyIndex - interExecutionMemory.strategyStage = yesterday.strategyStage - - interExecutionMemory.stopLossPhase = yesterday.stopLossPhase - interExecutionMemory.stopLossStage = yesterday.stopLossStage - - interExecutionMemory.takeProfitPhase = yesterday.takeProfitPhase - interExecutionMemory.takeProfitStage = yesterday.takeProfitStage - - interExecutionMemory.currentStrategy = { - begin: yesterday.currentStrategy.begin, - end: yesterday.currentStrategy.end, - status: yesterday.currentStrategy.status, - number: yesterday.currentStrategy.number, - beginRate: yesterday.currentStrategy.beginRate, - endRate: yesterday.currentStrategy.endRate, - triggerOnSituation: yesterday.currentStrategy.triggerOnSituation - } - - interExecutionMemory.currentTrade = { - begin: yesterday.currentTrade.begin, - end: yesterday.currentTrade.end, - status: yesterday.currentTrade.status, - profit: yesterday.currentTrade.profit, - exitType: yesterday.currentTrade.exitType, - beginRate: yesterday.currentTrade.beginRate, - endRate: yesterday.currentTrade.endRate, - takePositionSituation: yesterday.currentTrade.takePositionSituation - } - - interExecutionMemory.balanceBaseAsset = yesterday.balanceBaseAsset; - interExecutionMemory.balanceQuotedAsset = yesterday.balanceQuotedAsset; - interExecutionMemory.lastTradeProfitLoss = yesterday.lastTradeProfitLoss; - interExecutionMemory.profit = yesterday.profit; - interExecutionMemory.lastTradeROI = yesterday.lastTradeROI; - - interExecutionMemory.roundtrips = yesterday.roundtrips; - interExecutionMemory.fails = yesterday.fails; - interExecutionMemory.hits = yesterday.hits; - interExecutionMemory.periods = yesterday.periods; - interExecutionMemory.positionPeriods = yesterday.positionPeriods; - - interExecutionMemory.distanceToLast.triggerOn = yesterday.distanceToLast.triggerOn - interExecutionMemory.distanceToLast.triggerOff = yesterday.distanceToLast.triggerOff - interExecutionMemory.distanceToLast.takePosition = yesterday.distanceToLast.takePosition - interExecutionMemory.distanceToLast.closePosition = yesterday.distanceToLast.closePosition - - interExecutionMemory.messageId = interExecutionMemory.messageId + yesterday.messageId; - interExecutionMemory.orderId = interExecutionMemory.orderId + yesterday.orderId; - - interExecutionMemory.hitRatio = yesterday.hitRatio; - interExecutionMemory.ROI = yesterday.ROI; - interExecutionMemory.anualizedRateOfReturn = yesterday.anualizedRateOfReturn; - } - } - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> callback -> recordsArray.length = " + recordsArray.length); } - - callback(tradingSystem, recordsArray, conditionsArray, strategiesArray, tradesArray); - } - - function getElement(pArray, currentCandle, datasetName) { - if (pArray === undefined) {return} - try { - let element; - for (let i = 0; i < pArray.length; i++) { - element = pArray[i]; - - if (currentCandle.end === element.end) { // when there is an exact match at the end we take that element - return element - } else { - if ( - i > 0 && - element.end > currentCandle.end - ) { - let previousElement = pArray[i - 1] - if (previousElement.end < currentCandle.end) { - return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element. - } else { - return - } - } - if ( - i === pArray.length - 1 // If we reach the end of the array, then we return the last element. - && - element.end < currentCandle.end - ) { - return element - } - } - } - return - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> getElement -> datasetName = " + datasetName); - logger.write(MODULE_NAME, "[ERROR] runSimulation -> getElement -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] runSimulation -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } -}; - - - diff --git a/package-lock.json b/package-lock.json index 9da031d46..fc670f1b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "ccxt": { - "version": "1.25.80", - "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.25.80.tgz", - "integrity": "sha512-AUWagxMa8PMexRabbktWezlmVacj6L2Ep4844ebIXUOyME2Ommg7O/9/pq0F2Ne1IHcJnNJzHrMYQvkh2gBtlw==" + "version": "1.27.45", + "resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.27.45.tgz", + "integrity": "sha512-4ADlbcIWDzIWjtLvsrtux5jMFj7pU4kVuiqtI2Btq6NEhQ4r/4+LirXdEFlCF6/wIU3XSHabxMd/8nu8HC1KJw==" }, "debug": { "version": "4.1.1", @@ -48,9 +48,9 @@ "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==" }, "telegraf": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.37.0.tgz", - "integrity": "sha512-V3448qwfOolBqkIc87yxjW4zMvR2P6AIF24pPTlX9WhZPwA1TF/x3nQhnWPRLtGh2SJuvDcr83iTkXPXT7Opnw==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.38.0.tgz", + "integrity": "sha512-va4VlrKWp64JrowFoZX/NPzzA6q38kvaIukVXOWFO1V+jR1G8+hCfgJy4TX8Z3rwLJzwaBEet1QhikHDRZWl3A==", "requires": { "debug": "4.1.1", "minimist": "1.2.5", @@ -66,9 +66,9 @@ "integrity": "sha512-njVv1EAhIZnmQVLocZEADYUyqA1WIXuVcDYlsp+mXua/XB0pxx+PKtMSPeZ/EE4wPWTw9h/hA9ASTT6yQelkiw==" }, "ws": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", - "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" } } } diff --git a/server.js b/server.js index 80ac46a62..e2840cc6d 100644 --- a/server.js +++ b/server.js @@ -57,7 +57,7 @@ function finalizeSessions() { } } -process.on('exit', function (code) { +process.on('exit', function (config) { if (global.TASK_NODE !== undefined) { /* We send an event signaling that the Task is being terminated. */ @@ -68,7 +68,7 @@ process.on('exit', function (code) { global.EVENT_SERVER_CLIENT = undefined } - //console.log('[INFO] Task Server -> server -> process.on.exit -> About to exit -> code = ' + code) + //console.log('[INFO] Task Server -> server -> process.on.exit -> About to exit -> config = ' + config) }) /* Here we listen for the message to stop this Task / Process comming from the Task Manager, which is the paret of this node js process. */ @@ -84,7 +84,7 @@ process.on('message', message => { let key = global.TASK_NODE.name + '-' + global.TASK_NODE.type + '-' + global.TASK_NODE.id console.log('[INFO] Task Server -> server -> process.on -> Stopping Task ' + key + '. Nodejs process will be exited in less than 1 minute.') setTimeout(global.EXIT_NODE_PROCESS, 60000); - } + } }); @@ -101,7 +101,7 @@ global.EXIT_NODE_PROCESS = function exitProcess() { clearInterval(global.HEARTBEAT_INTERVAL_HANDLER) for (let i = 0; i < global.TASK_NODE.bot.processes.length; i++) { - let code = global.TASK_NODE.bot.processes[i].code + let config = global.TASK_NODE.bot.processes[i].config let process = global.TASK_NODE.bot.processes[i] key = process.name + '-' + process.type + '-' + process.id @@ -138,7 +138,7 @@ let taskId = process.argv[2] // reading what comes as an argument of the nodejs /* Setting up the global Event Handler */ let EVENT_SERVER_CLIENT = require('./EventServerClient.js'); - + global.EVENT_SERVER_CLIENT = EVENT_SERVER_CLIENT.newEventsServerClient() global.EVENT_SERVER_CLIENT.initialize(preLoader) global.STOP_TASK_GRACEFULLY = false; @@ -162,7 +162,7 @@ function preLoader() { } } else { /* This process was started not by the Task Manager, but independently (most likely for debugging purposes). In this case we listen to an event with the Task Info that should be emitted at the UI */ - try { + try { //console.log('[INFO] Task Server -> server -> preLoader -> Waiting for event to start debugging...') global.EVENT_SERVER_CLIENT.listenToEvent('Task Server', 'Debug Task Started', undefined, 'Task Server', undefined, startDebugging) function startDebugging(message) { @@ -190,15 +190,15 @@ function bootLoader() { function taskHearBeat() { - /* The heartbeat event is raised at the event handler of the instance of this task, created at the UI. */ + /* The heartbeat event is raised at the event handler of the instance of this task, created at the UI. */ let event = { seconds: (new Date()).getSeconds() } - global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Heartbeat', event) + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Heartbeat', event) } for (let processIndex = 0; processIndex < global.TASK_NODE.bot.processes.length; processIndex++) { - let code = global.TASK_NODE.bot.processes[processIndex].code + let config = global.TASK_NODE.bot.processes[processIndex].config /* Validate that the minimun amount of input required are defined. */ @@ -257,17 +257,17 @@ function bootLoader() { continue } - if (global.TASK_NODE.bot.processes[processIndex].referenceParent.code.codeName === undefined) { + if (global.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName === undefined) { console.log("[WARN] Task Server -> server -> bootLoader -> Process Definition without a codeName defined. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent)); continue } - if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.code.codeName === undefined) { + if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.config.codeName === undefined) { console.log("[WARN] Task Server -> server -> bootLoader -> Bot Definition without a codeName defined. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode)); continue } - if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.code.codeName === undefined) { + if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName === undefined) { console.log("[WARN] Task Server -> server -> bootLoader -> Data Mine without a codeName defined. -> Data Mine Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode)); continue } @@ -278,7 +278,7 @@ function bootLoader() { function startRoot(processIndex) { - // console.log('[INFO] Task Server -> server -> startRoot -> Entering function. ') + // console.log('[INFO] Task Server -> server -> startRoot -> Entering function. ') const ROOT_MODULE = require('./Root') let root = ROOT_MODULE.newRoot() @@ -299,13 +299,13 @@ global.getPercentage = function (fromDate, currentDate, lastDate) { if ((lastDays - fromDays) === 0) { percentage = 100 } - return percentage + return percentage } global.areEqualDates = function (date1, date2) { let day1Days = Math.trunc(date1.valueOf() / ONE_DAY_IN_MILISECONDS) let day2Days = Math.trunc(date2.valueOf() / ONE_DAY_IN_MILISECONDS) - + if (day1Days === day2Days) { return true } else {