diff --git a/.vs/AACloud/v15/.suo b/.vs/AACloud/v15/.suo index c4e15f92b..a77a0e353 100644 Binary files a/.vs/AACloud/v15/.suo and b/.vs/AACloud/v15/.suo differ diff --git a/Assistant.js b/Assistant.js index 1bb495387..bc57fa469 100644 --- a/Assistant.js +++ b/Assistant.js @@ -1,6 +1,7 @@ exports.newAssistant = function newAssistant(BOT, DEBUG_MODULE) { const FULL_LOG = true; + const LOG_FILE_CONTENT = false; /* @@ -11,6 +12,7 @@ const MODULE_NAME = "Assistant"; let thisObject = { + dataDependencies: undefined, initialize: initialize, putPosition: putPosition, movePosition: movePosition, @@ -33,13 +35,12 @@ let context; let exchangeAPI; - let datasource; let marketRate; return thisObject; - function initialize(pContext, pExchangeAPI, pDatasource, callBackFunction) { + function initialize(pContext, pExchangeAPI, pDataDependencies, callBackFunction) { try { @@ -49,62 +50,139 @@ context = pContext; exchangeAPI = pExchangeAPI; - datasource = pDatasource; + thisObject.dataDependencies = pDataDependencies; - /* Procedure to get the current market rate. */ + getMarketRate(); - let candleArray = datasource.candlesMap.get("01-min"); // In this version of the platform, this array will contain the las 10 candles. - let candle = candleArray[candleArray.length - 1]; // The last candle of the 10 candles array for the 1 min Time Period. + function getMarketRate() { - marketRate = candle.close; - - /* - Now we verify that this candle is not too old. Lets say no more than 2 minutes old. This could happen if the datasets for - any reason stops being updated. - */ - - if (candle.begin < bot.processDatetime.valueOf() - 2 * 60 * 1000) { - - logger.write("[ERROR] initialize -> Candles more than two minutes old. Retrying later."); - callBack(global.DEFAULT_RETRY_RESPONSE); - return; - } - - /* If we are in Exchange Simulation Mode then there is not much to do here. */ - - if (global.EXCHANGE_SIMULATION_MODE === true) { - - logger.write("[WARN] initialize -> Exchange Simulation Mode detected. -> Skipping all validations."); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - - /* Procedure to validate we are in sync with the exchange. */ - - getPositionsAtExchange(onDone); - - function onDone(err) { try { - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write("[INFO] initialize -> onDone -> Execution finished well. :-)"); - callBackFunction(global.DEFAULT_OK_RESPONSE); - return; - } - break; - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write("[ERROR] initialize -> onDone -> Retry Later. Requesting Execution Retry."); + if (FULL_LOG === true) { logger.write("[INFO] initialize -> getMarketRate -> Entering function."); } + + /* Procedure to get the current market rate. */ + + let key = + global.PLATFORM_CONFIG.marketRateProvider.devTeam + "-" + + global.PLATFORM_CONFIG.marketRateProvider.bot + "-" + + global.PLATFORM_CONFIG.marketRateProvider.product + "-" + + global.PLATFORM_CONFIG.marketRateProvider.dataSet + "-" + + global.PLATFORM_CONFIG.marketRateProvider.dataSetVersion; + + let dataSet = thisObject.dataDependencies.dataSets.get(key); + + let timePeriod = "01-min"; + let fileName = '' + global.MARKET.assetA + '_' + global.MARKET.assetB + '.json'; + let filePath = bot.filePathRoot + "/Output/" + global.PLATFORM_CONFIG.marketRateProvider.product + "/" + global.PLATFORM_CONFIG.marketRateProvider.dataSet + "/" + timePeriod; + + dataSet.getTextFile(filePath, fileName, onFileReceived, true); + + function onFileReceived(err, text) { + + if (FULL_LOG === true) { logger.write("[INFO] initialize -> getMarketRate -> onFileReceived -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write("[INFO] initialize -> getMarketRate -> onFileReceived -> text = " + text); } + + let candleArray; + + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + try { + candleArray = JSON.parse(text); + + let lastCandleRecord = candleArray[candleArray.length - 1]; // The last candle contains at its close value the market rate. + + let candle = { + open: lastCandleRecord[2], + close: lastCandleRecord[3], + min: lastCandleRecord[0], + max: lastCandleRecord[1], + begin: lastCandleRecord[4], + end: lastCandleRecord[5] + }; + + marketRate = candle.close; + + /* + Now we verify that this candle is not too old. Lets say no more than 2 minutes old. This could happen if the datasets for + any reason stops being updated. + */ + + if (candle.begin < bot.processDatetime.valueOf() - 2 * 60 * 1000) { + + logger.write("[ERROR] initialize -> Candles more than two minutes old. Retrying later."); + callBack(global.DEFAULT_RETRY_RESPONSE); + return; + } + + /* If we are in Exchange Simulation Mode then there is not much to do here. */ + + if (global.EXCHANGE_SIMULATION_MODE === true) { + + logger.write("[WARN] initialize -> Exchange Simulation Mode detected. -> Skipping all validations."); + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + + } else { + + validateExchangeSyncronicity(); + return; + } + + + } catch (err) { + logger.write("[ERROR] initialize -> getMarketRate -> onFileReceived -> err = " + err.message); + logger.write("[ERROR] initialize -> getMarketRate -> onFileReceived -> Asuming this is a temporary situation. Requesting a Retry."); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); + } + } else { + logger.write("[ERROR] initialize -> getMarketRate -> onFileReceived -> err = " + err.message); callBackFunction(err); - return; } - break; - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write("[ERROR] initialize -> onDone -> Operation Failed. Aborting the process."); - callBackFunction(err); - return; + } + + } catch (err) { + logger.write("[ERROR] initialize -> getMarketRate -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function validateExchangeSyncronicity() { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] initialize -> validateExchangeSyncronicity -> Entering function."); } + + /* Procedure to validate we are in sync with the exchange. */ + + getPositionsAtExchange(onDone); + + function onDone(err) { + try { + + switch (err.result) { + case global.DEFAULT_OK_RESPONSE.result: { + logger.write("[INFO] initialize -> validateExchangeSyncronicity -> onDone -> Execution finished well. :-)"); + callBackFunction(global.DEFAULT_OK_RESPONSE); + return; + } + break; + case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. + logger.write("[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> Retry Later. Requesting Execution Retry."); + callBackFunction(err); + return; + } + break; + case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. + logger.write("[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> Operation Failed. Aborting the process."); + callBackFunction(err); + return; + } + break; + } + + } catch (err) { + logger.write("[ERROR] initialize -> validateExchangeSyncronicity -> onDone -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } - break; } } catch (err) { diff --git a/Context.js b/Context.js index 242eca792..fc42f878b 100644 --- a/Context.js +++ b/Context.js @@ -76,17 +76,18 @@ let cloudStorage = BLOB_STORAGE.newBlobStorage(bot); - let dependencies; + let statusDependencies; return thisObject; - function initialize(pDependencies, callBackFunction) { + function initialize(pStatusDependencies, callBackFunction) { try { if (FULL_LOG === true) { logger.write("[INFO] initialize -> Entering function."); } - dependencies = pDependencies; + statusDependencies = pStatusDependencies; + /* Here we get the positions the bot did and that are recorded at the bot storage account. We will use them through out the rest @@ -153,7 +154,7 @@ let key = bot.devTeam + "-" + bot.codeName + "-" + bot.process + "-" + bot.dataSetVersion; - statusReportModule = dependencies.statusReports.get(key); + statusReportModule = statusDependencies.statusReports.get(key); thisObject.statusReport = statusReportModule.file; if (thisObject.statusReport.lastExecution !== undefined) { diff --git a/Data Dependencies.js b/Data Dependencies.js index 8de8f4b7b..2a7435b99 100644 --- a/Data Dependencies.js +++ b/Data Dependencies.js @@ -1,4 +1,4 @@ -exports.newDataDependencies = function newDataDependencies(BOT, DEBUG_MODULE, STATUS_REPORT, BLOB_STORAGE, UTILITIES) { +exports.newDataDependencies = function newDataDependencies(BOT, DEBUG_MODULE, DATA_SET, BLOB_STORAGE, UTILITIES) { const FULL_LOG = true; const LOG_FILE_CONTENT = false; @@ -6,13 +6,13 @@ const MODULE_NAME = "Data Dependencies"; let thisObject = { - statusReports: new Map(), + dataSets: new Map(), initialize: initialize, keys: [] }; let bot = BOT; - let ownerBot; // This is the bot owner of the Status Report. Only owners can save the report and override the existing content. + let ownerBot; // This is the bot owner of the Data Set. const logger = DEBUG_MODULE.newDebugLog(); logger.fileName = MODULE_NAME; @@ -20,7 +20,7 @@ return thisObject; - function initialize(pDataDependenciesConfig, pMonth, pYear, callBackFunction) { + function initialize(pDataDependenciesConfig, callBackFunction) { try { @@ -28,17 +28,17 @@ /* - For each dependency declared at the bot config, we will initialize the status report, and load it as part of this initialization process. + For each dependency declared at the bot config, we will initialize a DataSet as part of this initialization process. */ let alreadyCalledBack = false; - let loadCount = 0; + let addCount = 0; for (let i = 0; i < pDataDependenciesConfig.length; i++) { - let statusReportModule = STATUS_REPORT.newStatusReport(BOT, DEBUG_MODULE, BLOB_STORAGE, UTILITIES); + let dataSetModule = DATA_SET.newDataSet(BOT, DEBUG_MODULE, BLOB_STORAGE, UTILITIES); - statusReportModule.initialize(pDataDependenciesConfig[i], pMonth, pYear, onInitilized); + dataSetModule.initialize(pDataDependenciesConfig[i], onInitilized); function onInitilized(err) { @@ -50,78 +50,25 @@ return; } - statusReportModule.load(onLoad); + addDataSet(); } - function onLoad(err) { + function addDataSet() { - try { + if (FULL_LOG === true) { logger.write("[INFO] initialize -> addDataSet -> Entering function."); } - statusReportModule.status = err.message; - - switch (err.message) { - case global.DEFAULT_OK_RESPONSE.message: { - logger.write("[INFO] initialize -> onLoad -> Execution finished well. -> bot = " + pDataDependenciesConfig[i].bot); - logger.write("[INFO] initialize -> onLoad -> Execution finished well. -> process = " + pDataDependenciesConfig[i].process); - - addReport(); - return; - } - case "Status Report was never created.": { - - logger.write("[WARN] initialize -> onLoad -> err.message = " + err.message); - logger.write("[WARN] initialize -> onLoad -> Report Not Found. -> bot = " + pDataDependenciesConfig[i].bot); - logger.write("[WARN] initialize -> onLoad -> Report Not Found. -> process = " + pDataDependenciesConfig[i].process); - addReport(); - return; - } - - case "Status Report is corrupt.": { - - logger.write("[WARN] initialize -> onLoad -> err.message = " + err.message); - logger.write("[WARN] initialize -> onLoad -> Report Not Found. -> bot = " + pDataDependenciesConfig[i].bot); - logger.write("[WARN] initialize -> onLoad -> Report Not Found. -> process = " + pDataDependenciesConfig[i].process); - addReport(); - return; - } - default: - { - logger.write("[ERROR] initialize -> onLoad -> Operation Failed."); - - if (alreadyCalledBack === false) { - alreadyCalledBack = true; - callBackFunction(err); - } - return; - } - } - } - catch (err) { - logger.write("[ERROR] initialize -> onLoad -> err = " + err.message); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function addReport() { - - if (FULL_LOG === true) { logger.write("[INFO] initialize -> addReport -> Entering function."); } - - loadCount++; + addCount++; let key; - if (pDataDependenciesConfig[i].dataSetSection === "Month") { - key = pDataDependenciesConfig[i].devTeam + "-" + pDataDependenciesConfig[i].bot + "-" + pDataDependenciesConfig[i].process + "-" + pDataDependenciesConfig[i].dataSetVersion + "-" + pYear + "-" + pMonth; - } else { - key = pDataDependenciesConfig[i].devTeam + "-" + pDataDependenciesConfig[i].bot + "-" + pDataDependenciesConfig[i].process + "-" + pDataDependenciesConfig[i].dataSetVersion; - } + key = pDataDependenciesConfig[i].devTeam + "-" + pDataDependenciesConfig[i].bot + "-" + pDataDependenciesConfig[i].product + "-" + pDataDependenciesConfig[i].dataSet + "-" + pDataDependenciesConfig[i].dataSetVersion; thisObject.keys.push(key); - thisObject.statusReports.set(key, statusReportModule); + thisObject.dataSets.set(key, dataSetModule); - if (FULL_LOG === true) { logger.write("[INFO] initialize -> addReport -> Report added to Map. -> key = " + key); } + if (FULL_LOG === true) { logger.write("[INFO] initialize -> addDataSet -> DataSet added to Map. -> key = " + key); } - if (loadCount === pDataDependenciesConfig.length) { + if (addCount === pDataDependenciesConfig.length) { if (alreadyCalledBack === false) { callBackFunction(global.DEFAULT_OK_RESPONSE); return; @@ -135,5 +82,4 @@ callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - }; \ No newline at end of file diff --git a/Data Set.js b/Data Set.js new file mode 100644 index 000000000..00de4af72 --- /dev/null +++ b/Data Set.js @@ -0,0 +1,128 @@ +exports.newDataSet = function newDataSet(BOT, DEBUG_MODULE, BLOB_STORAGE, UTILITIES) { + + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + + const MODULE_NAME = "Data Set"; + + let thisObject = { + file: undefined, // Here we have the JSON object representing the file content. + initialize: initialize, + getTextFile: getTextFile, + createTextFile: createTextFile + }; + + let bot = BOT; + let dependencyConfig; + + const logger = DEBUG_MODULE.newDebugLog(); + logger.bot = bot; + + /* Utilities needed. */ + + let utilities = UTILITIES.newUtilities(bot); + + /* Storage account to be used here. */ + + let cloudStorage = BLOB_STORAGE.newBlobStorage(bot); + + return thisObject; + + function initialize(pDependencyConfig, callBackFunction) { + + try { + + logger.fileName = MODULE_NAME + "." + pDependencyConfig.bot + "." + pDependencyConfig.product + "." + pDependencyConfig.dataSet; + + dependencyConfig = pDependencyConfig; + + if (FULL_LOG === true) { logger.write("[INFO] initialize -> Entering function."); } + + initializeStorage(); + + function initializeStorage() { + + cloudStorage.initialize(dependencyConfig, onInizialized); + + function onInizialized(err) { + + if (err.result === global.DEFAULT_OK_RESPONSE.result) { + + if (FULL_LOG === true) { logger.write("[INFO] initialize -> initializeStorage -> onInizialized -> Entering function."); } + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } else { + logger.write("[ERROR] initialize -> initializeStorage -> onInizialized -> err = " + err.message); + callBackFunction(err); + } + } + } + } catch (err) { + logger.write("[ERROR] initialize -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function getTextFile(pFolderPath, pFileName, callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] getTextFile -> Entering function."); } + if (FULL_LOG === true) { logger.write("[INFO] getTextFile -> pFolderPath = " + pFolderPath); } + if (FULL_LOG === true) { logger.write("[INFO] getTextFile -> pFileName = " + pFileName); } + + cloudStorage.getTextFile(pFolderPath, pFileName, onFileReceived); + + function onFileReceived(err, text) { + + if (FULL_LOG === true) { logger.write("[INFO] getTextFile -> onFileReceived -> Entering function."); } + + callBackFunction(err, text); + + } + } + catch (err) { + logger.write("[ERROR] 'getTextFile' -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function createTextFile(pFolderPath, pFileName, pFileContent, callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write("[INFO] createTextFile -> Entering function."); } + if (FULL_LOG === true) { logger.write("[INFO] createTextFile -> pFolderPath = " + pFolderPath); } + if (FULL_LOG === true) { logger.write("[INFO] createTextFile -> pFileName = " + pFileName); } + + let ownerId = dependencyConfig.devTeam + "-" + dependencyConfig.bot + "-" + dependencyConfig.botVersion.major + "-" + dependencyConfig.botVersion.minor + "-" + dependencyConfig.dataSetVersion; + let botId = bot.devTeam + "-" + bot.codeName + "-" + bot.version.major + "-" + bot.version.minor + "-" + bot.dataSetVersion; + + if (ownerId !== botId) { + + let customErr = { + result: global.CUSTOM_FAIL_RESPONSE.result, + message: "Only bots owners of a Data Set can create text files on them." + }; + logger.write("[ERROR] save -> customErr = " + customErr.message); + callBackFunction(customErr); + return; + } + + cloudStorage.createTextFile(pFolderPath, pFileName, pFileContent, onFileCreated); + + function onFileCreated(err) { + + if (FULL_LOG === true) { logger.write("[INFO] createTextFile -> onFileCreated -> Entering function."); } + + callBackFunction(err); + + } + } + catch (err) { + logger.write("[ERROR] 'createTextFile' -> err = " + err.message); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + +}; \ No newline at end of file diff --git a/Project.njsproj b/Project.njsproj index 96598de65..a798d6c02 100644 --- a/Project.njsproj +++ b/Project.njsproj @@ -29,6 +29,9 @@ Code + + Code + Code diff --git a/Trading Bot Process Main Loop.js b/Trading Bot Process Main Loop.js index a4b9de001..8ebfba91f 100644 --- a/Trading Bot Process Main Loop.js +++ b/Trading Bot Process Main Loop.js @@ -71,6 +71,7 @@ const DATASOURCE = require(ROOT_DIR + 'Datasource'); const ASSISTANT = require(ROOT_DIR + 'Assistant'); const STATUS_REPORT = require(ROOT_DIR + 'Status Report'); + const DATA_SET = require(ROOT_DIR + 'Data Set'); const STATUS_DEPENDENCIES = require(ROOT_DIR + 'Status Dependencies'); const DATA_DEPENDENCIES = require(ROOT_DIR + 'Data Dependencies'); @@ -81,7 +82,6 @@ /* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */ let context; - let datasource; let exchangeAPI; let assistant; let userBot; @@ -127,9 +127,9 @@ if (FULL_LOG === true) { logger.write("[INFO] run -> loop -> initializeDataDependencies -> Entering function."); } - dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, DEBUG_MODULE, STATUS_REPORT, BLOB_STORAGE, UTILITIES); + dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, DEBUG_MODULE, DATA_SET, BLOB_STORAGE, UTILITIES); - dataDependencies.initialize(processConfig.dataDependencies, undefined, undefined, onInizialized); + dataDependencies.initialize(processConfig.dataDependencies, onInizialized); function onInizialized(err) { @@ -166,7 +166,7 @@ switch (err.result) { case global.DEFAULT_OK_RESPONSE.result: { logger.write("[INFO] run -> loop -> initializeContext -> onInizialized > Execution finished well. :-)"); - initializeDatasource(); + initializeExchangeAPI(); return; } case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. @@ -184,36 +184,6 @@ } } - function initializeDatasource() { - - if (FULL_LOG === true) { logger.write("[INFO] run -> loop -> initializeDatasource -> Entering function."); } - - datasource = DATASOURCE.newDatasource(bot, DEBUG_MODULE, BLOB_STORAGE, UTILITIES); - datasource.initialize(onInizialized); - - function onInizialized(err) { - - switch (err.result) { - case global.DEFAULT_OK_RESPONSE.result: { - logger.write("[INFO] run -> loop -> initializeDatasource -> onInizialized > Execution finished well. :-)"); - initializeExchangeAPI(); - return; - } - case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. - logger.write("[ERROR] run -> loop -> initializeDatasource -> onInizialized > Retry Later. Requesting Execution Retry."); - nextWaitTime = 'Retry'; - loopControl(nextWaitTime); - return; - } - case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. - logger.write("[ERROR] run -> loop -> initializeDatasource -> onInizialized > Operation Failed. Aborting the process."); - callBackFunction(err); - return; - } - } - } - } - function initializeExchangeAPI() { if (FULL_LOG === true) { logger.write("[INFO] run -> loop -> initializeExchangeAPI -> Entering function."); } @@ -249,7 +219,7 @@ if (FULL_LOG === true) { logger.write("[INFO] run -> loop -> initializeAssistant -> Entering function."); } assistant = ASSISTANT.newAssistant(bot, DEBUG_MODULE); - assistant.initialize(context, exchangeAPI, datasource, onInizialized); + assistant.initialize(context, exchangeAPI, dataDependencies, onInizialized); function onInizialized(err) { @@ -280,12 +250,7 @@ usertBot = USER_BOT_MODULE.newUserBot(bot, DEBUG_MODULE, COMMONS_MODULE); - let platform = { - datasource: datasource, - assistant: assistant - }; - - usertBot.initialize(platform, onInizialized); + usertBot.initialize(assistant, onInizialized); function onInizialized(err) { diff --git a/this.config.json b/this.config.json index 91b0f2026..c96f6c4ec 100644 --- a/this.config.json +++ b/this.config.json @@ -45,5 +45,16 @@ "stopGracefully": "false", "exchangeSimulationMode": "false", "storageConnStringFolder": "Production", - "maxLogLoops": 5 + "maxLogLoops": 5, + "marketRateProvider": { + "devTeam": "AAMasters", + "bot": "AAOlivia", + "botVersion": { + "major": 1, + "minor": 0 + }, + "product": "Candles", + "dataSet": "Multi-Period-Daily", + "dataSetVersion": "dataSet.V1" + } } \ No newline at end of file