diff --git a/Multi-Period-Market/User.bot.js b/Multi-Period-Market/User.bot.js new file mode 100644 index 000000000..5db94ce30 --- /dev/null +++ b/Multi-Period-Market/User.bot.js @@ -0,0 +1,580 @@ +var LRCIndicator = require('./LRCIndicator'); + +exports.newUserBot = function newUserBot(BOT, COMMONS, UTILITIES, DEBUG_MODULE, BLOB_STORAGE, FILE_STORAGE) { + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + const USE_PARTIAL_LAST_CANDLE = true; // When running live the last candle generated is a partial candle. + + let bot = BOT; + + const ONE_MIN_IN_MILISECONDS = 60 * 1000; + + const MODULE_NAME = "User Bot"; + + const EXCHANGE_NAME = "Poloniex"; + + const logger = DEBUG_MODULE.newDebugLog(); + logger.fileName = MODULE_NAME; + logger.bot = bot; + + thisObject = { + initialize: initialize, + start: start + }; + + let gaussStorage = BLOB_STORAGE.newBlobStorage(bot); + + let utilities = UTILITIES.newUtilities(bot); + + let statusDependencies; + + return thisObject; + + function initialize(pStatusDependencies, pMonth, pYear, callBackFunction) { + + try { + + logger.fileName = MODULE_NAME; + + if (FULL_LOG === true) { logger.write("[INFO] initialize -> Entering function."); } + + statusDependencies = pStatusDependencies; + + gaussStorage.initialize({ bot: "AAGauss", devTeam: "AAMasters" }, onGaussInizialized); + + function onGaussInizialized(err) { + + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + + if (FULL_LOG === true) { logger.write("[INFO] initialize -> onGaussInizialized -> Initialization Succeed."); } + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } else { + logger.write("[ERROR] initialize -> onGaussInizialized -> err = " + err.message); + callBackFunction(err); + } + } + + } catch (err) { + logger.write("[ERROR] initialize -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + /* + TODO Create Description + */ + + function start(callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] start -> Entering function."); } + + let market = global.MARKET; + let reportFilePath = EXCHANGE_NAME + "/Processes/" + bot.process; + let executionTime; + let maxCandleTime; + let lastCandles; + + getContextVariables(); + + function getContextVariables() { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] start -> getContextVariables -> Entering function."); } + + let reportKey = "AAMasters" + "-" + "AAOlivia" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + if (FULL_LOG === true) { logger.write("[INFO] start -> getContextVariables -> reportKey = " + reportKey); } + + if (statusDependencies.statusReports.get(reportKey).status === "Status Report is corrupt.") { + logger.write("[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return; + } + + let thisReport = statusDependencies.statusReports.get(reportKey).file; + + if (thisReport.lastFile === undefined) { + logger.write("[WARN] start -> getContextVariables -> Undefined Last File. -> reportKey = " + reportKey); + + let customOK = { + result: global.CUSTOM_OK_RESPONSE.result, + message: "Dependency not ready." + } + logger.write("[WARN] start -> getContextVariables -> customOK = " + customOK.message); + callBackFunction(customOK); + return; + } + + maxCandleTime = new Date(Date.UTC(thisReport.lastFile.year, thisReport.lastFile.month - 1, thisReport.lastFile.days, thisReport.lastFile.hours, thisReport.lastFile.minutes)); + + reportKey = "AAMasters" + "-" + "AAGauss" + "-" + "Multi-Period-Daily" + "-" + "dataSet.V1"; + if (FULL_LOG === true) { logger.write("[INFO] start -> getContextVariables -> reportKey = " + reportKey); } + + if (statusDependencies.statusReports.get(reportKey).status === "Status Report is corrupt.") { + logger.write("[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.lastCandles !== undefined) { + logger.write("[INFO] start -> getContextVariables -> thisReport.lastCandles: " + JSON.stringify(thisReport.lastCandles)); + lastCandles = thisReport.lastCandles; + + buildLRCPoints(); + + } else { + + if (FULL_LOG === true) { logger.write("[INFO] start -> getContextVariables -> First time running the bot."); } + + lastCandles = []; + + for (n = 0; n < global.dailyFilePeriods.length; n++) { + lastCandles.push(0); + } + + lastCandles[10] = new Date(Date.UTC(2018, 3).valueOf()); + + if (FULL_LOG === true) { logger.write("[INFO] start -> getContextVariables -> Starting at: " + new Date(lastCandles[10]).toISOString()); } + + buildLRCPoints(); + + } + + } catch (err) { + logger.write("[ERROR] start -> getContextVariables -> err = " + err.message); + if (err.message === "Cannot read property 'file' of undefined") { + logger.write("[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. "); + logger.write("[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys)); + } + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function buildLRCPoints() { + + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCPoints -> Entering function."); } + + advanceTime(); + + function advanceTime() { + + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCPoints -> advanceTime -> Entering function."); } + + executionTime = new Date(lastCandles[10].valueOf() + ONE_MIN_IN_MILISECONDS); + + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCPoints -> advanceTime -> New processing time @ " + executionTime.toISOString()); } + + /* Validation that we are not going past the head of the market. */ + + if (executionTime.valueOf() > maxCandleTime.valueOf()) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCPoints -> advanceTime -> Head of the market found @ " + maxCandleTime.toISOString()); } + + callBackFunction(global.DEFAULT_OK_RESPONSE); // Here is where we finish processing and wait for the platform to run this module again. + return; + } + + periodsLoop(); + + } + } + + function periodsLoop() { + + let n = 0 // loop Variable representing each possible period as defined at the periods array. + + loopBody(); + + function loopBody() { + + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCChannels -> periodsLoop -> loopBody -> Entering function. Time: " + executionTime.toISOString() + ". Loop: "+n); } + + const outputPeriod = global.dailyFilePeriods[n][0]; + const folderName = global.dailyFilePeriods[n][1]; + + isTimeToRun(); + + function isTimeToRun() { + let nextExecution = lastCandles[n].valueOf() + outputPeriod; + if (executionTime.valueOf() >= nextExecution) { + getLRCPoints(); + } else { + + if (FULL_LOG === true) logger.write("[INFO] start -> periodsLoop -> loopBody -> It's not time to run this period. Next Execution: " + new Date(nextExecution).toISOString()); + + controlLoop(); + } + } + + function getLRCPoints() { + + if (FULL_LOG === true) { logger.write("[INFO] start -> getLRCPoints -> Entering function."); } + + const maxLRCDepth = 63; + const maxBackwardsCount = 60; + + let backwardsCount = 0; + let candleArray = []; + + let queryDate = new Date(executionTime); + let candleFile = getDailyFile(queryDate, onDailyFileReceived); + + function onDailyFileReceived(err, candleFile) { + if (FULL_LOG === true) { logger.write("[INFO] start -> getLRCPoints -> onDailyFileReceived."); } + + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + for (let i = 0; i < candleFile.length; i++) { + let candle = { + open: undefined, + close: undefined, + min: 10000000000000, + max: 0, + begin: undefined, + end: undefined + }; + + candle.min = candleFile[i][0]; + candle.max = candleFile[i][1]; + + candle.open = candleFile[i][2]; + candle.close = candleFile[i][3]; + + candle.begin = candleFile[i][4]; + candle.end = candleFile[i][5]; + + if (LOG_FILE_CONTENT === true) { logger.write("[INFO] Candle Date: " + new Date(candle.begin).toISOString() + ". Process Date: " + executionTime.toISOString()); } + + if (candleArray.length < maxLRCDepth && candle.begin <= executionTime.valueOf()) { + candleArray.push(candleFile[i]); + } + } + + if (FULL_LOG === true) { logger.write("[INFO] start -> getLRCPoints -> Candle Array Length: " + candleArray.length); } + + if (candleArray.length >= maxLRCDepth) { + if (FULL_LOG === true) { logger.write("[INFO] start -> getLRCPoints -> All candles available proceed with LRC calculations."); } + + let lrcChannel = performLRCCalculations(candleArray); + saveChannel(lrcChannel); + + } else if (backwardsCount <= maxBackwardsCount) { + if (FULL_LOG === true) { logger.write("[INFO] start -> getChannelTilt -> Getting file for day before."); } + + queryDate.setDate(queryDate.getDate() - 1); + getDailyFile(queryDate, onDailyFileReceived); + backwardsCount++; + } else { + logger.write("[ERROR] start -> getChannelTilt -> Not enough history to calculate LRC."); + callBack(global.DEFAULT_RETRY_RESPONSE); + } + } + } + + function getDailyFile(dateTime, onDailyFileReceived) { + try { + if (FULL_LOG === true) { logger.write("[INFO] start -> getLRCPoints -> getDailyFile -> Entering function."); } + + let datePath = dateTime.getUTCFullYear() + "/" + utilities.pad(dateTime.getUTCMonth() + 1, 2) + "/" + utilities.pad(dateTime.getUTCDate(), 2); + let filePath = "AAMasters/AAOlivia.1.0/AACloud.1.1/Poloniex/dataSet.V1/Output/Candles/Multi-Period-Daily/" + folderName + "/" + datePath; + let fileName = market.assetA + '_' + market.assetB + ".json" + + gaussStorage.getTextFile(filePath, fileName, onFileReceived); + + function onFileReceived(err, text) { + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + if (FULL_LOG === true) { logger.write("[INFO] start -> getLRCPoints -> getDailyFile -> onFileReceived > Entering Function."); } + + let candleFile = JSON.parse(text); + onDailyFileReceived(global.DEFAULT_OK_RESPONSE, candleFile); + } else { + logger.write("[ERROR] start -> getLRCPoints -> getDailyFile -> onFileReceived -> Failed to get the file. Will abort the process and request a retry."); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + return; + } + } + } catch (err) { + logger.write("[ERROR] start -> getLRCPoints -> getDailyFile -> err = " + err.message); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } + } + + function performLRCCalculations(candleArray) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> performLRCCalculations -> Entering function."); } + + /* + * It's needed to order since it's possible that we need to get another file and it will put an older candle at the end of the array. + */ + candleArray.sort(function (a, b) { + return a[4] - b[4]; + }); + + if (USE_PARTIAL_LAST_CANDLE === false) candleArray = candleArray.slice(0, candleArray.length - 1); + + let lrcPoints = []; + lrcPoints.push(0); //firstCandleBeginTime 0 + lrcPoints.push(0); //firstCandleEndTime 1 + lrcPoints.push(0); //lastCandleBeginTime 2 + lrcPoints.push(0); //lastCandleEndTime 3 + lrcPoints.push(0); //minimumChannelValue 4 + lrcPoints.push(0); //middleChannelValue 5 + lrcPoints.push(0); //maximumChannelValue 6 + + if (LOG_FILE_CONTENT === true) + lrcPoints.push(candleArray); //Entire candle array 7 + + calculateLRC(candleArray, lrcPoints); + + let lastCandle = candleArray[0]; + let firstCandle = candleArray[candleArray.length - 1]; + lrcPoints[0] = firstCandle[4]; + lrcPoints[1] = firstCandle[5]; + lrcPoints[2] = lastCandle[4]; + lrcPoints[3] = lastCandle[5]; + + if (FULL_LOG === true) { + logger.write("[INFO] start -> getChannelTilt -> performLRCCalculations -> LRC Points calculation results: " + JSON.stringify(lrcPoints)); } + + return lrcPoints; + } + + function calculateLRC(candlesArray, lrcPoints) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> getChannelTilt -> calculateLRC -> Entering function."); } + + let lrcMinIndicator = new LRCIndicator(15); + let lrcMidIndicator = new LRCIndicator(30); + let lrcMaxIndicator = new LRCIndicator(60); + + for (let i = 0; i < candlesArray.length; i++) { + let tempCandle = candlesArray[i]; + let averagePrice = (tempCandle[0] + tempCandle[1] + tempCandle[2] + tempCandle[3]) / 4; // TODO Check which price should be take to get the LRC + + lrcMinIndicator.update(averagePrice); + lrcMidIndicator.update(averagePrice); + lrcMaxIndicator.update(averagePrice); + } + + /* + * Only if there is enough history the result will be calculated + */ + if (lrcMinIndicator.result != false && lrcMidIndicator.result != false && lrcMaxIndicator.result != false) { + lrcPoints[4] = lrcMinIndicator.result; + lrcPoints[5] = lrcMidIndicator.result; + lrcPoints[6] = lrcMaxIndicator.result; + + return lrcPoints; + } else { + logger.write("[ERROR] start -> getChannelTilt -> calculateLRC -> There is not enough history to calculate the LRC."); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function saveChannel(lrcChannel) { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] start -> saveChannel -> Entering function."); } + let fileContent; + let datePath = executionTime.getUTCFullYear() + "/" + utilities.pad(executionTime.getUTCMonth() + 1, 2) + "/" + utilities.pad(executionTime.getUTCDate(), 2); + let filePath = bot.filePathRoot + "/Output/LRC-Channel/Multi-Period-Daily/" + folderName + "/" + datePath; + let fileName = market.assetA + '_' + market.assetB + ".json" + + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCChannels -> periodsLoop -> loopBody -> getCurrentContent -> fileName = " + fileName); } + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCChannels -> periodsLoop -> loopBody -> getCurrentContent -> filePath = " + filePath); } + + gaussStorage.getTextFile(filePath, fileName, onFileRetrieved); + + function onFileRetrieved(err, text) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> onFileRetrieved -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + if (err.message === "File does not exist.") { + if (FULL_LOG === true) { logger.write("[INFO] start -> onFileRetrieved -> Daily file does not exist, creating folders."); } + + createFolders(filePath); + return; + } else { + logger.write("[ERROR] start -> onFileRetrieved -> err = " + err.message); + callBackFunction(err); + return; + } + } + + if (FULL_LOG === true) { logger.write("[INFO] start -> onFileRetrieved -> Daily file exist, appending content."); } + + fileContent = JSON.parse(text); + appendToFile(fileContent); + } + + + function createFolders(filePath) { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] start -> createFolders -> Entering function."); } + + utilities.createFolderIfNeeded(filePath, gaussStorage, onFolderACreated); + + function onFolderACreated(err) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> createFolders -> onFolderACreated -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write("[ERROR] start -> createFolders -> onFolderACreated -> err = " + err.message); + callBackFunction(err); + return; + } + + utilities.createFolderIfNeeded(reportFilePath, gaussStorage, onFolderCreated); + } + + function onFolderCreated(err) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> createFolders -> onFolderCreated -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write("[ERROR] start -> createFolders -> onFolderCreated -> err = " + err.message); + callBackFunction(err); + return; + } + createFile(); + } + + } catch (err) { + logger.write("[ERROR] start -> createFolders -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function createFile() { + + if (FULL_LOG === true) { logger.write("[INFO] start -> saveChannel -> createFile -> Entering function."); } + + let existingContent = []; + existingContent.push(lrcChannel); + + gaussStorage.createTextFile(filePath, fileName, JSON.stringify(existingContent), onFileCreated); + + function onFileCreated(err) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> saveChannel -> onFileCreated -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write("[ERROR] start -> saveChannel -> onFileCreated -> err = " + err.message); + callBackFunction(err); + return; + } + + if (LOG_FILE_CONTENT === true) { + logger.write("[INFO] start -> saveChannel -> onFileCreated -> Content written = " + JSON.stringify(existingContent)); + } + + // We keep a record of the last candle used for the time period + lastCandles[n] = lrcChannel[0]; + + controlLoop(); + } + } + + function appendToFile(fileContent) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> saveChannel -> appendToFile -> Entering function."); } + + fileContent.push(lrcChannel); + + gaussStorage.createTextFile(filePath, fileName, JSON.stringify(fileContent), onExistingFileUpdated); + + function onExistingFileUpdated(err) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> saveChannel -> onExistingFileUpdated -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write("[ERROR] start -> saveChannel -> onExistingFileUpdated -> err = " + err.message); + callBackFunction(err); + return; + } + + if (LOG_FILE_CONTENT === true) { + logger.write("[INFO] start -> saveChannel -> onExistingFileUpdated -> Content written = " + JSON.stringify(fileContent)); + } + + // We keep a record of the last candle used for the time period + lastCandles[n] = lrcChannel[0]; + + controlLoop(); + } + } + } catch (err) { + logger.write("[ERROR] start -> saveChannel -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + } + + function controlLoop() { + + if (FULL_LOG === true) { logger.write("[INFO] start -> buildLRCChannels -> periodsLoop -> controlLoop -> Entering function."); } + + n++; + + if (n < global.dailyFilePeriods.length) { + + loopBody(); + + } else { + + writeStatusReport(); + + } + } + + } + + function writeStatusReport() { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] start -> writeStatusReport -> Entering function."); } + + let key = bot.devTeam + "-" + bot.codeName + "-" + bot.process + "-" + bot.dataSetVersion; + + let statusReport = statusDependencies.statusReports.get(key); + + statusReport.file.lastExecution = bot.processDatetime; + statusReport.file.lastCandles = lastCandles; + statusReport.save(onSaved); + + function onSaved(err) { + + if (FULL_LOG === true) { logger.write("[INFO] start -> writeStatusReport -> onSaved -> Entering function."); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + logger.write("[ERROR] start -> writeStatusReport -> onSaved -> err = " + err.message); + callBackFunction(err); + return; + } + + callBackFunction(global.DEFAULT_OK_RESPONSE); + } + + } catch (err) { + logger.write("[ERROR] start -> writeStatusReport -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + + } catch (err) { + logger.write("[ERROR] start -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } +};