Simulator moved to Commons
This commit is contained in:
1 parent
59fda24d28
commit
009417144c
2 files changed
+937
-938
No files matched your search
+936
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user