diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..82c487bdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ + +.vs/* +obj/* + +bin/* + +*.config +*.log +*.cache +node_modules/* + diff --git a/AAJason-Trading-Bot.sln b/AAJason-Trading-Bot.sln new file mode 100644 index 000000000..da30e179e --- /dev/null +++ b/AAJason-Trading-Bot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Project", "Project.njsproj", "{AA2901BC-8F81-40F0-A402-5CA79A3F22AD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B4BDAEAF-E863-458A-87C1-CC6D8C490784} + EndGlobalSection +EndGlobal diff --git a/Commons.js b/Commons.js new file mode 100644 index 000000000..7b8bc19bf --- /dev/null +++ b/Commons.js @@ -0,0 +1,3121 @@ +exports.newCommons = function newCommons(bot, logger, UTILITIES) { + + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + + const MODULE_NAME = "Commons -> " + bot.SESSION.name ; + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; + const GMT_SECONDS = ':00.000 GMT+0000'; + + let thisObject = { + finalize: finalize, + runSimulation: runSimulation, + buildPercentageBandwidthArray: buildPercentageBandwidthArray, + buildBollingerBandsArray: buildBollingerBandsArray, + buildBollingerChannelsArray: buildBollingerChannelsArray, + buildBollingerSubChannelsArray: buildBollingerSubChannelsArray, + buildCandles: buildCandles + }; + + let utilities = UTILITIES.newCloudUtilities(bot, logger); + + let percentageBandwidthAt = {} + let bollingerBandsAt = {} + let bollingerChannelsAt = {} + let bollingerSubChannelsAt = {} + let candlesAt = {} + + + return thisObject; + + function finalize() { + percentageBandwidthAt = undefined + bollingerBandsAt = undefined + bollingerChannelsAt = undefined + bollingerSubChannelsAt = undefined + candlesAt = undefined + thisObject = undefined + } + + function runSimulation( + timePeriod, + timePeriodLabel, + currentDay, + interExecutionMemory, + assistant, + callback, + callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> Entering function."); } + let processingDailyFiles + if (timePeriod > global.dailyFilePeriods[0][0]) { + processingDailyFiles = false + } else { + processingDailyFiles = true + } + + let recordsArray = []; + let conditionsArray = []; + let strategiesArray = []; + let tradesArray = []; + + let tradingSystem = bot.DEFINITION.tradingSystem; + + /* 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 previousBalanceAssetA = 0; + let previousBalanceAssetB = 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 + } + + let currentTrade = { + begin: 0, + end: 0, + status: 0, + profit: 0, + exitType: 0, + beginRate: 0, + endRate: 0 + } + + let balanceAssetA = bot.VALUES_TO_USE.initialBalanceA; + let balanceAssetB = 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; + + /* 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; + + let yesterday = {}; + + /* Initialization */ + + yesterday.stopLoss = 0 + yesterday.takeProfit = 0 + + yesterday.previousBalanceAssetA = previousBalanceAssetA + yesterday.previousBalanceAssetB = previousBalanceAssetB + + 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 + } + + yesterday.currentTrade = { + begin: 0, + end: 0, + status: 0, + profit: 0, + exitType: 0, + beginRate: 0, + endRate: 0 + } + + yesterday.balanceAssetA = balanceAssetA; + yesterday.balanceAssetB = balanceAssetB; + + 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.previousBalanceAssetA = previousBalanceAssetA + interExecutionMemory.previousBalanceAssetB = previousBalanceAssetB + + 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 + } + + interExecutionMemory.currentTrade = { + begin: 0, + end: 0, + status: 0, + profit: 0, + exitType: 0, + beginRate: 0, + endRate: 0 + } + + interExecutionMemory.balanceAssetA = balanceAssetA; + interExecutionMemory.balanceAssetB = balanceAssetB; + + 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 + + previousBalanceAssetA = interExecutionMemory.previousBalanceAssetA + previousBalanceAssetB = interExecutionMemory.previousBalanceAssetB + + 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 + } + + 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 + } + + 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. + + balanceAssetA = interExecutionMemory.balanceAssetA; + balanceAssetB = interExecutionMemory.balanceAssetB; + + yesterday.balanceAssetA = balanceAssetA; + yesterday.balanceAssetB = balanceAssetB; + + } + } + + 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.previousBalanceAssetA = previousBalanceAssetA + yesterday.previousBalanceAssetB = previousBalanceAssetB + + 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 + } + + yesterday.currentTrade = { + begin: currentTrade.begin, + end: currentTrade.end, + status: currentTrade.status, + profit: currentTrade.profit, + exitType: currentTrade.exitType, + beginRate: currentTrade.beginRate, + endRate: currentTrade.endRate + } + + 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 candles = candlesAt[timePeriodLabel] + let percentageBandwidthArray = percentageBandwidthAt[timePeriodLabel] + let bollingerBandsArray = bollingerBandsAt[timePeriodLabel] + let bollingerChannelsArray = bollingerChannelsAt[timePeriodLabel] + let bollingerSubChannelsArray = bollingerSubChannelsAt[timePeriodLabel] + + /* Last Candle */ + let lastCandle = candles[candles.length - 1]; + + /* Main Simulation Loop: We go thourgh all the candles at this time period. */ + let i + + /* 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 / timePeriod + + i = Math.trunc(amount) + if (i < 0) { i = 0 } + if (i > 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. */ + i = 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 # " + i); } + + let candle = candles[i]; + + /* 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 + } + + let percentageBandwidth = getElement(percentageBandwidthArray, candle, 'Current' + '-' + 'Percentage Bandwidth'); + let bollingerBand = getElement(bollingerBandsArray, candle, 'Current' + '-' + 'Bollinger Band'); + let bollingerChannel = getElement(bollingerChannelsArray, candle, 'Current' + '-' + 'Bollnger Channel'); + let bollingerSubChannel = getElement(bollingerSubChannelsArray, candle, 'Current' + '-' + 'Bollnger Sub Channel'); + + let chart = {} + + if (processingDailyFiles) { + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + + let mapKey = dailyFilePeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', ''); + + chart[propertyName] = {} + chart[propertyName].candle = getElement(candlesAt[mapKey], candle, 'Daily' + '-' + mapKey + '-' + 'Candles'); + chart[propertyName].percentageBandwidth = getElement(percentageBandwidthAt[mapKey], candle, 'Daily' + '-' + mapKey + '-' + 'Percentage Bandwidth'); + chart[propertyName].bollingerBand = getElement(bollingerBandsAt[mapKey], candle, 'Daily' + '-' + mapKey + '-' + 'Bollinger Band'); + chart[propertyName].bollingerChannel = getElement(bollingerChannelsAt[mapKey], candle, 'Daily' + '-' + mapKey + '-' + 'Bollnger Channel'); + chart[propertyName].bollingerSubChannel = getElement(bollingerSubChannelsAt[mapKey], candle, 'Daily' + '-' + mapKey + '-' + 'Bollnger Sub Channel'); + } + } + + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + + let mapKey = marketFilesPeriods[j][1] + let propertyName = 'at' + mapKey.replace('-', ''); + + chart[propertyName] = {} + chart[propertyName].candle = getElement(candlesAt[mapKey], candle, 'Market' + '-' + mapKey + '-' + 'Candles'); + chart[propertyName].percentageBandwidth = getElement(percentageBandwidthAt[mapKey], candle, 'Market' + '-' + mapKey + '-' + 'Percentage Bandwidth'); + chart[propertyName].bollingerBand = getElement(bollingerBandsAt[mapKey], candle, 'Market' + '-' + mapKey + '-' + 'Bollinger Band'); + chart[propertyName].bollingerChannel = getElement(bollingerChannelsAt[mapKey], candle, 'Market' + '-' + mapKey + '-' + 'Bollnger Channel'); + chart[propertyName].bollingerSubChannel = getElement(bollingerSubChannelsAt[mapKey], candle, 'Market' + '-' + mapKey + '-' + 'Bollnger Sub Channel'); + } + + /* 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()) } + + /* Assistant Info */ + + 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 # " + i + " @ " + processingDate)} + console.log("Jason -> " + MODULE_NAME + " -> runSimulation -> loop -> Simulation " + bot.sessionKey + " Loop # " + i + " @ " + processingDate) + + bot.sessionHeartBeat(processingDate) // tell the world we are alive and doing well + } + previousLoopingDay = loopingDay.valueOf() + + /* If any of the needed indicators is missing, then that period is not calculated */ + if (bollingerBand === undefined) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because Bollinger Band is undefined."); } + controlLoop(); + return + } + if (percentageBandwidth === undefined) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because %B is undefined."); } + controlLoop(); + return + } // percentageBandwidth might start after the first few candles. + if (bollingerChannel === undefined) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because Bollingeer Channel is undefined."); } + controlLoop(); + return + } + if (bollingerSubChannel === undefined) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> Skipping Candle because Bollingeer Sub Channel is undefined."); } + controlLoop(); + return + } + + //if (LRC === undefined) { controlLoop(); return} + + periods++; + days = periods * timePeriod / ONE_DAY_IN_MILISECONDS; + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.periods = periods + } + + /* We skip the candle at the head of the market because i has not closed yet. */ + let candlesPerDay = ONE_DAY_IN_MILISECONDS / timePeriod + if (i === 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 (i === 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.code !== undefined) { + newCondition(key, condition.code); + } + } + } + } + + 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.code !== undefined) { + newCondition(key, condition.code); + } + } + } + } + + 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.code !== undefined) { + newCondition(key, condition.code); + } + } + } + } + } + + let openStage = strategy.openStage + + if (openStage !== undefined) { + + /* Default Values*/ + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + positionSize = balanceAssetA; + positionRate = candle.close; + } else { + positionSize = balanceAssetB; + 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + positionSize = balanceAssetA; + } else { + positionSize = balanceAssetB; + } + } else { + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + if (positionSize > balanceAssetA) { positionSize = balanceAssetA } + } else { + if (positionSize > balanceAssetB) { positionSize = balanceAssetB } + } + } + } + } + } + + 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + 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.code !== undefined) { + newCondition(key, condition.code); + } + } + } + } + } + } + + 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.code !== undefined) { + newCondition(key, condition.code); + } + } + } + } + } + } + } + + 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.code !== undefined) { + newCondition(key, condition.code); + } + } + } + } + } + } + + 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.code !== undefined) { + newCondition(key, condition.code); + } + } + } + } + } + } + } + + function newCondition(key, node) { + + 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + balance = balanceAssetA + minimumBalance = bot.VALUES_TO_USE.minimumBalanceA + maximumBalance = bot.VALUES_TO_USE.maximumBalanceA + } else { + balance = balanceAssetB + minimumBalance = bot.VALUES_TO_USE.minimumBalanceB + maximumBalance = bot.VALUES_TO_USE.maximumBalanceB + } + + if (balance > minimumBalance && balance < maximumBalance) { + + /* + 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'; + currentStrategyIndex = j; + currentStrategy.begin = candle.begin; + currentStrategy.beginRate = candle.min; + currentStrategy.endRate = candle.min; // In case the strategy does not get exited + + 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; + } + } + + 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; + } + } + } + } + } + } else { + let stopRunningDate = new Date(candle.begin) + if (balance <= minimumBalance) { + tradingSystem.error = "Min Balance @ " + stopRunningDate.toLocaleString() + } + if (balance >= maximumBalance) { + tradingSystem.error = "Max Balance @ " + stopRunningDate.toLocaleString() + } + } + } + + /* 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; + 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'; + 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 + + 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 + } + } + + let phase = parentNode.stopLoss.phases[p]; + + 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' } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.stopLossPhase = stopLossPhase; + yesterday.stopLossStage = stopLossStage; + yesterday.strategyStage = strategyStage; + } + } + + checkAnnouncements(nextPhaseEvent) + 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 + } + } + + let phase = parentNode.takeProfit.phases[p]; + + 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' } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.takeProfitPhase = takeProfitPhase; + yesterday.takeProfitStage = takeProfitStage; + yesterday.strategyStage = strategyStage; + } + } + + checkAnnouncements(nextPhaseEvent) + 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 * timePeriod / 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 ((bot.VALUES_TO_USE.baseAsset === 'BTC' && candle.max >= stopLoss) || (bot.VALUES_TO_USE.baseAsset !== 'BTC' && 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + slippedStopLoss = slippedStopLoss + slippageAmount + } else { + slippedStopLoss = slippedStopLoss - slippageAmount + } + + let finalStopLoss = slippedStopLoss; + + let feePaid = 0 + + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + strategy.positionSize = balanceAssetB / finalStopLoss; + strategy.positionRate = finalStopLoss; + + feePaid = balanceAssetB / finalStopLoss * bot.VALUES_TO_USE.feeStructure.taker / 100 + + balanceAssetA = balanceAssetA + balanceAssetB / finalStopLoss - feePaid; + balanceAssetB = 0; + } else { + strategy.positionSize = balanceAssetA * finalStopLoss; + strategy.positionRate = finalStopLoss; + + feePaid = balanceAssetA * finalStopLoss * bot.VALUES_TO_USE.feeStructure.taker / 100 + + balanceAssetB = balanceAssetB + balanceAssetA * finalStopLoss - feePaid; + balanceAssetA = 0; + } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.balanceAssetA = balanceAssetA; + yesterday.balanceAssetB = balanceAssetB; + } + } + + marketRate = finalStopLoss; + type = '"Close@StopLoss"'; + strategyStage = 'Close Stage'; + stopLossStage = 'No Stage'; + takeProfitStage = 'No Stage'; + currentTrade.end = candle.end; + currentTrade.status = 1; + currentTrade.exitType = 1; + currentTrade.endRate = finalStopLoss; + + 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 ((bot.VALUES_TO_USE.baseAsset === 'BTC' && candle.min <= takeProfit) || (bot.VALUES_TO_USE.baseAsset !== 'BTC' && 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + slippedTakeProfit = slippedTakeProfit + slippageAmount + } else { + slippedTakeProfit = slippedTakeProfit - slippageAmount + } + + let finalTakeProfit = slippedTakeProfit; + + let feePaid = 0 + + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + strategy.positionSize = balanceAssetB / finalTakeProfit; + strategy.positionRate = finalTakeProfit; + + feePaid = balanceAssetB / finalTakeProfit * bot.VALUES_TO_USE.feeStructure.taker / 100 + + balanceAssetA = balanceAssetA + balanceAssetB / finalTakeProfit - feePaid; + balanceAssetB = 0; + } else { + strategy.positionSize = balanceAssetA * finalTakeProfit; + strategy.positionRate = finalTakeProfit; + + feePaid = balanceAssetA * finalTakeProfit * bot.VALUES_TO_USE.feeStructure.taker / 100 + + balanceAssetB = balanceAssetB + balanceAssetA * finalTakeProfit - feePaid; + balanceAssetA = 0; + } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.balanceAssetA = balanceAssetA; + yesterday.balanceAssetB = balanceAssetB; + + } + } + + marketRate = finalTakeProfit; + type = '"Close@TakeProfit"'; + strategyStage = 'Close Stage'; + stopLossStage = 'No Stage'; + takeProfitStage = 'No Stage'; + + currentTrade.end = candle.end; + currentTrade.status = 1; + currentTrade.exitType = 2; + currentTrade.endRate = finalTakeProfit; + + 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + 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 (i > 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; + } + } + + + /* Checking the status of current positions */ + let positions = assistant.getPositions(); + if (positions.length > 0) { + let position = positions[positions.length - 1] // We are allways checking the the last position is not open. + if (position.status === 'open') { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Not placing the trade at the exchange because the last position is still open. "); } + afterLoop(); + 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 openPositionRate + let amountA + let amountB + let positionDirection + let availableBalance = assistant.getAvailableBalance() + + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + positionDirection = "sell" + + openPositionRate = tradePositionRate - 100 // 100 is Provisional since we do have management of orders yet + + amountA = tradePositionSize * openPositionRate + amountB = tradePositionSize + + if (amountB > availableBalance.assetB) { // The assistant know what fees were paid. + amountB = availableBalance.assetB + } + } else { + positionDirection = "buy" + + openPositionRate = tradePositionRate + 100 // 100 is Provisional since we do have management of orders yet + + amountA = tradePositionSize + amountB = tradePositionSize / openPositionRate + + if (amountA > availableBalance.assetA) { // The assistant know what fees were paid. + amountA = availableBalance.assetA + } + } + + interExecutionMemory.executionContext = { + status: "Taking Position", + periods: periods, + } + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> Ready to put order."); } + assistant.putPosition(positionDirection, openPositionRate, amountA, amountB, onOrderPut) + + function onOrderPut(err) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderPut -> Entering function."); } + + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderPut -> DEFAULT_OK_RESPONSE "); } + interExecutionMemory.executionContext = { + status: "In a Position", + periods: periods, + amountA: amountA, + amountB: amountB + } + takePositionAtSimulation() + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putOpeningOrder -> onOrderPut -> DEFAULT_FAIL_RESPONSE "); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderPut -> 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 -> onOrderPut -> DEFAULT_RETRY_RESPONSE "); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderPut -> Message = " + err.message); } + strategy.openStage.openExecution.error = err.message + afterLoop() + return; + } + } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderPut -> Unexpected Response -> Message = " + err.message); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putOpeningOrder -> onOrderPut -> 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; + + previousBalanceAssetA = balanceAssetA; + previousBalanceAssetB = balanceAssetB; + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.previousBalanceAssetA = previousBalanceAssetA; + yesterday.previousBalanceAssetB = previousBalanceAssetB; + } + } + + lastTradeProfitLoss = 0; + lastTradeROI = 0; + + let feePaid = 0 + + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + + feePaid = tradePositionSize * tradePositionRate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + balanceAssetB = balanceAssetB + tradePositionSize * tradePositionRate - feePaid; + balanceAssetA = balanceAssetA - tradePositionSize; + } else { + + feePaid = tradePositionSize / tradePositionRate * bot.VALUES_TO_USE.feeStructure.taker / 100 + + balanceAssetA = balanceAssetA + tradePositionSize / tradePositionRate - feePaid; + balanceAssetB = balanceAssetB - tradePositionSize; + } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.balanceAssetA = balanceAssetA; + yesterday.balanceAssetB = balanceAssetB; + + 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 (i > 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."); } + + /* Checking the status of current positions */ + let positions = assistant.getPositions(); + if (positions.length > 0) { + let position = positions[positions.length - 1] // We are allways checking the the last position is not open. + if (position.status === 'open') { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> Exiting function because status of last position is Open."); } + afterLoop(); + 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 -> putClosingOrder -> Exiting function because this closing was already submited at a previous execution."); } + closePositionAtSimulation() + return; + } + } + } + + let closePositionRate + let amountA + let amountB + let positionDirection + let availableBalance = assistant.getAvailableBalance() + + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + positionDirection = "buy" + + closePositionRate = ticker.last + 100; // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. + + amountA = availableBalance.assetA + amountB = availableBalance.assetA / closePositionRate + + } else { + positionDirection = "sell" + + closePositionRate = ticker.last - 100; // This is provisional and totally arbitrary, until we have a formula on the designer that defines this stuff. + + amountA = availableBalance.assetB * closePositionRate + amountB = availableBalance.assetB + + } + + 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."); } + assistant.putPosition(positionDirection, closePositionRate, amountA, amountB, onOrderPut) + + function onOrderPut(err) { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderPut -> Entering function."); } + + try { + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderPut -> DEFAULT_OK_RESPONSE "); } + interExecutionMemory.executionContext = { + status: "Position Closed", + periods: periods, + amountA: amountA, + amountB: amountB + } + closePositionAtSimulation() + return; + } + case global.DEFAULT_FAIL_RESPONSE.result: { + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> loop -> putClosingOrder -> onOrderPut -> 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 -> onOrderPut -> DEFAULT_RETRY_RESPONSE "); } + strategy.closeStage.closeExecution.error = err.message + afterLoop() + return; + } + } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderPut -> Unexpected Response -> Message = " + err.message); } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] runSimulation -> loop -> putClosingOrder -> onOrderPut -> 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 + } + } + + if (bot.VALUES_TO_USE.baseAsset === 'BTC') { + lastTradeProfitLoss = balanceAssetA - previousBalanceAssetA; + lastTradeROI = lastTradeProfitLoss * 100 / tradePositionSize; + if (isNaN(lastTradeROI)) { lastTradeROI = 0; } + profit = balanceAssetA - bot.VALUES_TO_USE.initialBalanceA; + } else { + lastTradeProfitLoss = balanceAssetB - previousBalanceAssetB; + lastTradeROI = lastTradeProfitLoss * 100 / tradePositionSize; + if (isNaN(lastTradeROI)) { lastTradeROI = 0; } + profit = balanceAssetB - 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 (bot.VALUES_TO_USE.baseAsset === 'BTC') { + 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; + + 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 (balanceAssetA === Infinity) { + balanceAssetA = Number.MAX_SAFE_INTEGER + } + + if (balanceAssetB === Infinity) { + balanceAssetB = Number.MAX_SAFE_INTEGER + } + + let quotedBaseAsset = '"' + bot.VALUES_TO_USE.baseAsset + '"' + + simulationRecord = { + begin: candle.begin, + end: candle.end, + type: type, + marketRate: marketRate, + amount: 1, + balanceA: balanceAssetA, + balanceB: balanceAssetB, + 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, + 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); + + /* Prepare the information for the Strategies File*/ + + if ( + (currentStrategy.begin !== 0 && currentStrategy.end !== 0) || + (currentStrategy.begin !== 0 && i === candles.length - 1 && lastCandle.end !== lastInstantOfTheDay) + ) { + + strategiesArray.push(currentStrategy); + + currentStrategy = { + begin: 0, + end: 0, + status: 0, + number: 0, + beginRate: 0, + endRate: 0 + } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.currentStrategy = { + begin: 0, + end: 0, + status: 0, + number: 0, + beginRate: 0, + endRate: 0 + } + } + } + } + + /* Prepare the information for the Trades File */ + + if ( + (currentTrade.begin !== 0 && currentTrade.end !== 0) || + (currentTrade.begin !== 0 && i === candles.length - 1 && lastCandle.end !== lastInstantOfTheDay) + ) { + + currentTrade.profit = lastTradeProfitLoss; + + tradesArray.push(currentTrade); + + currentTrade = { + begin: 0, + end: 0, + status: 0, + lastTradeROI: 0, + exitType: 0, + beginRate: 0, + endRate: 0 + } + + if (processingDailyFiles) { + if (positionedAtYesterday) { + yesterday.currentTrade = { + begin: 0, + end: 0, + status: 0, + lastTradeROI: 0, + exitType: 0, + beginRate: 0, + endRate: 0 + } + } + } + } + } + + function checkAnnouncements(node, value) { + /* + Value is an optional parameter that represents the value that the announcement is monitoring for change. + 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) { + + /* The Value Variation is what tells us how much the value already announced must change in order to annouce it again. */ + let valueVariantion + try { + let code = JSON.parse(announcement.code) + valueVariantion = code.valueVariantion + } catch (err) { + announcement.error = err.message + } + + if (newAnnouncementRecord.value !== undefined && valueVariantion !== undefined) { + let upperLimit = newAnnouncementRecord.value + newAnnouncementRecord.value * valueVariantion / 100 + let lowerLimit = newAnnouncementRecord.value - newAnnouncementRecord.value * valueVariantion / 100 + if (value > lowerLimit && value < upperLimit ) { + /* There is not enough variation to announce this again. */ + return + } + } + + /* 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 { + formulaValue = eval(announcement.formula.code); + } catch (err) { + announcement.formula.error = err.message + } + } + announcement.formulaValue = formulaValue + + bot.SESSION.socialBots.announce(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 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."); } + console.log("[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing.") + afterLoop() + return + } + + i++ + if (i < 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.previousBalanceAssetA = yesterday.previousBalanceAssetA + interExecutionMemory.previousBalanceAssetB = yesterday.previousBalanceAssetB + + 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 + } + + 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 + } + + interExecutionMemory.balanceAssetA = yesterday.balanceAssetA; + interExecutionMemory.balanceAssetB = yesterday.balanceAssetB; + 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) { + + 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); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] runSimulation -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildPercentageBandwidthArray(dataFile, timePeriodLabel, callBackFunction) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] buildPercentageBandwidthArray -> Entering function."); } + + try { + + let percentageBandwidthArray = []; + + let previous; + + for (let i = 0; i < dataFile.length; i++) { + + let percentageBandwidth = { + begin: dataFile[i][0], + end: dataFile[i][1], + value: dataFile[i][2], + movingAverage: dataFile[i][3], + bandwidth: dataFile[i][4] + }; + + if (previous !== undefined) { + + if (previous.movingAverage > percentageBandwidth.movingAverage) { percentageBandwidth.direction = 'Down'; } + if (previous.movingAverage < percentageBandwidth.movingAverage) { percentageBandwidth.direction = 'Up'; } + if (previous.movingAverage === percentageBandwidth.movingAverage) { percentageBandwidth.direction = 'Side'; } + + } + + percentageBandwidth.previous = previous; + + percentageBandwidthArray.push(percentageBandwidth); + + previous = percentageBandwidth; + + } + + percentageBandwidthAt[timePeriodLabel] = percentageBandwidthArray + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] buildPercentageBandwidthArray -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildBollingerBandsArray(dataFile, timePeriodLabel, callBackFunction) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] buildBollingerBandsArray -> Entering function."); } + + try { + + let bollingerBandsArray = []; + + let previous; + + for (let i = 0; i < dataFile.length; i++) { + + let bollingerBand = { + begin: dataFile[i][0], + end: dataFile[i][1], + movingAverage: dataFile[i][2], + standardDeviation: dataFile[i][3], + deviation: dataFile[i][4] + }; + + if (previous !== undefined) { + + if (previous.movingAverage > bollingerBand.movingAverage) { bollingerBand.direction = 'Down'; } + if (previous.movingAverage < bollingerBand.movingAverage) { bollingerBand.direction = 'Up'; } + if (previous.movingAverage === bollingerBand.movingAverage) { bollingerBand.direction = 'Side'; } + + } + + bollingerBand.previous = previous; + + bollingerBandsArray.push(bollingerBand); + + previous = bollingerBand; + } + + bollingerBandsAt[timePeriodLabel] = bollingerBandsArray + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] buildBollingerBandsArray -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildBollingerChannelsArray(dataFile, timePeriodLabel, callBackFunction) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] buildBollingerChannelsArray -> Entering function."); } + + try { + + let bollingerChannelsArray = []; + + let previous; + + for (let i = 0; i < dataFile.length; i++) { + + let bollingerChannel = { + begin: dataFile[i][0], + end: dataFile[i][1], + direction: dataFile[i][2], + period: dataFile[i][3] + }; + + bollingerChannel.previous = previous; + + bollingerChannelsArray.push(bollingerChannel); + + previous = bollingerChannel; + } + + bollingerChannelsAt[timePeriodLabel] = bollingerChannelsArray + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] buildBollingerChannelsArray -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildBollingerSubChannelsArray(dataFile, timePeriodLabel, callBackFunction) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] buildBollingerSubChannelsArray -> Entering function."); } + + try { + let bollingerSubChannelsArray = []; + + let previous; + + for (let i = 0; i < dataFile.length; i++) { + + let bollingerSubChannel = { + begin: dataFile[i][0], + end: dataFile[i][1], + direction: dataFile[i][2], + slope: dataFile[i][3], + period: dataFile[i][4], + firstMovingAverage: dataFile[i][5], + lastMovingAverage: dataFile[i][6], + firstDeviation: dataFile[i][7], + lastDeviation: dataFile[i][8] + }; + + bollingerSubChannel.previous = previous; + + bollingerSubChannelsArray.push(bollingerSubChannel); + + previous = bollingerSubChannel; + } + + bollingerSubChannelsAt[timePeriodLabel] = bollingerSubChannelsArray + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] buildBollingerSubChannelsArray -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildCandles(dataFile, timePeriodLabel, callBackFunction) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] buildCandles -> Entering function."); } + + try { + let candles = []; + + let previous; + + for (let i = 0; i < dataFile.length; i++) { + + let candle = { + open: undefined, + close: undefined, + min: 10000000000000, + max: 0, + begin: undefined, + end: undefined, + direction: undefined + }; + + candle.min = dataFile[i][0]; + candle.max = dataFile[i][1]; + + candle.open = dataFile[i][2]; + candle.close = dataFile[i][3]; + + candle.begin = dataFile[i][4]; + candle.end = dataFile[i][5]; + + if (candle.open > candle.close) { candle.direction = 'Down'; } + if (candle.open < candle.close) { candle.direction = 'Up'; } + if (candle.open === candle.close) { candle.direction = 'Side'; } + + candle.previous = previous; + + candles.push(candle); + + previous = candle; + } + + candlesAt[timePeriodLabel] = candles + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] buildCandles -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } +}; + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..10e295746 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Financial Beings License + +Collective Copyright by the Advanced Algos Community + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software including without limitation the rights to use, copy, modify, +merge, publish, distribute and sublicense copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following +conditions: + + 1. Using, copying, modifying, publishing, distributing and sublicensing rights + are restricted to the Advanced Algos Platform, meaning the rights granted can + only be exercised through the Advanced Algos Service. + + 2. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Multi-Period/User.Bot.js b/Multi-Period/User.Bot.js new file mode 100644 index 000000000..658848110 --- /dev/null +++ b/Multi-Period/User.Bot.js @@ -0,0 +1,992 @@ +exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, fileStorage) { + + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + + const MODULE_NAME = "User Bot"; + + 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 commons = COMMONS.newCommons(bot, logger, UTILITIES); + + thisObject = { + initialize: initialize, + finalize: finalize, + start: start + }; + + let utilities = UTILITIES.newCloudUtilities(bot, logger); + + let assistant; + + return thisObject; + + function initialize(callBackFunction, pAssistant) { + + try { + + logger.fileName = MODULE_NAME; + logger.initialize(); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } + + assistant = pAssistant; + + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function finalize() { + commons.finalize() + commons = undefined + utilities = undefined + assistant = undefined + thisObject = undefined + } + + function start(multiPeriodDataFiles, timePeriod, timePeriodLabel, currentDay, interExecutionMemory, callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } + + let market = global.MARKET; + let dataFile; + + let recordsArray + let conditionsArray + let strategiesArray + let tradesArray + + let tradingSystem = {}; + + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + + let mapKey = marketFilesPeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(mapKey) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Building Dataset for timePeriod = " + mapKey); } + + if (dataFiles) { + for (let i = 0; i < 5; i++) { + + dataFile = dataFiles[i]; + + switch (i) { + case 0: { + commons.buildBollingerBandsArray(dataFile, mapKey, callBackFunction); + break; + } + case 1: { + commons.buildPercentageBandwidthArray(dataFile, mapKey, callBackFunction); + break; + } + case 2: { + commons.buildBollingerChannelsArray(dataFile, mapKey, callBackFunction); + break; + } + case 3: { + commons.buildBollingerSubChannelsArray(dataFile, mapKey, callBackFunction); + break; + } + case 4: { + commons.buildCandles(dataFile, mapKey, callBackFunction); + break; + } + } + } + } + } + + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + + let mapKey = global.dailyFilePeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(mapKey) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Building Dataset for timePeriod = " + mapKey); } + + if (dataFiles) { + for (let i = 0; i < 5; i++) { + + dataFile = dataFiles[i]; + + switch (i) { + case 0: { + commons.buildBollingerBandsArray(dataFile, mapKey, callBackFunction); + break; + } + case 1: { + commons.buildPercentageBandwidthArray(dataFile, mapKey, callBackFunction); + break; + } + case 2: { + commons.buildBollingerChannelsArray(dataFile, mapKey, callBackFunction); + break; + } + case 3: { + commons.buildBollingerSubChannelsArray(dataFile, mapKey, callBackFunction); + break; + } + case 4: { + commons.buildCandles(dataFile, mapKey, callBackFunction); + break; + } + } + } + } + } + + commons.runSimulation( + timePeriod, + timePeriodLabel, + currentDay, + interExecutionMemory, + assistant, + writeFiles, + callBackFunction) + + function writeFiles(pTradingSystem, pRecordsArray, pConditionsArray, pStrategiesArray, pTradesArray) { + + tradingSystem = pTradingSystem + recordsArray = pRecordsArray + conditionsArray = pConditionsArray + strategiesArray = pStrategiesArray + tradesArray = pTradesArray + + if (timePeriod > 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.positionPeriods + "," + + record.positionDays + "," + + record.distanceToLastTriggerOn + "," + + record.distanceToLastTriggerOff + "," + + record.distanceToLastTakePosition + "," + + record.distanceToLastClosePosition + "]"; + + if (separator === "") { separator = ","; } + + fileRecordCounter++; + + } + + fileContent = "[" + fileContent + "]"; + + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriodLabel; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + 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 = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriodLabel; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + 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 + "]"; + + if (separator === "") { separator = ","; } + + fileRecordCounter++; + + } + + fileContent = "[" + fileContent + "]"; + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriodLabel; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + 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 + "]"; + + if (separator === "") { separator = ","; } + + fileRecordCounter++; + + } + + fileContent = "[" + fileContent + "]"; + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriodLabel; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + callBackFunction(err); + return; + + } + + /* Emit event that signals that simulation files were updated */ + global.SYSTEM_EVENT_HANDLER.raiseEvent('Jason-Multi-Period', 'Simulation Files Updated') + + 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.positionPeriods + "," + + record.positionDays + "]"; + + if (separator === "") { separator = ","; } + + fileRecordCounter++; + + } + + fileContent = "[" + fileContent + "]"; + + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timePeriodLabel; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + 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.positionPeriods + "," + + record.positionDays + "]"; + + if (separator === "") { separator = ","; } + + fileRecordCounter++; + + } + + fileContent = "[" + fileContent + "]"; + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timePeriodLabel + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + 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 = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + CONDITIONS_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timePeriodLabel + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + 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 + "]"; + + if (separator === "") { separator = ","; } + + fileRecordCounter++; + + } + + fileContent = "[" + fileContent + "]"; + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + STRATEGIES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timePeriodLabel + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + 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 + "]"; + + if (separator === "") { separator = ","; } + + fileRecordCounter++; + + } + + fileContent = "[" + fileContent + "]"; + + let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + + let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion; + let filePath = filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + TRADES_FOLDER_NAME + "/" + "Multi-Period-Daily" + "/" + timePeriodLabel + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.createTextFile(bot.devTeam, 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.assetA + "_" + market.assetB); + + callBackFunction(err); + return; + + } + + /* Emit event that signals that simulation files were updated */ + global.SYSTEM_EVENT_HANDLER.raiseEvent('Jason-Multi-Period', 'Simulation Files Updated') + + 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/Project.njsproj b/Project.njsproj new file mode 100644 index 000000000..0351129cc --- /dev/null +++ b/Project.njsproj @@ -0,0 +1,91 @@ + + + + Debug + 2.0 + {aa2901bc-8f81-40f0-a402-5ca79a3f22ad} + + ShowAllFiles + + + c:\users\luis\source\repos\AAOlivia\Multi-Period-Market + . + {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + Project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + False + True + 0 + / + http://localhost:48022/ + False + True + http://localhost:1337 + False + + + + + + + CurrentPage + True + False + False + False + + + + + + + + + False + False + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..099b74e58 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# I'm Jason, an AA i-Bot! + +# Disclaimer + +THE AA MASTERS BOTS AND THEIR ASSOCIATED PRODUCTS AND SERVICES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, SUITABILITY FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + +IN NO EVENT WILL ADVANCED ALGOS BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY USE OF THE AACLOUD, THE AA ARENA COMPETITION, THE AA MASTERS BOTS, OR ANY OTHER ASSOCIATED SERVICE OR WEB SITE, INCLUDING, WITHOUT LIMITATION, ANY LOST PROFITS, BUSINESS INTERRUPTION, LOSS OF FUNDS, PROGRAMS OR OTHER DATA OR OTHERWISE, EVEN IF WE ARE EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..4fc77c7f4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "AAToma", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@superalgos/mqservice": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@superalgos/mqservice/-/mqservice-0.0.3.tgz", + "integrity": "sha512-7beJRbcylkzND1MnWdEIrmtrqOyFvRsS131JKk6Xap5fFpS8kPpXKHEY50K97qwBnbTJZSJyNvSjafJPRUNjqQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..76760c537 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "AAToma", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Luis-Fernando-Molina", + "license": "ISC", + "dependencies": { + "@superalgos/mqservice": "0.0.3" + } +} diff --git a/this.bot.config.json b/this.bot.config.json new file mode 100644 index 000000000..8361d7bf1 --- /dev/null +++ b/this.bot.config.json @@ -0,0 +1,537 @@ +{ + "displayName": "Jason", + "codeName": "AAJason", + "type": "Trading Bot", + "version": { + "major": 1, + "minor": 0, + "patch": 0 + }, + "devTeam": "AAMasters", + "profilePicture": "Jason.png", + "dataSetVersion": "dataSet.V1", + "processes": [ + { + "name": "Multi-Period", + "description": "Produces a Daily or Market files depending on the current timePeriod.", + "startMode": { + "userDefined": { + "run": "true" + } + }, + "deadWaitTime": 0, + "liveWaitTime": 60000, + "normalWaitTime": 5000, + "retryWaitTime": 10000, + "sleepWaitTime": 3600000, + "comaWaitTime": 86400000, + "updatesDatasets": [ + { + "product": "Trading-Simulation", + "dataSet": "Multi-Period-Market" + }, + { + "product": "Simulation-Conditions", + "dataSet": "Multi-Period-Market" + }, + { + "product": "Simulation-Strategies", + "dataSet": "Multi-Period-Market" + }, + { + "product": "Simulation-Trades", + "dataSet": "Multi-Period-Market" + }, + { + "product": "Live Trading History", + "dataSet": "Multi-Period-Market" + }, + { + "product": "Trading-Simulation", + "dataSet": "Multi-Period-Daily" + }, + { + "product": "Simulation-Conditions", + "dataSet": "Multi-Period-Daily" + }, + { + "product": "Simulation-Strategies", + "dataSet": "Multi-Period-Daily" + }, + { + "product": "Simulation-Trades", + "dataSet": "Multi-Period-Daily" + }, + { + "product": "Live Trading History", + "dataSet": "Oficial History" + } + ], + "framework": { + "name": "Multi-Period", + "validPeriods": [ + "45-min", + "40-min", + "30-min", + "20-min", + "15-min", + "10-min", + "05-min", + "04-min", + "03-min", + "02-min", + "01-min" + ], + "startDate": { + "takeItFromStatusDependency": 2 + }, + "endDate": { + "takeItFromStatusDependency": 1 + } + }, + "statusDependencies": [ + { + "devTeam": "AAMasters", + "bot": "AACharly", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Historic-Trades", + "dataSetVersion": "dataSet.V1" + }, + { + "devTeam": "AAMasters", + "bot": "AAOlivia", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Multi-Period-Daily", + "dataSetVersion": "dataSet.V1" + }, + { + "devTeam": "AAMasters", + "bot": "AAJason", + "type": "Trading Engine", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Multi-Period", + "dataSetVersion": "dataSet.V1" + }, + { + "devTeam": "AAMasters", + "bot": "AAJason", + "type": "Trading Engine", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Context", + "dataSetVersion": "dataSet.V1" + } + ], + "dataDependencies": [ + { + "devTeam": "AAMasters", + "bot": "AAChris", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Percentage-Bandwidth", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Market" + }, + { + "devTeam": "AAMasters", + "bot": "AAChris", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Bollinger-Bands", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Market" + }, + { + "devTeam": "AAMasters", + "bot": "AAPaula", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Bollinger-Standard-Channels", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Market" + }, + { + "devTeam": "AAMasters", + "bot": "AAPaula", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Bollinger-Standard-Sub-Channels", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Market" + }, + { + "devTeam": "AAMasters", + "bot": "AAOlivia", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Candles", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Market" + }, + { + "devTeam": "AAMasters", + "bot": "AAChris", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Percentage-Bandwidth", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Daily" + }, + { + "devTeam": "AAMasters", + "bot": "AAChris", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Bollinger-Bands", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Daily" + }, + { + "devTeam": "AAMasters", + "bot": "AAPaula", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Bollinger-Standard-Channels", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Daily" + }, + { + "devTeam": "AAMasters", + "bot": "AAPaula", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Bollinger-Standard-Sub-Channels", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Daily" + }, + { + "devTeam": "AAMasters", + "bot": "AAOlivia", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Candles", + "dataSetVersion": "dataSet.V1", + "dataSet": "Multi-Period-Daily" + } + ] + } + ], + "products": [ + { + "codeName": "Trading-Simulation", + "displayName": "Simulation", + "description": "Trading Simulation", + "storageAccount": "aajason", + "dataSets": [ + { + "codeName": "Multi-Period-Market", + "type": "Market Files", + "validPeriods": [ + "24-hs", + "12-hs", + "08-hs", + "06-hs", + "04-hs", + "03-hs", + "02-hs", + "01-hs" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Trading-Simulation/Multi-Period-Market/@Period", + "fileName": "@AssetA_@AssetB.json" + }, + { + "codeName": "Multi-Period-Daily", + "type": "Daily Files", + "validPeriods": [ + "45-min", + "40-min", + "30-min", + "20-min", + "15-min", + "10-min", + "05-min", + "04-min", + "03-min", + "02-min", + "01-min" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Trading-Simulation/Multi-Period-Daily/@Period/@Year/@Month/@Day", + "fileName": "@AssetA_@AssetB.json", + "dataRange": { + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Trading-Simulation/Multi-Period-Daily", + "fileName": "Data.Range.@AssetA_@AssetB.json" + } + } + ], + "exchangeList": [ + { + "name": "Poloniex" + }, + { + "name": "Coss" + } + ], + "plotter": { + "devTeam": "AAMasters", + "codeName": "PlottersTradingSimulation", + "moduleName": "TradingSimulation" + } + }, + { + "codeName": "Simulation-Conditions", + "displayName": "Conditions", + "description": "Simulation Conditions", + "storageAccount": "aajason", + "dataSets": [ + { + "codeName": "Multi-Period-Market", + "type": "Market Files", + "validPeriods": [ + "24-hs", + "12-hs", + "08-hs", + "06-hs", + "04-hs", + "03-hs", + "02-hs", + "01-hs" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Conditions/Multi-Period-Market/@Period", + "fileName": "@AssetA_@AssetB.json" + }, + { + "codeName": "Multi-Period-Daily", + "type": "Daily Files", + "validPeriods": [ + "45-min", + "40-min", + "30-min", + "20-min", + "15-min", + "10-min", + "05-min", + "04-min", + "03-min", + "02-min", + "01-min" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Conditions/Multi-Period-Daily/@Period/@Year/@Month/@Day", + "fileName": "@AssetA_@AssetB.json", + "dataRange": { + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Conditions/Multi-Period-Daily", + "fileName": "Data.Range.@AssetA_@AssetB.json" + } + } + ], + "exchangeList": [ + { + "name": "Poloniex" + }, + { + "name": "Coss" + } + ], + "plotter": { + "devTeam": "AAMasters", + "codeName": "PlottersTradingSimulation", + "moduleName": "Conditions" + } + }, + { + "codeName": "Simulation-Strategies", + "displayName": "Strategies", + "description": "Simulation Strategies", + "storageAccount": "aajason", + "dataSets": [ + { + "codeName": "Multi-Period-Market", + "type": "Market Files", + "validPeriods": [ + "24-hs", + "12-hs", + "08-hs", + "06-hs", + "04-hs", + "03-hs", + "02-hs", + "01-hs" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Strategies/Multi-Period-Market/@Period", + "fileName": "@AssetA_@AssetB.json" + }, + { + "codeName": "Multi-Period-Daily", + "type": "Daily Files", + "validPeriods": [ + "45-min", + "40-min", + "30-min", + "20-min", + "15-min", + "10-min", + "05-min", + "04-min", + "03-min", + "02-min", + "01-min" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Strategies/Multi-Period-Daily/@Period/@Year/@Month/@Day", + "fileName": "@AssetA_@AssetB.json", + "dataRange": { + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Strategies/Multi-Period-Daily", + "fileName": "Data.Range.@AssetA_@AssetB.json" + } + } + ], + "exchangeList": [ + { + "name": "Poloniex" + }, + { + "name": "Coss" + } + ], + "plotter": { + "devTeam": "AAMasters", + "codeName": "PlottersTradingSimulation", + "moduleName": "Strategies" + } + }, + { + "codeName": "Simulation-Trades", + "displayName": "Trades", + "description": "Simulation Trades", + "storageAccount": "aajason", + "dataSets": [ + { + "codeName": "Multi-Period-Market", + "type": "Market Files", + "validPeriods": [ + "24-hs", + "12-hs", + "08-hs", + "06-hs", + "04-hs", + "03-hs", + "02-hs", + "01-hs" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Trades/Multi-Period-Market/@Period", + "fileName": "@AssetA_@AssetB.json" + }, + { + "codeName": "Multi-Period-Daily", + "type": "Daily Files", + "validPeriods": [ + "45-min", + "40-min", + "30-min", + "20-min", + "15-min", + "10-min", + "05-min", + "04-min", + "03-min", + "02-min", + "01-min" + ], + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Trades/Multi-Period-Daily/@Period/@Year/@Month/@Day", + "fileName": "@AssetA_@AssetB.json", + "dataRange": { + "filePath": "AAMasters/AAJason.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/@Session/Simulation-Trades/Multi-Period-Daily", + "fileName": "Data.Range.@AssetA_@AssetB.json" + } + } + ], + "exchangeList": [ + { + "name": "Poloniex" + }, + { + "name": "Coss" + } + ], + "plotter": { + "devTeam": "AAMasters", + "codeName": "PlottersTradingSimulation", + "moduleName": "Trades" + } + }, + { + "codeName": "Live-Trading-History", + "displayName": "Live", + "shortDisplayName": "Live", + "description": "Live Trading Execution.", + "dataSets": [ + { + "codeName": "Oficial History", + "type": "File Sequence", + "validPeriods": [ + "24-hs", + "12-hs", + "08-hs", + "06-hs", + "04-hs", + "03-hs", + "02-hs", + "01-hs", + "45-min", + "40-min", + "30-min", + "20-min", + "15-min", + "10-min", + "05-min", + "04-min", + "03-min", + "02-min", + "01-min" + ], + "filePath": "@DevTeam/@Bot.1.0/AACloud.1.1/Poloniex/dataSet.V1/Output/@Session/Trading-Process", + "fileName": "Execution.History.Live.@Sequence.json" + } + ], + "exchangeList": [ + { + "name": "Poloniex" + } + ], + "plotter": { + "devTeam": "AAMasters", + "codeName": "PlottersTradingSimulation", + "moduleName": "SimulationExecution" + } + } + ] +} \ No newline at end of file