From 009417144c42004dff962fb456719ffdaf6cea76 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Fri, 15 Mar 2019 18:23:06 +0100 Subject: [PATCH] Simulator moved to Commons --- Commons.js | 937 ++++++++++++++++++++++++++++++- Multi-Period-Market/User.Bot.js | 938 +------------------------------- 2 files changed, 937 insertions(+), 938 deletions(-) diff --git a/Commons.js b/Commons.js index a6da45316..4fe9e107f 100644 --- a/Commons.js +++ b/Commons.js @@ -8,7 +8,8 @@ const GMT_SECONDS = ':00.000 GMT+0000'; let thisObject = { - initializeStorage: initializeStorage + initializeStorage: initializeStorage, + runSimulation: runSimulation }; let utilities = UTILITIES.newCloudUtilities(bot, logger); @@ -98,4 +99,938 @@ } } + function runSimulation( + candles, + bollingerBandsMap, + percentageBandwidthMap, + LRCMap, + bollingerChannelsArray, + bollingerSubChannelsArray, + recordsArray, + conditionsArray, + simulationLogic, + outputPeriod, + callback) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] runSimulation -> Entering function."); } + + /* Initial Values */ + + let initialDate = new Date("2018-08-01"); + let initialBalanceA = 1; + let minimunBalanceA = 0.5; + + /* Strategy and Phases */ + + let strategyNumber = 0; + let strategyPhase = 0; // So far we will consider 5 possible phases: 0 = Initial state, 1 = Signal to buy, 2 = Buy, 3 = After Buy, 4 = Sell. + + /* Stop Loss Management */ + + let stopLossPercentage = 1.5; + let previousStopLoss = 0; + let stopLoss = 0; + let stopLossDecay = 0; + let stopLossDecayIncrement = 0.06; + let stopLossPhase = 0; + + /* Buy Order Management */ + + let buyOrderPercentage = 1; + let previousBuyOrder = 0; + let buyOrder = 0; + let buyOrderDecay = 0; + let buyOrderPhase = 0; + + /* Building records */ + + let record; + let balanceAssetA = initialBalanceA; + let balanceAssetB = 0; + let profit = 0; + let lastProfit = 0; + let lastProfitPercent = 0; + let sellRate = 0; + + let previousBalanceAssetA = 0; + let roundtrips = 0; + let hits = 0; + let fails = 0; + let hitRatio = 0; + let ROI = 0; + let periods = 0; + let days = 0; + let anualizedRateOfReturn = 0; + let type = '""'; + let rate = 0; + let newStopLoss; + + let initialBuffer = 3; + + simulationLogic.strategies = [ + { + name: "Trend Following", + entryPoint: { + situations: [ + { + name: "Price Collapsing", + conditions: [ + { + name: "%B Moving Average going up", + code: "percentageBandwidth.previous.direction === 'down' && percentageBandwidth.direction === 'down'" + }, + { + name: "%B Bandwidth going up", + code: "percentageBandwidth.previous.previous.bandwidth < percentageBandwidth.previous.bandwidth && percentageBandwidth.previous.bandwidth < percentageBandwidth.bandwidth" + }, + { + name: "Candles Min going down", + code: "candle.previous.previous.min > candle.previous.min && candle.previous.min > candle.min" + } + ] + } + ] + }, + exitPoint: { + situations: [ + { + name: "Market Reversing", + conditions: [ + { + name: "Close above Band Moving Average", + code: "candle.close > bollingerBand.movingAverage" + } + ] + } + ] + }, + sellPoint: { + situations: [ + { + name: "Min below lower bollingerBand.", + conditions: [ + { + name: "3 Candles MIN below Lower Band", + code: "candle.previous.previous.min < bollingerBand.previous.previous.movingAverage - bollingerBand.previous.previous.deviation && candle.previous.min < bollingerBand.previous.movingAverage - bollingerBand.previous.deviation && candle.min < bollingerBand.movingAverage - bollingerBand.deviation" + } + ] + } + ] + }, + stopLoss: { + phases: [ + { + name: "Following Sell Rate", + code: "newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100", + situations: [ + { + name: "Candle below Moving Average", + conditions: [ + { + name: "Candle fully below Band Moving Average", + code: "candle.max < bollingerBand.movingAverage" + }, + { + name: "Band Moving Average going down", + code: "bollingerBand.direction === 'down'" + } + ] + } + ] + }, + { + name: "Above Bands Moving Average", + code: "newStopLoss = bollingerBand.movingAverage + bollingerBand.movingAverage * (stopLossPercentage - stopLossDecay) / 100", + situations: [ + { + name: "Candle below Moving Average", + conditions: [ + { + name: "Candle MAX below lower band", + code: "candle.max < bollingerBand.movingAverage - bollingerBand.deviation" + } + ] + } + ] + }, + { + name: "At Bands Moving Average", + code: "newStopLoss = bollingerBand.movingAverage", + situations: [ + ] + } + ] + }, + buyOrder: { + phases: [ + { + name: "12 times standard deviation", + code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 12", + situations: [ + { + name: "Candle cut by lower band", + conditions: [ + { + name: "Max above lower band", + code: "candle.max > bollingerBand.movingAverage - bollingerBand.deviation" + }, + { + name: "MIN below lower band", + code: "candle.min < bollingerBand.movingAverage - bollingerBand.deviation" + }, + { + name: "Band Moving Average going down", + code: "bollingerBand.previous.movingAverage > bollingerBand.movingAverage" + } + ] + } + ] + }, + { + name: "10 times standard deviation", + code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 10", + situations: [ + { + name: "%B starting to revert", + conditions: [ + { + name: "%B Moving Average going up", + code: "percentageBandwidth.direction === 'up'" + }, + { + name: "%B Moving Average above 0", + code: "percentageBandwidth.movingAverage > 0" + } + ] + } + ] + }, + { + name: "4 times standard deviation", + code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 4", + situations: [ + { + name: "%B going down again", + conditions: [ + { + name: "%B Moving Average going down", + code: "percentageBandwidth.direction === 'down'" + } + ] + } + ] + }, + { + name: "3 times standard deviation", + code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 3", + situations: [ + { + name: "%B going up and above 30", + conditions: [ + { + name: "%B Moving Average going up", + code: "percentageBandwidth.direction === 'up'" + }, + { + name: "%B Moving Average above 30", + code: "percentageBandwidth.movingAverage > 30" + } + ] + } + ] + }, + { + name: "At lower band", + code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 2", + situations: [ + ] + } + ] + } + }, + { + name: "Range Trading", + entryPoint: { + situations: [ + { + name: "Gentle Up Trend", + conditions: [ + { + name: "Sub-Channel going up", + code: "subChannel.direction === 'Up'" + }, + { + name: "Sub-Channel Side|Gentle|Medium", + code: "subChannel.slope === 'Side' || subChannel.slope === 'Gentle' || subChannel.slope === 'Medium'" + }, + { + name: "Candle Close above Lower Band", + code: "candle.close > bollingerBand.movingAverage + bollingerBand.deviation" + }, + { + name: "Impossible", + code: " 1 === 2" + } + ] + } + ] + }, + exitPoint: { + situations: [ + { + name: "Outside Sub-Channel", + conditions: [ + { + name: "Going down or too Steep", + code: "subChannel.direction === 'Down' || subChannel.slope === 'Steep' || subChannel.slope === 'Extreme'" + } + ] + } + ] + }, + sellPoint: { + situations: [ + { + name: "Min below lower bollingerBand.", + conditions: [ + { + name: "%B Moving Average going down", + code: "percentageBandwidth.previous.movingAverage > percentageBandwidth.movingAverage" + }, + { + name: "%B Moving Average above 90", + code: "percentageBandwidth.previous.movingAverage > 90" + } + ] + } + ] + }, + stopLoss: { + phases: [ + { + name: "Following Sell Rate", + code: "newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100", + situations: [ + ] + } + ] + }, + buyOrder: { + phases: [ + { + name: "Between Band Moving Average and Lower Band", + code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation", + situations: [ + ] + } + ] + } + } + , + { + name: "LRC Strategy", + entryPoint: { + situations: [ + { + name: "Lines in the right order", + conditions: [ + { + name: "60 > 30", + code: "LRC._60 > LRC._30" + }, + { + name: "30 > 15", + code: "LRC._30 > LRC._15" + }, + { + name: "Impossible", + code: " 1 === 2" + } + ] + } + ] + }, + exitPoint: { + situations: [ + { + name: "Lines out of order", + conditions: [ + { + name: "60 < 30 or 30 < 15", + code: "LRC._60 < LRC._30 || LRC._30 < LRC._15 " + } + ] + } + ] + }, + sellPoint: { + situations: [ + { + name: "All lines going down", + conditions: [ + { + name: "60 going down", + code: "LRC.direction60 === 'down'" + }, + { + name: "30 going down", + code: "LRC.direction30 === 'down'" + }, + { + name: "15 going down", + code: "LRC.direction15 === 'down'" + } + ] + } + ] + }, + stopLoss: { + phases: [ + { + name: "Following Sell Rate", + code: "newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100", + situations: [ + { + name: "Stop Loss < 60", + conditions: [ + { + name: "Stop Loss < 60", + code: "stopLoss < LRC._60" + } + ] + } + ] + }, + { + name: "Following 60", + code: "newStopLoss = LRC._60", + situations: [ + ] + } + ] + }, + buyOrder: { + phases: [ + { + name: "Between Band Moving Average and Lower Band", + code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 4", + situations: [ + ] + } + ] + } + } + ]; + + /* Main Simulation Loop: We go thourgh all the candles at this time period. */ + + for (let i = 0 + initialBuffer; i < candles.length; i++) { + + /* Update all the data objects available for the simulation. */ + + let candle = candles[i]; + let percentageBandwidth = percentageBandwidthMap.get(candle.begin); + let bollingerBand = bollingerBandsMap.get(candle.begin); + let LRC = LRCMap.get(candle.begin); + + if (LRC === undefined) { continue; } + if (percentageBandwidth === undefined) { continue; } // percentageBandwidth might start after the first few candles. + if (candle.begin < initialDate.valueOf()) { continue; } + + periods++; + + let subChannel = getElement(bollingerSubChannelsArray, candle.begin, candle.end); + + let conditions = new Map; // Here we store the conditions values that will be use in the simulator for decision making. + 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. + + /* 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 < simulationLogic.strategies.length; j++) { + + let strategy = simulationLogic.strategies[j]; + + for (let k = 0; k < strategy.entryPoint.situations.length; k++) { + + let situation = strategy.entryPoint.situations[k]; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + situation.name + '-' + condition.name; + + newCondition(key, condition.code); + } + } + + for (let k = 0; k < strategy.exitPoint.situations.length; k++) { + + let situation = strategy.exitPoint.situations[k]; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + situation.name + '-' + condition.name; + + newCondition(key, condition.code); + } + } + + for (let k = 0; k < strategy.sellPoint.situations.length; k++) { + + let situation = strategy.sellPoint.situations[k]; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + situation.name + '-' + condition.name; + + newCondition(key, condition.code); + } + } + + for (let p = 0; p < strategy.stopLoss.phases.length; p++) { + + let phase = strategy.stopLoss.phases[p]; + + for (let k = 0; k < phase.situations.length; k++) { + + let situation = phase.situations[k]; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name; + + newCondition(key, condition.code); + } + } + } + + for (let p = 0; p < strategy.buyOrder.phases.length; p++) { + + let phase = strategy.buyOrder.phases[p]; + + for (let k = 0; k < phase.situations.length; k++) { + + let situation = phase.situations[k]; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name; + + newCondition(key, condition.code); + } + } + } + } + + function newCondition(key, code) { + + let condition; + + condition = { + key: key, + value: eval(code) + }; + + conditions.set(condition.key, condition); + + if (condition.value) { + conditionsArrayValues.push(1); + } else { + conditionsArrayValues.push(0); + } + } + + /* While we are outside all strategies, we evaluate whether it is time to enter one or not. */ + + if (strategyNumber === 0 && + balanceAssetA > minimunBalanceA) { + + /* + 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 an entering condition. Once we enter + into one strategy, we will ignore market conditions for others. However there is also + a strategy exit condition which can be hit before entering into a trade. If hit, we would + be outside a strategy again and looking for the condition to enter all over again. + + */ + + checkEntryPoints(); + + function checkEntryPoints() { + + for (let j = 0; j < simulationLogic.strategies.length; j++) { + + let strategy = simulationLogic.strategies[j]; + + for (let k = 0; k < strategy.entryPoint.situations.length; k++) { + + let situation = strategy.entryPoint.situations[k]; + let passed = true; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + situation.name + '-' + condition.name + + let value = conditions.get(key).value; + + if (value === false) { passed = false; } + } + + if (passed) { + + strategyPhase = 1; + strategyNumber = j + 1; + + return; + } + } + } + } + } + + /* Strategy Exit Condition */ + + if (strategyPhase === 1) { + + checkExitPoints(); + + function checkExitPoints() { + + let strategy = simulationLogic.strategies[strategyNumber - 1]; + + for (let k = 0; k < strategy.exitPoint.situations.length; k++) { + + let situation = strategy.exitPoint.situations[k]; + let passed = true; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + situation.name + '-' + condition.name + + let value = conditions.get(key).value; + + if (value === false) { passed = false; } + } + + if (passed) { + + strategyPhase = 0; + strategyNumber = 0; + + return; + } + } + } + } + + if (strategyPhase === 3) { + + /* Checking what happened since the last execution. We need to know if the Stop Loss + or our Buy Order were hit. */ + + /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ + + if (candle.max >= stopLoss) { + + balanceAssetA = balanceAssetB / stopLoss; + + balanceAssetB = 0; + rate = stopLoss; + type = '"Buy@StopLoss"'; + strategyPhase = 4; + } + + /* Buy Order condition: Here we verify if the Buy Order was filled or not. */ + + if (candle.min <= buyOrder) { + + balanceAssetA = balanceAssetB / buyOrder; + + balanceAssetB = 0; + rate = buyOrder; + type = '"Buy@BuyOrder"'; + strategyPhase = 4; + } + } + + /* Strategy Sell Condition */ + + if (strategyPhase === 1) { + + checkSellPoints(); + + function checkSellPoints() { + + let strategy = simulationLogic.strategies[strategyNumber - 1]; + + for (let k = 0; k < strategy.sellPoint.situations.length; k++) { + + let situation = strategy.sellPoint.situations[k]; + let passed = true; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + situation.name + '-' + condition.name + + let value = conditions.get(key).value; + + if (value === false) { passed = false; } + } + + if (passed) { + + type = '"Sell"'; + + strategyPhase = 2; + stopLossPhase = 1; + buyOrderPhase = 1; + + return; + } + } + } + } + + /* Stop Loss Management */ + + if (strategyPhase === 3) { + + checkStopLoss(); + + function checkStopLoss() { + + let strategy = simulationLogic.strategies[strategyNumber - 1]; + + let phase = strategy.stopLoss.phases[stopLossPhase - 1]; + + eval(phase.code); // Here is where we apply the formula given for the stop loss for this phase. + + if (newStopLoss < previousStopLoss) { + stopLoss = newStopLoss; + } else { + stopLoss = previousStopLoss; + } + + for (let k = 0; k < phase.situations.length; k++) { + + let situation = phase.situations[k]; + let passed = true; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name + + let value = conditions.get(key).value; + + if (value === false) { passed = false; } + } + + if (passed) { + + stopLossPhase++; + + return; + } + } + } + } + + /* Buy Order Management */ + + if (strategyPhase === 3) { + + checkBuyOrder(); + + function checkBuyOrder() { + + let strategy = simulationLogic.strategies[strategyNumber - 1]; + + let phase = strategy.buyOrder.phases[buyOrderPhase - 1]; + + eval(phase.code); // Here is where we apply the formula given for the buy order at this phase. + + for (let k = 0; k < phase.situations.length; k++) { + + let situation = phase.situations[k]; + let passed = true; + + for (let m = 0; m < situation.conditions.length; m++) { + + let condition = situation.conditions[m]; + let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name + + let value = conditions.get(key).value; + + if (value === false) { passed = false; } + } + + if (passed) { + + buyOrderPhase++; + + return; + } + } + } + } + + /* Check if we need to sell. */ + + if (strategyPhase === 2) { + + previousBalanceAssetA = balanceAssetA; + lastProfit = 0; + lastProfitPercent = 0; + + balanceAssetB = balanceAssetA * candle.close; + balanceAssetA = 0; + + rate = candle.close; + sellRate = rate; + + stopLoss = sellRate + sellRate * stopLossPercentage / 100; + + stopLossDecay = 0; + + addRecord(); + + strategyPhase = 3; + continue; + } + + /* Check if we need to buy. */ + + if (strategyPhase === 4) { + + roundtrips++; + lastProfit = balanceAssetA - previousBalanceAssetA; + + lastProfitPercent = lastProfit / previousBalanceAssetA * 100; + if (isNaN(lastProfitPercent)) { lastProfitPercent = 0; } + + profit = profit + lastProfit; + + ROI = (initialBalanceA + profit) / initialBalanceA - 1; + //if (isNaN(ROI)) { ROI = 0; } + + if (lastProfit > 0) { + hits++; + } else { + fails++; + } + hitRatio = hits / roundtrips; + + let miliSecondsPerDay = 24 * 60 * 60 * 1000; + days = periods * outputPeriod / miliSecondsPerDay; + anualizedRateOfReturn = ROI / days * 365; + + addRecord(); + + strategyNumber = 0; + stopLoss = 0; + sellRate = 0; + buyOrder = 0; + strategyPhase = 0; + stopLossPhase = 0; + buyOrderPhase = 0; + + continue; + + } + + /* Not a buy or sell condition */ + + rate = candle.close; + addRecord(); + + function addRecord() { + + record = { + begin: candle.begin, + end: candle.end, + type: type, + rate: rate, + amount: 1, + balanceA: balanceAssetA, + balanceB: balanceAssetB, + profit: profit, + lastProfit: lastProfit, + stopLoss: stopLoss, + roundtrips: roundtrips, + hits: hits, + fails: fails, + hitRatio: hitRatio, + ROI: ROI, + periods: periods, + days: days, + anualizedRateOfReturn: anualizedRateOfReturn, + sellRate: sellRate, + lastProfitPercent: lastProfitPercent, + strategy: strategyNumber, + strategyPhase: strategyPhase, + buyOrder: buyOrder, + stopLossPhase: stopLossPhase, + buyOrderPhase: buyOrderPhase + } + + recordsArray.push(record); + + previousStopLoss = stopLoss; + + type = '""'; + + conditionsArrayRecord.push(strategyNumber); + conditionsArrayRecord.push(strategyPhase); + conditionsArrayRecord.push(stopLossPhase); + conditionsArrayRecord.push(buyOrderPhase); + conditionsArrayRecord.push(conditionsArrayValues); + + conditionsArray.push(conditionsArrayRecord); + + } + } + + callback(); + + function getElement(pArray, begin, end) { + + let element; + + for (let i = 0; i < pArray.length; i++) { + + element = pArray[i]; + + if (begin >= element.begin && end <= element.end) { + return element + } + } + + element = { + direction: 'unknown', + slope: 'unknown' + }; + return element; + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] runSimulation -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } }; \ No newline at end of file diff --git a/Multi-Period-Market/User.Bot.js b/Multi-Period-Market/User.Bot.js index 1797fa834..102cd4145 100644 --- a/Multi-Period-Market/User.Bot.js +++ b/Multi-Period-Market/User.Bot.js @@ -653,7 +653,7 @@ previous = candle; } - runSimulation( + commons.runSimulation( candles, bollingerBandsMap, percentageBandwidthMap, @@ -894,942 +894,6 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - - function runSimulation( - candles, - bollingerBandsMap, - percentageBandwidthMap, - LRCMap, - bollingerChannelsArray, - bollingerSubChannelsArray, - recordsArray, - conditionsArray, - simulationLogic, - outputPeriod, - callback) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> runSimulation -> Entering function."); } - - /* Initial Values */ - - let initialDate = new Date("2018-08-01"); - let initialBalanceA = 1; - let minimunBalanceA = 0.5; - - /* Strategy and Phases */ - - let strategyNumber = 0; - let strategyPhase = 0; // So far we will consider 5 possible phases: 0 = Initial state, 1 = Signal to buy, 2 = Buy, 3 = After Buy, 4 = Sell. - - /* Stop Loss Management */ - - let stopLossPercentage = 1.5; - let previousStopLoss = 0; - let stopLoss = 0; - let stopLossDecay = 0; - let stopLossDecayIncrement = 0.06; - let stopLossPhase = 0; - - /* Buy Order Management */ - - let buyOrderPercentage = 1; - let previousBuyOrder = 0; - let buyOrder = 0; - let buyOrderDecay = 0; - let buyOrderPhase = 0; - - /* Building records */ - - let record; - let balanceAssetA = initialBalanceA; - let balanceAssetB = 0; - let profit = 0; - let lastProfit = 0; - let lastProfitPercent = 0; - let sellRate = 0; - - let previousBalanceAssetA = 0; - let roundtrips = 0; - let hits = 0; - let fails = 0; - let hitRatio = 0; - let ROI = 0; - let periods = 0; - let days = 0; - let anualizedRateOfReturn = 0; - let type = '""'; - let rate = 0; - let newStopLoss; - - let initialBuffer = 3; - - simulationLogic.strategies = [ - { - name: "Trend Following", - entryPoint: { - situations: [ - { - name: "Price Collapsing", - conditions: [ - { - name: "%B Moving Average going up", - code: "percentageBandwidth.previous.direction === 'down' && percentageBandwidth.direction === 'down'" - }, - { - name: "%B Bandwidth going up", - code: "percentageBandwidth.previous.previous.bandwidth < percentageBandwidth.previous.bandwidth && percentageBandwidth.previous.bandwidth < percentageBandwidth.bandwidth" - }, - { - name: "Candles Min going down", - code: "candle.previous.previous.min > candle.previous.min && candle.previous.min > candle.min" - } - ] - } - ] - }, - exitPoint: { - situations: [ - { - name: "Market Reversing", - conditions: [ - { - name: "Close above Band Moving Average", - code: "candle.close > bollingerBand.movingAverage" - } - ] - } - ] - }, - sellPoint: { - situations: [ - { - name: "Min below lower bollingerBand.", - conditions: [ - { - name: "3 Candles MIN below Lower Band", - code: "candle.previous.previous.min < bollingerBand.previous.previous.movingAverage - bollingerBand.previous.previous.deviation && candle.previous.min < bollingerBand.previous.movingAverage - bollingerBand.previous.deviation && candle.min < bollingerBand.movingAverage - bollingerBand.deviation" - } - ] - } - ] - }, - stopLoss: { - phases: [ - { - name: "Following Sell Rate", - code: "newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100", - situations: [ - { - name: "Candle below Moving Average", - conditions: [ - { - name: "Candle fully below Band Moving Average", - code: "candle.max < bollingerBand.movingAverage" - }, - { - name: "Band Moving Average going down", - code: "bollingerBand.direction === 'down'" - } - ] - } - ] - }, - { - name: "Above Bands Moving Average", - code: "newStopLoss = bollingerBand.movingAverage + bollingerBand.movingAverage * (stopLossPercentage - stopLossDecay) / 100", - situations: [ - { - name: "Candle below Moving Average", - conditions: [ - { - name: "Candle MAX below lower band", - code: "candle.max < bollingerBand.movingAverage - bollingerBand.deviation" - } - ] - } - ] - }, - { - name: "At Bands Moving Average", - code: "newStopLoss = bollingerBand.movingAverage", - situations: [ - ] - } - ] - }, - buyOrder: { - phases: [ - { - name: "12 times standard deviation", - code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 12", - situations: [ - { - name: "Candle cut by lower band", - conditions: [ - { - name: "Max above lower band", - code: "candle.max > bollingerBand.movingAverage - bollingerBand.deviation" - }, - { - name: "MIN below lower band", - code: "candle.min < bollingerBand.movingAverage - bollingerBand.deviation" - }, - { - name: "Band Moving Average going down", - code: "bollingerBand.previous.movingAverage > bollingerBand.movingAverage" - } - ] - } - ] - }, - { - name: "10 times standard deviation", - code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 10", - situations: [ - { - name: "%B starting to revert", - conditions: [ - { - name: "%B Moving Average going up", - code: "percentageBandwidth.direction === 'up'" - }, - { - name: "%B Moving Average above 0", - code: "percentageBandwidth.movingAverage > 0" - } - ] - } - ] - }, - { - name: "4 times standard deviation", - code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 4", - situations: [ - { - name: "%B going down again", - conditions: [ - { - name: "%B Moving Average going down", - code: "percentageBandwidth.direction === 'down'" - } - ] - } - ] - }, - { - name: "3 times standard deviation", - code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 3", - situations: [ - { - name: "%B going up and above 30", - conditions: [ - { - name: "%B Moving Average going up", - code: "percentageBandwidth.direction === 'up'" - }, - { - name: "%B Moving Average above 30", - code: "percentageBandwidth.movingAverage > 30" - } - ] - } - ] - }, - { - name: "At lower band", - code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 2", - situations: [ - ] - } - ] - } - }, - { - name: "Range Trading", - entryPoint: { - situations: [ - { - name: "Gentle Up Trend", - conditions: [ - { - name: "Sub-Channel going up", - code: "subChannel.direction === 'Up'" - }, - { - name: "Sub-Channel Side|Gentle|Medium", - code: "subChannel.slope === 'Side' || subChannel.slope === 'Gentle' || subChannel.slope === 'Medium'" - }, - { - name: "Candle Close above Lower Band", - code: "candle.close > bollingerBand.movingAverage + bollingerBand.deviation" - }, - { - name: "Impossible", - code: " 1 === 2" - } - ] - } - ] - }, - exitPoint: { - situations: [ - { - name: "Outside Sub-Channel", - conditions: [ - { - name: "Going down or too Steep", - code: "subChannel.direction === 'Down' || subChannel.slope === 'Steep' || subChannel.slope === 'Extreme'" - } - ] - } - ] - }, - sellPoint: { - situations: [ - { - name: "Min below lower bollingerBand.", - conditions: [ - { - name: "%B Moving Average going down", - code: "percentageBandwidth.previous.movingAverage > percentageBandwidth.movingAverage" - }, - { - name: "%B Moving Average above 90", - code: "percentageBandwidth.previous.movingAverage > 90" - } - ] - } - ] - }, - stopLoss: { - phases: [ - { - name: "Following Sell Rate", - code: "newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100", - situations: [ - ] - } - ] - }, - buyOrder: { - phases: [ - { - name: "Between Band Moving Average and Lower Band", - code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation", - situations: [ - ] - } - ] - } - } - , - { - name: "LRC Strategy", - entryPoint: { - situations: [ - { - name: "Lines in the right order", - conditions: [ - { - name: "60 > 30", - code: "LRC._60 > LRC._30" - }, - { - name: "30 > 15", - code: "LRC._30 > LRC._15" - }, - { - name: "Impossible", - code: " 1 === 2" - } - ] - } - ] - }, - exitPoint: { - situations: [ - { - name: "Lines out of order", - conditions: [ - { - name: "60 < 30 or 30 < 15", - code: "LRC._60 < LRC._30 || LRC._30 < LRC._15 " - } - ] - } - ] - }, - sellPoint: { - situations: [ - { - name: "All lines going down", - conditions: [ - { - name: "60 going down", - code: "LRC.direction60 === 'down'" - }, - { - name: "30 going down", - code: "LRC.direction30 === 'down'" - }, - { - name: "15 going down", - code: "LRC.direction15 === 'down'" - } - ] - } - ] - }, - stopLoss: { - phases: [ - { - name: "Following Sell Rate", - code: "newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100", - situations: [ - { - name: "Stop Loss < 60", - conditions: [ - { - name: "Stop Loss < 60", - code: "stopLoss < LRC._60" - } - ] - } - ] - }, - { - name: "Following 60", - code: "newStopLoss = LRC._60", - situations: [ - ] - } - ] - }, - buyOrder: { - phases: [ - { - name: "Between Band Moving Average and Lower Band", - code: "buyOrder = bollingerBand.movingAverage - bollingerBand.standardDeviation * 4", - situations: [ - ] - } - ] - } - } - ]; - - /* Main Simulation Loop: We go thourgh all the candles at this time period. */ - - for (let i = 0 + initialBuffer; i < candles.length; i++) { - - /* Update all the data objects available for the simulation. */ - - let candle = candles[i]; - let percentageBandwidth = percentageBandwidthMap.get(candle.begin); - let bollingerBand = bollingerBandsMap.get(candle.begin); - let LRC = LRCMap.get(candle.begin); - - if (LRC === undefined) { continue; } - if (percentageBandwidth === undefined) { continue; } // percentageBandwidth might start after the first few candles. - if (candle.begin < initialDate.valueOf()) { continue; } - - periods++; - - let subChannel = getElement(bollingerSubChannelsArray, candle.begin, candle.end); - - let conditions = new Map; // Here we store the conditions values that will be use in the simulator for decision making. - 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. - - /* 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 < simulationLogic.strategies.length; j++) { - - let strategy = simulationLogic.strategies[j]; - - for (let k = 0; k < strategy.entryPoint.situations.length; k++) { - - let situation = strategy.entryPoint.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + situation.name + '-' + condition.name; - - newCondition(key, condition.code); - } - } - - for (let k = 0; k < strategy.exitPoint.situations.length; k++) { - - let situation = strategy.exitPoint.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + situation.name + '-' + condition.name; - - newCondition(key, condition.code); - } - } - - for (let k = 0; k < strategy.sellPoint.situations.length; k++) { - - let situation = strategy.sellPoint.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + situation.name + '-' + condition.name; - - newCondition(key, condition.code); - } - } - - for (let p = 0; p < strategy.stopLoss.phases.length; p++) { - - let phase = strategy.stopLoss.phases[p]; - - for (let k = 0; k < phase.situations.length; k++) { - - let situation = phase.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name; - - newCondition(key, condition.code); - } - } - } - - for (let p = 0; p < strategy.buyOrder.phases.length; p++) { - - let phase = strategy.buyOrder.phases[p]; - - for (let k = 0; k < phase.situations.length; k++) { - - let situation = phase.situations[k]; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name; - - newCondition(key, condition.code); - } - } - } - } - - function newCondition(key, code) { - - let condition; - - condition = { - key: key, - value: eval(code) - }; - - conditions.set(condition.key, condition); - - if (condition.value) { - conditionsArrayValues.push(1); - } else { - conditionsArrayValues.push(0); - } - } - - /* While we are outside all strategies, we evaluate whether it is time to enter one or not. */ - - if (strategyNumber === 0 && - balanceAssetA > minimunBalanceA) { - - /* - 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 an entering condition. Once we enter - into one strategy, we will ignore market conditions for others. However there is also - a strategy exit condition which can be hit before entering into a trade. If hit, we would - be outside a strategy again and looking for the condition to enter all over again. - - */ - - checkEntryPoints(); - - function checkEntryPoints() { - - for (let j = 0; j < simulationLogic.strategies.length; j++) { - - let strategy = simulationLogic.strategies[j]; - - for (let k = 0; k < strategy.entryPoint.situations.length; k++) { - - let situation = strategy.entryPoint.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + situation.name + '-' + condition.name - - let value = conditions.get(key).value; - - if (value === false) { passed = false; } - } - - if (passed) { - - strategyPhase = 1; - strategyNumber = j + 1; - - return; - } - } - } - } - } - - /* Strategy Exit Condition */ - - if (strategyPhase === 1) { - - checkExitPoints(); - - function checkExitPoints() { - - let strategy = simulationLogic.strategies[strategyNumber - 1]; - - for (let k = 0; k < strategy.exitPoint.situations.length; k++) { - - let situation = strategy.exitPoint.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + situation.name + '-' + condition.name - - let value = conditions.get(key).value; - - if (value === false) { passed = false; } - } - - if (passed) { - - strategyPhase = 0; - strategyNumber = 0; - - return; - } - } - } - } - - if (strategyPhase === 3) { - - /* Checking what happened since the last execution. We need to know if the Stop Loss - or our Buy Order were hit. */ - - /* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */ - - if (candle.max >= stopLoss) { - - balanceAssetA = balanceAssetB / stopLoss; - - balanceAssetB = 0; - rate = stopLoss; - type = '"Buy@StopLoss"'; - strategyPhase = 4; - } - - /* Buy Order condition: Here we verify if the Buy Order was filled or not. */ - - if (candle.min <= buyOrder) { - - balanceAssetA = balanceAssetB / buyOrder; - - balanceAssetB = 0; - rate = buyOrder; - type = '"Buy@BuyOrder"'; - strategyPhase = 4; - } - } - - /* Strategy Sell Condition */ - - if (strategyPhase === 1) { - - checkSellPoints(); - - function checkSellPoints() { - - let strategy = simulationLogic.strategies[strategyNumber - 1]; - - for (let k = 0; k < strategy.sellPoint.situations.length; k++) { - - let situation = strategy.sellPoint.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + situation.name + '-' + condition.name - - let value = conditions.get(key).value; - - if (value === false) { passed = false; } - } - - if (passed) { - - type = '"Sell"'; - - strategyPhase = 2; - stopLossPhase = 1; - buyOrderPhase = 1; - - return; - } - } - } - } - - /* Stop Loss Management */ - - if (strategyPhase === 3) { - - checkStopLoss(); - - function checkStopLoss() { - - let strategy = simulationLogic.strategies[strategyNumber - 1]; - - let phase = strategy.stopLoss.phases[stopLossPhase - 1]; - - eval(phase.code); // Here is where we apply the formula given for the stop loss for this phase. - - if (newStopLoss < previousStopLoss) { - stopLoss = newStopLoss; - } else { - stopLoss = previousStopLoss; - } - - for (let k = 0; k < phase.situations.length; k++) { - - let situation = phase.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name - - let value = conditions.get(key).value; - - if (value === false) { passed = false; } - } - - if (passed) { - - stopLossPhase++; - - return; - } - } - } - } - - /* Buy Order Management */ - - if (strategyPhase === 3) { - - checkBuyOrder(); - - function checkBuyOrder() { - - let strategy = simulationLogic.strategies[strategyNumber - 1]; - - let phase = strategy.buyOrder.phases[buyOrderPhase - 1]; - - eval(phase.code); // Here is where we apply the formula given for the buy order at this phase. - - for (let k = 0; k < phase.situations.length; k++) { - - let situation = phase.situations[k]; - let passed = true; - - for (let m = 0; m < situation.conditions.length; m++) { - - let condition = situation.conditions[m]; - let key = strategy.name + '-' + phase.name + '-' + situation.name + '-' + condition.name - - let value = conditions.get(key).value; - - if (value === false) { passed = false; } - } - - if (passed) { - - buyOrderPhase++; - - return; - } - } - } - } - - /* Check if we need to sell. */ - - if (strategyPhase === 2) { - - previousBalanceAssetA = balanceAssetA; - lastProfit = 0; - lastProfitPercent = 0; - - balanceAssetB = balanceAssetA * candle.close; - balanceAssetA = 0; - - rate = candle.close; - sellRate = rate; - - stopLoss = sellRate + sellRate * stopLossPercentage / 100; - - stopLossDecay = 0; - - addRecord(); - - strategyPhase = 3; - continue; - } - - /* Check if we need to buy. */ - - if (strategyPhase === 4) { - - roundtrips++; - lastProfit = balanceAssetA - previousBalanceAssetA; - - lastProfitPercent = lastProfit / previousBalanceAssetA * 100; - if (isNaN(lastProfitPercent)) { lastProfitPercent = 0; } - - profit = profit + lastProfit; - - ROI = (initialBalanceA + profit) / initialBalanceA - 1; - //if (isNaN(ROI)) { ROI = 0; } - - if (lastProfit > 0) { - hits++; - } else { - fails++; - } - hitRatio = hits / roundtrips; - - let miliSecondsPerDay = 24 * 60 * 60 * 1000; - days = periods * outputPeriod / miliSecondsPerDay; - anualizedRateOfReturn = ROI / days * 365; - - addRecord(); - - strategyNumber = 0; - stopLoss = 0; - sellRate = 0; - buyOrder = 0; - strategyPhase = 0; - stopLossPhase = 0; - buyOrderPhase = 0; - - continue; - - } - - /* Not a buy or sell condition */ - - rate = candle.close; - addRecord(); - - function addRecord() { - - record = { - begin: candle.begin, - end: candle.end, - type: type, - rate: rate, - amount: 1, - balanceA: balanceAssetA, - balanceB: balanceAssetB, - profit: profit, - lastProfit: lastProfit, - stopLoss: stopLoss, - roundtrips: roundtrips, - hits: hits, - fails: fails, - hitRatio: hitRatio, - ROI: ROI, - periods: periods, - days: days, - anualizedRateOfReturn: anualizedRateOfReturn, - sellRate: sellRate, - lastProfitPercent: lastProfitPercent, - strategy: strategyNumber, - strategyPhase: strategyPhase, - buyOrder: buyOrder, - stopLossPhase: stopLossPhase, - buyOrderPhase: buyOrderPhase - } - - recordsArray.push(record); - - previousStopLoss = stopLoss; - - type = '""'; - - conditionsArrayRecord.push(strategyNumber); - conditionsArrayRecord.push(strategyPhase); - conditionsArrayRecord.push(stopLossPhase); - conditionsArrayRecord.push(buyOrderPhase); - conditionsArrayRecord.push(conditionsArrayValues); - - conditionsArray.push(conditionsArrayRecord); - - } - } - - callback(); - - function getElement(pArray, begin, end) { - - let element; - - for (let i = 0; i < pArray.length; i++) { - - element = pArray[i]; - - if (begin >= element.begin && end <= element.end) { - return element - } - } - - element = { - direction: 'unknown', - slope: 'unknown' - }; - return element; - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> runSimulation -> err = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } catch (err) {