diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..82c487bdd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ + +.vs/* +obj/* + +bin/* + +*.config +*.log +*.cache +node_modules/* + diff --git a/Json.json b/Json.json new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Json.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..10e295746 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Financial Beings License + +Collective Copyright by the Advanced Algos Community + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software including without limitation the rights to use, copy, modify, +merge, publish, distribute and sublicense copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following +conditions: + + 1. Using, copying, modifying, publishing, distributing and sublicensing rights + are restricted to the Advanced Algos Platform, meaning the rights granted can + only be exercised through the Advanced Algos Service. + + 2. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Multi-Period-Daily/User.Bot.js b/Multi-Period-Daily/User.Bot.js new file mode 100644 index 000000000..3d85b8526 --- /dev/null +++ b/Multi-Period-Daily/User.Bot.js @@ -0,0 +1,1647 @@ +exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, FILE_STORAGE) { + + const FULL_LOG = true; + const INTENSIVE_LOG = false; + const LOG_FILE_CONTENT = false; + + 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 = "User Bot"; + + const TRADES_FOLDER_NAME = "Trades"; + + const CANDLES_FOLDER_NAME = "Candles"; + const CANDLE_STAIRS_FOLDER_NAME = "Candle-Stairs"; + + const VOLUMES_FOLDER_NAME = "Volumes"; + const VOLUME_STAIRS_FOLDER_NAME = "Volume-Stairs"; + + thisObject = { + initialize: initialize, + start: start + }; + + let utilities = UTILITIES.newCloudUtilities(bot, logger); + let fileStorage = FILE_STORAGE.newFileStorage(logger); + + let statusDependencies; + let beginingOfMarket + + return thisObject; + + function initialize(pStatusDependencies, callBackFunction) { + + try { + + logger.fileName = MODULE_NAME; + logger.initialize(); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } + + statusDependencies = pStatusDependencies; + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + /* + + This process is going to do the following: + + Read the candles and volumes from Candles Volumes and produce for each market two files with candles stairs and volumes stairs respectively. + + */ + + function start(callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } + + /* One of the challenges of this process is that each imput file contains one day of candles. So if a stair spans more than one day + then we dont want to break the stais in two pieces. What we do is that we read to candles files at the time and record at the current + date all stairs of the day plus the ones thas spans to the second day without bigining at the second day. Then when we process the next + day, we must remember where the last stairs of each type endded, so as not to create overlapping stairs in the current day. */ + + let market = bot.market; + + /* Context Variables */ + + let contextVariables = { + lastCandleFile: undefined, // Datetime of the last file files sucessfully produced by this process. + firstTradeFile: undefined, // Datetime of the first trade file in the whole market history. + maxCandleFile: undefined // Datetime of the last file available to be used as an input of this process. + }; + + let previousDay; // Holds the date of the previous day relative to the processing date. + let processDate; // Holds the processing date. + + getContextVariables(); + + function getContextVariables() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Entering function."); } + + let thisReport; + let reportKey; + let statusReport; + + /* We look first for Exchange Raw Data in order to get when the market starts. */ + + reportKey = "Masters" + "-" + "Exchange-Raw-Data" + "-" + "Historic-OHLCVs" + "-" + "dataSet.V1"; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } + + statusReport = statusDependencies.statusReports.get(reportKey); + + if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + if (statusReport.status === "Status Report is corrupt.") { + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + thisReport = statusDependencies.statusReports.get(reportKey).file; + + if (thisReport.beginingOfMarket === undefined) { + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> reportKey = " + reportKey); + logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> It is too early too run this process since the trade history of the market is not there yet."); + + let customOK = { + result: global.CUSTOM_OK_RESPONSE.result, + message: "Dependency does not exist." + } + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message); + callBackFunction(customOK); + return; + } + + contextVariables.firstTradeFile = new Date(thisReport.beginingOfMarket.year + "-" + thisReport.beginingOfMarket.month + "-" + thisReport.beginingOfMarket.days + " " + thisReport.beginingOfMarket.hours + ":" + thisReport.beginingOfMarket.minutes + GMT_SECONDS); + + /* Second, we get the report from Candles Volumes, to know when the marted ends. */ + + reportKey = "Masters" + "-" + "Candles-Volumes" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } + + statusReport = statusDependencies.statusReports.get(reportKey); + + if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + if (statusReport.status === "Status Report is corrupt.") { + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + thisReport = statusDependencies.statusReports.get(reportKey).file; + + if (thisReport.lastFile === undefined) { + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> reportKey = " + reportKey); + + let customOK = { + result: global.CUSTOM_OK_RESPONSE.result, + message: "Dependency not ready." + } + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message); + callBackFunction(customOK); + return; + } + + contextVariables.maxCandleFile = new Date(thisReport.lastFile.valueOf()); + + /* Finally we get our own Status Report. */ + + reportKey = "Masters" + "-" + "Stairs-Patterns" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } + + statusReport = statusDependencies.statusReports.get(reportKey); + + if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month. + logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. "); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + + if (statusReport.status === "Status Report is corrupt.") { + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + thisReport = statusDependencies.statusReports.get(reportKey).file; + + if (thisReport.lastFile !== undefined) { + + beginingOfMarket = new Date(thisReport.beginingOfMarket); + + if (beginingOfMarket.valueOf() !== contextVariables.firstTradeFile.valueOf()) { // Reset Mechanism for Begining of the Market + logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Reset Mechanism for Begining of the Market Activated. -> reportKey = " + reportKey); + + beginingOfMarket = new Date(contextVariables.firstTradeFile.getUTCFullYear() + "-" + (contextVariables.firstTradeFile.getUTCMonth() + 1) + "-" + contextVariables.firstTradeFile.getUTCDate() + " " + "00:00" + GMT_SECONDS); + contextVariables.lastCandleFile = new Date(contextVariables.firstTradeFile.getUTCFullYear() + "-" + (contextVariables.firstTradeFile.getUTCMonth() + 1) + "-" + contextVariables.firstTradeFile.getUTCDate() + " " + "00:00" + GMT_SECONDS); + contextVariables.lastCandleFile = new Date(contextVariables.lastCandleFile.valueOf() + ONE_DAY_IN_MILISECONDS) + + buildStairs(); + return; + } + + contextVariables.lastCandleFile = new Date(thisReport.lastFile); + + /* + The stairs objects can span more than one day. In order not to cut these objects into two when this happens, this process will alsways read + 3 files. + + 1. The first file is a stairs file corresponding to processDay -2. From there we will know where the last staris ended. + 2. The second is a candle or volume file corresponding to processDay -1. + 3. The third is a candle of volume file at processDay. + + We will recalculate 2 and 3 considering the objects already in 1, so as to make the transition between 2 and 3 smooth. + */ + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> thisReport.lastFile !== undefined"); } + + buildStairs(); + return; + + } else { + + /* + In the case when there is no status report, we take the date of the file with the first trades as the begining of the market. Then we will + go one day fordward in time, so that when we enter the loop, the "previous day" does not fall outside the begining of the market. + */ + + contextVariables.lastCandleFile = new Date(contextVariables.firstTradeFile.getUTCFullYear() + "-" + (contextVariables.firstTradeFile.getUTCMonth() + 1) + "-" + contextVariables.firstTradeFile.getUTCDate() + " " + "00:00" + GMT_SECONDS); + contextVariables.lastCandleFile = new Date(contextVariables.lastCandleFile.valueOf() + ONE_DAY_IN_MILISECONDS) + + beginingOfMarket = new Date(contextVariables.firstTradeFile.getUTCFullYear() + "-" + (contextVariables.firstTradeFile.getUTCMonth() + 1) + "-" + contextVariables.firstTradeFile.getUTCDate() + " " + "00:00" + GMT_SECONDS); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> thisReport.lastFile === undefined"); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> contextVariables.lastCandleFile = " + contextVariables.lastCandleFile); } + + buildStairs(); + return; + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack); + if (err.message === "Cannot read property 'file' of undefined") { + logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. "); + logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys)); + } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildStairs() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> Entering function."); } + + let n; + processDate = new Date(contextVariables.lastCandleFile.valueOf() - ONE_DAY_IN_MILISECONDS); // Go back one day to start well when we advance time at the begining of the loop. + let fromDate = new Date(processDate.valueOf()) + let lastDate = new Date() + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> processDate = " + processDate); } + + advanceTime(); + + function advanceTime() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> advanceTime -> Entering function."); } + + processDate = new Date(processDate.valueOf() + ONE_DAY_IN_MILISECONDS); + previousDay = new Date(processDate.valueOf() - ONE_DAY_IN_MILISECONDS); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> advanceTime -> processDate = " + processDate); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> advanceTime -> previousDay = " + previousDay); } + + /* Validation that we are not going past the head of the market. */ + + if (processDate.valueOf() > contextVariables.maxCandleFile.valueOf()) { + + const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + "."; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> advanceTime -> " + logText); } + + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + + } + + /* Telling the world we are alive and doing well */ + let currentDateString = processDate.getUTCFullYear() + '-' + utilities.pad(processDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(processDate.getUTCDate(), 2); + let currentDate = new Date(processDate) + let percentage = global.getPercentage(fromDate, currentDate, lastDate) + bot.processHeartBeat(currentDateString, percentage) + + if (global.areEqualDates(currentDate, new Date()) === false) { + logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage); + } + + periodsLoop(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> advanceTime -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function periodsLoop() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> periodsLoop -> Entering function."); } + + /* + + We will iterate through all posible timeFrames. + + */ + + n = 0 // loop Variable representing each possible period as defined at the periods array. + + loopBody(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function loopBody() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> Entering function."); } + + const timeFrame = global.dailyFilePeriods[n][1]; + + let endOfLastCandleStair = new Date(previousDay.valueOf() - ONE_DAY_IN_MILISECONDS); + let endOfLastBuyVolumeStair = new Date(previousDay.valueOf() - ONE_DAY_IN_MILISECONDS); + let endOfLastSellVolumeStair = new Date(previousDay.valueOf() - ONE_DAY_IN_MILISECONDS); + + /* + By default we set the starting date of the processDay - 2 file. If we find the file this value shoulld be overwritten by the value of the end property + of the last object on the file. + */ + + endOfLastCandleStair = endOfLastCandleStair.valueOf(); + endOfLastBuyVolumeStair = endOfLastBuyVolumeStair.valueOf(); + endOfLastSellVolumeStair = endOfLastSellVolumeStair.valueOf(); + + getEndOfLastCandleStair(); + + function getEndOfLastCandleStair() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastCandleStair -> Entering function."); } + + let fileDate = new Date(previousDay.valueOf() - ONE_DAY_IN_MILISECONDS); + getCandleStairsFile(); + + function getCandleStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastCandleStair -> getCandleStairsFile -> Entering function."); } + + let dateForPath = fileDate.getUTCFullYear() + '/' + utilities.pad(fileDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(fileDate.getUTCDate(), 2); + let fileName = "Data.json" + let filePath = bot.filePathRoot + "/Output/" + CANDLE_STAIRS_FOLDER_NAME + '/' + "Multi-Period-Daily" + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileReceived); + + function onFileReceived(err, text) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastCandleStair -> getCandleStairsFile -> onFileReceived -> Entering function."); } + + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastCandleStair -> getCandleStairsFile -> onFileReceived -> text = " + text); } + + if ( + err.result === global.CUSTOM_FAIL_RESPONSE.result && + (err.message === 'Folder does not exist.' || err.message === 'File does not exist.') + ) { + + getEndOfLastVolumeStair(); + return; + } + + let stairsFile = JSON.parse(text); + + if (stairsFile.length > 0) { + + endOfLastCandleStair = stairsFile[stairsFile.length - 1][5]; // We get from the last regord the end value. Position 5 = Stairs.end + getEndOfLastVolumeStair(); + + } else { + + getEndOfLastVolumeStair(); + return; + + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastCandleStair -> getCandleStairsFile -> onFileReceived -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastCandleStair -> getCandleStairsFile -> onFileReceived -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastCandleStair -> getCandleStairsFile -> onFileReceived -> market = " + market.baseAsset + '_' + market.quotedAsset); + + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastCandleStair -> getCandleStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastCandleStair -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function getEndOfLastVolumeStair() { + + try { + + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastVolumeStair -> Entering function."); } + + let fileDate = new Date(previousDay.valueOf() - ONE_DAY_IN_MILISECONDS); + getCandleStairsFile(); + + function getCandleStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastVolumeStair -> getCandleStairsFile -> Entering function."); } + + let dateForPath = fileDate.getUTCFullYear() + '/' + utilities.pad(fileDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(fileDate.getUTCDate(), 2); + let fileName = "Data.json" + let filePath = bot.filePathRoot + "/Output/" + VOLUME_STAIRS_FOLDER_NAME + '/' + "Multi-Period-Daily" + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileReceived); + + function onFileReceived(err, text) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastVolumeStair -> getCandleStairsFile -> onFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> getEndOfLastVolumeStair -> getCandleStairsFile -> onFileReceived -> text = " + text); } + + if ( + err.result === global.CUSTOM_FAIL_RESPONSE.result && + (err.message === 'Folder does not exist.' || err.message === 'File does not exist.') + ) { + + processCandles(); + return; + } + + let stairsFile = JSON.parse(text); + + if (stairsFile.length > 0) { + + for (let i = 0; i < stairsFile.length; i++) { + + let stairs = { + type: stairsFile[i][0], + begin: stairsFile[i][1], + end: stairsFile[i][2], + direction: stairsFile[i][3], + barsCount: stairsFile[i][4], + firstAmount: stairsFile[i][5], + lastAmount: stairsFile[i][6] + }; + + if (stairs.type === 'buy') { + + endOfLastBuyVolumeStair = stairs.end; + + } else { + + endOfLastSellVolumeStair = stairs.end; + + } + } + + processCandles(); + + } else { + + processCandles(); + return; + + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastVolumeStair -> getCandleStairsFile -> onFileReceived -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastVolumeStair -> getCandleStairsFile -> onFileReceived -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastVolumeStair -> getCandleStairsFile -> onFileReceived -> market = " + market.baseAsset + '_' + market.quotedAsset); + + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastVolumeStair -> getCandleStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> getEndOfLastVolumeStair -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function processCandles() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> Entering function."); } + + let candles = []; // Here we will put all the candles of the 2 files read. + + let previousDayStairsArray = []; // Here All the stairs of the previous day. + let processDayStairsArray = []; // Here All the stairs of the process day. + + let previousDayFile; + let processDayFile; + + getCandleStairsFile(); + + function getCandleStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> getCandleStairsFile -> Entering function."); } + + let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2); + let fileName = "Data.json" + let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + bot.dataMine + "/" + "Candles-Volumes"; + let filePath = filePathRoot + "/Output/" + CANDLES_FOLDER_NAME + '/' + "Multi-Period-Daily" + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onCurrentDayFileReceived); + + function onCurrentDayFileReceived(err, text) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> getCandleStairsFile -> onCurrentDayFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> getCandleStairsFile -> onCurrentDayFileReceived -> text = " + text); } + + previousDayFile = JSON.parse(text); + getProcessDayFile() + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getCandleStairsFile -> onCurrentDayFileReceived -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getCandleStairsFile -> onCurrentDayFileReceived -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getCandleStairsFile -> onCurrentDayFileReceived -> market = " + market.baseAsset + '_' + market.quotedAsset); + + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getCandleStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function getProcessDayFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> getProcessDayFile -> Entering function."); } + + let dateForPath = processDate.getUTCFullYear() + '/' + utilities.pad(processDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(processDate.getUTCDate(), 2); + let fileName = "Data.json" + let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + bot.dataMine + "/" + "Candles-Volumes"; + let filePath = filePathRoot + "/Output/" + CANDLES_FOLDER_NAME + '/' + "Multi-Period-Daily" + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onCurrentDayFileReceived); + + function onCurrentDayFileReceived(err, text) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> getProcessDayFile -> onCurrentDayFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> getProcessDayFile -> onCurrentDayFileReceived -> text = " + text); } + + processDayFile = JSON.parse(text); + buildCandles(); + + } catch (err) { + + if (processDate.valueOf() > contextVariables.maxCandleFile.valueOf()) { + + processDayFile = []; // we are past the head of the market, then no worries if this file is non existent. + buildCandles(); + + } else { + + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getProcessDayFile -> onCurrentDayFileReceived -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getProcessDayFile -> onCurrentDayFileReceived -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getProcessDayFile -> onCurrentDayFileReceived -> market = " + market.baseAsset + '_' + market.quotedAsset); + + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> getProcessDayFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildCandles() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> buildCandles -> Entering function."); } + + pushCandles(previousDayFile); + pushCandles(processDayFile); + findCandleStairs(); + + function pushCandles(candlesFile) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> buildCandles -> pushCandles -> Entering function."); } + + for (let i = 0; i < candlesFile.length; i++) { + + let candle = { + open: undefined, + close: undefined, + min: 10000000000000, + max: 0, + begin: undefined, + end: undefined, + direction: undefined + }; + + candle.min = candlesFile[i][0]; + candle.max = candlesFile[i][1]; + + candle.open = candlesFile[i][2]; + candle.close = candlesFile[i][3]; + + candle.begin = candlesFile[i][4]; + candle.end = candlesFile[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); + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> buildCandles -> pushCandles -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> buildCandles -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + } + + function findCandleStairs() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> findCandleStairs -> Entering function."); } + + /* 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) { + + /* + Here we detect stairs that started at process day - 2. + */ + + if (stairs.begin > endOfLastCandleStair) { + + if (stairs.begin >= processDate.valueOf()) { + + processDayStairsArray.push(stairs); + + } else { + + previousDayStairsArray.push(stairs); + + } + } + + stairs = undefined; + } + } + } + + writeCandleStairsFile(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> findCandleStairs -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + } + + function writeCandleStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> Entering function."); } + + writeFile(previousDayStairsArray, previousDay, onPreviousFileWritten); + + function onPreviousFileWritten() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> onPreviousFileWritten -> Entering function."); } + + writeFile(processDayStairsArray, processDate, onProcessFileWritten); + + function onProcessFileWritten() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> onPreviousFileWritten -> onProcessFileWritten -> Entering function."); } + + processVolumes(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> writeCandleStairsFile -> onPreviousFileWritten -> onProcessFileWritten -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> writeCandleStairsFile -> onPreviousFileWritten -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeFile(pStairs, pDate, callback) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> writeFile -> Entering function."); } + + let separator = ""; + let fileRecordCounter = 0; + + let fileContent = ""; + + for (i = 0; i < pStairs.length; i++) { + + let stairs = pStairs[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 dateForPath = pDate.getUTCFullYear() + '/' + utilities.pad(pDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(pDate.getUTCDate(), 2); + let fileName = 'Data.json'; + + let filePath = bot.filePathRoot + "/Output/" + CANDLE_STAIRS_FOLDER_NAME + "/" + bot.process + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> writeFile -> onFileCreated -> Entering function."); } + + if (LOG_FILE_CONTENT === true) { + logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> writeFile -> onFileCreated -> Content written = " + fileContent); + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> writeFile -> onFileCreated -> err = " + err.stack); + callBack(err); + return; + } + + const logText = "[WARN] Finished with File @ " + market.baseAsset + "_" + market.quotedAsset + ", " + fileRecordCounter + " records inserted into " + filePath + "/" + fileName + ""; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processCandles -> writeCandleStairsFile -> writeFile -> onFileCreated -> " + logText); } + + callback(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> writeCandleStairsFile -> writeFile -> onFileCreated -> err = " + err.stack); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> writeCandleStairsFile -> writeFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> writeCandleStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processCandles -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function processVolumes() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> Entering function."); } + + let volumes = []; + + let previousDayStairsArray = []; // Here All the stairs of the previous day. + let processDayStairsArray = []; // Here All the stairs of the process day. + + let previousDayFile; + let processDayFile; + + getCandleStairsFile(); + + function getCandleStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> getCandleStairsFile -> Entering function."); } + + let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2); + let fileName = "Data.json" + let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + bot.dataMine + "/" + "Candles-Volumes"; + let filePath = filePathRoot + "/Output/" + VOLUMES_FOLDER_NAME + '/' + "Multi-Period-Daily" + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onCurrentDayFileReceived); + + function onCurrentDayFileReceived(err, text) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> getCandleStairsFile -> onCurrentDayFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> getCandleStairsFile -> onCurrentDayFileReceived -> text = " + text); } + + previousDayFile = JSON.parse(text); + getProcessDayFile() + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getCandleStairsFile -> onCurrentDayFileReceived -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getCandleStairsFile -> onCurrentDayFileReceived -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getCandleStairsFile -> onCurrentDayFileReceived -> market = " + market.baseAsset + '_' + market.quotedAsset); + + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getCandleStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function getProcessDayFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> getProcessDayFile -> Entering function."); } + + let dateForPath = processDate.getUTCFullYear() + '/' + utilities.pad(processDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(processDate.getUTCDate(), 2); + let fileName = "Data.json" + let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + bot.dataMine + "/" + "Candles-Volumes"; + let filePath = filePathRoot + "/Output/" + VOLUMES_FOLDER_NAME + '/' + "Multi-Period-Daily" + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onCurrentDayFileReceived); + + function onCurrentDayFileReceived(err, text) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> getProcessDayFile -> onCurrentDayFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> getProcessDayFile -> onCurrentDayFileReceived -> text = " + text); } + + processDayFile = JSON.parse(text); + buildVolumes(); + + } catch (err) { + + if (processDate.valueOf() > contextVariables.maxCandleFile.valueOf()) { + + processDayFile = []; // we are past the head of the market, then no worries if this file is non existent. + buildVolumes(); + + } else { + + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getProcessDayFile -> onCurrentDayFileReceived -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getProcessDayFile -> onCurrentDayFileReceived -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getProcessDayFile -> onCurrentDayFileReceived -> market = " + market.baseAsset + '_' + market.quotedAsset); + + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> getProcessDayFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildVolumes() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> buildVolumes -> Entering function."); } + + pushVolumes(previousDayFile); + pushVolumes(processDayFile); + findVolumesStairs(); + + function pushVolumes(volumesFile) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> buildVolumes -> pushVolumes -> Entering function."); } + + for (let i = 0; i < volumesFile.length; i++) { + + let volume = { + amountBuy: 0, + amountSell: 0, + begin: undefined, + end: undefined + }; + + volume.amountBuy = volumesFile[i][0]; + volume.amountSell = volumesFile[i][1]; + + volume.begin = volumesFile[i][2]; + volume.end = volumesFile[i][3]; + + volumes.push(volume); + + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> buildVolumes -> pushVolumes -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> buildVolumes -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function findVolumesStairs() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> findVolumesStairs -> Entering function."); } + + /* 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) { + + pushToArray(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) { + + pushToArray(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) { + + pushToArray(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) { + + pushToArray(sellDownStairs); + } + + sellDownStairs = undefined; + } + } + + function pushToArray(stairs) { + + if (INTENSIVE_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> findVolumesStairs -> pushToArray -> Entering function."); } + + try { + + if (stairs !== undefined) { + + /* + Here we detect stairs that started at process day - 2. + */ + + if (stairs.type === 'sell') { + + if (stairs.begin > endOfLastSellVolumeStair) { + + if (stairs.begin >= processDate.valueOf()) { + + processDayStairsArray.push(stairs); + + } else { + + previousDayStairsArray.push(stairs); + + } + } + } + else { + + if (stairs.begin > endOfLastBuyVolumeStair) { + + if (stairs.begin >= processDate.valueOf()) { + + processDayStairsArray.push(stairs); + + } else { + + previousDayStairsArray.push(stairs); + + } + } + + } + + stairs = undefined; + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> findVolumesStairs -> pushToArray -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + + writeVolumeStairsFile(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> findVolumesStairs -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeVolumeStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> Entering function."); } + + writeFile(previousDayStairsArray, previousDay, onPreviousFileWritten); + + function onPreviousFileWritten() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> onPreviousFileWritten -> Entering function."); } + + writeFile(processDayStairsArray, processDate, onProcessFileWritten); + + function onProcessFileWritten() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> onPreviousFileWritten -> onProcessFileWritten -> Entering function."); } + + controlLoop(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> writeVolumeStairsFile -> onPreviousFileWritten -> onProcessFileWritten -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> writeVolumeStairsFile -> onPreviousFileWritten -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeFile(pStairs, pDate, callback) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> writeFile -> Entering function."); } + + let separator = ""; + let fileRecordCounter = 0; + + let fileContent = ""; + + for (i = 0; i < pStairs.length; i++) { + + let stairs = pStairs[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 dateForPath = pDate.getUTCFullYear() + '/' + utilities.pad(pDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(pDate.getUTCDate(), 2); + let fileName = 'Data.json'; + let filePath = bot.filePathRoot + "/Output/" + VOLUME_STAIRS_FOLDER_NAME + "/" + bot.process + "/" + timeFrame + "/" + dateForPath; + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> writeFile -> onFileCreated -> Entering function."); } + + if (LOG_FILE_CONTENT === true) { + logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> writeFile -> onFileCreated -> Content written = " + fileContent); + } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> writeFile -> onFileCreated -> err = " + err.stack); + callBack(err); + return; + } + + const logText = "[WARN] Finished with File @ " + market.baseAsset + "_" + market.quotedAsset + ", " + fileRecordCounter + " records inserted into " + filePath + "/" + fileName + ""; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> processVolumes -> writeVolumeStairsFile -> writeFile -> onFileCreated -> " + logText); } + + callback(); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> writeVolumeStairsFile -> onPreviousFileWritten -> writeFile -> onFileCreated -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> writeVolumeStairsFile -> onPreviousFileWritten -> writeFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> writeVolumeStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> processVolumes -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> loopBody -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function controlLoop() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> controlLoop -> Entering function."); } + + n++; + + if (n < global.dailyFilePeriods.length) { + + loopBody(); + + } else { + + n = 0; + + writeDataRanges(onWritten); + + function onWritten(err) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> controlLoop -> onWritten -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> controlLoop -> onWritten -> err = " + err.stack); + callBack(err); + return; + } + + writeStatusReport(processDate, advanceTime); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> controlLoop -> onWritten -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> periodsLoop -> controlLoop -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> err.message = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeDataRanges(callBack) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRanges -> Entering function."); } + + writeDataRange(contextVariables.firstTradeFile, processDate, CANDLE_STAIRS_FOLDER_NAME, onCandlesStairsDataRangeWritten); + + function onCandlesStairsDataRangeWritten(err) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRanges -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] writeDataRanges -> writeDataRanges -> onCandlesStairsDataRangeWritten -> err = " + err.stack); + callBack(err); + return; + } + + writeDataRange(contextVariables.firstTradeFile, processDate, VOLUME_STAIRS_FOLDER_NAME, onVolumeStairsDataRangeWritten); + + function onVolumeStairsDataRangeWritten(err) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] writeDataRanges -> writeDataRanges -> onVolumeStairsDataRangeWritten -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] writeDataRanges -> writeDataRanges -> onVolumeStairsDataRangeWritten -> err = " + err.stack); + callBack(err); + return; + } + + callBack(global.DEFAULT_OK_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRanges -> err = " + err.stack); + callBack(global.DEFAULT_FAIL_RESPONSE); + } + + } + + function writeDataRange(pBegin, pEnd, pProductFolder, callBack) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> Entering function."); } + + let dataRange = { + begin: pBegin.valueOf(), + end: pEnd.valueOf() + }; + + let fileContent = JSON.stringify(dataRange); + + let fileName = 'Data.Range.json'; + let filePath = bot.filePathRoot + "/Output/" + pProductFolder + "/" + bot.process; + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack); + callBack(err); + return; + } + + if (LOG_FILE_CONTENT === true) { + logger.write(MODULE_NAME, "[INFO] start -> writeDataRange -> onFileCreated -> Content written = " + fileContent); + } + + let key = bot.dataMine + "-" + bot.codeName + "-" + pProductFolder + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset + let event = { + dateRange: dataRange + } + + global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event) + + callBack(global.DEFAULT_OK_RESPONSE); + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> err = " + err.stack); + callBack(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeStatusReport(lastFileDate, callBack) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeStatusReport -> Entering function."); } + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeStatusReport -> lastFileDate = " + lastFileDate); } + + try { + + let reportKey = "Masters" + "-" + "Stairs-Patterns" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + let thisReport = statusDependencies.statusReports.get(reportKey); + + thisReport.file.lastExecution = bot.processDatetime; + thisReport.file.lastFile = lastFileDate; + thisReport.file.beginingOfMarket = beginingOfMarket.toUTCString() + thisReport.save(callBack); + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> err.message = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } +}; diff --git a/Multi-Period-Market/User.Bot.js b/Multi-Period-Market/User.Bot.js new file mode 100644 index 000000000..3c79011eb --- /dev/null +++ b/Multi-Period-Market/User.Bot.js @@ -0,0 +1,818 @@ +exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, FILE_STORAGE) { + + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + + 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 = "User Bot"; + + const TRADES_FOLDER_NAME = "Trades"; + + const CANDLES_FOLDER_NAME = "Candles"; + const CANDLE_STAIRS_FOLDER_NAME = "Candle-Stairs"; + + const VOLUMES_FOLDER_NAME = "Volumes"; + const VOLUME_STAIRS_FOLDER_NAME = "Volume-Stairs"; + + thisObject = { + initialize: initialize, + start: start + }; + + let statusDependencies; + let utilities = UTILITIES.newCloudUtilities(bot, logger); + let fileStorage = FILE_STORAGE.newFileStorage(logger); + + return thisObject; + + function initialize(pStatusDependencies, callBackFunction) { + + try { + + logger.fileName = MODULE_NAME; + logger.initialize(); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } + + statusDependencies = pStatusDependencies; + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + /* + + This process is going to do the following: + + Read the candles and volumes from Candles Volumes and produce for each market two files with candles stairs and volumes stairs respectively. + + */ + + function start(callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } + + let market = bot.market; + + buildStairs(); + + function buildStairs() { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> Entering function."); } + + try { + + let n; + + periodsLoop(); + + function periodsLoop() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> 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 -> buildStairs -> periodsLoop -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function loopBody() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> Entering function."); } + + const outputPeriod = global.marketFilesPeriods[n][0]; + const timeFrame = global.marketFilesPeriods[n][1]; + + nextCandleFile(); + + function nextCandleFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> Entering function."); } + + let fileName = "Data.json"; + + let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + bot.dataMine + "/" + "Candles-Volumes"; + let filePath = filePathRoot + "/Output/" + CANDLES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrame; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileReceived); + + function onFileReceived(err, text) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> text = " + text); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> err = " + err.stack); + callBackFunction(err); + return; + + } + + let marketFile = JSON.parse(text); + + let candles = []; + let stairsArray = []; + + buildCandles(); + + function buildCandles() { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> 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); + + } + + findCandleStairs(); + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> buildCandles -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function findCandleStairs() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> findCandleStairs -> Entering function."); } + + /* 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(); + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> findCandleStairs -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeCandleStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> Entering function."); } + + 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 = 'Data.json'; + let filePath = bot.filePathRoot + "/Output/" + CANDLE_STAIRS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrame; + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> onFileCreated -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> onFileCreated -> fileContent = " + fileContent); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> onFileCreated -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> onFileCreated -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); + + callBackFunction(err); + return; + + } + + nextVolumeFile(); + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> onFileCreated -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> writeCandleStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> onFileReceived -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextCandleFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + + function nextVolumeFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> Entering function."); } + + let fileName = "Data.json" + + let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + bot.dataMine + "/" + "Candles-Volumes"; + let filePath = filePathRoot + "/Output/" + VOLUMES_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrame; + filePath += '/' + fileName + + fileStorage.getTextFile(filePath, onFileReceived); + + function onFileReceived(err, text) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> text = " + text); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> err = " + err.stack); + callBackFunction(err); + return; + + } + + let marketFile = JSON.parse(text); + + let volumes = []; + let stairsArray = []; + + buildVolumes(); + + function buildVolumes() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> buildVolumes -> Entering function."); } + + 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(volume); + + } + + findVolumesStairs(); + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> buildVolumes -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function findVolumesStairs() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> findVolumesStairs -> Entering function."); } + + /* 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(); + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> findVolumesStairs -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeVolumeStairsFile() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> Entering function."); } + + 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 = 'Data.json'; + let filePath = bot.filePathRoot + "/Output/" + VOLUME_STAIRS_FOLDER_NAME + "/" + "Multi-Period-Market" + "/" + timeFrame; + filePath += '/' + fileName + + fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> onFileCreated -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> onFileCreated -> fileContent = " + fileContent); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> onFileCreated -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> onFileCreated -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset); + + callBackFunction(err); + return; + + } + + controlLoop(); + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> onFileCreated -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> onFileReceived -> writeVolumeStairsFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> nextVolumeFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> loopBody -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function controlLoop() { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> buildStairs -> controlLoop -> Entering function."); } + + n++; + + if (n < global.marketFilesPeriods.length) { + + loopBody(); + + } else { + + writeStatusReport(callBackFunction); + + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> controlLoop -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> buildStairs -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeStatusReport(callBack) { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeStatusReport -> Entering function."); } + + try { + + let reportKey = "Masters" + "-" + "Stairs-Patterns" + "-" + "Multi-Period-Market" + "-" + "dataSet.V1"; + let thisReport = statusDependencies.statusReports.get(reportKey); + + thisReport.file.lastExecution = bot.processDatetime; + thisReport.save(callBack); + + if (global.areEqualDates(bot.processDatetime, new Date()) === false) { + logger.newInternalLoop(bot.codeName, bot.process, bot.processDatetime); + } + + /* Telling the world we are alive and doing well */ + let currentDateString = bot.processDatetime.getUTCFullYear() + '-' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.processDatetime.getUTCDate(), 2); + let currentDate = new Date(bot.processDatetime) + let lastDate = new Date() + bot.processHeartBeat(currentDateString, global.getPercentage(currentDate, currentDate, lastDate)) + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } +}; diff --git a/Project.njsproj b/Project.njsproj new file mode 100644 index 000000000..4cab6c556 --- /dev/null +++ b/Project.njsproj @@ -0,0 +1,91 @@ + + + + Debug + 2.0 + {aa2901bc-8f81-40f0-a402-5ca79a3f22ad} + + ShowAllFiles + + + c:\users\luis\source\repos\AAOlivia\Multi-Period-Market + . + {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + Project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + True + 0 + / + http://localhost:48022/ + False + True + http://localhost:1337 + False + + + + + + + CurrentPage + True + False + False + False + + + + + + + + + False + False + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..057a53b4a --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# I'm Stairs Patterns, an AA i-Bot! + +# Disclaimer + +THE AA MASTERS BOTS AND THEIR ASSOCIATED PRODUCTS AND SERVICES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, SUITABILITY FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + +IN NO EVENT WILL ADVANCED ALGOS BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY USE OF THE AACLOUD, THE AA ARENA COMPETITION, THE AA MASTERS BOTS, OR ANY OTHER ASSOCIATED SERVICE OR WEB SITE, INCLUDING, WITHOUT LIMITATION, ANY LOST PROFITS, BUSINESS INTERRUPTION, LOSS OF FUNDS, PROGRAMS OR OTHER DATA OR OTHERWISE, EVEN IF WE ARE EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. diff --git a/Stairs-Patterns-Indicator-Bot.sln b/Stairs-Patterns-Indicator-Bot.sln new file mode 100644 index 000000000..da30e179e --- /dev/null +++ b/Stairs-Patterns-Indicator-Bot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2027 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Project", "Project.njsproj", "{AA2901BC-8F81-40F0-A402-5CA79A3F22AD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA2901BC-8F81-40F0-A402-5CA79A3F22AD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B4BDAEAF-E863-458A-87C1-CC6D8C490784} + EndGlobalSection +EndGlobal diff --git a/package.json b/package.json new file mode 100644 index 000000000..6a1cc344a --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "Stairs-Patternsa", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Luis-Fernando-Molina", + "license": "ISC", + "dependencies": { + } +} diff --git a/this.bot.config.json b/this.bot.config.json new file mode 100644 index 000000000..064bff527 --- /dev/null +++ b/this.bot.config.json @@ -0,0 +1,232 @@ +{ + "displayName": "Stairs Patterns", + "codeName": "Stairs-Patterns", + "type": "Indicator Bot", + "version": { + "major": 1, + "minor": 0, + "patch": 0 + }, + "dataMine": "Masters", + "profilePicture": "Stairs Patterns.png", + "dataSetVersion": "dataSet.V1", + "processes": [ + { + "name": "Multi-Period-Market", + "description": "Produces for each market two files with candles stairs and volumes stairs respectively.", + "startMode": { + "allMonths": { + "run": "false", + "minYear": "", + "maxYear": "" + }, + "oneMonth": { + "run": "false", + "year": "", + "month": "" + }, + "noTime": { + "run": "true" + }, + "fixedInterval": { + "run": "false", + "interval": 0 + } + }, + "deadWaitTime": 0, + "normalWaitTime": 0, + "retryWaitTime": 10000, + "sleepWaitTime": 3600000, + "comaWaitTime": 86400000, + "updatesDatasets": [ + { + "product": "Candle-Stairs", + "dataSet": "Multi-Period-Market" + }, + { + "product": "Volume-Stairs", + "dataSet": "Multi-Period-Market" + } + ], + "statusDependencies": [ + { + "dataMine": "Masters", + "bot": "Stairs-Patterns", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Multi-Period-Market", + "dataSetVersion": "dataSet.V1" + }, + { + "dataMine": "Masters", + "bot": "Candles-Volumes", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Multi-Period-Market", + "dataSetVersion": "dataSet.V1", + "waitUntilNextUpdate": true + } + ] + }, + { + "name": "Multi-Period-Daily", + "description": "Produces for each market two files with candles stairs and volumes stairs respectively at each day of the market history.", + "startMode": { + "allMonths": { + "run": "false", + "minYear": "", + "maxYear": "" + }, + "oneMonth": { + "run": "false", + "year": "", + "month": "" + }, + "noTime": { + "run": "true" + }, + "fixedInterval": { + "run": "false", + "interval": 0 + } + }, + "deadWaitTime": 0, + "normalWaitTime": 0, + "retryWaitTime": 10000, + "sleepWaitTime": 3600000, + "comaWaitTime": 86400000, + "updatesDatasets": [ + { + "product": "Candle-Stairs", + "dataSet": "Multi-Period-Daily" + }, + { + "product": "Volume-Stairs", + "dataSet": "Multi-Period-Daily" + } + ], + "statusDependencies": [ + { + "dataMine": "Masters", + "bot": "Exchange-Raw-Data", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Historic-Trades", + "dataSetVersion": "dataSet.V1" + }, + { + "dataMine": "Masters", + "bot": "Candles-Volumes", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Multi-Period-Daily", + "dataSetVersion": "dataSet.V1", + "waitUntilNextUpdate": true + }, + { + "dataMine": "Masters", + "bot": "Stairs-Patterns", + "botVersion": { + "major": 1, + "minor": 0 + }, + "process": "Multi-Period-Daily", + "dataSetVersion": "dataSet.V1" + } + ] + } + ], + "products": [ + { + "codeName": "Candle-Stairs", + "displayName": "Candle-Stairs Patters", + "description": "Two sets of files per period that contains candle-stairs patterns.", + "storageAccount": "Stairs-Patterns", + "shareWith": "Public", + "dataSets": [ + { + "codeName": "Multi-Period-Market", + "type": "Market Files", + "validPeriods": [ "24-hs", "12-hs", "08-hs", "06-hs", "04-hs", "03-hs", "02-hs", "01-hs" ], + "filePath": "Masters/Stairs-Patterns.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Candle-Stairs/Multi-Period-Market/@Period", + "fileName": "@AssetA_@AssetB.json" + }, + { + "codeName": "Multi-Period-Daily", + "type": "Daily Files", + "validPeriods": [ "45-min", "40-min", "30-min", "20-min", "15-min", "10-min", "05-min", "04-min", "03-min", "02-min", "01-min" ], + "filePath": "Masters/Stairs-Patterns.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Candle-Stairs/Multi-Period-Daily/@Period/@Year/@Month/@Day", + "fileName": "@AssetA_@AssetB.json", + "dataRange": { + "filePath": "Masters/Stairs-Patterns.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Candle-Stairs/Multi-Period-Daily", + "fileName": "Data.Range.@AssetA_@AssetB.json" + } + } + ], + "exchangeList": [ + { + "name": "Poloniex" + }, + { + "name": "Coss" + } + ], + "plotter": { + "dataMine": "Masters", + "codeName": "PlottersStairsPatterns", + "moduleName": "CandleStairs" + } + }, + { + "codeName": "Volume-Stairs", + "displayName": "Volume-Stairs Patters", + "description": "Two sets of files per period that contains volume-stairs patterns.", + "storageAccount": "Stairs-Patterns", + "shareWith": "Public", + "dataSets": [ + { + "codeName": "Multi-Period-Market", + "type": "Market Files", + "validPeriods": [ "24-hs", "12-hs", "08-hs", "06-hs", "04-hs", "03-hs", "02-hs", "01-hs" ], + "filePath": "Masters/Stairs-Patterns.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Volume-Stairs/Multi-Period-Market/@Period", + "fileName": "@AssetA_@AssetB.json" + }, + { + "codeName": "Multi-Period-Daily", + "type": "Daily Files", + "validPeriods": [ "45-min", "40-min", "30-min", "20-min", "15-min", "10-min", "05-min", "04-min", "03-min", "02-min", "01-min" ], + "filePath": "Masters/Stairs-Patterns.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Volume-Stairs/Multi-Period-Daily/@Period/@Year/@Month/@Day", + "fileName": "@AssetA_@AssetB.json", + "dataRange": { + "filePath": "Masters/Stairs-Patterns.1.0/AACloud.1.1/@Exchange/dataSet.V1/Output/Volume-Stairs/Multi-Period-Daily", + "fileName": "Data.Range.@AssetA_@AssetB.json" + } + } + ], + "exchangeList": [ + { + "name": "Poloniex" + }, + { + "name": "Coss" + } + ], + "plotter": { + "dataMine": "Masters", + "codeName": "PlottersStairsPatterns", + "moduleName": "VolumeStairs" + } + } + ] +} + + +