849 lines
43 KiB
JavaScript
849 lines
43 KiB
JavaScript
exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, BLOB_STORAGE, FILE_STORAGE) {
|
|
|
|
const FULL_LOG = true;
|
|
const LOG_FILE_CONTENT = false;
|
|
|
|
const MODULE_NAME = "User Bot";
|
|
|
|
const EXCHANGE_NAME = "Poloniex";
|
|
|
|
const PERCENTAGE_BANDWIDTH_FOLDER_NAME = "Percentage-Bandwidth";
|
|
const BOLLINGER_BANDS_FOLDER_NAME = "Bollinger-Bands";
|
|
const CANDLES_FOLDER_NAME = "Candles";
|
|
const SIMULATED_RECORDS_FOLDER_NAME = "Trading-Simulation";
|
|
|
|
const commons = COMMONS.newCommons(bot, logger, UTILITIES);
|
|
|
|
thisObject = {
|
|
initialize: initialize,
|
|
start: start
|
|
};
|
|
|
|
let chrisStorage = BLOB_STORAGE.newBlobStorage(bot, logger);
|
|
let oliviaStorage = BLOB_STORAGE.newBlobStorage(bot, logger);
|
|
let jasonStorage = BLOB_STORAGE.newBlobStorage(bot, logger);
|
|
|
|
let utilities = UTILITIES.newCloudUtilities(bot, logger);
|
|
|
|
let statusDependencies;
|
|
|
|
return thisObject;
|
|
|
|
function initialize(pStatusDependencies, pMonth, pYear, callBackFunction) {
|
|
|
|
try {
|
|
|
|
logger.fileName = MODULE_NAME;
|
|
logger.initialize();
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
|
|
|
|
statusDependencies = pStatusDependencies;
|
|
|
|
commons.initializeStorage(chrisStorage, jasonStorage, oliviaStorage, onInizialized);
|
|
|
|
function onInizialized(err) {
|
|
|
|
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> onInizialized -> Initialization Succeed."); }
|
|
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
|
|
|
} else {
|
|
logger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> err = " + err.message);
|
|
callBackFunction(err);
|
|
}
|
|
}
|
|
|
|
} catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function start(callBackFunction) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); }
|
|
|
|
let market = global.MARKET;
|
|
|
|
buildRecords();
|
|
|
|
function buildRecords() {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> Entering function."); }
|
|
|
|
try {
|
|
|
|
let n;
|
|
|
|
periodsLoop();
|
|
|
|
function periodsLoop() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> periodsLoop -> Entering function."); }
|
|
|
|
/*
|
|
|
|
We will iterate through all posible periods.
|
|
|
|
*/
|
|
|
|
n = 0 // loop Variable representing each possible period as defined at the periods array.
|
|
|
|
loopBody();
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> periodsLoop -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function loopBody() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> Entering function."); }
|
|
|
|
const outputPeriod = global.marketFilesPeriods[n][0];
|
|
const timePeriod = global.marketFilesPeriods[n][1];
|
|
|
|
let marketFile;
|
|
|
|
let percentgeBandwidthMap = new Map();
|
|
let bollingerBandsMap = new Map();
|
|
|
|
let candles = [];
|
|
let recordsArray = [];
|
|
|
|
nextPercentageBandwidth();
|
|
|
|
function nextPercentageBandwidth() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextPercentageBandwidth -> Entering function."); }
|
|
|
|
let fileName = market.assetA + '_' + market.assetB + ".json";
|
|
|
|
let filePathRoot = bot.devTeam + "/" + "AAChris" + "." + bot.version.major + "." + bot.version.minor + "/" + global.PLATFORM_CONFIG.codeName + "." + global.PLATFORM_CONFIG.version.major + "." + global.PLATFORM_CONFIG.version.minor + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion;
|
|
let filePath = filePathRoot + "/Output/" + PERCENTAGE_BANDWIDTH_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriod;
|
|
|
|
chrisStorage.getTextFile(filePath, fileName, onFileReceived, true);
|
|
|
|
function onFileReceived(err, text) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextPercentageBandwidth -> onFileReceived -> Entering function."); }
|
|
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextPercentageBandwidth -> onFileReceived -> text = " + text); }
|
|
|
|
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
|
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextPercentageBandwidth -> onFileReceived -> err = " + err.message);
|
|
callBackFunction(err);
|
|
return;
|
|
|
|
}
|
|
|
|
marketFile = JSON.parse(text);
|
|
|
|
buildPercentageBandwidthMap();
|
|
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextPercentageBandwidth -> onFileReceived -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextPercentageBandwidth -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function buildPercentageBandwidthMap() {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> buildPercentageBandwidthMap -> Entering function."); }
|
|
|
|
try {
|
|
|
|
for (let i = 0; i < marketFile.length; i++) {
|
|
|
|
let percentgeBandwidth = {
|
|
begin: marketFile[i][0],
|
|
end: marketFile[i][1],
|
|
value: marketFile[i][2],
|
|
movingAverage: marketFile[i][3]
|
|
};
|
|
|
|
percentgeBandwidthMap.set(percentgeBandwidth.begin, percentgeBandwidth);
|
|
|
|
}
|
|
|
|
nextBollingerBands();
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> buildPercentageBandwidthMap -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function nextBollingerBands() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextBollingerBands -> Entering function."); }
|
|
|
|
let fileName = market.assetA + '_' + market.assetB + ".json";
|
|
|
|
let filePathRoot = bot.devTeam + "/" + "AAChris" + "." + bot.version.major + "." + bot.version.minor + "/" + global.PLATFORM_CONFIG.codeName + "." + global.PLATFORM_CONFIG.version.major + "." + global.PLATFORM_CONFIG.version.minor + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion;
|
|
let filePath = filePathRoot + "/Output/" + BOLLINGER_BANDS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriod;
|
|
|
|
chrisStorage.getTextFile(filePath, fileName, onFileReceived, true);
|
|
|
|
function onFileReceived(err, text) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextBollingerBands -> onFileReceived -> Entering function."); }
|
|
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextBollingerBands -> onFileReceived -> text = " + text); }
|
|
|
|
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
|
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextBollingerBands -> onFileReceived -> err = " + err.message);
|
|
callBackFunction(err);
|
|
return;
|
|
|
|
}
|
|
|
|
marketFile = JSON.parse(text);
|
|
|
|
buildBollingerBandsMap();
|
|
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextBollingerBands -> onFileReceived -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextBollingerBands -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function buildBollingerBandsMap() {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> buildBollingerBandsMap -> Entering function."); }
|
|
|
|
try {
|
|
|
|
for (let i = 0; i < marketFile.length; i++) {
|
|
|
|
let bollingerBands = {
|
|
begin: marketFile[i][0],
|
|
end: marketFile[i][1],
|
|
movingAverage: marketFile[i][2],
|
|
standardDeviation: marketFile[i][3],
|
|
deviation: marketFile[i][4]
|
|
};
|
|
|
|
bollingerBandsMap.set(bollingerBands.begin, bollingerBands);
|
|
|
|
}
|
|
|
|
nextCandlesFile();
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> buildBollingerBandsMap -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function nextCandlesFile() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextCandlesFile -> Entering function."); }
|
|
|
|
let fileName = market.assetA + '_' + market.assetB + ".json";
|
|
|
|
let filePathRoot = bot.devTeam + "/" + "AAOlivia" + "." + bot.version.major + "." + bot.version.minor + "/" + global.PLATFORM_CONFIG.codeName + "." + global.PLATFORM_CONFIG.version.major + "." + global.PLATFORM_CONFIG.version.minor + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion;
|
|
let filePath = filePathRoot + "/Output/" + CANDLES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriod;
|
|
|
|
oliviaStorage.getTextFile(filePath, fileName, onFileReceived, true);
|
|
|
|
function onFileReceived(err, text) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextCandlesFile -> onFileReceived -> Entering function."); }
|
|
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> nextCandlesFile -> onFileReceived -> text = " + text); }
|
|
|
|
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
|
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextCandlesFile -> onFileReceived -> err = " + err.message);
|
|
callBackFunction(err);
|
|
return;
|
|
|
|
}
|
|
|
|
marketFile = JSON.parse(text);
|
|
|
|
buildCandles();
|
|
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextCandlesFile -> onFileReceived -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> nextCandlesFile -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function buildCandles() {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> buildCandles -> Entering function."); }
|
|
|
|
try {
|
|
|
|
for (let i = 0; i < marketFile.length; i++) {
|
|
|
|
let candle = {
|
|
open: undefined,
|
|
close: undefined,
|
|
min: 10000000000000,
|
|
max: 0,
|
|
begin: undefined,
|
|
end: undefined,
|
|
direction: undefined
|
|
};
|
|
|
|
candle.min = marketFile[i][0];
|
|
candle.max = marketFile[i][1];
|
|
|
|
candle.open = marketFile[i][2];
|
|
candle.close = marketFile[i][3];
|
|
|
|
candle.begin = marketFile[i][4];
|
|
candle.end = marketFile[i][5];
|
|
|
|
if (candle.open > candle.close) { candle.direction = 'down'; }
|
|
if (candle.open < candle.close) { candle.direction = 'up'; }
|
|
if (candle.open === candle.close) { candle.direction = 'side'; }
|
|
|
|
candles.push(candle);
|
|
|
|
}
|
|
|
|
buildRecords();
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> buildCandles -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function buildRecords() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> buildRecords -> Entering function."); }
|
|
|
|
/* Initial Values */
|
|
|
|
let lastOperation = 'Buy';
|
|
let initialDate = new Date("2018-08-01");
|
|
let initialBalanceA = 1;
|
|
|
|
/* Strategy and Phases */
|
|
|
|
let strategy = 0;
|
|
let strategyPhase = 0;
|
|
|
|
/* Building records */
|
|
|
|
let stopLossPercentage = 1.5;
|
|
let previousStopLoss = 0;
|
|
let trailingStop = false;
|
|
let stopLoss = 0;
|
|
let stopLossDecay = 0;
|
|
let stopLossDecayIncrement = 0.06;
|
|
|
|
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 signal = '""';
|
|
let rate = 0;
|
|
|
|
let presellModeIsActive = false;
|
|
|
|
let initialBuffer = 3;
|
|
|
|
/* Main Simulation Loop: We go thourgh all the candles at this time period. */
|
|
|
|
for (let i = 0 + initialBuffer; i < candles.length; i++) {
|
|
|
|
let candle = candles[i];
|
|
let percentgeBandwidth = percentgeBandwidthMap.get(candle.begin);
|
|
let band = bollingerBandsMap.get(candle.begin);
|
|
|
|
if (percentgeBandwidth === undefined) { continue;} // percentageBandwidth might start after the first few candles.
|
|
if (candle.begin < initialDate.valueOf()) { continue; }
|
|
|
|
periods++;
|
|
|
|
let band1 = bollingerBandsMap.get(candles[i - 1].begin);
|
|
let band2 = bollingerBandsMap.get(candles[i - 2].begin);
|
|
let band3 = bollingerBandsMap.get(candles[i - 3].begin);
|
|
|
|
let percentgeBandwidth1 = percentgeBandwidthMap.get(candles[i - 1].begin);
|
|
let percentgeBandwidth2 = percentgeBandwidthMap.get(candles[i - 2].begin);
|
|
let percentgeBandwidth3 = percentgeBandwidthMap.get(candles[i - 3].begin);
|
|
|
|
if (strategy === 0) {
|
|
|
|
/*
|
|
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 need a new indicator called Bollinger Channels,
|
|
which I dont have right now, so for the time being, I will choose the strategy
|
|
manually.
|
|
*/
|
|
|
|
strategy = 1;
|
|
}
|
|
|
|
if (strategy === 1) {
|
|
|
|
/* Strategy #1: Trend Following. */
|
|
|
|
/*
|
|
Strategy Summary:
|
|
|
|
Once Percentage Bandwidth (PB) moving average is going down and
|
|
it is abobe 70%, we enter into Pre-Sell mode, which means that we are ready to
|
|
sell as soon as the trend starts going down. The trend should be measured by the
|
|
Bollinger Bands moving average.
|
|
|
|
Once the band's moving average starts going down we Sell and we set the stop loss,
|
|
which initially is static or going down if the prices starts going up too.
|
|
|
|
Once the candles minimun goes out of the lower Bollinger band, we enter into stop loss
|
|
trailing mode, which means that the stop loss will follow the band's moving average
|
|
from there on, getting closer or farther from it depending on the slope of the moving average
|
|
itself.
|
|
|
|
*/
|
|
|
|
if (
|
|
percentgeBandwidth.value >= 70 &&
|
|
lastOperation === 'Buy' &&
|
|
(percentgeBandwidth1.movingAverage > percentgeBandwidth.movingAverage) &&
|
|
presellModeIsActive === false
|
|
) {
|
|
signal = '"Pre-Sell"';
|
|
presellModeIsActive = true;
|
|
strategyPhase = 1;
|
|
|
|
};
|
|
|
|
let sellSignalActivated = false;
|
|
|
|
/* Sell Condition #1 */
|
|
|
|
if (
|
|
presellModeIsActive === true &&
|
|
band2.movingAverage > band1.movingAverage &&
|
|
band1.movingAverage > band.movingAverage &&
|
|
lastOperation === 'Buy'
|
|
) {
|
|
|
|
type = '"Sell-1"';
|
|
sellSignalActivated = true;
|
|
strategyPhase = 2;
|
|
}
|
|
|
|
/* Sell Condition #2 */
|
|
|
|
if (
|
|
candles[i - 3].min < band3.movingAverage - band3.deviation &&
|
|
candles[i - 2].min < band2.movingAverage - band2.deviation &&
|
|
candles[i - 1].min < band1.movingAverage - band1.deviation &&
|
|
candle.min < band.movingAverage - band.deviation &&
|
|
lastOperation === 'Buy'
|
|
) {
|
|
|
|
type = '"Sell-2"';
|
|
sellSignalActivated = true;
|
|
strategyPhase = 2;
|
|
}
|
|
|
|
if (sellSignalActivated === true) {
|
|
|
|
previousBalanceAssetA = balanceAssetA;
|
|
lastProfit = 0;
|
|
lastProfitPercent = 0;
|
|
|
|
balanceAssetB = balanceAssetA * candle.close;
|
|
balanceAssetA = 0;
|
|
|
|
rate = candle.close;
|
|
sellRate = rate;
|
|
|
|
stopLoss = sellRate + sellRate * stopLossPercentage / 100;
|
|
|
|
lastOperation = 'Sell';
|
|
presellModeIsActive = false;
|
|
stopLossDecay = 0;
|
|
|
|
addRecord();
|
|
|
|
sellSignalActivated = false;
|
|
continue;
|
|
}
|
|
|
|
/* Start Trailing Stop Condition */
|
|
|
|
if (
|
|
candle.max < band.movingAverage &&
|
|
lastOperation === 'Sell' &&
|
|
band1.movingAverage > band.movingAverage &&
|
|
candle.min < band.movingAverage - band.deviation
|
|
) {
|
|
|
|
strategyPhase = 3;
|
|
trailingStop = true;
|
|
|
|
}
|
|
|
|
/* Stop Loss Management: Initially it is tied to the sell rate but when it enters
|
|
into trailing mode it follows the moving average of the band.*/
|
|
|
|
let newStopLoss;
|
|
|
|
if (trailingStop === true) {
|
|
newStopLoss = band.movingAverage + band.movingAverage * (stopLossPercentage - stopLossDecay) / 100;
|
|
} else {
|
|
newStopLoss = sellRate + sellRate * (stopLossPercentage - stopLossDecay) / 100;
|
|
}
|
|
/* A trailing stop loss can never go higher. */
|
|
|
|
if (newStopLoss < previousStopLoss) {
|
|
stopLoss = newStopLoss;
|
|
} else {
|
|
stopLoss = previousStopLoss;
|
|
}
|
|
|
|
}
|
|
|
|
if (strategy === 2) {
|
|
|
|
/* Strategy #2: Range Trading. */
|
|
|
|
/*
|
|
Strategy Summary:
|
|
|
|
Once the candles maximun gets above the Bollinger Upper Band we wait until the B% moving average points
|
|
downward and then we Sell.
|
|
|
|
We keep our buy order just below the the Bollinger Bands moving average.
|
|
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Common Logic */
|
|
|
|
/* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */
|
|
|
|
let buySignalActivated = false;
|
|
|
|
if (candle.max >= stopLoss && lastOperation === 'Sell') {
|
|
|
|
balanceAssetA = balanceAssetB / stopLoss;
|
|
balanceAssetB = 0;
|
|
rate = stopLoss;
|
|
buySignalActivated = true;
|
|
}
|
|
|
|
if (buySignalActivated === true) {
|
|
|
|
stopLoss = 0;
|
|
sellRate = 0;
|
|
trailingStop = false;
|
|
|
|
roundtrips++;
|
|
lastProfit = balanceAssetA - previousBalanceAssetA;
|
|
lastProfitPercent = lastProfit / previousBalanceAssetA * 100;
|
|
profit = profit + lastProfit;
|
|
ROI = (initialBalanceA + profit) / initialBalanceA - 1;
|
|
|
|
if (lastProfit > 0) {
|
|
hits++;
|
|
} else {
|
|
fails++;
|
|
}
|
|
hitRatio = hits / roundtrips;
|
|
|
|
let miliSecondsPerDay = 24 * 60 * 60 * 1000;
|
|
days = periods * outputPeriod / miliSecondsPerDay;
|
|
anualizedRateOfReturn = ROI / days * 365;
|
|
|
|
type = '"Buy"';
|
|
lastOperation = 'Buy';
|
|
strategy = 0;
|
|
|
|
addRecord();
|
|
|
|
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,
|
|
signal: signal,
|
|
strategy: strategy,
|
|
strategyPhase: strategyPhase
|
|
}
|
|
|
|
recordsArray.push(record);
|
|
|
|
previousStopLoss = stopLoss;
|
|
|
|
if (bollingerBandsMap.get(candles[i - 1].begin).movingAverage < band.movingAverage) {
|
|
stopLossDecay = stopLossDecay + stopLossDecayIncrement * 2;
|
|
} else {
|
|
stopLossDecay = stopLossDecay - stopLossDecayIncrement / 2;
|
|
}
|
|
|
|
type = '""';
|
|
signal = '""';
|
|
|
|
}
|
|
}
|
|
|
|
writeRecordsFile();
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> buildRecords -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function writeRecordsFile() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> writeRecordsFile -> Entering function."); }
|
|
|
|
let separator = "";
|
|
let fileRecordCounter = 0;
|
|
|
|
let fileContent = "";
|
|
|
|
for (i = 0; i < recordsArray.length; i++) {
|
|
|
|
let record = recordsArray[i];
|
|
|
|
fileContent = fileContent + separator + '[' +
|
|
record.begin + "," +
|
|
record.end + "," +
|
|
record.type + "," +
|
|
record.rate + "," +
|
|
record.amount + "," +
|
|
record.balanceA + "," +
|
|
record.balanceB + "," +
|
|
record.profit + "," +
|
|
record.lastProfit + "," +
|
|
record.stopLoss + "," +
|
|
record.roundtrips + "," +
|
|
record.hits + "," +
|
|
record.fails + "," +
|
|
record.hitRatio + "," +
|
|
record.ROI + "," +
|
|
record.periods + "," +
|
|
record.days + "," +
|
|
record.anualizedRateOfReturn + "," +
|
|
record.sellRate + "," +
|
|
record.lastProfitPercent + "," +
|
|
record.signal + "," +
|
|
record.strategy + "," +
|
|
record.strategyPhase + "]";
|
|
|
|
if (separator === "") { separator = ","; }
|
|
|
|
fileRecordCounter++;
|
|
|
|
}
|
|
|
|
fileContent = "[" + fileContent + "]";
|
|
|
|
let fileName = '' + market.assetA + '_' + market.assetB + '.json';
|
|
|
|
let filePathRoot = bot.devTeam + "/" + bot.codeName + "." + bot.version.major + "." + bot.version.minor + "/" + global.PLATFORM_CONFIG.codeName + "." + global.PLATFORM_CONFIG.version.major + "." + global.PLATFORM_CONFIG.version.minor + "/" + global.EXCHANGE_NAME + "/" + bot.dataSetVersion;
|
|
let filePath = filePathRoot + "/Output/" + SIMULATED_RECORDS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timePeriod;
|
|
|
|
jasonStorage.createTextFile(filePath, fileName, fileContent + '\n', onFileCreated);
|
|
|
|
function onFileCreated(err) {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> writeRecordsFile -> onFileCreated -> Entering function."); }
|
|
if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> loopBody -> writeRecordsFile -> onFileCreated -> fileContent = " + fileContent); }
|
|
|
|
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
|
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> writeRecordsFile -> onFileCreated -> err = " + err.message);
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> writeRecordsFile -> onFileCreated -> filePath = " + filePath);
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> writeRecordsFile -> onFileCreated -> market = " + market.assetA + "_" + market.assetB);
|
|
|
|
callBackFunction(err);
|
|
return;
|
|
|
|
}
|
|
|
|
controlLoop();
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> writeRecordsFile -> onFileCreated -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> writeRecordsFile -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> loopBody -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function controlLoop() {
|
|
|
|
try {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildRecords -> controlLoop -> Entering function."); }
|
|
|
|
n++;
|
|
|
|
if (n < global.marketFilesPeriods.length) {
|
|
|
|
loopBody();
|
|
|
|
} else {
|
|
|
|
writeStatusReport(callBackFunction);
|
|
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> controlLoop -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> buildRecords -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
|
|
function writeStatusReport(callBack) {
|
|
|
|
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeStatusReport -> Entering function."); }
|
|
|
|
try {
|
|
|
|
let reportKey = "AAMasters" + "-" + "AAJason" + "-" + "Multi-Period-Market" + "-" + "dataSet.V1";
|
|
let thisReport = statusDependencies.statusReports.get(reportKey);
|
|
|
|
thisReport.file.lastExecution = bot.processDatetime;
|
|
thisReport.save(callBack);
|
|
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.message);
|
|
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
|
}
|
|
}
|
|
};
|