Files
Superalgos/Commons.js
T

1036 lines
41 KiB
JavaScript

exports.newCommons = function newCommons(bot, logger, UTILITIES) {
const FULL_LOG = true;
const LOG_FILE_CONTENT = false;
const MODULE_NAME = "Commons";
const GMT_SECONDS = ':00.000 GMT+0000';
let thisObject = {
initializeStorage: initializeStorage,
runSimulation: runSimulation
};
let utilities = UTILITIES.newCloudUtilities(bot, logger);
return thisObject;
function initializeStorage(chrisStorage, jasonStorage, oliviaStorage, gaussStorage, callBackFunction) {
try {
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initializeStorage -> Entering function."); }
initializetomStorage();
function initializetomStorage() {
jasonStorage.initialize(bot.devTeam, onBruceInizialized);
function onBruceInizialized(err) {
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
initializeChrisStorage();
} else {
logger.write(MODULE_NAME, "[ERROR] initializeStorage -> initializetomStorage -> onBruceInizialized -> err = " + err.message);
callBackFunction(err);
}
}
}
function initializeChrisStorage() {
chrisStorage.initialize(bot.devTeam, onChrisInizialized);
function onChrisInizialized(err) {
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
initializeGaussStorage();
} else {
logger.write(MODULE_NAME, "[ERROR] initializeStorage -> initializeChrisStorage -> onChrisInizialized -> err = " + err.message);
callBackFunction(err);
}
}
}
function initializeGaussStorage() {
gaussStorage.initialize("AAVikings", onGaussInizialized);
function onGaussInizialized(err) {
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
initializeOliviaStorage();
} else {
logger.write(MODULE_NAME, "[ERROR] initializeStorage -> initializeGaussStorage -> onChrisInizialized -> err = " + err.message);
callBackFunction(err);
}
}
}
function initializeOliviaStorage() {
oliviaStorage.initialize(bot.devTeam, onOliviaInizialized);
function onOliviaInizialized(err) {
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
callBackFunction(global.DEFAULT_OK_RESPONSE);
} else {
logger.write(MODULE_NAME, "[ERROR] initializeStorage -> initializeChrisStorage -> onChrisInizialized -> err = " + err.message);
callBackFunction(err);
}
}
}
}
catch (err) {
logger.write(MODULE_NAME, "[ERROR] initializeStorage -> err = " + err.message);
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] 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);
}
}
};