diff --git a/Commons.js b/Commons.js new file mode 100644 index 000000000..2a52f8c6f --- /dev/null +++ b/Commons.js @@ -0,0 +1,460 @@ +exports.newCommons = function newCommons(bot, logger, UTILITIES, FILE_STORAGE) { + + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; + + const MODULE_NAME = "Commons"; + + let thisObject = { + validateDataDependencies: validateDataDependencies, + validateOutputDatasets: validateOutputDatasets, + inflateDatafiles: inflateDatafiles, + dataBuildingProcedure: dataBuildingProcedure, + calculationsProcedure: calculationsProcedure, + generateFileContent: generateFileContent, + writeFile: writeFile + }; + + let utilities = UTILITIES.newCloudUtilities(logger); + let fileStorage = FILE_STORAGE.newFileStorage(logger); + + return thisObject; + + function validateDataDependencies(dataDependencies, callBackFunction) { + for (let i = 0; i < dataDependencies.length; i++) { + + let dataDependencyNode = dataDependencies[i].referenceParent + + /* Basic validations to see if we have everything we need. */ + if (dataDependencyNode.parentNode.code.singularVariableName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (dataDependencyNode.parentNode.code.pluralVariableName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (dataDependencyNode.parentNode.record === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Record Definition. Product Definition = " + JSON.stringify(dataDependencyNode.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + } + return true + } + + function validateOutputDatasets(outputDatasets, callBackFunction) { + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + + if (outputDatasetNode.referenceParent === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Output Dataset without Reference Parent. Output Dataset = " + JSON.stringify(outputDatasetNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (outputDatasetNode.referenceParent.parentNode.code.singularVariableName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (outputDatasetNode.referenceParent.parentNode.code.pluralVariableName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (outputDatasetNode.referenceParent.parentNode.record === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Record Definition. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (outputDatasetNode.referenceParent.parentNode.dataBuilding === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Data Building Procedure. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + if (outputDatasetNode.referenceParent.code.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Dataset witn no codeName defined. Product Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + + if (outputDatasetNode.referenceParent.parentNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Dataset not attached to a Product Definition. Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + + if (outputDatasetNode.referenceParent.parentNode.code.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition witn no codeName defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + + if (outputDatasetNode.referenceParent.parentNode.parentNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + + if (outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + + if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Team. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + + if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { + logger.write(MODULE_NAME, "[ERROR] start -> Team witn no codeName defined. Team = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode.parentNode)); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + return + } + } + return true + } + + function inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timePeriod) { + /* + For each dataDependencyNode in our data dependencies, we should have a dataFile containing the records needed as an imput for this process. + What we need to do first is transform those records into JSON objects that can be used by user-defined formulas. + The first step does that but with the not calculated properties, the second step adds the calculated properties. + */ + for (let i = 0; i < dataDependencies.length; i++) { + + let dataFile = dataFiles[i] + let jsonData // Datafile converted into Json objects + let inputData // Includes calculated properties + let singularVariableName // name of the variable for this product + let recordDefinition + + let dataDependencyNode = dataDependencies[i].referenceParent + + recordDefinition = dataDependencyNode.parentNode.record + singularVariableName = dataDependencyNode.parentNode.code.singularVariableName + /* Transform the raw data into JSON objects */ + jsonData = jsonifyDataFile(dataFile, recordDefinition) + /* Add the calculated properties */ + if (dataDependencyNode.parentNode.calculations !== undefined) { + inputData = calculationsProcedure(jsonData, recordDefinition, dataDependencyNode.parentNode.calculations, singularVariableName, timePeriod) + } else { + inputData = jsonData + } + products[dataDependencyNode.parentNode.code.pluralVariableName] = inputData + + /* The main dependency is defined as the first dependency processed. */ + if (mainDependency.records === undefined) { + mainDependency.records = inputData + } + } + } + + function jsonifyDataFile(dataFile, recordDefinition) { + + /* + This function has as an input the raw data on files and creates with it an array of JSON objects + with not calculated properties for later being consumed by Formulas + */ + + let jsonifiedArray = [] + + for (let i = 0; i < dataFile.length; i++) { + + let record = {} + for (let j = 0; j < recordDefinition.properties.length; j++) { + let property = recordDefinition.properties[j] + if (property.code.isCalculated !== true) { + record[property.code.codeName] = dataFile[i][j] + } + } + + jsonifiedArray.push(record); + } + + return jsonifiedArray + } + + function calculationsProcedure(jsonArray, recordDefinition, calculationsProcedure, variableName, timePeriod) { + + /* + This function has as an input an array of JSON objects, and it adds calculated properties to + complete the set of properties that will be available for Formulas. + */ + + let system = { // These are the available system variables to be used in User Code and Formulas + timePeriod: timePeriod, + ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS + } + let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. + let results = [] + + /* This is Initialization Code */ + if (calculationsProcedure.initialization !== undefined) { + if (calculationsProcedure.initialization.code !== undefined) { + try { + eval(calculationsProcedure.initialization.code.code) + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> initialization -> Error executing User Code. Error = " + err.stack) + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> initialization -> Error executing User Code. Code = " + calculationsProcedure.initialization.code.code); + throw ("Error Executing User Code.") + } + } + } + + /* This is Initialization Code */ + if (calculationsProcedure.loop !== undefined) { + if (calculationsProcedure.loop.code !== undefined) { + for (let index = 0; index < jsonArray.length; index++) { + + let product = {} + product[variableName] = jsonArray[index] + + /* This is Loop Code */ + try { + eval(calculationsProcedure.loop.code.code) + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> Error executing User Code. Error = " + err.stack) + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> Error executing User Code. product = " + JSON.stringify(product)) + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> Error executing User Code. Code = " + calculationsProcedure.loop.code.code); + throw ("Error Executing User Code.") + } + + /* For each calculated property we apply its formula*/ + for (let j = 0; j < recordDefinition.properties.length; j++) { + let property = recordDefinition.properties[j] + if (property.code.isCalculated === true) { + if (property.formula !== undefined) { + if (property.formula.code !== undefined) { + try { + let newValue = eval(property.formula.code) + let currentRecord = product[variableName] + currentRecord[property.code.codeName] = newValue + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) + logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Code = " + property.formula.code); + throw ("Error Executing User Code.") + } + } + } + } + } + + /* Adding the new element to the resulting array */ + results.push(product[variableName]); + } + } + } + return results + } + + function dataBuildingProcedure(products, mainDependency, recordDefinition, dataBuildingProcedure, variableName, timePeriod, resultsWithIrregularPeriods) { + + /* + This function has as an input the products object, with all the information + of all products calculated so far by the process. Based on that information + the function will evaluate user supplied code and formulas in order to build + a new set of information. + */ + + let system = { // These are the available system variables to be used in User Code and Formulas + timePeriod: timePeriod, + ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS + } + let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. + let results = [] + + /* This is Initialization Code */ + if (dataBuildingProcedure.initialization !== undefined) { + if (dataBuildingProcedure.initialization.code !== undefined) { + try { + eval(dataBuildingProcedure.initialization.code.code) + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> initialization -> Error executing User Code. Error = " + err.stack) + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> initialization -> Error executing User Code. Code = " + dataBuildingProcedure.initialization.code.code); + throw ("Error Executing User Code.") + } + } + } + + /* This is Initialization Code */ + if (dataBuildingProcedure.loop !== undefined) { + if (dataBuildingProcedure.loop.code !== undefined) { + for (let index = 0; index < mainDependency.records.length; index++) { + + let record = { + previous: mainDependency.records[index - 1], + current: mainDependency.records[index], + next: mainDependency.records[index + 1] + } + + let product = {} + product[variableName] = {} + + /* This is Loop Code */ + try { + eval(dataBuildingProcedure.loop.code.code) + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> Error executing User Code. Error = " + err.stack) + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> Error executing User Code. product = " + JSON.stringify(product)) + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> Error executing User Code. Code = " + dataBuildingProcedure.loop.code.code); + throw ("Error Executing User Code.") + } + + /* For each non-calculated property we apply its formula*/ + for (let j = 0; j < recordDefinition.properties.length; j++) { + let property = recordDefinition.properties[j] + if (property.code.isCalculated !== true) { + if (property.formula !== undefined) { + if (property.formula.code !== undefined) { + try { + let newValue = eval(property.formula.code) + let currentRecord = product[variableName] + currentRecord[property.code.codeName] = newValue + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) + logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. Code = " + property.formula.code); + throw ("Error Executing User Code.") + } + } + } + } + } + + /* If within the code the new data structure was not yet pushed to the results array, we do it */ + if (resultsWithIrregularPeriods !== true) { + /* Adding the new element to the resulting array */ + results.push(product[variableName]); + } + } + } + } + return results + } + + function generateFileContent(records, recordDefinition, resultsWithIrregularPeriods, processingDailyFiles, callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> generateFileContent -> Entering function."); } + + let fileContent = ""; + let recordSeparator = ""; + + for (let i = 0; i < records.length; i++) { + let record = records[i]; + + if (processingDailyFiles === true) { + + if (resultsWithIrregularPeriods === true) { + /* + Here we have an special problem that occurs when an object spans several time peridos. If not taken care of + it can happen that the object gets splitted between 2 days, which we dont want since it would loose some of + its properties. + + To solve this issue, we wont save objects which ends at the last candle of the day, because we will save it + at the next day, in whole, even if it starts in the previous day. + */ + + let lastInstantOdDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1; + + if (record.end < currentDay.valueOf() - 1) { continue; } + if (record.end === lastInstantOdDay) { continue; } + } else { + /* + The normal situation is that objects are respecting the time size of its main dependency, in which case we only + care of not adding objects of the previous day we are currently processing (while on daily files) + */ + if (record.end < currentDay.valueOf()) { continue; } + } + } + + fileContent = fileContent + recordSeparator + '[' + let propertySeparator = "" + for (let j = 0; j < recordDefinition.properties.length; j++) { + let property = recordDefinition.properties[j] + if (property.code.isCalculated !== true) { + fileContent = fileContent + propertySeparator + if (property.code.isNumeric !== true) { + fileContent = fileContent + '"' + } + fileContent = fileContent + record[property.code.codeName] + if (property.code.isNumeric !== true) { + fileContent = fileContent + '"' + } + if (propertySeparator === "") { propertySeparator = ","; } + } + } + fileContent = fileContent + ']' + if (recordSeparator === "") { recordSeparator = ","; } + } + fileContent = "[" + fileContent + "]"; + return fileContent + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> generateFileContent -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, outputPeriodLabel, callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeFile -> Entering function."); } + + let market = global.MARKET; + let fileName = '' + market.assetA + '_' + market.assetB + '.json'; + let dateForPath = '' + + if (processingDailyFiles === true) { + dateForPath = "/" + currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); + } + + let filePathRoot = contextSummary.devTeam + "/" + contextSummary.bot + "." + contextSummary.botVersion.major + "." + contextSummary.botVersion.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + contextSummary.dataSetVersion; + let filePath = filePathRoot + "/Output/" + contextSummary.product + "/" + contextSummary.dataset + "/" + outputPeriodLabel + dateForPath; + filePath += '/' + fileName + + fileStorage.createTextFile(contextSummary.devTeam, filePath, fileContent + '\n', onFileCreated); + + function onFileCreated(err) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeFile -> onFileCreated -> Entering function."); } + if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeFile -> onFileCreated -> fileContent = " + fileContent); } + + if (err.result !== global.DEFAULT_OK_RESPONSE.result) { + + logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> err = " + err.stack); + logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> filePath = " + filePath); + logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> market = " + market.assetA + "_" + market.assetB); + + callBackFunction(err); + return; + + } + + anotherFileWritten(); + + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } +} + diff --git a/IndicatorBot.js b/IndicatorBot.js index ab38cd4c0..117d8fe30 100644 --- a/IndicatorBot.js +++ b/IndicatorBot.js @@ -6,7 +6,7 @@ const MODULE_NAME = "Indicator Bot"; - thisObject = { + let thisObject = { initialize: initialize, start: start }; @@ -14,6 +14,9 @@ let utilities = UTILITIES.newCloudUtilities(logger); let fileStorage = FILE_STORAGE.newFileStorage(logger); + const COMMONS = require('./Commons.js'); + let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE); + return thisObject; function initialize(callBackFunction) { @@ -33,15 +36,14 @@ } } - function start(dataFiles, timePeriod, outputPeriodLabel, currentDay, startDate, endDate, interExecutionMemory, callBackFunction) { + function start(dataFiles, timePeriod, outputPeriodLabel, currentDay, interExecutionMemory, callBackFunction) { try { if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } - let market = global.MARKET; let products = {} - let mainDependency + let mainDependency = {} let processingDailyFiles if (currentDay !== undefined) { @@ -50,60 +52,17 @@ processingDailyFiles = false } - /* The first phase is about transforming the inputs into a format that can be used to apply the user defined code. */ - let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies - for (let i = 0; i < dataDependencies.length; i++) { + /* The first phase here is about checking that we have everything we need at the definition level. */ + let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return } - let dataDependencyNode = dataDependencies[i].referenceParent - let dataFile = dataFiles[i] - let jsonData // Datafile converted into Json objects - let inputData // Includes calculated properties - let singularVariableName // name of the variable for this product - let recordDefinition - - /* - For each dataDependencyNode in our data dependencies, we should have da dataFile containing the records needed as an imput for this process. - What we need to do first is transform those records into JSON objects that can be used by user-defined formulas. - The first step does that but with the not calculated properties, the second step adds the calculated properties. - */ - - /* Basic validations to see if we have everything we need. */ - if (dataDependencyNode.parentNode.code.singularVariableName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (dataDependencyNode.parentNode.code.pluralVariableName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (dataDependencyNode.parentNode.record === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Record Definition. Product Definition = " + JSON.stringify(dataDependencyNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - recordDefinition = dataDependencyNode.parentNode.record - singularVariableName = dataDependencyNode.parentNode.code.singularVariableName - /* Transform the raw data into JSON objects */ - jsonData = jsonifyDataFile(dataFile, recordDefinition) - /* Add the calculated properties */ - if (dataDependencyNode.parentNode.calculations !== undefined) { - inputData = calculationsProcedure(jsonData, recordDefinition, dataDependencyNode.parentNode.calculations, singularVariableName, timePeriod) - } else { - inputData = jsonData - } - products[dataDependencyNode.parentNode.code.pluralVariableName] = inputData - - /* The main dependency is defined as the first dependency processed. */ - if (mainDependency === undefined) { - mainDependency = inputData - } - } - - /* During the second phase, we need to generate the data of the different products this process produces an output */ let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + + /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timePeriod) + + /* During the third phase, we need to generate the data of the different products this process produces an output */ for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] @@ -118,33 +77,6 @@ For each outputDatasetNode in our process output, we will build the information based on our input products. */ - /* Basic validations to see if we have everything we need. */ - if (outputDatasetNode.referenceParent === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Output Dataset without Reference Parent. Output Dataset = " + JSON.stringify(outputDatasetNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (outputDatasetNode.referenceParent.parentNode.code.singularVariableName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (outputDatasetNode.referenceParent.parentNode.code.pluralVariableName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (outputDatasetNode.referenceParent.parentNode.record === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Record Definition. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - if (outputDatasetNode.referenceParent.parentNode.dataBuilding === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Data Building Procedure. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - recordDefinition = outputDatasetNode.referenceParent.parentNode.record singularVariableName = outputDatasetNode.referenceParent.parentNode.code.singularVariableName @@ -154,18 +86,18 @@ } /* Build the data */ - jsonData = dataBuildingProcedure(products, mainDependency, recordDefinition, outputDatasetNode.referenceParent.parentNode.dataBuilding, singularVariableName, timePeriod, resultsWithIrregularPeriods) + jsonData = commons.dataBuildingProcedure(products, mainDependency, recordDefinition, outputDatasetNode.referenceParent.parentNode.dataBuilding, singularVariableName, timePeriod, resultsWithIrregularPeriods) /* Add the calculated properties */ if (outputDatasetNode.referenceParent.parentNode.calculations !== undefined) { - outputData = calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timePeriod) + outputData = commons.calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timePeriod) } else { outputData = jsonData } products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName] = outputData } - /*At the third and last phase, we will save the new information generated into files corresponding to each output outputDatasetNode.*/ + /*At the fourth and last phase, we will save the new information generated into files corresponding to each output outputDatasetNode.*/ let totalFilesWritten = 0 for (let i = 0; i < outputDatasets.length; i++) { let outputDatasetNode = outputDatasets[i] @@ -173,49 +105,6 @@ let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution. let contextSummary = {} - /* Some very basic validations that we have all the information needed. */ - if (outputDatasetNode.referenceParent.code.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Dataset witn no codeName defined. Product Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - if (outputDatasetNode.referenceParent.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Dataset not attached to a Product Definition. Dataset = " + JSON.stringify(outputDatasetNode.referenceParent)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - if (outputDatasetNode.referenceParent.parentNode.code.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition witn no codeName defined. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - if (outputDatasetNode.referenceParent.parentNode.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - if (outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Team. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - - if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName === undefined) { - logger.write(MODULE_NAME, "[ERROR] start -> Team witn no codeName defined. Team = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode.parentNode)); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - return - } - /* Check Irregular Periods */ if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.code.code.indexOf('results.push') >= 0) { resultsWithIrregularPeriods = true @@ -235,193 +124,10 @@ } contextSummary.dataSetVersion = "dataSet.V1" - let fileContent = generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods) - writeFile(contextSummary, fileContent) + let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, callBackFunction) + commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, outputPeriodLabel, callBackFunction) } - function jsonifyDataFile(dataFile, recordDefinition) { - - /* - This function has as an input the raw data on files and creates with it an array of JSON objects - with not calculated properties for later being consumed by Formulas - */ - - let jsonifiedArray = [] - - for (let i = 0; i < dataFile.length; i++) { - - let record = {} - for (let j = 0; j < recordDefinition.properties.length; j++) { - let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { - record[property.code.codeName] = dataFile[i][j] - } - } - - jsonifiedArray.push(record); - } - - return jsonifiedArray - } - - function calculationsProcedure(jsonArray, recordDefinition, calculationsProcedure, variableName, timePeriod) { - - /* - This function has as an input an array of JSON objects, and it adds calculated properties to - complete the set of properties that will be available for Formulas. - */ - - let system = { // These are the available system variables to be used in User Code and Formulas - timePeriod: timePeriod, - ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS - } - let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. - let results = [] - - /* This is Initialization Code */ - if (calculationsProcedure.initialization !== undefined) { - if (calculationsProcedure.initialization.code !== undefined) { - try { - eval(calculationsProcedure.initialization.code.code) - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> initialization -> Error executing User Code. Error = " + err.stack) - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> initialization -> Error executing User Code. Code = " + calculationsProcedure.initialization.code.code); - throw ("Error Executing User Code.") - } - } - } - - /* This is Initialization Code */ - if (calculationsProcedure.loop !== undefined) { - if (calculationsProcedure.loop.code !== undefined) { - for (let index = 0; index < jsonArray.length; index++) { - - let product = {} - product[variableName] = jsonArray[index] - - /* This is Loop Code */ - try { - eval(calculationsProcedure.loop.code.code) - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> Error executing User Code. Error = " + err.stack) - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> Error executing User Code. product = " + JSON.stringify(product)) - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> Error executing User Code. Code = " + calculationsProcedure.loop.code.code); - throw ("Error Executing User Code.") - } - - /* For each calculated property we apply its formula*/ - for (let j = 0; j < recordDefinition.properties.length; j++) { - let property = recordDefinition.properties[j] - if (property.code.isCalculated === true) { - if (property.formula !== undefined) { - if (property.formula.code !== undefined) { - try { - let newValue = eval(property.formula.code) - let currentRecord = product[variableName] - currentRecord[property.code.codeName] = newValue - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) - logger.write(MODULE_NAME, "[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Code = " + property.formula.code); - throw ("Error Executing User Code.") - } - } - } - } - } - - /* Adding the new element to the resulting array */ - results.push(product[variableName]); - } - } - } - return results - } - - function dataBuildingProcedure(products, mainDependency, recordDefinition, dataBuildingProcedure, variableName, timePeriod, resultsWithIrregularPeriods) { - - /* - This function has as an input the products object, with all the information - of all products calculated so far by the process. Based on that information - the function will evaluate user supplied code and formulas in order to build - a new set of information. - */ - - let system = { // These are the available system variables to be used in User Code and Formulas - timePeriod: timePeriod, - ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS - } - let variable = {} // This is the structure where the user will define its own variables that will be shared across different code blocks and formulas. - let results = [] - - /* This is Initialization Code */ - if (dataBuildingProcedure.initialization !== undefined) { - if (dataBuildingProcedure.initialization.code !== undefined) { - try { - eval(dataBuildingProcedure.initialization.code.code) - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> initialization -> Error executing User Code. Error = " + err.stack) - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> initialization -> Error executing User Code. Code = " + dataBuildingProcedure.initialization.code.code); - throw ("Error Executing User Code.") - } - } - } - - /* This is Initialization Code */ - if (dataBuildingProcedure.loop !== undefined) { - if (dataBuildingProcedure.loop.code !== undefined) { - for (let index = 0; index < mainDependency.length; index++) { - - let record = { - previous: mainDependency[index - 1], - current: mainDependency[index], - next: mainDependency[index + 1] - } - - let product = {} - product[variableName] = {} - - /* This is Loop Code */ - try { - eval(dataBuildingProcedure.loop.code.code) - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> Error executing User Code. Error = " + err.stack) - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> Error executing User Code. product = " + JSON.stringify(product)) - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> Error executing User Code. Code = " + dataBuildingProcedure.loop.code.code); - throw ("Error Executing User Code.") - } - - /* For each non-calculated property we apply its formula*/ - for (let j = 0; j < recordDefinition.properties.length; j++) { - let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { - if (property.formula !== undefined) { - if (property.formula.code !== undefined) { - try { - let newValue = eval(property.formula.code) - let currentRecord = product[variableName] - currentRecord[property.code.codeName] = newValue - } catch (err) { - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. Error = " + err.stack) - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. product = " + JSON.stringify(product)) - logger.write(MODULE_NAME, "[ERROR] dataBuildingProcedure -> loop -> formula -> Error executing User Code. Code = " + property.formula.code); - throw ("Error Executing User Code.") - } - } - } - } - } - - /* If within the code the new data structure was not yet pushed to the results array, we do it */ - if (resultsWithIrregularPeriods !== true) { - /* Adding the new element to the resulting array */ - results.push(product[variableName]); - } - } - } - } - return results - } function anotherFileWritten() { totalFilesWritten++ @@ -429,123 +135,6 @@ callBackFunction(global.DEFAULT_OK_RESPONSE); } } - - function generateFileContent(records, recordDefinition, resultsWithIrregularPeriods) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> generateFileContent -> Entering function."); } - - let fileContent = ""; - let recordSeparator = ""; - - for (let i = 0; i < records.length; i++) { - let record = records[i]; - - if (processingDailyFiles === true) { - - if (resultsWithIrregularPeriods === true) { - /* - Here we have an special problem that occurs when an object spans several time peridos. If not taken care of - it can happen that the object gets splitted between 2 days, which we dont want since it would loose some of - its properties. - - To solve this issue, we wont save objects which ends at the last candle of the day, because we will save it - at the next day, in whole, even if it starts in the previous day. - */ - - let lastInstantOdDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1; - - if (record.end < currentDay.valueOf() - 1) { continue; } - if (record.end === lastInstantOdDay) { continue; } - } else { - /* - The normal situation is that objects are respecting the time size of its main dependency, in which case we only - care of not adding objects of the previous day we are currently processing (while on daily files) - */ - if (record.end < currentDay.valueOf()) { continue; } - } - } - - fileContent = fileContent + recordSeparator + '[' - let propertySeparator = "" - for (let j = 0; j < recordDefinition.properties.length; j++) { - let property = recordDefinition.properties[j] - if (property.code.isCalculated !== true) { - fileContent = fileContent + propertySeparator - if (property.code.isNumeric !== true) { - fileContent = fileContent + '"' - } - fileContent = fileContent + record[property.code.codeName] - if (property.code.isNumeric !== true) { - fileContent = fileContent + '"' - } - if (propertySeparator === "") { propertySeparator = ","; } - } - } - fileContent = fileContent + ']' - if (recordSeparator === "") { recordSeparator = ","; } - } - fileContent = "[" + fileContent + "]"; - return fileContent - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> generateFileContent -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - - function writeFile(contextSummary, fileContent) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeFile -> Entering function."); } - - let fileName = '' + market.assetA + '_' + market.assetB + '.json'; - let dateForPath = '' - - if (processingDailyFiles === true) { - dateForPath = "/" + currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2); - } - - let filePathRoot = contextSummary.devTeam + "/" + contextSummary.bot + "." + contextSummary.botVersion.major + "." + contextSummary.botVersion.minor + "/" + global.CLONE_EXECUTOR.codeName + "." + global.CLONE_EXECUTOR.version + "/" + global.EXCHANGE_NAME + "/" + contextSummary.dataSetVersion; - let filePath = filePathRoot + "/Output/" + contextSummary.product + "/" + contextSummary.dataset + "/" + outputPeriodLabel + dateForPath; - filePath += '/' + fileName - - fileStorage.createTextFile(contextSummary.devTeam, filePath, fileContent + '\n', onFileCreated); - - function onFileCreated(err) { - - try { - - if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeFile -> onFileCreated -> Entering function."); } - if (LOG_FILE_CONTENT === true) { logger.write(MODULE_NAME, "[INFO] start -> writeFile -> onFileCreated -> fileContent = " + fileContent); } - - if (err.result !== global.DEFAULT_OK_RESPONSE.result) { - - logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> err = " + err.stack); - logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> filePath = " + filePath); - logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> market = " + market.assetA + "_" + market.assetB); - - callBackFunction(err); - return; - - } - - anotherFileWritten(); - - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> onFileCreated -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } - } - catch (err) { - logger.write(MODULE_NAME, "[ERROR] start -> writeFile -> err = " + err.stack); - callBackFunction(global.DEFAULT_FAIL_RESPONSE); - } - } } catch (err) { logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); diff --git a/IndicatorBotProcessMainLoop.js b/IndicatorBotProcessMainLoop.js index 55a4ba13a..962719483 100644 --- a/IndicatorBotProcessMainLoop.js +++ b/IndicatorBotProcessMainLoop.js @@ -412,12 +412,12 @@ switch (processConfig.framework.name) { case 'Multi-Period-Market': { - processFramework = MULTI_PERIOD_MARKET.newMultiPeriodMarket(bot, logger, COMMONS_MODULE, UTILITIES, USER_BOT_MODULE, COMMONS_MODULE, FILE_STORAGE); + processFramework = MULTI_PERIOD_MARKET.newMultiPeriodMarket(bot, logger, UTILITIES, USER_BOT_MODULE, FILE_STORAGE); intitializeProcessFramework(); break; } case 'Multi-Period-Daily': { - processFramework = MULTI_PERIOD_DAILY.newMultiPeriodDaily(bot, logger, COMMONS_MODULE, UTILITIES, USER_BOT_MODULE, COMMONS_MODULE, FILE_STORAGE); + processFramework = MULTI_PERIOD_DAILY.newMultiPeriodDaily(bot, logger, UTILITIES, USER_BOT_MODULE, FILE_STORAGE); intitializeProcessFramework(); break; } diff --git a/MultiPeriodDaily.js b/MultiPeriodDaily.js index fd00ee330..898c092aa 100644 --- a/MultiPeriodDaily.js +++ b/MultiPeriodDaily.js @@ -1,4 +1,4 @@ -exports.newMultiPeriodDaily = function newMultiPeriodDaily(bot, logger, COMMONS, UTILITIES, USER_BOT_MODULE, COMMONS_MODULE, FILE_STORAGE) { +exports.newMultiPeriodDaily = function newMultiPeriodDaily(bot, logger, UTILITIES, USER_BOT_MODULE, FILE_STORAGE) { const FULL_LOG = true; const LOG_FILE_CONTENT = false; @@ -593,8 +593,6 @@ timePeriod, outputPeriodLabel, bot.multiPeriodDailyProcessDatetime, - contextVariables.dateBeginOfMarket, - contextVariables.dateEndOfMarket, interExecutionMemoryArray[n], onBotFinished); diff --git a/MultiPeriodMarket.js b/MultiPeriodMarket.js index 8133547bb..2b6c480a3 100644 --- a/MultiPeriodMarket.js +++ b/MultiPeriodMarket.js @@ -1,4 +1,4 @@ -exports.newMultiPeriodMarket = function newMultiPeriodMarket(bot, logger, COMMONS, UTILITIES, USER_BOT_MODULE, COMMONS_MODULE, FILE_STORAGE) { +exports.newMultiPeriodMarket = function newMultiPeriodMarket(bot, logger, UTILITIES, USER_BOT_MODULE, FILE_STORAGE) { const FULL_LOG = true; const LOG_FILE_CONTENT = false; @@ -260,8 +260,6 @@ timePeriod, outputPeriodLabel, undefined, - contextVariables.dateBeginOfMarket, - contextVariables.dateEndOfMarket, undefined, onBotFinished); diff --git a/Project.njsproj b/Project.njsproj index bfe531e65..8d7e2a509 100644 --- a/Project.njsproj +++ b/Project.njsproj @@ -25,6 +25,9 @@ Code + + Code + @@ -46,6 +49,9 @@ Code + + Code + Code diff --git a/TradingBot.js b/TradingBot.js new file mode 100644 index 000000000..6ad3f07e2 --- /dev/null +++ b/TradingBot.js @@ -0,0 +1,166 @@ +exports.newTradingBot = function newTradingBot(bot, logger, USER_BOT_COMMONS, UTILITIES, FILE_STORAGE) { + + const FULL_LOG = true; + const LOG_FILE_CONTENT = false; + const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; + + const MODULE_NAME = "Trading Bot"; + + let thisObject = { + initialize: initialize, + start: start + }; + + let utilities = UTILITIES.newCloudUtilities(logger); + let fileStorage = FILE_STORAGE.newFileStorage(logger); + + const COMMONS = require('./Commons.js'); + let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE); + + return thisObject; + + function initialize(callBackFunction) { + + try { + + logger.fileName = MODULE_NAME; + logger.initialize(); + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); } + + callBackFunction(global.DEFAULT_OK_RESPONSE); + + } catch (err) { + logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } + + function start(multiPeriodDataFiles, timePeriod, outputPeriodLabel, currentDay, interExecutionMemory, callBackFunction) { + + try { + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); } + + let products = {} + let mainDependency = {} + let processingDailyFiles + + if (currentDay !== undefined) { + processingDailyFiles = true + } else { + processingDailyFiles = false + } + + /* The first phase here is about checking that we have everything we need at the definition level. */ + let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies + if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) {return} + + let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets + if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return } + + /* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */ + for (let j = 0; j < global.marketFilesPeriods.length; j++) { + + let mapKey = marketFilesPeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(mapKey) + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Building Dataset for timePeriod = " + mapKey); } + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timePeriod) + } + } + + for (let j = 0; j < global.dailyFilePeriods.length; j++) { + + let mapKey = global.dailyFilePeriods[j][1] + let dataFiles = multiPeriodDataFiles.get(mapKey) + + if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Building Dataset for timePeriod = " + mapKey); } + + if (dataFiles !== undefined) { + commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timePeriod) + } + } + + /* During the third phase, we need to generate the data of the different products this process produces an output */ + for (let i = 0; i < outputDatasets.length; i++) { + + let outputDatasetNode = outputDatasets[i] + let jsonData // Just build data as Json objects + let outputData // Data built as a result of applying user defined code and formulas at the Data Building Procedure + let singularVariableName // Name of the variable for this product + let recordDefinition // Record as defined by the user at the UI + let fileContent // Here we store the contents of the new data built just before writing it to a file. + let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution. + + /* + For each outputDatasetNode in our process output, we will build the information based on our input products. + */ + + recordDefinition = outputDatasetNode.referenceParent.parentNode.record + singularVariableName = outputDatasetNode.referenceParent.parentNode.code.singularVariableName + + /* Check Irregular Periods */ + if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.code.code.indexOf('results.push') >= 0) { + resultsWithIrregularPeriods = true + } + + /* Build the data */ + jsonData = commons.dataBuildingProcedure(products, mainDependency, recordDefinition, outputDatasetNode.referenceParent.parentNode.dataBuilding, singularVariableName, timePeriod, resultsWithIrregularPeriods) + + /* Add the calculated properties */ + if (outputDatasetNode.referenceParent.parentNode.calculations !== undefined) { + outputData = commons.calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timePeriod) + } else { + outputData = jsonData + } + products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName] = outputData + } + + /*At the fourth and last phase, we will save the new information generated into files corresponding to each output outputDatasetNode.*/ + let totalFilesWritten = 0 + for (let i = 0; i < outputDatasets.length; i++) { + let outputDatasetNode = outputDatasets[i] + let outputData = products[outputDatasetNode.referenceParent.parentNode.code.pluralVariableName] + let resultsWithIrregularPeriods // A product will have irregular periods when the User Code inserts new result records at will, in contrast with normal procedure where the platform insert one record per loop execution. + let contextSummary = {} + + /* Check Irregular Periods */ + if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.code.code.indexOf('results.push') >= 0) { + resultsWithIrregularPeriods = true + } + + /* Simplifying the access to basic info */ + + contextSummary.dataset = outputDatasetNode.referenceParent.code.codeName + contextSummary.product = outputDatasetNode.referenceParent.parentNode.code.codeName + contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.code.codeName + contextSummary.devTeam = outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.code.codeName + + /* This stuff is still hardcoded and unresolved. */ + contextSummary.botVersion = { + "major": 1, + "minor": 0 + } + contextSummary.dataSetVersion = "dataSet.V1" + + let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, callBackFunction) + commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, outputPeriodLabel, callBackFunction) + } + + + function anotherFileWritten() { + totalFilesWritten++ + if (totalFilesWritten === outputDatasets.length) { + callBackFunction(global.DEFAULT_OK_RESPONSE); + } + } + } + catch (err) { + logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); + } + } +}; +