Merge remote-tracking branch 'TaskServer/develop' into develop
This commit is contained in:
commit
a34b4a0cc5
53 files changed
+17601
No files matched your search
@@ -0,0 +1,14 @@
|
||||
.vs/**
|
||||
obj/**
|
||||
|
||||
bin/**
|
||||
|
||||
*.config
|
||||
*.log
|
||||
*.cache
|
||||
.env*
|
||||
|
||||
\.vscode/
|
||||
Project.njsproj
|
||||
coverage/
|
||||
.nyc_output/
|
||||
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
.vs/*
|
||||
obj/*
|
||||
|
||||
bin/*
|
||||
|
||||
*.config
|
||||
*.log
|
||||
*.cache
|
||||
node_modules/*
|
||||
exchange/Auth0.js
|
||||
.env*
|
||||
|
||||
\.vscode/
|
||||
Project.njsproj
|
||||
coverage/*
|
||||
.nyc_output/**
|
||||
@@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team using the online form in the contacts page of the superalgos.org website. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -0,0 +1,9 @@
|
||||
# Contributing
|
||||
|
||||
We are thrilled you are interested in contributing to this repository. Your help may be essential to the project's success and we want you in!
|
||||
|
||||
Superalgos is a collective project, currently at a very early stage. We not only welcome contributions but may even choose to incentivize relevant contributions via the project's native crypto token.
|
||||
|
||||
Please read the [Join the Dev Team]( https://superalgos.org/developers-join-dev-team.shtml) page in our website to learn more about how you may become a relevant member of the Community and even make a living out of contributing code to the project.
|
||||
|
||||
Contributions are deemed _voluntary work_, irrespective of the incentives that may be in place, and are subject to the conditions described in the LICENSE
|
||||
@@ -0,0 +1,85 @@
|
||||
|
||||
exports.newCloudUtilities = function newCloudUtilities(logger) {
|
||||
|
||||
const FULL_LOG = true;
|
||||
|
||||
const MODULE_NAME = "Cloud Utilities";
|
||||
|
||||
let utilities = {
|
||||
pad: pad,
|
||||
createFolderIfNeeded: createFolderIfNeeded,
|
||||
calculatePresicion: calculatePresicion
|
||||
};
|
||||
|
||||
return utilities;
|
||||
|
||||
function createFolderIfNeeded(path, azureFileStorage, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] createFolderIfNeeded -> Entering function."); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] createFolderIfNeeded -> path = " + path); }
|
||||
|
||||
let splittedPath = path.split("/");
|
||||
|
||||
let partialPath = '';
|
||||
let separator = "";
|
||||
|
||||
let i = 0;
|
||||
loop();
|
||||
|
||||
function loop() {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] createFolderIfNeeded -> path = " + path); }
|
||||
|
||||
partialPath = partialPath + separator + splittedPath[i];
|
||||
separator = "/";
|
||||
|
||||
i++;
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] createFolderIfNeeded -> partialPath = " + partialPath); }
|
||||
|
||||
azureFileStorage.createFolder(partialPath, checkLoop);
|
||||
|
||||
function checkLoop(err) {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] createFolderIfNeeded -> loop -> checkLoop -> Entering function."); }
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] createFolderIfNeeded -> loop -> checkLoop -> err = "+ err.stack);
|
||||
callBackFunction(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (i === splittedPath.length) {
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} else {
|
||||
|
||||
loop();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] createFolderIfNeeded -> Error = " + err.message);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function calculatePresicion(number) {
|
||||
|
||||
for (let i = -10; i <= 10; i++) {
|
||||
if (number < Math.pow(10, i)) {
|
||||
return Math.pow(10, i - 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad("0" + str, max) : str;
|
||||
}
|
||||
}
|
||||
+572
@@ -0,0 +1,572 @@
|
||||
exports.newCommons = function newCommons(bot, logger, UTILITIES, FILE_STORAGE) {
|
||||
|
||||
const FULL_LOG = true;
|
||||
const LOG_FILE_CONTENT = false;
|
||||
|
||||
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]
|
||||
|
||||
/* Basic validations to see if we have everything we need. */
|
||||
if (dataDependencyNode.referenceParent.parentNode.config.singularVariableName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Single Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.referenceParent.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
if (dataDependencyNode.referenceParent.parentNode.config.pluralVariableName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Plural Variable Name defined. Product Definition = " + JSON.stringify(dataDependencyNode.referenceParent.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
if (dataDependencyNode.referenceParent.parentNode.record === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Product Definition without a Record Definition. Product Definition = " + JSON.stringify(dataDependencyNode.referenceParent.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 === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Dataset not a child of a Product Definition. Dataset = " + JSON.stringify(outputDatasetNode.referenceParent));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
if (outputDatasetNode.referenceParent.parentNode.config.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.config.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, "[WARN] start -> Product Definition " + outputDatasetNode.referenceParent.parentNode.name + " without a Data Building Procedure. Product Definition Name = " + outputDatasetNode.referenceParent.parentNode.name);
|
||||
}
|
||||
if (outputDatasetNode.referenceParent.config.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.config.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
|
||||
}
|
||||
|
||||
let botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Indicator Bot')
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Trading Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Learning Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = ");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (botNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
let dataMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Data Mine')
|
||||
if (dataMineNode === undefined) {
|
||||
let tradingMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Trading Mine')
|
||||
if (tradingMineNode === undefined) {
|
||||
let learningMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Learning Mine')
|
||||
if (learningMineNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine, Trading Mine or Learning Mine.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
} else {
|
||||
if (tradingMineNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Learning Mine witn no codeName defined.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tradingMineNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Trading Mine witn no codeName defined.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (dataMineNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, timeFrame) {
|
||||
/*
|
||||
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
|
||||
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]
|
||||
dataFile = dataFiles.get(dataDependencyNode.id)
|
||||
|
||||
if (dataFile === undefined) { continue } // When a datafile is not found it might be because we are processing market or daily and at the dependency array there are both types mixed up.
|
||||
|
||||
recordDefinition = dataDependencyNode.referenceParent.parentNode.record
|
||||
singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
|
||||
/* Transform the raw data into JSON objects */
|
||||
jsonData = jsonifyDataFile(dataFile, recordDefinition)
|
||||
/* Add the calculated properties */
|
||||
if (dataDependencyNode.referenceParent.parentNode.calculations !== undefined) {
|
||||
inputData = calculationsProcedure(jsonData, recordDefinition, dataDependencyNode.referenceParent.parentNode.calculations, singularVariableName, timeFrame)
|
||||
} else {
|
||||
inputData = jsonData
|
||||
}
|
||||
products[dataDependencyNode.referenceParent.parentNode.config.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 = []
|
||||
let previous;
|
||||
|
||||
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.config.isCalculated !== true) {
|
||||
record[property.config.codeName] = dataFile[i][j]
|
||||
}
|
||||
}
|
||||
|
||||
record.previous = previous;
|
||||
jsonifiedArray.push(record);
|
||||
previous = record;
|
||||
}
|
||||
|
||||
return jsonifiedArray
|
||||
}
|
||||
|
||||
function calculationsProcedure(jsonArray, recordDefinition, calculationsProcedure, variableName, timeFrame) {
|
||||
|
||||
/*
|
||||
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
|
||||
timeFrame: timeFrame,
|
||||
ONE_DAY_IN_MILISECONDS: global.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.javascriptCode !== undefined) {
|
||||
try {
|
||||
eval(calculationsProcedure.initialization.javascriptCode.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.javascriptCode.code);
|
||||
throw ("Error Executing User Code.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This is Initialization Code */
|
||||
if (calculationsProcedure.loop !== undefined) {
|
||||
if (calculationsProcedure.loop.javascriptCode !== undefined) {
|
||||
for (let index = 0; index < jsonArray.length; index++) {
|
||||
|
||||
let product = {}
|
||||
product[variableName] = jsonArray[index]
|
||||
|
||||
/* This is Loop Code */
|
||||
try {
|
||||
eval(calculationsProcedure.loop.javascriptCode.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.javascriptCode.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.config.isCalculated === true) {
|
||||
if (property.formula !== undefined) {
|
||||
if (property.formula.code !== undefined) {
|
||||
try {
|
||||
let newValue = eval(property.formula.code)
|
||||
let currentRecord = product[variableName]
|
||||
currentRecord[property.config.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,
|
||||
productName,
|
||||
timeFrame,
|
||||
timeFrameLabel,
|
||||
resultsWithIrregularPeriods,
|
||||
interExecutionMemory,
|
||||
processingDailyFiles,
|
||||
currentDay
|
||||
) {
|
||||
|
||||
/*
|
||||
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 lastInstantOfTheDay
|
||||
let yesterday = {}
|
||||
let system = { // These are the available system variables to be used in User Code and Formulas
|
||||
timeFrame: timeFrame,
|
||||
ONE_DAY_IN_MILISECONDS: global.ONE_DAY_IN_MILISECONDS,
|
||||
ONE_MIN_IN_MILISECONDS: global.ONE_MIN_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 = []
|
||||
|
||||
/*
|
||||
We are going to build a map structure so that users can easily get the current object of
|
||||
each data dependency based on the current element of the main data dependency.
|
||||
We will use as key for these maps the begin and end property, meaning only elements with exactly
|
||||
the same begin and end property will be matched.
|
||||
*/
|
||||
|
||||
const PROPERTIES_COUNT = Object.keys(products).length
|
||||
|
||||
let dataDependencies = {}
|
||||
if (PROPERTIES_COUNT > 1) { // only if we have secondary data dependencies.
|
||||
for (let i = 1; i < PROPERTIES_COUNT; i++) {
|
||||
let dataDependecyArray = Object.values(products)[i]
|
||||
let dataDependencyName = Object.keys(products)[i]
|
||||
let dataDependencyMap = new Map()
|
||||
for (let j = 0; j < dataDependecyArray.length; j++) {
|
||||
let record = dataDependecyArray[j]
|
||||
let key = record.begin.toString() + '-' + record.end.toString()
|
||||
dataDependencyMap.set(key, record)
|
||||
}
|
||||
dataDependencies[dataDependencyName] = dataDependencyMap
|
||||
}
|
||||
}
|
||||
|
||||
function getElement(dependencyName, currentRecordPrimaryDataDependency) {
|
||||
let key = currentRecordPrimaryDataDependency.begin.toString() + '-' + currentRecordPrimaryDataDependency.end.toString()
|
||||
return dataDependencies[dependencyName].get(key)
|
||||
}
|
||||
|
||||
/* This is Initialization Code */
|
||||
if (dataBuildingProcedure.initialization !== undefined) {
|
||||
if (dataBuildingProcedure.initialization.javascriptCode !== undefined) {
|
||||
try {
|
||||
eval(dataBuildingProcedure.initialization.javascriptCode.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.javascriptCode.code);
|
||||
throw ("Error Executing User Code.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (processingDailyFiles) {
|
||||
/* Initialization of Last Instance */
|
||||
lastInstantOfTheDay = currentDay.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1;
|
||||
|
||||
if (interExecutionMemory[productName] === undefined) {
|
||||
/* The first time the intialization variables goes to the Inter Execution Memory. */
|
||||
interExecutionMemory[productName] = {}
|
||||
interExecutionMemory[productName].variable = JSON.parse(JSON.stringify(variable))
|
||||
}
|
||||
else {
|
||||
/* We override the initialization, since the valid stuff is already at the Inter Execution Memory */
|
||||
variable = JSON.parse(JSON.stringify(interExecutionMemory[productName].variable))
|
||||
}
|
||||
}
|
||||
|
||||
/* This is Initialization Code */
|
||||
if (dataBuildingProcedure.loop !== undefined) {
|
||||
if (dataBuildingProcedure.loop.javascriptCode !== undefined) {
|
||||
let lastRecord
|
||||
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]
|
||||
}
|
||||
lastRecord = record
|
||||
let product = {}
|
||||
product[variableName] = {}
|
||||
|
||||
/* This is Loop Code */
|
||||
try {
|
||||
eval(dataBuildingProcedure.loop.javascriptCode.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.javascriptCode.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.config.isCalculated !== true) {
|
||||
if (property.formula !== undefined) {
|
||||
if (property.formula.code !== undefined) {
|
||||
try {
|
||||
let newValue = eval(property.formula.code)
|
||||
let currentRecord = product[variableName]
|
||||
currentRecord[property.config.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]);
|
||||
}
|
||||
|
||||
/* At the last instant of yesterday, we update the yesterday object. */
|
||||
if (processingDailyFiles) {
|
||||
if (record.current.end + 1 === currentDay.valueOf()) {
|
||||
yesterday.variable = JSON.parse(JSON.stringify(variable))
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Before returning we need to see if we have to record some of our counters at the interExecutionMemory.
|
||||
To do that, the condition to be met is that this execution must include all candles of the current day.
|
||||
*/
|
||||
if (processingDailyFiles) {
|
||||
if (lastRecord.current.end === lastInstantOfTheDay && yesterday.variable !== undefined) {
|
||||
interExecutionMemory[productName] = {}
|
||||
interExecutionMemory[productName].variable = JSON.parse(JSON.stringify(yesterday.variable))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
function generateFileContent(records, recordDefinition, resultsWithIrregularPeriods, processingDailyFiles, currentDay, 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() + global.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.config.isCalculated !== true) {
|
||||
fileContent = fileContent + propertySeparator
|
||||
if (property.config.isString === true) {
|
||||
fileContent = fileContent + '"'
|
||||
}
|
||||
fileContent = fileContent + record[property.config.codeName]
|
||||
if (property.config.isString === 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, timeFrameLabel, currentDay, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeFile -> Entering function."); }
|
||||
|
||||
let market = bot.market;
|
||||
let fileName = 'Data.json';
|
||||
let dateForPath = ''
|
||||
|
||||
if (processingDailyFiles === true) {
|
||||
dateForPath = "/" + currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2);
|
||||
}
|
||||
|
||||
let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + contextSummary.dataMine + "/" + contextSummary.bot;
|
||||
let filePath = filePathRoot + "/Output/" + contextSummary.product + "/" + contextSummary.dataset + "/" + timeFrameLabel + dateForPath;
|
||||
filePath += '/' + fileName
|
||||
|
||||
fileStorage.createTextFile(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.baseAsset + "_" + market.quotedAsset);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
exports.newDataDependencies = function newDataDependencies(BOT, logger, DATA_SET) {
|
||||
|
||||
const FULL_LOG = true;
|
||||
const LOG_FILE_CONTENT = false;
|
||||
const MODULE_NAME = "Data Dependencies";
|
||||
|
||||
let bot = BOT
|
||||
|
||||
let thisObject = {
|
||||
nodeArray: undefined,
|
||||
dataSetsModulesArray: [],
|
||||
isItADepenency: isItADepenency,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
};
|
||||
|
||||
let filter = new Map()
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(callBackFunction) {
|
||||
try {
|
||||
/* Basic Valdidations */
|
||||
if (bot.processNode.referenceParent.processDependencies !== undefined) {
|
||||
thisObject.nodeArray = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency')
|
||||
if (thisObject.nodeArray.length === 0) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> It is not possible to not have data dependencies at all.");
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
We will filter ourt declared dependencies that are not present in the workspace.
|
||||
This will allow the user to have less Data Mines loaded at the workspace
|
||||
that the ones that a Trading Mine depends on.
|
||||
*/
|
||||
thisObject.nodeArray = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(thisObject.nodeArray)
|
||||
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> It is not possible not to have process dependencies, which means not data dependencies.");
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
if (thisObject.nodeArray.length === 0) {
|
||||
|
||||
// We allow old indicators not to declare their data dependencies.
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Session based dependency filters */
|
||||
if (bot.DEPENDENCY_FILTER !== undefined) {
|
||||
for (let i = 0; i < bot.DEPENDENCY_FILTER.length; i++) {
|
||||
let key = bot.DEPENDENCY_FILTER[i]
|
||||
filter.set(key, true)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
For each dependency declared at the nodeArray, we will initialize a DataSet as part of this initialization process.
|
||||
*/
|
||||
let alreadyCalledBack = false
|
||||
let addCount = 0
|
||||
let skipCount = 0
|
||||
|
||||
/*
|
||||
The current nodeArray that we have includes all the dependencies daclared in the Data Mine | Trading Mine | Learning Mine
|
||||
process dependencies, minus the ones without reference parent, meaning that references Data Mines that are
|
||||
not present at the workspace. From all the remaining dependencies there will be others that we need to
|
||||
filter out, and they are the ones which the user does not have a data product anywhere on the network where
|
||||
this task is running, that is storing the data associated to this dependency. The way we use to filter this
|
||||
out is by analizyng the response of the initialization call to the DataSet module. If it did not fail but
|
||||
it was not initialized, then we will assume this happened because the data could not be located and we will
|
||||
assume the user did this on purpose and we will filter out this dependency by not putting it on this new array.
|
||||
*/
|
||||
let newNodeArray = []
|
||||
|
||||
for (let i = 0; i < thisObject.nodeArray.length; i++) {
|
||||
let dataSetModule = DATA_SET.newDataSet(BOT, logger);
|
||||
dataSetModule.initialize(thisObject.nodeArray[i], onInitilized);
|
||||
|
||||
function onInitilized(err, wasInitialized) {
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err = " + JSON.stringify(err));
|
||||
|
||||
alreadyCalledBack = true;
|
||||
callBackFunction(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wasInitialized === true) {
|
||||
addCount++;
|
||||
newNodeArray.push(thisObject.nodeArray[i])
|
||||
addDataSet()
|
||||
} else {
|
||||
skipCount++
|
||||
}
|
||||
checkIfWeAreDone()
|
||||
}
|
||||
|
||||
function addDataSet() {
|
||||
thisObject.dataSetsModulesArray.push(dataSetModule);
|
||||
}
|
||||
|
||||
function checkIfWeAreDone() {
|
||||
if (addCount + skipCount === thisObject.nodeArray.length) {
|
||||
if (alreadyCalledBack === false) {
|
||||
alreadyCalledBack = true
|
||||
thisObject.nodeArray = newNodeArray
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
for (let i = 0; i < thisObject.dataSetsModulesArray.length; i++) {
|
||||
let dataSetModule = thisObject.dataSetsModulesArray[i]
|
||||
dataSetModule.finalize()
|
||||
}
|
||||
thisObject.dataSetsModulesArray = undefined
|
||||
filter = undefined
|
||||
bot = undefined
|
||||
}
|
||||
|
||||
function isItADepenency(timeFrame, product) {
|
||||
let key = timeFrame + '-' + product
|
||||
|
||||
return filter.get(key)
|
||||
}
|
||||
};
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
exports.newDataSet = function newDataSet(BOT, logger) {
|
||||
|
||||
const MODULE_NAME = "Dataset";
|
||||
|
||||
let bot = BOT;
|
||||
|
||||
let thisObject = {
|
||||
node: undefined,
|
||||
networkNode: undefined,
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
getTextFile: getTextFile,
|
||||
createTextFile: createTextFile
|
||||
};
|
||||
|
||||
/* Storage account to be used here. */
|
||||
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
let fileStorage
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(dataDependency, callBackFunction) {
|
||||
|
||||
try {
|
||||
thisObject.node = dataDependency.referenceParent;
|
||||
logger.fileName = MODULE_NAME
|
||||
|
||||
/* Some very basic validations that we have all the information needed. */
|
||||
if (thisObject.node === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Data Dependency without Reference Parent -> dataDependency = " + JSON.stringify(dataDependency));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
logger.fileName = MODULE_NAME + "." + thisObject.node.type + "." + thisObject.node.name + "." + thisObject.node.id;
|
||||
|
||||
if (thisObject.node.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Dataset witn no codeName defined -> Product Dataset = " + JSON.stringify(thisObject.node));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (thisObject.node.parentNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Dataset not attached to a Product Definition -> Dataset = " + JSON.stringify(thisObject.node));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (thisObject.node.parentNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Product Definition witn no codeName defined -> Product Definition = " + JSON.stringify(thisObject.node.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (thisObject.node.parentNode.parentNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Product Definition not attached to a Bot -> Product Definition = " + JSON.stringify(thisObject.node.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (thisObject.node.parentNode.parentNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Bot witn no codeName defined. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (thisObject.node.parentNode.parentNode.parentNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (thisObject.node.parentNode.parentNode.parentNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(thisObject.node.parentNode.parentNode.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* Now we will see where do we need to fetch this data from. */
|
||||
let network = global.TASK_NETWORK
|
||||
let datasetProductDefinition = thisObject.node.parentNode
|
||||
|
||||
let nodeArray = global.NODE_MESH_TO_PATH_ARRAY(network, datasetProductDefinition.id)
|
||||
let networkNode = global.FIND_NODE_IN_NODE_ARRAY(nodeArray, 'Network Node')
|
||||
|
||||
if (networkNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> Network Node not found.")
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> Initialization Failed because we could not find where the data of this dataset is located within the network. Check the logs for more info.");
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> Could not find where " + datasetProductDefinition.name + " for " + bot.exchange + " " + bot.market.baseAsset + "/" + bot.market.quotedAsset + " is stored within the network.");
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE, false);
|
||||
return
|
||||
}
|
||||
|
||||
/* We found where the data is located on the network. */
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving data from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); }
|
||||
|
||||
fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE, true);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
fileStorage = undefined
|
||||
thisObject.networkNode = undefined
|
||||
bot = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
function getTextFile(pFolderPath, pFileName, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTextFile -> Entering function."); }
|
||||
|
||||
let dataMineCodeName = thisObject.node.parentNode.parentNode.parentNode.config.codeName
|
||||
let botCodeName = thisObject.node.parentNode.parentNode.config.codeName
|
||||
|
||||
let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + dataMineCodeName + "/" + botCodeName;
|
||||
let filePath = filePathRoot + "/Output/" + pFolderPath;
|
||||
filePath += '/' + pFileName
|
||||
|
||||
fileStorage.getTextFile(filePath, onFileReceived);
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] getTextFile -> onFileReceived -> Entering function."); }
|
||||
|
||||
callBackFunction(err, text);
|
||||
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] 'getTextFile' -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function createTextFile(pFolderPath, pFileName, pFileContent, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createTextFile -> Entering function."); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createTextFile -> pFolderPath = " + pFolderPath); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createTextFile -> pFileName = " + pFileName); }
|
||||
|
||||
let ownerId = thisObject.node.dataMine + "-" + thisObject.node.bot
|
||||
let botId = bot.dataMine + "-" + bot.codeName
|
||||
|
||||
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(MODULE_NAME, "[ERROR] save -> customErr = " + customErr.message);
|
||||
callBackFunction(customErr);
|
||||
return;
|
||||
}
|
||||
|
||||
let filePathRoot = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + thisObject.node.dataMine + "/" + thisObject.node.bot;
|
||||
let filePath = filePathRoot + "/Output/" + pFolderPath + '/' + pFileName;
|
||||
|
||||
fileStorage.createTextFile(filePath, pFileContent, onFileCreated);
|
||||
|
||||
function onFileCreated(err) {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] createTextFile -> onFileCreated -> Entering function."); }
|
||||
|
||||
callBackFunction(err);
|
||||
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] 'createTextFile' -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
exports.newDebugLog = function newDebugLog() {
|
||||
|
||||
const MODULE_NAME = "DebugLog";
|
||||
|
||||
let executionDatetime = "D." + global.EXECUTION_DATETIME.getUTCFullYear() +
|
||||
"." + pad(global.EXECUTION_DATETIME.getUTCMonth() + 1, 2) +
|
||||
"." + pad(global.EXECUTION_DATETIME.getUTCDate(), 2) +
|
||||
".T." + pad(global.EXECUTION_DATETIME.getUTCHours(), 2) +
|
||||
"." + pad(global.EXECUTION_DATETIME.getUTCMinutes(), 2) +
|
||||
"." + pad(global.EXECUTION_DATETIME.getUTCSeconds(), 2);
|
||||
|
||||
let messageId = 0;
|
||||
|
||||
let thisObject = {
|
||||
bot: undefined,
|
||||
write: write,
|
||||
newInternalLoop: newInternalLoop,
|
||||
persist: persist, // This method is executed at the end of each Main Loop.
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
};
|
||||
|
||||
let accumulatedLog = "[";
|
||||
let disableLogging;
|
||||
let internalLoopCounter = -1;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pdisableLogging) {
|
||||
|
||||
try {
|
||||
|
||||
disableLogging = pdisableLogging
|
||||
|
||||
} catch (err) {
|
||||
console.log("[ERROR] Debug Log -> initialize -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
persist()
|
||||
}
|
||||
|
||||
function newInternalLoop(pBot, pProcess, date, percentage) {
|
||||
if (percentage === undefined) {
|
||||
percentage = ""
|
||||
} else {
|
||||
percentage = percentage.toFixed(2) + " %"
|
||||
}
|
||||
|
||||
if (date === undefined) { date = thisObject.bot.processDatetime }
|
||||
date = date.getUTCFullYear() + '-' + strPad(date.getUTCMonth() + 1, 2, "0") + '-' + strPad(date.getUTCDate(), 2, "0");
|
||||
|
||||
console.log(new Date().toISOString() + " " + strPad(thisObject.bot.exchange, 20) + " " + strPad(thisObject.bot.market.baseAsset + '/' + thisObject.bot.market.quotedAsset, 10) + " " + strPad(pBot, 30) + " " + strPad(pProcess, 30)
|
||||
+ " Internal Loop # " + strPad(internalLoopCounter + 1, 8) + " " + strPad(date, 30) + " " + strPad(percentage, 10))
|
||||
|
||||
persist();
|
||||
|
||||
function strPad(str, max, fill) {
|
||||
if (fill === undefined) { fill = " " }
|
||||
str = str.toString();
|
||||
return str.length < max ? strPad(fill + str, max) : str;
|
||||
}
|
||||
}
|
||||
|
||||
function persist() {
|
||||
|
||||
/* Here we actually write the content of the in-memory log to a file */
|
||||
|
||||
try {
|
||||
if (accumulatedLog === "[") { return } // nothing to persist at the moment.
|
||||
|
||||
internalLoopCounter++;
|
||||
|
||||
let contentToPersist = accumulatedLog;
|
||||
accumulatedLog = "[";
|
||||
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage();
|
||||
|
||||
let filePath = thisObject.bot.filePathRoot + "/Logs/" + thisObject.bot.process + "/"
|
||||
|
||||
if (thisObject.bot.TRADING_SESSION !== undefined) {
|
||||
filePath = filePath + thisObject.bot.TRADING_SESSION.folderName + "/" + executionDatetime;
|
||||
} else {
|
||||
if (thisObject.bot.LEARNING_SESSION !== undefined) {
|
||||
filePath = filePath + thisObject.bot.LEARNING_SESSION.folderName + "/" + executionDatetime;
|
||||
} else {
|
||||
filePath = filePath + executionDatetime;
|
||||
}
|
||||
}
|
||||
|
||||
if (thisObject.bot.debug.year !== undefined) {
|
||||
|
||||
filePath = filePath + "/" + thisObject.bot.debug.year + "/" + thisObject.bot.debug.month;
|
||||
}
|
||||
|
||||
let fileName;
|
||||
if (internalLoopCounter >= 0) {
|
||||
fileName = "Loop." + pad(thisObject.bot.loopCounter, 8) + "." + pad(internalLoopCounter, 4) + ".json";
|
||||
} else {
|
||||
fileName = "Loop." + pad(thisObject.bot.loopCounter, 8) + ".json";
|
||||
}
|
||||
|
||||
writeLog();
|
||||
|
||||
/* This is the implementation of the mechanism to auto-mantain logs. */
|
||||
if (contentToPersist.indexOf('[ERROR]') < 0 && contentToPersist.indexOf('[PERSIST]') < 0 && contentToPersist.indexOf('[IMPORTANT]') < 0) {
|
||||
thisObject.bot.LOGS_TO_DELETE_QUEUE.push(filePath + '/' + fileName)
|
||||
}
|
||||
if (thisObject.bot.LOGS_TO_DELETE_QUEUE.length > thisObject.bot.DELETE_QUEUE_SIZE) {
|
||||
let fileToDelete = thisObject.bot.LOGS_TO_DELETE_QUEUE[0]
|
||||
thisObject.bot.LOGS_TO_DELETE_QUEUE.splice(0, 1)
|
||||
/* Will delete this file only if it does not contains ERROR inside. */
|
||||
let fileContent = fileStorage.getTextFile(fileToDelete, onGetFile, true)
|
||||
function onGetFile(err, fileContent) {
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
if (fileContent.indexOf("ERROR") < 0) {
|
||||
/* Logs will only be deleted when they contain no ERROR in them. */
|
||||
fileStorage.deleteTextFile(fileToDelete);
|
||||
}
|
||||
} else {
|
||||
fileStorage.deleteTextFile(fileToDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeLog() {
|
||||
|
||||
fileStorage.createTextFile(filePath + '/' + fileName, contentToPersist + '\r\n' + "]", onFileCreated, undefined, true);
|
||||
function onFileCreated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
console.log("[ERROR] DebugLog -> persist -> onInizialized -> onFileCreated -> err = " + err.message);
|
||||
setTimeout(writeLog, 10000); // Lets retry until we make it.
|
||||
return;
|
||||
}
|
||||
contentToPersist = "";
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("[ERROR] DebugLog -> persist -> err = " + err.stack);
|
||||
console.log("[ERROR] DebugLog -> persist -> onInizialized -> contentToPersist = " + contentToPersist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function write(pModule, pMessage) {
|
||||
try {
|
||||
if (disableLogging === true) { return; }
|
||||
|
||||
if (thisObject.bot === undefined) { return; }
|
||||
|
||||
if (pModule === "") { // For debugging purposes
|
||||
console.log(pMessage)
|
||||
}
|
||||
|
||||
let newDate = new Date();
|
||||
newDate = newDate.toISOString();
|
||||
|
||||
messageId++;
|
||||
|
||||
/* When writting one file for all modules we use this. */
|
||||
|
||||
let message = "['" + newDate + "'," + messageId + ",'" + pModule + "','" + pMessage + "']"
|
||||
let logLine = '\r\n' + message;
|
||||
|
||||
if (process.env.CONSOLE_LOG === "true" || message.indexOf("ERROR") > 0) {
|
||||
let key = ''
|
||||
if (thisObject.bot) {
|
||||
key = thisObject.bot.dataMine + '-' + thisObject.bot.codeName + '-' + thisObject.bot.process
|
||||
}
|
||||
console.log('*********** ' + message + ' @ ' + key)
|
||||
}
|
||||
|
||||
accumulatedLog = accumulatedLog + logLine;
|
||||
|
||||
} catch (err) {
|
||||
console.log("[ERROR] DebugLog -> write -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad("0" + str, max) : str;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
exports.newEventsServerClient = function newEventsServerClient(host, port) {
|
||||
|
||||
const MODULE = "Task Server "
|
||||
|
||||
const INFO_LOG = false
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
createEventHandler: createEventHandler,
|
||||
deleteEventHandler: deleteEventHandler,
|
||||
listenToEvent: listenToEvent,
|
||||
stopListening: stopListening,
|
||||
raiseEvent: raiseEvent
|
||||
}
|
||||
|
||||
let eventListeners = new Map()
|
||||
let responseWaiters = new Map()
|
||||
|
||||
let WEB_SOCKETS_CLIENT
|
||||
const WEB_SOCKET = require('ws')
|
||||
|
||||
if (host === undefined) {
|
||||
host = 'localhost'
|
||||
}
|
||||
if (port === undefined) {
|
||||
port = process.env.WEB_SOCKETS_SERVER_PORT
|
||||
}
|
||||
|
||||
let messageCounter = 0
|
||||
let commandCounter = 0
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize(callBackFunction) {
|
||||
|
||||
setuptWebSockets(callBackFunction)
|
||||
|
||||
}
|
||||
|
||||
function setuptWebSockets(callBackFunction) {
|
||||
try {
|
||||
|
||||
if (INFO_LOG === true) {
|
||||
console.log('setuptWebSockets at ' + host + ':' + port)
|
||||
}
|
||||
WEB_SOCKETS_CLIENT = new WEB_SOCKET('ws://' + host + ':' + port )
|
||||
|
||||
WEB_SOCKETS_CLIENT.onerror = err => {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> On connection error -> error = ' + err.stack)
|
||||
console.log('[ERROR] This could mean that the port '+ port +' is taken by some other app running at your system. To resolve this issue, please pick another port number and change it at the .ENV file inside the Superalgos folder AND at the .ENV file inside the TaskServer folder. After that run the app again. ')
|
||||
}
|
||||
WEB_SOCKETS_CLIENT.onopen = () => {
|
||||
try {
|
||||
if (INFO_LOG === true) {
|
||||
console.log('Websocket connection opened.')
|
||||
}
|
||||
|
||||
if (callBackFunction !== undefined) {
|
||||
callBackFunction()
|
||||
}
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> onopen -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
WEB_SOCKETS_CLIENT.onmessage = e => {
|
||||
try {
|
||||
if (INFO_LOG === true) {
|
||||
console.log('Websocket Message Received: ' + e.data.substring(0, 1000))
|
||||
}
|
||||
|
||||
let message = JSON.parse(e.data)
|
||||
|
||||
if (message.action === 'Event Raised') {
|
||||
let key
|
||||
if (message.callerId !== undefined) {
|
||||
key = message.eventHandlerName + '-' + message.eventType + '-' + message.callerId
|
||||
} else {
|
||||
key = message.eventHandlerName + '-' + message.eventType
|
||||
}
|
||||
let handler = eventListeners.get(key)
|
||||
if (handler) {
|
||||
handler.callBack(message)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (message.action === 'Event Server Response') {
|
||||
let handler = responseWaiters.get(message.callerId)
|
||||
if (handler) {
|
||||
handler(message)
|
||||
}
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> onmessage -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
/* Before disconnecting we will forze all eventListeners to stop listening. */
|
||||
const eventListenersArray = [...eventListeners.values()]
|
||||
for (let i = 0; i < eventListenersArray.length; i++) {
|
||||
let handler = eventListenersArray[i]
|
||||
let eventCommand = {
|
||||
action: 'stopListening',
|
||||
eventHandlerName: handler.eventHandlerName,
|
||||
eventType: handler.eventType,
|
||||
callerId: handler.callerId
|
||||
}
|
||||
sendCommand(eventCommand)
|
||||
}
|
||||
WEB_SOCKETS_CLIENT.close();
|
||||
}
|
||||
|
||||
function sendCommand(command, responseCallBack, eventsCallBack) {
|
||||
try {
|
||||
if (command.action === 'listenToEvent') {
|
||||
let key
|
||||
if (command.callerId) {
|
||||
key = command.eventHandlerName + '-' + command.eventType + '-' + command.callerId
|
||||
} else {
|
||||
key = command.eventHandlerName + '-' + command.eventType
|
||||
}
|
||||
let handler = {
|
||||
eventHandlerName: command.eventHandlerName,
|
||||
eventType: command.eventType,
|
||||
callerId: command.callerId,
|
||||
callBack: eventsCallBack
|
||||
}
|
||||
eventListeners.set(key, handler)
|
||||
}
|
||||
if (command.callerId && responseCallBack) {
|
||||
responseWaiters.set(command.callerId, responseCallBack)
|
||||
}
|
||||
|
||||
if (WEB_SOCKETS_CLIENT.readyState === 1) { // 1 means connected and ready.
|
||||
|
||||
WEB_SOCKETS_CLIENT.send("Task Server" + "|*|" + "|*|" + JSON.stringify(command))
|
||||
|
||||
} else {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> sendCommand -> WebSocket message could not be sent because the connection was not ready. Message = ' + JSON.stringify(command))
|
||||
}
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> sendCommand -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
|
||||
function createEventHandler(eventHandlerName, callerId, responseCallBack) {
|
||||
let eventCommand = {
|
||||
action: 'createEventHandler',
|
||||
eventHandlerName: eventHandlerName,
|
||||
callerId: callerId
|
||||
}
|
||||
sendCommand(eventCommand, responseCallBack)
|
||||
}
|
||||
|
||||
function deleteEventHandler(eventHandlerName, callerId, responseCallBack) {
|
||||
let eventCommand = {
|
||||
action: 'deleteEventHandler',
|
||||
eventHandlerName: eventHandlerName,
|
||||
callerId: callerId
|
||||
}
|
||||
sendCommand(eventCommand, responseCallBack)
|
||||
}
|
||||
|
||||
function listenToEvent(eventHandlerName, eventType, extraData, callerId, responseCallBack, eventsCallBack) {
|
||||
let eventCommand = {
|
||||
action: 'listenToEvent',
|
||||
eventHandlerName: eventHandlerName,
|
||||
eventType: eventType,
|
||||
extraData: extraData,
|
||||
callerId: callerId
|
||||
}
|
||||
sendCommand(eventCommand, responseCallBack, eventsCallBack)
|
||||
}
|
||||
|
||||
function stopListening(eventHandlerName, eventType, eventSubscriptionId, callerId, responseCallBack) {
|
||||
/* User either needs to specify a valid eventSubscriptionId OR the 3 params: eventHandlerName, eventType, callerId which were used when start listening to events. */
|
||||
let eventCommand = {
|
||||
action: 'stopListening',
|
||||
eventHandlerName: eventHandlerName,
|
||||
eventType: eventType,
|
||||
eventSubscriptionId: eventSubscriptionId,
|
||||
callerId: callerId
|
||||
}
|
||||
sendCommand(eventCommand, responseCallBack)
|
||||
}
|
||||
|
||||
function raiseEvent(eventHandlerName, eventType, event, callerId, responseCallBack) {
|
||||
let eventCommand = {
|
||||
action: 'raiseEvent',
|
||||
eventHandlerName: eventHandlerName,
|
||||
eventType: eventType,
|
||||
event: event,
|
||||
callerId: callerId
|
||||
}
|
||||
sendCommand(eventCommand, responseCallBack)
|
||||
}
|
||||
}
|
||||
+533
@@ -0,0 +1,533 @@
|
||||
|
||||
const path = require('path')
|
||||
|
||||
exports.newFileStorage = function newFileStorage(logger, host, port) {
|
||||
|
||||
const MODULE_NAME = 'FileStorage'
|
||||
const MAX_RETRY = 10
|
||||
const FAST_RETRY_TIME_IN_MILISECONDS = 500
|
||||
const SLOW_RETRY_TIME_IN_MILISECONDS = 2000
|
||||
|
||||
if (logger === undefined) { // Dummy logger
|
||||
logger = {}
|
||||
function write() {
|
||||
|
||||
}
|
||||
logger.write = write
|
||||
}
|
||||
|
||||
let thisObject = {
|
||||
asyncGetTextFile: asyncGetTextFile,
|
||||
asyncCreateTextFile: asyncCreateTextFile,
|
||||
getTextFile: getTextFile,
|
||||
createTextFile: createTextFile,
|
||||
deleteTextFile: deleteTextFile
|
||||
}
|
||||
|
||||
return thisObject
|
||||
|
||||
async function asyncGetTextFile(filePath, noRetry, canUsePrevious) {
|
||||
/* This function allows its caller to work with async / await instead of callbacks */
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
getTextFile(filePath, onFileRead, noRetry, canUsePrevious)
|
||||
function onFileRead(err, text) {
|
||||
|
||||
let response = {
|
||||
err: err,
|
||||
text: text
|
||||
}
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
async function asyncCreateTextFile(filePath, fileContent, keepPrevious, noTemp) {
|
||||
/* This function allows its caller to work with async / await instead of callbacks */
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
createTextFile(filePath, fileContent, onFileWriten, keepPrevious, noTemp)
|
||||
function onFileWriten(err) {
|
||||
|
||||
let response = {
|
||||
err: err
|
||||
}
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
function getTextFile(filePath, callBackFunction, noRetry, canUsePrevious) {
|
||||
|
||||
let currentRetryGetTextFile = 0
|
||||
|
||||
recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious)
|
||||
|
||||
function recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious) {
|
||||
let fileDoesNotExist = false
|
||||
|
||||
/* Choose path for either bots or data */
|
||||
let fileLocation
|
||||
let mustBeJason
|
||||
if (filePath.indexOf("/bots/") > 0) {
|
||||
fileLocation = process.env.BOTS_PATH + '/' + filePath
|
||||
mustBeJason = false
|
||||
} else {
|
||||
fileLocation = process.env.STORAGE_PATH + '/' + filePath
|
||||
mustBeJason = true
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] FileStorage -> getTextFile -> fileLocation: ' + fileLocation)
|
||||
|
||||
/* Here we actually reaad the file. */
|
||||
if (host === undefined || host === 'localhost') {
|
||||
/* We read the file from the local file system. */
|
||||
const fs = require('fs')
|
||||
fs.readFile(fileLocation, onFileRead)
|
||||
} else {
|
||||
/* We read the file via a web server over http */
|
||||
getFileViaHTTP(fileLocation, onFileRead, callBackFunction)
|
||||
}
|
||||
|
||||
function onFileRead(err, text) {
|
||||
let retryTimeToUse = FAST_RETRY_TIME_IN_MILISECONDS
|
||||
if (currentRetryGetTextFile > MAX_RETRY - 2) {
|
||||
retryTimeToUse = SLOW_RETRY_TIME_IN_MILISECONDS
|
||||
}
|
||||
if (err) {
|
||||
|
||||
if (err.code === 'ENOENT') { // since files are deleted before being replaced, it can happen that it does not exist and after a retry it does.
|
||||
if (noRetry === true) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> File does not exist. Not Retrying. ')
|
||||
let customResponse = {
|
||||
result: global.CUSTOM_FAIL_RESPONSE.result,
|
||||
message: 'File does not exist.'
|
||||
}
|
||||
callBackFunction(customResponse)
|
||||
return
|
||||
}
|
||||
|
||||
fileDoesNotExist = true
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> File does not exist. Retrying. ')
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
return
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Error reading file -> err = ' + err.stack)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Error reading file -> filePath = ' + filePath)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Error reading file -> err = ' + err.stack)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
It might happen that we try to read a file just at the moment it is being written by another process. It that case we might get
|
||||
an empty file. If we are not allowed to use the Previous version of the file in this case, then we will retry to read it again
|
||||
a little bit later.
|
||||
*/
|
||||
if (text.toString() === "" && canUsePrevious !== true) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Read and Empty File -> filePath = ' + filePath)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
return
|
||||
}
|
||||
|
||||
if (mustBeJason === false) {
|
||||
/* In this case there is nothing else to check. */
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString())
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
We are going to check if the file is a valir JSON object
|
||||
*/
|
||||
try {
|
||||
let jsonCheck = JSON.parse(text.toString())
|
||||
|
||||
/* The file was correctly read and could be parsed. */
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString())
|
||||
return
|
||||
|
||||
} catch (err) {
|
||||
|
||||
if (canUsePrevious === true) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Could read the file, but could not parse it as it is not a valid JSON. Will try to read the PREVIOUS version instead. -> file = ' + fileLocation)
|
||||
|
||||
if (host === undefined || host === 'localhost') {
|
||||
/* We read the file from the local file system. */
|
||||
const fs = require('fs')
|
||||
fs.readFile(fileLocation + '.Previous.json', onPreviousFileRead)
|
||||
} else {
|
||||
/* We read the file via a web server over http */
|
||||
getFileViaHTTP(fileLocation + '.Previous.json', onPreviousFileRead, callBackFunction)
|
||||
}
|
||||
|
||||
function onPreviousFileRead(err, text) {
|
||||
|
||||
if (err) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> canUsePrevious -> Could not read the Previous file either. Giving up.')
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> canUsePrevious -> err = ' + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
/* Returning the previous file */
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE, text.toString())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> Could read the file, but could not parse it as it is not a valid JSON.')
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> err = ' + err.stack)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> onFileRead -> text.toString() = ' + text.toString())
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error reading file -> file = ' + fileLocation)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> Error reading file -> err = ' + err.stack)
|
||||
retry()
|
||||
}
|
||||
|
||||
function retry() {
|
||||
console.log(new Date(), '[WARN] Read File Retry #: ' + currentRetryGetTextFile, fileLocation)
|
||||
if (currentRetryGetTextFile < MAX_RETRY) {
|
||||
currentRetryGetTextFile++
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> retry -> Will try to read the file again -> Retry #: ' + currentRetryGetTextFile)
|
||||
recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious)
|
||||
} else {
|
||||
currentRetryGetTextFile = 0
|
||||
|
||||
if (fileDoesNotExist === true) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> getTextFile -> retry -> Max retries reached reading a file. File Not Found.')
|
||||
let customResponse = {
|
||||
result: global.CUSTOM_FAIL_RESPONSE.result,
|
||||
message: 'File does not exist.'
|
||||
}
|
||||
callBackFunction(customResponse)
|
||||
return
|
||||
} else {
|
||||
|
||||
logger.write(MODULE_NAME, '[ERROR] FileStorage -> getTextFile -> retry -> Max retries reached reading a file. Giving up.')
|
||||
logger.write(MODULE_NAME, '[ERROR] FileStorage -> getTextFile -> retry -> file = ' + fileLocation)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createTextFile(filePath, fileContent, callBackFunction, keepPrevious, noTemp) {
|
||||
|
||||
let currentRetryWriteTextFile = 0
|
||||
|
||||
recursiveCreateTextFile(filePath, fileContent, callBackFunction, keepPrevious, noTemp)
|
||||
|
||||
function recursiveCreateTextFile(filePath, fileContent, callBackFunction, keepPrevious, noTemp) {
|
||||
/* Choose path for either logs or data */
|
||||
let fileLocation
|
||||
if (filePath.indexOf("/Logs/") > 0) {
|
||||
fileLocation = process.env.LOG_PATH + '/' + filePath
|
||||
} else {
|
||||
fileLocation = process.env.STORAGE_PATH + '/' + filePath
|
||||
}
|
||||
|
||||
try {
|
||||
logger.write(MODULE_NAME, '[INFO] FileStorage -> createTextFile -> fileLocation: ' + fileLocation)
|
||||
|
||||
/* If necesary a folder or folders are created before writing the file to disk. */
|
||||
let directoryPath = fileLocation.substring(0, fileLocation.lastIndexOf('/') + 1);
|
||||
mkDirByPathSync(directoryPath)
|
||||
|
||||
/*
|
||||
Here we write the file with a temporary name so as to avoid dirty read from other processes.
|
||||
Then we delete the original file, if exists, and finally we rename the temporary into the original name.
|
||||
*/
|
||||
const fs = require('fs')
|
||||
if (noTemp === true) {
|
||||
fs.writeFile(fileLocation, fileContent, onFileWriten)
|
||||
} else {
|
||||
fs.writeFile(fileLocation + '.tmp', fileContent, onFileWriten)
|
||||
}
|
||||
|
||||
function onFileWriten(err) {
|
||||
let retryTimeToUse = FAST_RETRY_TIME_IN_MILISECONDS
|
||||
if (currentRetryWriteTextFile > MAX_RETRY - 2) {
|
||||
retryTimeToUse = SLOW_RETRY_TIME_IN_MILISECONDS
|
||||
}
|
||||
if (err) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> Error writing file -> file = ' + fileLocation + '.tmp')
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> Error writing file -> err = ' + err.stack)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
} else {
|
||||
|
||||
if (noTemp === true) {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
}
|
||||
if (keepPrevious === true) {
|
||||
/*
|
||||
In some cases, we are going to keep a copy of the previous version of the file being written. This will be usefull to recover from crashes
|
||||
when the file written gets corrupted for any reason.
|
||||
*/
|
||||
fs.unlink(fileLocation + '.Previous.json', onUnlinked)
|
||||
|
||||
function onUnlinked(err) {
|
||||
let code = ''
|
||||
if (err) {
|
||||
code = err.code
|
||||
}
|
||||
if (code !== '' && code !== 'ENOENT') {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> file = ' + fileLocation + '.Previous.json')
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> err = ' + err.stack)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
return
|
||||
}
|
||||
|
||||
/* Rename de Original into Previous */
|
||||
fs.rename(fileLocation, fileLocation + '.Previous.json', onOriginalRenamed)
|
||||
|
||||
function onOriginalRenamed(err) {
|
||||
let code = ''
|
||||
if (err) {
|
||||
code = err.code
|
||||
}
|
||||
if (code !== '' && code !== 'ENOENT') { // Unless the file does not exists we do this...
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> Error renaming original file -> file = ' + fileLocation)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> Error renaming original file -> err = ' + err.stack)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
return
|
||||
}
|
||||
|
||||
/* Rename de Temp into Original */
|
||||
fs.rename(fileLocation + '.tmp', fileLocation, onTempRenamed)
|
||||
|
||||
function onTempRenamed(err) {
|
||||
if (err) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> onTempRenamed -> Error renaming temp file -> file = ' + fileLocation + '.tmp')
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> onTempRenamed -> Error renaming temp file -> err = ' + err.stack)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
} else {
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
/* In this case, there is no need to keep a copy of the file being replaced, so we just delete and that's it. */
|
||||
fs.unlink(fileLocation, onUnlinked)
|
||||
|
||||
function onUnlinked(err) {
|
||||
let code = ''
|
||||
if (err) {
|
||||
code = err.code
|
||||
}
|
||||
if (code !== '' && code !== 'ENOENT') {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> file = ' + fileLocation)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> Error deleting file -> err = ' + err.stack)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
return
|
||||
}
|
||||
|
||||
/* Rename de Temp into Original */
|
||||
fs.rename(fileLocation + '.tmp', fileLocation, onTempRenamed)
|
||||
|
||||
function onTempRenamed(err) {
|
||||
if (err) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> onTempRenamed -> Error renaming temp file -> file = ' + fileLocation + '.tmp')
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> onFileWriten -> onUnlinked -> onOriginalRenamed -> onTempRenamed -> Error renaming temp file -> err = ' + err.stack)
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
} else {
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> Error writing file -> file = ' + fileLocation)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> Error writing file -> err = ' + err.stack)
|
||||
retry()
|
||||
}
|
||||
|
||||
function retry() {
|
||||
if (currentRetryWriteTextFile < MAX_RETRY) {
|
||||
currentRetryWriteTextFile++
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> createTextFile -> retry -> Will try to write the file again -> Retry #: ' + currentRetryWriteTextFile)
|
||||
recursiveCreateTextFile(filePath, fileContent, callBackFunction, keepPrevious)
|
||||
} else {
|
||||
currentRetryWriteTextFile = 0
|
||||
logger.write(MODULE_NAME, '[ERROR] FileStorage -> createTextFile -> retry -> Max retries reached writting a file. Giving up.')
|
||||
logger.write(MODULE_NAME, '[ERROR] FileStorage -> createTextFile -> retry -> file = ' + fileLocation)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteTextFile(filePath, callBackFunction) {
|
||||
|
||||
let currentRetryDeleteTextFile = 0
|
||||
|
||||
recursiveDeleteTextFile(filePath, callBackFunction)
|
||||
|
||||
function recursiveDeleteTextFile(filePath, callBackFunction) {
|
||||
logger.write(MODULE_NAME, '[INFO] FileStorage -> deleteTextFile -> Entering Function.')
|
||||
|
||||
/* Choose path for either logs or data */
|
||||
let fileLocation
|
||||
if (filePath.indexOf("/Logs/") > 0) {
|
||||
fileLocation = process.env.LOG_PATH + '/' + filePath
|
||||
} else {
|
||||
fileLocation = process.env.STORAGE_PATH + '/' + filePath
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] FileStorage -> deleteTextFile -> fileLocation: ' + fileLocation)
|
||||
|
||||
const fs = require('fs')
|
||||
fs.unlink(fileLocation, onUnlinked)
|
||||
|
||||
function onUnlinked(err) {
|
||||
let retryTimeToUse = FAST_RETRY_TIME_IN_MILISECONDS
|
||||
if (currentRetryDeleteTextFile > MAX_RETRY - 2) {
|
||||
retryTimeToUse = SLOW_RETRY_TIME_IN_MILISECONDS
|
||||
}
|
||||
if (err) {
|
||||
if (callBackFunction !== undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
} else {
|
||||
setTimeout(retry, retryTimeToUse)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> deleteTextFile -> Error writing file -> file = ' + fileLocation)
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> deleteTextFile -> Error writing file -> err = ' + err.stack)
|
||||
if (callBackFunction !== undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function retry() {
|
||||
if (currentRetryDeleteTextFile < MAX_RETRY) {
|
||||
currentRetryDeleteTextFile++
|
||||
logger.write(MODULE_NAME, '[WARN] FileStorage -> deleteTextFile -> retry -> Will try to delete the file again -> Retry #: ' + currentRetryDeleteTextFile)
|
||||
recursiveDeleteTextFile(filePath, callBackFunction)
|
||||
} else {
|
||||
currentRetryDeleteTextFile = 0
|
||||
logger.write(MODULE_NAME, '[ERROR] FileStorage -> deleteTextFile -> retry -> Max retries reached deleting a file. Giving up.')
|
||||
logger.write(MODULE_NAME, '[ERROR] FileStorage -> deleteTextFile -> retry -> file = ' + fileLocation)
|
||||
if (callBackFunction !== undefined) {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Function to create folders of missing folders at any path. */
|
||||
function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) {
|
||||
const sep = '/';
|
||||
const initDir = path.isAbsolute(targetDir) ? sep : '';
|
||||
const baseDir = isRelativeToScript ? __dirname : '.';
|
||||
|
||||
return targetDir.split(sep).reduce((parentDir, childDir) => {
|
||||
const curDir = path.resolve(baseDir, parentDir, childDir);
|
||||
try {
|
||||
const fs = require('fs')
|
||||
fs.mkdirSync(curDir);
|
||||
} catch (err) {
|
||||
if (err.code === 'EEXIST') { // curDir already exists!
|
||||
return curDir;
|
||||
}
|
||||
|
||||
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
|
||||
if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure.
|
||||
throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
|
||||
}
|
||||
|
||||
const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
|
||||
if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) {
|
||||
throw err; // Throw if it's just the last created dir.
|
||||
}
|
||||
}
|
||||
|
||||
return curDir;
|
||||
}, initDir);
|
||||
}
|
||||
|
||||
function getFileViaHTTP(filePath, callback, callBackFunction) {
|
||||
try {
|
||||
|
||||
/* The filePath received is the one that is needed to fetch data from with fs. To do it via http we need to remove the prefix that includes this: ./Data-Storage/ */
|
||||
filePath = filePath.substring(15, filePath.length)
|
||||
|
||||
let http = require('http');
|
||||
let url = 'http://' + host +
|
||||
':' + port +
|
||||
'/Storage/' +
|
||||
filePath
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] FileStorage -> getFileViaHTTP -> url = ' + url)
|
||||
|
||||
let request = http.get(url, onResponse);
|
||||
|
||||
request.on('error', function (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onError -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onError -> Failed to fetch file via HTTP. Will retry later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
})
|
||||
|
||||
function onResponse(response) {
|
||||
|
||||
try {
|
||||
const chunks = []
|
||||
|
||||
response.on('data', onMessegesArrived)
|
||||
response.on('end', onEnd)
|
||||
|
||||
function onMessegesArrived(chunk) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
function onEnd() {
|
||||
let fileContent = Buffer.concat(chunks).toString('utf8')
|
||||
let err = null
|
||||
if (fileContent.indexOf('does not exist') >= 0) {
|
||||
err = {
|
||||
code: "ENOENT" // This is how fs would have returned upon this situation.
|
||||
}
|
||||
fileContent = undefined
|
||||
}
|
||||
callback(err, fileContent)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onResponse -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> onResponse -> Failed to fetch file via HTTP. Will retry later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] getFileViaHTTP -> Failed to fetch file via HTTP. Will retry later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
+786
@@ -0,0 +1,786 @@
|
||||
exports.newIndicatorBot = function newIndicatorBot(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Indicator Bot";
|
||||
const FULL_LOG = true;
|
||||
|
||||
let USER_BOT_MODULE;
|
||||
let COMMONS_MODULE;
|
||||
|
||||
const MULTI_PERIOD_MARKET = require(global.ROOT_DIR + 'MultiPeriodMarket');
|
||||
const MULTI_PERIOD_DAILY = require(global.ROOT_DIR + 'MultiPeriodDaily');
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(parentLogger);
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble.
|
||||
|
||||
let nextLoopTimeoutHandle;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
run: run
|
||||
};
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
try {
|
||||
processConfig = pProcessConfig;
|
||||
|
||||
if (bot.definedByUI === true) {
|
||||
/* The code of the bot is defined at the UI. No need to load a file with the code. */
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* This bot is not ready for taking its code from the UI, then we need to load it from its repo. */
|
||||
let filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo + "/" + pProcessConfig.codeName
|
||||
filePath += "/User.Bot.js"
|
||||
|
||||
fileStorage.getTextFile(filePath, onBotDownloaded);
|
||||
|
||||
function onBotDownloaded(err, text) {
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message);
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
USER_BOT_MODULE = {}
|
||||
USER_BOT_MODULE.newUserBot = eval(text); // TODO This needs to be changed function
|
||||
|
||||
filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo
|
||||
filePath += "/Commons.js"
|
||||
|
||||
fileStorage.getTextFile(filePath, onCommonsDownloaded);
|
||||
|
||||
function onCommonsDownloaded(err, text) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.message);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
COMMONS_MODULE = {}
|
||||
COMMONS_MODULE.newCommons = eval(text); // TODO This needs to be changed function
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function run(callBackFunction) {
|
||||
try {
|
||||
let fixedTimeLoopIntervalHandle;
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.processHeartBeat = processHeartBeat
|
||||
|
||||
if (bot.runAtFixedInterval === true) {
|
||||
fixedTimeLoopIntervalHandle = setInterval(loop, bot.fixedInterval);
|
||||
} else {
|
||||
loop();
|
||||
}
|
||||
|
||||
function loop() {
|
||||
try {
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
/* For each loop we want to create a new log file. */
|
||||
if (logger !== undefined) {
|
||||
logger.finalize()
|
||||
}
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set(MODULE_NAME, logger)
|
||||
logger.bot = bot;
|
||||
logger.initialize();
|
||||
|
||||
bot.loopCounter++;
|
||||
bot.loopStartTime = new Date().valueOf();
|
||||
|
||||
/* We tell the UI that we are running. */
|
||||
processHeartBeat()
|
||||
|
||||
/* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */
|
||||
const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities');
|
||||
const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport');
|
||||
const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies');
|
||||
const DATA_DEPENDENCIES = require(global.ROOT_DIR + 'DataDependencies');
|
||||
const DATA_SET = require(global.ROOT_DIR + 'DataSet');
|
||||
const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents');
|
||||
const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput');
|
||||
|
||||
/* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */
|
||||
bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution.
|
||||
|
||||
/* High level log entry */
|
||||
console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30)
|
||||
+ " Main Loop # " + pad(Number(bot.loopCounter), 8))
|
||||
|
||||
/* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */
|
||||
let processExecutionEvents
|
||||
let userBot;
|
||||
let processFramework;
|
||||
let statusDependencies;
|
||||
let dataDependencies;
|
||||
|
||||
let nextWaitTime;
|
||||
|
||||
initializeProcessExecutionEvents();
|
||||
|
||||
function initializeProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
|
||||
processExecutionEvents.initialize(processConfig, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well.");
|
||||
startProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.start(onStarted);
|
||||
|
||||
function onStarted(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well.");
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
loopControl()
|
||||
return
|
||||
}
|
||||
|
||||
initializeStatusDependencies();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> 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(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeStatusDependencies() {
|
||||
try {
|
||||
statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT);
|
||||
statusDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well.");
|
||||
initializeDataDependencies();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDataDependencies() {
|
||||
try {
|
||||
dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET);
|
||||
dataDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
/* If the process is configured to run inside a framework, we continue there, otherwise we run the bot directly. */
|
||||
if (processConfig.framework === undefined) {
|
||||
initializeUserBot();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (processConfig.framework.name) {
|
||||
case 'Multi-Period-Market': {
|
||||
processFramework = MULTI_PERIOD_MARKET.newMultiPeriodMarket(bot, logger, UTILITIES, FILE_STORAGE);
|
||||
intitializeProcessFramework();
|
||||
break;
|
||||
}
|
||||
case 'Multi-Period-Daily': {
|
||||
processFramework = MULTI_PERIOD_DAILY.newMultiPeriodDaily(bot, logger, UTILITIES, FILE_STORAGE);
|
||||
intitializeProcessFramework();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported.");
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + processConfig.framework.name);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> 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.
|
||||
processStopped()
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeUserBot() {
|
||||
try {
|
||||
usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE, UTILITIES, FILE_STORAGE);
|
||||
usertBot.initialize(statusDependencies, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> onInizialized -> Execution finished well.");
|
||||
startUserBot();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeUserBot -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startUserBot() {
|
||||
try {
|
||||
usertBot.start(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> onFinished -> Execution finished well.");
|
||||
finishProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
case "Dependency does not exist.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Dependency does not exist. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case "Dependency not ready.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished -> Dependency not ready. Will Retry Later.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function intitializeProcessFramework() {
|
||||
try {
|
||||
processFramework.initialize(processConfig, statusDependencies, dataDependencies, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well.");
|
||||
startProcessFramework();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> 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(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessFramework() {
|
||||
try {
|
||||
processFramework.start(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processFramework.finalize()
|
||||
processFramework = undefined
|
||||
dataDependencies.finalize()
|
||||
dataDependencies = undefined
|
||||
statusDependencies.finalize()
|
||||
statusDependencies = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well.");
|
||||
finishProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
case "Dependency does not exist.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case "Dependency not ready.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function finishProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.finish(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processExecutionEvents.finalize()
|
||||
processExecutionEvents = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
loopControl(nextWaitTime);
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function loopControl(nextWaitTime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); }
|
||||
|
||||
/* We show we reached the end of the loop. */
|
||||
processHeartBeat()
|
||||
|
||||
/* Here we check if we must stop the loop gracefully. */
|
||||
shallWeStop(onStop, onContinue);
|
||||
|
||||
function onStop() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); }
|
||||
processStopped()
|
||||
return;
|
||||
}
|
||||
|
||||
function onContinue() {
|
||||
/* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */
|
||||
switch (nextWaitTime) {
|
||||
case 'Normal': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.normalWaitTime / 1000) + " seconds."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.normalWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.normalWaitTime / 1000 + " seconds for next execution.")
|
||||
if (global.WRITE_LOGS_TO_FILES === 'true') {
|
||||
logger.persist();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Retry': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Sleep': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Coma': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shallWeStop(stopCallBack, continueCallBack) {
|
||||
try {
|
||||
/* IMPORTANT: This function is exactly the same on the 3 modules. */
|
||||
if (!global.STOP_TASK_GRACEFULLY) {
|
||||
continueCallBack();
|
||||
} else {
|
||||
stopCallBack();
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function processHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
|
||||
}
|
||||
|
||||
function processStopped() {
|
||||
if (global.unexpectedError !== undefined) {
|
||||
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
|
||||
} else {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
|
||||
}
|
||||
logger.persist();
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
if (global.unexpectedError !== undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
} else {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack);
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
exports.newIndicatorOutput = function newIndicatorOutput(bot, logger, UTILITIES, FILE_STORAGE) {
|
||||
const FULL_LOG = true;
|
||||
const MODULE_NAME = "Indicator Bot";
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
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 finalize() {
|
||||
thisObject = undefined
|
||||
utilities = undefined
|
||||
fileStorage = undefined
|
||||
commons = undefined
|
||||
}
|
||||
|
||||
function initialize(callBackFunction) {
|
||||
try {
|
||||
logger.fileName = MODULE_NAME;
|
||||
logger.initialize();
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function start(dataFiles, timeFrame, timeFrameLabel, currentDay, interExecutionMemory, callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
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 = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency')
|
||||
|
||||
if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return }
|
||||
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
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, timeFrame)
|
||||
|
||||
/* 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.config.singularVariableName
|
||||
|
||||
/* Check Irregular Periods */
|
||||
|
||||
if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.javascriptCode !== undefined) {
|
||||
if (outputDatasetNode.referenceParent.parentNode.dataBuilding.loop.javascriptCode.code.indexOf('results.push') >= 0) {
|
||||
resultsWithIrregularPeriods = true
|
||||
}
|
||||
}
|
||||
|
||||
/* Build the data */
|
||||
jsonData = commons.dataBuildingProcedure(
|
||||
products,
|
||||
mainDependency,
|
||||
recordDefinition,
|
||||
outputDatasetNode.referenceParent.parentNode.dataBuilding,
|
||||
singularVariableName,
|
||||
outputDatasetNode.referenceParent.parentNode.config.codeName,
|
||||
timeFrame,
|
||||
timeFrameLabel,
|
||||
resultsWithIrregularPeriods,
|
||||
interExecutionMemory,
|
||||
processingDailyFiles,
|
||||
currentDay
|
||||
)
|
||||
|
||||
/* Add the calculated properties */
|
||||
if (outputDatasetNode.referenceParent.parentNode.calculations !== undefined) {
|
||||
outputData = commons.calculationsProcedure(jsonData, recordDefinition, outputDatasetNode.referenceParent.parentNode.calculations, singularVariableName, timeFrame)
|
||||
} else {
|
||||
outputData = jsonData
|
||||
}
|
||||
products[outputDatasetNode.referenceParent.parentNode.config.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.config.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.javascriptCode.code.indexOf('results.push') >= 0) {
|
||||
resultsWithIrregularPeriods = true
|
||||
}
|
||||
|
||||
/* Simplifying the access to basic info */
|
||||
|
||||
contextSummary.dataset = outputDatasetNode.referenceParent.config.codeName
|
||||
contextSummary.product = outputDatasetNode.referenceParent.parentNode.config.codeName
|
||||
|
||||
let botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Indicator Bot')
|
||||
contextSummary.bot = botNode.config.codeName
|
||||
let dataMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Data Mine')
|
||||
contextSummary.dataMine = dataMineNode.config.codeName
|
||||
|
||||
let fileContent = commons.generateFileContent(outputData, outputDatasetNode.referenceParent.parentNode.record, resultsWithIrregularPeriods, processingDailyFiles, currentDay, callBackFunction)
|
||||
commons.writeFile(contextSummary, fileContent, anotherFileWritten, processingDailyFiles, timeFrameLabel, currentDay, 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,519 @@
|
||||
exports.newLearningSession = function newLearningSession(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Learning Session"
|
||||
const FULL_LOG = true;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize
|
||||
}
|
||||
|
||||
const SOCIAL_BOTS_MODULE = require('./SocialBots.js')
|
||||
let socialBotsModule = SOCIAL_BOTS_MODULE.newSocialBots(bot, parentLogger)
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
try {
|
||||
/* Initialize this info so that everything is logged propeerly */
|
||||
bot.LEARNING_SESSION = {
|
||||
name: bot.processNode.session.name,
|
||||
id: bot.processNode.session.id
|
||||
}
|
||||
|
||||
/* Set the folderName for early logging */
|
||||
if (bot.processNode.session.config.folderName === undefined) {
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.LEARNING_SESSION.id
|
||||
} else {
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
|
||||
/* Check if there is a session */
|
||||
if (bot.processNode.session === undefined) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> Cannot run without a Session.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Listen to event to start or stop the session. */
|
||||
bot.sessionKey = bot.processNode.session.name + '-' + bot.processNode.session.type + '-' + bot.processNode.session.id
|
||||
global.SESSION_MAP.set(bot.sessionKey, bot.sessionKey)
|
||||
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Learning Session Status', undefined, bot.sessionKey, undefined, onSessionStatus)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Learning Session', undefined, bot.sessionKey, undefined, onSessionRun)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Learning Session', undefined, bot.sessionKey, undefined, onSessionStop)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Resume Learning Session', undefined, bot.sessionKey, undefined, onSessionResume)
|
||||
|
||||
/* Connect this here so that it is accesible from other places */
|
||||
bot.sessionError = sessionError
|
||||
bot.sessionWarning = sessionWarning
|
||||
bot.sessionInfo = sessionInfo
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.sessionHeartBeat = sessionHeartBeat
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
|
||||
function onSessionStatus() {
|
||||
if (bot.SESSION_STATUS === 'Running') {
|
||||
let event = {
|
||||
status: 'Learning Session Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Status Response', event)
|
||||
} else {
|
||||
let event = {
|
||||
status: 'Learning Session Not Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Status Response', event)
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionRun(message) {
|
||||
try {
|
||||
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
|
||||
if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionRun -> Event received to run the Session while it was already running. ")
|
||||
return
|
||||
}
|
||||
|
||||
/* We are going to run the Definition comming at the event. */
|
||||
bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem)
|
||||
bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine)
|
||||
bot.LEARNING_SESSION = JSON.parse(message.event.session)
|
||||
bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter)
|
||||
bot.RESUME = false
|
||||
bot.FIRST_EXECUTION = true
|
||||
bot.LEARNING_SESSION.stop = stopSession // stop function
|
||||
|
||||
setUpSessionFolderName()
|
||||
|
||||
/* We validate all parameters received and complete some that might be missing if needed. */
|
||||
if (checkParemeters() === false) { return }
|
||||
|
||||
socialBotsModule.initialize()
|
||||
|
||||
let allGood
|
||||
switch (bot.LEARNING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
allGood = startBackTesting(message)
|
||||
break
|
||||
}
|
||||
case 'Live Trading Session': {
|
||||
allGood = startLiveTrading(message)
|
||||
break
|
||||
}
|
||||
case 'Fordward Testing Session': {
|
||||
allGood = startFordwardTesting(message)
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
allGood = startPaperTrading(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allGood === true) {
|
||||
bot.SESSION_STATUS = 'Idle'
|
||||
bot.STOP_SESSION = false
|
||||
} else {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] onSessionRun -> Stopping the Session now. ') }
|
||||
}
|
||||
|
||||
socialBotsModule.sendMessage(bot.LEARNING_SESSION.type + " '" + bot.LEARNING_SESSION.name + "' is starting.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionRun -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionStop() {
|
||||
stopSession('Session Stopped From the User Interface.')
|
||||
}
|
||||
|
||||
function onSessionResume(message) {
|
||||
try {
|
||||
if (bot.LEARNING_SESSION.stop === undefined) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session that have never be ran before. ")
|
||||
return
|
||||
}
|
||||
|
||||
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
|
||||
if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session while it was already running. ")
|
||||
return
|
||||
}
|
||||
|
||||
bot.RESUME = true
|
||||
bot.STOP_SESSION = false
|
||||
|
||||
socialBotsModule.sendMessage(bot.LEARNING_SESSION.type + " '" + bot.LEARNING_SESSION.name + "' is resuming.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionResume -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function stopSession(commandOrigin) {
|
||||
|
||||
socialBotsModule.sendMessage(bot.LEARNING_SESSION.type + " '" + bot.LEARNING_SESSION.name + "' is stopping " + commandOrigin)
|
||||
socialBotsModule.finalize()
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] stopSession -> Stopping the Session now. ') }
|
||||
sessionInfo(bot.LEARNING_SESSION, commandOrigin)
|
||||
}
|
||||
|
||||
function setUpSessionFolderName() {
|
||||
/*
|
||||
The session object is overwritten when the session is run. For that reason we
|
||||
need to setup again the folder name at the Session level.
|
||||
Set the folderName for logging, reports, context and data output
|
||||
*/
|
||||
let config
|
||||
if (bot.LEARNING_SESSION.config !== undefined) {
|
||||
config = bot.LEARNING_SESSION.config
|
||||
if (config.folderName === undefined) {
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.LEARNING_SESSION.id
|
||||
} else {
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkParemeters() {
|
||||
/*
|
||||
Here we check all the Session Parameters received. If something critical is missing we abort returning false. If something
|
||||
non critical is missing, we complete it with a default value.
|
||||
*/
|
||||
|
||||
if (bot.LEARNING_SESSION.learningParameters === undefined) {
|
||||
let errorMessage = "Session Node with no Parameters."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Time Range */
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange === undefined) { // if the Time Range is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange = {
|
||||
name: 'Missing Time Range',
|
||||
type: 'Time Range',
|
||||
config: {
|
||||
initialDatetime: (new Date()).valueOf(),
|
||||
finalDatetime: (new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Check that we received valid dates */
|
||||
if (bot.LEARNING_SESSION.type === 'Backtesting Session') {
|
||||
if (isNaN(new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (isNaN(new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* Session Type Forced Values */
|
||||
let today = (new Date()).valueOf()
|
||||
let aYearAgo = today - global.ONE_YEAR_IN_MILISECONDS
|
||||
let aYearFromNow = today + global.ONE_YEAR_IN_MILISECONDS
|
||||
switch (bot.LEARNING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
useDefaultDatetimes(aYearAgo, today)
|
||||
break
|
||||
}
|
||||
case 'Live Trading Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
case 'Fordward Testing Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function useDefaultDatetimes(initialDefault, finalDefault) {
|
||||
/*
|
||||
Note that inside the system, we are going to deal with these
|
||||
dates in their numeric value representation.
|
||||
*/
|
||||
|
||||
/* Initial Datetime */
|
||||
if (bot.LEARNING_SESSION.type === 'Backtesting Session') {
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime === undefined) {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = (new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
} else {
|
||||
/* Non backtest session can start from the past only if explicitly configured that way */
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime === undefined || bot.LEARNING_SESSION.learningParameters.timeRange.config.allowStartingFromThePast !== true) {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = (new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Final Datetime */
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime === undefined) {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime = finalDefault
|
||||
} else {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime = (new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Time Frame */
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeFrame === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeFrame.config.label === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame Label configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
bot.LEARNING_SESSION.learningParameters.timeFrame.config.value = getTimeFrameFromLabel(bot.LEARNING_SESSION.learningParameters.timeFrame.config.label)
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeFrame.config.value === undefined) {
|
||||
let errorMessage = "Config error: label value not recognized. Try 01-min or 01-hs for example."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Base Asset */
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionBaseAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Base Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Base Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.sessionBaseAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Quoted Asset */
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Quoted Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Quoted Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Slippage */
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage === undefined) { // if the Slippage is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage = {
|
||||
name: 'Missing Slippage',
|
||||
type: 'Slippage',
|
||||
config: {
|
||||
marketOrderRate: 0,
|
||||
stopLoss: 0,
|
||||
takeProfit: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage.config.marketOrderRate === undefined) { // if the marketOrderRate is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage.config.marketOrderRate = 0
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage.config.stopLoss === undefined) { // if the stopLoss is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage.config.stopLoss = 0
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage.config.takeProfit === undefined) { // if the takeProfit is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage.config.takeProfit = 0
|
||||
}
|
||||
|
||||
/* Fee Structure */
|
||||
if (bot.LEARNING_SESSION.learningParameters.feeStructure === undefined) { // if the Fee Structure is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.feeStructure = {
|
||||
name: 'Missing Fee Structure',
|
||||
type: 'Fee Structure',
|
||||
config: {
|
||||
maker: 0,
|
||||
taker: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.feeStructure.config.maker === undefined) { // if the maker is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.feeStructure.config.maker = 0
|
||||
}
|
||||
if (bot.LEARNING_SESSION.learningParameters.feeStructure.config.taker === undefined) { // if the taker is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.feeStructure.config.taker = 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function startBackTesting(message) {
|
||||
return true
|
||||
}
|
||||
|
||||
function startLiveTrading(message) {
|
||||
if (bot.KEY === undefined || bot.SECRET === undefined) {
|
||||
let errorMessage = "Key 'codeName' or 'secret' not provided. Plese check that and try again."
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> " + errorMessage); }
|
||||
bot.sessionError(bot.LEARNING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function startFordwardTesting(message) {
|
||||
if (bot.KEY === undefined || bot.SECRET === undefined) {
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> startFordwardTesting -> Key name or Secret not provided, not possible to run the process in Forward Testing mode."); }
|
||||
console.log("Key 'codeName' or 'secret' not provided. Plese check that and try again.")
|
||||
return false
|
||||
}
|
||||
|
||||
/* Reduce the balance */
|
||||
let balancePercentage = 1 // This is the default value
|
||||
|
||||
if (bot.LEARNING_SESSION.config.balancePercentage !== undefined) {
|
||||
balancePercentage = bot.LEARNING_SESSION.config.balancePercentage
|
||||
}
|
||||
|
||||
bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.initialBalance = bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.initialBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.initialBalance = bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.initialBalance * balancePercentage / 100
|
||||
|
||||
bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.minimumBalance = bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.minimumBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.minimumBalance = bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.minimumBalance * balancePercentage / 100
|
||||
|
||||
bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.maximumBalance = bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.maximumBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.maximumBalance = bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.maximumBalance * balancePercentage / 100
|
||||
|
||||
pProcessConfig.normalWaitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function startPaperTrading(message) {
|
||||
return true
|
||||
}
|
||||
|
||||
function getTimeFrameFromLabel(timeFrameLabel) {
|
||||
|
||||
for (let i = 0; i < global.marketFilesPeriods.length; i++) {
|
||||
let value = global.marketFilesPeriods[i][0]
|
||||
let label = global.marketFilesPeriods[i][1]
|
||||
|
||||
if (timeFrameLabel === label) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < global.dailyFilePeriods.length; i++) {
|
||||
let value = global.dailyFilePeriods[i][0]
|
||||
let label = global.dailyFilePeriods[i][1]
|
||||
|
||||
if (timeFrameLabel === label) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sessionHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Heartbeat', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionHeartBeat -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionError(node, errorMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Error', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionError -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionWarning(node, warningMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Warning', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionWarning -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionInfo(node, infoMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Info', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionInfo -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
exports.newAnnouncements = function newAnnouncements(bot, logger) {
|
||||
|
||||
const MODULE_NAME = 'Announcements'
|
||||
|
||||
let thisObject = {
|
||||
makeAnnoucements: makeAnnoucements,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingSystem
|
||||
let tradingEngine
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingSystem = undefined
|
||||
tradingEngine = undefined
|
||||
bot.TRADING_SESSION.messagesSent = undefined
|
||||
}
|
||||
|
||||
function makeAnnoucements(node) {
|
||||
if (node.announcements !== undefined) {
|
||||
for (let i = 0; i < node.announcements.length; i++) {
|
||||
let announcement = node.announcements[i]
|
||||
let canAnnounce = true
|
||||
if (announcement.announcementCondition !== undefined) {
|
||||
let conditionValue
|
||||
try {
|
||||
conditionValue = eval(announcement.announcementCondition.code)
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([announcement.announcementCondition.id, err.message])
|
||||
}
|
||||
if (conditionValue !== true) { canAnnounce = false }
|
||||
}
|
||||
if (canAnnounce === true) {
|
||||
let text = announcement.config.text
|
||||
if (announcement.formula !== undefined) {
|
||||
text = tradingSystem.formulas.get(announcement.formula.id)
|
||||
}
|
||||
|
||||
if (bot.TRADING_SESSION.socialBots !== undefined) {
|
||||
bot.TRADING_SESSION.socialBots.announce(text)
|
||||
tradingSystem.announcements.push([announcement.id, text])
|
||||
} else {
|
||||
tradingSystem.errors.push([announcement.id, 'Could not announce because session does not have Social Bots.'])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
exports.newExchangeAPI = function newExchangeAPI(bot, logger) {
|
||||
|
||||
let MODULE_NAME = "Exchange API";
|
||||
|
||||
let thisObject = {
|
||||
getOrder: getOrder,
|
||||
createOrder: createOrder,
|
||||
cancelOrder: cancelOrder,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
};
|
||||
|
||||
let tradingSystem
|
||||
|
||||
let exchangeId
|
||||
let options = {}
|
||||
let exchange
|
||||
|
||||
const ccxt = require('ccxt')
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
|
||||
exchangeId = bot.exchange.toLowerCase()
|
||||
|
||||
let key = bot.KEY
|
||||
let secret = bot.SECRET
|
||||
|
||||
const exchangeClass = ccxt[exchangeId]
|
||||
const exchangeConstructorParams = {
|
||||
'apiKey': key,
|
||||
'secret': secret,
|
||||
'timeout': 30000,
|
||||
'enableRateLimit': true,
|
||||
verbose: false,
|
||||
'adjustForTimeDifference': true,
|
||||
options: options
|
||||
}
|
||||
exchange = new exchangeClass(exchangeConstructorParams)
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingSystem = undefined
|
||||
exchangeId = undefined
|
||||
options = undefined
|
||||
exchange = undefined
|
||||
}
|
||||
|
||||
async function getOrder(tradingSystemOrder, tradingEngineOrder) {
|
||||
|
||||
let orderId = tradingEngineOrder.exchangeId.value
|
||||
const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
|
||||
/* Basic Logging */
|
||||
logInfo("getOrder -> orderId = " + orderId)
|
||||
logInfo("getOrder -> symbol = " + symbol)
|
||||
|
||||
/* Basic Validations */
|
||||
if (exchange.has['fetchOrder'] === false) {
|
||||
logError("getOrder -> Exchange does not support fetchOrder command.");
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let order = await exchange.fetchOrder(orderId, symbol)
|
||||
|
||||
logInfo("getOrder -> Response from the Exchange: " + JSON.stringify(order));
|
||||
return order
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([tradingSystemOrder.id, "getOrder -> Error = " + err.message])
|
||||
logError("getOrder -> Error = " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function createOrder(tradingSystemOrder, tradingEngineOrder) {
|
||||
|
||||
let price = tradingEngineOrder.rate.value // CCXT: how much quote currency you are willing to pay for a trade lot of base currency (for limit orders only)
|
||||
let type // CCXT: a string literal type of order, ccxt currently unifies market and limit orders only
|
||||
let side // CCXT: a string literal for the direction of your order, buy or sell
|
||||
let symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset // CCXT: a string literal symbol of the market you wish to trade on, like BTC/USD, ZEC/ETH, DOGE/DASH, etc
|
||||
let amount = tradingEngineOrder.orderBaseAsset.size.value // CCXT: how much of currency you want to trade, in Base Asset.
|
||||
|
||||
switch (tradingSystemOrder.type) {
|
||||
case 'Market Buy Order': {
|
||||
type = 'market'
|
||||
side = 'buy'
|
||||
break
|
||||
}
|
||||
case 'Market Sell Order': {
|
||||
type = 'market'
|
||||
side = 'sell'
|
||||
break
|
||||
}
|
||||
case 'Limit Buy Order': {
|
||||
type = 'limit'
|
||||
side = 'buy'
|
||||
break
|
||||
}
|
||||
case 'Limit Sell Order': {
|
||||
type = 'limit'
|
||||
side = 'sell'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/* Basic Logging */
|
||||
logInfo("createOrder -> symbol = " + symbol);
|
||||
logInfo("createOrder -> side = " + side);
|
||||
logInfo("createOrder -> type = " + type);
|
||||
logInfo("createOrder -> amount = " + amount);
|
||||
logInfo("createOrder -> price = " + price);
|
||||
|
||||
/* Basic Validations */
|
||||
if (side !== "buy" && side !== "sell") {
|
||||
logError("createOrder -> side must be either 'buy' or 'sell'.");
|
||||
return
|
||||
}
|
||||
if (exchange.has['createOrder'] === false) {
|
||||
logError("createOrder -> Exchange does not support createOrder command.");
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let order = await (exchange.createOrder(symbol, type, side, amount, price))
|
||||
|
||||
logInfo("createOrder -> Response from the Exchange: " + JSON.stringify(order));
|
||||
return order.id
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([tradingSystemOrder.id, "createOrder -> Error = " + err.message])
|
||||
logError("createOrder -> Error = " + err.message)
|
||||
if (err.message.indexOf('API-key format invalid') >= 0) {
|
||||
logError ('The exchange says the API key provided is not good or it is not in the correct format. This is what you are using:')
|
||||
errorFooter()
|
||||
}
|
||||
if (err.message.indexOf('Signature for this request is not valid.') >= 0) {
|
||||
logError ('The exchange says the secret provided is not good, incorrect or not in the correct format. This is what you are using:')
|
||||
errorFooter()
|
||||
}
|
||||
function errorFooter(){
|
||||
logError ('key: ->' + bot.KEY + '<-')
|
||||
logError ('secret: ->' + bot.SECRET + '<-')
|
||||
logError ('key.length:' + bot.KEY.length )
|
||||
logError ('secret.length:' + bot.SECRET.length )
|
||||
logError ('Double check that you copied the key at the codeName property and the secret at secret property without bringing any invisible caracter from the exchange web site, or leaving any character from the sample text. Also check that the keys are not disabled at the exchange ot that they are not restricted by IP.')
|
||||
logError ('As a remainder. Binance key and secret are 64 bytes in lenght each one. ' )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelOrder(tradingSystemOrder, tradingEngineOrder) {
|
||||
|
||||
let orderId = tradingEngineOrder.exchangeId.value
|
||||
|
||||
/* Basic Logging */
|
||||
logInfo("cancelOrder -> Entering function. orderId = " + orderId);
|
||||
|
||||
const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
|
||||
/* Basic Validations */
|
||||
if (exchange.has['fetchOrder'] === false) {
|
||||
logError("cancelOrder -> Exchange does not support fetchOrder command.");
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let order = await exchange.cancelOrder(orderId, symbol)
|
||||
|
||||
logInfo("cancelOrder -> Response from the Exchange: " + JSON.stringify(order));
|
||||
return true
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([tradingSystemOrder.id, "cancelOrder -> Error = " + err.message])
|
||||
logError("cancelOrder -> Error = " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function logInfo(message) {
|
||||
logger.write(MODULE_NAME, '[INFO] ' + message)
|
||||
}
|
||||
|
||||
function logError(message) {
|
||||
logger.write(MODULE_NAME, '[ERROR] ' + message)
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,316 @@
|
||||
exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
/*
|
||||
When we are live trading, we need to syncronize with the exchange.
|
||||
*/
|
||||
const MODULE_NAME = 'Orders Calculations'
|
||||
|
||||
let thisObject = {
|
||||
actualSizeCalculation: actualSizeCalculation,
|
||||
actualRateCalculation: actualRateCalculation,
|
||||
feesToBePaidCalculation: feesToBePaidCalculation,
|
||||
amountReceivedCalculation: amountReceivedCalculation,
|
||||
finapercentageFilledCalculationlize: percentageFilledCalculation,
|
||||
feesPaidCalculation: feesPaidCalculation,
|
||||
sizeFilledCalculation: sizeFilledCalculation,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
sessionParameters = undefined
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
}
|
||||
|
||||
async function actualSizeCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
When we submit the order to the exchange, we do it specifying the order size
|
||||
in Base Asset. It is mandatory to be in Base Asset as per CCXT Library API and
|
||||
probably as per the Exchange API too.
|
||||
|
||||
The exchange might take that size or might change it a little
|
||||
bit. For example, if it does not accept as many decimals as we are sending.
|
||||
|
||||
We have also seen that for Market Orders, the exchange usually does not take
|
||||
the size requested exactly. Sometimes is a little bit less or even a little bit
|
||||
more. For Limit orders it is usually the same.
|
||||
|
||||
If it happens that the size accepted (Actual Size) is different than the
|
||||
size requested (Size), we need to make several adjustments so that the
|
||||
accounting syncronizes with reality.
|
||||
*/
|
||||
|
||||
/* This calculation needs to happen only once, the first time the order is checked. */
|
||||
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
|
||||
|
||||
/* We receive the actual size from the exchange at the order.amount field. */
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = order.amount
|
||||
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
function recalculateActualSize() {
|
||||
/*
|
||||
We will recalculate the Quoted Asset Actual Size. This will also give to it an initial value.
|
||||
In the next formula, we are unsing the rate of the order because we dont know yet the Actual Rate.
|
||||
This rate might be replaced afteerwards for the Actual Rate when this is calculated again once
|
||||
the Actual Rate is known.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.rate.value
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.actualSize.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
}
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
/*
|
||||
We will also recalculate the Size Placed that was previously calculated with the order Size,
|
||||
and now we will do it with the order Actual Size.
|
||||
|
||||
We need to unaccount the previous size placed and correctly account
|
||||
for the the new actual sizes we now know.
|
||||
*/
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value -
|
||||
tradingEngineOrder.orderBaseAsset.size.value
|
||||
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
|
||||
tradingEngineOrder.orderQuotedAsset.size.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10)
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
|
||||
}
|
||||
}
|
||||
|
||||
async function actualRateCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
Actual Rate Calculation: Let's remember that the exchange may not take the rate
|
||||
we send it with the order (in Limit orders) and for any reason change it a little bit.
|
||||
For example we might have sent a rate with too many decimals and the exchange truncate
|
||||
them. Another thing that might happen is that we send a rate that is matching orders already
|
||||
at the order book. In this case we would get an actual rate not only different than the one
|
||||
we sent, but also, better since the trading engine inside the exchange would match orders
|
||||
with the first orders best by price.
|
||||
|
||||
In the case of Market Orders we don't even send a rate. For all those reason
|
||||
we get and store the Actual Rate at which the order was filled.
|
||||
*/
|
||||
if (order.average !== undefined) {
|
||||
/*
|
||||
We will use the average whenever is available. As of today it is not 100% clear when this is
|
||||
available, but it seems sometimes it is not. In those cases we use the price.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = order.average
|
||||
} else {
|
||||
/*
|
||||
We use the order.price when the average is not available.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = order.price
|
||||
}
|
||||
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
|
||||
/*
|
||||
If the Actual Rate happens to be exctly the same than the order rate, then there is
|
||||
nothing else to do here. Otherwise there are adjustments to be made.
|
||||
*/
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
|
||||
let previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
function recalculateActualSize() {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
/*
|
||||
Now we know the Actual Rate at which the order was filled. Since the actual rate
|
||||
is not the same as the Rate we defined for the order, we need to syncronize
|
||||
the Actual Order Size for Quoted Asset since it was calculated with the Order Size that we
|
||||
now know it is not the one really used at the exchange. We will recalculate the
|
||||
Actual Size in Quoted Asset and not in Base Asset, since the Actual Size in Base Asset
|
||||
is the one we accepted by the exchange, so that is fixed, regardless of how we initially
|
||||
got the Order Size in Base Asset, either from direct imput from the user or calculated
|
||||
from the Order Size in Quoted Asset input it by the user. So here we go.
|
||||
*/
|
||||
previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.actualRate.value
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderQuotedAsset.actualSize.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Actual Size (' + previousQuotedAssetActualSize + ') recalculated (' + tradingEngineOrder.orderQuotedAsset.actualSize.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
let previousStageQuotedAssetSizePlaced = tradingEngineStage.stageQuotedAsset.sizePlaced.value
|
||||
/*
|
||||
Since we changed the Actual Size in Quoted Asset, we need to fix the Size Placed in Quoted Asset
|
||||
that was previously calculated with the previous value of actualSize.
|
||||
*/
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
|
||||
previousQuotedAssetActualSize
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineStage.stageQuotedAsset.sizePlaced.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Size Placed (' + previousStageQuotedAssetSizePlaced + ') recalculated (' + tradingEngineStage.stageQuotedAsset.sizePlaced.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function feesToBePaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function percentageFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
Percentage Filled Calculation: The only relevant information we get from the exchange is the order.filled.
|
||||
We know this field will go up until reaching the Actual Size. Once it reaches that number
|
||||
the order will be 100% filled.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / tradingEngineOrder.orderBaseAsset.actualSize.value
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
|
||||
}
|
||||
|
||||
async function feesPaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
Within the order information received from the exchange we can not see the fees paid so we need to
|
||||
calculate them ourselves. Knwowing the Fees Paid is critical to later update the balances correctly,
|
||||
since at every trade we will receive less than just the Actual Size. In fact we will receive the Actual
|
||||
Size minus the Fees Paid.
|
||||
|
||||
The percentage at which the exchange will charge fees needs to be configured at the the Session Fees Parameters.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function sizeFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
CCXT returns order.filled with an amount denominated in Base Asset. We will
|
||||
take it from there for our Order Base Asset. The amount in order.filled does
|
||||
not reflect the Fees Paid, even if we are paying the fees in Base Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10)
|
||||
/*
|
||||
For Quoted Asset, CCXT does not return any field with the size filled, so we
|
||||
need to calculate that by ourselves.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
|
||||
}
|
||||
|
||||
async function amountReceivedCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
This calculation is for informational purposes, so that users do not have to calculate
|
||||
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
|
||||
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
|
||||
are selling then it will be in Quoted Asset.
|
||||
*/
|
||||
|
||||
switch (true) {
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Base Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderBaseAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Quoted Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
exports.newOrdersSimulations = function newOrdersSimulations(bot, logger) {
|
||||
/*
|
||||
When we are backtesting or paper trading, we need to simulate what would have happened at the exchange.
|
||||
*/
|
||||
const MODULE_NAME = 'Orders Simulations'
|
||||
|
||||
let thisObject = {
|
||||
actualSizeSimulation: actualSizeSimulation,
|
||||
actualRateSimulation: actualRateSimulation,
|
||||
feesToBePaidSimulation: feesToBePaidSimulation,
|
||||
amountReceivedSimulation: amountReceivedSimulation,
|
||||
percentageFilledSimulation: percentageFilledSimulation,
|
||||
feesPaidSimulation: feesPaidSimulation,
|
||||
sizeFilledSimulation: sizeFilledSimulation,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
sessionParameters = undefined
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
}
|
||||
|
||||
async function actualSizeSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
The Actual Size is the size the exchange accepts for the order,
|
||||
regarless of the size sent in the request to the exchange.
|
||||
|
||||
While backtesting and paper trading, we do not really need to
|
||||
simulate an Actual Size. We will just copy to that field the
|
||||
size of the order.
|
||||
|
||||
This calculation needs to happen only once, so as to set these values.
|
||||
*/
|
||||
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.size.value
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderQuotedAsset.size.value
|
||||
}
|
||||
|
||||
function actualRateSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/* Actual Rate Simulation */
|
||||
let previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) {
|
||||
/*
|
||||
The default value for the Actual Rate is the rate of the order.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value
|
||||
}
|
||||
|
||||
basedOnSessionParameters()
|
||||
basedOnTradingSystem()
|
||||
considerBestMatchWithOrderBook()
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
function basedOnSessionParameters() {
|
||||
/*
|
||||
The default way to calculate the Actual Rate is to apply the Slippage defined
|
||||
at the Session Parameters Definition. We are not going to apply the default if there
|
||||
is a definition for this order on how to calculate the actual rate.
|
||||
*/
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) {
|
||||
return
|
||||
}
|
||||
}
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
|
||||
/* Actual Rate is simulated based on the Session Paremeters */
|
||||
let slippageAmount = tradingEngineOrder.rate.value * sessionParameters.slippage.config.marketOrderRate / 100
|
||||
if (slippageAmount !== 0) {
|
||||
switch (tradingSystemOrder.type) {
|
||||
case 'Market Sell Order': {
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[sessionParameters.slippage.id, tradingEngineOrder.rate.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'slippage.config.marketOrderRate Slippage Amount (' + slippageAmount + ') substracted from Order Rate (' + tradingEngineOrder.rate.value + ') to get the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ')'
|
||||
]
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Market Buy Order': {
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[sessionParameters.slippage.id, tradingEngineOrder.rate.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'slippage.config.marketOrderRate Slippage Amount (' + slippageAmount + ') added to Order Rate (' + tradingEngineOrder.rate.value + ') to get the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ')'
|
||||
]
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
/* In Limit Orders the actual rate is the rate of the order, there is no slippage */
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function basedOnTradingSystem() {
|
||||
/*
|
||||
If the user specified a Formula to simulate the Actual Rate for this order,
|
||||
we use that formula here. Note that if this Formula exists, this will override
|
||||
the calculation based on Slippage.
|
||||
*/
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) {
|
||||
/* Calculate this only once for this order */
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) {
|
||||
let newValue = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id)
|
||||
newValue = global.PRECISE(newValue, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id,
|
||||
'Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') calculated with this Formula (' + newValue + ')'
|
||||
]
|
||||
)
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function considerBestMatchWithOrderBook() {
|
||||
/*
|
||||
Until here we have got the Actual Rate based on the formula definition or based on session parameters slippage.
|
||||
The last check is about watching what happened in the market. Let's remember that the exchange will
|
||||
fill the order with the best possible matches at it's order book. That means that if the rate
|
||||
we set for the order was too low (for a sale order) or too hight (for a buy order), the actual rate
|
||||
should be better than expected.
|
||||
|
||||
We don't know what it is at the order book, but whe know that the last candle includes trades that bounced between
|
||||
the min an max of the candle. That could tell us the above the max and below the min, the order would be matched
|
||||
with orders at the order book. For that reason, we do this:
|
||||
*/
|
||||
switch (true) {
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value > tradingEngine.current.episode.candle.max.value) {
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderStatistics.actualRate.id, tradingEngine.current.episode.candle.max.id],
|
||||
'Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') too high. Changed to candle.max (' + tradingEngine.current.episode.candle.max.value + ')'
|
||||
]
|
||||
)
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.max.value
|
||||
}
|
||||
break
|
||||
}
|
||||
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value < tradingEngine.current.episode.candle.min.value) {
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderStatistics.actualRate.id, tradingEngine.current.episode.candle.min.id],
|
||||
'Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') too low. Changed to candle.min (' + tradingEngine.current.episode.candle.max.value + ')'
|
||||
]
|
||||
)
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.min.value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
}
|
||||
|
||||
function recalculateActualSize() {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
/*
|
||||
Now that we have an Actual Rate, we need to adjust the Actual Size in Quoted Asset, since
|
||||
Base Asset is what the exchange accepts as input and give information at. Quotes Asset properties are always calculated.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.actualRate.value
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderQuotedAsset.actualSize.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Actual Size (' + previousQuotedAssetActualSize + ') recalculated (' + tradingEngineOrder.orderQuotedAsset.actualSize.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
/*
|
||||
Since the Actual Rate might have changed, we need to recalculate the Size Placed, where we accumulate all the Size Placed of
|
||||
all orders of a Stage. For Base Asset there is nothing to do, since the Actual Rate does not have an inpact on it.
|
||||
|
||||
For Quoted Asset, we need to first unaccount what this same orded added before to Size Placed (with the precious Actual Size) and
|
||||
account with the new Actual Size.
|
||||
*/
|
||||
let previousStageQuotedAssetSizePlaced = tradingEngineStage.stageQuotedAsset.sizePlaced.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
|
||||
previousQuotedAssetActualSize
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineStage.stageQuotedAsset.sizePlaced.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Size Placed (' + previousStageQuotedAssetSizePlaced + ') recalculated (' + tradingEngineStage.stageQuotedAsset.sizePlaced.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function feesToBePaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function percentageFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
When we are live, it is the exchange who knows which order was filled and in
|
||||
which amount. Since we are backtesting here, we need to simulate what happens
|
||||
at the exchange. We can not simulate that an order is filled if the candle we
|
||||
see did not touch the order.
|
||||
|
||||
For limit buy orders we are going to check if the price was below the order actual rate, and
|
||||
for limit sell orders we are going to check if the price was above the order actual rate.
|
||||
|
||||
If we have a partial filling defined specifically for this order,
|
||||
we are going to use it to estimate how much of the order we will consider filled.
|
||||
*/
|
||||
let orderWasHit
|
||||
switch (tradingSystemOrder.type) {
|
||||
case 'Limit Buy Order': {
|
||||
if (tradingEngine.current.episode.candle.min.value <= tradingEngineOrder.orderStatistics.actualRate.value) {
|
||||
orderWasHit = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Limit Sell Order': {
|
||||
if (tradingEngine.current.episode.candle.max.value >= tradingEngineOrder.orderStatistics.actualRate.value) {
|
||||
orderWasHit = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Market Buy Order': {
|
||||
orderWasHit = true
|
||||
break
|
||||
}
|
||||
case 'Market Sell Order': {
|
||||
orderWasHit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (orderWasHit === true) {
|
||||
/*
|
||||
Initially, we will assume that 100% of the order was filled. This might be overwritten
|
||||
if there is a configuration for this order that details how much is must be considered
|
||||
filled each time we check this.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = 100
|
||||
|
||||
/* Based on the Trading System Definition */
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) {
|
||||
|
||||
/* Percentage Filled */
|
||||
let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100
|
||||
if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) {
|
||||
percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
}
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function feesPaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
The way the Fess Paid are calculated is as follows: From the order Actual Size we apply the % of fee,
|
||||
that would give us the total fees for that order. To that we apply the % filled of the orders,
|
||||
that give use the final result of the fees paid for the current % filled.
|
||||
|
||||
But before that, we will see if there is an specific definition on how to simulate the fees for
|
||||
this order, or if there is not, we use the Fees Structure defined at the Session Parameters.
|
||||
*/
|
||||
|
||||
/* Based on the Trading System Definition */
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
|
||||
applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Another way to simulate the Fees Paid is by using the Session Paremeters configuration for Fees.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sizeFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
Size Filled is the amount of Base Asset or Quoted Asset that at the exchange matches
|
||||
another counterparty order. We calculate this using the Percentage Filled we already simulated.
|
||||
*/
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
|
||||
}
|
||||
|
||||
async function amountReceivedSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
This simulation is for informational purposes, so that users do not have to calculate
|
||||
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
|
||||
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
|
||||
are selling then it will be in Quoted Asset.
|
||||
*/
|
||||
|
||||
switch (true) {
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Base Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderBaseAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Quoted Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
exports.newSnapshots = function newSnapshots(bot, logger) {
|
||||
/*
|
||||
This module encapsulates the snapshots functionality.
|
||||
*/
|
||||
const MODULE_NAME = 'Snapshots'
|
||||
|
||||
let thisObject = {
|
||||
updateChart: updateChart,
|
||||
strategyEntry: strategyEntry,
|
||||
strategyExit: strategyExit,
|
||||
positionEntry: positionEntry,
|
||||
positionExit: positionExit,
|
||||
addCodeToSnapshot: addCodeToSnapshot,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
const SNAPSHOTS_FOLDER_NAME = 'Snapshots'
|
||||
|
||||
/* Snapshots of Strategies and Positions */
|
||||
let snapshots = {
|
||||
headers: [],
|
||||
strategies: [],
|
||||
positions: []
|
||||
}
|
||||
|
||||
let positionKeys = new Map() // This is to prevent adding the same indicator / variable to the output collection.
|
||||
let strategyKeys = new Map()
|
||||
let createHeaders = true // This is going to be true only once
|
||||
let createCloseHeaders = true // This is going to be true only once
|
||||
let closeValues
|
||||
let strategyValues
|
||||
let addToStrategyValues
|
||||
let positionValues
|
||||
let addToPositionValues
|
||||
|
||||
let chart
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
writeSnapshotFiles()
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
sessionParameters = undefined
|
||||
|
||||
positionKeys = undefined
|
||||
strategyKeys = undefined
|
||||
|
||||
chart = undefined
|
||||
}
|
||||
|
||||
function updateChart(pChart) {
|
||||
chart = pChart
|
||||
}
|
||||
|
||||
function strategyEntry() {
|
||||
/*
|
||||
We will record here a snapshot of all values used in conditions and formulas within the Trading System.
|
||||
If the strategy exists with a trade, this information will be added to the snapshots file.
|
||||
*/
|
||||
|
||||
strategyKeys = new Map()
|
||||
strategyValues = []
|
||||
addToStrategyValues = true
|
||||
|
||||
evalNode(tradingSystem)
|
||||
|
||||
/* Turn off this */
|
||||
createHeaders = false
|
||||
addToStrategyValues = false
|
||||
}
|
||||
|
||||
function strategyExit() {
|
||||
getResults(tradingEngine.current.strategy.begin.value, tradingEngine.current.strategy.end.value)
|
||||
let valuesArray = closeValues.concat(strategyValues)
|
||||
snapshots.strategies.push(valuesArray)
|
||||
}
|
||||
|
||||
function positionEntry() {
|
||||
|
||||
positionKeys = new Map()
|
||||
positionValues = []
|
||||
addToPositionValues = true
|
||||
|
||||
evalNode(tradingSystem)
|
||||
|
||||
/* Turn off this */
|
||||
createHeaders = false
|
||||
addToPositionValues = false
|
||||
}
|
||||
|
||||
function positionExit() {
|
||||
getResults(tradingEngine.current.position.begin.value, tradingEngine.current.position.end.value)
|
||||
let valuesArray = closeValues.concat(positionValues)
|
||||
snapshots.positions.push(valuesArray)
|
||||
}
|
||||
|
||||
function evalNode(node) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Here we check if there is a codition to be evaluated */
|
||||
if (node.type === 'Condition') {
|
||||
/* We will eval this condition */
|
||||
evalCondition(node)
|
||||
}
|
||||
|
||||
/* Here we check if there is a formula to be evaluated */
|
||||
if (node.type === 'Formula') {
|
||||
if (node.code !== undefined) {
|
||||
/* We will eval this formula */
|
||||
evalFormula(node)
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
evalNode(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
evalNode(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function evalCondition(node) {
|
||||
|
||||
/*
|
||||
The code can be at the condition node if it was done with the Conditions Editor, or it can also be
|
||||
at a Javascript Code node. If there is a Javascript object we will give it preference and take the code
|
||||
from there. Otherwise we will take the code from the Condition node.
|
||||
*/
|
||||
|
||||
let nodeWithCode = node
|
||||
if (node.javascriptCode !== undefined) {
|
||||
if (node.javascriptCode.code !== undefined) {
|
||||
nodeWithCode = node.javascriptCode
|
||||
}
|
||||
}
|
||||
addCodeToSnapshot(nodeWithCode)
|
||||
}
|
||||
|
||||
function evalFormula(node) {
|
||||
let nodeWithCode = node
|
||||
addCodeToSnapshot(nodeWithCode)
|
||||
}
|
||||
|
||||
function addCodeToSnapshot(nodeWithCode) {
|
||||
if (nodeWithCode === undefined) { return }
|
||||
if (nodeWithCode.code === undefined) { return }
|
||||
|
||||
try {
|
||||
let instructionsArray = nodeWithCode.code.split(' ')
|
||||
for (let i = 0; i < instructionsArray.length; i++) {
|
||||
let instruction = instructionsArray[i]
|
||||
instruction = instruction.replace('(', '')
|
||||
instruction = instruction.replace(')', '')
|
||||
instruction = instruction.replace(/</g, '')
|
||||
instruction = instruction.replace(/>/g, '')
|
||||
instruction = instruction.replace(/<=/g, '')
|
||||
instruction = instruction.replace(/>=/g, '')
|
||||
instruction = instruction.replace(/!=/g, '')
|
||||
instruction = instruction.replace(/!==/g, '')
|
||||
instruction = instruction.replace(/==/g, '')
|
||||
instruction = instruction.replace(/===/g, '')
|
||||
instruction = instruction.replace(/{/g, '')
|
||||
instruction = instruction.replace(/}/g, '')
|
||||
if (instruction.indexOf('chart') >= 0) {
|
||||
let parts = instruction.split('.')
|
||||
let timeFrame = parts[1]
|
||||
let product = parts[2]
|
||||
let property
|
||||
checkPrevious(3)
|
||||
function checkPrevious(index) {
|
||||
property = parts[index]
|
||||
if (property === 'previous') {
|
||||
product = product + '.previous'
|
||||
checkPrevious(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10
|
||||
if (timeFrame !== 'atAnyTimeFrame') {
|
||||
timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7)
|
||||
}
|
||||
let key = timeFrame + '-' + product + '-' + property
|
||||
let existingKey
|
||||
|
||||
if (addToStrategyValues === true) {
|
||||
addValues(strategyKeys, strategyValues)
|
||||
}
|
||||
|
||||
if (addToPositionValues === true) {
|
||||
addValues(positionKeys, positionValues)
|
||||
}
|
||||
|
||||
function addValues(keys, values) {
|
||||
existingKey = keys.get(key)
|
||||
|
||||
if (existingKey === undefined) { // means that at the current loop this property of this product was not used before.
|
||||
keys.set(key, key)
|
||||
if (createHeaders === true) {
|
||||
snapshots.headers.push(key)
|
||||
}
|
||||
|
||||
try {
|
||||
let value = eval(instruction)
|
||||
values.push(value)
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([nodeWithCode.id, 'Instruction ' + instruction + ' evalueates with error ' + err.message])
|
||||
values.push(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([nodeWithCode.id, err.message])
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> nodeWithCode.code = ' + nodeWithCode.code)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
|
||||
function getResults(openDatetime, closeDatetime) {
|
||||
|
||||
let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result In Base Asset', 'Result In Quoted Asset', 'ROI in Base Asset', 'ROI in Quoted Asset', 'Exit Type']
|
||||
closeValues = [
|
||||
tradingEngine.current.episode.episodeCounters.positions.value, // Position Number
|
||||
(new Date(openDatetime)).toISOString(), // Open Datetime
|
||||
(new Date(closeDatetime)).toISOString(), // Open Datetime
|
||||
tradingEngine.current.strategy.strategyName.value, // Strategy Name
|
||||
tradingEngine.current.strategy.situationName.value, // Trigger On Situation
|
||||
tradingEngine.current.position.situationName.value, // Take Position Situation
|
||||
tradingEngine.current.position.positionBaseAsset.hitFail.value, // Result in Base Asset
|
||||
tradingEngine.current.position.positionQuotedAsset.hitFail.value, // Result in Base Asset
|
||||
tradingEngine.current.position.positionBaseAsset.ROI.value, // ROI in Base Asseet
|
||||
tradingEngine.current.position.positionQuotedAsset.ROI.value, // ROI in Quoted Asset
|
||||
tradingEngine.current.position.exitType.value // Exit Type
|
||||
]
|
||||
|
||||
if (createCloseHeaders === true) {
|
||||
snapshots.headers = closeHeaders.concat(JSON.parse(JSON.stringify(snapshots.headers)))
|
||||
createCloseHeaders = false
|
||||
}
|
||||
}
|
||||
|
||||
function writeSnapshotFiles() {
|
||||
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
writeSnapshotFile(snapshots.strategies, 'Strategies')
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
writeSnapshotFile(snapshots.positions, 'Positions')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeSnapshotFile(snapshotArray, pFileName) {
|
||||
try {
|
||||
const FILE_STORAGE = require('../FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger);
|
||||
|
||||
let fileContent = "";
|
||||
let separator = "\r\n";
|
||||
|
||||
parseRecord(snapshots.headers)
|
||||
|
||||
for (let i = 0; i < snapshotArray.length; i++) {
|
||||
let record = snapshotArray[i];
|
||||
parseRecord(record)
|
||||
}
|
||||
|
||||
function parseRecord(record) {
|
||||
for (let j = 0; j < record.length; j++) {
|
||||
let property = record[j]
|
||||
|
||||
fileContent = fileContent + '' + property
|
||||
if (j !== record.length - 1) {
|
||||
fileContent = fileContent + ","
|
||||
}
|
||||
}
|
||||
fileContent = fileContent + separator
|
||||
}
|
||||
|
||||
fileContent = "" + fileContent + "";
|
||||
|
||||
let fileName = pFileName + '.csv';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + SNAPSHOTS_FOLDER_NAME;
|
||||
filePath += '/' + fileName
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated);
|
||||
|
||||
function onFileCreated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> filePath = " + filePath);
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> onFileCreated -> market = " + market.baseAsset + "_" + market.quotedAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] writeSnapshotFile -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
/*
|
||||
We call the Trading Engine to the data structure that is needed in order to exevute the
|
||||
trading protocol with the specific rules defined at the Trading System.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Engine'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
getNodeById: getNodeById,
|
||||
cloneValues: cloneValues,
|
||||
setCurrentCandle: setCurrentCandle,
|
||||
setCurrentCycle: setCurrentCycle,
|
||||
initializeNode: initializeNode,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
let nodesMap = new Map()
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
initializeNodeMap(tradingEngine)
|
||||
|
||||
if (bot.FIRST_EXECUTION === true) {
|
||||
/*
|
||||
Here we will go through all the nodes in the Trading Engine hiriarchy and
|
||||
apply the initial value to the value property when needed
|
||||
*/
|
||||
initializeNode(tradingEngine)
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
nodesMap = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
}
|
||||
|
||||
function reset() {
|
||||
|
||||
}
|
||||
|
||||
function getNodeById(NodeId) {
|
||||
return nodesMap.get(NodeId)
|
||||
}
|
||||
|
||||
function setCurrentCandle(candle) {
|
||||
tradingEngine.current.episode.candle.begin.value = candle.begin
|
||||
tradingEngine.current.episode.candle.end.value = candle.end
|
||||
tradingEngine.current.episode.candle.open.value = candle.open
|
||||
tradingEngine.current.episode.candle.close.value = candle.close
|
||||
tradingEngine.current.episode.candle.min.value = candle.min
|
||||
tradingEngine.current.episode.candle.max.value = candle.max
|
||||
}
|
||||
|
||||
function setCurrentCycle(cycle) {
|
||||
tradingEngine.current.episode.cycle.value = cycle
|
||||
tradingEngine.current.episode.cycle.lastBegin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
tradingEngine.current.episode.cycle.lastEnd.value = tradingEngine.current.episode.cycle.end.value
|
||||
switch (cycle) {
|
||||
case 'First': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2 - 1
|
||||
break
|
||||
}
|
||||
case 'Second': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value * 4 / 4 - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
/*
|
||||
We can not have the last begin or last end to be zero, because that would prevent
|
||||
objects starting with lastBegin in zero to being saved on file. For that reason
|
||||
we will do this:
|
||||
*/
|
||||
if (tradingEngine.current.episode.cycle.lastBegin.value === 0) {
|
||||
tradingEngine.current.episode.cycle.lastBegin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
}
|
||||
if (tradingEngine.current.episode.cycle.lastEnd.value === 0) {
|
||||
tradingEngine.current.episode.cycle.lastEnd.value = tradingEngine.current.episode.cycle.end.value
|
||||
}
|
||||
}
|
||||
|
||||
function cloneValues(originNode, destinationNode) {
|
||||
if (originNode === undefined) { return }
|
||||
if (destinationNode === undefined) { return }
|
||||
|
||||
/* Here copy the node value */
|
||||
destinationNode.value = originNode.value
|
||||
|
||||
/* Now we go down through all the children of the origin node, assuming the destination node has the same children structure*/
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(originNode.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
cloneValues(originNode[property.name], destinationNode[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
let originNodePropertyArray = originNode[property.name]
|
||||
let destinationNodePropertyArray = destinationNode[property.name]
|
||||
for (let m = 0; m < originNodePropertyArray.length; m++) {
|
||||
cloneValues(originNodePropertyArray[m], destinationNodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNodeMap(node) {
|
||||
if (node === undefined) { return }
|
||||
/* Add to Node Map */
|
||||
nodesMap.set(node.id, node)
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
initializeNodeMap(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
initializeNodeMap(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNode(node) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Here we initialize the node value */
|
||||
if (node.config !== undefined) {
|
||||
if (node.config.initialValue !== undefined) {
|
||||
node.value = node.config.initialValue
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
initializeNode(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
initializeNode(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngineModule) {
|
||||
/*
|
||||
This module packages all functions related to Episodes.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Episode'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
openEpisode: openEpisode,
|
||||
updateExitType: updateExitType,
|
||||
closeEpisode: closeEpisode,
|
||||
cycleBasedStatistics: cycleBasedStatistics,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
updateCounters()
|
||||
updateStatistics()
|
||||
updateEnds()
|
||||
updateDistanceToEventsCounters()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resetTradingEngineDataStructure()
|
||||
}
|
||||
|
||||
function openEpisode() {
|
||||
/*
|
||||
This function is called each time the simulation starts. That does not mean the Episode
|
||||
must be opened there, since it might happen that it is looping at the end of the market
|
||||
or the task / session was restarted. We will only execute if the episode was never opened before.
|
||||
*/
|
||||
if (tradingEngine.current.episode.begin.value !== tradingEngine.current.episode.begin.config.initialValue) { return }
|
||||
|
||||
/* Starting begin and end */
|
||||
tradingEngine.current.episode.begin.value = tradingEngine.current.episode.candle.begin.value
|
||||
tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value
|
||||
|
||||
/* Getting the begin Balance from the session configuration */
|
||||
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value = sessionParameters.sessionBaseAsset.config.initialBalance
|
||||
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
|
||||
|
||||
/* The current balance is also the begin balance, that is how this starts. */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value = sessionParameters.sessionBaseAsset.config.initialBalance
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
|
||||
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
tradingEngine.current.episode.status.value = 'Open'
|
||||
tradingEngine.current.episode.serialNumber.value = 1
|
||||
tradingEngine.current.episode.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.episode.beginRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
|
||||
function updateExitType(exitType) {
|
||||
tradingEngine.current.episode.exitType.value = exitType
|
||||
}
|
||||
|
||||
function closeEpisode() {
|
||||
tradingEngine.current.episode.status.value = 'Closed'
|
||||
tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.episode.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.episode.episodeBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.episode.episodeQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
}
|
||||
|
||||
function resetEpisode() {
|
||||
tradingEngineModule.initializeNode(tradingEngine.current.episode)
|
||||
}
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.episode.status.value === 'Open') {
|
||||
tradingEngine.current.episode.end.value = tradingEngine.current.episode.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.episode.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.episode.episodeBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.episode.episodeQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
}
|
||||
}
|
||||
|
||||
function resetTradingEngineDataStructure() {
|
||||
if (tradingEngine.current.episode.status.value === 'Closed') {
|
||||
resetEpisode()
|
||||
}
|
||||
}
|
||||
|
||||
function updateCounters() {
|
||||
if (tradingEngine.current.episode.status.value === 'Open') {
|
||||
tradingEngine.current.episode.episodeCounters.periods.value++
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatistics() {
|
||||
|
||||
/* Daus Calculation */
|
||||
tradingEngine.current.episode.episodeStatistics.days.value =
|
||||
tradingEngine.current.episode.episodeCounters.periods.value *
|
||||
sessionParameters.timeFrame.config.value /
|
||||
global.ONE_DAY_IN_MILISECONDS
|
||||
}
|
||||
|
||||
function cycleBasedStatistics() {
|
||||
|
||||
calculateAssetsStatistics()
|
||||
calculateEpisodeStatistics()
|
||||
|
||||
function calculateAssetsStatistics() {
|
||||
/* Updating Profit Loss */
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value -
|
||||
sessionParameters.sessionBaseAsset.config.initialBalance
|
||||
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
|
||||
sessionParameters.sessionQuotedAsset.config.initialBalance
|
||||
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10)
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10)
|
||||
|
||||
/*
|
||||
Updating ROI
|
||||
|
||||
https://www.investopedia.com/articles/basics/10/guide-to-calculating-roi.asp
|
||||
*/
|
||||
tradingEngine.current.episode.episodeBaseAsset.ROI.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value /
|
||||
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * 100
|
||||
|
||||
tradingEngine.current.episode.episodeQuotedAsset.ROI.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value /
|
||||
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value * 100
|
||||
|
||||
tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10)
|
||||
tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10)
|
||||
|
||||
/* Updating Hit Ratio */
|
||||
if (tradingEngine.current.episode.episodeCounters.positions.value > 0) {
|
||||
tradingEngine.current.episode.episodeBaseAsset.hitRatio.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.hits.value /
|
||||
tradingEngine.current.episode.episodeCounters.positions.value
|
||||
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hits.value /
|
||||
tradingEngine.current.episode.episodeCounters.positions.value
|
||||
|
||||
tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10)
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10)
|
||||
}
|
||||
|
||||
/*
|
||||
Updating Annualized Rate Of Return
|
||||
|
||||
https://www.investopedia.com/terms/a/annualized-rate.asp
|
||||
*/
|
||||
tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value =
|
||||
Math.pow(
|
||||
(
|
||||
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value +
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value
|
||||
) / tradingEngine.current.episode.episodeBaseAsset.beginBalance.value
|
||||
,
|
||||
(365 / tradingEngine.current.episode.episodeStatistics.days.value)
|
||||
) - 1
|
||||
|
||||
tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value =
|
||||
Math.pow(
|
||||
(
|
||||
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value
|
||||
) / tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value
|
||||
,
|
||||
(365 / tradingEngine.current.episode.episodeStatistics.days.value)
|
||||
) - 1
|
||||
|
||||
tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value, 10)
|
||||
tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value, 10)
|
||||
|
||||
/* Updating Hit or Fail */
|
||||
if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) {
|
||||
tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit'
|
||||
} else {
|
||||
tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Fail'
|
||||
}
|
||||
if (tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value > 0) {
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Hit'
|
||||
} else {
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail'
|
||||
}
|
||||
}
|
||||
|
||||
function calculateEpisodeStatistics() {
|
||||
/* Updating Profit Loss */
|
||||
tradingEngine.current.episode.episodeStatistics.profitLoss.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.candle.close.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value
|
||||
|
||||
tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10)
|
||||
|
||||
/*
|
||||
Updating ROI
|
||||
|
||||
https://www.investopedia.com/articles/basics/10/guide-to-calculating-roi.asp
|
||||
*/
|
||||
tradingEngine.current.episode.episodeStatistics.ROI.value =
|
||||
(
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.episode.endRate.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value
|
||||
) / (
|
||||
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value
|
||||
) * 100
|
||||
|
||||
tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10)
|
||||
|
||||
/*
|
||||
Updating Annualized Rate Of Return
|
||||
|
||||
https://www.investopedia.com/terms/a/annualized-rate.asp
|
||||
*/
|
||||
tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value =
|
||||
Math.pow(
|
||||
(
|
||||
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value +
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value
|
||||
) /
|
||||
(
|
||||
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value * tradingEngine.current.episode.beginRate.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value
|
||||
)
|
||||
,
|
||||
(365 / tradingEngine.current.episode.episodeStatistics.days.value)
|
||||
) - 1
|
||||
|
||||
tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10)
|
||||
|
||||
/* Updating Hit or Fail */
|
||||
if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) {
|
||||
tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit'
|
||||
} else {
|
||||
tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateDistanceToEventsCounters() {
|
||||
/* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOn.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOn.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOff.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOff.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.closePosition.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.closePosition.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.createOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.createOrder.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.cancelOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.cancelOrder.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
exports.newTradingExecution = function newTradingExecution(bot, logger, tradingEngineModule) {
|
||||
/*
|
||||
The Trading Execution modules manages the execution nodes and execution algoritms.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Execution'
|
||||
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
runExecution: runExecution,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
const TRADING_ORDERS_MODULE = require('./TradingOrders.js')
|
||||
let tradingOrdersModule = TRADING_ORDERS_MODULE.newTradingOrders(bot, logger, tradingEngineModule)
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
tradingOrdersModule.initialize()
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingSystem = undefined
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
|
||||
tradingOrdersModule.finalize()
|
||||
tradingOrdersModule = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
tradingOrdersModule.mantain()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
tradingOrdersModule.reset()
|
||||
}
|
||||
|
||||
async function runExecution(
|
||||
executionNode,
|
||||
tradingEngineStage
|
||||
) {
|
||||
try {
|
||||
/* Trading Engine Stage Validations */
|
||||
if (tradingEngineStage.stageBaseAsset === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageBaseAsset.targetSize === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.targetSize === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageBaseAsset.sizePlaced === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.sizePlaced === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.sizeFilled === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'stageBaseAsset.feesPaid === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageQuotedAsset === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageQuotedAsset.targetSize === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.targetSize === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageQuotedAsset.sizePlaced === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.sizePlaced === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.sizeFilled === undefined', tradingEngineStage) }
|
||||
if (tradingEngineStage.stageQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'stageQuotedAsset.feesPaid === undefined', tradingEngineStage) }
|
||||
|
||||
await checkExecutionAlgorithms(executionNode)
|
||||
|
||||
async function checkExecutionAlgorithms(executionNode) {
|
||||
for (let i = 0; i < executionNode.executionAlgorithms.length; i++) {
|
||||
let executionAlgorithm = executionNode.executionAlgorithms[i]
|
||||
await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode)
|
||||
await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode)
|
||||
await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode)
|
||||
await tradingOrdersModule.checkOrders(tradingEngineStage, executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([executionNode.id, err.message])
|
||||
|
||||
if (typeof err === 'string' || err instanceof String) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err)
|
||||
}
|
||||
if (err.stack !== undefined) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function badDefinitionUnhandledException(err, message, node) {
|
||||
tradingSystem.errors.push([node.id, message])
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] -> " + message);
|
||||
logger.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name);
|
||||
logger.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type);
|
||||
logger.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config));
|
||||
if (err !== undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack);
|
||||
}
|
||||
throw 'Please fix the problem and try again.'
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,233 @@
|
||||
exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineModule, UTILITIES, FILE_STORAGE) {
|
||||
/*
|
||||
This module will load if necesary all the data outputs so that they can be appended with new
|
||||
records if needed. After running the simulation, it will save all the data outputs.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Output'
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
}
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger)
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger)
|
||||
|
||||
return thisObject
|
||||
|
||||
function finalize() {
|
||||
thisObject = undefined
|
||||
utilities = undefined
|
||||
fileStorage = undefined
|
||||
commons = undefined
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
}
|
||||
|
||||
async function start(chart, timeFrame, timeFrameLabel, tradingProcessDate) {
|
||||
try {
|
||||
|
||||
if (timeFrame > global.dailyFilePeriods[0][0]) {
|
||||
bot.processingDailyFiles = false
|
||||
} else {
|
||||
bot.processingDailyFiles = true
|
||||
}
|
||||
|
||||
/* Preparing everything for the Simulation */
|
||||
const TRADING_SIMULATION = require('./TradingSimulation.js')
|
||||
let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES)
|
||||
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
let outputDatasetsMap = new Map()
|
||||
|
||||
if (bot.processingDailyFiles === true) {
|
||||
/*
|
||||
For Daily Files, we are going to initialize the outputs at the first execution
|
||||
if we are not resuming, but also at every following execution except when
|
||||
we are at the head of the market, because at the head of the market we need to
|
||||
append more data to the same output files that already exists.
|
||||
*/
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
await initializeOutputs()
|
||||
} else {
|
||||
if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) {
|
||||
await readFiles()
|
||||
} else {
|
||||
await initializeOutputs()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
Form Market Files, we are only going to initialize the outputs at the first run
|
||||
and if we are not resuming execution. Subsequent runs means that we are at the
|
||||
head of the market. Remember that backtests finishes the session once the reach
|
||||
the final datetime.
|
||||
*/
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
await initializeOutputs()
|
||||
} else {
|
||||
await readFiles()
|
||||
}
|
||||
}
|
||||
|
||||
await tradingSimulation.runSimulation(
|
||||
chart,
|
||||
outputDatasetsMap,
|
||||
writeFiles
|
||||
)
|
||||
|
||||
tradingSimulation.finalize()
|
||||
return
|
||||
|
||||
async function initializeOutputs() {
|
||||
if (bot.processingDailyFiles) {
|
||||
await initializeDailyFiles()
|
||||
} else {
|
||||
await initializeMarketFiles()
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeDailyFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
outputDatasetsMap.set(dataset.parentNode.config.codeName, [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeMarketFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
outputDatasetsMap.set(dataset.parentNode.config.codeName, [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function readFiles() {
|
||||
/*
|
||||
This bot have an output of files that it generates. At every call to the bot, it needs to read the previously generated
|
||||
files in order to later append more information after the execution is over. Here in this function we are going to
|
||||
read those output files and get them ready for appending content during the simulation.
|
||||
*/
|
||||
if (bot.processingDailyFiles) {
|
||||
await readDailyFiles()
|
||||
} else {
|
||||
await readMarketFiles()
|
||||
}
|
||||
}
|
||||
|
||||
async function readMarketFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
|
||||
await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function readDailyFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
|
||||
let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function readOutputFile(fileName, filePath, productName) {
|
||||
filePath += '/' + fileName
|
||||
|
||||
let response = await fileStorage.asyncGetTextFile(filePath, true)
|
||||
|
||||
if (response.err.message === 'File does not exist.') {
|
||||
outputDatasetsMap.set(productName, [])
|
||||
return
|
||||
}
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw(response.err)
|
||||
}
|
||||
outputDatasetsMap.set(productName, JSON.parse(response.text))
|
||||
}
|
||||
|
||||
async function writeFiles() {
|
||||
/*
|
||||
The output of files which were appended with information during the simulation execution, now needs to be saved.
|
||||
*/
|
||||
if (bot.processingDailyFiles) {
|
||||
await writeDailyFiles()
|
||||
} else {
|
||||
await writeMarketFiles()
|
||||
}
|
||||
}
|
||||
|
||||
async function writeMarketFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
|
||||
await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeDailyFiles() {
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
|
||||
let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeOutputFile(fileName, filePath, productName) {
|
||||
filePath += '/' + fileName
|
||||
let fileContent = JSON.stringify(outputDatasetsMap.get(productName))
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent)
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw(response.err)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] start -> err = ' + err.stack)
|
||||
throw(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEngineModule) {
|
||||
/*
|
||||
This module packages all functions related to Positions.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Position'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
cycleBasedStatistics: cycleBasedStatistics,
|
||||
openPosition: openPosition,
|
||||
closingPosition: closingPosition,
|
||||
closePosition: closePosition,
|
||||
applyStopLossFormula: applyStopLossFormula,
|
||||
applyTakeProfitFormula: applyTakeProfitFormula,
|
||||
updateStopLoss: updateStopLoss,
|
||||
updateTakeProfit: updateTakeProfit,
|
||||
initialTargets: initialTargets,
|
||||
updateEnds: updateEnds,
|
||||
resetTradingEngineDataStructure: resetTradingEngineDataStructure,
|
||||
updateCounters: updateCounters,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
updateCounters()
|
||||
updateEnds()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resetTradingEngineDataStructure()
|
||||
}
|
||||
|
||||
function openPosition(situationName) {
|
||||
|
||||
/* Starting begin and end */
|
||||
tradingEngine.current.position.begin.value = tradingEngine.current.episode.cycle.lastBegin.value
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
tradingEngine.current.position.status.value = 'Open'
|
||||
tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value + 1
|
||||
tradingEngine.current.position.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.position.beginRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.position.positionBaseAsset.beginBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.beginBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
tradingEngine.current.position.situationName.value = situationName
|
||||
|
||||
/* Initializing Stop and Take Phase */
|
||||
tradingEngine.current.position.stopLoss.stopLossPhase.value = 1
|
||||
tradingEngine.current.position.takeProfit.takeProfitPhase.value = 1
|
||||
|
||||
/* Updating Episode Counters */
|
||||
tradingEngine.current.episode.episodeCounters.positions.value++
|
||||
|
||||
/* Inicializing this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value = 1
|
||||
|
||||
/* Remember the balance we had before taking the position to later calculate profit or loss */
|
||||
tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
}
|
||||
|
||||
function closingPosition(exitType) {
|
||||
tradingEngine.current.position.status.value = 'Closing'
|
||||
tradingEngine.current.position.exitType.value = exitType
|
||||
|
||||
/*
|
||||
By the time we figured out that the stop loss or take profit were hit, or
|
||||
for whatever reason the position needs to be closed, the take profit and
|
||||
stop loss values for the next candle have already been calculated. In order
|
||||
to avoid being plotted, we will put them in zero.
|
||||
*/
|
||||
tradingEngine.current.position.stopLoss.value = 0
|
||||
tradingEngine.current.position.takeProfit.value = 0
|
||||
}
|
||||
|
||||
function closePosition() {
|
||||
tradingEngine.current.position.status.value = 'Closed'
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
|
||||
/*
|
||||
Now that the position is closed, it is the right time to move this position from current to last at the Trading Engine data structure.
|
||||
*/
|
||||
tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position)
|
||||
|
||||
cycleBasedStatistics()
|
||||
|
||||
/* Updating Hits & Fails */
|
||||
if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) {
|
||||
tradingEngine.current.episode.episodeBaseAsset.hits.value++
|
||||
}
|
||||
if (tradingEngine.current.position.positionBaseAsset.profitLoss.value < 0) {
|
||||
tradingEngine.current.episode.episodeBaseAsset.fails.value++
|
||||
}
|
||||
if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) {
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hits.value++
|
||||
}
|
||||
if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value < 0) {
|
||||
tradingEngine.current.episode.episodeQuotedAsset.fails.value++
|
||||
}
|
||||
}
|
||||
|
||||
function applyStopLossFormula(formulas, formulaId) {
|
||||
updateStopLossTakeProfitFinalValue(tradingEngine.current.position.stopLoss)
|
||||
tradingEngine.current.position.stopLoss.value = formulas.get(formulaId)
|
||||
updateStopLossTakeProfitInitialValue(tradingEngine.current.position.stopLoss)
|
||||
updateStopLossTakeProfitBeginEnd(tradingEngine.current.position.stopLoss)
|
||||
}
|
||||
|
||||
function applyTakeProfitFormula(formulas, formulaId) {
|
||||
updateStopLossTakeProfitFinalValue(tradingEngine.current.position.takeProfit)
|
||||
tradingEngine.current.position.takeProfit.value = formulas.get(formulaId)
|
||||
updateStopLossTakeProfitInitialValue(tradingEngine.current.position.takeProfit)
|
||||
updateStopLossTakeProfitBeginEnd(tradingEngine.current.position.takeProfit)
|
||||
}
|
||||
|
||||
function updateStopLossTakeProfitInitialValue(node) {
|
||||
/*
|
||||
We store the first Stop Loss or Take Profit value in a separate node.
|
||||
To know if it is the first one we compare its current value to the initial value.
|
||||
*/
|
||||
if (node.initialValue.value === node.initialValue.config.initialValue) {
|
||||
node.initialValue.value = node.value
|
||||
}
|
||||
}
|
||||
|
||||
function updateStopLossTakeProfitFinalValue(node) {
|
||||
/*
|
||||
We set the final value just before calcualting the new value, since the new calculated
|
||||
value is going to be discarded if the position is closed after it is calculated, but
|
||||
during the same candle.
|
||||
*/
|
||||
node.finalValue.value = node.value
|
||||
}
|
||||
|
||||
function updateStopLossTakeProfitBeginEnd(node) {
|
||||
/*
|
||||
Both the Stop Loss and the Take Profit have their own Begin and End.
|
||||
Ther reason for this is because they represent targets to be checked
|
||||
at the next candle, so it does not apply that they share the begin and
|
||||
end of the position itself.
|
||||
*/
|
||||
node.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
node.end.value =
|
||||
tradingEngine.current.episode.candle.end.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
}
|
||||
|
||||
function updateStopLoss(phase) {
|
||||
tradingEngine.current.position.stopLoss.stopLossPhase.value = phase
|
||||
}
|
||||
|
||||
function updateTakeProfit(phase) {
|
||||
tradingEngine.current.position.takeProfit.takeProfitPhase.value = phase
|
||||
}
|
||||
|
||||
function initialTargets(tradingSystemStageNode, tradingEngineStageNode) {
|
||||
|
||||
if (tradingSystemStageNode.initialTargets === undefined) {
|
||||
const message = 'Stage without Initial Targets node. Add one please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode)
|
||||
}
|
||||
|
||||
setTargetRate()
|
||||
setTargetSize()
|
||||
|
||||
function setTargetRate() {
|
||||
if (tradingSystemStageNode.initialTargets.targetRate === undefined) {
|
||||
const message = 'Target Rate Node not Found. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets)
|
||||
}
|
||||
if (tradingSystemStageNode.initialTargets.targetRate.formula === undefined) {
|
||||
const message = 'Formula of Target Rate Node not Found. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate)
|
||||
}
|
||||
|
||||
let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetRate.formula.id)
|
||||
if (value === undefined) {
|
||||
const message = 'Target Rate can not be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula)
|
||||
}
|
||||
if (isNaN(value)) {
|
||||
const message = 'Target Rate must be a number. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula)
|
||||
}
|
||||
|
||||
switch (tradingSystemStageNode.type) {
|
||||
case 'Open Stage': {
|
||||
tradingEngine.current.position.entryTargetRate.value = global.PRECISE(value, 10)
|
||||
break
|
||||
}
|
||||
case 'Close Stage': {
|
||||
tradingEngine.current.position.exitTargetRate.value = global.PRECISE(value, 10)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTargetSize() {
|
||||
/* Basic Validation */
|
||||
if (
|
||||
tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined &&
|
||||
tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined
|
||||
) {
|
||||
const message = 'Only Target Size In Base Asset or Target Size In Quoted Asset is allowed. Remove one of them please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode)
|
||||
}
|
||||
|
||||
/* Position In Base Asset */
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined) {
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) {
|
||||
let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula.id)
|
||||
if (value === undefined) {
|
||||
const message = 'Target Size In Base Asset cannot be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
}
|
||||
if (value === 0) {
|
||||
const message = 'Target Size In Base Asset cannot be zero. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
}
|
||||
|
||||
switch (tradingSystemStageNode.type) {
|
||||
case 'Open Stage': {
|
||||
tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = global.PRECISE(value, 10)
|
||||
tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value =
|
||||
global.PRECISE(value * tradingEngine.current.position.entryTargetRate.value, 10)
|
||||
break
|
||||
}
|
||||
case 'Close Stage': {
|
||||
tradingEngine.current.position.positionBaseAsset.exitTargetSize.value = global.PRECISE(value, 10)
|
||||
tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value =
|
||||
global.PRECISE(value * tradingEngine.current.position.exitTargetRate.value, 10)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember how the end user defined this stage. */
|
||||
tradingEngineStageNode.stageDefinedIn.value = 'Base Asset'
|
||||
} else {
|
||||
const message = 'You need to specify a Formula for this.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset)
|
||||
}
|
||||
}
|
||||
|
||||
/* Position In Quoted Asset */
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined) {
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) {
|
||||
let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula.id)
|
||||
if (value === undefined) {
|
||||
const message = 'Target Size In Quoted Asset cannot be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
}
|
||||
if (value === 0) {
|
||||
const message = 'Target Size In Quoted Asset cannot be zero. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
}
|
||||
switch (tradingSystemStageNode.type) {
|
||||
case 'Open Stage': {
|
||||
tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = global.PRECISE(value, 10)
|
||||
tradingEngine.current.position.positionBaseAsset.entryTargetSize.value =
|
||||
global.PRECISE(value / tradingEngine.current.position.entryTargetRate.value, 10)
|
||||
break
|
||||
}
|
||||
case 'Close Stage': {
|
||||
tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value = global.PRECISE(value, 10)
|
||||
tradingEngine.current.position.positionBaseAsset.exitTargetSize.value =
|
||||
global.PRECISE(value / tradingEngine.current.position.exitTargetRate.value, 10)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember how the end user defined this stage. */
|
||||
tradingEngineStageNode.stageDefinedIn.value = 'Quoted Asset'
|
||||
} else {
|
||||
const message = 'You need to specify a Formula for this.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.position.status.value === 'Open') {
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.position.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
}
|
||||
}
|
||||
|
||||
function resetTradingEngineDataStructure() {
|
||||
if (tradingEngine.current.position.status.value === 'Closed') {
|
||||
tradingEngineModule.initializeNode(tradingEngine.current.position)
|
||||
}
|
||||
}
|
||||
|
||||
function updateCounters() {
|
||||
if (tradingEngine.current.position.status.value === 'Open') {
|
||||
tradingEngine.current.position.positionCounters.periods.value++
|
||||
}
|
||||
}
|
||||
|
||||
function cycleBasedStatistics() {
|
||||
|
||||
calculateAssetsStatistics()
|
||||
calculatePositionStatistics()
|
||||
|
||||
function calculateAssetsStatistics() {
|
||||
/* Profit Loss Calculation */
|
||||
tradingEngine.current.position.positionBaseAsset.profitLoss.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value -
|
||||
tradingEngine.current.position.positionBaseAsset.beginBalance
|
||||
|
||||
tradingEngine.current.position.positionQuotedAsset.profitLoss.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
|
||||
tradingEngine.current.position.positionQuotedAsset.beginBalance
|
||||
|
||||
tradingEngine.current.position.positionBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.profitLoss.value, 10)
|
||||
tradingEngine.current.position.positionQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.profitLoss.value, 10)
|
||||
|
||||
/* ROI Calculation */
|
||||
tradingEngine.current.position.positionBaseAsset.ROI.value =
|
||||
tradingEngine.current.position.positionBaseAsset.profitLoss.value * 100 /
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value
|
||||
|
||||
tradingEngine.current.position.positionQuotedAsset.ROI.value =
|
||||
tradingEngine.current.position.positionQuotedAsset.profitLoss.value * 100 /
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value
|
||||
|
||||
tradingEngine.current.position.positionBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionBaseAsset.ROI.value, 10)
|
||||
tradingEngine.current.position.positionQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.position.positionQuotedAsset.ROI.value, 10)
|
||||
|
||||
/* Hit Fail Calculation */
|
||||
if (tradingEngine.current.position.positionBaseAsset.ROI.value > 0) {
|
||||
tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Hit'
|
||||
}
|
||||
if (tradingEngine.current.position.positionBaseAsset.ROI.value < 0) {
|
||||
tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Fail'
|
||||
}
|
||||
if (tradingEngine.current.position.positionBaseAsset.ROI.value === 0) {
|
||||
tradingEngine.current.position.positionBaseAsset.hitFail.value = 'Even'
|
||||
}
|
||||
if (tradingEngine.current.position.positionQuotedAsset.ROI.value > 0) {
|
||||
tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Hit'
|
||||
}
|
||||
if (tradingEngine.current.position.positionQuotedAsset.ROI.value < 0) {
|
||||
tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Fail'
|
||||
}
|
||||
if (tradingEngine.current.position.positionQuotedAsset.ROI.value === 0) {
|
||||
tradingEngine.current.position.positionQuotedAsset.hitFail.value = 'Even'
|
||||
}
|
||||
}
|
||||
|
||||
function calculatePositionStatistics() {
|
||||
/* Profit Loss Calculation */
|
||||
tradingEngine.current.position.positionStatistics.profitLoss.value =
|
||||
(
|
||||
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.position.endRate.value +
|
||||
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value
|
||||
)
|
||||
|
||||
tradingEngine.current.position.positionStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.position.positionStatistics.profitLoss.value, 10)
|
||||
|
||||
/* ROI Calculation */
|
||||
tradingEngine.current.position.positionStatistics.ROI.value =
|
||||
(
|
||||
tradingEngine.current.position.positionStatistics.profitLoss.value
|
||||
)
|
||||
* 100 /
|
||||
(
|
||||
tradingEngine.current.position.positionBaseAsset.beginBalance * tradingEngine.current.position.beginRate.value +
|
||||
tradingEngine.current.position.positionQuotedAsset.beginBalance
|
||||
)
|
||||
tradingEngine.current.position.positionStatistics.ROI.value = global.PRECISE(tradingEngine.current.position.positionStatistics.ROI.value, 10)
|
||||
|
||||
/* Hit Fail Calculation */
|
||||
if (tradingEngine.current.position.positionStatistics.ROI.value > 0) {
|
||||
tradingEngine.current.position.positionStatistics.hitFail.value = 'Hit'
|
||||
}
|
||||
if (tradingEngine.current.position.positionStatistics.ROI.value < 0) {
|
||||
tradingEngine.current.position.positionStatistics.hitFail.value = 'Fail'
|
||||
}
|
||||
if (tradingEngine.current.position.positionStatistics.ROI.value === 0) {
|
||||
tradingEngine.current.position.positionStatistics.hitFail.value = 'Even'
|
||||
}
|
||||
|
||||
/* Days Calculation */
|
||||
tradingEngine.current.position.positionStatistics.days.value =
|
||||
tradingEngine.current.position.positionCounters.periods.value *
|
||||
sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS
|
||||
|
||||
tradingEngine.current.position.positionStatistics.days.value = global.PRECISE(tradingEngine.current.position.positionStatistics.days.value, 10)
|
||||
}
|
||||
}
|
||||
|
||||
function badDefinitionUnhandledException(err, message, node) {
|
||||
tradingSystem.errors.push([node.id, message])
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] -> " + message);
|
||||
logger.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name);
|
||||
logger.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type);
|
||||
logger.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config));
|
||||
if (err !== undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack);
|
||||
}
|
||||
throw 'Please fix the problem and try again.'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,786 @@
|
||||
exports.newTradingProcess = function newTradingProcess(bot, logger, UTILITIES) {
|
||||
/*
|
||||
This Module will load all the process data dependencies from files and send them downstream.
|
||||
After execution, will save the time range and status report of the process.
|
||||
*/
|
||||
const FULL_LOG = true;
|
||||
const MODULE_NAME = "Trading Process";
|
||||
const GMT_SECONDS = ':00.000 GMT+0000';
|
||||
|
||||
thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
};
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger);
|
||||
|
||||
let statusDependencies;
|
||||
let dataDependenciesModule;
|
||||
let dataFiles = new Map();
|
||||
let multiPeriodDataFiles = new Map();
|
||||
|
||||
const FILE_STORAGE = require('../FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger);
|
||||
|
||||
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
|
||||
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
|
||||
|
||||
let TRADING_OUTPUT_MODULE = require("./TradingOutput")
|
||||
let tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, tradingEngineModule, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) {
|
||||
try {
|
||||
logger.fileName = MODULE_NAME
|
||||
logger.initialize()
|
||||
|
||||
statusDependencies = pStatusDependencies
|
||||
dataDependenciesModule = pDataDependencies
|
||||
processConfig = pProcessConfig
|
||||
|
||||
tradingOutputModule.initialize()
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
|
||||
tradingEngineModule.finalize()
|
||||
tradingEngineModule = undefined
|
||||
|
||||
tradingOutputModule.finalize()
|
||||
tradingOutputModule = undefined
|
||||
|
||||
dataFiles = undefined
|
||||
multiPeriodDataFiles = undefined
|
||||
statusDependencies = undefined
|
||||
dataDependenciesModule = undefined
|
||||
fileStorage = undefined
|
||||
processConfig = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
async function start(callBackFunction) {
|
||||
try {
|
||||
let currentTimeFrame
|
||||
let currentTimeFrameLabel
|
||||
|
||||
/* Context Variables */
|
||||
let contextVariables = {
|
||||
lastFile: undefined, // Datetime of the last file files sucessfully produced by this process.
|
||||
dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history.
|
||||
dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process.
|
||||
};
|
||||
|
||||
getContextVariables()
|
||||
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
Here is where the Trading Engine and Trading Systems received are moved to the simulation state.
|
||||
*/
|
||||
bot.simulationState.tradingEngine = bot.TRADING_ENGINE
|
||||
bot.simulationState.tradingSystem = bot.TRADING_SYSTEM
|
||||
}
|
||||
|
||||
/* We set up the Trading Engine Module. */
|
||||
tradingEngineModule.initialize()
|
||||
|
||||
/* Initializing the Trading Process Date */
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
This funcion is going to be called many times by the Trading Bot Loop.
|
||||
Only during the first execution and when the User is not resuming the execution
|
||||
of a stopped session / task; we are going to initialize the Process Date Time.
|
||||
This variable tell us which day we are standing at, specially while working
|
||||
with Daily Files. From this Date is that we are going to load the Daily Files.
|
||||
*/
|
||||
bot.simulationState.tradingEngine.current.episode.processDate.value = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime).valueOf()
|
||||
}
|
||||
|
||||
/*
|
||||
This is the Date that is going to be used across the execution of this Trading Process.
|
||||
We need this because it has a different life cycle than the processData stored at the
|
||||
Trading Engine data structure. This date has to remain the same during the whole execution
|
||||
of the Trading Process until the end, inclusind the writting of Data Ranges and Status Reports.
|
||||
The processDate of the Trading Engine data structure on the other hand can be changed during
|
||||
the simulation loop, once we discover that all candles from a certain date have benn processed.
|
||||
Here is the point where we sync one and the other.
|
||||
*/
|
||||
let tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
await processSingleFiles()
|
||||
|
||||
if (await processMarketFiles() === false) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
This is the Data Structure used at the Simulation with all indicator data.
|
||||
We start creating it right here.
|
||||
*/
|
||||
let chart = {}
|
||||
/*
|
||||
Here we check if we need to get Daily Files or not. As an optimization, when
|
||||
we are running on a Time Frame of 1hs or above, we are not going to load
|
||||
dependencies on Daily Files. The way we recognize that is by checking if
|
||||
we alreaady set a value to currentTimeFrame. We are also not going to loop
|
||||
through days if we are processing market files.
|
||||
*/
|
||||
if (currentTimeFrame) {
|
||||
/* We are processing Market Files */
|
||||
/*
|
||||
With all the indicators data files loaded, we will build the chart object
|
||||
data structure that will be used in user-defied conditions and formulas.
|
||||
*/
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
buildCharts(chart)
|
||||
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date. No candles found at.')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
/* We are processing Daily Files */
|
||||
do {
|
||||
global.EMIT_SESSION_STATUS(bot.TRADING_SESSION_STATUS, bot.TRADING_SESSIONKey)
|
||||
/*
|
||||
We update the Trading Process Date with the date calculated at the simulation.
|
||||
We will use this date to load indicator and output files. After that we will
|
||||
use it to save Output Files and later the Data Ranges. This is the point where
|
||||
the date calculated by the Simulation is applied at the Trading Process Level.
|
||||
*/
|
||||
tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
|
||||
if (checkStopTaskGracefully() === false) { break }
|
||||
if (checkStopProcessing() === false) { break }
|
||||
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
if (await processDailyFiles() === false) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
With all the indicators data files loaded, we will build the chart object
|
||||
data structure that will be used in user-defied conditions and formulas.
|
||||
*/
|
||||
buildCharts(chart)
|
||||
/*
|
||||
The process of generating the output includes the trading simulation.
|
||||
*/
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
If for any reason the session was stopped, we will break this loop and exit the process.
|
||||
*/
|
||||
if (bot.STOP_SESSION === true) { break }
|
||||
/*
|
||||
When we get to the end of the market, we need to break this process loop in order
|
||||
to let time pass, new information be collected from the exchange, new data built
|
||||
into indicators, and eventually a new execution of this process.
|
||||
*/
|
||||
if (checkStopHeadOfTheMarket() === false) { break }
|
||||
|
||||
}
|
||||
while (true)
|
||||
}
|
||||
|
||||
checkIfSessionMustStop()
|
||||
/*
|
||||
Everything worked as expected. We return an OK code and wait for
|
||||
the Bot Loop to call us again later.
|
||||
*/
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
function checkThereAreCandles(chart) {
|
||||
let sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
|
||||
let candles = chart[propertyName].candles
|
||||
|
||||
if (candles.length === 0) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function getContextVariables() {
|
||||
try {
|
||||
let thisReport;
|
||||
let statusReport;
|
||||
|
||||
/* We are going to use the start date as beging of market date. */
|
||||
contextVariables.dateBeginOfMarket = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)
|
||||
/*
|
||||
Here we get the status report from the bot who knows which is the end of the market.
|
||||
*/
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.lastFile === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport = " + JSON.stringify(thisReport));
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: "Dependency not ready."
|
||||
}
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message);
|
||||
callBackFunction(customOK);
|
||||
return
|
||||
}
|
||||
|
||||
contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf());
|
||||
|
||||
/* Finally we get our own Status Report. */
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Self Reference")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
bot.simulationState = thisReport.simulationState;
|
||||
if (bot.simulationState === undefined) { bot.simulationState = {} } // This should happen only when there is no status report
|
||||
|
||||
if (thisReport.lastFile !== undefined) {
|
||||
if (bot.RESUME !== true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); }
|
||||
startFromBegining();
|
||||
return
|
||||
}
|
||||
contextVariables.lastFile = new Date(thisReport.lastFile);
|
||||
return
|
||||
|
||||
} else {
|
||||
/*
|
||||
We are here becuse either:
|
||||
1. There is no status report
|
||||
2. There is no Last File (this happens on Market Files)
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); }
|
||||
startFromBegining();
|
||||
return
|
||||
}
|
||||
|
||||
function startFromBegining() {
|
||||
contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS);
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); }
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack);
|
||||
if (err.message === "Cannot read property 'file' of undefined") {
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. ");
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys));
|
||||
}
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
async function processSingleFiles() {
|
||||
dataFiles = new Map();
|
||||
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (datasetModule.node.config.codeName !== "Single-File") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
continue
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName;
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
}
|
||||
|
||||
let mapKey = "Single Files"
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
|
||||
async function processMarketFiles() {
|
||||
/*
|
||||
We do market files first since if the simulation is run on daily files, there will
|
||||
be a loop to get each of those files and we do not need that loop to reload market files.
|
||||
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
for (let n = 0; n < global.marketFilesPeriods.length; n++) {
|
||||
const timeFrame = global.marketFilesPeriods[n][0];
|
||||
const timeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
|
||||
dataFiles = new Map();
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.marketFilesPeriods[n][0];
|
||||
currentTimeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
}
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel;
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.message === 'File does not exist.') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record)
|
||||
dataFiles.set(dependency.id, trimmedDataFile);
|
||||
|
||||
function trimDataFile(dataFile, recordDefinition) {
|
||||
/*
|
||||
Here we will discard all the records in a file that are outside of the current time range.
|
||||
We will include the las element previous to the begining of the time range. This is needed
|
||||
because during the simulation, the current period is not the open one, but the previous to
|
||||
the open, and if we do not include the previous to the initial datetime there will be no
|
||||
current objects at the begining of the simulation for many time frames.
|
||||
*/
|
||||
let beginIndex
|
||||
let endIndex
|
||||
let result = []
|
||||
for (let i = 0; i < recordDefinition.properties.length; i++) {
|
||||
let property = recordDefinition.properties[i]
|
||||
if (property.config.codeName === 'begin') {
|
||||
beginIndex = i
|
||||
}
|
||||
if (property.config.codeName === 'end') {
|
||||
endIndex = i
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < dataFile.length; i++) {
|
||||
let dataRecord = dataFile[i]
|
||||
let begin = dataRecord[beginIndex]
|
||||
let end = dataRecord[endIndex]
|
||||
if (end + timeFrame < bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element
|
||||
if (begin > bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime) { continue }
|
||||
result.push(dataRecord)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
let mapKey = global.marketFilesPeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function processDailyFiles() {
|
||||
/* Telling the world we are alive and doing well and which date we are processing right now. */
|
||||
let processingDateString = tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
bot.processHeartBeat(processingDateString, undefined, "Running...")
|
||||
|
||||
/*
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
for (let n = 0; n < global.dailyFilePeriods.length; n++) {
|
||||
const timeFrame = global.dailyFilePeriods[n][0];
|
||||
const timeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
|
||||
if (processConfig.framework.validtimeFrames !== undefined) {
|
||||
let validPeriod = false;
|
||||
for (let i = 0; i < processConfig.framework.validtimeFrames.length; i++) {
|
||||
let period = processConfig.framework.validtimeFrames[i];
|
||||
if (period === timeFrameLabel) { validPeriod = true }
|
||||
}
|
||||
if (validPeriod === false) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.dailyFilePeriods[n][0];
|
||||
currentTimeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
}
|
||||
|
||||
dataFiles = new Map();
|
||||
|
||||
/*
|
||||
We will iterate through all dependencies, in order to load the
|
||||
files that later will end up at the chart data structure.
|
||||
*/
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath;
|
||||
}
|
||||
let fileName = "Data.json";
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.message === 'File does not exist.') {
|
||||
return false
|
||||
}
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
}
|
||||
|
||||
let mapKey = global.dailyFilePeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkStopHeadOfTheMarket() {
|
||||
/*
|
||||
We need to check if we have reached the head of the market in order to know
|
||||
when to break the Daily Files Process loop and give time for a new candles /
|
||||
indicators to be built and continue the processing once this process is called
|
||||
again.
|
||||
*/
|
||||
if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopTaskGracefully() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopProcessing() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (bot.STOP_SESSION === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function buildCharts(chart) {
|
||||
const COMMONS = require('../Commons.js')
|
||||
let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let mainDependency = {}
|
||||
|
||||
/* The first phase here is about checking that we have everything we need at the definition level. */
|
||||
let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency')
|
||||
/*
|
||||
We will filter ourt declared dependencies that are not present in the workspace.
|
||||
This will allow the user to have less Data Mines loaded at the workspace
|
||||
that the ones that a Trading Mine depends on.
|
||||
*/
|
||||
dataDependencies = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(dataDependencies)
|
||||
|
||||
if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return }
|
||||
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
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 timeFrameLabel = marketFilesPeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
|
||||
let timeFrameLabel = global.dailyFilePeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
/* Single Files */
|
||||
let dataFiles = multiPeriodDataFiles.get('Single Files')
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'atAnyTimeFrame'
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
async function generateOutput(chart) {
|
||||
await tradingOutputModule.start(
|
||||
chart,
|
||||
currentTimeFrame,
|
||||
currentTimeFrameLabel,
|
||||
tradingProcessDate
|
||||
)
|
||||
|
||||
/*
|
||||
From here on, all other loops executions wont be the first execution and also
|
||||
we will consider that it is not resuming a previous execution as well.
|
||||
*/
|
||||
bot.FIRST_EXECUTION = false
|
||||
bot.RESUME = false
|
||||
}
|
||||
|
||||
async function writeProcessFiles() {
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
|
||||
await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel)
|
||||
await writeDataRanges()
|
||||
|
||||
if (currentTimeFrame > global.dailyFilePeriods[0][0]) {
|
||||
await writeMarketStatusReport(currentTimeFrameLabel)
|
||||
} else {
|
||||
await writeDailyStatusReport(currentTimeFrameLabel)
|
||||
}
|
||||
|
||||
async function writeDataRanges() {
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeDataRange(productCodeName);
|
||||
}
|
||||
|
||||
async function writeDataRange(productCodeName) {
|
||||
let dataRange = {
|
||||
begin: contextVariables.dateBeginOfMarket.valueOf(),
|
||||
end: tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS
|
||||
}
|
||||
|
||||
let fileContent = JSON.stringify(dataRange);
|
||||
let fileName = '/Data.Range.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
/*
|
||||
Raise the event that the Data Range was Updated.
|
||||
*/
|
||||
let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
dateRange: dataRange
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event)
|
||||
}
|
||||
}
|
||||
|
||||
async function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) {
|
||||
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily')
|
||||
await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Market')
|
||||
|
||||
async function writeTimeFramesFile(currentTimeFrameLabel, productCodeName, processType) {
|
||||
|
||||
let timeFramesArray = []
|
||||
timeFramesArray.push(currentTimeFrameLabel)
|
||||
|
||||
let fileContent = JSON.stringify(timeFramesArray)
|
||||
let fileName = '/Time.Frames.json';
|
||||
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + processType + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeDailyStatusReport(currentTimeFrameLabel) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey)
|
||||
|
||||
thisReport.file.lastExecution = bot.currentDaytime
|
||||
thisReport.file.lastFile = tradingProcessDate
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
await thisReport.asyncSave()
|
||||
}
|
||||
|
||||
async function writeMarketStatusReport(currentTimeFrameLabel) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey);
|
||||
|
||||
thisReport.file.lastExecution = bot.processDatetime
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
|
||||
logger.newInternalLoop(bot.codeName, bot.process, tradingProcessDate)
|
||||
await thisReport.asyncSave()
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfSessionMustStop() {
|
||||
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
/*
|
||||
Backtests needs only one execution of this process to complete.
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] checkIfSessionMustStop -> Backtesting Session Finished. Stopping the Session now. ') }
|
||||
bot.TRADING_SESSION.stop('Backtesting Session Finished.')
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
/* An unhandled exception occured. in this case we return Fail and log the stack. */
|
||||
if (err.stack) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Unhandled Exception. Will Abort this process. err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* Some expected file was not found. We will return a RETRY code and move on. */
|
||||
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] File not Found. Will Retry the Process Loop.")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* Some other handled exception occured. We return Fail and move on. */
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Handled Exception. Will Abort this process. err = " + err.message);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
async function asyncGetDatasetFile(datasetModule, filePath, fileName) {
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived);
|
||||
function onFileReceived(err, text) {
|
||||
|
||||
let response = {
|
||||
err: err,
|
||||
text: text
|
||||
}
|
||||
resolve(response)
|
||||
}
|
||||
});
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
/*
|
||||
This module facilitates the appending of records to the output of the process.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Records'
|
||||
|
||||
let thisObject = {
|
||||
appendRecords: appendRecords,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
let outputDatasetsMap
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize(pOutputDatasetsMap) {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
outputDatasetsMap = pOutputDatasetsMap // These are the files turned into arrays, stored in a Map by Product codeName.
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
sessionParameters = undefined
|
||||
outputDatasetsMap = undefined
|
||||
}
|
||||
|
||||
function appendRecords() {
|
||||
/*
|
||||
Here we add records to the output files. At the product config property nodePath
|
||||
we have a pointer to the node that have the information we need to extract.
|
||||
Later, based on the product record definition we will extract each individual value.
|
||||
*/
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
|
||||
for (let i = 0; i < outputDatasets.length; i++) {
|
||||
let outputDatasetNode = outputDatasets[i]
|
||||
let dataset = outputDatasetNode.referenceParent
|
||||
let product = dataset.parentNode
|
||||
let outputDatasetArray = outputDatasetsMap.get(product.config.codeName)
|
||||
|
||||
if (bot.processingDailyFiles === true && dataset.config.type === 'Daily Files') {
|
||||
persistRecords()
|
||||
}
|
||||
|
||||
if (bot.processingDailyFiles === false && dataset.config.type === 'Market Files') {
|
||||
persistRecords()
|
||||
}
|
||||
|
||||
function persistRecords() {
|
||||
/*
|
||||
There are a few products configured to be saved only at an specific cycle.
|
||||
*/
|
||||
if (product.config.saveAtCycle !== undefined) {
|
||||
if (tradingEngine.current.episode.cycle.value !== product.config.saveAtCycle) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean the file from information of previous executions */
|
||||
//pruneOutputFile(product, outputDatasetArray, tradingEngine.current.episode.candle.end.value)
|
||||
|
||||
/* Clean Open Records */
|
||||
if (product.config.saveAsObjects === true) {
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) {
|
||||
/* Remove Open Records */
|
||||
spliceOpenRecords(j, product, outputDatasetArray)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The product root can be a node or a node property of type array.
|
||||
*/
|
||||
let productRoot
|
||||
try {
|
||||
productRoot = eval(product.config.nodePath)
|
||||
} catch (err) {
|
||||
badDefinitionUnhandledException(err, 'Cannot get productRoot -> nodePath = ' + product.config.nodePath, product, undefined)
|
||||
}
|
||||
|
||||
if (product.config.nodePathType === 'array') {
|
||||
/*
|
||||
This means that the configured nodePath is not pointing to a node, but to a node property that is an array.
|
||||
For that reason we will assume that each element of the array is a record to be outputed
|
||||
*/
|
||||
for (let index = 0; index < productRoot.length; index++) {
|
||||
/*
|
||||
The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values.
|
||||
*/
|
||||
let productRootNode = productRoot[index]
|
||||
let record = scanRecordDefinition(product, productRootNode, index)
|
||||
if (record !== undefined) {
|
||||
/*
|
||||
We will add the index value to the record itself, so that the plotter can know to which
|
||||
brach of the trading engine data structure it belongs.
|
||||
*/
|
||||
record.push(index)
|
||||
persistIndividualRecord(record, product, outputDatasetArray)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
This means that the configured nodePath points to a single node, which is the one whose children constitutes
|
||||
the record to be saved at the output file.
|
||||
*/
|
||||
/*
|
||||
The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values.
|
||||
*/
|
||||
let productRootNode = productRoot
|
||||
let record = scanRecordDefinition(product, productRootNode)
|
||||
if (record !== undefined) {
|
||||
persistIndividualRecord(record, product, outputDatasetArray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanRecordDefinition(product, productRootNode, index) {
|
||||
|
||||
let record = []
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
/*
|
||||
The Property Root Node is the Root of the Hiriarchy branch we must find in order
|
||||
to get the node where we are going to extract the value. Initially
|
||||
we point it to the Product Root Node, because that is the default in case
|
||||
a property does not have at its configuration a different nodePath configured
|
||||
pointing to an specific Root for the property.
|
||||
*/
|
||||
let propertyRootNode = productRootNode
|
||||
/*
|
||||
If we find at the configuration a nodePath, then we take this path to find
|
||||
the Root Node specifically for this property only.
|
||||
*/
|
||||
if (recordProperty.config.nodePath !== undefined) {
|
||||
if (recordProperty.config.nodePath === "tradingEngine.exchangeOrders.marketBuyOrders.marketOrders[index].orderBaseAsset") {
|
||||
let a = tradingEngine
|
||||
}
|
||||
try {
|
||||
propertyRootNode = eval(recordProperty.config.nodePath)
|
||||
} catch (err) {
|
||||
badDefinitionUnhandledException(err, 'Error Evaluating Record Property nodePath -> nodePath = ' + recordProperty.config.nodePath, product, recordProperty)
|
||||
}
|
||||
}
|
||||
/*
|
||||
The Target Node is the node from where we are going to exctract the value.
|
||||
We will use the codeName of the Record Property to match it with
|
||||
any of the properties of the Root Node to get the Target Node.
|
||||
*/
|
||||
let targetNode
|
||||
try {
|
||||
targetNode = propertyRootNode[recordProperty.config.codeName]
|
||||
} catch (err) {
|
||||
badDefinitionUnhandledException(err, 'Error setting Target Node.', product, recordProperty)
|
||||
}
|
||||
/*
|
||||
If the codeName of the Record Property can not match the name of the property at
|
||||
the target node, the user can explicitly specify the property name at the configuration,
|
||||
and in those cases we need to use that. This happens when there are many Record Properties
|
||||
pointing to the same property at the Target Node.
|
||||
*/
|
||||
if (recordProperty.config.childProperty !== undefined) {
|
||||
targetNode = propertyRootNode[recordProperty.config.childProperty]
|
||||
}
|
||||
/*
|
||||
It can happen that intead of having a Node in targetNode what we have is an
|
||||
array of nodes. We need to pick one of the elements of the array and for that
|
||||
we use the Index value we find at the configuration of the Record Property.
|
||||
*/
|
||||
if (recordProperty.config.index !== undefined) {
|
||||
try {
|
||||
targetNode = targetNode[recordProperty.config.index]
|
||||
} catch (err) {
|
||||
badDefinitionUnhandledException(err, 'Error setting Target Node.', product, recordProperty)
|
||||
}
|
||||
}
|
||||
/*
|
||||
By Default the value is extracted from the value property of the Target Node.
|
||||
But it might happen the the Target Node does not exist for example when there is an Array
|
||||
of Nodes defined in the Record Properties but not all of them exist at the Root Node.
|
||||
We filter out those cases by not extracting the value from the value property.
|
||||
*/
|
||||
let value = 0 // This is a default value, since we do not want null in files because it breakes JSON format.
|
||||
if (targetNode !== undefined) {
|
||||
if (targetNode.type !== undefined) {
|
||||
/*
|
||||
In this case the Target Node is really a node (since it has a type), so we extract the value
|
||||
from its value property.
|
||||
*/
|
||||
value = targetNode.value
|
||||
|
||||
if (recordProperty.config.decimals !== undefined) {
|
||||
try {
|
||||
value = Number(value.toFixed(recordProperty.config.decimals))
|
||||
} catch (err) {
|
||||
badDefinitionUnhandledException(err, 'Error applying configured decimals.', product, recordProperty)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
In this case the Target Node is not really node, but the value itself. Se we return this as the
|
||||
value of the Record Property.
|
||||
*/
|
||||
value = targetNode
|
||||
}
|
||||
}
|
||||
|
||||
/* We are not going to add records where the begin or end are missing */
|
||||
if (
|
||||
recordProperty.config.codeName === 'begin' && value === 0 ||
|
||||
recordProperty.config.codeName === 'end' && value === 0
|
||||
) { return }
|
||||
|
||||
if (recordProperty.config.isString !== true && Array.isArray(value) !== true) {
|
||||
value = safeNumericValue(value)
|
||||
}
|
||||
if (recordProperty.config.isString === true && Array.isArray(value) !== true) {
|
||||
value = safeStringValue(value)
|
||||
}
|
||||
record.push(value)
|
||||
}
|
||||
return record
|
||||
}
|
||||
|
||||
function persistIndividualRecord(record, product, outputDatasetArray) {
|
||||
|
||||
if (product.config.saveAsObjects === true) {
|
||||
/* For saving objects we need to take care of a different set of rules. */
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) {
|
||||
let propertyValue = record[j]
|
||||
|
||||
if (bot.processingDailyFiles) {
|
||||
if (product.config.doNotCutObjectInDays !== true) {
|
||||
/*
|
||||
By default we will cut objects in days.
|
||||
*/
|
||||
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
When dealing with Daily Files, we need to avoid to write an open object at the last 'candle' of the day,
|
||||
since the object will be duplicated on the next day. How do we know we are positioned at the last candle
|
||||
of the day? Easy: the end of the candle must be 1 millisecod before the next day. That happens at any
|
||||
time frame.
|
||||
*/
|
||||
let currentDay = new Date(tradingEngine.current.episode.candle.end.value)
|
||||
let nextDay = new Date(tradingEngine.current.episode.candle.end.value + 1)
|
||||
if (currentDay.getUTCDate() !== nextDay.getUTCDate()) {
|
||||
/*
|
||||
We will save the object only if it is closed, becasuse we are at the last candle of the day.
|
||||
*/
|
||||
if (propertyValue === product.config.propertyValueThatClosesObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
When we are not at the end of the day, we will save the object normally, like in market files.
|
||||
*/
|
||||
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
For Market Files we will add a record everytime that proeprty value does not match this
|
||||
*/
|
||||
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
When we are not dealing with objects, we add every record to the existing file except
|
||||
for the ones that are filtered out at the Product Definition.
|
||||
*/
|
||||
if (product.config.codeName === "Trading-System-Errors") {
|
||||
let a = 1
|
||||
}
|
||||
if (product.config.propertyNameThatDefinesStatus !== undefined && product.config.propertyValueThatPreventsSavingObject !== undefined) {
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesStatus) {
|
||||
let propertyValue = record[j]
|
||||
|
||||
if (product.config.propertyValueThatPreventsSavingObject === "->Empty Array->" && propertyValue.length === 0) {
|
||||
break
|
||||
}
|
||||
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function safeNumericValue(value) {
|
||||
/*
|
||||
The purpose of this function is to check that value variable does not have a value that
|
||||
will later break the JSON format of files where this is going to be stored at.
|
||||
*/
|
||||
if (value === Infinity) {
|
||||
value = Number.MAX_SAFE_INTEGER
|
||||
}
|
||||
if (value === undefined) {
|
||||
value = 0
|
||||
}
|
||||
if (value === null) {
|
||||
value = 0
|
||||
}
|
||||
if (isNaN(value)) {
|
||||
value = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function safeStringValue(value) {
|
||||
/*
|
||||
The purpose of this function is to check that value variable does not have a value that
|
||||
will later break the JSON format of files where this is going to be stored at.
|
||||
*/
|
||||
if (value === undefined) {
|
||||
value = ''
|
||||
}
|
||||
if (value === null) {
|
||||
value = ''
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function pruneOutputFile(product, outputFile, currentEnd) {
|
||||
if (outputFile.isPrunned === true) { return }
|
||||
/*
|
||||
When a session is resumed, we will be potentially reading output files belonging to a previous session execution.
|
||||
For that reason we need to prune all the records that are beyond the current candle. We do not delete everything
|
||||
because we might be resuming a stopped session, which is fine.
|
||||
*/
|
||||
for (let i = 0; i < outputFile.length; i++) {
|
||||
let record = outputFile[i]
|
||||
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
if (recordProperty.config.codeName === 'end') {
|
||||
let end = record[j]
|
||||
if (end >= currentEnd) {
|
||||
outputFile.splice(i, 1)
|
||||
/*
|
||||
This will execute the next prune call in the next iteration of the NodeJs event loop
|
||||
allowing for other callbacks to be executed. It also prevents the error
|
||||
'Maximum call stack size exceeded', since the call is not placed at the call stack.
|
||||
*/
|
||||
setImmediate(pruneOutputFile, product, outputFile, currentEnd)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
outputFile.isPrunned = true
|
||||
}
|
||||
|
||||
function spliceOpenRecords(j, product, outputDatasetArray) {
|
||||
/*
|
||||
Before adding records to the output file, we need to remove all open records,
|
||||
because we will be adding the same records later with potentially
|
||||
new or updated information.
|
||||
*/
|
||||
|
||||
for (let i = 0; i < outputDatasetArray.length; i++) {
|
||||
let dataRecord = outputDatasetArray[i]
|
||||
if (dataRecord !== undefined) {
|
||||
let dataRecordValue = dataRecord[j]
|
||||
if (dataRecordValue !== product.config.propertyValueThatClosesObject) {
|
||||
outputDatasetArray.splice(i, 1)
|
||||
spliceOpenRecords(j, product, outputDatasetArray)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function badDefinitionUnhandledException(err, message, product, recordProperty) {
|
||||
logger.write(MODULE_NAME, "[ERROR] appendRecords -> " + message);
|
||||
logger.write(MODULE_NAME, "[ERROR] appendRecords -> product.name = " + product.name);
|
||||
logger.write(MODULE_NAME, "[ERROR] appendRecords -> product.config = " + JSON.stringify(product.config));
|
||||
|
||||
if (recordProperty) {
|
||||
logger.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.name = " + recordProperty.name);
|
||||
logger.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.config.codeName = " + recordProperty.config.codeName);
|
||||
logger.write(MODULE_NAME, "[ERROR] appendRecords -> recordProperty.config = " + JSON.stringify(recordProperty.config));
|
||||
}
|
||||
|
||||
if (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] appendRecords -> err.stack = " + err.stack);
|
||||
}
|
||||
throw 'Can not continue with a Definition Error like this.'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES) {
|
||||
/*
|
||||
This Module represents the trading simulacion. Escentially a loop through a set of candles and
|
||||
the execution at each loop cycle of the Trading System Protocol.
|
||||
*/
|
||||
const FULL_LOG = true
|
||||
const MODULE_NAME = 'Trading Simulation -> ' + bot.TRADING_SESSION.name
|
||||
|
||||
let thisObject = {
|
||||
finalize: finalize,
|
||||
runSimulation: runSimulation
|
||||
}
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(bot, logger)
|
||||
|
||||
return thisObject
|
||||
|
||||
function finalize() {
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
async function runSimulation(
|
||||
chart,
|
||||
outputDatasetsMap,
|
||||
writeFiles,
|
||||
) {
|
||||
try {
|
||||
|
||||
let tradingSystem = bot.simulationState.tradingSystem
|
||||
let tradingEngine = bot.simulationState.tradingEngine
|
||||
let sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) }
|
||||
|
||||
/* These are the Modules we will need to run the Simulation */
|
||||
const TRADING_RECORDS_MODULE = require('./TradingRecords.js')
|
||||
let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger)
|
||||
tradingRecordsModule.initialize(outputDatasetsMap)
|
||||
|
||||
const TRADING_SYSTEM_MODULE = require('./TradingSystem.js')
|
||||
let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger, tradingEngineModule)
|
||||
tradingSystemModule.initialize()
|
||||
|
||||
const TRADING_EPISODE_MODULE = require('./TradingEpisode.js')
|
||||
let tradingEpisodeModule = TRADING_EPISODE_MODULE.newTradingEpisode(bot, logger, tradingEngineModule)
|
||||
tradingEpisodeModule.initialize()
|
||||
|
||||
/* Setting up the candles array: The whole simulation is based on the array of candles at the time-frame defined at the session parameters. */
|
||||
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
|
||||
let candles = chart[propertyName].candles
|
||||
|
||||
/* Variables needed for heartbeat functionality */
|
||||
let heartBeatDate
|
||||
let previousHeartBeatDate
|
||||
/*
|
||||
Estimation of the Initial Candle to Process in this Run.
|
||||
*/
|
||||
let initialCandle
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/* Estimate Initial Candle based on the timeRage configured for the session. */
|
||||
let firstEnd = candles[0].end
|
||||
let targetEnd = sessionParameters.timeRange.config.initialDatetime
|
||||
let diff = targetEnd - firstEnd
|
||||
let amount = diff / sessionParameters.timeFrame.config.value
|
||||
|
||||
initialCandle = Math.trunc(amount)
|
||||
if (initialCandle < 0) { initialCandle = 0 }
|
||||
if (initialCandle > candles.length - 1) {
|
||||
/*
|
||||
This will happen when the sessionParameters.timeRange.config.initialDatetime is beyond the last candle available,
|
||||
meaning that the dataSet needs to be updated with more up-to-date data.
|
||||
*/
|
||||
bot.TRADING_SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.')
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Data is not up-to-date enough. Stopping the Session now. ') }
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
/*
|
||||
In this case we already have at the last candle index the next candle to be
|
||||
processed. We will just continue with this candle.
|
||||
*/
|
||||
initialCandle = tradingEngine.current.episode.candle.index.value
|
||||
}
|
||||
/*
|
||||
Main Simulation Loop
|
||||
*/
|
||||
/* We are going to use this to exit the loop if needed. */
|
||||
let breakLoop = false
|
||||
|
||||
/*
|
||||
We will assume that we are at the head of the market here. We do this
|
||||
because the loop could be empty and no validation is going to run. If the
|
||||
loop is not empty, then the lascCandle() check will override this value
|
||||
depending on if we really are at the head of the market or not.
|
||||
*/
|
||||
tradingEngine.current.episode.headOfTheMarket.value = true
|
||||
/*
|
||||
This is the main simulation loop. It will go through the initial candle
|
||||
until one less than the last candle available. We will never process the last
|
||||
candle available since it is not considered a closed candle, but a candle
|
||||
that still can change. So effectively will be processing all closed candles.
|
||||
*/
|
||||
for (let i = initialCandle; i < candles.length - 1; i++) {
|
||||
tradingEngine.current.episode.candle.index.value = i
|
||||
|
||||
/* This is the current candle the Simulation is working at. */
|
||||
let candle = candles[tradingEngine.current.episode.candle.index.value]
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toLocaleString()) }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) }
|
||||
|
||||
tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading engine data structure to make it available to anyone, including conditions and formulas.
|
||||
|
||||
/* We emit a heart beat so that the UI can now where we are at the overal process.*/
|
||||
heartBeat()
|
||||
|
||||
/* Opening the Episode, if needed. */
|
||||
tradingEpisodeModule.openEpisode()
|
||||
|
||||
if (checkInitialDatetime() === false) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Before the Initia Date Time @ ' + (new Date(candle.begin)).toLocaleString()) }
|
||||
continue
|
||||
}
|
||||
|
||||
positionChartAtCurrentCandle()
|
||||
|
||||
/* The chart was recalculated based on the current candle. */
|
||||
tradingSystemModule.updateChart(chart)
|
||||
|
||||
/*
|
||||
Do the stuff needed previous to the run like
|
||||
Episode Counters and Statistics update. Mantaince is done
|
||||
once per simulation candle.
|
||||
*/
|
||||
tradingSystemModule.mantain()
|
||||
tradingEpisodeModule.mantain()
|
||||
tradingEngineModule.mantain()
|
||||
|
||||
/*
|
||||
Run the first cycle of the Trading System. In this first cycle we
|
||||
give some room so that orders can be canceled or filled and we can
|
||||
write those records into the output memory. During this cycle new
|
||||
orders can not be created, since otherwise the could be cancelled at
|
||||
the second cycle without spending real time at the order book.
|
||||
*/
|
||||
tradingEngineModule.setCurrentCycle('First')
|
||||
await runCycle()
|
||||
|
||||
/*
|
||||
We check if we need to stop before appending the records so that the stop
|
||||
reason is also propery recorded. Note also that we check this after the first
|
||||
cycle, where orders have not been submitted to the exchange yet, but we
|
||||
had the chance to check for the status of placed orders or even cancel
|
||||
the ones that needed cancellation.
|
||||
*/
|
||||
checkIfWeNeedToStopBetweenCycles()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords()
|
||||
|
||||
if (breakLoop === true) { break }
|
||||
/*
|
||||
Run the second cycle of the Trading System. During this second run
|
||||
some new orders might be created at slots freed up during the first
|
||||
run. This allows for example for a Limit Order to be cancelled during the
|
||||
first run, and the same Limit Order definition to spawn a new order
|
||||
without the need to wait until the next candle. Orders can not be cancelled
|
||||
during the second cycle.
|
||||
*/
|
||||
tradingEngineModule.setCurrentCycle('Second')
|
||||
await runCycle()
|
||||
|
||||
checkIfWeNeedToStopAfterBothCycles()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords()
|
||||
|
||||
if (breakLoop === true) { break }
|
||||
|
||||
async function runCycle() {
|
||||
/* Reset Data Structures */
|
||||
tradingSystemModule.reset()
|
||||
tradingEpisodeModule.reset()
|
||||
tradingEngineModule.reset()
|
||||
|
||||
let infoMessage = 'Processing candle # ' + tradingEngine.current.episode.candle.index.value + ' @ the ' + tradingEngine.current.episode.cycle.value + ' cycle.'
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> ' + infoMessage) }
|
||||
tradingSystem.infos.push([tradingSystem.id, infoMessage])
|
||||
|
||||
await tradingSystemModule.run()
|
||||
}
|
||||
|
||||
function checkIfWeNeedToStopBetweenCycles() {
|
||||
if (bot.STOP_SESSION === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') }
|
||||
updateEpisode('Session Stopped')
|
||||
breakLoop = true
|
||||
return
|
||||
}
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') }
|
||||
updateEpisode('Task Stopped')
|
||||
breakLoop = true
|
||||
return
|
||||
}
|
||||
|
||||
if (checkFinalDatetime() === false) {
|
||||
closeEpisode('Final Datetime Reached')
|
||||
breakLoop = true
|
||||
bot.TRADING_SESSION.stop('Final Datetime Reached')
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Final Datetime Reached. Stopping the Session now. ') }
|
||||
return
|
||||
}
|
||||
|
||||
if (checkMinimunAndMaximunBalance() === false) {
|
||||
closeEpisode('Min or Max Balance Reached')
|
||||
breakLoop = true
|
||||
bot.TRADING_SESSION.stop('Min or Max Balance Reached')
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Min or Max Balance Reached. Stopping the Session now. ') }
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfWeNeedToStopAfterBothCycles() {
|
||||
if (checkNextCandle() === false) {
|
||||
updateEpisode('All Candles Processed')
|
||||
breakLoop = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tradingSystemModule.finalize()
|
||||
tradingRecordsModule.finalize()
|
||||
tradingEpisodeModule.finalize()
|
||||
|
||||
tradingSystemModule = undefined
|
||||
tradingRecordsModule = undefined
|
||||
tradingEpisodeModule = undefined
|
||||
|
||||
await writeFiles()
|
||||
|
||||
function closeEpisode(exitType) {
|
||||
tradingEpisodeModule.updateExitType(exitType)
|
||||
tradingEpisodeModule.closeEpisode()
|
||||
}
|
||||
|
||||
function updateEpisode(exitType) {
|
||||
tradingEpisodeModule.updateExitType(exitType)
|
||||
}
|
||||
|
||||
function heartBeat() {
|
||||
let hartbeatText = ''
|
||||
if (sessionParameters.heartbeats !== undefined) {
|
||||
if (sessionParameters.heartbeats.config.date === true || sessionParameters.heartbeats.config.candleIndex === true) {
|
||||
/* We will produce a simulation level heartbeat in order to inform the user this is running. */
|
||||
|
||||
heartBeatDate = new Date(Math.trunc(tradingEngine.current.episode.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS)
|
||||
|
||||
let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime)
|
||||
let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime)
|
||||
|
||||
let currentDateString = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2)
|
||||
let currentDate = new Date(heartBeatDate)
|
||||
let percentage = global.getPercentage(fromDate, currentDate, lastDate)
|
||||
|
||||
/*
|
||||
Theere are a few tasks that we need to do only when the date changes,
|
||||
otherwise it would be suboptimal.
|
||||
*/
|
||||
if (heartBeatDate.valueOf() !== previousHeartBeatDate) {
|
||||
previousHeartBeatDate = heartBeatDate.valueOf()
|
||||
|
||||
let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2)
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.TRADING_SESSIONKey + ' Loop # ' + tradingEngine.current.episode.candle.index.value + ' @ ' + processingDate) }
|
||||
|
||||
/* Logging to console and disk */
|
||||
if (global.areEqualDates(currentDate, new Date()) === false) {
|
||||
logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage)
|
||||
}
|
||||
|
||||
/* Date only hearbeat */
|
||||
if (sessionParameters.heartbeats.config.date === true && sessionParameters.heartbeats.config.candleIndex === false) {
|
||||
hartbeatText = hartbeatText + currentDateString
|
||||
bot.processHeartBeat(hartbeatText, percentage)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
When the Candle Index nees to be shown, then we can not send the hearbet
|
||||
only when the dates changes, we have to send it for every candle.
|
||||
It might also contain the date information.
|
||||
*/
|
||||
if (sessionParameters.heartbeats.config.candleIndex === true) {
|
||||
if (sessionParameters.heartbeats.config.date === true) {
|
||||
hartbeatText = hartbeatText + currentDateString
|
||||
}
|
||||
hartbeatText = hartbeatText + ' Candle # ' + tradingEngine.current.episode.candle.index.value
|
||||
bot.processHeartBeat(hartbeatText, percentage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function positionChartAtCurrentCandle() {
|
||||
/*
|
||||
In conditions and Formulas, we want users to have an easy sintax to refer to indicators. In order to achieve that, we need the user to have
|
||||
easy access to the current candle for instance, or the current bollinger band, meaning the one the Simulation is currently standing at.
|
||||
For that reason we do the following processing, to have at the chart data structure the current objects of each indicator / time frame.
|
||||
*/
|
||||
let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency')
|
||||
dataDependencies = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(dataDependencies)
|
||||
|
||||
/* Finding the Current Element on Market Files */
|
||||
if (bot.processingDailyFiles) {
|
||||
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
|
||||
let mapKey = dailyFilePeriods[j][1]
|
||||
let propertyName = 'at' + mapKey.replace('-', '')
|
||||
let thisChart = chart[propertyName]
|
||||
|
||||
for (let k = 0; k < dataDependencies.length; k++) {
|
||||
let dataDependencyNode = dataDependencies[k]
|
||||
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue }
|
||||
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
|
||||
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
|
||||
if (thisChart[pluralVariableName] !== undefined) {
|
||||
let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName)
|
||||
if (currentElement !== undefined) {
|
||||
thisChart[singularVariableName] = currentElement
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Finding the Current Element on Market Files */
|
||||
for (let j = 0; j < global.marketFilesPeriods.length; j++) {
|
||||
let mapKey = marketFilesPeriods[j][1]
|
||||
let propertyName = 'at' + mapKey.replace('-', '')
|
||||
let thisChart = chart[propertyName]
|
||||
|
||||
for (let k = 0; k < dataDependencies.length; k++) {
|
||||
let dataDependencyNode = dataDependencies[k]
|
||||
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue }
|
||||
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
|
||||
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
|
||||
if (thisChart[pluralVariableName] !== undefined) {
|
||||
let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName)
|
||||
if (currentElement !== undefined) {
|
||||
thisChart[singularVariableName] = currentElement
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Finding the Current Element At Single Files*/
|
||||
let propertyName = 'atAnyTimeFrame'
|
||||
let thisChart = chart[propertyName]
|
||||
|
||||
for (let k = 0; k < dataDependencies.length; k++) {
|
||||
let dataDependencyNode = dataDependencies[k]
|
||||
if (dataDependencyNode.referenceParent.config.codeName !== 'Single-File') { continue }
|
||||
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
|
||||
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
|
||||
let elementArray = thisChart[pluralVariableName]
|
||||
let currentElement
|
||||
if (elementArray !== undefined) {
|
||||
currentElement = elementArray[elementArray.length - 1]
|
||||
}
|
||||
thisChart[singularVariableName] = currentElement
|
||||
}
|
||||
}
|
||||
|
||||
function getElement(pArray, datasetName) {
|
||||
if (pArray === undefined) { return }
|
||||
try {
|
||||
let element
|
||||
for (let i = 0; i < pArray.length; i++) {
|
||||
element = pArray[i]
|
||||
|
||||
if (tradingEngine.current.episode.candle.end.value === element.end) { // when there is an exact match at the end we take that element
|
||||
return element
|
||||
} else {
|
||||
if (
|
||||
i > 0 &&
|
||||
element.end > tradingEngine.current.episode.candle.end.value
|
||||
) {
|
||||
let previousElement = pArray[i - 1]
|
||||
if (previousElement.end < tradingEngine.current.episode.candle.end.value) {
|
||||
return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element.
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack)
|
||||
throw (global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function checkNextCandle() {
|
||||
/*
|
||||
We need to check that the candle we have just processed it is not the last candle.
|
||||
The candle at the head of the market is already skipped from the loop because it has not closed yet.
|
||||
Note: for Daily Files, this means that the last candle of each day will never be processed.
|
||||
|
||||
The first +1 is because array indexes are based on 0.
|
||||
The second +1 is because we need to compare the next candle (remember that the loops allways avoid the
|
||||
last candle of the dataset available.)
|
||||
*/
|
||||
if (tradingEngine.current.episode.candle.index.value + 1 + 1 === candles.length) {
|
||||
/*
|
||||
When processing daily files, we need a mechanism to turn from one day to the next one.
|
||||
That mechanism is the one implemented here. If we detect that the next candle is the last candle of
|
||||
the day, we will advance current process day one day. By doing so, during the next execution, the
|
||||
simulation will receive the candles and indicators files of the next day.
|
||||
*/
|
||||
let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value
|
||||
if (
|
||||
bot.processingDailyFiles &&
|
||||
tradingEngine.current.episode.candle.index.value + 1 + 1 === candlesPerDay
|
||||
) {
|
||||
/*
|
||||
Here we found that the next candle of the dataset is the last candle of the day.
|
||||
It is time to move to the next day so as to receive at the next execution, the indicator files from
|
||||
the next day. At the same time we will reset the index to be pointing to
|
||||
the first candle of the new dataset we shall receive. The first candle of the next day starts
|
||||
at index 0, so we will position the index now at zero.
|
||||
*/
|
||||
tradingEngine.current.episode.candle.index.value = 0
|
||||
tradingEngine.current.episode.processDate.value =
|
||||
tradingEngine.current.episode.processDate.value + global.ONE_DAY_IN_MILISECONDS
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
We reached the head of the market but we are not at the last candle of a day during
|
||||
Daily Files processing. We will advance to the next candle index anyways because in the
|
||||
next execution it will likely have more candles at the dataset. And if it does not,
|
||||
it will just wait there until it does.
|
||||
*/
|
||||
tradingEngine.current.episode.headOfTheMarket.value = true
|
||||
tradingEngine.current.episode.candle.index.value++
|
||||
return false
|
||||
} else {
|
||||
|
||||
/* Wd did not reach the head of the market */
|
||||
tradingEngine.current.episode.headOfTheMarket.value = false
|
||||
tradingEngine.current.episode.candle.index.value++
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function checkInitialDatetime() {
|
||||
/* Here we check that the current candle is not before the initial datetime defined at the session parameters.*/
|
||||
if (tradingEngine.current.episode.candle.end.value < sessionParameters.timeRange.config.initialDatetime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') }
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkFinalDatetime() {
|
||||
/* Here we check that the next candle is not after of the user-defined final datetime at the session parameters.*/
|
||||
if (tradingEngine.current.episode.candle.begin.value + sessionParameters.timeFrame.config.value > sessionParameters.timeRange.config.finalDatetime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') }
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkMinimunAndMaximunBalance() {
|
||||
/* Checks for Minimun and Maximun Balance. We do the check while not inside any strategy only. */
|
||||
if (
|
||||
tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue
|
||||
) {
|
||||
/*
|
||||
We will perform this check only when we are not inside a position,
|
||||
because there the balances have shifted from their resting position.
|
||||
*/
|
||||
|
||||
let stopRunningDate = (new Date(tradingEngine.current.episode.candle.begin.value)).toLocaleString()
|
||||
|
||||
if (sessionParameters.sessionBaseAsset.config.minimumBalance !== undefined) {
|
||||
if (tradingEngine.current.episode.episodeBaseAsset.balance.value <= sessionParameters.sessionBaseAsset.config.minimumBalance) {
|
||||
const errorMessage = 'Min Balance reached @ ' + stopRunningDate
|
||||
tradingSystem.errors.push([tradingSystem.id, errorMessage])
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionParameters.sessionBaseAsset.config.maximumBalance !== undefined) {
|
||||
if (tradingEngine.current.episode.episodeBaseAsset.balance.value >= sessionParameters.sessionBaseAsset.config.maximumBalance) {
|
||||
const errorMessage = 'Max Balance reached @ ' + stopRunningDate
|
||||
tradingSystem.errors.push([tradingSystem.id, errorMessage])
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionParameters.sessionQuotedAsset.config.minimumBalance !== undefined) {
|
||||
if (tradingEngine.current.episode.episodeQuotedAsset.balance.value <= sessionParameters.sessionQuotedAsset.config.minimumBalance) {
|
||||
const errorMessage = 'Min Balance reached @ ' + stopRunningDate
|
||||
tradingSystem.errors.push([tradingSystem.id, errorMessage])
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionParameters.sessionQuotedAsset.config.maximumBalance !== undefined) {
|
||||
if (tradingEngine.current.episode.episodeQuotedAsset.balance.value >= sessionParameters.sessionQuotedAsset.config.maximumBalance) {
|
||||
const errorMessage = 'Max Balance reached @ ' + stopRunningDate
|
||||
tradingSystem.errors.push([tradingSystem.id, errorMessage])
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[WARN] runSimulation -> checkMinimunAndMaximunBalance -> ' + errorMessage) }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack)
|
||||
throw (global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,87 @@
|
||||
exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEngineModule) {
|
||||
/*
|
||||
This module packages all functions related to Strategies.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Strategy'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
openStrategy: openStrategy,
|
||||
closeStrategy: closeStrategy,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
updateCounters()
|
||||
updateEnds()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resetTradingEngineDataStructure()
|
||||
}
|
||||
|
||||
function openStrategy(index, situationName, strategyName) {
|
||||
/* Starting begin and end */
|
||||
tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.cycle.lastBegin.value
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
tradingEngine.current.strategy.status.value = 'Open'
|
||||
tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value
|
||||
tradingEngine.current.strategy.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.strategy.beginRate.value = tradingEngine.current.episode.candle.min.value
|
||||
|
||||
tradingEngine.current.strategy.index.value = index
|
||||
tradingEngine.current.strategy.situationName.value = situationName
|
||||
tradingEngine.current.strategy.strategyName.value = strategyName
|
||||
|
||||
/* Updating Episode Counters */
|
||||
tradingEngine.current.episode.episodeCounters.strategies.value++
|
||||
}
|
||||
|
||||
function closeStrategy(exitType) {
|
||||
tradingEngine.current.strategy.status.value = 'Closed'
|
||||
tradingEngine.current.strategy.exitType.value = exitType
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
tradingEngine.current.strategy.endRate.value = tradingEngine.current.episode.candle.min.value
|
||||
/*
|
||||
Now that the strategy is closed, it is the right time to move this strategy from current to last at the Trading Engine data structure.
|
||||
*/
|
||||
tradingEngineModule.cloneValues(tradingEngine.current.strategy, tradingEngine.last.strategy)
|
||||
}
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.strategy.status.value === 'Open') {
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.strategy.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.strategy.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
}
|
||||
|
||||
function resetTradingEngineDataStructure() {
|
||||
if (tradingEngine.current.strategy.status.value === 'Closed') {
|
||||
tradingEngineModule.initializeNode(tradingEngine.current.strategy)
|
||||
}
|
||||
}
|
||||
|
||||
function updateCounters() {
|
||||
if (tradingEngine.current.strategy.status.value === 'Open') {
|
||||
tradingEngine.current.strategy.strategyCounters.periods.value++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineModule) {
|
||||
/*
|
||||
The Trading System is the user defined set of rules compliant with the Trading Protocol that
|
||||
defines the trading logic to be applied during each cycle of the Simulation.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading System'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
run: run,
|
||||
updateChart: updateChart,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let chart
|
||||
let tradingSystem
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
let dynamicIndicators
|
||||
|
||||
const TRADING_STAGES_MODULE = require('./TradingStages.js')
|
||||
let tradingStagesModule = TRADING_STAGES_MODULE.newTradingStages(bot, logger, tradingEngineModule)
|
||||
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
tradingSystem.conditions = new Map()
|
||||
tradingSystem.formulas = new Map()
|
||||
|
||||
tradingStagesModule.initialize()
|
||||
|
||||
/* Adding Functions used elsewhere to Trading System Definition */
|
||||
tradingSystem.checkConditions = function (situation, passed) {
|
||||
/* Given a Situation, we check if all children conditions are true or not */
|
||||
for (let m = 0; m < situation.conditions.length; m++) {
|
||||
|
||||
let condition = situation.conditions[m]
|
||||
let value = false
|
||||
if (tradingSystem.conditions.get(condition.id) !== undefined) {
|
||||
value = tradingSystem.conditions.get(condition.id)
|
||||
}
|
||||
if (value === true) {
|
||||
tradingSystem.highlights.push(condition.id)
|
||||
}
|
||||
else {
|
||||
passed = false
|
||||
}
|
||||
tradingSystem.values.push([condition.id, value])
|
||||
}
|
||||
return passed
|
||||
}
|
||||
|
||||
tradingSystem.evalConditions = function (startingNode, descendentOfNodeType) {
|
||||
evalNode(startingNode, 'Conditions', descendentOfNodeType)
|
||||
}
|
||||
|
||||
tradingSystem.evalFormulas = function (startingNode, descendentOfNodeType) {
|
||||
evalNode(startingNode, 'Formulas', descendentOfNodeType)
|
||||
}
|
||||
|
||||
function isItInside(elementWithTimestamp, elementWithBeginEnd) {
|
||||
/*
|
||||
The function is to allow in Conditions and Formulas to easily know when an element with timestamp (like the ones of single files) are inside the time range
|
||||
of an element with a time range, like a candle.
|
||||
*/
|
||||
if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingStagesModule.finalize()
|
||||
tradingStagesModule = undefined
|
||||
|
||||
chart = undefined
|
||||
|
||||
tradingSystem.conditions = undefined
|
||||
tradingSystem.formulas = undefined
|
||||
|
||||
tradingSystem.highlights = undefined
|
||||
tradingSystem.errors = undefined
|
||||
tradingSystem.warnings = undefined
|
||||
tradingSystem.infos = undefined
|
||||
tradingSystem.values = undefined
|
||||
tradingSystem.status = undefined
|
||||
tradingSystem.progress = undefined
|
||||
tradingSystem.running = undefined
|
||||
|
||||
tradingSystem = undefined
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
tradingStagesModule.mantain()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
tradingStagesModule.reset()
|
||||
|
||||
tradingSystem.highlights = []
|
||||
tradingSystem.errors = []
|
||||
tradingSystem.warnings = []
|
||||
tradingSystem.infos = []
|
||||
tradingSystem.values = []
|
||||
tradingSystem.status = []
|
||||
tradingSystem.progress = []
|
||||
tradingSystem.running = []
|
||||
tradingSystem.announcements = []
|
||||
}
|
||||
|
||||
function updateChart(pChart) {
|
||||
chart = pChart // We need chat to be a local object accessible from conditions and formulas.
|
||||
tradingStagesModule.updateChart(pChart)
|
||||
}
|
||||
|
||||
function buildDynamicIndicators() {
|
||||
if (tradingSystem.dynamicIndicators !== undefined) {
|
||||
dynamicIndicators = {}
|
||||
/* Eval Dynamic Indicators */
|
||||
tradingSystem.evalFormulas(tradingSystem.dynamicIndicators, 'Indicator Function')
|
||||
|
||||
for (let i = 0; i < tradingSystem.dynamicIndicators.indicatorFunctions.length; i++) {
|
||||
let indicatorFunction = tradingSystem.dynamicIndicators.indicatorFunctions[i]
|
||||
if (indicatorFunction.formula === undefined) { return }
|
||||
if (indicatorFunction.config.codeName === undefined) { return }
|
||||
dynamicIndicators[indicatorFunction.config.codeName] = tradingSystem.formulas.get(indicatorFunction.formula.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
/* Dynamic Indicators */
|
||||
buildDynamicIndicators()
|
||||
|
||||
/* Run the Trigger Stage */
|
||||
tradingStagesModule.runTriggerStage()
|
||||
|
||||
/* Run the Open Stage */
|
||||
await tradingStagesModule.runOpenStage()
|
||||
|
||||
/* Run the Manage Stage */
|
||||
tradingStagesModule.runManageStage()
|
||||
|
||||
/* Run the Close Stage */
|
||||
await tradingStagesModule.runCloseStage()
|
||||
|
||||
tradingStagesModule.cycleBasedStatistics()
|
||||
|
||||
} catch (err) {
|
||||
/*
|
||||
If an error ocurred during execution, it was alreeady logged and
|
||||
included at the errors array. That means we need to do nothing here,
|
||||
just prevent the execution to be halted for not handling exceptions.
|
||||
*/
|
||||
if (typeof err === 'string' || err instanceof String) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err)
|
||||
}
|
||||
if (err.stack !== undefined) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runExecution -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function evalNode(node, evaluating, descendentOfNodeType, isDescendent) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Verify if this node is decendent of the specified node type */
|
||||
if (isDescendent !== true) {
|
||||
if (node.type === descendentOfNodeType) {
|
||||
isDescendent = true
|
||||
}
|
||||
}
|
||||
|
||||
/* Here we check if there is a codition to be evaluated */
|
||||
if (node.type === 'Condition' && evaluating === 'Conditions') {
|
||||
/* We will eval this condition */
|
||||
if (isDescendent === true) {
|
||||
evalCondition(node)
|
||||
}
|
||||
}
|
||||
|
||||
/* Here we check if there is a formula to be evaluated */
|
||||
if ((node.type === 'Formula' || node.type === 'Announcement Formula') && evaluating === 'Formulas') {
|
||||
if (node.code !== undefined) {
|
||||
/* We will eval this formula */
|
||||
if (isDescendent === true) {
|
||||
evalFormula(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
evalNode(node[property.name], evaluating, descendentOfNodeType, isDescendent)
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
evalNode(nodePropertyArray[m], evaluating, descendentOfNodeType, isDescendent)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function evalCondition(node) {
|
||||
let value
|
||||
let error
|
||||
/*
|
||||
The code can be at the condition node if it was done with the Conditions Editor, or it can also be
|
||||
at a Javascript Code node. If there is a Javascript object we will give it preference and take the code
|
||||
from there. Otherwise we will take the code from the Condition node.
|
||||
*/
|
||||
let code = node.code
|
||||
if (node.javascriptCode !== undefined) {
|
||||
if (node.javascriptCode.code !== undefined) {
|
||||
code = node.javascriptCode.code
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
logger.write(MODULE_NAME, '[INFO] evalCondition -> ' + node.name + ' -> code = ' + code)
|
||||
value = eval(code)
|
||||
} catch (err) {
|
||||
/*
|
||||
One possible error is that the conditions references a .previous that is undefined. This
|
||||
will not be considered an error.
|
||||
*/
|
||||
value = false
|
||||
error = err.message
|
||||
}
|
||||
|
||||
tradingSystem.conditions.set(node.id, value)
|
||||
|
||||
if (value === true) {
|
||||
tradingSystem.highlights.push(node.id)
|
||||
}
|
||||
if (error !== undefined) {
|
||||
tradingSystem.errors.push([node.id, error])
|
||||
}
|
||||
if (value !== undefined) {
|
||||
tradingSystem.values.push([node.id, value])
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] evalCondition -> value = ' + value)
|
||||
logger.write(MODULE_NAME, '[INFO] evalCondition -> error = ' + error)
|
||||
}
|
||||
|
||||
function evalFormula(node) {
|
||||
let value
|
||||
let error
|
||||
|
||||
try {
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> ' + node.name + ' -> code = ' + node.code)
|
||||
value = eval(node.code)
|
||||
} catch (err) {
|
||||
/*
|
||||
One possible error is that the formula references a .previous that is undefined. This
|
||||
will not be considered an error.
|
||||
*/
|
||||
value = 0
|
||||
error = err.message
|
||||
}
|
||||
|
||||
if (error !== undefined) {
|
||||
tradingSystem.errors.push([node.id, error])
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> error = ' + error)
|
||||
return
|
||||
}
|
||||
if (value !== undefined) {
|
||||
if (node.type === 'Formula' && isNaN(value)) {
|
||||
tradingSystem.errors.push([node.id, 'Formula needs to return a number.'])
|
||||
return
|
||||
}
|
||||
|
||||
tradingSystem.values.push([node.id, value])
|
||||
tradingSystem.formulas.set(node.id, value)
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> value = ' + value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,570 @@
|
||||
exports.newMultiPeriodDaily = function newMultiPeriodDaily(bot, logger, UTILITIES, FILE_STORAGE) {
|
||||
const FULL_LOG = true;
|
||||
const MODULE_NAME = "Multi Period Daily";
|
||||
const GMT_SECONDS = ':00.000 GMT+0000';
|
||||
|
||||
thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
};
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger)
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger)
|
||||
|
||||
let statusDependencies;
|
||||
let dataDependenciesModule;
|
||||
let dataFiles = new Map;
|
||||
let indicatorOutputModule;
|
||||
|
||||
let bootstrappingTheProcess = false
|
||||
|
||||
let processConfig;
|
||||
let beginingOfMarket
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) {
|
||||
logger.fileName = MODULE_NAME;
|
||||
logger.initialize()
|
||||
|
||||
statusDependencies = pStatusDependencies;
|
||||
dataDependenciesModule = pDataDependencies;
|
||||
processConfig = pProcessConfig;
|
||||
|
||||
let INDICATOR_OUTPUT_MODULE = require("./IndicatorOutput")
|
||||
|
||||
indicatorOutputModule = INDICATOR_OUTPUT_MODULE.newIndicatorOutput(bot, logger, UTILITIES, FILE_STORAGE)
|
||||
indicatorOutputModule.initialize(callBackFunction)
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
dataFiles = undefined
|
||||
statusDependencies = undefined
|
||||
dataDependenciesModule = undefined
|
||||
indicatorOutputModule = undefined
|
||||
fileStorage = undefined
|
||||
processConfig = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
function start(callBackFunction) {
|
||||
try {
|
||||
let market = bot.market;
|
||||
|
||||
/* Context Variables */
|
||||
let contextVariables = {
|
||||
lastFile: undefined, // Datetime of the last file files sucessfully produced by this process.
|
||||
dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history.
|
||||
dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process.
|
||||
};
|
||||
|
||||
let previousDay; // Holds the date of the previous day relative to the processing date.
|
||||
let interExecutionMemoryArray;
|
||||
|
||||
getContextVariables()
|
||||
|
||||
function getContextVariables() {
|
||||
try {
|
||||
let thisReport;
|
||||
let statusReport;
|
||||
/*
|
||||
We look first for the bot who knows the begining of the market in order to get when the market starts.
|
||||
*/
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Market Starting Point")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Market Starting Point -> Status Report does not exist or Market Starting Point not defined. Retrying Later. ")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. ")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.beginingOfMarket === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport.beginingOfMarket === undefined ")
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> It is too early too run this process since the trade history of the market is not there yet.")
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: "Dependency does not exist."
|
||||
}
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message)
|
||||
callBackFunction(customOK)
|
||||
return
|
||||
}
|
||||
|
||||
contextVariables.dateBeginOfMarket = new Date(thisReport.beginingOfMarket.year + "-" + thisReport.beginingOfMarket.month + "-" + thisReport.beginingOfMarket.days + " " + thisReport.beginingOfMarket.hours + ":" + thisReport.beginingOfMarket.minutes + GMT_SECONDS)
|
||||
/*
|
||||
Here we get the status report from the bot who knows which is the end of the market.
|
||||
*/
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Market Ending Point -> Status Report does not exist or Market Ending Point not defined. Retrying Later. ")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. ")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.lastFile === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport.lastFile === undefined")
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: "Dependency not ready."
|
||||
}
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message)
|
||||
callBackFunction(customOK)
|
||||
return
|
||||
}
|
||||
|
||||
contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf())
|
||||
|
||||
/* Finally we get our own Status Report. */
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Self Reference")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Self Reference -> Status Report does not exist or Self Reference not defined or badly configured. Check that at the Status Dependency you have chosen Self Reference at the config. Retrying Later. ")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process.")
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.lastFile !== undefined) {
|
||||
|
||||
beginingOfMarket = new Date(thisReport.beginingOfMarket)
|
||||
|
||||
if (beginingOfMarket.valueOf() !== contextVariables.dateBeginOfMarket.valueOf()) { // Reset Mechanism for Begining of the Market
|
||||
logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Reset Mechanism for Begining of the Market Activated.")
|
||||
|
||||
beginingOfMarket = new Date(contextVariables.dateBeginOfMarket.valueOf())
|
||||
startFromBegining()
|
||||
return
|
||||
}
|
||||
|
||||
if (bot.hasTheBotJustStarted === true && processConfig.framework.startDate !== undefined && processConfig.framework.startDate.resumeExecution === false) {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true.") }
|
||||
startFromBegining()
|
||||
return
|
||||
}
|
||||
|
||||
contextVariables.lastFile = new Date(thisReport.lastFile)
|
||||
interExecutionMemoryArray = thisReport.interExecutionMemoryArray;
|
||||
|
||||
processTimeFrames()
|
||||
} else {
|
||||
|
||||
/*
|
||||
In the case when there is no status report, we assume like the last processed file is the one on the date of Begining of Market.
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined.") }
|
||||
beginingOfMarket = new Date(contextVariables.dateBeginOfMarket.valueOf())
|
||||
startFromBegining()
|
||||
}
|
||||
|
||||
function startFromBegining() {
|
||||
contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS)
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile) }
|
||||
/*
|
||||
The first time the process is running is the right time to create the data structure that is going to be shared across different executions.
|
||||
This data structure has one object per each timeFrame.
|
||||
*/
|
||||
interExecutionMemoryArray = []
|
||||
/*
|
||||
Also, we will remember that we are bootstrapping the process, this will allow us, to advanceTime if necesary.
|
||||
It can happen that a process depends on data from another process that does not produce a file during the first day, or even more days,
|
||||
in that situation we want the process not to just wait for that data that will never arrive, but to advance time until it finds valid data.
|
||||
*/
|
||||
bootstrappingTheProcess = true
|
||||
|
||||
for (let i = 0; i < global.dailyFilePeriods.length; i++) {
|
||||
let emptyObject = {};
|
||||
interExecutionMemoryArray.push(emptyObject)
|
||||
}
|
||||
|
||||
processTimeFrames()
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack)
|
||||
if (err.message === "Cannot read property 'file' of undefined") {
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. ")
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys))
|
||||
}
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function processTimeFrames() {
|
||||
let n;
|
||||
let botNeverRan = true;
|
||||
|
||||
bot.multiPeriodDailyProcessDatetime = new Date(contextVariables.lastFile.valueOf() - ONE_DAY_IN_MILISECONDS) // Go back one day to start well when we advance time at the begining of the loop.
|
||||
let fromDate = new Date(bot.multiPeriodDailyProcessDatetime.valueOf())
|
||||
let lastDate = new Date()
|
||||
|
||||
advanceTime()
|
||||
|
||||
function advanceTime() {
|
||||
bot.multiPeriodDailyProcessDatetime = new Date(bot.multiPeriodDailyProcessDatetime.valueOf() + ONE_DAY_IN_MILISECONDS)
|
||||
previousDay = new Date(bot.multiPeriodDailyProcessDatetime.valueOf() - ONE_DAY_IN_MILISECONDS)
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> advanceTime -> bot.multiPeriodDailyProcessDatetime = " + bot.multiPeriodDailyProcessDatetime) }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> advanceTime -> previousDay = " + previousDay) }
|
||||
|
||||
/* Validation that we are not going past the head of the market. */
|
||||
if (bot.multiPeriodDailyProcessDatetime.valueOf() > contextVariables.dateEndOfMarket.valueOf()) {
|
||||
|
||||
const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> advanceTime -> " + logText) }
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
/* Telling the world we are alive and doing well */
|
||||
let currentDateString = bot.multiPeriodDailyProcessDatetime.getUTCFullYear() + '-' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCDate(), 2)
|
||||
let currentDate = new Date(bot.multiPeriodDailyProcessDatetime)
|
||||
let percentage = global.getPercentage(fromDate, currentDate, lastDate)
|
||||
bot.processHeartBeat(currentDateString, percentage)
|
||||
|
||||
if (global.areEqualDates(currentDate, new Date()) === false) {
|
||||
logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage)
|
||||
}
|
||||
checkStopTaskGracefully()
|
||||
}
|
||||
function checkStopTaskGracefully() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
}
|
||||
timeFramesLoop()
|
||||
}
|
||||
|
||||
function timeFramesLoop() {
|
||||
/*
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
n = 0 // loop Variable representing each possible period as defined at the timeFrames array.
|
||||
timeFramesLoopBody()
|
||||
}
|
||||
|
||||
function timeFramesLoopBody() {
|
||||
|
||||
const timeFrame = global.dailyFilePeriods[n][0]
|
||||
const timeFrameLabel = global.dailyFilePeriods[n][1]
|
||||
|
||||
/* Check Time Frames Filter */
|
||||
if (bot.dailyTimeFrames !== undefined) {
|
||||
if (bot.dailyTimeFrames.includes(timeFrameLabel) === false) {
|
||||
/* We are not going to process this Time Frame */
|
||||
timeFramesControlLoop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (processConfig.framework.validPeriods !== undefined) {
|
||||
let validPeriod = false;
|
||||
for (let i = 0; i < processConfig.framework.validPeriods.length; i++) {
|
||||
let period = processConfig.framework.validPeriods[i]
|
||||
if (period === timeFrameLabel) { validPeriod = true }
|
||||
}
|
||||
if (validPeriod === false) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> processTimeFrames -> timeFramesLoopBody -> Discarding period for not being listed as a valid period. -> timeFrameLabel = " + timeFrameLabel) }
|
||||
timeFramesControlLoop()
|
||||
return
|
||||
}
|
||||
}
|
||||
let dependencyIndex = 0
|
||||
dataFiles = new Map()
|
||||
|
||||
dependencyLoopBody()
|
||||
|
||||
function dependencyLoopBody() {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex]
|
||||
if (dependency === undefined) {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> You need to add at least one Data Dependency to the process Multi Period Daily. Aborting process.")
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]
|
||||
let previousFile
|
||||
let currentFile
|
||||
|
||||
if (bot.multiPeriodDailyProcessDatetime.valueOf() > contextVariables.dateBeginOfMarket.valueOf()) {
|
||||
getPreviousFile()
|
||||
} else {
|
||||
previousFile = []
|
||||
getCurrentFile()
|
||||
}
|
||||
|
||||
function getPreviousFile() {
|
||||
let dateForPath = previousDay.getUTCFullYear() + '/' + utilities.pad(previousDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(previousDay.getUTCDate(), 2)
|
||||
let filePath
|
||||
if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath;
|
||||
}
|
||||
let fileName = "Data.json"
|
||||
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived)
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
if ((err.message === "File does not exist." && botNeverRan === true) || err.code === 'The specified key does not exist.') {
|
||||
/*
|
||||
Sometimes one of the dependencies of an indicator for some reasons are not calculated from the begining of the market.
|
||||
When that happens we can not find those files. What we do in this situation is to move the time fordward until we can find
|
||||
all the dependencies and the first run of the bot is successful.
|
||||
|
||||
After that, we will not accept more missing files on any of the dependencies, and if any is missing we will abort the processing.
|
||||
*/
|
||||
logger.write(MODULE_NAME, "[WARN] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> Skipping day because file " + filePath + "/" + fileName + " was not found.")
|
||||
|
||||
advanceTime()
|
||||
return
|
||||
}
|
||||
|
||||
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack)
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getPreviousFile -> onFileReceived -> err = " + err.stack)
|
||||
callBackFunction(err)
|
||||
return
|
||||
}
|
||||
|
||||
previousFile = JSON.parse(text)
|
||||
getCurrentFile()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getCurrentFile() {
|
||||
let dateForPath = bot.multiPeriodDailyProcessDatetime.getUTCFullYear() + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.multiPeriodDailyProcessDatetime.getUTCDate(), 2)
|
||||
let filePath
|
||||
if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath;
|
||||
}
|
||||
let fileName = "Data.json";
|
||||
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived)
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
|
||||
if (bootstrappingTheProcess === true) {
|
||||
/*
|
||||
In the special situation where we are running this process for the first time (no status report found),
|
||||
we will consider the possibility that a depanant process does not produce a file during the first day of the market,
|
||||
or even more than one day. That is why we are going to advance time here.
|
||||
*/
|
||||
advanceTime()
|
||||
return
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> err = " + err.code)
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> filePath = " + filePath)
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> onFileReceived -> fileName = " + fileName)
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getCurrentFile -> Not OK -> onFileReceived -> err = " + err.code)
|
||||
callBackFunction(err)
|
||||
return
|
||||
}
|
||||
|
||||
currentFile = JSON.parse(text)
|
||||
let dataFile = previousFile.concat(currentFile)
|
||||
dataFiles.set(dependency.id, dataFile)
|
||||
dependencyControlLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dependencyControlLoop() {
|
||||
dependencyIndex++;
|
||||
if (dependencyIndex < dataDependenciesModule.nodeArray.length) {
|
||||
dependencyLoopBody()
|
||||
} else {
|
||||
generateOutput()
|
||||
}
|
||||
|
||||
function generateOutput() {
|
||||
|
||||
indicatorOutputModule.start(
|
||||
dataFiles,
|
||||
timeFrame,
|
||||
timeFrameLabel,
|
||||
bot.multiPeriodDailyProcessDatetime,
|
||||
interExecutionMemoryArray[n],
|
||||
onOutputGenerated)
|
||||
|
||||
function onOutputGenerated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
callBackFunction(err)
|
||||
return
|
||||
}
|
||||
botNeverRan = false;
|
||||
timeFramesControlLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function timeFramesControlLoop() {
|
||||
n++
|
||||
if (n < global.dailyFilePeriods.length) {
|
||||
timeFramesLoopBody()
|
||||
} else {
|
||||
n = 0;
|
||||
writeDataRanges(onWritten)
|
||||
|
||||
function onWritten(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> controlLoop -> onWritten -> err = " + err.stack)
|
||||
callBackFunction(err)
|
||||
return
|
||||
}
|
||||
writeStatusReport(bot.multiPeriodDailyProcessDatetime, advanceTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeDataRanges(callBack) {
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
let outputDatasetIndex = -1;
|
||||
controlLoop()
|
||||
|
||||
function productLoopBody() {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
writeDataRange(contextVariables.dateBeginOfMarket, bot.multiPeriodDailyProcessDatetime, productCodeName, controlLoop)
|
||||
}
|
||||
|
||||
function controlLoop() {
|
||||
outputDatasetIndex++
|
||||
if (outputDatasetIndex < outputDatasets.length) {
|
||||
productLoopBody()
|
||||
} else {
|
||||
callBack(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeDataRange(pBegin, pEnd, productCodeName, callBack) {
|
||||
let dataRange = {
|
||||
begin: pBegin.valueOf(),
|
||||
end: pEnd.valueOf() + ONE_DAY_IN_MILISECONDS
|
||||
};
|
||||
let fileContent = JSON.stringify(dataRange)
|
||||
let fileName = '/Data.Range.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + productCodeName + "/" + bot.process + fileName;
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated)
|
||||
|
||||
function onFileCreated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeDataRange -> onFileCreated -> err = " + err.stack)
|
||||
callBack(err)
|
||||
return
|
||||
}
|
||||
let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
dateRange: dataRange
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event)
|
||||
writeTimeFramesFile(productCodeName, callBack)
|
||||
}
|
||||
}
|
||||
|
||||
function writeTimeFramesFile(productCodeName, callBack) {
|
||||
|
||||
let timeFramesArray = []
|
||||
for (let n = 0; n < global.dailyFilePeriods.length; n++) {
|
||||
let timeFrameLabel = global.dailyFilePeriods[n][1]
|
||||
|
||||
/* Check Time Frames Filter */
|
||||
if (bot.dailyTimeFrames !== undefined) {
|
||||
if (bot.dailyTimeFrames.includes(timeFrameLabel) === true) {
|
||||
timeFramesArray.push(timeFrameLabel)
|
||||
}
|
||||
} else {
|
||||
timeFramesArray.push(timeFrameLabel)
|
||||
}
|
||||
}
|
||||
|
||||
let fileContent = JSON.stringify(timeFramesArray)
|
||||
let fileName = '/Time.Frames.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + productCodeName + "/" + bot.process + fileName;
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated)
|
||||
|
||||
function onFileCreated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeTimeFramesFiles -> onFileCreated -> err = " + err.stack)
|
||||
callBack(err)
|
||||
return
|
||||
}
|
||||
callBack(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function writeStatusReport(lastFileDate, callBack) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period-Daily"
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey)
|
||||
|
||||
thisReport.file.lastExecution = bot.currentDaytime;
|
||||
thisReport.file.lastFile = lastFileDate;
|
||||
thisReport.file.interExecutionMemoryArray = interExecutionMemoryArray;
|
||||
thisReport.file.beginingOfMarket = beginingOfMarket.toUTCString()
|
||||
if (bot.dailyTimeFrames !== undefined) {
|
||||
thisReport.file.timeFrames = bot.dailyTimeFrames
|
||||
}
|
||||
thisReport.save(callBack)
|
||||
|
||||
bot.hasTheBotJustStarted = false;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,221 @@
|
||||
exports.newMultiPeriodMarket = function newMultiPeriodMarket(bot, logger, UTILITIES, FILE_STORAGE) {
|
||||
const MODULE_NAME = "Multi Period Market";
|
||||
thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
};
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger)
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger)
|
||||
|
||||
let statusDependencies;
|
||||
let dataDependenciesModule;
|
||||
let dataFiles = new Map()
|
||||
let indicatorOutputModule;
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) {
|
||||
logger.fileName = MODULE_NAME;
|
||||
logger.initialize()
|
||||
|
||||
statusDependencies = pStatusDependencies;
|
||||
dataDependenciesModule = pDataDependencies;
|
||||
processConfig = pProcessConfig;
|
||||
|
||||
let INDICATOR_OUTPUT_MODULE = require("./IndicatorOutput")
|
||||
|
||||
indicatorOutputModule = INDICATOR_OUTPUT_MODULE.newIndicatorOutput(bot, logger, UTILITIES, FILE_STORAGE)
|
||||
indicatorOutputModule.initialize(callBackFunction)
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
fileStorage = undefined
|
||||
dataFiles = undefined
|
||||
statusDependencies = undefined
|
||||
dataDependenciesModule = undefined
|
||||
indicatorOutputModule = undefined
|
||||
processConfig = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
function start(callBackFunction) {
|
||||
try {
|
||||
processTimeFrames()
|
||||
|
||||
function processTimeFrames() {
|
||||
let n;
|
||||
timeFramesLoop()
|
||||
|
||||
function timeFramesLoop() {
|
||||
/* We will iterate through all posible timeFrames.*/
|
||||
n = 0 // loop Variable representing each possible period as defined at the timeFrames array.
|
||||
timeFramesLoopBody()
|
||||
}
|
||||
|
||||
function timeFramesLoopBody() {
|
||||
const timeFrame = global.marketFilesPeriods[n][0]
|
||||
const timeFrameLabel = global.marketFilesPeriods[n][1]
|
||||
|
||||
/* Check Time Frames Filter */
|
||||
if (bot.marketTimeFrames !== undefined) {
|
||||
if (bot.marketTimeFrames.includes(timeFrameLabel) === false) {
|
||||
/* We are not going to process this Time Frame */
|
||||
timeFramesControlLoop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let dependencyIndex = 0;
|
||||
dataFiles = new Map;
|
||||
|
||||
dependencyLoopBody()
|
||||
|
||||
function dependencyLoopBody() {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex]
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex]
|
||||
|
||||
getFile()
|
||||
|
||||
function getFile() {
|
||||
let fileName = "Data.json";
|
||||
let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel;
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived)
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processTimeFrames -> timeFramesLoopBody -> dependencyLoopBody -> getFile -> onFileReceived -> err = " + err.stack)
|
||||
callBackFunction(err)
|
||||
return;
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(text)
|
||||
dataFiles.set(dependency.id, dataFile)
|
||||
|
||||
dependencyControlLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dependencyControlLoop() {
|
||||
dependencyIndex++;
|
||||
if (dependencyIndex < dataDependenciesModule.nodeArray.length) {
|
||||
dependencyLoopBody()
|
||||
} else {
|
||||
generateOutput()
|
||||
}
|
||||
|
||||
function generateOutput() {
|
||||
indicatorOutputModule.start(
|
||||
dataFiles,
|
||||
timeFrame,
|
||||
timeFrameLabel,
|
||||
undefined,
|
||||
undefined,
|
||||
onOutputGenerated)
|
||||
|
||||
function onOutputGenerated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
callBackFunction(err)
|
||||
return;
|
||||
}
|
||||
timeFramesControlLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function timeFramesControlLoop() {
|
||||
n++;
|
||||
if (n < global.marketFilesPeriods.length) {
|
||||
timeFramesLoopBody()
|
||||
} else {
|
||||
writeTimeFramesFiles(onTimeFrameFilesWritten)
|
||||
function onTimeFrameFilesWritten() {
|
||||
writeStatusReport(callBackFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeTimeFramesFiles(callBack) {
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
let outputDatasetIndex = -1;
|
||||
controlLoop()
|
||||
|
||||
function productLoopBody() {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
writeTimeFramesFile(productCodeName, controlLoop)
|
||||
}
|
||||
|
||||
function controlLoop() {
|
||||
outputDatasetIndex++
|
||||
if (outputDatasetIndex < outputDatasets.length) {
|
||||
productLoopBody()
|
||||
} else {
|
||||
callBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function writeTimeFramesFile(productCodeName, callBack) {
|
||||
|
||||
let timeFramesArray = []
|
||||
for (let n = 0; n < global.marketFilesPeriods.length; n++) {
|
||||
let timeFrameLabel = global.marketFilesPeriods[n][1]
|
||||
|
||||
/* Check Time Frames Filter */
|
||||
if (bot.marketTimeFrames !== undefined) {
|
||||
if (bot.marketTimeFrames.includes(timeFrameLabel) === true) {
|
||||
timeFramesArray.push(timeFrameLabel)
|
||||
}
|
||||
} else {
|
||||
timeFramesArray.push(timeFrameLabel)
|
||||
}
|
||||
}
|
||||
|
||||
let fileContent = JSON.stringify(timeFramesArray)
|
||||
let fileName = '/Time.Frames.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + productCodeName + "/" + bot.process + fileName;
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated)
|
||||
|
||||
function onFileCreated(err) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> writeTimeFramesFile -> onFileCreated -> err = " + err.stack)
|
||||
callBack(err)
|
||||
return
|
||||
}
|
||||
callBack()
|
||||
}
|
||||
}
|
||||
|
||||
function writeStatusReport(callBack) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + "Multi-Period-Market"
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey)
|
||||
|
||||
thisReport.file.lastExecution = bot.processDatetime;
|
||||
if (bot.marketTimeFrames !== undefined) {
|
||||
thisReport.file.timeFrames = bot.marketTimeFrames
|
||||
}
|
||||
thisReport.save(callBack)
|
||||
|
||||
if (global.areEqualDates(bot.processDatetime, new Date()) === false) {
|
||||
logger.newInternalLoop(bot.codeName, bot.process, bot.processDatetime)
|
||||
}
|
||||
|
||||
/* Telling the world we are alive and doing well */
|
||||
let currentDateString = bot.processDatetime.getUTCFullYear() + '-' + utilities.pad(bot.processDatetime.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.processDatetime.getUTCDate(), 2)
|
||||
let currentDate = new Date(bot.processDatetime)
|
||||
let lastDate = new Date()
|
||||
bot.processHeartBeat(currentDateString, global.getPercentage(currentDate, currentDate, lastDate))
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
exports.newProcessExecutionEvents = function newProcessExecutionEvents(BOT, logger) {
|
||||
/*
|
||||
Here we manage the process execution events. This allow us to emit events
|
||||
when the process starts and finishes. Also to wait for a dependent event
|
||||
of another process.
|
||||
*/
|
||||
const MODULE_NAME = "Process Execution Events";
|
||||
logger.fileName = MODULE_NAME
|
||||
|
||||
let bot = BOT;
|
||||
|
||||
let thisObject = {
|
||||
networkNode: undefined,
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start,
|
||||
finish: finish
|
||||
};
|
||||
|
||||
let processThisDependsOn; // This is the process this process needs to wait for it to finishes in order to start.
|
||||
let currentProcessKey
|
||||
|
||||
let EVENT_SERVER_CLIENT = require('./EventServerClient.js');
|
||||
let eventServerClient
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(processConfig, callBackFunction) {
|
||||
try {
|
||||
let name = 'Not Depends on any Process'
|
||||
logger.fileName = MODULE_NAME + "." + name;
|
||||
|
||||
if (bot.processNode.referenceParent === undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Initialization Failed because the Process Instance needs to have a Reference Parent.");
|
||||
return
|
||||
}
|
||||
|
||||
let market = bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
currentProcessKey = bot.processNode.referenceParent.name + "-" + bot.processNode.referenceParent.type + "-" + bot.processNode.referenceParent.id + "-" + bot.exchange + "-" + market
|
||||
|
||||
/*
|
||||
A process can depend for its own execution on another process to finish, and
|
||||
this can be achieved in two different ways. The first one is by referencing
|
||||
the Execution Started Event node from a Process Definition node to a Execution
|
||||
Ended Event node of another Process Definition. The second way is when there is
|
||||
a Execution Started Event node at a Process Instance. This allows the user to
|
||||
override the definition created by the Data Mine for an specific task.
|
||||
*/
|
||||
|
||||
/*
|
||||
Here we check if we can find this relationship at the Data Mine level.
|
||||
*/
|
||||
if (bot.processNode.referenceParent.executionStartedEvent !== undefined) {
|
||||
if (bot.processNode.referenceParent.executionStartedEvent.referenceParent !== undefined) {
|
||||
if (bot.processNode.referenceParent.executionStartedEvent.referenceParent.parentNode !== undefined) {
|
||||
processThisDependsOn = bot.processNode.referenceParent.executionStartedEvent.referenceParent.parentNode
|
||||
|
||||
name = processThisDependsOn.name
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the Data Mine depends on " + name); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Here we check if at the task level if there is an overriding configuration.
|
||||
*/
|
||||
if (bot.processNode.executionStartedEvent !== undefined) {
|
||||
if (bot.processNode.executionStartedEvent.referenceParent !== undefined) {
|
||||
if (bot.processNode.executionStartedEvent.referenceParent.parentNode !== undefined) {
|
||||
processThisDependsOn = bot.processNode.executionStartedEvent.referenceParent.parentNode
|
||||
|
||||
name = processThisDependsOn.name
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " based on the User's Task depends on " + name); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (processThisDependsOn !== undefined) {
|
||||
|
||||
/* We need to remember that this process waits for another process in order to start. */
|
||||
processConfig.waitsForExecutionFinishedEvent = true
|
||||
|
||||
/* We need to find at which network node is running the process that we need to hear from when it finished. */
|
||||
let network = global.TASK_NETWORK
|
||||
|
||||
/*
|
||||
Notice that the first task that matches the dependency is going to be considered.
|
||||
This means that if the same task, with the same process, pointing to the same
|
||||
process definition in the same data mine, with the same exchange and market
|
||||
configured, is at two different network nodes at the same time, the current task
|
||||
is going to depend on the first one found, not necesarily the one running at the
|
||||
same network node. If you need to control witch task specifically to depend on
|
||||
and need to exactly the same task to exist, you should split the network in two
|
||||
different networks, each one with unique tasks so as to avoid confussion.
|
||||
*/
|
||||
for (let i = 0; i < network.networkNodes.length; i++) {
|
||||
let networkNode = network.networkNodes[i]
|
||||
if (networkNode.dataMining !== undefined) {
|
||||
for (let j = 0; j < networkNode.dataMining.exchangeDataTasks.length; j++) {
|
||||
let exchangeTasks = networkNode.dataMining.exchangeDataTasks[j]
|
||||
for (let p = 0; p < exchangeTasks.marketDataTasks.length; p++) {
|
||||
let marketTasks = exchangeTasks.marketDataTasks[p]
|
||||
if (marketTasks.referenceParent === undefined) { continue }
|
||||
if (marketTasks.referenceParent.id !== global.MARKET_NODE.id) { continue }
|
||||
for (let q = 0; q < marketTasks.dataMineTasks.length; q++) {
|
||||
let mineTasks = marketTasks.dataMineTasks[q]
|
||||
for (let k = 0; k < mineTasks.taskManagers.length; k++) {
|
||||
let taskManager = mineTasks.taskManagers[k]
|
||||
for (let m = 0; m < taskManager.tasks.length; m++) {
|
||||
let task = taskManager.tasks[m]
|
||||
if (task.bot !== undefined) {
|
||||
for (let n = 0; n < task.bot.processes.length; n++) {
|
||||
let process = task.bot.processes[n]
|
||||
if (process.referenceParent !== undefined) {
|
||||
let processDefinition = process.referenceParent
|
||||
if (processThisDependsOn.id === processDefinition.id) {
|
||||
/*
|
||||
We found where the task that runs the process definition
|
||||
we are waiting for is located on the network.
|
||||
*/
|
||||
thisObject.networkNode = networkNode
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) {
|
||||
logger.write(MODULE_NAME,
|
||||
"[INFO] initialize -> Connecting to Websockets Server " + networkNode.name +
|
||||
" -> host = " + networkNode.config.host +
|
||||
' -> port = ' + networkNode.config.webSocketsPort + '.');
|
||||
}
|
||||
|
||||
eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.config.host, networkNode.config.webSocketsPort)
|
||||
eventServerClient.initialize(onConnected)
|
||||
|
||||
function onConnected() {
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) {
|
||||
logger.write(MODULE_NAME, "[INFO] initialize -> Connected to Websockets Server " + networkNode.name +
|
||||
" -> host = " + networkNode.config.host +
|
||||
' -> port = ' + networkNode.config.webSocketsPort + '.');
|
||||
}
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Initialization Failed because we could not find where the task that runs the Process Definition we are waiting for is located within the network. Check the logs for more info.");
|
||||
return
|
||||
}
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
if (eventServerClient !== undefined) {
|
||||
eventServerClient.finalize()
|
||||
eventServerClient = undefined
|
||||
}
|
||||
|
||||
processThisDependsOn = undefined
|
||||
currentProcessKey = undefined
|
||||
thisObject.networkNode = undefined
|
||||
bot = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
function start(callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (processThisDependsOn !== undefined) {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> waitForDependantProcess -> Entering Code Block."); }
|
||||
|
||||
/* This forces this process to wait until the process that this one depends on, emits its event signaling that the process execution has finished. */
|
||||
|
||||
let extraCallerId = '-' + Math.trunc(Math.random() * 10000) + '-'
|
||||
|
||||
let market = bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let key = processThisDependsOn.name + "-" + processThisDependsOn.type + "-" + processThisDependsOn.id + "-" + bot.exchange + "-" + market
|
||||
let callerId = bot.dataMine + "-" + bot.codeName + "-" + bot.process + extraCallerId
|
||||
|
||||
let subscriptionId
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> waitForDependantProcess -> started listening to Process Execution Finished. "); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> waitForDependantProcess -> key = " + key); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> waitForDependantProcess -> callerId = " + callerId); }
|
||||
|
||||
eventServerClient.listenToEvent(key, 'Process Execution Finished', undefined, callerId, responseCallBack, eventsCallBack)
|
||||
|
||||
bot.processHeartBeat(undefined, undefined, "Waiting for " + thisObject.networkNode.name + "->" + processThisDependsOn.parentNode.parentNode.name + "->" + processThisDependsOn.parentNode.config.codeName + "->" + processThisDependsOn.config.codeName)
|
||||
|
||||
function responseCallBack(message) {
|
||||
if (message.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> responseCallBack -> message = " + JSON.stringify(message))
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
} else {
|
||||
subscriptionId = message.eventSubscriptionId
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> waitForDependantProcess -> subscriptionId = " + subscriptionId); }
|
||||
}
|
||||
}
|
||||
|
||||
function eventsCallBack(message) {
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> Entering function."); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> message = " + JSON.stringify(message)); }
|
||||
|
||||
/* We continue the normal flow after we learn the dependent process has finished its execution. */
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> stopListening to Process Execution Finished. "); }
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> subscriptionId = " + subscriptionId); }
|
||||
eventServerClient.stopListening(key, 'Process Execution Finished', subscriptionId)
|
||||
|
||||
if (message.event.err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
/* We emit our own event that the process started. */
|
||||
let event = {
|
||||
err: global.DEFAULT_OK_RESPONSE
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Started')
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Started', event)
|
||||
|
||||
bot.processHeartBeat(undefined, undefined, "Running...")
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> eventsCallBack -> " + currentProcessKey + " Process Execution Started because " + key + " Finished."); }
|
||||
}
|
||||
|
||||
callBackFunction(message.event.err);
|
||||
}
|
||||
} else {
|
||||
/* In this case, the Process does not depends on a process which needs to wait for. */
|
||||
/* We emit our own event that the process started. */
|
||||
let event = {
|
||||
err: global.DEFAULT_OK_RESPONSE
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Started')
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Started', event)
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] start -> " + currentProcessKey + " Process Execution Started "); }
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finish(callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] finish -> Entering function."); }
|
||||
|
||||
let event = {
|
||||
err: global.DEFAULT_OK_RESPONSE
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.createEventHandler(currentProcessKey, 'Process Execution Finished')
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(currentProcessKey, 'Process Execution Finished', event)
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] finish -> " + currentProcessKey + " Process Execution Finished "); }
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] finish -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,184 @@
|
||||
exports.newProcessOutput = function newProcessOutput(BOT, logger) {
|
||||
|
||||
/*
|
||||
Here we emit the events that signals that a Dataset was updated.
|
||||
*/
|
||||
|
||||
const MODULE_NAME = "Process Output";
|
||||
|
||||
let bot = BOT;
|
||||
|
||||
let thisObject = {
|
||||
raiseEvents: raiseEvents,
|
||||
asyncRaiseEvents: asyncRaiseEvents
|
||||
};
|
||||
|
||||
return thisObject;
|
||||
|
||||
function raiseEvents(lastFile, timeFrames, callBackFunction) {
|
||||
try {
|
||||
/* We will reaise the events for the datasets impacted by the process that just finished. */
|
||||
|
||||
if (bot.processNode.referenceParent.processOutput !== undefined) {
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
|
||||
for (let j = 0; j < outputDatasets.length; j++) {
|
||||
let outputDataset = outputDatasets[j]
|
||||
|
||||
let botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Indicator Bot')
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Trading Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Sensor Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Learning Bot')
|
||||
}
|
||||
|
||||
/* Some validations to verify that everything is in place. */
|
||||
if (outputDataset.referenceParent !== undefined) {
|
||||
if (outputDataset.referenceParent.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
if (outputDataset.referenceParent.parentNode !== undefined) {
|
||||
if (outputDataset.referenceParent.parentNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
if (botNode !== undefined) {
|
||||
if (botNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(botNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
if (botNode.parentNode !== undefined) {
|
||||
if (botNode.parentNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(botNode.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(botNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Product not attached to a Bot. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Dataset Definition not attached to a Product. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Output Dataset with no reference to a Dataset Definition. Output Dataset = " + JSON.stringify(outputDataset));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* All good, lets emit the event. */
|
||||
|
||||
let dataMine = botNode.parentNode.config.codeName
|
||||
let botName = botNode.config.codeName
|
||||
let product = outputDataset.referenceParent.parentNode.config.codeName
|
||||
let dataset = outputDataset.referenceParent.config.codeName
|
||||
|
||||
let key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
lastFile: lastFile,
|
||||
timeFrames: timeFrames
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.createEventHandler(key, 'Dataset Updated')
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Dataset Updated', event)
|
||||
}
|
||||
}
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function asyncRaiseEvents(lastFile, timeFrames) {
|
||||
|
||||
if (bot.processNode.referenceParent.processOutput !== undefined) {
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
|
||||
for (let j = 0; j < outputDatasets.length; j++) {
|
||||
let outputDataset = outputDatasets[j]
|
||||
|
||||
let botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Indicator Bot')
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Trading Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Sensor Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Learning Bot')
|
||||
}
|
||||
|
||||
/* Some validations to verify that everything is in place. */
|
||||
if (outputDataset.referenceParent !== undefined) {
|
||||
if (outputDataset.referenceParent.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Dataset Definition not defined. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
if (outputDataset.referenceParent.parentNode !== undefined) {
|
||||
if (outputDataset.referenceParent.parentNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Product not defined. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
if (botNode !== undefined) {
|
||||
if (botNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(botNode));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
if (botNode.parentNode !== undefined) {
|
||||
if (botNode.parentNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Mine not defined. Mine = " + JSON.stringify(botNode.parentNode));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine | Trading Mine | Learning Mine. Bot = " + JSON.stringify(botNode));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Product not attached to a Bot. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Dataset Definition not attached to a Product. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Output Dataset with no reference to a Dataset Definition. Output Dataset = " + JSON.stringify(outputDataset));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
|
||||
/* All good, lets emit the event. */
|
||||
|
||||
let dataMine = botNode.parentNode.config.codeName
|
||||
let botName = botNode.config.codeName
|
||||
let product = outputDataset.referenceParent.parentNode.config.codeName
|
||||
let dataset = outputDataset.referenceParent.config.codeName
|
||||
|
||||
let key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
lastFile: lastFile,
|
||||
timeFrames: timeFrames
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.createEventHandler(key, 'Dataset Updated')
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Dataset Updated', event)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{ae0a3fe2-7021-4cc2-9922-a887ca5cdd7c}</ProjectGuid>
|
||||
<ProjectHome />
|
||||
<ProjectView>ShowAllFiles</ProjectView>
|
||||
<StartupFile>server.js</StartupFile>
|
||||
<WorkingDirectory>.\</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<Name>Project</Name>
|
||||
<ScriptArguments>
|
||||
</ScriptArguments>
|
||||
<NodeExePath>C:\Program Files\nodejs\node.exe</NodeExePath>
|
||||
<StartWebBrowser>False</StartWebBrowser>
|
||||
<LastActiveSolutionConfig>Debug|Any CPU</LastActiveSolutionConfig>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'" />
|
||||
<ItemGroup>
|
||||
<Compile Include="Assistant.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Commons.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ExchangeAPI.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="IndicatorBot.js" />
|
||||
<Compile Include="FileStorage.js" />
|
||||
<Compile Include="MultiPeriod.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MultiPeriodDaily.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MultiPeriodMarket.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ProcessExecutionEvents.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ProcessOutput.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Session.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="EventServerClient.js" />
|
||||
<Compile Include="TradingBot.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="TradingBotProcessMainLoop.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="TradingSimulation.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="DataDependencies.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="DataSet.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Root.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="StatusDependencies.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="SensorBotProcessMainLoop.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Context.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="DebugLog.js" />
|
||||
<Compile Include="IndicatorBotProcessMainLoop.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="server.js" />
|
||||
<Compile Include="StatusReport.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CloudUtilities.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include=".env" />
|
||||
<Content Include=".gitignore" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<!--Do not delete the following Import Project. While this appears to do nothing it is a marker for setting TypeScript properties before our import that depends on them.-->
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="False" />
|
||||
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsTools.targets" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>0</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:48022/</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>True</UseCustomServer>
|
||||
<CustomServerUrl>http://localhost:1337</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
|
||||
<WebProjectProperties>
|
||||
<StartPageUrl>
|
||||
</StartPageUrl>
|
||||
<StartAction>CurrentPage</StartAction>
|
||||
<AspNetDebugging>True</AspNetDebugging>
|
||||
<SilverlightDebugging>False</SilverlightDebugging>
|
||||
<NativeDebugging>False</NativeDebugging>
|
||||
<SQLDebugging>False</SQLDebugging>
|
||||
<ExternalProgram>
|
||||
</ExternalProgram>
|
||||
<StartExternalURL>
|
||||
</StartExternalURL>
|
||||
<StartCmdLineArguments>
|
||||
</StartCmdLineArguments>
|
||||
<StartWorkingDirectory>
|
||||
</StartWorkingDirectory>
|
||||
<EnableENC>False</EnableENC>
|
||||
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
# Task Server
|
||||
|
||||
This repo is one of the repositories built into Superalgos. For more information [read the docs](https://docs.superalgos.org/) or check the [Readme](https://github.com/Superalgos/Superalgos/) at the main repository of the project.
|
||||
|
||||
## Copyright and License Notice
|
||||
|
||||
© Copyright 2020 Superalgos. Please see the [License](/LICENSE) for information on how you can use this software.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
THE SOFTWARE AND ITS ASSOCIATED PRODUCTS AND SERVICES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, SUITABILITY FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
|
||||
|
||||
IN NO EVENT WILL SUPERALGOS BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY USE OF THE SOFTWARE, OR ANY OTHER ASSOCIATED SERVICE OR WEB SITE, INCLUDING, WITHOUT LIMITATION, ANY LOST PROFITS, BUSINESS INTERRUPTION, LOSS OF FUNDS, PROGRAMS OR OTHER DATA OR OTHERWISE, EVEN IF WE ARE EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
# Maintainer
|
||||
|
||||
[](https://github.com/Luis-Fernando-Molina) |
|
||||
---|
|
||||
[Luis-Fernando-Molina](https://github.com/Luis-Fernando-Molina) |
|
||||
@@ -0,0 +1,685 @@
|
||||
exports.newLearningBot = function newLearningBot(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Learning Bot";
|
||||
const FULL_LOG = true;
|
||||
|
||||
const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/MachineLearningProcess.js');
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
const SESSION = require(global.ROOT_DIR + 'TradingSession');
|
||||
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(parentLogger);
|
||||
let session = SESSION.newLearningSession(bot, parentLogger)
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble.
|
||||
|
||||
let nextLoopTimeoutHandle;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
run: run
|
||||
};
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
/* This function is exactly the same in the 3 modules representing the 2 different bot types loops. */
|
||||
try {
|
||||
processConfig = pProcessConfig;
|
||||
if (bot.definedByUI === true) {
|
||||
/* The code of the bot is defined at the UI. No need to load a file with the code. */
|
||||
session.initialize(processConfig, callBackFunction)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function run(callBackFunction) {
|
||||
try {
|
||||
/* Some initial values*/
|
||||
let fixedTimeLoopIntervalHandle;
|
||||
bot.STOP_SESSION = true;
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] run -> Stopping the Session now. ') }
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.processHeartBeat = processHeartBeat
|
||||
|
||||
loop();
|
||||
|
||||
function loop() {
|
||||
try {
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
/* For each loop we want to create a new log file. */
|
||||
if (logger !== undefined) {
|
||||
logger.finalize()
|
||||
}
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set(MODULE_NAME, logger)
|
||||
logger.bot = bot;
|
||||
logger.initialize();
|
||||
|
||||
bot.loopCounter++;
|
||||
bot.loopStartTime = new Date().valueOf();
|
||||
|
||||
let nextWaitTime;
|
||||
|
||||
/* We tell the UI that we are running. */
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
|
||||
/* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */
|
||||
const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities');
|
||||
const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport');
|
||||
const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies');
|
||||
const DATA_DEPENDENCIES = require(global.ROOT_DIR + 'DataDependencies');
|
||||
const DATA_SET = require(global.ROOT_DIR + 'DataSet');
|
||||
const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents');
|
||||
const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput');
|
||||
|
||||
/* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */
|
||||
bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution.
|
||||
|
||||
/* High level log entry */
|
||||
console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30)
|
||||
+ " Main Loop # " + pad(Number(bot.loopCounter), 8) + " " + bot.processNode.session.type + " " + bot.processNode.session.name)
|
||||
|
||||
/* Checking if we need to need to emit any event */
|
||||
if (bot.SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) {
|
||||
bot.SESSION_STATUS = 'Running'
|
||||
}
|
||||
|
||||
if (bot.SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) {
|
||||
bot.SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
|
||||
global.EMIT_SESSION_STATUS (bot.SESSION_STATUS, bot.sessionKey)
|
||||
|
||||
/* Checking if we should process this loop or not.*/
|
||||
if (bot.STOP_SESSION === true) {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run."); }
|
||||
|
||||
console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.process, 30)
|
||||
+ " Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ");
|
||||
|
||||
nextWaitTime = 'Waiting for Session';
|
||||
loopControl(nextWaitTime);
|
||||
return
|
||||
}
|
||||
|
||||
/* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */
|
||||
|
||||
let processExecutionEvents
|
||||
let userBot;
|
||||
let processFramework;
|
||||
let statusDependencies;
|
||||
let dataDependencies;
|
||||
|
||||
initializeProcessExecutionEvents();
|
||||
|
||||
function initializeProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
|
||||
processExecutionEvents.initialize(processConfig, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well.");
|
||||
startProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.start(onStarted);
|
||||
|
||||
function onStarted(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well.");
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
loopControl()
|
||||
return
|
||||
}
|
||||
|
||||
initializeStatusDependencies();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> 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(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeStatusDependencies() {
|
||||
try {
|
||||
statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT);
|
||||
statusDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well.");
|
||||
initializeDataDependencies();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDataDependencies() {
|
||||
try {
|
||||
dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET);
|
||||
dataDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); }
|
||||
switch (processConfig.framework.name) {
|
||||
case 'Low-Frequency-Trading-Process': {
|
||||
processFramework = TRADING_PROCESS_MODULE.newMachineLearningProcess(bot, logger, UTILITIES);
|
||||
intitializeProcessFramework();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported.");
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + processConfig.framework.name);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function intitializeProcessFramework() {
|
||||
try {
|
||||
processFramework.initialize(processConfig, statusDependencies, dataDependencies, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well.");
|
||||
startProcessFramework();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> 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(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessFramework() {
|
||||
try {
|
||||
processFramework.start(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processFramework.finalize()
|
||||
processFramework = undefined
|
||||
dataDependencies.finalize()
|
||||
dataDependencies = undefined
|
||||
statusDependencies.finalize()
|
||||
statusDependencies = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
finishProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
case "Dependency does not exist.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case "Dependency not ready.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function finishProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.finish(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processExecutionEvents.finalize()
|
||||
processExecutionEvents = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
loopControl(nextWaitTime);
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function loopControl(nextWaitTime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); }
|
||||
|
||||
/* We show we reached the end of the loop. */
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
|
||||
/* Here we check if we must stop the loop gracefully. */
|
||||
shallWeStop(onStop, onContinue);
|
||||
|
||||
function onStop() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); }
|
||||
|
||||
if (global.WRITE_LOGS_TO_FILES === 'true') {
|
||||
logger.persist();
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped')
|
||||
processStopped()
|
||||
return;
|
||||
}
|
||||
|
||||
function onContinue() {
|
||||
/* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */
|
||||
switch (nextWaitTime) {
|
||||
case 'Waiting for Session': {
|
||||
let waitTime = processConfig.sessionRunWaitTime
|
||||
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
|
||||
let waitingTime = waitTime / 1000 / 60
|
||||
let label = 'minute/s'
|
||||
if (waitingTime < 1) {
|
||||
waitingTime = waitTime / 1000
|
||||
label = 'seconds'
|
||||
}
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ")
|
||||
logger.persist();
|
||||
}
|
||||
break
|
||||
case 'Normal': {
|
||||
let waitTime
|
||||
if (processConfig.waitsForExecutionFinishedEvent === true) {
|
||||
waitTime = 0
|
||||
} else {
|
||||
switch (bot.LEARNING_SESSION.type) {
|
||||
case 'Live Trading Session': {
|
||||
waitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Fordward Tessting Session': {
|
||||
waitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
waitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Backtesting Session': {
|
||||
waitTime = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
|
||||
let waitingTime = waitTime / 1000 / 60
|
||||
let label = 'minute/s'
|
||||
if (waitingTime < 1) {
|
||||
waitingTime = waitTime / 1000
|
||||
label = 'seconds'
|
||||
}
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Retry': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Sleep': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Coma': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shallWeStop(stopCallBack, continueCallBack) {
|
||||
try {
|
||||
/* IMPORTANT: This function is exactly the same on the 3 modules. */
|
||||
if (!global.STOP_TASK_GRACEFULLY) {
|
||||
continueCallBack();
|
||||
} else {
|
||||
stopCallBack();
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function processHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
|
||||
}
|
||||
|
||||
function processStopped() {
|
||||
if (global.unexpectedError !== undefined) {
|
||||
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
|
||||
} else {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
|
||||
}
|
||||
sessionStopped()
|
||||
logger.persist();
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
if (global.unexpectedError !== undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
} else {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function sessionStopped() {
|
||||
if (bot.SESSION_STATUS === 'Running') {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped')
|
||||
bot.SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack);
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,786 @@
|
||||
exports.newLearningProcess = function newLearningProcess(bot, logger, UTILITIES) {
|
||||
/*
|
||||
This Module will load all the process data dependencies from files and send them downstream.
|
||||
After execution, will save the time range and status report of the process.
|
||||
*/
|
||||
const FULL_LOG = true;
|
||||
const MODULE_NAME = "Learning Process";
|
||||
const GMT_SECONDS = ':00.000 GMT+0000';
|
||||
|
||||
thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
};
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger);
|
||||
|
||||
let statusDependencies;
|
||||
let dataDependenciesModule;
|
||||
let dataFiles = new Map();
|
||||
let multiPeriodDataFiles = new Map();
|
||||
|
||||
const FILE_STORAGE = require('../FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger);
|
||||
|
||||
const MARKOV_DECISION_PROCESS_MODULE = require('./MarkovDecisionProcess.js')
|
||||
let markovDecisionProcessModule = MARKOV_DECISION_PROCESS_MODULE.newMarkovDecisionProcess(bot, logger)
|
||||
|
||||
let ALGORITHM_MODULE = require("./Algorithm")
|
||||
let algorithmModule = ALGORITHM_MODULE.newTradingOutput(bot, logger, markovDecisionProcessModule, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) {
|
||||
try {
|
||||
logger.fileName = MODULE_NAME
|
||||
logger.initialize()
|
||||
|
||||
statusDependencies = pStatusDependencies
|
||||
dataDependenciesModule = pDataDependencies
|
||||
processConfig = pProcessConfig
|
||||
|
||||
algorithmModule.initialize()
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
|
||||
markovDecisionProcessModule.finalize()
|
||||
markovDecisionProcessModule = undefined
|
||||
|
||||
algorithmModule.finalize()
|
||||
algorithmModule = undefined
|
||||
|
||||
dataFiles = undefined
|
||||
multiPeriodDataFiles = undefined
|
||||
statusDependencies = undefined
|
||||
dataDependenciesModule = undefined
|
||||
fileStorage = undefined
|
||||
processConfig = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
async function start(callBackFunction) {
|
||||
try {
|
||||
let currentTimeFrame
|
||||
let currentTimeFrameLabel
|
||||
|
||||
/* Context Variables */
|
||||
let contextVariables = {
|
||||
lastFile: undefined, // Datetime of the last file files sucessfully produced by this process.
|
||||
dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history.
|
||||
dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process.
|
||||
};
|
||||
|
||||
getContextVariables()
|
||||
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
Here is where the Trading Engine and Trading Systems received are moved to the simulation state.
|
||||
*/
|
||||
bot.simulationState.tradingEngine = bot.TRADING_ENGINE
|
||||
bot.simulationState.tradingSystem = bot.TRADING_SYSTEM
|
||||
}
|
||||
|
||||
/* We set up the Trading Engine Module. */
|
||||
markovDecisionProcessModule.initialize()
|
||||
|
||||
/* Initializing the Trading Process Date */
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
This funcion is going to be called many times by the Trading Bot Loop.
|
||||
Only during the first execution and when the User is not resuming the execution
|
||||
of a stopped session / task; we are going to initialize the Process Date Time.
|
||||
This variable tell us which day we are standing at, specially while working
|
||||
with Daily Files. From this Date is that we are going to load the Daily Files.
|
||||
*/
|
||||
bot.simulationState.tradingEngine.current.episode.processDate.value = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime).valueOf()
|
||||
}
|
||||
|
||||
/*
|
||||
This is the Date that is going to be used across the execution of this Trading Process.
|
||||
We need this because it has a different life cycle than the processData stored at the
|
||||
Trading Engine data structure. This date has to remain the same during the whole execution
|
||||
of the Trading Process until the end, inclusind the writting of Data Ranges and Status Reports.
|
||||
The processDate of the Trading Engine data structure on the other hand can be changed during
|
||||
the simulation loop, once we discover that all candles from a certain date have benn processed.
|
||||
Here is the point where we sync one and the other.
|
||||
*/
|
||||
let LearningProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
await processSingleFiles()
|
||||
|
||||
if (await processMarketFiles() === false) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
This is the Data Structure used at the Simulation with all indicator data.
|
||||
We start creating it right here.
|
||||
*/
|
||||
let chart = {}
|
||||
/*
|
||||
Here we check if we need to get Daily Files or not. As an optimization, when
|
||||
we are running on a Time Frame of 1hs or above, we are not going to load
|
||||
dependencies on Daily Files. The way we recognize that is by checking if
|
||||
we alreaady set a value to currentTimeFrame. We are also not going to loop
|
||||
through days if we are processing market files.
|
||||
*/
|
||||
if (currentTimeFrame) {
|
||||
/* We are processing Market Files */
|
||||
/*
|
||||
With all the indicators data files loaded, we will build the chart object
|
||||
data structure that will be used in user-defied conditions and formulas.
|
||||
*/
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
buildCharts(chart)
|
||||
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date. No candles found at.')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
/* We are processing Daily Files */
|
||||
do {
|
||||
global.EMIT_SESSION_STATUS(bot.TRADING_SESSION_STATUS, bot.TRADING_SESSIONKey)
|
||||
/*
|
||||
We update the Trading Process Date with the date calculated at the simulation.
|
||||
We will use this date to load indicator and output files. After that we will
|
||||
use it to save Output Files and later the Data Ranges. This is the point where
|
||||
the date calculated by the Simulation is applied at the Trading Process Level.
|
||||
*/
|
||||
LearningProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
|
||||
if (checkStopTaskGracefully() === false) { break }
|
||||
if (checkStopProcessing() === false) { break }
|
||||
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
if (await processDailyFiles() === false) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
With all the indicators data files loaded, we will build the chart object
|
||||
data structure that will be used in user-defied conditions and formulas.
|
||||
*/
|
||||
buildCharts(chart)
|
||||
/*
|
||||
The process of generating the output includes the trading simulation.
|
||||
*/
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
If for any reason the session was stopped, we will break this loop and exit the process.
|
||||
*/
|
||||
if (bot.STOP_SESSION === true) { break }
|
||||
/*
|
||||
When we get to the end of the market, we need to break this process loop in order
|
||||
to let time pass, new information be collected from the exchange, new data built
|
||||
into indicators, and eventually a new execution of this process.
|
||||
*/
|
||||
if (checkStopHeadOfTheMarket() === false) { break }
|
||||
|
||||
}
|
||||
while (true)
|
||||
}
|
||||
|
||||
checkIfSessionMustStop()
|
||||
/*
|
||||
Everything worked as expected. We return an OK code and wait for
|
||||
the Bot Loop to call us again later.
|
||||
*/
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
function checkThereAreCandles(chart) {
|
||||
let sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
|
||||
let candles = chart[propertyName].candles
|
||||
|
||||
if (candles.length === 0) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function getContextVariables() {
|
||||
try {
|
||||
let thisReport;
|
||||
let statusReport;
|
||||
|
||||
/* We are going to use the start date as beging of market date. */
|
||||
contextVariables.dateBeginOfMarket = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)
|
||||
/*
|
||||
Here we get the status report from the bot who knows which is the end of the market.
|
||||
*/
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.lastFile === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport = " + JSON.stringify(thisReport));
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: "Dependency not ready."
|
||||
}
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message);
|
||||
callBackFunction(customOK);
|
||||
return
|
||||
}
|
||||
|
||||
contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf());
|
||||
|
||||
/* Finally we get our own Status Report. */
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Self Reference")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
bot.simulationState = thisReport.simulationState;
|
||||
if (bot.simulationState === undefined) { bot.simulationState = {} } // This should happen only when there is no status report
|
||||
|
||||
if (thisReport.lastFile !== undefined) {
|
||||
if (bot.RESUME !== true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); }
|
||||
startFromBegining();
|
||||
return
|
||||
}
|
||||
contextVariables.lastFile = new Date(thisReport.lastFile);
|
||||
return
|
||||
|
||||
} else {
|
||||
/*
|
||||
We are here becuse either:
|
||||
1. There is no status report
|
||||
2. There is no Last File (this happens on Market Files)
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); }
|
||||
startFromBegining();
|
||||
return
|
||||
}
|
||||
|
||||
function startFromBegining() {
|
||||
contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS);
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); }
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack);
|
||||
if (err.message === "Cannot read property 'file' of undefined") {
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. ");
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys));
|
||||
}
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
async function processSingleFiles() {
|
||||
dataFiles = new Map();
|
||||
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (datasetModule.node.config.codeName !== "Single-File") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
continue
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName;
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
}
|
||||
|
||||
let mapKey = "Single Files"
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
|
||||
async function processMarketFiles() {
|
||||
/*
|
||||
We do market files first since if the simulation is run on daily files, there will
|
||||
be a loop to get each of those files and we do not need that loop to reload market files.
|
||||
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
for (let n = 0; n < global.marketFilesPeriods.length; n++) {
|
||||
const timeFrame = global.marketFilesPeriods[n][0];
|
||||
const timeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
|
||||
dataFiles = new Map();
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.marketFilesPeriods[n][0];
|
||||
currentTimeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
}
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel;
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.message === 'File does not exist.') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record)
|
||||
dataFiles.set(dependency.id, trimmedDataFile);
|
||||
|
||||
function trimDataFile(dataFile, recordDefinition) {
|
||||
/*
|
||||
Here we will discard all the records in a file that are outside of the current time range.
|
||||
We will include the las element previous to the begining of the time range. This is needed
|
||||
because during the simulation, the current period is not the open one, but the previous to
|
||||
the open, and if we do not include the previous to the initial datetime there will be no
|
||||
current objects at the begining of the simulation for many time frames.
|
||||
*/
|
||||
let beginIndex
|
||||
let endIndex
|
||||
let result = []
|
||||
for (let i = 0; i < recordDefinition.properties.length; i++) {
|
||||
let property = recordDefinition.properties[i]
|
||||
if (property.config.codeName === 'begin') {
|
||||
beginIndex = i
|
||||
}
|
||||
if (property.config.codeName === 'end') {
|
||||
endIndex = i
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < dataFile.length; i++) {
|
||||
let dataRecord = dataFile[i]
|
||||
let begin = dataRecord[beginIndex]
|
||||
let end = dataRecord[endIndex]
|
||||
if (end + timeFrame < bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element
|
||||
if (begin > bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime) { continue }
|
||||
result.push(dataRecord)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
let mapKey = global.marketFilesPeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function processDailyFiles() {
|
||||
/* Telling the world we are alive and doing well and which date we are processing right now. */
|
||||
let processingDateString = LearningProcessDate.getUTCFullYear() + '-' + utilities.pad(LearningProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(LearningProcessDate.getUTCDate(), 2);
|
||||
bot.processHeartBeat(processingDateString, undefined, "Running...")
|
||||
|
||||
/*
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
for (let n = 0; n < global.dailyFilePeriods.length; n++) {
|
||||
const timeFrame = global.dailyFilePeriods[n][0];
|
||||
const timeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
|
||||
if (processConfig.framework.validtimeFrames !== undefined) {
|
||||
let validPeriod = false;
|
||||
for (let i = 0; i < processConfig.framework.validtimeFrames.length; i++) {
|
||||
let period = processConfig.framework.validtimeFrames[i];
|
||||
if (period === timeFrameLabel) { validPeriod = true }
|
||||
}
|
||||
if (validPeriod === false) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.dailyFilePeriods[n][0];
|
||||
currentTimeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
}
|
||||
|
||||
dataFiles = new Map();
|
||||
|
||||
/*
|
||||
We will iterate through all dependencies, in order to load the
|
||||
files that later will end up at the chart data structure.
|
||||
*/
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let dateForPath = LearningProcessDate.getUTCFullYear() + '/' + utilities.pad(LearningProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(LearningProcessDate.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath;
|
||||
}
|
||||
let fileName = "Data.json";
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.message === 'File does not exist.') {
|
||||
return false
|
||||
}
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
}
|
||||
|
||||
let mapKey = global.dailyFilePeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkStopHeadOfTheMarket() {
|
||||
/*
|
||||
We need to check if we have reached the head of the market in order to know
|
||||
when to break the Daily Files Process loop and give time for a new candles /
|
||||
indicators to be built and continue the processing once this process is called
|
||||
again.
|
||||
*/
|
||||
if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopTaskGracefully() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopProcessing() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (bot.STOP_SESSION === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function buildCharts(chart) {
|
||||
const COMMONS = require('../Commons.js')
|
||||
let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let mainDependency = {}
|
||||
|
||||
/* The first phase here is about checking that we have everything we need at the definition level. */
|
||||
let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency')
|
||||
/*
|
||||
We will filter ourt declared dependencies that are not present in the workspace.
|
||||
This will allow the user to have less Data Mines loaded at the workspace
|
||||
that the ones that a Trading Mine depends on.
|
||||
*/
|
||||
dataDependencies = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(dataDependencies)
|
||||
|
||||
if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return }
|
||||
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
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 timeFrameLabel = marketFilesPeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
|
||||
let timeFrameLabel = global.dailyFilePeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
/* Single Files */
|
||||
let dataFiles = multiPeriodDataFiles.get('Single Files')
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'atAnyTimeFrame'
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
async function generateOutput(chart) {
|
||||
await algorithmModule.start(
|
||||
chart,
|
||||
currentTimeFrame,
|
||||
currentTimeFrameLabel,
|
||||
LearningProcessDate
|
||||
)
|
||||
|
||||
/*
|
||||
From here on, all other loops executions wont be the first execution and also
|
||||
we will consider that it is not resuming a previous execution as well.
|
||||
*/
|
||||
bot.FIRST_EXECUTION = false
|
||||
bot.RESUME = false
|
||||
}
|
||||
|
||||
async function writeProcessFiles() {
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
|
||||
await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel)
|
||||
await writeDataRanges()
|
||||
|
||||
if (currentTimeFrame > global.dailyFilePeriods[0][0]) {
|
||||
await writeMarketStatusReport(currentTimeFrameLabel)
|
||||
} else {
|
||||
await writeDailyStatusReport(currentTimeFrameLabel)
|
||||
}
|
||||
|
||||
async function writeDataRanges() {
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeDataRange(productCodeName);
|
||||
}
|
||||
|
||||
async function writeDataRange(productCodeName) {
|
||||
let dataRange = {
|
||||
begin: contextVariables.dateBeginOfMarket.valueOf(),
|
||||
end: LearningProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS
|
||||
}
|
||||
|
||||
let fileContent = JSON.stringify(dataRange);
|
||||
let fileName = '/Data.Range.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
/*
|
||||
Raise the event that the Data Range was Updated.
|
||||
*/
|
||||
let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
dateRange: dataRange
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event)
|
||||
}
|
||||
}
|
||||
|
||||
async function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) {
|
||||
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily')
|
||||
await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Market')
|
||||
|
||||
async function writeTimeFramesFile(currentTimeFrameLabel, productCodeName, processType) {
|
||||
|
||||
let timeFramesArray = []
|
||||
timeFramesArray.push(currentTimeFrameLabel)
|
||||
|
||||
let fileContent = JSON.stringify(timeFramesArray)
|
||||
let fileName = '/Time.Frames.json';
|
||||
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + processType + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeDailyStatusReport(currentTimeFrameLabel) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey)
|
||||
|
||||
thisReport.file.lastExecution = bot.currentDaytime
|
||||
thisReport.file.lastFile = LearningProcessDate
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
await thisReport.asyncSave()
|
||||
}
|
||||
|
||||
async function writeMarketStatusReport(currentTimeFrameLabel) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey);
|
||||
|
||||
thisReport.file.lastExecution = bot.processDatetime
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
|
||||
logger.newInternalLoop(bot.codeName, bot.process, LearningProcessDate)
|
||||
await thisReport.asyncSave()
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfSessionMustStop() {
|
||||
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
/*
|
||||
Backtests needs only one execution of this process to complete.
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] checkIfSessionMustStop -> Backtesting Session Finished. Stopping the Session now. ') }
|
||||
bot.TRADING_SESSION.stop('Backtesting Session Finished.')
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
/* An unhandled exception occured. in this case we return Fail and log the stack. */
|
||||
if (err.stack) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Unhandled Exception. Will Abort this process. err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* Some expected file was not found. We will return a RETRY code and move on. */
|
||||
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] File not Found. Will Retry the Process Loop.")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* Some other handled exception occured. We return Fail and move on. */
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Handled Exception. Will Abort this process. err = " + err.message);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
async function asyncGetDatasetFile(datasetModule, filePath, fileName) {
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived);
|
||||
function onFileReceived(err, text) {
|
||||
|
||||
let response = {
|
||||
err: err,
|
||||
text: text
|
||||
}
|
||||
resolve(response)
|
||||
}
|
||||
});
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
exports.newMarkovDecisionProcess = function newMarkovDecisionProcess(bot, logger) {
|
||||
/*
|
||||
We call the Trading Engine to the data structure that is needed in order to exevute the
|
||||
trading protocol with the specific rules defined at the Trading System.
|
||||
*/
|
||||
const MODULE_NAME = 'Markov Decision Process'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
getNodeById: getNodeById,
|
||||
cloneValues: cloneValues,
|
||||
setCurrentCandle: setCurrentCandle,
|
||||
setCurrentCycle: setCurrentCycle,
|
||||
initializeNode: initializeNode,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
let nodesMap = new Map()
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.LEARNING_SESSION.learningParameters
|
||||
|
||||
initializeNodeMap(tradingEngine)
|
||||
|
||||
if (bot.FIRST_EXECUTION === true) {
|
||||
/*
|
||||
Here we will go through all the nodes in the Trading Engine hiriarchy and
|
||||
apply the initial value to the value property when needed
|
||||
*/
|
||||
initializeNode(tradingEngine)
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
nodesMap = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
}
|
||||
|
||||
function reset() {
|
||||
|
||||
}
|
||||
|
||||
function getNodeById(NodeId) {
|
||||
return nodesMap.get(NodeId)
|
||||
}
|
||||
|
||||
function setCurrentCandle(candle) {
|
||||
tradingEngine.current.episode.candle.begin.value = candle.begin
|
||||
tradingEngine.current.episode.candle.end.value = candle.end
|
||||
tradingEngine.current.episode.candle.open.value = candle.open
|
||||
tradingEngine.current.episode.candle.close.value = candle.close
|
||||
tradingEngine.current.episode.candle.min.value = candle.min
|
||||
tradingEngine.current.episode.candle.max.value = candle.max
|
||||
}
|
||||
|
||||
function setCurrentCycle(cycle) {
|
||||
tradingEngine.current.episode.cycle.value = cycle
|
||||
tradingEngine.current.episode.cycle.lastBegin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
tradingEngine.current.episode.cycle.lastEnd.value = tradingEngine.current.episode.cycle.end.value
|
||||
switch (cycle) {
|
||||
case 'First': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2 - 1
|
||||
break
|
||||
}
|
||||
case 'Second': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value * 4 / 4 - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cloneValues(originNode, destinationNode) {
|
||||
if (originNode === undefined) { return }
|
||||
if (destinationNode === undefined) { return }
|
||||
|
||||
/* Here copy the node value */
|
||||
destinationNode.value = originNode.value
|
||||
|
||||
/* Now we go down through all the children of the origin node, assuming the destination node has the same children structure*/
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(originNode.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
cloneValues(originNode[property.name], destinationNode[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
let originNodePropertyArray = originNode[property.name]
|
||||
let destinationNodePropertyArray = destinationNode[property.name]
|
||||
for (let m = 0; m < originNodePropertyArray.length; m++) {
|
||||
cloneValues(originNodePropertyArray[m], destinationNodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNodeMap(node) {
|
||||
if (node === undefined) { return }
|
||||
/* Add to Node Map */
|
||||
nodesMap.set(node.id, node)
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
initializeNodeMap(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
initializeNodeMap(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNode(node) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Here we initialize the node value */
|
||||
if (node.config !== undefined) {
|
||||
if (node.config.initialValue !== undefined) {
|
||||
node.value = node.config.initialValue
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
initializeNode(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
initializeNode(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,644 @@
|
||||
exports.newRoot = function newRoot() {
|
||||
|
||||
/* This is the Execution Datetime */
|
||||
|
||||
global.EXECUTION_DATETIME = new Date();
|
||||
|
||||
/* Time Frames Definitions. */
|
||||
|
||||
global.marketFilesPeriods =
|
||||
'[' +
|
||||
'[' + 24 * 60 * 60 * 1000 + ',' + '"24-hs"' + ']' + ',' +
|
||||
'[' + 12 * 60 * 60 * 1000 + ',' + '"12-hs"' + ']' + ',' +
|
||||
'[' + 8 * 60 * 60 * 1000 + ',' + '"08-hs"' + ']' + ',' +
|
||||
'[' + 6 * 60 * 60 * 1000 + ',' + '"06-hs"' + ']' + ',' +
|
||||
'[' + 4 * 60 * 60 * 1000 + ',' + '"04-hs"' + ']' + ',' +
|
||||
'[' + 3 * 60 * 60 * 1000 + ',' + '"03-hs"' + ']' + ',' +
|
||||
'[' + 2 * 60 * 60 * 1000 + ',' + '"02-hs"' + ']' + ',' +
|
||||
'[' + 1 * 60 * 60 * 1000 + ',' + '"01-hs"' + ']' + ']';
|
||||
|
||||
global.marketFilesPeriods = JSON.parse(global.marketFilesPeriods);
|
||||
|
||||
global.dailyFilePeriods =
|
||||
'[' +
|
||||
'[' + 45 * 60 * 1000 + ',' + '"45-min"' + ']' + ',' +
|
||||
'[' + 40 * 60 * 1000 + ',' + '"40-min"' + ']' + ',' +
|
||||
'[' + 30 * 60 * 1000 + ',' + '"30-min"' + ']' + ',' +
|
||||
'[' + 20 * 60 * 1000 + ',' + '"20-min"' + ']' + ',' +
|
||||
'[' + 15 * 60 * 1000 + ',' + '"15-min"' + ']' + ',' +
|
||||
'[' + 10 * 60 * 1000 + ',' + '"10-min"' + ']' + ',' +
|
||||
'[' + 05 * 60 * 1000 + ',' + '"05-min"' + ']' + ',' +
|
||||
'[' + 04 * 60 * 1000 + ',' + '"04-min"' + ']' + ',' +
|
||||
'[' + 03 * 60 * 1000 + ',' + '"03-min"' + ']' + ',' +
|
||||
'[' + 02 * 60 * 1000 + ',' + '"02-min"' + ']' + ',' +
|
||||
'[' + 01 * 60 * 1000 + ',' + '"01-min"' + ']' + ']';
|
||||
|
||||
global.dailyFilePeriods = JSON.parse(global.dailyFilePeriods);
|
||||
|
||||
const MODULE_NAME = "Root";
|
||||
const WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START = 10000 // This avoid a race condition that could happen if one process finished before all the other even started.
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
start: start
|
||||
}
|
||||
|
||||
const FULL_LOG = true;
|
||||
|
||||
let logDisplace = "Task Server" + " ";
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
/* Global control of logging. */
|
||||
|
||||
global.LOG_CONTROL = {
|
||||
"Status Report": {
|
||||
logInfo: false,
|
||||
logWarnings: false,
|
||||
logErrors: true,
|
||||
logContent: false,
|
||||
intensiveLogging: false
|
||||
},
|
||||
"Dataset": {
|
||||
logInfo: false,
|
||||
logWarnings: false,
|
||||
logErrors: true,
|
||||
logContent: false,
|
||||
intensiveLogging: false
|
||||
},
|
||||
"Process Execution Events": {
|
||||
logInfo: false,
|
||||
logWarnings: false,
|
||||
logErrors: true,
|
||||
logContent: false,
|
||||
intensiveLogging: false
|
||||
},
|
||||
"Process Output": {
|
||||
logInfo: false,
|
||||
logWarnings: false,
|
||||
logErrors: true,
|
||||
logContent: false,
|
||||
intensiveLogging: false
|
||||
}
|
||||
};
|
||||
|
||||
callBackFunction();
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] initialize -> err = " + err.stack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function start(processIndex) {
|
||||
|
||||
try {
|
||||
|
||||
/* Now we will run according to what we see at the config file. */
|
||||
|
||||
/* Small Function to fix numbers into strings in a cool way. */
|
||||
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad("0" + str, max) : str;
|
||||
}
|
||||
|
||||
/* Process Loops Declarations. */
|
||||
|
||||
const INDICATOR_BOT_MODULE = require('./IndicatorBot');
|
||||
const SENSOR_BOT = require('./SensorBot');
|
||||
const TRADING_BOT_MODULE = require('./TradingBot');
|
||||
|
||||
let botConfig;
|
||||
let processInstance = global.TASK_NODE.bot.processes[processIndex]
|
||||
|
||||
/* Here we will check if we need to load the configuration and code of the bot from a file or we will take that from the UI. */
|
||||
if (
|
||||
processInstance.referenceParent.config.framework !== undefined &&
|
||||
(processInstance.referenceParent.config.framework.name === 'Multi-Period-Market' ||
|
||||
processInstance.referenceParent.config.framework.name === 'Multi-Period-Daily' ||
|
||||
processInstance.referenceParent.config.framework.name === 'Low-Frequency-Trading-Process')
|
||||
) {
|
||||
botConfig = processInstance.referenceParent.parentNode.config
|
||||
botConfig.definedByUI = true
|
||||
bootingBot(processIndex)
|
||||
return
|
||||
} else {
|
||||
getBotConfigFromFile();
|
||||
return
|
||||
}
|
||||
|
||||
function getBotConfigFromFile() {
|
||||
|
||||
try {
|
||||
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage();
|
||||
|
||||
let filePath = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName + '/bots/' + processInstance.referenceParent.parentNode.config.repo + '/this.bot.config.json';
|
||||
|
||||
fileStorage.getTextFile(filePath, onFileReceived);
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> err = " + JSON.stringify(err));
|
||||
console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> filePath = " + filePath);
|
||||
console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> dataMine = " + global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
botConfig = JSON.parse(text);
|
||||
botConfig.repo = global.TASK_NODE.bot.config.repo;
|
||||
bootingBot(processIndex);
|
||||
} catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> onInizialized -> onFileReceived -> err = " + err.stack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> getBotConfigFromFile -> err = " + err.stack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function bootingBot(processIndex) {
|
||||
|
||||
try {
|
||||
|
||||
botConfig.process = global.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName
|
||||
botConfig.debug = {};
|
||||
botConfig.processNode = global.TASK_NODE.bot.processes[processIndex]
|
||||
|
||||
/* Logs Mantainance Stuff */
|
||||
botConfig.LOGS_TO_DELETE_QUEUE = []
|
||||
botConfig.DELETE_QUEUE_SIZE = 10 // This number represents how many log files can be at the queue at any point in time, which means how many logs are not still deleted.
|
||||
|
||||
/* Simplifying the access to basic info */
|
||||
botConfig.dataMine = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName
|
||||
botConfig.exchange = global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.parentNode.parentNode.name
|
||||
botConfig.exchangeNode = global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.parentNode.parentNode
|
||||
botConfig.market = {
|
||||
baseAsset: global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.baseAsset.referenceParent.config.codeName,
|
||||
quotedAsset: global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent.quotedAsset.referenceParent.config.codeName
|
||||
}
|
||||
botConfig.uiStartDate = global.TASK_NODE.bot.config.startDate
|
||||
botConfig.config = global.TASK_NODE.bot.config
|
||||
|
||||
/* Loop Counter */
|
||||
botConfig.loopCounter = 0;
|
||||
|
||||
/* File Path Root */
|
||||
botConfig.filePathRoot = botConfig.exchange + "/" + botConfig.market.baseAsset + "-" + botConfig.market.quotedAsset + "/" + botConfig.dataMine + "/" + botConfig.codeName;
|
||||
|
||||
/* Process Key */
|
||||
botConfig.processKey = global.TASK_NODE.bot.processes[processIndex].name + '-' + global.TASK_NODE.bot.processes[processIndex].type + '-' + global.TASK_NODE.bot.processes[processIndex].id
|
||||
|
||||
/* Bot Type */
|
||||
botConfig.type = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.type
|
||||
|
||||
/* Time Frame Filter */
|
||||
if (global.TASK_NODE.bot.timeFramesFilter !== undefined) {
|
||||
botConfig.dailyTimeFrames = global.TASK_NODE.bot.timeFramesFilter.config.dailyTimeFrames
|
||||
botConfig.marketTimeFrames = global.TASK_NODE.bot.timeFramesFilter.config.marketTimeFrames
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.keyReference !== undefined) {
|
||||
if (global.TASK_NODE.keyReference.referenceParent !== undefined) {
|
||||
let key = global.TASK_NODE.keyReference.referenceParent
|
||||
|
||||
botConfig.KEY = key.config.codeName
|
||||
botConfig.SECRET = key.config.secret
|
||||
}
|
||||
}
|
||||
|
||||
let processConfig = global.TASK_NODE.bot.processes[processIndex].referenceParent.config
|
||||
|
||||
if (processConfig.framework !== undefined) {
|
||||
if (processConfig.framework.name === "Multi-Period-Daily" || processConfig.framework.name === "Multi-Period-Market" || processConfig.framework.name === "Low-Frequency-Trading-Process") {
|
||||
if (processConfig.framework.startDate !== undefined) {
|
||||
processConfig.framework.startDate.resumeExecution = true;
|
||||
if (processConfig.startMode.noTime !== undefined) {
|
||||
if (processConfig.startMode.noTime.run === "true") {
|
||||
if (processConfig.startMode.noTime.beginDatetime !== undefined) {
|
||||
processConfig.framework.startDate.fixedDate = processConfig.startMode.noTime.beginDatetime;
|
||||
processConfig.framework.startDate.resumeExecution = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (processConfig.startMode !== undefined) {
|
||||
if (processConfig.startMode.userDefined !== undefined) {
|
||||
if (processConfig.startMode.userDefined.run === "true") {
|
||||
if (processConfig.startMode.userDefined.beginDatetime !== undefined) {
|
||||
processConfig.framework.startDate.fixedDate = processConfig.startMode.userDefined.beginDatetime;
|
||||
processConfig.framework.startDate.resumeExecution = processConfig.startMode.userDefined.resumeExecution;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
if (processConfig.startMode === undefined) { // Default
|
||||
|
||||
botConfig.hasTheBotJustStarted = true;
|
||||
|
||||
switch (botConfig.type) {
|
||||
case 'Sensor Bot': {
|
||||
runSensorBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Indicator Bot': {
|
||||
runIndicatorBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Trading Bot': {
|
||||
runTradingBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Learning Bot': {
|
||||
runLearningBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected bot type. -> botConfig.type = " + botConfig.type);
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (processConfig.startMode.noTime !== undefined) {
|
||||
|
||||
if (processConfig.startMode.noTime.run === "true") {
|
||||
|
||||
if (processConfig.startMode.noTime.resumeExecution === true) {
|
||||
botConfig.hasTheBotJustStarted = false;
|
||||
} else {
|
||||
botConfig.hasTheBotJustStarted = true;
|
||||
}
|
||||
|
||||
switch (botConfig.type) {
|
||||
case 'Sensor Bot': {
|
||||
runSensorBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Indicator Bot': {
|
||||
runIndicatorBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Trading Bot': {
|
||||
runTradingBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Learning Bot': {
|
||||
runLearningBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected bot type. -> botConfig.type = " + botConfig.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (processConfig.startMode.fixedInterval !== undefined) {
|
||||
|
||||
if (processConfig.startMode.fixedInterval.run === "true") {
|
||||
|
||||
botConfig.runAtFixedInterval = true;
|
||||
botConfig.fixedInterval = processConfig.startMode.fixedInterval.interval;
|
||||
|
||||
switch (botConfig.type) {
|
||||
case 'Sensor Bot': {
|
||||
runSensorBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected bot type. -> botConfig.type = " + botConfig.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (processConfig.startMode.userDefined !== undefined) {
|
||||
|
||||
if (processConfig.startMode.userDefined.run === "true") {
|
||||
|
||||
botConfig.startMode = "User Defined";
|
||||
|
||||
if (processConfig.startMode.userDefined.resumeExecution === true) {
|
||||
botConfig.hasTheBotJustStarted = false;
|
||||
} else {
|
||||
botConfig.hasTheBotJustStarted = true;
|
||||
}
|
||||
|
||||
switch (botConfig.type) {
|
||||
case 'Trading Bot': {
|
||||
runTradingBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Learning Bot': {
|
||||
runLearningBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected bot type. -> botConfig.type = " + botConfig.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected exception. -> err = " + err.stack);
|
||||
}
|
||||
|
||||
|
||||
function runSensorBot(pBotConfig, pProcessConfig) {
|
||||
|
||||
try {
|
||||
global.TOTAL_PROCESS_INSTANCES_CREATED++
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger;
|
||||
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set('runSensorBot', logger)
|
||||
logger.bot = pBotConfig;
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runSensorBot -> Entering function."); }
|
||||
|
||||
let sensorBot = SENSOR_BOT.newSensorBot(pBotConfig, logger);
|
||||
sensorBot.initialize(pProcessConfig, onInitializeReady);
|
||||
|
||||
function onInitializeReady(err) {
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
sensorBot.run(whenRunFinishes);
|
||||
|
||||
function whenRunFinishes(err) {
|
||||
|
||||
pBotConfig.loopCounter = 0;
|
||||
|
||||
let botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process;
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully.");
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
|
||||
logger.persist();
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. ");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bye.");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> whenStartFinishes -> Bot execution was aborted.");
|
||||
logger.persist();
|
||||
}
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> Bot will not be started. ");
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runSensorBot -> onInitializeReady -> err = " + err.message);
|
||||
|
||||
logger.persist();
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runSensorBot -> err = " + err.stack);
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
|
||||
function runIndicatorBot(pBotConfig, pProcessConfig) {
|
||||
|
||||
try {
|
||||
global.TOTAL_PROCESS_INSTANCES_CREATED++
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger;
|
||||
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set('runIndicatorBot', logger)
|
||||
logger.bot = pBotConfig;
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runIndicatorBot -> Entering function."); }
|
||||
|
||||
let indicatorBot = INDICATOR_BOT_MODULE.newIndicatorBot(pBotConfig, logger);
|
||||
indicatorBot.initialize(pProcessConfig, onInitializeReady);
|
||||
|
||||
function onInitializeReady(err) {
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
indicatorBot.run(whenRunFinishes);
|
||||
|
||||
function whenRunFinishes(err) {
|
||||
|
||||
pBotConfig.loopCounter = 0;
|
||||
|
||||
let botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process;
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully.");
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
|
||||
logger.persist();
|
||||
|
||||
} else {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. ");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Bye.");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished with errors. Please check the logs.");
|
||||
logger.persist();
|
||||
}
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> Failed to initialize the bot. ");
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runIndicatorBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.persist();
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runIndicatorBot -> err = " + err.stack);
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
|
||||
function runTradingBot(pBotConfig, pProcessConfig) {
|
||||
|
||||
global.TOTAL_PROCESS_INSTANCES_CREATED++
|
||||
|
||||
try {
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger;
|
||||
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set('runTradingBot', logger)
|
||||
logger.bot = pBotConfig;
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runTradingBot -> Entering function."); }
|
||||
|
||||
let tradingBot = TRADING_BOT_MODULE.newTradingBot(pBotConfig, logger);
|
||||
tradingBot.initialize(pProcessConfig, onInitializeReady);
|
||||
|
||||
function onInitializeReady(err) {
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
tradingBot.run(whenRunFinishes);
|
||||
|
||||
function whenRunFinishes(err) {
|
||||
|
||||
pBotConfig.loopCounter = 0;
|
||||
|
||||
let botId;
|
||||
|
||||
botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process;
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully.");
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
|
||||
logger.persist();
|
||||
|
||||
} else {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> whenStartFinishes -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. ");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bye.");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished with errors. Please check the logs.");
|
||||
logger.persist();
|
||||
}
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> Failed to initialize the bot. ");
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runTradingBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.persist();
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runTradingBot -> err = " + err.stack);
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
|
||||
function runLearningBot(pBotConfig, pProcessConfig) {
|
||||
|
||||
global.TOTAL_PROCESS_INSTANCES_CREATED++
|
||||
|
||||
try {
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger;
|
||||
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set('runLearningBot', logger)
|
||||
logger.bot = pBotConfig;
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runLearningBot -> Entering function."); }
|
||||
|
||||
let learningBot = TRADING_BOT_MODULE.newLearningBot(pBotConfig, logger);
|
||||
learningBot.initialize(pProcessConfig, onInitializeReady);
|
||||
|
||||
function onInitializeReady(err) {
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
learningBot.run(whenRunFinishes);
|
||||
|
||||
function whenRunFinishes(err) {
|
||||
|
||||
pBotConfig.loopCounter = 0;
|
||||
|
||||
let botId;
|
||||
|
||||
botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process;
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully.");
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
|
||||
logger.persist();
|
||||
|
||||
} else {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. ");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bye.");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished with errors. Please check the logs.");
|
||||
logger.persist();
|
||||
}
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> Failed to initialize the bot. ");
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.persist();
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runLearningBot -> err = " + err.stack);
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> err = " + err.stack);
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
|
||||
function exitProcessInstance() {
|
||||
|
||||
global.ENDED_PROCESSES_COUNTER++
|
||||
//console.log("[INFO] Task Server -> " + global.TASK_NODE.name + " -> exitProcessInstance -> Process #" + global.ENDED_PROCESSES_COUNTER + " from " + global.TOTAL_PROCESS_INSTANCES_CREATED + " exiting.");
|
||||
|
||||
if (global.ENDED_PROCESSES_COUNTER === global.TOTAL_PROCESS_INSTANCES_CREATED) {
|
||||
global.EXIT_NODE_PROCESS()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("[ERROR] Task Server -> " + global.TASK_NODE.name + " -> Root -> Start -> Err = " + err.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
exports.newSensorBot = function newSensorBot(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Sensor Bot";
|
||||
const FULL_LOG = true;
|
||||
|
||||
let USER_BOT_MODULE;
|
||||
let COMMONS_MODULE;
|
||||
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(parentLogger);
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble.
|
||||
|
||||
let nextLoopTimeoutHandle;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
run: run
|
||||
};
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
/* This function is exactly the same in the 3 modules representing the 2 different bot types loops. */
|
||||
try {
|
||||
processConfig = pProcessConfig;
|
||||
|
||||
if (bot.definedByUI === true) {
|
||||
/* The code of the bot is defined at the UI. No need to load a file with the code. */
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
let filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo + "/" + pProcessConfig.codeName
|
||||
filePath += "/User.Bot.js"
|
||||
|
||||
fileStorage.getTextFile(filePath, onBotDownloaded);
|
||||
|
||||
function onBotDownloaded(err, text) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message);
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
}
|
||||
USER_BOT_MODULE = {}
|
||||
|
||||
try {
|
||||
USER_BOT_MODULE.newUserBot = eval(text); // TODO This needs to be changed function
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onBotDownloaded -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
|
||||
filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo;
|
||||
filePath += "/Commons.js"
|
||||
|
||||
fileStorage.getTextFile(filePath, onCommonsDownloaded);
|
||||
|
||||
function onCommonsDownloaded(err, text) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.message);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
COMMONS_MODULE = {}
|
||||
|
||||
COMMONS_MODULE.newCommons = eval(text); // TODO This needs to be changed function
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function run(callBackFunction) {
|
||||
try {
|
||||
let fixedTimeLoopIntervalHandle;
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.processHeartBeat = processHeartBeat
|
||||
|
||||
if (bot.runAtFixedInterval === true) {
|
||||
fixedTimeLoopIntervalHandle = setInterval(loop, bot.fixedInterval);
|
||||
loop(); // First run.
|
||||
} else {
|
||||
loop();
|
||||
}
|
||||
|
||||
function loop() {
|
||||
try {
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
/* For each loop we want to create a new log file. */
|
||||
if (logger !== undefined) {
|
||||
logger.finalize()
|
||||
}
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set(MODULE_NAME, logger)
|
||||
logger.bot = bot;
|
||||
logger.initialize();
|
||||
|
||||
bot.loopCounter++;
|
||||
bot.loopStartTime = new Date().valueOf();
|
||||
|
||||
/* We tell the UI that we are running. */
|
||||
processHeartBeat()
|
||||
|
||||
/* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */
|
||||
const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities');
|
||||
const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport');
|
||||
const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies');
|
||||
const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents');
|
||||
const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput');
|
||||
|
||||
/* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */
|
||||
bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution.
|
||||
|
||||
/* High level log entry */
|
||||
console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30)
|
||||
+ " Main Loop # " + pad(Number(bot.loopCounter), 8))
|
||||
|
||||
/* We will prepare first the infraestructure needed for the bot to run. There are 4 modules we need to sucessfullly initialize first. */
|
||||
let processExecutionEvents
|
||||
let userBot;
|
||||
let statusDependencies;
|
||||
let exchangeAPI;
|
||||
|
||||
let nextWaitTime;
|
||||
|
||||
initializeProcessExecutionEvents();
|
||||
|
||||
function initializeProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
|
||||
processExecutionEvents.initialize(processConfig, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well.");
|
||||
startProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.start(onStarted);
|
||||
|
||||
function onStarted(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well.");
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
loopControl()
|
||||
return
|
||||
}
|
||||
|
||||
initializeStatusDependencies();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> 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(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeStatusDependencies() {
|
||||
try {
|
||||
statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT);
|
||||
statusDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well.");
|
||||
initializeUserBot();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeUserBot() {
|
||||
try {
|
||||
usertBot = USER_BOT_MODULE.newUserBot(bot, logger, COMMONS_MODULE, UTILITIES, FILE_STORAGE, STATUS_REPORT, exchangeAPI);
|
||||
usertBot.initialize(statusDependencies, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeUserBot -> onInizialized > Execution finished well.");
|
||||
startUserBot();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeUserBot -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized > Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeUserBot -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startUserBot() {
|
||||
try {
|
||||
usertBot.start(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startUserBot -> onFinished > Execution finished well.");
|
||||
finishProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished > 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(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
case "Dependency does not exist.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished > Dependency does not exist. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case "Dependency not ready.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startUserBot -> onFinished > Dependency not ready. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished > Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startUserBot -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function finishProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.finish(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processExecutionEvents.finalize()
|
||||
processExecutionEvents = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
loopControl(nextWaitTime);
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function loopControl(nextWaitTime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); }
|
||||
|
||||
/* We show we reached the end of the loop. */
|
||||
/* Here we check if we must stop the loop gracefully. */
|
||||
shallWeStop(onStop, onContinue);
|
||||
|
||||
function onStop() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); }
|
||||
processStopped()
|
||||
return;
|
||||
}
|
||||
|
||||
function onContinue() {
|
||||
/* Sensor bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */
|
||||
switch (nextWaitTime) {
|
||||
case 'Normal': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.normalWaitTime / 1000) + " seconds."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.normalWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.normalWaitTime / 1000 + " seconds for next execution.")
|
||||
if (global.WRITE_LOGS_TO_FILES === 'true') {
|
||||
logger.persist();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Retry': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Sleep': {
|
||||
if (bot.runAtFixedInterval === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Fixed Interval Sleep exit point reached."); }
|
||||
logger.persist();
|
||||
return;
|
||||
} else {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Coma': {
|
||||
if (bot.runAtFixedInterval === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Fixed Interval Coma exit point reached."); }
|
||||
logger.persist();
|
||||
return;
|
||||
} else {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shallWeStop(stopCallBack, continueCallBack) {
|
||||
try {
|
||||
/* IMPORTANT: This function is exactly the same on the 3 modules. */
|
||||
if (!global.STOP_TASK_GRACEFULLY) {
|
||||
continueCallBack();
|
||||
} else {
|
||||
stopCallBack();
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function processHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
|
||||
}
|
||||
|
||||
function processStopped() {
|
||||
if (global.unexpectedError !== undefined) {
|
||||
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
|
||||
} else {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
|
||||
}
|
||||
logger.persist();
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
if (global.unexpectedError !== undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
} else {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack);
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
exports.newSocialBots = function newSocialBots(bot, logger) {
|
||||
|
||||
const MODULE_NAME = 'Social Bots'
|
||||
|
||||
let thisObject = {
|
||||
sendMessage: sendMessage,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
const TELEGRAM_BOT_MODULE = require('./TelegramBot.js')
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
if (bot.TRADING_SESSION.socialBots !== undefined) {
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
let config = socialBot.config
|
||||
socialBot.botInstance = TELEGRAM_BOT_MODULE.newTelegramBot(bot, logger)
|
||||
socialBot.botInstance.initialize(config.botToken, config.chatId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function announce(text) {
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
try {
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, text).catch(err => logger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err))
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> announce -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bot.TRADING_SESSION.socialBots.announce = announce
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
if (bot.TRADING_SESSION.socialBots === undefined) { return }
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
socialBot.botInstance.finalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessage(message) {
|
||||
if (bot.TRADING_SESSION.socialBots !== undefined) {
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
socialBot.botInstance.sendMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
exports.newStatusDependencies = function newStatusDependencies(BOT, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT) {
|
||||
|
||||
const FULL_LOG = true;
|
||||
const LOG_FILE_CONTENT = false;
|
||||
const MODULE_NAME = "Status Dependencies";
|
||||
|
||||
let bot = BOT
|
||||
|
||||
let thisObject = {
|
||||
nodeArray: undefined,
|
||||
statusReports: new Map(),
|
||||
reportsByMainUtility: new Map(),
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
keys: []
|
||||
};
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(callBackFunction) {
|
||||
try {
|
||||
/* Basic Valdidations */
|
||||
if (bot.processNode.referenceParent.processDependencies !== undefined) {
|
||||
if (bot.processNode.referenceParent.processDependencies.statusDependencies !== undefined) {
|
||||
thisObject.nodeArray = bot.processNode.referenceParent.processDependencies.statusDependencies
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> It is not possible to not have status dependencies at all.");
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> It is not possible to not have process dependencies, which means not status dependencies.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
/*For each dependency we will initialize the status report, and load it as part of this initialization process.*/
|
||||
|
||||
let alreadyCalledBack = false;
|
||||
let loadCount = 0;
|
||||
let dependenciesToProcess = []
|
||||
for (let i = 0; i < thisObject.nodeArray.length; i++) {
|
||||
let statusDependency = {
|
||||
dependency: thisObject.nodeArray[i]
|
||||
}
|
||||
|
||||
dependenciesToProcess.push(statusDependency)
|
||||
|
||||
}
|
||||
|
||||
for (let i = 0; i < dependenciesToProcess.length; i++) {
|
||||
|
||||
let statusReportModule = STATUS_REPORT.newStatusReport(BOT, logger, UTILITIES, PROCESS_OUTPUT);
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] initialize -> onInitilized -> Initializing Status Report # " + (i + 1));
|
||||
let statusDependency = dependenciesToProcess[i]
|
||||
statusReportModule.initialize(statusDependency.dependency, onInitilized);
|
||||
|
||||
function onInitilized(err) {
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] initialize -> onInitilized -> Initialized Status Report # " + (i + 1));
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
if (alreadyCalledBack === false) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err.message = " + err.message);
|
||||
alreadyCalledBack = true;
|
||||
callBackFunction(err);
|
||||
} else {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] initialize -> Can not call back because I already did."); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] initialize -> onInitilized -> Loading Status Report # " + (i + 1));
|
||||
statusReportModule.load(onLoad);
|
||||
}
|
||||
|
||||
function onLoad(err) {
|
||||
|
||||
try {
|
||||
logger.write(MODULE_NAME, "[INFO] initialize -> onLoad -> Loaded Status Report # " + (i + 1));
|
||||
statusReportModule.status = err.message;
|
||||
|
||||
switch (err.message) {
|
||||
case global.DEFAULT_OK_RESPONSE.message: {
|
||||
addReport();
|
||||
return;
|
||||
}
|
||||
case "Status Report was never created.": {
|
||||
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> onLoad -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> onLoad -> Report Not Found. -> Status Dependency = " + JSON.stringify(statusDependency.dependency))
|
||||
|
||||
addReport();
|
||||
return;
|
||||
}
|
||||
|
||||
case "Status Report is corrupt.": {
|
||||
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> onLoad -> err = " + err.stack);
|
||||
logger.write(MODULE_NAME, "[WARN] initialize -> onLoad -> Report Not Found. -> Status Dependency = " + JSON.stringify(statusDependency.dependency))
|
||||
|
||||
addReport();
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onLoad -> Operation Failed.");
|
||||
|
||||
if (alreadyCalledBack === false) {
|
||||
alreadyCalledBack = true;
|
||||
callBackFunction(err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onLoad -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function addReport() {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> addReport -> Entering function."); }
|
||||
logger.write(MODULE_NAME, "[INFO] initialize -> addReport -> Adding Status Report # " + (i + 1));
|
||||
|
||||
loadCount++;
|
||||
logger.write(MODULE_NAME, "[INFO] initialize -> addReport -> Total Added = " + loadCount);
|
||||
|
||||
let key = statusDependency.dependency.dataMine + "-" + statusDependency.dependency.bot + "-" + statusDependency.dependency.process
|
||||
|
||||
thisObject.keys.push(key);
|
||||
thisObject.statusReports.set(key, statusReportModule);
|
||||
|
||||
if (statusReportModule.mainUtility !== undefined) {
|
||||
thisObject.reportsByMainUtility.set(statusReportModule.mainUtility, statusReportModule)
|
||||
}
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> addReport -> Report added to Map. -> key = " + key); }
|
||||
|
||||
if (loadCount === dependenciesToProcess.length) {
|
||||
if (alreadyCalledBack === false) {
|
||||
alreadyCalledBack = true
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
} else {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[WARN] initialize -> addReport -> Can not call back because I already did."); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
thisObject.statusReports.forEach(forEachStatusDependency)
|
||||
function forEachStatusDependency(statusDependency) {
|
||||
statusDependency.finalize()
|
||||
}
|
||||
thisObject.statusReports = undefined
|
||||
bot = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
};
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
exports.newStatusReport = function newStatusReport(BOT, logger, UTILITIES, PROCESS_OUTPUT) {
|
||||
|
||||
/*
|
||||
|
||||
A Status Report is a file that every bot reads at the begining of its execution and saves after it finishes its job.
|
||||
The purpose of the file is to record checkpoint information of what was the last thing done by the bot and helpfull enough to start the next execution.
|
||||
It usually does not include business related context data.
|
||||
|
||||
*/
|
||||
|
||||
const MODULE_NAME = "Status Report";
|
||||
|
||||
let bot = BOT;
|
||||
|
||||
let thisObject = {
|
||||
networkNode: undefined,
|
||||
mainUtility: undefined,
|
||||
file: undefined, // Here we have the JSON object representing the file content.
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
load: load,
|
||||
save: save,
|
||||
asyncSave: asyncSave,
|
||||
status: undefined
|
||||
};
|
||||
|
||||
let statusDependencyNode;
|
||||
|
||||
/* Utilities needed. */
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger);
|
||||
|
||||
/* Storage account to be used here. */
|
||||
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
let fileStorage
|
||||
|
||||
let sessionPath = ''
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pStatusDependencyNode, callBackFunction) {
|
||||
try {
|
||||
statusDependencyNode = pStatusDependencyNode;
|
||||
logger.fileName = MODULE_NAME + "." + statusDependencyNode.type + "." + statusDependencyNode.name + "." + statusDependencyNode.id;
|
||||
|
||||
/* Some very basic validations that we have all the information needed. */
|
||||
if (statusDependencyNode.referenceParent === undefined) {
|
||||
validationFailed(statusDependencyNode, "Status Dependency without Reference Parent.")
|
||||
return
|
||||
}
|
||||
|
||||
if (statusDependencyNode.referenceParent.parentNode === undefined) {
|
||||
validationFailed(statusDependencyNode.referenceParent, "Status Report not attached to a Process Definition.")
|
||||
return
|
||||
}
|
||||
|
||||
if (statusDependencyNode.referenceParent.parentNode.config.codeName === undefined) {
|
||||
validationFailed(statusDependencyNode.referenceParent.parentNode, "Process Definition witn no codeName defined.")
|
||||
return
|
||||
}
|
||||
|
||||
if (statusDependencyNode.referenceParent.parentNode.parentNode === undefined) {
|
||||
validationFailed(statusDependencyNode.referenceParent.parentNode, "Process Definition not attached to a Bot.")
|
||||
return
|
||||
}
|
||||
|
||||
if (statusDependencyNode.referenceParent.parentNode.parentNode.config.codeName === undefined) {
|
||||
validationFailed(statusDependencyNode.referenceParent.parentNode.parentNode, "Bot with no codeName defined.")
|
||||
return
|
||||
}
|
||||
|
||||
if (statusDependencyNode.referenceParent.parentNode.parentNode.parentNode === undefined) {
|
||||
validationFailed(statusDependencyNode.referenceParent.parentNode.parentNode, "Bot not attached to a Data Mine.")
|
||||
return
|
||||
}
|
||||
|
||||
if (statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) {
|
||||
validationFailed(statusDependencyNode.referenceParent.parentNode.parentNode.parentNode, "Data Mine witn no codeName defined.")
|
||||
return
|
||||
}
|
||||
|
||||
function validationFailed(errorInNode, errorMessage) {
|
||||
let nodeString = JSON.stringify(errorInNode)
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> " + errorMessage + ' -> nodeString = ' + nodeString)
|
||||
global.PROCESS_ERROR(bot.processKey, errorInNode, errorMessage)
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
|
||||
/* Simplifying the access to basic info */
|
||||
statusDependencyNode.bot = statusDependencyNode.referenceParent.parentNode.parentNode.config.codeName
|
||||
statusDependencyNode.process = statusDependencyNode.referenceParent.parentNode.config.codeName
|
||||
statusDependencyNode.bottype = statusDependencyNode.referenceParent.parentNode.parentNode.type
|
||||
statusDependencyNode.dataMine = statusDependencyNode.referenceParent.parentNode.parentNode.parentNode.config.codeName
|
||||
|
||||
/* We retrieve the report main utility */
|
||||
if (statusDependencyNode.config !== undefined) {
|
||||
if (statusDependencyNode.config.mainUtility !== undefined) {
|
||||
thisObject.mainUtility = statusDependencyNode.config.mainUtility
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.TRADING_SESSION !== undefined && statusDependencyNode.bottype === "Trading Bot") {
|
||||
sessionPath = bot.TRADING_SESSION.folderName + "/"
|
||||
}
|
||||
|
||||
if (bot.LEARNING_SESSION !== undefined && statusDependencyNode.bottype === "Learning Bot") {
|
||||
sessionPath = bot.LEARNING_SESSION.folderName + "/"
|
||||
}
|
||||
|
||||
/* Now we will see where do we need to fetch this status report from. */
|
||||
let network = global.TASK_NETWORK
|
||||
let processThisDependsOn = statusDependencyNode.referenceParent.parentNode
|
||||
|
||||
for (let i = 0; i < network.networkNodes.length; i++) {
|
||||
let networkNode = network.networkNodes[i]
|
||||
|
||||
if (checkThisDataBranch(networkNode.dataMining) === true) { return }
|
||||
if (checkThisTradingBranch(networkNode.testingEnvironment) === true) { return }
|
||||
if (checkThisTradingBranch(networkNode.productionEnvironment) === true) { return }
|
||||
|
||||
function checkThisDataBranch(branch) {
|
||||
if (branch === undefined) { return }
|
||||
for (let j = 0; j < branch.exchangeDataTasks.length; j++) {
|
||||
let exchangeTasks = branch.exchangeDataTasks[j]
|
||||
for (let p = 0; p < exchangeTasks.marketDataTasks.length; p++) {
|
||||
let marketTasks = exchangeTasks.marketDataTasks[p]
|
||||
if (marketTasks.referenceParent === undefined) { continue }
|
||||
if (marketTasks.referenceParent.id !== global.MARKET_NODE.id) { continue }
|
||||
for (let q = 0; q < marketTasks.dataMineTasks.length; q++) {
|
||||
let mineTasks = marketTasks.dataMineTasks[q]
|
||||
for (let k = 0; k < mineTasks.taskManagers.length; k++) {
|
||||
let taskManager = mineTasks.taskManagers[k]
|
||||
for (let m = 0; m < taskManager.tasks.length; m++) {
|
||||
let task = taskManager.tasks[m]
|
||||
if (task.bot === undefined) { continue }
|
||||
for (let n = 0; n < task.bot.processes.length; n++) {
|
||||
let process = task.bot.processes[n]
|
||||
if (process.referenceParent === undefined) { continue }
|
||||
let processDefinition = process.referenceParent
|
||||
if (processThisDependsOn.id === processDefinition.id) {
|
||||
if (process.type === 'Trading Process Instance') {
|
||||
if (process.session !== undefined) {
|
||||
if (bot.processNode.session.id !== process.session.id) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
/*
|
||||
We found where the task that runs the process definition this status report depends on
|
||||
and where it is located on the network.
|
||||
*/
|
||||
thisObject.networkNode = networkNode
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); }
|
||||
|
||||
fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkThisTradingBranch(branch) {
|
||||
if (branch === undefined) { return }
|
||||
for (let j = 0; j < branch.exchangeTradingTasks.length; j++) {
|
||||
let exchangeTasks = branch.exchangeTradingTasks[j]
|
||||
for (let p = 0; p < exchangeTasks.marketTradingTasks.length; p++) {
|
||||
let marketTasks = exchangeTasks.marketTradingTasks[p]
|
||||
if (marketTasks.referenceParent === undefined) { continue }
|
||||
if (marketTasks.referenceParent.id !== global.MARKET_NODE.id) { continue }
|
||||
for (let q = 0; q < marketTasks.tradingMineTasks.length; q++) {
|
||||
let mineTasks = marketTasks.tradingMineTasks[q]
|
||||
for (let k = 0; k < mineTasks.taskManagers.length; k++) {
|
||||
let taskManager = mineTasks.taskManagers[k]
|
||||
for (let m = 0; m < taskManager.tasks.length; m++) {
|
||||
let task = taskManager.tasks[m]
|
||||
if (task.bot === undefined) { continue }
|
||||
for (let n = 0; n < task.bot.processes.length; n++) {
|
||||
let process = task.bot.processes[n]
|
||||
if (process.referenceParent === undefined) { continue }
|
||||
let processDefinition = process.referenceParent
|
||||
if (processThisDependsOn.id === processDefinition.id) {
|
||||
if (process.type === 'Trading Process Instance') {
|
||||
if (process.session !== undefined) {
|
||||
if (bot.processNode.session.id !== process.session.id) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
/*
|
||||
We found where the task that runs the process definition this status report depends on
|
||||
and where it is located on the network.
|
||||
*/
|
||||
thisObject.networkNode = networkNode
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Retrieving status report from " + networkNode.name + " -> host = " + networkNode.config.host + ' -> port = ' + networkNode.config.webPort + '.'); }
|
||||
|
||||
fileStorage = FILE_STORAGE.newFileStorage(logger, networkNode.config.host, networkNode.config.webPort);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Initialization Failed because we could not find where the data of this status report is located within the network. Check the logs for more info.");
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> bot = " + statusDependencyNode.bot);
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> process = " + statusDependencyNode.process);
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> bottype = " + statusDependencyNode.bottype);
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> dataMine = " + statusDependencyNode.dataMine);
|
||||
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
fileStorage = undefined
|
||||
thisObject.networkNode = undefined
|
||||
bot = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
function load(callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] load -> Entering function."); }
|
||||
|
||||
let fileName = "Status.Report.json";
|
||||
let filePath;
|
||||
|
||||
let ownerId = statusDependencyNode.dataMine + "-" + statusDependencyNode.bot + "-" + statusDependencyNode.process
|
||||
let botId = bot.dataMine + "-" + bot.codeName + "-" + bot.process
|
||||
|
||||
if (ownerId !== botId) {
|
||||
|
||||
let rootPath = bot.exchange + "/" + bot.market.baseAsset + "-" + bot.market.quotedAsset + "/" + statusDependencyNode.dataMine + "/" + statusDependencyNode.bot
|
||||
|
||||
filePath = rootPath + "/Reports/" + sessionPath + statusDependencyNode.process;
|
||||
} else {
|
||||
filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process;
|
||||
}
|
||||
|
||||
filePath += '/' + fileName
|
||||
|
||||
let canUserPrevious
|
||||
/*
|
||||
If we are funning Trading Engines, we can not allow ourselves to use a Status Report that is not the latest one, because it might contain
|
||||
transactioinal information related to the context of the operations the trading engine is doing.
|
||||
|
||||
On the contraty, if we are running a Sensor bot or an Indicator bot, we might, if necesary, use a previous version of a Status Report since
|
||||
there will be no big impact, just some reprocessing.
|
||||
*/
|
||||
if (bot.TRADING_SESSION !== undefined || bot.LEARNING_SESSION !== undefined) {
|
||||
canUserPrevious = false
|
||||
} else {
|
||||
canUserPrevious = true
|
||||
}
|
||||
|
||||
fileStorage.getTextFile(filePath, onFileReceived, undefined, canUserPrevious);
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
|
||||
if (err.result === global.CUSTOM_FAIL_RESPONSE.result && (err.message === 'Folder does not exist.' || err.message === 'File does not exist.')
|
||||
|| err.code === "The specified key does not exist.") {
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] load -> onFileReceived -> err = " + err.message);
|
||||
|
||||
/* In this case we can assume that this is the first execution ever of this bot.*/
|
||||
|
||||
thisObject.file = JSON.parse('{}');
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: "Status Report was never created."
|
||||
};
|
||||
logger.write(MODULE_NAME, "[WARN] load -> onFileReceived -> customOK = " + customOK.message);
|
||||
callBackFunction(customOK);
|
||||
return;
|
||||
}
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] load -> onFileReceived -> err = " + err.message);
|
||||
callBackFunction(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logContent === true) {
|
||||
logger.write(MODULE_NAME, "[INFO] load -> onFileReceived -> Content received = " + text);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
thisObject.file = JSON.parse(text);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
|
||||
} catch (err) {
|
||||
|
||||
/*
|
||||
It might happen that the file content is corrupt. We will consider this as a temporary situation, since sometimes the file
|
||||
is being updated at the moment of the read. The bot can not run without a valid Status Report but we can request the platform to retry later.
|
||||
*/
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] load -> onFileReceived -> Error Parsing the Status report. -> Err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[WARN] load -> onFileReceived -> Error Parsing the Status report. -> text = " + text);
|
||||
|
||||
let customFail = {
|
||||
result: global.CUSTOM_FAIL_RESPONSE.result,
|
||||
message: "Status Report is corrupt."
|
||||
};
|
||||
logger.write(MODULE_NAME, "[WARN] load -> onFileReceived -> customFail = " + customFail.message);
|
||||
callBackFunction(customFail);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] load -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function save(callBackFunction) {
|
||||
|
||||
try {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] save -> Entering function."); }
|
||||
|
||||
let ownerId = statusDependencyNode.dataMine + "-" + statusDependencyNode.bot + "-" + statusDependencyNode.process
|
||||
let botId = bot.dataMine + "-" + bot.codeName + "-" + bot.process
|
||||
|
||||
if (ownerId !== botId && statusDependencyNode.process !== "Context") { // Context is a special case where the report is created by the Context.js module itself.
|
||||
|
||||
let customErr = {
|
||||
result: global.CUSTOM_FAIL_RESPONSE.result,
|
||||
message: "Only bots owners of a Status Report can save them."
|
||||
};
|
||||
logger.write(MODULE_NAME, "[ERROR] save -> customErr = " + customErr.message);
|
||||
callBackFunction(customErr);
|
||||
return;
|
||||
}
|
||||
|
||||
let fileName = "Status.Report.json";
|
||||
let filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process;
|
||||
|
||||
filePath += '/' + fileName
|
||||
let fileContent = JSON.stringify(thisObject.file);
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated, true);
|
||||
|
||||
function onFileCreated(err) {
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] save -> onFileCreated -> Entering function."); }
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] save -> onFileCreated -> err = " + err.stack);
|
||||
callBackFunction(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (global.LOG_CONTROL[MODULE_NAME].logContent === true) {
|
||||
logger.write(MODULE_NAME, "[INFO] save -> onFileCreated -> Content written = " + fileContent);
|
||||
}
|
||||
|
||||
/* All good, lets emit the event that means data has been updated. */
|
||||
|
||||
let processOutput = PROCESS_OUTPUT.newProcessOutput(bot, logger)
|
||||
|
||||
processOutput.raiseEvents(thisObject.file.lastFile, thisObject.file.timeFrames, callBackFunction);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] save -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
async function asyncSave() {
|
||||
|
||||
let fileName = "Status.Report.json";
|
||||
let filePath = bot.filePathRoot + "/Reports/" + sessionPath + statusDependencyNode.process;
|
||||
|
||||
filePath += '/' + fileName
|
||||
let fileContent = JSON.stringify(thisObject.file);
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n', true)
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
/* All good, lets emit the event that means data has been updated. */
|
||||
let processOutput = PROCESS_OUTPUT.newProcessOutput(bot, logger)
|
||||
processOutput.asyncRaiseEvents(thisObject.file.lastFile, thisObject.file.timeFrames)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
exports.newTelegramBot = function newTelegramBot(bot, logger) {
|
||||
|
||||
const MODULE_NAME = 'Telegram Bot'
|
||||
|
||||
let thisObject = {
|
||||
telegramBot: undefined,
|
||||
telegramAPI: undefined,
|
||||
chatId: undefined,
|
||||
sendMessage: sendMessage,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize(botToken, chatId) {
|
||||
/* Telegram Bot Initialization */
|
||||
try {
|
||||
thisObject.chatId = chatId
|
||||
|
||||
const Telegraf = require('telegraf')
|
||||
|
||||
thisObject.telegramBot = new Telegraf(botToken)
|
||||
thisObject.telegramBot.start((ctx) => ctx.reply('Hi! I am the Telegram Bot that will tell you what happens with your trading session.'))
|
||||
thisObject.telegramBot.help((ctx) => ctx.reply('I can stop the session if you type STOP.'))
|
||||
thisObject.telegramBot.hears('STOP', (ctx) => bot.TRADING_SESSION.stop(' by STOP command at Telegram.'))
|
||||
thisObject.telegramBot.launch()
|
||||
|
||||
const Telegram = require('telegraf/telegram')
|
||||
|
||||
thisObject.telegramAPI = new Telegram(botToken)
|
||||
|
||||
const message = "Telegram bot is starting."
|
||||
thisObject.telegramAPI.sendMessage(thisObject.chatId, message).catch(err => parentLogger.write(MODULE_NAME, "[WARN] initializeTelegramBot -> Telegram API error -> err = " + err))
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] initialize -> err = " + err.stack);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
const message = "Telegram bot is signing off."
|
||||
thisObject.telegramAPI.sendMessage(thisObject.chatId, message).catch(err => parentLogger.write(MODULE_NAME, "[WARN] finalize -> Telegram API error -> err = " + err))
|
||||
}
|
||||
|
||||
function sendMessage(message) {
|
||||
thisObject.telegramAPI.sendMessage(thisObject.chatId, message).catch(err => parentLogger.write(MODULE_NAME, "[WARN] announce -> Telegram API error -> err = " + err))
|
||||
}
|
||||
|
||||
}
|
||||
+685
@@ -0,0 +1,685 @@
|
||||
exports.newTradingBot = function newTradingBot(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Trading Bot";
|
||||
const FULL_LOG = true;
|
||||
|
||||
const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/TradingProcess.js');
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
const SESSION = require(global.ROOT_DIR + 'TradingSession');
|
||||
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(parentLogger);
|
||||
let session = SESSION.newTradingSession(bot, parentLogger)
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble.
|
||||
|
||||
let nextLoopTimeoutHandle;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
run: run
|
||||
};
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
/* This function is exactly the same in the 3 modules representing the 2 different bot types loops. */
|
||||
try {
|
||||
processConfig = pProcessConfig;
|
||||
if (bot.definedByUI === true) {
|
||||
/* The code of the bot is defined at the UI. No need to load a file with the code. */
|
||||
session.initialize(processConfig, callBackFunction)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function run(callBackFunction) {
|
||||
try {
|
||||
/* Some initial values*/
|
||||
let fixedTimeLoopIntervalHandle;
|
||||
bot.STOP_SESSION = true;
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] run -> Stopping the Session now. ') }
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.processHeartBeat = processHeartBeat
|
||||
|
||||
loop();
|
||||
|
||||
function loop() {
|
||||
try {
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
/* For each loop we want to create a new log file. */
|
||||
if (logger !== undefined) {
|
||||
logger.finalize()
|
||||
}
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set(MODULE_NAME, logger)
|
||||
logger.bot = bot;
|
||||
logger.initialize();
|
||||
|
||||
bot.loopCounter++;
|
||||
bot.loopStartTime = new Date().valueOf();
|
||||
|
||||
let nextWaitTime;
|
||||
|
||||
/* We tell the UI that we are running. */
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
|
||||
/* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */
|
||||
const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities');
|
||||
const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport');
|
||||
const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies');
|
||||
const DATA_DEPENDENCIES = require(global.ROOT_DIR + 'DataDependencies');
|
||||
const DATA_SET = require(global.ROOT_DIR + 'DataSet');
|
||||
const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents');
|
||||
const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput');
|
||||
|
||||
/* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */
|
||||
bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution.
|
||||
|
||||
/* High level log entry */
|
||||
console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30)
|
||||
+ " Main Loop # " + pad(Number(bot.loopCounter), 8) + " " + bot.processNode.session.type + " " + bot.processNode.session.name)
|
||||
|
||||
/* Checking if we need to need to emit any event */
|
||||
if (bot.TRADING_SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) {
|
||||
bot.TRADING_SESSION_STATUS = 'Running'
|
||||
}
|
||||
|
||||
if (bot.TRADING_SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) {
|
||||
bot.TRADING_SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
|
||||
global.EMIT_SESSION_STATUS (bot.TRADING_SESSION_STATUS, bot.TRADING_SESSIONKey)
|
||||
|
||||
/* Checking if we should process this loop or not.*/
|
||||
if (bot.STOP_SESSION === true) {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run."); }
|
||||
|
||||
console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.process, 30)
|
||||
+ " Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ");
|
||||
|
||||
nextWaitTime = 'Waiting for Session';
|
||||
loopControl(nextWaitTime);
|
||||
return
|
||||
}
|
||||
|
||||
/* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */
|
||||
|
||||
let processExecutionEvents
|
||||
let userBot;
|
||||
let processFramework;
|
||||
let statusDependencies;
|
||||
let dataDependencies;
|
||||
|
||||
initializeProcessExecutionEvents();
|
||||
|
||||
function initializeProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
|
||||
processExecutionEvents.initialize(processConfig, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well.");
|
||||
startProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.start(onStarted);
|
||||
|
||||
function onStarted(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well.");
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
loopControl()
|
||||
return
|
||||
}
|
||||
|
||||
initializeStatusDependencies();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> 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(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeStatusDependencies() {
|
||||
try {
|
||||
statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT);
|
||||
statusDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well.");
|
||||
initializeDataDependencies();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDataDependencies() {
|
||||
try {
|
||||
dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET);
|
||||
dataDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); }
|
||||
switch (processConfig.framework.name) {
|
||||
case 'Low-Frequency-Trading-Process': {
|
||||
processFramework = TRADING_PROCESS_MODULE.newTradingProcess(bot, logger, UTILITIES);
|
||||
intitializeProcessFramework();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported.");
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + processConfig.framework.name);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> 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(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function intitializeProcessFramework() {
|
||||
try {
|
||||
processFramework.initialize(processConfig, statusDependencies, dataDependencies, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well.");
|
||||
startProcessFramework();
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> 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(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessFramework() {
|
||||
try {
|
||||
processFramework.start(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processFramework.finalize()
|
||||
processFramework = undefined
|
||||
dataDependencies.finalize()
|
||||
dataDependencies = undefined
|
||||
statusDependencies.finalize()
|
||||
statusDependencies = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
finishProcessExecutionEvents()
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
case "Dependency does not exist.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case "Dependency not ready.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function finishProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.finish(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processExecutionEvents.finalize()
|
||||
processExecutionEvents = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
loopControl(nextWaitTime);
|
||||
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(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> 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(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function loopControl(nextWaitTime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); }
|
||||
|
||||
/* We show we reached the end of the loop. */
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
|
||||
/* Here we check if we must stop the loop gracefully. */
|
||||
shallWeStop(onStop, onContinue);
|
||||
|
||||
function onStop() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); }
|
||||
|
||||
if (global.WRITE_LOGS_TO_FILES === 'true') {
|
||||
logger.persist();
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Stopped')
|
||||
processStopped()
|
||||
return;
|
||||
}
|
||||
|
||||
function onContinue() {
|
||||
/* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */
|
||||
switch (nextWaitTime) {
|
||||
case 'Waiting for Session': {
|
||||
let waitTime = processConfig.sessionRunWaitTime
|
||||
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
|
||||
let waitingTime = waitTime / 1000 / 60
|
||||
let label = 'minute/s'
|
||||
if (waitingTime < 1) {
|
||||
waitingTime = waitTime / 1000
|
||||
label = 'seconds'
|
||||
}
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ")
|
||||
logger.persist();
|
||||
}
|
||||
break
|
||||
case 'Normal': {
|
||||
let waitTime
|
||||
if (processConfig.waitsForExecutionFinishedEvent === true) {
|
||||
waitTime = 0
|
||||
} else {
|
||||
switch (bot.TRADING_SESSION.type) {
|
||||
case 'Live Trading Session': {
|
||||
waitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Fordward Tessting Session': {
|
||||
waitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
waitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Backtesting Session': {
|
||||
waitTime = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
|
||||
let waitingTime = waitTime / 1000 / 60
|
||||
let label = 'minute/s'
|
||||
if (waitingTime < 1) {
|
||||
waitingTime = waitTime / 1000
|
||||
label = 'seconds'
|
||||
}
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Retry': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Sleep': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Coma': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shallWeStop(stopCallBack, continueCallBack) {
|
||||
try {
|
||||
/* IMPORTANT: This function is exactly the same on the 3 modules. */
|
||||
if (!global.STOP_TASK_GRACEFULLY) {
|
||||
continueCallBack();
|
||||
} else {
|
||||
stopCallBack();
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function processHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
|
||||
}
|
||||
|
||||
function processStopped() {
|
||||
if (global.unexpectedError !== undefined) {
|
||||
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
|
||||
} else {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
|
||||
}
|
||||
sessionStopped()
|
||||
logger.persist();
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
if (global.unexpectedError !== undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
} else {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function sessionStopped() {
|
||||
if (bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Stopped')
|
||||
bot.TRADING_SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack);
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,519 @@
|
||||
exports.newTradingSession = function newTradingSession(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Trading Session"
|
||||
const FULL_LOG = true;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize
|
||||
}
|
||||
|
||||
const SOCIAL_BOTS_MODULE = require('./SocialBots.js')
|
||||
let socialBotsModule = SOCIAL_BOTS_MODULE.newSocialBots(bot, parentLogger)
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
try {
|
||||
/* Initialize this info so that everything is logged propeerly */
|
||||
bot.TRADING_SESSION = {
|
||||
name: bot.processNode.session.name,
|
||||
id: bot.processNode.session.id
|
||||
}
|
||||
|
||||
/* Set the folderName for early logging */
|
||||
if (bot.processNode.session.config.folderName === undefined) {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.TRADING_SESSION.id
|
||||
} else {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
|
||||
/* Check if there is a session */
|
||||
if (bot.processNode.session === undefined) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> Cannot run without a Session.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Listen to event to start or stop the session. */
|
||||
bot.TRADING_SESSIONKey = bot.processNode.session.name + '-' + bot.processNode.session.type + '-' + bot.processNode.session.id
|
||||
global.SESSION_MAP.set(bot.TRADING_SESSIONKey, bot.TRADING_SESSIONKey)
|
||||
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Trading Session Status', undefined, bot.TRADING_SESSIONKey, undefined, onSessionStatus)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Run Trading Session', undefined, bot.TRADING_SESSIONKey, undefined, onSessionRun)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Stop Trading Session', undefined, bot.TRADING_SESSIONKey, undefined, onSessionStop)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Resume Trading Session', undefined, bot.TRADING_SESSIONKey, undefined, onSessionResume)
|
||||
|
||||
/* Connect this here so that it is accesible from other places */
|
||||
bot.TRADING_SESSIONError = sessionError
|
||||
bot.TRADING_SESSIONWarning = sessionWarning
|
||||
bot.TRADING_SESSIONInfo = sessionInfo
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.TRADING_SESSIONHeartBeat = sessionHeartBeat
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
|
||||
function onSessionStatus() {
|
||||
if (bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
let event = {
|
||||
status: 'Trading Session Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Status Response', event)
|
||||
} else {
|
||||
let event = {
|
||||
status: 'Trading Session Not Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Status Response', event)
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionRun(message) {
|
||||
try {
|
||||
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
|
||||
if (bot.TRADING_SESSION_STATUS === 'Idle' || bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionRun -> Event received to run the Session while it was already running. ")
|
||||
return
|
||||
}
|
||||
|
||||
/* We are going to run the Definition comming at the event. */
|
||||
bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem)
|
||||
bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine)
|
||||
bot.TRADING_SESSION = JSON.parse(message.event.session)
|
||||
bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter)
|
||||
bot.RESUME = false
|
||||
bot.FIRST_EXECUTION = true
|
||||
bot.TRADING_SESSION.stop = stopSession // stop function
|
||||
|
||||
setUpSessionFolderName()
|
||||
|
||||
/* We validate all parameters received and complete some that might be missing if needed. */
|
||||
if (checkParemeters() === false) { return }
|
||||
|
||||
socialBotsModule.initialize()
|
||||
|
||||
let allGood
|
||||
switch (bot.TRADING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
allGood = startBackTesting(message)
|
||||
break
|
||||
}
|
||||
case 'Live Trading Session': {
|
||||
allGood = startLiveTrading(message)
|
||||
break
|
||||
}
|
||||
case 'Fordward Testing Session': {
|
||||
allGood = startFordwardTesting(message)
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
allGood = startPaperTrading(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allGood === true) {
|
||||
bot.TRADING_SESSION_STATUS = 'Idle'
|
||||
bot.STOP_SESSION = false
|
||||
} else {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] onSessionRun -> Stopping the Session now. ') }
|
||||
}
|
||||
|
||||
socialBotsModule.sendMessage(bot.TRADING_SESSION.type + " '" + bot.TRADING_SESSION.name + "' is starting.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionRun -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionStop() {
|
||||
stopSession('Session Stopped From the User Interface.')
|
||||
}
|
||||
|
||||
function onSessionResume(message) {
|
||||
try {
|
||||
if (bot.TRADING_SESSION.stop === undefined) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session that have never be ran before. ")
|
||||
return
|
||||
}
|
||||
|
||||
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
|
||||
if (bot.TRADING_SESSION_STATUS === 'Idle' || bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session while it was already running. ")
|
||||
return
|
||||
}
|
||||
|
||||
bot.RESUME = true
|
||||
bot.STOP_SESSION = false
|
||||
|
||||
socialBotsModule.sendMessage(bot.TRADING_SESSION.type + " '" + bot.TRADING_SESSION.name + "' is resuming.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionResume -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function stopSession(commandOrigin) {
|
||||
|
||||
socialBotsModule.sendMessage(bot.TRADING_SESSION.type + " '" + bot.TRADING_SESSION.name + "' is stopping " + commandOrigin)
|
||||
socialBotsModule.finalize()
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] stopSession -> Stopping the Session now. ') }
|
||||
sessionInfo(bot.TRADING_SESSION, commandOrigin)
|
||||
}
|
||||
|
||||
function setUpSessionFolderName() {
|
||||
/*
|
||||
The session object is overwritten when the session is run. For that reason we
|
||||
need to setup again the folder name at the Session level.
|
||||
Set the folderName for logging, reports, context and data output
|
||||
*/
|
||||
let config
|
||||
if (bot.TRADING_SESSION.config !== undefined) {
|
||||
config = bot.TRADING_SESSION.config
|
||||
if (config.folderName === undefined) {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.TRADING_SESSION.id
|
||||
} else {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkParemeters() {
|
||||
/*
|
||||
Here we check all the Session Parameters received. If something critical is missing we abort returning false. If something
|
||||
non critical is missing, we complete it with a default value.
|
||||
*/
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters === undefined) {
|
||||
let errorMessage = "Session Node with no Parameters."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Time Range */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange === undefined) { // if the Time Range is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange = {
|
||||
name: 'Missing Time Range',
|
||||
type: 'Time Range',
|
||||
config: {
|
||||
initialDatetime: (new Date()).valueOf(),
|
||||
finalDatetime: (new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Check that we received valid dates */
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (isNaN(new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (isNaN(new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* Session Type Forced Values */
|
||||
let today = (new Date()).valueOf()
|
||||
let aYearAgo = today - global.ONE_YEAR_IN_MILISECONDS
|
||||
let aYearFromNow = today + global.ONE_YEAR_IN_MILISECONDS
|
||||
switch (bot.TRADING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
useDefaultDatetimes(aYearAgo, today)
|
||||
break
|
||||
}
|
||||
case 'Live Trading Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
case 'Fordward Testing Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function useDefaultDatetimes(initialDefault, finalDefault) {
|
||||
/*
|
||||
Note that inside the system, we are going to deal with these
|
||||
dates in their numeric value representation.
|
||||
*/
|
||||
|
||||
/* Initial Datetime */
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime === undefined) {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = (new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
} else {
|
||||
/* Non backtest session can start from the past only if explicitly configured that way */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime === undefined || bot.TRADING_SESSION.tradingParameters.timeRange.config.allowStartingFromThePast !== true) {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = (new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Final Datetime */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime === undefined) {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime = finalDefault
|
||||
} else {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime = (new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Time Frame */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame Label configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
bot.TRADING_SESSION.tradingParameters.timeFrame.config.value = getTimeFrameFromLabel(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label)
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.value === undefined) {
|
||||
let errorMessage = "Config error: label value not recognized. Try 01-min or 01-hs for example."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Base Asset */
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionBaseAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Base Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Base Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.sessionBaseAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Quoted Asset */
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Quoted Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Quoted Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Slippage */
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage === undefined) { // if the Slippage is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage = {
|
||||
name: 'Missing Slippage',
|
||||
type: 'Slippage',
|
||||
config: {
|
||||
marketOrderRate: 0,
|
||||
stopLoss: 0,
|
||||
takeProfit: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage.config.marketOrderRate === undefined) { // if the marketOrderRate is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage.config.marketOrderRate = 0
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage.config.stopLoss === undefined) { // if the stopLoss is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage.config.stopLoss = 0
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage.config.takeProfit === undefined) { // if the takeProfit is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage.config.takeProfit = 0
|
||||
}
|
||||
|
||||
/* Fee Structure */
|
||||
if (bot.TRADING_SESSION.tradingParameters.feeStructure === undefined) { // if the Fee Structure is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.feeStructure = {
|
||||
name: 'Missing Fee Structure',
|
||||
type: 'Fee Structure',
|
||||
config: {
|
||||
maker: 0,
|
||||
taker: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.feeStructure.config.maker === undefined) { // if the maker is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.feeStructure.config.maker = 0
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.feeStructure.config.taker === undefined) { // if the taker is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.feeStructure.config.taker = 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function startBackTesting(message) {
|
||||
return true
|
||||
}
|
||||
|
||||
function startLiveTrading(message) {
|
||||
if (bot.KEY === undefined || bot.SECRET === undefined) {
|
||||
let errorMessage = "Key 'codeName' or 'secret' not provided. Plese check that and try again."
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> " + errorMessage); }
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function startFordwardTesting(message) {
|
||||
if (bot.KEY === undefined || bot.SECRET === undefined) {
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> startFordwardTesting -> Key name or Secret not provided, not possible to run the process in Forward Testing mode."); }
|
||||
console.log("Key 'codeName' or 'secret' not provided. Plese check that and try again.")
|
||||
return false
|
||||
}
|
||||
|
||||
/* Reduce the balance */
|
||||
let balancePercentage = 1 // This is the default value
|
||||
|
||||
if (bot.TRADING_SESSION.config.balancePercentage !== undefined) {
|
||||
balancePercentage = bot.TRADING_SESSION.config.balancePercentage
|
||||
}
|
||||
|
||||
bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.initialBalance = bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.initialBalance * balancePercentage / 100
|
||||
bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.initialBalance = bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.initialBalance * balancePercentage / 100
|
||||
|
||||
bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.minimumBalance = bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.minimumBalance * balancePercentage / 100
|
||||
bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.minimumBalance = bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.minimumBalance * balancePercentage / 100
|
||||
|
||||
bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.maximumBalance = bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.maximumBalance * balancePercentage / 100
|
||||
bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.maximumBalance = bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.maximumBalance * balancePercentage / 100
|
||||
|
||||
pProcessConfig.normalWaitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function startPaperTrading(message) {
|
||||
return true
|
||||
}
|
||||
|
||||
function getTimeFrameFromLabel(timeFrameLabel) {
|
||||
|
||||
for (let i = 0; i < global.marketFilesPeriods.length; i++) {
|
||||
let value = global.marketFilesPeriods[i][0]
|
||||
let label = global.marketFilesPeriods[i][1]
|
||||
|
||||
if (timeFrameLabel === label) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < global.dailyFilePeriods.length; i++) {
|
||||
let value = global.dailyFilePeriods[i][0]
|
||||
let label = global.dailyFilePeriods[i][1]
|
||||
|
||||
if (timeFrameLabel === label) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sessionHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Heartbeat', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionHeartBeat -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionError(node, errorMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Error', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionError -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionWarning(node, warningMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Warning', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionWarning -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionInfo(node, infoMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Info', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionInfo -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
Generated
+74
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "@superalgos/taskserver",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ccxt": {
|
||||
"version": "1.27.45",
|
||||
"resolved": "https://registry.npmjs.org/ccxt/-/ccxt-1.27.45.tgz",
|
||||
"integrity": "sha512-4ADlbcIWDzIWjtLvsrtux5jMFj7pU4kVuiqtI2Btq6NEhQ4r/4+LirXdEFlCF6/wIU3XSHabxMd/8nu8HC1KJw=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"module-alias": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz",
|
||||
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
},
|
||||
"sandwich-stream": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz",
|
||||
"integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ=="
|
||||
},
|
||||
"telegraf": {
|
||||
"version": "3.38.0",
|
||||
"resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.38.0.tgz",
|
||||
"integrity": "sha512-va4VlrKWp64JrowFoZX/NPzzA6q38kvaIukVXOWFO1V+jR1G8+hCfgJy4TX8Z3rwLJzwaBEet1QhikHDRZWl3A==",
|
||||
"requires": {
|
||||
"debug": "4.1.1",
|
||||
"minimist": "1.2.5",
|
||||
"module-alias": "2.2.2",
|
||||
"node-fetch": "2.6.0",
|
||||
"sandwich-stream": "2.0.2",
|
||||
"telegram-typings": "3.6.1"
|
||||
}
|
||||
},
|
||||
"telegram-typings": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/telegram-typings/-/telegram-typings-3.6.1.tgz",
|
||||
"integrity": "sha512-njVv1EAhIZnmQVLocZEADYUyqA1WIXuVcDYlsp+mXua/XB0pxx+PKtMSPeZ/EE4wPWTw9h/hA9ASTT6yQelkiw=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz",
|
||||
"integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@superalgos/taskserver",
|
||||
"version": "2.0.0",
|
||||
"description": "Executes Tasks.",
|
||||
"author": "Superalgos.org",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Superalgos/TaskServer.git"
|
||||
},
|
||||
"main": "Run.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ccxt": "^1.25.80",
|
||||
"dotenv": "^8.2.0",
|
||||
"telegraf": "^3.37.0",
|
||||
"ws": "^7.1.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
|
||||
/* Callbacks default responses. */
|
||||
global.DEFAULT_OK_RESPONSE = {
|
||||
result: "Ok",
|
||||
message: "Operation Succeeded"
|
||||
};
|
||||
|
||||
global.DEFAULT_FAIL_RESPONSE = {
|
||||
result: "Fail",
|
||||
message: "Operation Failed"
|
||||
};
|
||||
|
||||
global.DEFAULT_RETRY_RESPONSE = {
|
||||
result: "Retry",
|
||||
message: "Retry Later"
|
||||
};
|
||||
|
||||
global.CUSTOM_OK_RESPONSE = {
|
||||
result: "Ok, but check Message",
|
||||
message: "Custom Message"
|
||||
};
|
||||
|
||||
global.CUSTOM_FAIL_RESPONSE = {
|
||||
result: "Fail Because",
|
||||
message: "Custom Message"
|
||||
};
|
||||
|
||||
global.ROOT_DIR = './';
|
||||
|
||||
global.ONE_YEAR_IN_MILISECONDS = 365 * 24 * 60 * 60 * 1000
|
||||
global.ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000
|
||||
global.ONE_MIN_IN_MILISECONDS = 60 * 1000
|
||||
global.LOGGER_MAP = new Map() // We will put all the loggers in a map, so that we can eventually finalize them.
|
||||
global.SESSION_MAP = new Map() // We will put all the sessions in a map, so that we can eventually finalize them.
|
||||
|
||||
global.UNIQUE_ID = function () {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
global.PRECISE = function (floatNumber, precision) {
|
||||
return this.parseFloat(floatNumber.toFixed(precision))
|
||||
}
|
||||
|
||||
global.REMOVE_TIME = function (datetime) {
|
||||
const GMT_SECONDS = ':00.000 GMT+0000';
|
||||
let date = new Date(datetime)
|
||||
return new Date(date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + " " + "00:00" + GMT_SECONDS);
|
||||
}
|
||||
|
||||
global.PROCESS_ERROR = function (processKey, node, errorMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(processKey, 'Error', event)
|
||||
}
|
||||
|
||||
global.PROCESS_WARNING = function (processKey, node, warningMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(processKey, 'Warning', event)
|
||||
}
|
||||
|
||||
global.PROCESS_INFO = function (processKey, node, infoMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(processKey, 'Info', event)
|
||||
}
|
||||
|
||||
global.NODE_BRANCH_TO_ARRAY = function (node, nodeType) {
|
||||
let resultArray = []
|
||||
scanNodeBranch(node, nodeType)
|
||||
return resultArray
|
||||
|
||||
function scanNodeBranch(startingNode) {
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(startingNode.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (startingNode.type === nodeType) {
|
||||
resultArray.push(startingNode)
|
||||
return
|
||||
}
|
||||
|
||||
if (nodeDefinition.properties === undefined) { return }
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
scanNodeBranch(startingNode[property.name])
|
||||
}
|
||||
break
|
||||
case 'array': {
|
||||
let startingNodePropertyArray = startingNode[property.name]
|
||||
if (startingNodePropertyArray !== undefined) {
|
||||
for (let m = 0; m < startingNodePropertyArray.length; m++) {
|
||||
scanNodeBranch(startingNodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.FIND_NODE_IN_NODE_MESH = function (node, nodeType) {
|
||||
/*
|
||||
This function scans a node mesh for a certain node type and
|
||||
returns the first instance found.
|
||||
*/
|
||||
let nodeFound
|
||||
scanNodeMesh(node, nodeType)
|
||||
return nodeFound
|
||||
|
||||
function scanNodeMesh(startingNode) {
|
||||
if (startingNode === undefined) { return }
|
||||
if (nodeFound !== undefined) { return }
|
||||
|
||||
let nodeDefinition = APP_SCHEMA_MAP.get(startingNode.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (startingNode.type === nodeType) {
|
||||
nodeFound = startingNode
|
||||
return
|
||||
}
|
||||
|
||||
/* We scan through this node children */
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
scanNodeMesh(startingNode[property.name])
|
||||
}
|
||||
break
|
||||
case 'array': {
|
||||
let startingNodePropertyArray = startingNode[property.name]
|
||||
if (startingNodePropertyArray !== undefined) {
|
||||
for (let m = 0; m < startingNodePropertyArray.length; m++) {
|
||||
scanNodeMesh(startingNodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* We scan parents nodes. */
|
||||
if (startingNode.parentNode !== undefined) {
|
||||
scanNodeMesh(startingNode.parentNode)
|
||||
}
|
||||
/* We scan reference parents too. */
|
||||
if (startingNode.referenceParent !== undefined) {
|
||||
scanNodeMesh(startingNode.referenceParent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.NODE_MESH_TO_PATH_ARRAY = function (node, nodeId) {
|
||||
/*
|
||||
This function scans a node mesh for a certain node if and
|
||||
returns an array with the path within that mesh to the
|
||||
requested node.
|
||||
*/
|
||||
let nodeArray = []
|
||||
scanNodeMesh(node)
|
||||
return nodeArray
|
||||
|
||||
function scanNodeMesh(startingNode) {
|
||||
if (startingNode === undefined) { return }
|
||||
|
||||
let nodeDefinition = APP_SCHEMA_MAP.get(startingNode.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (startingNode.id === nodeId) {
|
||||
nodeArray.push(startingNode)
|
||||
return
|
||||
}
|
||||
|
||||
/* We scan through this node children */
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
scanNodeMesh(startingNode[property.name])
|
||||
if (nodeArray.length > 0) {
|
||||
nodeArray.unshift(startingNode)
|
||||
return
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'array': {
|
||||
let startingNodePropertyArray = startingNode[property.name]
|
||||
if (startingNodePropertyArray !== undefined) {
|
||||
for (let m = 0; m < startingNodePropertyArray.length; m++) {
|
||||
scanNodeMesh(startingNodePropertyArray[m])
|
||||
if (nodeArray.length > 0) {
|
||||
nodeArray.unshift(startingNode)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* We scan parents nodes. */
|
||||
if (startingNode.parentNode !== undefined) {
|
||||
scanNodeMesh(startingNode.parentNode)
|
||||
if (nodeArray.length > 0) {
|
||||
nodeArray.unshift(startingNode)
|
||||
return
|
||||
}
|
||||
}
|
||||
/* We scan reference parents too. */
|
||||
if (startingNode.referenceParent !== undefined) {
|
||||
scanNodeMesh(startingNode.referenceParent)
|
||||
if (nodeArray.length > 0) {
|
||||
nodeArray.unshift(startingNode)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.FIND_NODE_IN_NODE_ARRAY = function (nodeArray, nodeType) {
|
||||
for (let i=0; i < nodeArray.length; i++) {
|
||||
let node = nodeArray[i]
|
||||
if (node.type === nodeType) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY = function (nodeArray) {
|
||||
let filteredNodeArray = []
|
||||
for (let i = 0; i < nodeArray.length; i++) {
|
||||
let arrayItem = nodeArray[i]
|
||||
if (arrayItem.referenceParent === undefined) {
|
||||
continue
|
||||
} else {
|
||||
filteredNodeArray.push(arrayItem)
|
||||
}
|
||||
}
|
||||
return filteredNodeArray
|
||||
}
|
||||
|
||||
global.EMIT_SESSION_STATUS = function (status, key) {
|
||||
switch(status) {
|
||||
case 'Running': {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Running')
|
||||
break
|
||||
}
|
||||
case 'Stopped': {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Stopped')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message)
|
||||
console.log('[ERROR] Task Server -> server -> uncaughtException -> err.stack = ' + err.stack)
|
||||
console.log(err.stack)
|
||||
global.EXIT_NODE_PROCESS()
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('[ERROR] Task Server -> server -> unhandledRejection -> reason = ' + JSON.stringify(reason))
|
||||
console.log('[ERROR] Task Server -> server -> unhandledRejection -> p = ' + JSON.stringify(p))
|
||||
console.log(reason.stack)
|
||||
global.EXIT_NODE_PROCESS()
|
||||
})
|
||||
|
||||
function finalizeLoggers() {
|
||||
global.LOGGER_MAP.forEach(forEachLogger)
|
||||
|
||||
function forEachLogger(logger) {
|
||||
if (logger !== undefined) {
|
||||
logger.finalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function finalizeSessions() {
|
||||
global.SESSION_MAP.forEach(forEachSession)
|
||||
|
||||
function forEachSession(session) {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(session, 'Stopped')
|
||||
}
|
||||
}
|
||||
|
||||
process.on('exit', function (config) {
|
||||
|
||||
if (global.TASK_NODE !== undefined) {
|
||||
/* We send an event signaling that the Task is being terminated. */
|
||||
let key = global.TASK_NODE.name + '-' + global.TASK_NODE.type + '-' + global.TASK_NODE.id
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Stopped') // Meaning Task Stopped
|
||||
global.EVENT_SERVER_CLIENT.finalize()
|
||||
global.EVENT_SERVER_CLIENT = undefined
|
||||
}
|
||||
|
||||
//console.log('[INFO] Task Server -> server -> process.on.exit -> About to exit -> config = ' + config)
|
||||
})
|
||||
|
||||
/* Here we listen for the message to stop this Task / Process comming from the Task Manager, which is the paret of this node js process. */
|
||||
process.on('message', message => {
|
||||
if (message === 'Stop this Task') {
|
||||
|
||||
global.STOP_TASK_GRACEFULLY = true;
|
||||
|
||||
/*
|
||||
There are some process that might no be able to end grafully, for example the ones schedulle to process information in a future day or month.
|
||||
In order to be sure that the process will be terminated, we schedulle one forced exit in 2 minutes from now.
|
||||
*/
|
||||
let key = global.TASK_NODE.name + '-' + global.TASK_NODE.type + '-' + global.TASK_NODE.id
|
||||
console.log('[INFO] Task Server -> server -> process.on -> Stopping Task ' + key + '. Nodejs process will be exited in less than 1 minute.')
|
||||
setTimeout(global.EXIT_NODE_PROCESS, 60000);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let shuttingDownProcess = false
|
||||
global.EXIT_NODE_PROCESS = function exitProcess() {
|
||||
|
||||
if (global.unexpectedError !== undefined) {
|
||||
global.taskError(undefined, "An unexpected error caused the Task to stop.")
|
||||
}
|
||||
|
||||
if (shuttingDownProcess === true) { return }
|
||||
shuttingDownProcess = true
|
||||
|
||||
/* Signal that all sessions are stopping. */
|
||||
finalizeSessions()
|
||||
|
||||
/* Cleaning Before Exiting. */
|
||||
clearInterval(global.HEARTBEAT_INTERVAL_HANDLER)
|
||||
|
||||
if (global.TASK_NODE !== undefined) {
|
||||
for (let i = 0; i < global.TASK_NODE.bot.processes.length; i++) {
|
||||
let config = global.TASK_NODE.bot.processes[i].config
|
||||
let process = global.TASK_NODE.bot.processes[i]
|
||||
|
||||
let key = process.name + '-' + process.type + '-' + process.id
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Stopped') // Meaning Process Stopped
|
||||
}
|
||||
}
|
||||
|
||||
finalizeLoggers()
|
||||
//console.log("[INFO] Task Server -> " + global.TASK_NODE.name + " -> EXIT_NODE_PROCESS -> Task Server will stop in 10 seconds.");
|
||||
|
||||
setTimeout(process.exit, 10000) // We will give 10 seconds to logs be written on file
|
||||
}
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
global.WRITE_LOGS_TO_FILES = process.env.WRITE_LOGS_TO_FILES
|
||||
|
||||
/*
|
||||
We need to count how many process instances we deployd and how many of them have already finished their job, either
|
||||
because they just finished or because there was a request to stop the proceses. In this way, once we reach the
|
||||
amount of instances started, we can safelly destroy the rest of the objects running and let this nodejs process die.
|
||||
*/
|
||||
|
||||
global.ENDED_PROCESSES_COUNTER = 0
|
||||
global.TOTAL_PROCESS_INSTANCES_CREATED = 0
|
||||
|
||||
/*
|
||||
|
||||
We read the first string sent as an argument when the process was created by the Task Manager. There we will find the information of the identity
|
||||
of this Task and know exactly what to run within this server instance.
|
||||
|
||||
*/
|
||||
let taskId = process.argv[2] // reading what comes as an argument of the nodejs process.
|
||||
|
||||
/* Setting up the global Event Handler */
|
||||
|
||||
let EVENT_SERVER_CLIENT = require('./EventServerClient.js');
|
||||
|
||||
global.EVENT_SERVER_CLIENT = EVENT_SERVER_CLIENT.newEventsServerClient()
|
||||
global.EVENT_SERVER_CLIENT.initialize(preLoader)
|
||||
global.STOP_TASK_GRACEFULLY = false;
|
||||
|
||||
function preLoader() {
|
||||
if (taskId !== undefined) {
|
||||
/* The Task Manager sent the info via a process argument. In this case we listen to an event with the Task Info that should be emitted at the UI */
|
||||
try {
|
||||
//console.log('[INFO] Task Server -> server -> preLoader -> Listening to starting event -> key = ' + 'Task Server - ' + taskId)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent('Task Server - ' + taskId, 'Run Task', undefined, 'Task Server - ' + taskId, undefined, eventReceived)
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent('Task Manager - ' + taskId, 'Nodejs Process Ready for Task')
|
||||
function eventReceived(message) {
|
||||
try {
|
||||
global.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema)
|
||||
setUpAppSchema()
|
||||
global.TASK_NODE = JSON.parse(message.event.taskDefinition)
|
||||
global.TASK_NETWORK = JSON.parse(message.event.networkDefinition)
|
||||
bootLoader()
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> eventReceived -> ' + err.stack)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> global.TASK_NODE -> ' + err.stack)
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> global.TASK_NODE = ' + JSON.stringify(global.TASK_NODE).substring(0, 1000))
|
||||
}
|
||||
}
|
||||
else { /* This process was started not by the Task Manager, but independently (most likely for debugging purposes). In this case we listen to an event with the Task Info that should be emitted at the UI */
|
||||
try {
|
||||
//console.log('[INFO] Task Server -> server -> preLoader -> Waiting for event to start debugging...')
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent('Task Server', 'Debug Task Started', undefined, 'Task Server', undefined, startDebugging)
|
||||
function startDebugging(message) {
|
||||
try {
|
||||
global.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema)
|
||||
setUpAppSchema()
|
||||
global.TASK_NODE = JSON.parse(message.event.taskDefinition)
|
||||
global.TASK_NETWORK = JSON.parse(message.event.networkDefinition)
|
||||
bootLoader()
|
||||
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> startDebugging -> ' + err.stack)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> global.TASK_NODE -> ' + err.stack)
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> global.TASK_NODE = ' + JSON.stringify(global.TASK_NODE).substring(0, 1000))
|
||||
}
|
||||
}
|
||||
|
||||
function setUpAppSchema() {
|
||||
/* Setup the APP_SCHEMA_MAP based on the APP_SCHEMA_ARRAY */
|
||||
global.APP_SCHEMA_MAP = new Map()
|
||||
for (let i = 0; i < global.APP_SCHEMA_ARRAY.length; i++) {
|
||||
let nodeDefinition = global.APP_SCHEMA_ARRAY[i]
|
||||
let key = nodeDefinition.type
|
||||
global.APP_SCHEMA_MAP.set(key, nodeDefinition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bootLoader() {
|
||||
|
||||
/* Heartbeat sent to the UI */
|
||||
|
||||
let key = global.TASK_NODE.name + '-' + global.TASK_NODE.type + '-' + global.TASK_NODE.id
|
||||
|
||||
global.EVENT_SERVER_CLIENT.createEventHandler(key)
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Running') // Meaning Task Running
|
||||
global.HEARTBEAT_INTERVAL_HANDLER = setInterval(taskHearBeat, 1000)
|
||||
|
||||
function taskHearBeat() {
|
||||
|
||||
/* The heartbeat event is raised at the event handler of the instance of this task, created at the UI. */
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds()
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Heartbeat', event)
|
||||
}
|
||||
|
||||
global.taskError = function taskError(node, errorMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Error', event)
|
||||
}
|
||||
|
||||
|
||||
for (let processIndex = 0; processIndex < global.TASK_NODE.bot.processes.length; processIndex++) {
|
||||
let config = global.TASK_NODE.bot.processes[processIndex].config
|
||||
|
||||
/* Validate that the minimun amount of input required are defined. */
|
||||
|
||||
if (global.TASK_NODE.parentNode === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Task without a Task Manager. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex]));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.parentNode.parentNode === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Task Manager without Mine Tasks. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex]));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.parentNode.parentNode.parentNode === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Mine Tasks without Market Tasks. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex]));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Market Tasks without a Market. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex]));
|
||||
continue
|
||||
}
|
||||
|
||||
global.MARKET_NODE = global.TASK_NODE.parentNode.parentNode.parentNode.referenceParent
|
||||
|
||||
if (global.MARKET_NODE.parentNode === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Market without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.MARKET_NODE.parentNode.parentNode === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Exchange Markets without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE.parentNode));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.MARKET_NODE.baseAsset === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Market without a Base Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.MARKET_NODE.quotedAsset === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Market without a Quoted Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.MARKET_NODE.baseAsset.referenceParent === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Base Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE.baseAsset));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.MARKET_NODE.quotedAsset.referenceParent === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Quoted Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.MARKET_NODE.quotedAsset));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.bot.processes[processIndex].referenceParent === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Process Instance without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex]));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Process Definition without parent Bot Definition. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Bot Definition without parent Data Mine. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.bot.processes[processIndex].referenceParent.config.codeName === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Process Definition without a codeName defined. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.config.codeName === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Bot Definition without a codeName defined. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode));
|
||||
continue
|
||||
}
|
||||
|
||||
if (global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName === undefined) {
|
||||
console.log("[ERROR] Task Server -> server -> bootLoader -> Data Mine without a codeName defined. -> Data Mine Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode));
|
||||
continue
|
||||
}
|
||||
|
||||
startRoot(processIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function startRoot(processIndex) {
|
||||
|
||||
// console.log('[INFO] Task Server -> server -> startRoot -> Entering function. ')
|
||||
|
||||
const ROOT_MODULE = require('./Root')
|
||||
let root = ROOT_MODULE.newRoot()
|
||||
|
||||
root.initialize(onInitialized)
|
||||
|
||||
function onInitialized() {
|
||||
//console.log('[INFO] Task Server -> server -> startRoot -> onInitialized -> Entering function. ')
|
||||
root.start(processIndex)
|
||||
}
|
||||
}
|
||||
|
||||
global.getPercentage = function (fromDate, currentDate, lastDate) {
|
||||
let fromDays = Math.trunc(fromDate.valueOf() / global.ONE_DAY_IN_MILISECONDS)
|
||||
let currentDays = Math.trunc(currentDate.valueOf() / global.ONE_DAY_IN_MILISECONDS)
|
||||
let lastDays = Math.trunc(lastDate.valueOf() / global.ONE_DAY_IN_MILISECONDS)
|
||||
let percentage = (currentDays - fromDays) * 100 / (lastDays - fromDays)
|
||||
if ((lastDays - fromDays) === 0) {
|
||||
percentage = 100
|
||||
}
|
||||
return percentage
|
||||
}
|
||||
|
||||
global.areEqualDates = function (date1, date2) {
|
||||
let day1Days = Math.trunc(date1.valueOf() / global.ONE_DAY_IN_MILISECONDS)
|
||||
let day2Days = Math.trunc(date2.valueOf() / global.ONE_DAY_IN_MILISECONDS)
|
||||
|
||||
if (day1Days === day2Days) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user