From 06d20408953a576c9bf794a77fa69c64eecb9a58 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Thu, 23 Jan 2020 13:34:08 +0100 Subject: [PATCH] Historic Trades implemented with CCXT --- Charly.png | Bin 69735 -> 0 bytes Historic-Trades/User.Bot.js | 1351 +++++++---------------------------- Hole-Fixing/User.Bot.js | 4 - Live-Trades/User.Bot.js | 6 - 4 files changed, 274 insertions(+), 1087 deletions(-) delete mode 100644 Charly.png diff --git a/Charly.png b/Charly.png deleted file mode 100644 index 02df2415234224066422a274cea15e15655fb7d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69735 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 diff --git a/Historic-Trades/User.Bot.js b/Historic-Trades/User.Bot.js index 21767c848..a3015764a 100644 --- a/Historic-Trades/User.Bot.js +++ b/Historic-Trades/User.Bot.js @@ -1,1103 +1,300 @@ -exports.newInterval = function newInterval(bot, logger, UTILITIES, FILE_STORAGE, MARKETS_MODULE, POLONIEX_CLIENT_MODULE) { + +exports.newUserBot = function newUserBot(bot, logger, COMMONS, UTILITIES, fileStorage, STATUS_REPORT, EXCHANGE_API) { - const ROOT_DIR = './'; + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; const GMT_SECONDS = ':00.000 GMT+0000'; const GMT_MILI_SECONDS = '.000 GMT+0000'; - - const MODULE_NAME = "Interval"; - const LOG_INFO = true; - - const EXCHANGE_ID = 1; - + const MODULE_NAME = "User Bot"; const TRADES_FOLDER_NAME = "Trades"; - const GO_RANDOM = false; - const FORCE_MARKET = 2; // This allows to debug the execution of an specific market. Not intended for production. - - interval = { + thisObject = { initialize: initialize, start: start }; - let markets; + let utilities = UTILITIES.newCloudUtilities(bot, logger) + let statusDependencies - let charlyStorage = FILE_STORAGE.newFileStorage(bot, logger); + const ONE_MINUTE = 60000 + const MAX_TRADES_PER_EXECUTION = 2000 + const ccxt = require('ccxt') - let utilities = UTILITIES.newCloudUtilities(bot, logger); + let allTrades = [] + let thisReport; + let since + let initialProcessTimestamp + let beginingOfMarket + let lastFileSaved + let filesToCreate = 0 + let filesCreated = 0 - let year; - let month; - - return interval; - - function initialize(yearAssigend, monthAssigned, callBackFunction) { + return thisObject; + function initialize(pStatusDependencies, pMonth, pYear, callBackFunction) { try { - year = yearAssigend; - month = monthAssigned; + logger.fileName = MODULE_NAME; + logger.initialize(); - month = utilities.pad(month, 2); // Adding a left zero when needed. + statusDependencies = pStatusDependencies; + callBackFunction(global.DEFAULT_OK_RESPONSE); - logger.fileName = MODULE_NAME + "-" + year + "-" + month; + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } - const logText = "[INFO] initialize - Entering function 'initialize' " + " @ " + year + "-" + month; - console.log(logText); - logger.write(logText); + function start(callBackFunction) { + try { - charlyStorage.initialize(bot.dataMine); + begin() - markets = MARKETS_MODULE.newMarkets(bot.logger); - markets.initialize(callBackFunction); + async function begin() { + getContextVariables() + defineSince() + await getTrades() + await saveTrades() + } + + function getContextVariables() { + + try { + let reportKey; + + reportKey = "AAMasters" + "-" + "AACharly" + "-" + "Historic-Trades" + "-" + "dataSet.V1"; + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> reportKey = " + reportKey); } + + if (statusDependencies.statusReports.get(reportKey).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) + + if (thisReport.file.beginingOfMarket !== undefined) { // This means this is not the first time this process run. + beginingOfMarket = new Date(thisReport.file.beginingOfMarket.year + "-" + thisReport.file.beginingOfMarket.month + "-" + thisReport.file.beginingOfMarket.days + " " + thisReport.file.beginingOfMarket.hours + ":" + thisReport.file.beginingOfMarket.minutes + GMT_SECONDS); + lastFileSaved = new Date(thisReport.file.lastFileSaved.year + "-" + thisReport.file.lastFileSaved.month + "-" + thisReport.file.lastFileSaved.days + " " + thisReport.file.lastFileSaved.hours + ":" + thisReport.file.lastFileSaved.minutes + GMT_SECONDS); + } else { // This means this is the first time this process run. + beginingOfMarket = new Date() + } + } 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 Status Dependencies. "); + logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys)); + } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function defineSince() { + + let uiStartDate = new Date(bot.uiStartDate) + + if (uiStartDate.valueOf() < beginingOfMarket.valueOf()) { + since = (new Date(bot.uiStartDate)).valueOf() + initialProcessTimestamp = since + beginingOfMarket = new Date(uiStartDate.valueOf()) + } else { + since = lastFileSaved.valueOf() + initialProcessTimestamp = lastFileSaved.valueOf() + } + } + + async function getTrades() { + + let lastTradeKey = '' + + const limit = 1000 + const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset + const exchangeId = bot.exchange.toLowerCase() + const exchangeClass = ccxt[exchangeId] + const exchange = new exchangeClass({ + 'timeout': 30000, + 'enableRateLimit': true//, + //verbose: true + }) + + try { + console.log("exchange.milliseconds()", exchange.milliseconds()) + while (since < exchange.milliseconds()) { + console.log("symbol", symbol) + console.log("since", since) + console.log("limit", limit) + const trades = await exchange.fetchTrades(symbol, since, limit) + console.log("trades.length:" + trades.length) + + if (trades.length >= 1 && allTrades.length < MAX_TRADES_PER_EXECUTION) { + since = trades[trades.length - 1]['timestamp'] + + for (let i = 0; i < trades.length; i++) { + let trade = trades[i] + let tradeKey = trade.timestamp + '-' + trade.side + '-' + trade.price.toFixed(16) + '-' + trade.amount.toFixed(16) + if (tradeKey !== lastTradeKey) { + allTrades.push([trade.timestamp, trade.side, trade.price, trade.amount]) + } + lastTradeKey = tradeKey + } + console.log("allTrades.length:", allTrades.length) + } else { + break + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> getTrades -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + async function saveTrades() { + + try { + + let fileContent = '[' + let previousRecordMinute = Math.trunc((initialProcessTimestamp - ONE_MINUTE) / ONE_MINUTE) + let currentRecordMinute + let needSeparator = false + let error + let separator + + for (let i = 0; i < allTrades.length; i++) { + let record = allTrades[i] + let trade = { + timestamp: record[0], + side: record[1], + price: record[2], + amount: record[3] + } + + currentRecordMinute = Math.trunc(trade.timestamp / ONE_MINUTE) + + if ( + currentRecordMinute !== previousRecordMinute + ) { + /* There are no more trades at this minute or it is the last trade, so we save the file.*/ + saveFile() + } + + if (needSeparator === false) { + needSeparator = true; + separator = ''; + } else { + separator = ','; + } + + /* Add the trade to the file content.*/ + fileContent = fileContent + separator + '[' + trade.timestamp + ',"' + trade.side + '",' + trade.price + ',' + trade.amount + ']'; + + if (i === allTrades.length) { + /* This is the last trade, so we save the file.*/ + saveFile() + /* It might happen that there are several minutes after the last trade without trades. We need to record empty files for them.*/ + if (allTrades.length < MAX_TRADES_PER_EXECUTION) { + let currentTimeMinute = Math.trunc((new Date()).valueOf() / ONE_MINUTE) + if (currentTimeMinute - currentRecordMinute > 1) { + createMissingEmptyFiles(currentRecordMinute, currentTimeMinute) + } + } + return + } + previousRecordMinute = currentRecordMinute + if (error) { + callBackFunction(error); + return; + } + function saveFile() { + fileContent = fileContent + ']' + if (currentRecordMinute - previousRecordMinute > 1) { + createMissingEmptyFiles(previousRecordMinute, currentRecordMinute) + } + let fileName = bot.market.baseAsset + '_' + bot.market.quotedAsset + '.json' + filesToCreate++ + fileStorage.createTextFile(bot.dataMine, getFilePath(currentRecordMinute * ONE_MINUTE) + '/' + fileName, fileContent + '\n', onFileCreated); + console.log("FILE CONTENT", getFilePath(currentRecordMinute * ONE_MINUTE) + '/' + fileName, fileContent) + fileContent = '[' + needSeparator = false + } + function createMissingEmptyFiles(begin, end) { + console.log("createMissingEmptyFiles", begin, end) + for (let j = begin + 1; j < end; j++) { + let fileName = bot.market.baseAsset + '_' + bot.market.quotedAsset + '.json' + filesToCreate++ + fileStorage.createTextFile(bot.dataMine, getFilePath(j * ONE_MINUTE) + '/' + fileName, "[]" + '\n', onFileCreated); + console.log("EMPTY FILE AT ", getFilePath(j * ONE_MINUTE) + '/' + fileName) + } + } + function onFileCreated(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> tradesReadyToBeSaved -> onFileBCreated -> err = " + err.stack); + error = err // This allows the loop to be breaked. + return; + } + filesCreated++ + lastFileSaved = new Date((currentRecordMinute * ONE_MINUTE)) + if (filesCreated === filesToCreate) { + writeStatusReport() + } + } + function getFilePath(timestamp) { + let datetime = new Date(timestamp) + let dateForPath = datetime.getUTCFullYear() + '/' + + utilities.pad(datetime.getUTCMonth() + 1, 2) + '/' + + utilities.pad(datetime.getUTCDate(), 2) + '/' + + utilities.pad(datetime.getUTCHours(), 2) + '/' + + utilities.pad(datetime.getUTCMinutes(), 2) + let filePath = bot.filePathRoot + "/Output/" + TRADES_FOLDER_NAME + '/' + dateForPath; + return filePath + } + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> saveTrades -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeStatusReport() { + try { + if (lastFileSaved === undefined) {return} + thisReport.file = { + lastFileSaved: { + year: lastFileSaved.getUTCFullYear(), + month: (lastFileSaved.getUTCMonth() + 1), + days: lastFileSaved.getUTCDate(), + hours: lastFileSaved.getUTCHours(), + minutes: lastFileSaved.getUTCMinutes() + }, + beginingOfMarket: { + year: beginingOfMarket.getUTCFullYear(), + month: (beginingOfMarket.getUTCMonth() + 1), + days: beginingOfMarket.getUTCDate(), + hours: beginingOfMarket.getUTCHours(), + minutes: beginingOfMarket.getUTCMinutes() + } + }; + + thisReport.save(onSaved); + + function onSaved(err) { + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> onSaved -> err = " + err.stack); + callBackFunction(err); + return; + } + callBackFunction(global.DEFAULT_OK_RESPONSE); + } + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> writeStatusReport -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } } catch (err) { - - const logText = "[ERROR] initialize - ' ERROR : " + err.message; - console.log(logText); - logger.write(logText); - - } - } - -/* - -This process is going to do the following: - -It will look for the oldest file for each market and it will request to the exchange the max amount of transactions from that moment, backwards. -Then it will iterate through those transactions to create the files for each minute with the trades it got from the exchange. Finally when it reaches the -begining of the history of the market, it will move to the next market and repead the process. - -This process complements the Live Trades process and write historical trades files exactly in the same format. - -*/ - - function start(callBackFunction) { - - try { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'start', with year = " + year + " and month = " + month); - } - - let processDate = new Date(year + "-" + month + "-1 00:00:00.000 GMT+0000"); - - processDate.setUTCMonth(processDate.getUTCMonth() + 1); // First we go 1 month into the future. - processDate.setUTCSeconds(processDate.getUTCSeconds() - 30); // Then we go back 30 seconds, or to the last minute of the original month. - - let thisDatetime = new Date(); - - if ((year === thisDatetime.getUTCFullYear() && month > thisDatetime.getUTCMonth() + 1) || year > thisDatetime.getUTCFullYear()) { - - logger.write(MODULE_NAME, "[INFO] We are too far in the future. Interval will not execute. Sorry."); - return; - - } - - let moreIntervalCallsNeeded = false; // Here we control if some processing has been done. If none, that means that it is not necesary to call this Interval again. - - let currentDate; // This will hold the current datetime of each execution. - let cursorDatetime; // This holds the datetime we are using to request records from, backwards. - - 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, - baseAsset: "", - quotedAsset: "" - }; - - let dateForPath; - - let filePath; - - let exchangeCallTime; - - const FIRST_RECORD_ID = 100; // This should be around 100. - - marketsLoop(); - - /* - - At every run, the process needs to loop through all the markets at this exchange. - The problem is that we can not fire all the requests at once to the exchange or we would get banned. - For that reason we create a queue with the markets to process and we do one at the time. - The following functions marketsLoop(), openMarket(), closeMarket() and closeAndOpenMarket() controls the serialization of this processing. - - */ - - function marketsLoop() { - - try { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'marketsLoop'"); - } - - currentDate = new Date(); - 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(MODULE_NAME, "[INFO] Entering function 'openMarket'"); - } - - - if (marketQueue.length === 0) { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] 'openMarket' - marketQueue.length === 0"); - } - - const logText = "[WARN] We processed all the markets."; - logger.write(logText); - - callBackFunction(moreIntervalCallsNeeded); - - return; - } - - - if (GO_RANDOM === true) { - const index = parseInt(Math.random() * (marketQueue.length - 1)); - - market.id = marketQueue[index][0]; - market.baseAsset = marketQueue[index][1]; - market.quotedAsset = marketQueue[index][2]; - market.status = marketQueue[index][3]; - - marketQueue.splice(index, 1); - } else { - let marketRecord = marketQueue.shift(); - - market.id = marketRecord[0]; - market.baseAsset = marketRecord[1]; - market.quotedAsset = marketRecord[2]; - market.status = marketRecord[3]; - - if (FORCE_MARKET > 0) { - if (FORCE_MARKET !== market.id) { - closeAndOpenMarket(); - return; - } - } - } - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] 'openMarket' - marketQueue.length = " + marketQueue.length); - logger.write(MODULE_NAME, "[INFO] 'openMarket' - market sucessfully opened : " + market.baseAsset + "_" + market.quotedAsset); - } - - if (market.status === markets.ENABLED) { - - getMarketStatusReport(); - - } else { - - logger.write(MODULE_NAME, "[INFO] 'openMarket' - market " + market.baseAsset + "_" + market.quotedAsset + " 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(MODULE_NAME, "[INFO] Entering function 'closeMarket'"); - } - - } - - function closeAndOpenMarket() { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'closeAndOpenMarket'"); - } - - openMarket(); - - } - - - /* - - The following code executes for each market, getting the trades from the exchange and saving them in files at the cloud storage. - - */ - - - function getMarketStatusReport() { - - /* - - The market status report is independent from the year and month this current interval is working on. We will read the report to - check if the whole market is already completed. If it is, there is no need to do anything else. - - */ - - try { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'getMarketStatusReport'"); - } - - let reportFilePath = bot.exchange + "/Processes/" + bot.process; - let fileName = "Status.Report." + market.baseAsset + '_' + market.quotedAsset + ".json"; - - charlyStorage.getTextFile(reportFilePath, fileName, onFileReceived); - - function onFileReceived(text) { - - if (text === undefined) { - - getMonthStatusReport(); - - } else { - - let statusReport = JSON.parse(text); - - if (statusReport.completeHistory === true) { - - const logText = "[INFO] 'getMarketStatusReport' - Market " + market.baseAsset + '_' + market.quotedAsset + " is already complete. Skipping it. "; - logger.write(logText); - - closeAndOpenMarket(); - - } else { - - getMonthStatusReport(); - - } - - } - - } - - } - catch (err) { - const logText = "[ERROR] 'getMarketStatusReport' - ERROR : " + err.message; - logger.write(logText); - closeMarket(); - } - } - - - function getMonthStatusReport() { - - /* - - In order to know where to start the process (which datetime) we need to read the StatusReport of the process. This file will tell - us where we left after the last execution or if the process has finished for this market. - - */ - - try { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'getMarketStatusReport'"); - } - - let reportFilePath = bot.exchange + "/Processes/" + bot.process + "/" + year + "/" + month; - let fileName = "Status.Report." + market.baseAsset + '_' + market.quotedAsset + ".json"; - - charlyStorage.getTextFile(reportFilePath, fileName, onFileReceived); - - function onFileReceived(text) { - - if (text === undefined) { - - if (processDate.valueOf() > thisDatetime.valueOf()) { - - currentDate = thisDatetime; - getTheTrades(); - - } else { - - currentDate = processDate; - getTheTrades(); - - } - - } else { - - let statusReport = JSON.parse(text); - - if (statusReport.completeHistory === true) { - - const logText = "[INFO] 'getMonthStatusReport' - Market " + market.baseAsset + '_' + market.quotedAsset + " is already complete. Skipping it. "; - logger.write(logText); - - closeAndOpenMarket(); - - } else { - - /* We get from the file the datetime of the last file created. */ - - currentDate = new Date(statusReport.lastFile.year + "-" + statusReport.lastFile.month + "-" + statusReport.lastFile.days + " " + statusReport.lastFile.hours + ":" + statusReport.lastFile.minutes + GMT_SECONDS); - - getTheTrades(); - - } - - } - - } - - } - catch (err) { - const logText = "[ERROR] 'getMonthStatusReport' - ERROR : " + err.message; - logger.write(logText); - closeMarket(); - } - } - - function getTheTrades() { - - try { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'getTheTrades'"); - } - - const firstTradeDate = parseInt(currentDate.valueOf() / 1000); - - const firstMomentOfMonth = new Date(year + "-" + month + "-1 00:00:00.000 GMT+0000"); - - /* We request to the Exchange API some more records than needed, anyway we will discard records out of the range we need. To do this we substract some seconds and add some - seconds to the already calculated UNIX timestamps. */ - - let startTime = parseInt(firstMomentOfMonth.valueOf() / 1000) - 5; - let endTime = firstTradeDate + 60; - - if (endTime - startTime > 60 * 60 * 24 * 30) { - - /* - - We can not ask this exchange for more than 30 days in a single request. So we substract in this case a few days so as not - to violate this policy on 31 days months. - - */ - - startTime = startTime + 60 * 60 * 25 * 7; // we add one week to be confortable. - - } - - exchangeCallTime = new Date(); - - let poloniexApiClient = POLONIEX_CLIENT_MODULE.newPoloniexAPIClient(global.EXCHANGE_KEYS[bot.exchange].Key, global.EXCHANGE_KEYS[bot.exchange].Secret); - - poloniexApiClient.API.returnPublicTradeHistory(market.baseAsset, market.quotedAsset, startTime, endTime, onExchangeCallReturned); - - } - catch (err) { - const logText = "[ERROR] 'getTheTrades' - ERROR : " + err.message; - logger.write(logText); - closeMarket(); - } - } - - function onExchangeCallReturned(err, tradesRequested) { - - try { - - if (LOG_INFO === true) { - - let exchangeResponseTime = new Date(); - let timeDifference = (exchangeResponseTime.valueOf() - exchangeCallTime.valueOf()) / 1000; - logger.write(MODULE_NAME, "[INFO] Entering function 'onExchangeCallReturned' - Call time recorded = " + timeDifference + " seconds."); - } - - if (err || tradesRequested.error !== undefined) { - try { - - if (err.message.indexOf("ETIMEDOUT") > 0) { - - const logText = "[WARN] onExchangeCallReturned - Timeout reached while trying to access the Exchange API. Trying again. : ERROR = " + err.message; - logger.write(logText); - - /* We try to reconnect to the exchange and fetch the data again. */ - - getTheTrades(); - return; - - } else { - - if (err.message.indexOf("ECONNRESET") > 0) { - - const logText = "[WARN] onExchangeCallReturned - The exchange reseted the connection. Trying again. : ERROR = " + err.message; - logger.write(logText); - - /* We try to reconnect to the exchange and fetch the data again. */ - - getTheTrades(); - return; - - } else { - - - const logText = "[ERROR] onExchangeCallReturned - Unexpected error trying to contact the Exchange. Giving up. : ERROR = " + err.message; - logger.write(logText); - closeMarket(); - return; - } - } - - } catch (err) { - const logText = "[ERROR] onExchangeCallReturned : ERROR : tradesRequested.error = " + tradesRequested.error; - logger.write(logText); - - if (tradesRequested.error === "Invalid currency pair.") { - - markets.disableMarket(EXCHANGE_ID, market.id, onMarketDeactivated); - - function onMarketDeactivated() { - - logger.write(MODULE_NAME, "[INFO] Market " + market.baseAsset + "_" + market.quotedAsset + " deactivated. Id = " + market.id); - - closeAndOpenMarket(); - return; - - } - - } - - closeMarket(); - return; - } - - closeMarket(); - return; - - } else { - - if (tradesRequested.length === 0) { - - const logText = "[WARN] 'onExchangeCallReturned' - The exchange didn't return any records. We assume this means the date requested is before the begining of this market. "; - logger.write(logText); - closeAndOpenMarket(); - - } else { - - tradesReadyToBeSaved(tradesRequested); - - } - } - } - catch (err) { - const logText = "[ERROR] 'onExchangeCallReturned' - ERROR : " + err.message; - logger.write(logText); - closeMarket(); - } - - } - - function tradesReadyToBeSaved(tradesRequested) { - - try { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'tradesReadyToBeSaved'"); - } - - moreIntervalCallsNeeded = true; // If we are requesting trades from the exchange, that means that something is being done. - - let filesWrittenCounter = 0; // We count each file written to the cloud so when we reach some number we partially update the status report - let fileRecordCounter = 0; - - let needSeparator; - let separator; - - let lastProcessMinute; // Stores the previous record minute during each iteration - let filesToSave = []; // Array where we will store all the content to be written to files - - needSeparator = false; - - let fileContent = ""; - - let beginingOfMarket = false; // This flag is turned on when we detect the conditions that makes us assume we are at the begining of the hitory of the market. - - let currentProcessMinute = Math.trunc(currentDate.valueOf() / 1000 / 60); - let firstProcessMinute = currentProcessMinute; - - /* We will iterate through all the records received from the exchange. We expect the exchange API to return the records ordered by ID DESC so we change it to ASC. So this is going to be going back in time as we advance. */ - - for (let i = 0; i < tradesRequested.length; i++) { - - let record = tradesRequested[i]; - - const trade = { - tradeIdAtExchange: record.tradeID, - marketIdAtExchange: record.globalTradeID, - type: record.type, - rate: record.rate, - amountA: record.total, - amountB: record.amount, - datetime: new Date(record.date + GMT_MILI_SECONDS) - }; - - trade.seconds = trade.datetime.getUTCSeconds(); - - if (trade.tradeIdAtExchange < FIRST_RECORD_ID) { // Experience has shown us that the first trade records of any market are out of range, because they are teststing the market with any value. So no worries if they are not recorded in our history. - - beginingOfMarket = true; - - } - - let currentRecordMinute = Math.trunc(trade.datetime.valueOf() / 1000 / 60); // This are the number of minutes since the begining of time of this trade. - - if (currentRecordMinute > currentProcessMinute) { - - /* We discard this trade, since it happened after the minute we want to record in the current file. */ - - continue; - } - - if (currentRecordMinute < currentProcessMinute) { - - /* - - The information is older that the current time. - We must store the current info and reset the pointer to the current time to match the one on the information currently being processd. - We know this can lead to a 'hole' or some empty files being skipped, but we solve that problem with the next loop. - - */ - - let blackMinutes = currentProcessMinute - currentRecordMinute; - - for (let j = 1; j <= blackMinutes; j++) { - - storeFileContent(); - currentProcessMinute--; - - } - } - - if (currentRecordMinute === currentProcessMinute) { - - if (needSeparator === false) { - - needSeparator = true; - separator = ''; - - } else { - separator = ','; - } - - fileContent = '[' + trade.tradeIdAtExchange + ',"' + trade.type + '",' + trade.rate + ',' + trade.amountA + ',' + trade.amountB + ',' + trade.seconds + ']' + separator + fileContent; - - fileRecordCounter++; - - } - - } - - if (fileContent !== "") { - - /* - - Normally, we would not save the content of the last file, since it usually contains some of the records of that minute. - There are a few exceptions: - - If it is the last file of the month (the one at the very begining of the month) we must save it. - - */ - - let date = new Date(currentProcessMinute * 60 * 1000); - - if ((date.getUTCFullYear() === year) && - (date.getUTCMonth() + 1 === parseInt(month)) && // remember we added left zeros to look cool. - (date.getUTCDate() === 1) && - (date.getUTCHours() === 0) && - (date.getUTCMinutes() === 0)) { - - storeFileContent(); - - } else { - - /* - - The second exception happens when we approach the begining of the month. - In markets with few volume, there might be a lot of minutes or even hours, close to the begining of the month with no records. Naturally the process - would stop with the last record received from the exchange. So we must fix that an to do it, we check if we processed only one minute, - in that case, we interpret that all the minutes below until the begining of the month, are with empty records. If this condition is not met, then we must - ignore the records as they might be some missing for the current minute. - - */ - - if (firstProcessMinute - currentProcessMinute === 1) { - - /* We will artificially generate all the minutes upto the begining of the month. The first will contain some records, all the rest will be empty. */ - - const firstMomentOfMonth = new Date(year + "-" + month + "-1 00:00:00.000 GMT+0000"); - - let firstMinute = Math.trunc(firstMomentOfMonth.valueOf() / 1000 / 60); // This are the number of minutes since the begining of time to the begining of the month. - - let blackMinutes = currentProcessMinute - firstMinute + 1; - - for (let j = 1; j <= blackMinutes; j++) { - - storeFileContent(); - currentProcessMinute--; - - } - - } else { - - /* We dont store the last processed file since it might be incomplete. */ - - } - } - } - - function storeFileContent() { - - fileContent = '[' + fileContent + ']'; - - let fileRecord = { - datetime: currentProcessMinute, - content: fileContent, - records: fileRecordCounter - }; - - filesToSave.push(fileRecord); - - fileRecordCounter = 0; - needSeparator = false; - fileContent = ""; - } - - /* - - Now it is time to process all the information we stored at filesToSave. - - */ - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] About to save " + (filesToSave.length + 1) + " files. "); - } - - let i = 0; - let date; - - nextRecord(); - - function nextRecord() { - - let fileName = '' + market.baseAsset + '_' + market.quotedAsset + '.json'; - - date = new Date(filesToSave[i].datetime * 60 * 1000); - fileRecordCounter = filesToSave[i].records; - fileContent = filesToSave[i].content; - - dateForPath = date.getUTCFullYear() + '/' + utilities.pad(date.getUTCMonth() + 1, 2) + '/' + utilities.pad(date.getUTCDate(), 2) + '/' + utilities.pad(date.getUTCHours(), 2) + '/' + utilities.pad(date.getUTCMinutes(), 2); - - filePath = bot.exchange + "/Output/" + TRADES_FOLDER_NAME + '/' + dateForPath; - - utilities.createFolderIfNeeded(filePath, charlyStorage, onFolderCreated); - - function onFolderCreated() { - - charlyStorage.createTextFile(filePath, fileName, fileContent + '\n', onFileCreated); - - function onFileCreated() { - - let paddedCounter = utilities.pad(fileRecordCounter, 4); - let paddedFile = utilities.pad(i + 1, 5); - - const logText = "[WARN] Finished with File # " + paddedFile + " @ " + market.baseAsset + "_" + market.quotedAsset + ", " + paddedCounter + " records inserted into " + filePath + "/" + fileName + ""; - console.log(logText); - logger.write(logText); - - controlLoop(); - } - } - } - - - function controlLoop() { - - i++; - - if (i < filesToSave.length) { - - filesWrittenCounter++; - - if (filesWrittenCounter === 30) { // Every now and then we write a Status Report so that if the process is terminated, it can resume later from there. - - writeStatusReport(date, false, false, onReportWritten); - - function onReportWritten() { - - filesWrittenCounter = 0; - nextRecord(); - - } - - } else { - - nextRecord(); - - } - - } else { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Leaving function 'tradesReadyToBeSaved'"); - } - - let monthCompleteHistory = false; - - if ((date.getUTCFullYear() === year) && - (date.getUTCMonth() + 1 === parseInt(month)) && // remember we added left zeros to look cool. - (date.getUTCDate() === 1) && - (date.getUTCHours() === 0) && - (date.getUTCMinutes() === 0)) { - - /* - - When we get to the first minute of the month, that menas we completed the whole month and this should later be recorded - at the Status Report. - - */ - - monthCompleteHistory = true; - - } - - if (beginingOfMarket === true) { - - /* - - If we already detected that we completed the whole market (by reaching a very low trade Id) then we can also say that - we completed the current month. - - */ - - monthCompleteHistory = true; - } - - writeStatusReport(date, monthCompleteHistory, beginingOfMarket, onReportWritten); - - function onReportWritten() { - - closeAndOpenMarket(); - - } - - } - } - } - catch (err) { - const logText = "[ERROR] 'tradesReadyToBeSaved' - ERROR : " + err.message; - logger.write(logText); - closeMarket(); - } - } - - - function writeStatusReport(lastFileDate, isMonthComplete, isBeginingOfMarket, callBack) { - - /* - - The report is a file that records important information to be used by a) another instance of the same process, b) another process or by c) another bot. - - The first thing to do is to read the current report file content, if it exists. - - In this particular process, we will record the last file processed. - - */ - - try { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] Entering function 'writeStatusReport'"); - } - - let reportFilePath = bot.exchange + "/Processes/" + bot.process + "/" + year + "/" + month; - - utilities.createFolderIfNeeded(reportFilePath, charlyStorage, onFolderCreated); - - function onFolderCreated() { - - let fileName = "Status.Report." + market.baseAsset + '_' + market.quotedAsset + ".json"; - - let report = { - lastFile: { - year: lastFileDate.getUTCFullYear(), - month: (lastFileDate.getUTCMonth() + 1), - days: lastFileDate.getUTCDate(), - hours: lastFileDate.getUTCHours(), - minutes: lastFileDate.getUTCMinutes() - }, - completeHistory: isMonthComplete - }; - - let fileContent = JSON.stringify(report); - - charlyStorage.createTextFile(reportFilePath, fileName, fileContent + '\n', onFileCreated); - - function onFileCreated() { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - Content written: " + fileContent); - } - - if (isBeginingOfMarket === true) { - - reportFilePath = bot.exchange + "/Processes/" + bot.process; - - utilities.createFolderIfNeeded(reportFilePath, charlyStorage, onFolderCreated); - - function onFolderCreated() { - - fileName = "Status.Report." + market.baseAsset + '_' + market.quotedAsset + ".json"; - - let report = { - lastFile: { - year: lastFileDate.getUTCFullYear(), - month: (lastFileDate.getUTCMonth() + 1), - days: lastFileDate.getUTCDate(), - hours: lastFileDate.getUTCHours(), - minutes: lastFileDate.getUTCMinutes() - }, - completeHistory: false // This will be true only when all months are completed. - }; - - let fileContent = JSON.stringify(report); - - charlyStorage.createTextFile(reportFilePath, fileName, fileContent + '\n', onMasterFileCreated); - - function onMasterFileCreated() { - - if (LOG_INFO === true) { - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - Begining of market reached !!! - Content written: " + fileContent); - } - - verifyMarketComplete(isMonthComplete, callBack); - - } - - } - - } - - else { - - verifyMarketComplete(isMonthComplete, callBack); - - } - - } - } - - - } - catch (err) { - const logText = "[ERROR] 'writeStatusReport' - ERROR : " + err.message; - logger.write(logText); - closeMarket(); - } - - } - - function verifyMarketComplete(isMonthComplete, callBack) { - - if (isMonthComplete === true) { - - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - verifyMarketComplete - Month Completed !!! "); - - /* - - To know if the whole market is complete, we need to ready all the status reports, one by one, since the first - month / year to the current month / year. - - To know which is the first month / year we will read the main Status Report. Only if it exists (that is after the first - month of the market reached a very low trade), we will know from it which is the initial month / year. - - Obtaining the current month / year is trivial, since it is current time. We will scan all the files and we need all of - them to report that the month has been completed in order to declare the whole market as completed. - - All of the above is what we call the last step. - - */ - - let initialYear; - let initialMonth; - - let finalYear = (new Date()).getUTCFullYear(); - let finalMonth = (new Date()).getUTCMonth() + 1; - - let reportFilePath = bot.exchange + "/Processes/" + bot.process; - let fileName = "Status.Report." + market.baseAsset + '_' + market.quotedAsset + ".json"; - - /* Lets read the main status report */ - - charlyStorage.getTextFile(reportFilePath, fileName, onFileReceived); - - function onFileReceived(text) { - - if (text === undefined) { - - /* The first month of the market didnt create this file yet. Aborting verification. */ - - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - verifyMarketComplete - Verification ABORTED since the main status report did not exist. "); - - callBack(); - - } else { - - let statusReport = JSON.parse(text); - - initialYear = statusReport.lastFile.year; - initialMonth = statusReport.lastFile.month; - - loopCycle(); - } - - } - - function loopCycle() { - - /* - - Here we read the status report file of each month / year to verify if it is complete or not. - - */ - let paddedInitialMonth = utilities.pad(initialMonth, 2); - - let reportFilePath = bot.exchange + "/Processes/" + bot.process + "/" + initialYear + "/" + paddedInitialMonth; - let fileName = "Status.Report." + market.baseAsset + '_' + market.quotedAsset + ".json"; - - charlyStorage.getTextFile(reportFilePath, fileName, onStatusReportFileReceived); - - function onStatusReportFileReceived(text) { - - if (text === undefined) { - - /* If any of the files do not exist, it is enough to know the market is not complete. */ - - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - verifyMarketComplete - Verification ABORTED since the status report for year " + initialYear + " and month " + initialMonth + " did not exist. "); - - callBack(); - - } else { - - let statusReport = JSON.parse(text); - - if (statusReport.completeHistory === true) { - - loop(); // Lets see the next month. - - } else { - - /* If any of the files says that month is not complete then it is enough to know the market is not complete. */ - - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - verifyMarketComplete - Verification ABORTED since the status report for year " + initialYear + " and month " + initialMonth + " did not have a complete history. "); - - callBack(); - - } - } - - } - - } - - function loop() { - - initialMonth++; - - if (initialMonth > 12) { - - initialMonth = 1; - initialYear++; - - } - - if ((initialYear === finalYear && initialMonth > finalMonth) || (initialYear > finalYear)) { - - /* We arrived to the point where we have checked all the status reports of every month and they are all complete. */ - - readAndWriteNewReport(); - - return; - } - - loopCycle(); - - } - - function readAndWriteNewReport() { - - /* We will read the current file to preserve its data, and save it again with market complete = true */ - - let reportFilePath = bot.exchange + "/Processes/" + bot.process; - let fileName = "Status.Report." + market.baseAsset + '_' + market.quotedAsset + ".json"; - - charlyStorage.getTextFile(reportFilePath, fileName, onFileReceived); - - function onFileReceived(text) { - - let statusReport = JSON.parse(text); - - statusReport.completeHistory = true; - - let fileContent = JSON.stringify(statusReport); - - charlyStorage.createTextFile(reportFilePath, fileName, fileContent + '\n', onMasterFileCreated); - - function onMasterFileCreated() { - - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - verifyMarketComplete - Verification SUCESSFULL . Market Completed !!! - Content written: " + fileContent); - - callBack(); - - } - } - } - - } else { - - logger.write(MODULE_NAME, "[INFO] 'writeStatusReport' - verifyMarketComplete - Verification ABORTED: Month is not complete. "); - callBack(); - - } - - } - - - - } - catch (err) { - const logText = "[ERROR] 'Start' - ERROR : " + err.message; - logger.write(logText); + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } }; diff --git a/Hole-Fixing/User.Bot.js b/Hole-Fixing/User.Bot.js index a59423a12..52619942d 100644 --- a/Hole-Fixing/User.Bot.js +++ b/Hole-Fixing/User.Bot.js @@ -2,12 +2,9 @@ const FULL_LOG = true; const LOG_FILE_CONTENT = false; - const GMT_SECONDS = ':00.000 GMT+0000'; const GMT_MILI_SECONDS = '.000 GMT+0000'; - const MODULE_NAME = "User Bot Hole Fixing"; - const TRADES_FOLDER_NAME = "Trades"; thisObject = { @@ -25,7 +22,6 @@ return thisObject; function initialize(pStatusDependencies, pMonth, pYear, callBackFunction) { - try { year = pYear; diff --git a/Live-Trades/User.Bot.js b/Live-Trades/User.Bot.js index 366038847..2f864e7d8 100644 --- a/Live-Trades/User.Bot.js +++ b/Live-Trades/User.Bot.js @@ -2,12 +2,9 @@ const FULL_LOG = true; const LOG_FILE_CONTENT = false; - const GMT_SECONDS = ':00.000 GMT+0000'; const GMT_MILI_SECONDS = '.000 GMT+0000'; - const MODULE_NAME = "User Bot"; - const TRADES_FOLDER_NAME = "Trades"; thisObject = { @@ -22,16 +19,13 @@ return thisObject; function initialize(pStatusDependencies, pMonth, pYear, callBackFunction) { - try { logger.fileName = MODULE_NAME; logger.initialize(); if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } - statusDependencies = pStatusDependencies; - callBackFunction(global.DEFAULT_OK_RESPONSE); } catch (err) {