diff --git a/.vs/AATom/v15/.suo b/.vs/AATom/v15/.suo
index a0d6ba1de..4788f6009 100644
Binary files a/.vs/AATom/v15/.suo and b/.vs/AATom/v15/.suo differ
diff --git a/AATom.njsproj b/AATom.njsproj
index 330587faa..acbe4e998 100644
--- a/AATom.njsproj
+++ b/AATom.njsproj
@@ -28,7 +28,7 @@
-
+
@@ -41,7 +41,7 @@
-
+
diff --git a/All-Time-High/Interval.js b/All-Time-High/Interval.js
deleted file mode 100644
index 34db0f2c5..000000000
--- a/All-Time-High/Interval.js
+++ /dev/null
@@ -1,1017 +0,0 @@
-exports.newInterval = function newInterval(BOT, UTILITIES, AZURE_FILE_STORAGE, DEBUG_MODULE, MARKETS_MODULE, POLONIEX_CLIENT_MODULE) {
-
- let bot = BOT;
-
- const GMT_SECONDS = ':00.000 GMT+0000';
- const GMT_MILI_SECONDS = '.000 GMT+0000';
- const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000;
-
- const MODULE_NAME = "Interval";
- const LOG_INFO = true;
-
- const EXCHANGE_NAME = "Poloniex";
- const EXCHANGE_ID = 1;
-
- const TRADES_FOLDER_NAME = "Trades";
-
- const CANDLES_FOLDER_NAME = "Candles";
- const CANDLES_ONE_MIN = "One-Min";
-
- const VOLUMES_FOLDER_NAME = "Volumes";
- const VOLUMES_ONE_MIN = "One-Min";
-
- const GO_RANDOM = false;
- const FORCE_MARKET = 2; // This allows to debug the execution of an specific market. Not intended for production. *
-
- const logger = DEBUG_MODULE.newDebugLog();
- logger.fileName = MODULE_NAME;
- logger.bot = bot;
-
- interval = {
- initialize: initialize,
- start: start
- };
-
- let markets;
-
- let charlyAzureFileStorage = AZURE_FILE_STORAGE.newAzureFileStorage(bot);
- let bruceAzureFileStorage = AZURE_FILE_STORAGE.newAzureFileStorage(bot);
- let oliviaAzureFileStorage = AZURE_FILE_STORAGE.newAzureFileStorage(bot);
-
- let utilities = UTILITIES.newUtilities(bot);
-
- return interval;
-
- function initialize(yearAssigend, monthAssigned, callBackFunction) {
-
- try {
-
- /* IMPORTANT NOTE:
-
- We are ignoring in this Interval the received Year and Month. This interval is not depending on Year Month since it procecess the whole market at once.
-
- */
-
- logger.fileName = MODULE_NAME;
-
- const logText = "[INFO] initialize - Entering function 'initialize' ";
- console.log(logText);
- logger.write(logText);
-
- charlyAzureFileStorage.initialize("Charly");
- bruceAzureFileStorage.initialize("Bruce");
- oliviaAzureFileStorage.initialize("Olivia");
-
- markets = MARKETS_MODULE.newMarkets(bot);
- markets.initialize(callBackFunction);
-
-
- } catch (err) {
-
- const logText = "[ERROR] initialize - ' ERROR : " + err.message;
- console.log(logText);
- logger.write(logText);
-
- }
- }
-
-/*
-
-This process is going to do the following:
-
-Read the candles and volumes from Bruce and produce a single Index File for each market with daily candles and volumes.
-
-*/
-
- function start(callBackFunction) {
-
- try {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] Entering function 'start'");
- }
-
- let nextIntervalExecution = false; // This tell weather the Interval module will be executed again or not. By default it will not unless some hole have been found in the current execution.
- let nextIntervalLapse; // With this we can request the next execution wait time.
-
- let marketQueue; // This is the queue of all markets to be procesesd at each interval.
- let market = { // This is the current market being processed after removing it from the queue.
- id: 0,
- assetA: "",
- assetB: ""
- };
-
- let lastCandleFile; // Datetime of the last file included on the Index File.
- let firstTradeFile; // Datetime of the first trade file in the whole market history.
- let maxCandleFile; // Datetime of the last file available to be included in the Index File.
-
- let periods =
- '[' +
- '[' + 24 * 60 * 60 * 1000 + ',' + '"24-hs"' + ']' + ',' +
- '[' + 12 * 60 * 60 * 1000 + ',' + '"12-hs"' + ']' + ',' +
- '[' + 8 * 60 * 60 * 1000 + ',' + '"08-hs"' + ']' + ',' +
- '[' + 6 * 60 * 60 * 1000 + ',' + '"06-hs"' + ']' + ',' +
- '[' + 4 * 60 * 60 * 1000 + ',' + '"04-hs"' + ']' + ',' +
- '[' + 3 * 60 * 60 * 1000 + ',' + '"03-hs"' + ']' + ',' +
- '[' + 2 * 60 * 60 * 1000 + ',' + '"02-hs"' + ']' + ',' +
- '[' + 1 * 60 * 60 * 1000 + ',' + '"01-hs"' + ']' + ']';
-
- const outputPeriods = JSON.parse(periods);
-
- marketsLoop();
-
- /*
-
- At every run, the process needs to loop through all the markets at this exchange.
- The following functions marketsLoop(), openMarket(), closeMarket() and closeAndOpenMarket() controls the serialization of this processing.
-
- */
-
- function marketsLoop() {
-
- try {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] Entering function 'marketsLoop'");
- }
-
- markets.getMarketsByExchange(EXCHANGE_ID, onMarketsReady);
-
- function onMarketsReady(marketsArray) {
-
- marketQueue = JSON.parse(marketsArray);
-
- openMarket(); // First execution and entering into the real loop.
-
- }
- }
- catch (err) {
- const logText = "[ERROR] 'marketsLoop' - ERROR : " + err.message;
- logger.write(logText);
- }
- }
-
- function openMarket() {
-
- // To open a Market means picking a new market from the queue.
-
- try {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] Entering function 'openMarket'");
- }
-
-
- if (marketQueue.length === 0) {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] 'openMarket' - marketQueue.length === 0");
- }
-
- const logText = "[WARN] We processed all the markets.";
- logger.write(logText);
-
- callBackFunction(nextIntervalExecution, nextIntervalLapse);
-
- return;
- }
-
- if (GO_RANDOM === true) {
- const index = parseInt(Math.random() * (marketQueue.length - 1));
-
- market.id = marketQueue[index][0];
- market.assetA = marketQueue[index][1];
- market.assetB = marketQueue[index][2];
- market.status = marketQueue[index][3];
-
- marketQueue.splice(index, 1);
- } else {
- let marketRecord = marketQueue.shift();
-
- market.id = marketRecord[0];
- market.assetA = marketRecord[1];
- market.assetB = marketRecord[2];
- market.status = marketRecord[3];
-
- if (FORCE_MARKET > 0) {
- if (FORCE_MARKET !== market.id) {
- closeAndOpenMarket();
- return;
- }
- }
- }
-
- if (LOG_INFO === true) {
- logger.write("[INFO] 'openMarket' - marketQueue.length = " + marketQueue.length);
- logger.write("[INFO] 'openMarket' - market sucessfully opened : " + market.assetA + "_" + market.assetB);
- }
-
- if (market.status === markets.ENABLED) {
-
- getStatusReport();
-
- } else {
-
- logger.write("[INFO] 'openMarket' - market " + market.assetA + "_" + market.assetB + " skipped because its status is not valid. Status = " + market.status);
- closeAndOpenMarket();
- return;
-
- }
-
-
- }
- catch (err) {
- const logText = "[ERROR] 'openMarket' - ERROR : " + err.message;
- logger.write(logText);
- closeMarket();
- }
- }
-
- function closeMarket() {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] Entering function 'closeMarket'");
- }
-
- }
-
- function closeAndOpenMarket() {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] Entering function 'closeAndOpenMarket'");
- }
-
- openMarket();
- }
-
- /*
-
- The following code executes for each market.
-
- */
-
- function getStatusReport() {
-
- try {
-
- let reportFilePath;
- let fileName = "Status.Report." + market.assetA + '_' + market.assetB + ".json"
-
- getHistoricTrades();
-
- function getHistoricTrades() {
-
- /*
-
- We need to know where is the begining of the market, since that will help us know where the Index Files should start.
-
- */
-
- reportFilePath = EXCHANGE_NAME + "/Processes/" + "Poloniex-Historic-Trades";
-
- charlyAzureFileStorage.getTextFile(reportFilePath, fileName, onStatusReportReceived, true);
-
- function onStatusReportReceived(text) {
-
- let statusReport;
-
- try {
-
- statusReport = JSON.parse(text);
-
- firstTradeFile = new Date(statusReport.lastFile.year + "-" + statusReport.lastFile.month + "-" + statusReport.lastFile.days + " " + statusReport.lastFile.hours + ":" + statusReport.lastFile.minutes + GMT_SECONDS);
-
- getOneMinDailyCandlesVolumes();
-
- } catch (err) {
-
- const logText = "[INFO] 'getStatusReport' - Failed to read main Historic Trades Status Report for market " + market.assetA + '_' + market.assetB + " . Skipping it. ";
- logger.write(logText);
-
- closeAndOpenMarket();
- }
- }
- }
-
- function getOneMinDailyCandlesVolumes() {
-
- /* We need to discover the maxCandle file, which is the last file with candles we can use as input. */
-
- let date = new Date();
- let currentYear = date.getUTCFullYear();
- let currentMonth = utilities.pad(date.getUTCMonth() + 1,2);
-
- reportFilePath = EXCHANGE_NAME + "/Processes/" + "One-Min-Daily-Candles-Volumes" + "/" + currentYear + "/" + currentMonth;
-
- bruceAzureFileStorage.getTextFile(reportFilePath, fileName, onStatusReportReceived, true);
-
- function onStatusReportReceived(text) {
-
- let statusReport;
-
- try {
-
- statusReport = JSON.parse(text);
-
- maxCandleFile = new Date(statusReport.lastFile.year + "-" + statusReport.lastFile.month + "-" + statusReport.lastFile.days + " " + "00:00" + GMT_SECONDS);
-
- getThisProcessReport();
-
- } catch (err) {
-
- /*
-
- It might happen that the file content is corrupt or it does not exist. In either case we will point our lastCandleFile
- to the last day of the previous month.
-
- */
-
- maxCandleFile = new Date(date.valueOf() - ONE_DAY_IN_MILISECONDS);
- getThisProcessReport();
-
- }
- }
- }
-
- function getThisProcessReport() {
-
- /* If the process run and was interrupted, there should be a status report that allows us to resume execution. */
-
- reportFilePath = EXCHANGE_NAME + "/Processes/" + bot.process;
-
- oliviaAzureFileStorage.getTextFile(reportFilePath, fileName, onStatusReportReceived, true);
-
- function onStatusReportReceived(text) {
-
- let statusReport;
-
- try {
-
- statusReport = JSON.parse(text);
-
- lastCandleFile = new Date(statusReport.lastFile.year + "-" + statusReport.lastFile.month + "-" + statusReport.lastFile.days + " " + "00:00" + GMT_SECONDS);
-
- /*
-
- Here we assume that the last day written might contain incomplete information. This actually happens every time the head of the market is reached.
- For that reason we go back one day, the partial information is discarded and added again with whatever new info is available.
-
- */
-
- lastCandleFile = new Date(lastCandleFile.valueOf() - ONE_DAY_IN_MILISECONDS);
-
- findPreviousContent();
-
- } catch (err) {
-
- /*
-
- It might happen that the file content is corrupt or it does not exist. In either case we will point our lastCandleFile
- to the begining of the market.
-
- */
-
- lastCandleFile = new Date(firstTradeFile.getUTCFullYear() + "-" + (firstTradeFile.getUTCMonth() + 1) + "-" + firstTradeFile.getUTCDate() + " " + "00:00" + GMT_SECONDS);
-
- lastCandleFile = new Date(lastCandleFile.valueOf() - ONE_DAY_IN_MILISECONDS); // Go back one day to start well.
-
- buildCandles();
-
- }
- }
- }
-
- }
- catch (err) {
- const logText = "[ERROR] 'getStatusReport' - ERROR : " + err.message;
- logger.write(logText);
- closeMarket();
- }
- }
-
- function findPreviousContent() {
-
- try {
-
- let n = 0 // loop Variable representing each possible period as defined at the periods array.
-
- let allPreviousCandles = [];
- let allPreviousVolumes = [];
-
-
- loopBody();
-
- function loopBody() {
-
- let folderName = outputPeriods[n][1];
-
- let previousCandles;
- let previousVolumes;
-
- getCandles();
-
- function getCandles() {
-
- let fileName = '' + market.assetA + '_' + market.assetB + '.json';
-
- let filePath = EXCHANGE_NAME + "/Output/" + CANDLES_FOLDER_NAME + "/" + bot.process + "/" + folderName;
-
- oliviaAzureFileStorage.getTextFile(filePath, fileName, onFileReceived, true);
-
- function onFileReceived(text) {
-
- let candlesFile;
-
- try {
-
- candlesFile = JSON.parse(text);
-
- previousCandles = candlesFile;
-
- getVolumes();
-
- } catch (err) {
-
- const logText = "[ERR] 'findPreviousContent' - Empty or corrupt candle file found at " + filePath + " for market " + market.assetA + '_' + market.assetB + " . Skipping this Market. ";
- logger.write(logText);
-
- closeAndOpenMarket();
- }
- }
- }
-
- function getVolumes() {
-
- let fileName = '' + market.assetA + '_' + market.assetB + '.json';
-
- let filePath = EXCHANGE_NAME + "/Output/" + VOLUMES_FOLDER_NAME + "/" + bot.process + "/" + folderName;
-
- oliviaAzureFileStorage.getTextFile(filePath, fileName, onFileReceived, true);
-
- function onFileReceived(text) {
-
- let volumesFile;
-
- try {
-
- volumesFile = JSON.parse(text);
-
- previousVolumes = volumesFile;
-
- allPreviousCandles.push(previousCandles);
- allPreviousVolumes.push(previousVolumes);
-
- controlLoop();
-
- } catch (err) {
-
- const logText = "[ERR] 'findPreviousContent' - Empty or corrupt volume file found at " + filePath + " for market " + market.assetA + '_' + market.assetB + " . Skipping this Market. ";
- logger.write(logText);
-
- closeAndOpenMarket();
- }
- }
- }
-
- }
-
- function controlLoop() {
-
- n++;
-
- if (n < outputPeriods.length) {
-
- loopBody();
-
- } else {
-
- buildCandles(allPreviousCandles, allPreviousVolumes);
-
- }
- }
- }
- catch (err) {
- const logText = "[ERROR] 'findPreviousContent' - ERROR : " + err.message;
- logger.write(logText);
- closeMarket();
- }
-
- }
-
- function buildCandles(allPreviousCandles, allPreviousVolumes) {
-
- try {
-
- /*
-
- Firstly we prepere the arrays that will accumulate all the information for each output file.
-
- */
-
- let outputCandles = [];
- let outputVolumes = [];
-
- for (n = 0; n < outputPeriods.length; n++) {
-
- const emptyArray1 = [];
- const emptyArray2 = [];
-
- outputCandles.push(emptyArray1);
- outputVolumes.push(emptyArray2);
-
- }
-
- advanceTime();
-
- function advanceTime() {
-
- lastCandleFile = new Date(lastCandleFile.valueOf() + ONE_DAY_IN_MILISECONDS);
-
- const logText = "[INFO] New processing time @ " + lastCandleFile.getUTCFullYear() + "/" + (lastCandleFile.getUTCMonth() + 1) + "/" + lastCandleFile.getUTCDate() + ".";
- console.log(logText);
- logger.write(logText);
-
- /* Validation that we are not going past the head of the market. */
-
- if (lastCandleFile.valueOf() > maxCandleFile.valueOf()) {
-
- nextIntervalExecution = true; // we request a new interval execution.
-
- const logText = "[INFO] 'buildCandles' - Head of the market found @ " + lastCandleFile.getUTCFullYear() + "/" + (lastCandleFile.getUTCMonth() + 1) + "/" + lastCandleFile.getUTCDate() + ".";
- logger.write(logText);
-
- closeAndOpenMarket();
-
- return;
-
- }
-
- periodsLoop();
-
- }
-
- function periodsLoop() {
-
- /*
-
- We will iterate through all posible periods.
-
- */
-
- let n = 0 // loop Variable representing each possible period as defined at the periods array.
-
- loopBody();
-
- function loopBody() {
-
- let previousCandles;
- let previousVolumes;
-
- if (allPreviousCandles !== undefined) {
-
- previousCandles = allPreviousCandles[n];
- previousVolumes = allPreviousVolumes[n];
-
- }
-
- const outputPeriod = outputPeriods[n][0];
- const folderName = outputPeriods[n][1];
-
- /* Lest see if we are adding previous candles. */
-
- if (previousCandles !== undefined) {
-
- for (let i = 0; i < previousCandles.length; i++) {
-
- let candle = {
- open: previousCandles[i][2],
- close: previousCandles[i][3],
- min: previousCandles[i][0],
- max: previousCandles[i][1],
- begin: previousCandles[i][4],
- end: previousCandles[i][5]
- };
-
- if (candle.end < lastCandleFile.valueOf()) {
-
- outputCandles[n].push(candle);
-
- } else {
-
- const logText = "[WARN] 'buildCandles' - Candle # " + i + " @ " + folderName + " discarded for closing past the current process time.";
- logger.write(logText);
-
- }
- }
-
- allPreviousCandles[n] = []; // erasing these so as not to duplicate them.
-
- }
-
- if (previousVolumes !== undefined) {
-
- for (let i = 0; i < previousVolumes.length; i++) {
-
- let volume = {
- begin: previousVolumes[i][2],
- end: previousVolumes[i][3],
- buy: previousVolumes[i][0],
- sell: previousVolumes[i][1]
- };
-
- if (volume.end < lastCandleFile.valueOf()) {
-
- outputVolumes[n].push(volume);
-
- } else {
-
- const logText = "[WARN] 'buildCandles' - Volume # " + i + " @ " + folderName + " discarded for closing past the current process time.";
- logger.write(logText);
-
- }
-
-
- }
-
- allPreviousVolumes[n] = []; // erasing these so as not to duplicate them.
-
- }
-
- nextCandleFile();
-
- function nextCandleFile() {
-
- let dateForPath = lastCandleFile.getUTCFullYear() + '/' + utilities.pad(lastCandleFile.getUTCMonth() + 1, 2) + '/' + utilities.pad(lastCandleFile.getUTCDate(), 2);
- let fileName = market.assetA + '_' + market.assetB + ".json"
- let filePath = EXCHANGE_NAME + "/Output/" + CANDLES_FOLDER_NAME + '/' + CANDLES_ONE_MIN + '/' + dateForPath;
-
- bruceAzureFileStorage.getTextFile(filePath, fileName, onFileReceived, true);
-
- function onFileReceived(text) {
-
- let candlesFile;
-
- try {
-
- candlesFile = JSON.parse(text);
-
- } catch (err) {
-
- const logText = "[ERR] 'buildCandles' - Empty or corrupt candle file found at " + filePath + " for market " + market.assetA + '_' + market.assetB + " . Skipping this Market. ";
- logger.write(logText);
-
- closeAndOpenMarket();
-
- nextIntervalExecution = true; // we request a new interval execution.
- nextIntervalLapse = 30000;
-
- return;
- }
-
- const inputCandlesPerdiod = 60 * 1000; // 1 min
- const inputFilePeriod = 24 * 60 * 60 * 1000; // 24 hs
-
- let totalOutputCandles = inputFilePeriod / outputPeriod; // this should be 2 in this case.
- let beginingOutputTime = lastCandleFile.valueOf();
-
- for (let i = 0; i < totalOutputCandles; i++) {
-
- let outputCandle = {
- open: 0,
- close: 0,
- min: 0,
- max: 0,
- begin: 0,
- end: 0
- };
-
- let saveCandle = false;
-
- outputCandle.begin = beginingOutputTime + i * outputPeriod;
- outputCandle.end = beginingOutputTime + (i + 1) * outputPeriod - 1;
-
- for (let j = 0; j < candlesFile.length; j++) {
-
- let candle = {
- open: candlesFile[j][2],
- close: candlesFile[j][3],
- min: candlesFile[j][0],
- max: candlesFile[j][1],
- begin: candlesFile[j][4],
- end: candlesFile[j][5]
- };
-
- /* Here we discard all the candles out of range. */
-
- if (candle.begin >= outputCandle.begin && candle.end <= outputCandle.end) {
-
- if (saveCandle === false) { // this will set the value only once.
-
- outputCandle.open = candle.open;
- outputCandle.min = candle.min;
- outputCandle.max = candle.max;
-
- }
-
- saveCandle = true;
-
- outputCandle.close = candle.close; // only the last one will be saved
-
- if (candle.min < outputCandle.min) {
-
- outputCandle.min = candle.min;
-
- }
-
- if (candle.max > outputCandle.max) {
-
- outputCandle.max = candle.max;
-
- }
- }
- }
-
- if (saveCandle === true) { // then we have a valid candle, otherwise it means there were no candles to fill this one in its time range.
-
- outputCandles[n].push(outputCandle);
-
- }
- }
-
- nextVolumeFile();
-
- }
- }
-
- function nextVolumeFile() {
-
- let dateForPath = lastCandleFile.getUTCFullYear() + '/' + utilities.pad(lastCandleFile.getUTCMonth() + 1, 2) + '/' + utilities.pad(lastCandleFile.getUTCDate(), 2);
- let fileName = market.assetA + '_' + market.assetB + ".json"
- let filePath = EXCHANGE_NAME + "/Output/" + VOLUMES_FOLDER_NAME + '/' + VOLUMES_ONE_MIN + '/' + dateForPath;
-
- bruceAzureFileStorage.getTextFile(filePath, fileName, onFileReceived, true);
-
- function onFileReceived(text) {
-
- let volumesFile;
-
- try {
-
- volumesFile = JSON.parse(text);
-
- } catch (err) {
-
- const logText = "[ERR] 'buildCandles' - Empty or corrupt candle file found at " + filePath + " for market " + market.assetA + '_' + market.assetB + " . Skipping this Market. ";
- logger.write(logText);
-
- closeAndOpenMarket();
-
- return;
- }
-
- const inputVolumesPerdiod = 60 * 1000; // 1 min
- const inputFilePeriod = 24 * 60 * 60 * 1000; // 24 hs
-
- let totalOutputVolumes = inputFilePeriod / outputPeriod; // this should be 2 in this case.
- let beginingOutputTime = lastCandleFile.valueOf();
-
- for (let i = 0; i < totalOutputVolumes; i++) {
-
- let outputVolume = {
- buy: 0,
- sell: 0,
- begin: 0,
- end: 0
- };
-
- let saveVolume = false;
-
- outputVolume.begin = beginingOutputTime + i * outputPeriod;
- outputVolume.end = beginingOutputTime + (i + 1) * outputPeriod - 1;
-
- for (let j = 0; j < volumesFile.length; j++) {
-
- let volume = {
- buy: volumesFile[j][0],
- sell: volumesFile[j][1],
- begin: volumesFile[j][2],
- end: volumesFile[j][3]
- };
-
- /* Here we discard all the Volumes out of range. */
-
- if (volume.begin >= outputVolume.begin && volume.end <= outputVolume.end) {
-
- saveVolume = true;
-
- outputVolume.buy = outputVolume.buy + volume.buy;
- outputVolume.sell = outputVolume.sell + volume.sell;
-
- }
- }
-
- if (saveVolume === true) {
-
- outputVolumes[n].push(outputVolume);
-
- }
- }
-
- writeFiles(outputCandles[n], outputVolumes[n], folderName, controlLoop);
-
- }
- }
- }
-
- function controlLoop() {
-
- n++;
-
- if (n < outputPeriods.length) {
-
- loopBody();
-
- } else {
-
- writeStatusReport(lastCandleFile, advanceTime);
-
- }
- }
- }
- }
-
- catch (err) {
- const logText = "[ERROR] 'buildCandles' - ERROR : " + err.message;
- logger.write(logText);
- closeMarket();
- }
-
- }
-
- function writeFiles(candles, volumes, folderName, callBack) {
-
- /*
-
- Here we will write the contents of the Candles and Volumens files.
-
- */
-
- try {
-
- writeCandles();
-
- function writeCandles() {
-
- let separator = "";
- let fileRecordCounter = 0;
-
- let fileContent = "";
-
- for (i = 0; i < candles.length; i++) {
-
- let candle = candles[i];
-
- fileContent = fileContent + separator + '[' + candles[i].min + "," + candles[i].max + "," + candles[i].open + "," + candles[i].close + "," + candles[i].begin + "," + candles[i].end + "]";
-
- if (separator === "") { separator = ","; }
-
- fileRecordCounter++;
-
- }
-
- fileContent = "[" + fileContent + "]";
-
- let fileName = '' + market.assetA + '_' + market.assetB + '.json';
-
- let filePath = EXCHANGE_NAME + "/Output/" + CANDLES_FOLDER_NAME + "/" + bot.process + "/" + folderName;
-
- utilities.createFolderIfNeeded(filePath, oliviaAzureFileStorage, onFolderCreated);
-
- function onFolderCreated() {
-
- oliviaAzureFileStorage.createTextFile(filePath, fileName, fileContent + '\n', onFileCreated);
-
- function onFileCreated() {
-
- const logText = "[WARN] Finished with File @ " + market.assetA + "_" + market.assetB + ", " + fileRecordCounter + " records inserted into " + filePath + "/" + fileName + "";
- console.log(logText);
- logger.write(logText);
-
- writeVolumes();
- }
- }
-
- }
-
- function writeVolumes() {
-
- let separator = "";
- let fileRecordCounter = 0;
-
- let fileContent = "";
-
- for (i = 0; i < volumes.length; i++) {
-
- let candle = volumes[i];
-
- fileContent = fileContent + separator + '[' + volumes[i].buy + "," + volumes[i].sell + "," + volumes[i].begin + "," + volumes[i].end + "]";
-
- if (separator === "") { separator = ","; }
-
- fileRecordCounter++;
-
- }
-
- fileContent = "[" + fileContent + "]";
-
- let fileName = '' + market.assetA + '_' + market.assetB + '.json';
-
- let filePath = EXCHANGE_NAME + "/Output/" + VOLUMES_FOLDER_NAME + "/" + bot.process + "/" + folderName;
-
- utilities.createFolderIfNeeded(filePath, oliviaAzureFileStorage, onFolderCreated);
-
- function onFolderCreated() {
-
- oliviaAzureFileStorage.createTextFile(filePath, fileName, fileContent + '\n', onFileCreated);
-
- function onFileCreated() {
-
- const logText = "[WARN] Finished with File @ " + market.assetA + "_" + market.assetB + ", " + fileRecordCounter + " records inserted into " + filePath + "/" + fileName + "";
- console.log(logText);
- logger.write(logText);
-
- callBack();
- }
- }
- }
- }
-
- catch (err) {
- const logText = "[ERROR] 'writeFiles' - ERROR : " + err.message;
- logger.write(logText);
- closeMarket();
- }
- }
-
- function writeStatusReport(lastFileDate, callBack) {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] Entering function 'writeStatusReport'");
- }
-
- try {
-
- let reportFilePath = EXCHANGE_NAME + "/Processes/" + bot.process;
-
- utilities.createFolderIfNeeded(reportFilePath, oliviaAzureFileStorage, onFolderCreated);
-
- function onFolderCreated() {
-
- try {
-
- let fileName = "Status.Report." + market.assetA + '_' + market.assetB + ".json";
-
- let report = {
- lastFile: {
- year: lastFileDate.getUTCFullYear(),
- month: (lastFileDate.getUTCMonth() + 1),
- days: lastFileDate.getUTCDate()
- }
- };
-
- let fileContent = JSON.stringify(report);
-
- oliviaAzureFileStorage.createTextFile(reportFilePath, fileName, fileContent + '\n', onFileCreated);
-
- function onFileCreated() {
-
- if (LOG_INFO === true) {
- logger.write("[INFO] 'writeStatusReport' - Content written: " + fileContent);
- }
-
- callBack();
- }
- }
- catch (err) {
- const logText = "[ERROR] 'writeStatusReport - onFolderCreated' - ERROR : " + err.message;
- logger.write(logText);
- closeMarket();
- }
- }
-
- }
- catch (err) {
- const logText = "[ERROR] 'writeStatusReport' - ERROR : " + err.message;
- logger.write(logText);
- closeMarket();
- }
-
- }
-
- }
- catch (err) {
- const logText = "[ERROR] 'Start' - ERROR : " + err.message;
- logger.write(logText);
- }
- }
-};
diff --git a/Candle-Stairs/Interval.js b/Candle-Stairs/Interval.js
new file mode 100644
index 000000000..d201b57dc
--- /dev/null
+++ b/Candle-Stairs/Interval.js
@@ -0,0 +1,1035 @@
+exports.newInterval = function newInterval(BOT, UTILITIES, AZURE_FILE_STORAGE, DEBUG_MODULE, MARKETS_MODULE, POLONIEX_CLIENT_MODULE) {
+
+ let bot = BOT;
+
+ const GMT_SECONDS = ':00.000 GMT+0000';
+ const GMT_MILI_SECONDS = '.000 GMT+0000';
+ const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000;
+
+ const MODULE_NAME = "Interval";
+ const LOG_INFO = true;
+
+ const EXCHANGE_NAME = "Poloniex";
+ const EXCHANGE_ID = 1;
+
+ const TRADES_FOLDER_NAME = "Trades";
+
+ const CANDLES_FOLDER_NAME = "Candles";
+ const CANDLES_ONE_MIN = "One-Min";
+
+ const VOLUMES_FOLDER_NAME = "Volumes";
+ const VOLUMES_ONE_MIN = "One-Min";
+
+ const GO_RANDOM = false;
+ const FORCE_MARKET = 2; // This allows to debug the execution of an specific market. Not intended for production. *
+
+ const logger = DEBUG_MODULE.newDebugLog();
+ logger.fileName = MODULE_NAME;
+ logger.bot = bot;
+
+ interval = {
+ initialize: initialize,
+ start: start
+ };
+
+ let markets;
+
+ let charlyAzureFileStorage = AZURE_FILE_STORAGE.newAzureFileStorage(bot);
+ let bruceAzureFileStorage = AZURE_FILE_STORAGE.newAzureFileStorage(bot);
+ let oliviaAzureFileStorage = AZURE_FILE_STORAGE.newAzureFileStorage(bot);
+
+ let utilities = UTILITIES.newUtilities(bot);
+
+ return interval;
+
+ function initialize(yearAssigend, monthAssigned, callBackFunction) {
+
+ try {
+
+ /* IMPORTANT NOTE:
+
+ We are ignoring in this Interval the received Year and Month. This interval is not depending on Year Month since it procecess the whole market at once.
+
+ */
+
+ logger.fileName = MODULE_NAME;
+
+ const logText = "[INFO] initialize - Entering function 'initialize' ";
+ console.log(logText);
+ logger.write(logText);
+
+ charlyAzureFileStorage.initialize("Charly");
+ bruceAzureFileStorage.initialize("Bruce");
+ oliviaAzureFileStorage.initialize("Olivia");
+
+ markets = MARKETS_MODULE.newMarkets(bot);
+ markets.initialize(callBackFunction);
+
+
+ } catch (err) {
+
+ const logText = "[ERROR] initialize - ' ERROR : " + err.message;
+ console.log(logText);
+ logger.write(logText);
+
+ }
+ }
+
+/*
+
+This process is going to do the following:
+
+Read the candles and volumes from Bruce and produce a single Index File for each market with daily candles and volumes.
+
+*/
+
+ function start(callBackFunction) {
+
+ try {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] Entering function 'start'");
+ }
+
+ let nextIntervalExecution = false; // This tell weather the Interval module will be executed again or not. By default it will not unless some hole have been found in the current execution.
+ let nextIntervalLapse; // With this we can request the next execution wait time.
+
+ let marketQueue; // This is the queue of all markets to be procesesd at each interval.
+ let market = { // This is the current market being processed after removing it from the queue.
+ id: 0,
+ assetA: "",
+ assetB: ""
+ };
+
+ let lastCandleFile; // Datetime of the last file included on the Index File.
+ let firstTradeFile; // Datetime of the first trade file in the whole market history.
+ let maxCandleFile; // Datetime of the last file available to be included in the Index File.
+
+ let periods =
+ '[' +
+ '[' + 24 * 60 * 60 * 1000 + ',' + '"24-hs"' + ']' + ',' +
+ '[' + 12 * 60 * 60 * 1000 + ',' + '"12-hs"' + ']' + ',' +
+ '[' + 8 * 60 * 60 * 1000 + ',' + '"08-hs"' + ']' + ',' +
+ '[' + 6 * 60 * 60 * 1000 + ',' + '"06-hs"' + ']' + ',' +
+ '[' + 4 * 60 * 60 * 1000 + ',' + '"04-hs"' + ']' + ',' +
+ '[' + 3 * 60 * 60 * 1000 + ',' + '"03-hs"' + ']' + ',' +
+ '[' + 2 * 60 * 60 * 1000 + ',' + '"02-hs"' + ']' + ',' +
+ '[' + 1 * 60 * 60 * 1000 + ',' + '"01-hs"' + ']' + ']';
+
+ const outputPeriods = JSON.parse(periods);
+
+ marketsLoop();
+
+ /*
+
+ At every run, the process needs to loop through all the markets at this exchange.
+ The following functions marketsLoop(), openMarket(), closeMarket() and closeAndOpenMarket() controls the serialization of this processing.
+
+ */
+
+ function marketsLoop() {
+
+ try {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] Entering function 'marketsLoop'");
+ }
+
+ markets.getMarketsByExchange(EXCHANGE_ID, onMarketsReady);
+
+ function onMarketsReady(marketsArray) {
+
+ marketQueue = JSON.parse(marketsArray);
+
+ openMarket(); // First execution and entering into the real loop.
+
+ }
+ }
+ catch (err) {
+ const logText = "[ERROR] 'marketsLoop' - ERROR : " + err.message;
+ logger.write(logText);
+ }
+ }
+
+ function openMarket() {
+
+ // To open a Market means picking a new market from the queue.
+
+ try {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] Entering function 'openMarket'");
+ }
+
+
+ if (marketQueue.length === 0) {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] 'openMarket' - marketQueue.length === 0");
+ }
+
+ const logText = "[WARN] We processed all the markets.";
+ logger.write(logText);
+
+ callBackFunction(nextIntervalExecution, nextIntervalLapse);
+
+ return;
+ }
+
+ if (GO_RANDOM === true) {
+ const index = parseInt(Math.random() * (marketQueue.length - 1));
+
+ market.id = marketQueue[index][0];
+ market.assetA = marketQueue[index][1];
+ market.assetB = marketQueue[index][2];
+ market.status = marketQueue[index][3];
+
+ marketQueue.splice(index, 1);
+ } else {
+ let marketRecord = marketQueue.shift();
+
+ market.id = marketRecord[0];
+ market.assetA = marketRecord[1];
+ market.assetB = marketRecord[2];
+ market.status = marketRecord[3];
+
+ if (FORCE_MARKET > 0) {
+ if (FORCE_MARKET !== market.id) {
+ closeAndOpenMarket();
+ return;
+ }
+ }
+ }
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] 'openMarket' - marketQueue.length = " + marketQueue.length);
+ logger.write("[INFO] 'openMarket' - market sucessfully opened : " + market.assetA + "_" + market.assetB);
+ }
+
+ if (market.status === markets.ENABLED) {
+
+ getStatusReport();
+
+ } else {
+
+ logger.write("[INFO] 'openMarket' - market " + market.assetA + "_" + market.assetB + " skipped because its status is not valid. Status = " + market.status);
+ closeAndOpenMarket();
+ return;
+
+ }
+
+
+ }
+ catch (err) {
+ const logText = "[ERROR] 'openMarket' - ERROR : " + err.message;
+ logger.write(logText);
+ closeMarket();
+ }
+ }
+
+ function closeMarket() {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] Entering function 'closeMarket'");
+ }
+
+ }
+
+ function closeAndOpenMarket() {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] Entering function 'closeAndOpenMarket'");
+ }
+
+ openMarket();
+ }
+
+ /*
+
+ The following code executes for each market.
+
+ */
+
+ function getStatusReport() {
+
+ try {
+
+ let reportFilePath;
+ let fileName = "Status.Report." + market.assetA + '_' + market.assetB + ".json"
+
+ getHistoricTrades();
+
+ function getHistoricTrades() {
+
+ /*
+
+ We need to know where is the begining of the market, since that will help us know where the Index Files should start.
+
+ */
+
+ reportFilePath = EXCHANGE_NAME + "/Processes/" + "Poloniex-Historic-Trades";
+
+ charlyAzureFileStorage.getTextFile(reportFilePath, fileName, onStatusReportReceived, true);
+
+ function onStatusReportReceived(text) {
+
+ let statusReport;
+
+ try {
+
+ statusReport = JSON.parse(text);
+
+ firstTradeFile = new Date(statusReport.lastFile.year + "-" + statusReport.lastFile.month + "-" + statusReport.lastFile.days + " " + statusReport.lastFile.hours + ":" + statusReport.lastFile.minutes + GMT_SECONDS);
+
+ getOneMinDailyCandlesVolumes();
+
+ } catch (err) {
+
+ const logText = "[INFO] 'getStatusReport' - Failed to read main Historic Trades Status Report for market " + market.assetA + '_' + market.assetB + " . Skipping it. ";
+ logger.write(logText);
+
+ closeAndOpenMarket();
+ }
+ }
+ }
+
+ function getOneMinDailyCandlesVolumes() {
+
+ /* We need to discover the maxCandle file, which is the last file with candles we can use as input. */
+
+ let date = new Date();
+ let currentYear = date.getUTCFullYear();
+ let currentMonth = utilities.pad(date.getUTCMonth() + 1,2);
+
+ reportFilePath = EXCHANGE_NAME + "/Processes/" + "One-Min-Daily-Candles-Volumes" + "/" + currentYear + "/" + currentMonth;
+
+ bruceAzureFileStorage.getTextFile(reportFilePath, fileName, onStatusReportReceived, true);
+
+ function onStatusReportReceived(text) {
+
+ let statusReport;
+
+ try {
+
+ statusReport = JSON.parse(text);
+
+ maxCandleFile = new Date(statusReport.lastFile.year + "-" + statusReport.lastFile.month + "-" + statusReport.lastFile.days + " " + "00:00" + GMT_SECONDS);
+
+ getThisProcessReport();
+
+ } catch (err) {
+
+ /*
+
+ It might happen that the file content is corrupt or it does not exist. In either case we will point our lastCandleFile
+ to the last day of the previous month.
+
+ */
+
+ maxCandleFile = new Date(date.valueOf() - ONE_DAY_IN_MILISECONDS);
+ getThisProcessReport();
+
+ }
+ }
+ }
+
+ function getThisProcessReport() {
+
+ /* If the process run and was interrupted, there should be a status report that allows us to resume execution. */
+
+ reportFilePath = EXCHANGE_NAME + "/Processes/" + bot.process;
+
+ oliviaAzureFileStorage.getTextFile(reportFilePath, fileName, onStatusReportReceived, true);
+
+ function onStatusReportReceived(text) {
+
+ let statusReport;
+
+ try {
+
+ statusReport = JSON.parse(text);
+
+ lastCandleFile = new Date(statusReport.lastFile.year + "-" + statusReport.lastFile.month + "-" + statusReport.lastFile.days + " " + "00:00" + GMT_SECONDS);
+
+ /*
+
+ Here we assume that the last day written might contain incomplete information. This actually happens every time the head of the market is reached.
+ For that reason we go back one day, the partial information is discarded and added again with whatever new info is available.
+
+ */
+
+ lastCandleFile = new Date(lastCandleFile.valueOf() - ONE_DAY_IN_MILISECONDS);
+
+ findPreviousContent();
+
+ } catch (err) {
+
+ /*
+
+ It might happen that the file content is corrupt or it does not exist. In either case we will point our lastCandleFile
+ to the begining of the market.
+
+ */
+
+ lastCandleFile = new Date(firstTradeFile.getUTCFullYear() + "-" + (firstTradeFile.getUTCMonth() + 1) + "-" + firstTradeFile.getUTCDate() + " " + "00:00" + GMT_SECONDS);
+
+ lastCandleFile = new Date(lastCandleFile.valueOf() - ONE_DAY_IN_MILISECONDS); // Go back one day to start well.
+
+ buildCandles();
+
+ }
+ }
+ }
+
+ }
+ catch (err) {
+ const logText = "[ERROR] 'getStatusReport' - ERROR : " + err.message;
+ logger.write(logText);
+ closeMarket();
+ }
+ }
+
+ function buildStairs() {
+
+ periodsLoop();
+
+ function periodsLoop() {
+
+ /*
+
+ We will iterate through all posible periods.
+
+ */
+
+ let n = 0 // loop Variable representing each possible period as defined at the periods array.
+
+ loopBody();
+
+
+ }
+
+ function loopBody() {
+
+ const outputPeriod = outputPeriods[n][0];
+ const folderName = outputPeriods[n][1];
+
+ nextCandleFile();
+
+ function nextCandleFile() {
+
+ let fileName = market.assetA + '_' + market.assetB + ".json"
+ let filePath = EXCHANGE_NAME + "/Output/" + CANDLES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + folderName;
+
+ oliviaFileStorage.getTextFile(filePath, fileName, onFileReceived, true);
+
+ function onFileReceived(text) {
+
+ let marketFile;
+
+ try {
+
+ marketFile = JSON.parse(text);
+
+ } catch (err) {
+
+ const logText = "[ERR] 'nextCandleFile' - Empty or corrupt candle file found at " + filePath + " for market " + market.assetA + '_' + market.assetB + " . Skipping this Market. ";
+ logger.write(logText);
+
+ closeAndOpenMarket();
+
+ nextIntervalExecution = true; // we request a new interval execution.
+ nextIntervalLapse = 30000;
+
+ return;
+ }
+
+ let candles = [];
+ let stairsArray = [];
+
+ buildCandles();
+
+ function buildCandles() {
+
+ 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);
+
+ }
+
+ findCandleStairs();
+
+ }
+
+ function findCandleStairs() {
+
+ /* Finding stairs */
+
+ let stairs;
+
+ for (let i = 0; i < candles.length - 1; i++) {
+
+ let currentCandle = candles[i];
+ let nextCandle = candles[i + 1];
+
+ if (currentCandle.direction === nextCandle.direction && currentCandle.direction !== 'side') {
+
+ if (stairs === undefined) {
+
+ stairs = {
+ open: undefined,
+ close: undefined,
+ min: 10000000000000,
+ max: 0,
+ begin: undefined,
+ end: undefined,
+ direction: undefined,
+ candleCount: 0,
+ firstMin: 0,
+ firstMax: 0,
+ lastMin: 0,
+ lastMax: 0
+ };
+
+ stairs.direction = currentCandle.direction;
+ stairs.candleCount = 2;
+
+ stairs.begin = currentCandle.begin;
+ stairs.end = nextCandle.end;
+
+ stairs.open = currentCandle.open;
+ stairs.close = nextCandle.close;
+
+ if (currentCandle.min < nextCandle.min) { stairs.min = currentCandle.min; } else { stairs.min = nextCandle.min; }
+ if (currentCandle.max > nextCandle.max) { stairs.max = currentCandle.max; } else { stairs.max = nextCandle.max; }
+
+ if (stairs.direction === 'up') {
+
+ stairs.firstMin = currentCandle.open;
+ stairs.firstMax = currentCandle.close;
+
+ stairs.lastMin = nextCandle.open;
+ stairs.lastMax = nextCandle.close;
+
+ } else {
+
+ stairs.firstMin = currentCandle.close;
+ stairs.firstMax = currentCandle.open;
+
+ stairs.lastMin = nextCandle.close;
+ stairs.lastMax = nextCandle.open;
+
+ }
+
+
+ } else {
+
+ stairs.candleCount++;
+ stairs.end = nextCandle.end;
+ stairs.close = nextCandle.close;
+
+ if (stairs.min < nextCandle.min) { stairs.min = currentCandle.min; }
+ if (stairs.max > nextCandle.max) { stairs.max = currentCandle.max; }
+
+ if (stairs.direction === 'up') {
+
+ stairs.lastMin = nextCandle.open;
+ stairs.lastMax = nextCandle.close;
+
+ } else {
+
+ stairs.lastMin = nextCandle.close;
+ stairs.lastMax = nextCandle.open;
+
+ }
+
+ }
+
+ } else {
+
+ if (stairs !== undefined) {
+ stairsArray.push(stairs);
+ stairs = undefined;
+ }
+ }
+ }
+
+ writeCandleStairsFile();
+ }
+
+ function writeCandleStairsFile() {
+
+ let separator = "";
+ let fileRecordCounter = 0;
+
+ let fileContent = "";
+
+ for (i = 0; i < stairsArray.length; i++) {
+
+ let stairs = stairsArray[i];
+
+ fileContent = fileContent + separator + '[' +
+ stairs.open + "," +
+ stairs.close + "," +
+ stairs.min + "," +
+ stairs.max + "," +
+ stairs.begin + "," +
+ stairs.end + "," +
+ stairs.direction + "," +
+ stairs.candleCount + "," +
+ stairs.firstMin + "," +
+ stairs.firstMax + "," +
+ stairs.lastMin + "," +
+ stairs.lastMax + "]";
+
+ if (separator === "") { separator = ","; }
+
+ fileRecordCounter++;
+
+ }
+
+ fileContent = "[" + fileContent + "]";
+
+ let fileName = '' + market.assetA + '_' + market.assetB + '.json';
+
+ let filePath = EXCHANGE_NAME + "/Output/" + CANDLES_FOLDER_NAME + "/" + bot.process + "/" + folderName;
+
+ utilities.createFolderIfNeeded(filePath, tomAzureFileStorage, onFolderCreated);
+
+ function onFolderCreated() {
+
+ tomAzureFileStorage.createTextFile(filePath, fileName, fileContent + '\n', onFileCreated);
+
+ function onFileCreated() {
+
+ const logText = "[WARN] Finished with File @ " + market.assetA + "_" + market.assetB + ", " + fileRecordCounter + " records inserted into " + filePath + "/" + fileName + "";
+ console.log(logText);
+ logger.write(logText);
+
+ nextVolumeFile();
+ }
+ }
+ }
+
+ }
+ }
+
+ function nextVolumeFile() {
+
+ let fileName = market.assetA + '_' + market.assetB + ".json"
+ let filePath = EXCHANGE_NAME + "/Output/" + VOLUMES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + folderName;
+
+ oliviaFileStorage.getTextFile(filePath, fileName, onFileReceived, true);
+
+ function onFileReceived(text) {
+
+ let marketFile;
+
+ try {
+
+ marketFile = JSON.parse(text);
+
+ } catch (err) {
+
+ const logText = "[ERR] 'nextVolumeFile' - Empty or corrupt candle file found at " + filePath + " for market " + market.assetA + '_' + market.assetB + " . Skipping this Market. ";
+ logger.write(logText);
+
+ closeAndOpenMarket();
+
+ nextIntervalExecution = true; // we request a new interval execution.
+ nextIntervalLapse = 30000;
+
+ return;
+ }
+
+ let volumes = [];
+ let stairsArray = [];
+
+ buildVolumes();
+
+ function buildVolumes() {
+
+ for (let i = 0; i < marketFile.length; i++) {
+
+ let volume = {
+ amountBuy: 0,
+ amountSell: 0,
+ begin: undefined,
+ end: undefined
+ };
+
+ volume.amountBuy = marketFile[i][0];
+ volume.amountSell = marketFile[i][1];
+
+ volume.begin = marketFile[i][2];
+ volume.end = marketFile[i][3];
+
+ volumes.push(candle);
+
+ }
+
+ findVolumesStairs();
+
+ }
+
+ function findVolumesStairs() {
+
+ /* Finding stairs */
+
+ let buyUpStairs;
+ let buyDownStairs;
+
+ let sellUpStairs;
+ let sellDownStairs;
+
+ for (let i = 0; i < volumes.length - 1; i++) {
+
+ let currentVolume = volumes[i];
+ let nextVolume = volumes[i + 1];
+
+
+ /* buy volume going up */
+
+ if (currentVolume.amountBuy < nextVolume.amountBuy) {
+
+ if (buyUpStairs === undefined) {
+
+ buyUpStairs = {
+ type: undefined,
+ begin: undefined,
+ end: undefined,
+ direction: undefined,
+ barsCount: 0,
+ firstAmount: 0,
+ lastAmount: 0
+ };
+
+ buyUpStairs.type = 'buy';
+ buyUpStairs.direction = 'up';
+ buyUpStairs.barsCount = 2;
+
+ buyUpStairs.begin = currentVolume.begin;
+ buyUpStairs.end = nextVolume.end;
+
+ buyUpStairs.firstAmount = currentVolume.amountBuy;
+ buyUpStairs.lastAmount = nextVolume.amountBuy;
+
+ } else {
+
+ buyUpStairs.barsCount++;
+ buyUpStairs.end = nextVolume.end;
+ buyUpStairs.lastAmount = nextVolume.amountBuy;
+
+ }
+
+ } else {
+
+ if (buyUpStairs !== undefined) {
+
+ if (buyUpStairs.barsCount > 2) {
+
+ stairsArray.push(buyUpStairs);
+ }
+
+ buyUpStairs = undefined;
+ }
+ }
+
+ /* buy volume going down */
+
+ if (currentVolume.amountBuy > nextVolume.amountBuy) {
+
+ if (buyDownStairs === undefined) {
+
+ buyDownStairs = {
+ type: undefined,
+ begin: undefined,
+ end: undefined,
+ direction: undefined,
+ barsCount: 0,
+ firstAmount: 0,
+ lastAmount: 0
+ };
+
+ buyDownStairs.type = 'buy';
+ buyDownStairs.direction = 'down';
+ buyDownStairs.barsCount = 2;
+
+ buyDownStairs.begin = currentVolume.begin;
+ buyDownStairs.end = nextVolume.end;
+
+ buyDownStairs.firstAmount = currentVolume.amountBuy;
+ buyDownStairs.lastAmount = nextVolume.amountBuy;
+
+ } else {
+
+ buyDownStairs.barsCount++;
+ buyDownStairs.end = nextVolume.end;
+ buyDownStairs.lastAmount = nextVolume.amountBuy;
+
+ }
+
+ } else {
+
+ if (buyDownStairs !== undefined) {
+
+ if (buyDownStairs.barsCount > 2) {
+
+ stairsArray.push(buyDownStairs);
+ }
+
+ buyDownStairs = undefined;
+ }
+ }
+
+ /* sell volume going up */
+
+ if (currentVolume.amountSell < nextVolume.amountSell) {
+
+ if (sellUpStairs === undefined) {
+
+ sellUpStairs = {
+ type: undefined,
+ begin: undefined,
+ end: undefined,
+ direction: undefined,
+ barsCount: 0,
+ firstAmount: 0,
+ lastAmount: 0
+ };
+
+ sellUpStairs.type = 'sell';
+ sellUpStairs.direction = 'up';
+ sellUpStairs.barsCount = 2;
+
+ sellUpStairs.begin = currentVolume.begin;
+ sellUpStairs.end = nextVolume.end;
+
+ sellUpStairs.firstAmount = currentVolume.amountSell;
+ sellUpStairs.lastAmount = nextVolume.amountSell;
+
+ } else {
+
+ sellUpStairs.barsCount++;
+ sellUpStairs.end = nextVolume.end;
+ sellUpStairs.lastAmount = nextVolume.amountSell;
+
+ }
+
+ } else {
+
+ if (sellUpStairs !== undefined) {
+
+ if (sellUpStairs.barsCount > 2) {
+
+ stairsArray.push(sellUpStairs);
+ }
+
+ sellUpStairs = undefined;
+ }
+ }
+
+ /* sell volume going down */
+
+ if (currentVolume.amountSell > nextVolume.amountSell) {
+
+ if (sellDownStairs === undefined) {
+
+ sellDownStairs = {
+ type: undefined,
+ begin: undefined,
+ end: undefined,
+ direction: undefined,
+ barsCount: 0,
+ firstAmount: 0,
+ lastAmount: 0
+ };
+
+ sellDownStairs.type = 'sell';
+ sellDownStairs.direction = 'down';
+ sellDownStairs.barsCount = 2;
+
+ sellDownStairs.begin = currentVolume.begin;
+ sellDownStairs.end = nextVolume.end;
+
+ sellDownStairs.firstAmount = currentVolume.amountSell;
+ sellDownStairs.lastAmount = nextVolume.amountSell;
+
+ } else {
+
+ sellDownStairs.barsCount++;
+ sellDownStairs.end = nextVolume.end;
+ sellDownStairs.lastAmount = nextVolume.amountSell;
+
+ }
+
+ } else {
+
+ if (sellDownStairs !== undefined) {
+
+ if (sellDownStairs.barsCount > 2) {
+
+ stairsArray.push(sellDownStairs);
+ }
+
+ sellDownStairs = undefined;
+ }
+ }
+ }
+
+ writeVolumeStairsFile();
+ }
+
+ function writeVolumeStairsFile() {
+
+ let separator = "";
+ let fileRecordCounter = 0;
+
+ let fileContent = "";
+
+ for (i = 0; i < stairsArray.length; i++) {
+
+ let stairs = stairsArray[i];
+
+ fileContent = fileContent + separator + '[' +
+ stairs.type + "," +
+ stairs.begin + "," +
+ stairs.end + "," +
+ stairs.direction + "," +
+ stairs.barsCount + "," +
+ stairs.firstAmount + "," +
+ stairs.lastAmount + "]";
+
+ if (separator === "") { separator = ","; }
+
+ fileRecordCounter++;
+
+ }
+
+ fileContent = "[" + fileContent + "]";
+
+ let fileName = '' + market.assetA + '_' + market.assetB + '.json';
+
+ let filePath = EXCHANGE_NAME + "/Output/" + VOLUMES_FOLDER_NAME + "/" + bot.process + "/" + folderName;
+
+ utilities.createFolderIfNeeded(filePath, tomAzureFileStorage, onFolderCreated);
+
+ function onFolderCreated() {
+
+ tomAzureFileStorage.createTextFile(filePath, fileName, fileContent + '\n', onFileCreated);
+
+ function onFileCreated() {
+
+ const logText = "[WARN] Finished with File @ " + market.assetA + "_" + market.assetB + ", " + fileRecordCounter + " records inserted into " + filePath + "/" + fileName + "";
+ console.log(logText);
+ logger.write(logText);
+
+ controlLoop();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ function controlLoop() {
+
+ n++;
+
+ if (n < outputPeriods.length) {
+
+ loopBody();
+
+ } else {
+
+ writeStatusReport(lastCandleFile, advanceTime);
+
+ }
+ }
+
+ }
+
+ function writeStatusReport(lastFileDate, callBack) {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] Entering function 'writeStatusReport'");
+ }
+
+ try {
+
+ let reportFilePath = EXCHANGE_NAME + "/Processes/" + bot.process;
+
+ utilities.createFolderIfNeeded(reportFilePath, oliviaAzureFileStorage, onFolderCreated);
+
+ function onFolderCreated() {
+
+ try {
+
+ let fileName = "Status.Report." + market.assetA + '_' + market.assetB + ".json";
+
+ let report = {
+ lastFile: {
+ year: lastFileDate.getUTCFullYear(),
+ month: (lastFileDate.getUTCMonth() + 1),
+ days: lastFileDate.getUTCDate()
+ }
+ };
+
+ let fileContent = JSON.stringify(report);
+
+ oliviaAzureFileStorage.createTextFile(reportFilePath, fileName, fileContent + '\n', onFileCreated);
+
+ function onFileCreated() {
+
+ if (LOG_INFO === true) {
+ logger.write("[INFO] 'writeStatusReport' - Content written: " + fileContent);
+ }
+
+ callBack();
+ }
+ }
+ catch (err) {
+ const logText = "[ERROR] 'writeStatusReport - onFolderCreated' - ERROR : " + err.message;
+ logger.write(logText);
+ closeMarket();
+ }
+ }
+
+ }
+ catch (err) {
+ const logText = "[ERROR] 'writeStatusReport' - ERROR : " + err.message;
+ logger.write(logText);
+ closeMarket();
+ }
+
+ }
+
+ }
+ catch (err) {
+ const logText = "[ERROR] 'Start' - ERROR : " + err.message;
+ logger.write(logText);
+ }
+ }
+};