Merge pull request #153 from AAMasters/develop

Beta 6 Pre-Release
This commit is contained in:
Luis Fernando Molina authored and GitHub committed 2020-10-05 17:17:51 +02:00
commit e07ae07616
34 files changed
+5344 -3061

No files matched your search

+30 -16
View File
@@ -75,7 +75,7 @@
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 = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode));
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));
@@ -95,29 +95,43 @@
return
}
if (outputDatasetNode.referenceParent.parentNode.parentNode === undefined) {
logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode));
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) {
logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = ");
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
return
}
if (outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName === undefined) {
logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode));
if (botNode.config.codeName === undefined) {
logger.write(MODULE_NAME, "[ERROR] start -> Bot witn no codeName defined.");
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
return
}
if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode === undefined) {
logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode));
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
return
}
if (outputDatasetNode.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) {
logger.write(MODULE_NAME, "[ERROR] start -> Data Mine witn no codeName defined. Data Mine = " + JSON.stringify(outputDatasetNode.referenceParent.parentNode.parentNode.parentNode));
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) {
logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine or a Trading Mine.");
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
}
+45 -27
View File
@@ -4,12 +4,12 @@
const LOG_FILE_CONTENT = false;
const MODULE_NAME = "Data Dependencies";
let bot = BOT
let bot = BOT
let thisObject = {
nodeArray: undefined,
dataSetsModulesArray: [],
isItADepenency: isItADepenency,
isItADepenency: isItADepenency,
initialize: initialize,
finalize: finalize
};
@@ -19,20 +19,22 @@
return thisObject;
function initialize(callBackFunction) {
try {
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
/* Basic Valdidations */
if (bot.processNode.referenceParent.processDependencies !== undefined) {
if (bot.processNode.referenceParent.processDependencies.dataDependencies !== undefined) {
thisObject.nodeArray = bot.processNode.referenceParent.processDependencies.dataDependencies
} else {
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 to not have process dependencies, which means not data dependencies.");
callBackFunction(global.DEFAULT_OK_RESPONSE)
@@ -51,46 +53,62 @@
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)
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 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
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) {
function onInitilized(err, wasInitialized) {
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err = "+ JSON.stringify(err));
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> err = " + JSON.stringify(err));
alreadyCalledBack = true;
callBackFunction(err);
return;
}
addDataSet();
if (wasInitialized === true) {
addCount++;
newNodeArray.push(thisObject.nodeArray[i])
addDataSet()
} else {
skipCount++
}
checkIfWeAreDone()
}
function addDataSet() {
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> addDataSet -> Entering function."); }
addCount++;
function addDataSet() {
thisObject.dataSetsModulesArray.push(dataSetModule);
}
if (addCount === thisObject.nodeArray.length) {
function checkIfWeAreDone() {
if (addCount + skipCount === thisObject.nodeArray.length) {
if (alreadyCalledBack === false) {
alreadyCalledBack = true
thisObject.nodeArray = newNodeArray
callBackFunction(global.DEFAULT_OK_RESPONSE);
return;
}
@@ -99,14 +117,14 @@
}
} catch (err) {
logger.write(MODULE_NAME, "[ERROR] initialize -> err = "+ err.stack);
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]
let dataSetModule = thisObject.dataSetsModulesArray[i]
dataSetModule.finalize()
}
thisObject.dataSetsModulesArray = undefined
@@ -115,7 +133,7 @@
}
function isItADepenency(timeFrame, product) {
let key = timeFrame + '-' + product
let key = timeFrame + '-' + product
return filter.get(key)
}
+13 -71
View File
@@ -23,9 +23,6 @@ exports.newDataSet = function newDataSet(BOT, logger) {
function initialize(dataDependency, callBackFunction) {
try {
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
thisObject.node = dataDependency.referenceParent;
logger.fileName = MODULE_NAME + "." + thisObject.node.type + "." + thisObject.node.name + "." + thisObject.node.id;
@@ -67,7 +64,7 @@ exports.newDataSet = function newDataSet(BOT, logger) {
}
if (thisObject.node.parentNode.parentNode.parentNode === undefined) {
logger.write(MODULE_NAME, "[ERROR] initialize -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(thisObject.node.parentNode.parentNode));
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
}
@@ -82,77 +79,22 @@ exports.newDataSet = function newDataSet(BOT, logger) {
let network = global.TASK_NETWORK
let datasetProductDefinition = thisObject.node.parentNode
for (let i = 0; i < network.networkNodes.length; i++) {
let networkNode = network.networkNodes[i]
if (networkNode.dataStorage !== undefined) {
if (networkNode.dataStorage.sessionIndependentData !== undefined) {
for (let j = 0; j < networkNode.dataStorage.sessionIndependentData.exchangeDataProducts.length; j++) {
let exchangeDataProduct = networkNode.dataStorage.sessionIndependentData.exchangeDataProducts[j]
for (let k = 0; k < exchangeDataProduct.singleMarketData.length; k++) {
let singleMarketData = exchangeDataProduct.singleMarketData[k]
if (singleMarketData.referenceParent !== undefined) {
let market = singleMarketData.referenceParent
let currentProcessMarket = bot.processNode.marketReference.referenceParent
let nodeArray = global.NODE_MESH_TO_PATH_ARRAY(network, datasetProductDefinition.id)
let networkNode = global.FIND_NODE_IN_NODE_ARRAY(nodeArray, 'Network Node')
if (currentProcessMarket.id === market.id) {
for (let m = 0; m < singleMarketData.dataProducts.length; m++) {
let dataProduct = singleMarketData.dataProducts[m]
if (dataProduct.referenceParent !== undefined) {
let productDefinition = dataProduct.referenceParent
if (datasetProductDefinition.id === productDefinition.id) {
/* 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);
return
}
}
}
}
}
}
}
}
if (networkNode.dataStorage.sessionBasedData !== undefined) {
for (let j = 0; j < networkNode.dataStorage.sessionBasedData.exchangeDataProducts.length; j++) {
let exchangeDataProduct = networkNode.dataStorage.sessionBasedData.exchangeDataProducts[j]
for (let s = 0; s < exchangeDataProduct.sessionReferences.length; s++) {
let sessionReference = exchangeDataProduct.sessionReferences[s]
let singleMarketData = sessionReference.singleMarketData
if (singleMarketData.referenceParent !== undefined) {
let market = singleMarketData.referenceParent
let currentProcessMarket = bot.processNode.marketReference.referenceParent
if (currentProcessMarket.id === market.id) {
for (let m = 0; m < singleMarketData.dataProducts.length; m++) {
let dataProduct = singleMarketData.dataProducts[m]
if (dataProduct.referenceParent !== undefined) {
let productDefinition = dataProduct.referenceParent
if (datasetProductDefinition.id === productDefinition.id) {
/* 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);
return
}
}
}
}
}
}
}
}
}
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
}
logger.write(MODULE_NAME, "[ERROR] 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, "[ERROR] initialize -> Could not find where " + datasetProductDefinition.name + " for " + bot.exchange + " " + bot.market.baseAsset + "/" + bot.market.quotedAsset + " is stored within the network.");
/* 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 + '.'); }
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
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);
+1 -1
View File
@@ -101,7 +101,7 @@ exports.newDebugLog = function newDebugLog() {
writeLog();
/* This is the implementation of the mechanism to auto-mantain logs. */
if (contentToPersist.indexOf('[ERROR]') < 0 && contentToPersist.indexOf('[PERSIST]') < 0) {
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) {
+42 -8
View File
@@ -4,7 +4,7 @@ const path = require('path')
exports.newFileStorage = function newFileStorage(logger, host, port) {
const MODULE_NAME = 'FileStorage'
const MAX_RETRY = 30
const MAX_RETRY = 10
const FAST_RETRY_TIME_IN_MILISECONDS = 500
const SLOW_RETRY_TIME_IN_MILISECONDS = 2000
@@ -17,6 +17,8 @@ exports.newFileStorage = function newFileStorage(logger, host, port) {
}
let thisObject = {
asyncGetTextFile: asyncGetTextFile,
asyncCreateTextFile: asyncCreateTextFile,
getTextFile: getTextFile,
createTextFile: createTextFile,
deleteTextFile: deleteTextFile
@@ -24,6 +26,41 @@ exports.newFileStorage = function newFileStorage(logger, host, port) {
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
@@ -31,9 +68,6 @@ exports.newFileStorage = function newFileStorage(logger, host, port) {
recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious)
function recursiveGetTextFile(filePath, callBackFunction, noRetry, canUsePrevious) {
logger.write(MODULE_NAME, '[INFO] FileStorage -> getTextFile -> Entering Function.')
let fileDoesNotExist = false
/* Choose path for either bots or data */
@@ -163,7 +197,7 @@ exports.newFileStorage = function newFileStorage(logger, host, port) {
}
function retry() {
//console.log(new Date(), ' Retry #: ' + currentRetryGetTextFile, fileLocation )
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)
@@ -219,12 +253,12 @@ exports.newFileStorage = function newFileStorage(logger, host, port) {
*/
const fs = require('fs')
if (noTemp === true) {
fs.writeFile(fileLocation, fileContent, onFileWritenn)
fs.writeFile(fileLocation, fileContent, onFileWriten)
} else {
fs.writeFile(fileLocation + '.tmp', fileContent, onFileWritenn)
fs.writeFile(fileLocation + '.tmp', fileContent, onFileWriten)
}
function onFileWritenn(err) {
function onFileWriten(err) {
let retryTimeToUse = FAST_RETRY_TIME_IN_MILISECONDS
if (currentRetryWriteTextFile > MAX_RETRY - 2) {
retryTimeToUse = SLOW_RETRY_TIME_IN_MILISECONDS
+4 -24
View File
@@ -36,7 +36,7 @@
}
/* 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.repo + "/" + pProcessConfig.codeName
let filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo + "/" + pProcessConfig.codeName
filePath += "/User.Bot.js"
fileStorage.getTextFile(filePath, onBotDownloaded);
@@ -54,7 +54,7 @@
USER_BOT_MODULE = {}
USER_BOT_MODULE.newUserBot = eval(text); // TODO This needs to be changed function
filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo;
filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo
filePath += "/Commons.js"
fileStorage.getTextFile(filePath, onCommonsDownloaded);
@@ -81,9 +81,6 @@
try {
let fixedTimeLoopIntervalHandle;
/* Errors sent to the UI */
bot.processError = processError
/* Heartbeats sent to the UI */
bot.processHeartBeat = processHeartBeat
@@ -145,7 +142,7 @@
function initializeProcessExecutionEvents() {
try {
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
processExecutionEvents.initialize(onInizialized);
processExecutionEvents.initialize(processConfig, onInizialized);
function onInizialized(err) {
try {
@@ -763,26 +760,9 @@
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
}
function processError(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.processKey, 'Error', event)
}
function processStopped() {
if (global.unexpectedError !== undefined) {
processError(undefined, "An unexpected error caused the Process to stop.")
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
} else {
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
}
+8 -8
View File
@@ -24,14 +24,10 @@
}
function initialize(callBackFunction) {
try {
logger.fileName = MODULE_NAME;
logger.initialize();
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
callBackFunction(global.DEFAULT_OK_RESPONSE);
} catch (err) {
@@ -57,10 +53,11 @@
}
/* The first phase here is about checking that we have everything we need at the definition level. */
let dataDependencies = bot.processNode.referenceParent.processDependencies.dataDependencies
let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency')
if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return }
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
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. */
@@ -134,8 +131,11 @@
contextSummary.dataset = outputDatasetNode.referenceParent.config.codeName
contextSummary.product = outputDatasetNode.referenceParent.parentNode.config.codeName
contextSummary.bot = outputDatasetNode.referenceParent.parentNode.parentNode.config.codeName
contextSummary.dataMine = outputDatasetNode.referenceParent.parentNode.parentNode.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)
+1 -5
View File
@@ -41,11 +41,7 @@ exports.newAnnouncements = function newAnnouncements(bot, logger) {
if (canAnnounce === true) {
let text = announcement.config.text
if (announcement.formula !== undefined) {
try {
text = eval(announcement.formula.code)
} catch (err) {
tradingSystem.errors.push([announcement.formula.id, err.message])
}
text = tradingSystem.formulas.get(announcement.formula.id)
}
if (bot.SESSION.socialBots !== undefined) {
+18 -11
View File
@@ -35,6 +35,7 @@
'timeout': 30000,
'enableRateLimit': true,
verbose: false,
'adjustForTimeDifference': true,
options: options
}
exchange = new exchangeClass(exchangeConstructorParams)
@@ -50,11 +51,11 @@
async function getOrder(tradingSystemOrder, tradingEngineOrder) {
let orderId = tradingEngineOrder.exchangeId.value
const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset
/* Basic Logging */
logInfo("getOrder -> Entering function. orderId = " + orderId);
const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset
logInfo("getOrder -> orderId = " + orderId)
logInfo("getOrder -> symbol = " + symbol)
/* Basic Validations */
if (exchange.has['fetchOrder'] === false) {
@@ -64,9 +65,11 @@
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, err.message])
tradingSystem.errors.push([tradingSystemOrder.id, "getOrder -> Error = " + err.message])
logError("getOrder -> Error = " + err.message);
}
}
@@ -74,10 +77,10 @@
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 amount = tradingEngineOrder.size.value // CCXT: how much of currency you want to trade
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': {
@@ -121,10 +124,12 @@
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, err.message])
logError("getOrder -> Error = " + err.message);
tradingSystem.errors.push([tradingSystemOrder.id, "createOrder -> Error = " + err.message])
logError("createOrder -> Error = " + err.message);
}
}
@@ -133,22 +138,24 @@
let orderId = tradingEngineOrder.exchangeId.value
/* Basic Logging */
logInfo("getOrder -> Entering function. orderId = " + orderId);
logInfo("cancelOrder -> Entering function. orderId = " + orderId);
const symbol = bot.market.baseAsset + '/' + bot.market.quotedAsset
/* Basic Validations */
if (exchange.has['fetchOrder'] === false) {
logError("getOrder -> Exchange does not support fetchOrder command.");
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, err.message])
logError("getOrder -> Error = " + err.message);
tradingSystem.errors.push([tradingSystemOrder.id, "cancelOrder -> Error = " + err.message])
logError("cancelOrder -> Error = " + err.message);
}
}
+17 -11
View File
@@ -5,7 +5,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
const MODULE_NAME = 'Snapshots'
let thisObject = {
reset: reset,
updateChart: updateChart,
strategyEntry: strategyEntry,
strategyExit: strategyExit,
positionEntry: positionEntry,
@@ -60,9 +60,8 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
chart = undefined
}
function reset(pChart) {
/* This function helps reset data structures at every cycle of the simulation loop/=. */
chart = pChart // We need chat to be a local object accessible from conditions and formulas.
function updateChart(pChart) {
chart = pChart
}
function strategyEntry() {
@@ -125,7 +124,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
}
/* Now we go down through all this node children */
let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type)
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
if (nodeDefinition === undefined) { return }
if (nodeDefinition.properties !== undefined) {
@@ -237,8 +236,13 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
snapshots.headers.push(key)
}
let value = eval(instruction)
values.push(value)
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)
}
}
}
}
@@ -252,16 +256,18 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
function getResults(openDatetime, closeDatetime) {
let closeHeaders = ['Trade Number', 'Open Datetime', 'Close Datetime', 'Strategy Name', 'Trigger On Situation', 'Take Position Situation', 'Result', 'ROI', 'Exit Type']
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.episode.episodeCounters.positions.value, // Position Number
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.positionStatistics.hitFail.value, // Result
tradingEngine.current.position.positionStatistics.ROI.value, // ROI
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
]
+88 -79
View File
@@ -5,11 +5,13 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
*/
const MODULE_NAME = 'Trading Engine'
let thisObject = {
mantain: mantain,
reset: reset,
getNodeById: getNodeById,
cloneValues: cloneValues,
setCurrentCandle: setCurrentCandle,
updateEpisodeCountersAndStatistics: updateEpisodeCountersAndStatistics,
updateDistanceToEventsCounters: updateDistanceToEventsCounters,
setCurrentCycle: setCurrentCycle,
initializeNode: initializeNode,
initialize: initialize,
finalize: finalize
}
@@ -21,15 +23,18 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
return thisObject
function initialize() {
/* Here we will go through all the nodes in the Trading Engine hiriarchy and apply the initial value to the value property when needed */
tradingEngine = bot.simulationState.tradingEngine
sessionParameters = bot.SESSION.parameters
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)
tradingEngine.current.balance.baseAsset.value = sessionParameters.sessionBaseAsset.config.initialBalance
tradingEngine.current.balance.quotedAsset.value = sessionParameters.sessionQuotedAsset.config.initialBalance
}
}
@@ -39,19 +44,51 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
nodesMap = undefined
}
function mantain() {
}
function reset() {
}
function getNodeById(NodeId) {
return nodesMap.get(NodeId)
}
function setCurrentCandle(candle) {
cloneValues(tradingEngine.current.candle, tradingEngine.previous.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
}
tradingEngine.current.candle.begin.value = candle.begin
tradingEngine.current.candle.end.value = candle.end
tradingEngine.current.candle.open.value = candle.open
tradingEngine.current.candle.close.value = candle.close
tradingEngine.current.candle.min.value = candle.min
tradingEngine.current.candle.max.value = candle.max
function setCurrentCycle(cycle) {
tradingEngine.current.episode.cycle.value = cycle
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) {
@@ -62,7 +99,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
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 = bot.APP_SCHEMA_MAP.get(originNode.type)
let nodeDefinition = global.APP_SCHEMA_MAP.get(originNode.type)
if (nodeDefinition === undefined) { return }
if (nodeDefinition.properties !== undefined) {
@@ -95,21 +132,56 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
}
}
function initializeNode(node) {
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
}
}
node.initialize = initializeNode // This will allow anyone to initialize this node and its children.
/* Now we go down through all this node children */
let nodeDefinition = bot.APP_SCHEMA_MAP.get(node.type)
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
if (nodeDefinition === undefined) { return }
if (nodeDefinition.properties !== undefined) {
@@ -140,68 +212,5 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
}
}
}
function updateEpisodeCountersAndStatistics() {
tradingEngine.episode.episodeCounters.periods.value++
tradingEngine.episode.episodeStatistics.days.value = tradingEngine.episode.episodeCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS
}
function updateDistanceToEventsCounters() {
/* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */
if (
tradingEngine.current.distanceToEvent.triggerOn.value > 0
) {
tradingEngine.current.distanceToEvent.triggerOn.value++
}
if (
tradingEngine.current.distanceToEvent.triggerOff.value > 0
) {
tradingEngine.current.distanceToEvent.triggerOff.value++
}
if (
tradingEngine.current.distanceToEvent.takePosition.value > 0
) {
tradingEngine.current.distanceToEvent.takePosition.value++
}
if (
tradingEngine.current.distanceToEvent.closePosition.value > 0
) {
tradingEngine.current.distanceToEvent.closePosition.value++
}
if (
tradingEngine.current.distanceToEvent.nextPhase.value > 0
) {
tradingEngine.current.distanceToEvent.nextPhase.value++
}
if (
tradingEngine.current.distanceToEvent.moveToPhase.value > 0
) {
tradingEngine.current.distanceToEvent.moveToPhase.value++
}
if (
tradingEngine.current.distanceToEvent.createOrder.value > 0
) {
tradingEngine.current.distanceToEvent.createOrder.value++
}
if (
tradingEngine.current.distanceToEvent.cancelOrder.value > 0
) {
tradingEngine.current.distanceToEvent.cancelOrder.value++
}
if (
tradingEngine.current.distanceToEvent.closeOrder.value > 0
) {
tradingEngine.current.distanceToEvent.closeOrder.value++
}
}
}
+317
View File
@@ -0,0 +1,317 @@
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.SESSION.parameters
}
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.
*/
if (bot.FIRST_EXECUTION === true) {
/* 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.begin.value = tradingEngine.current.episode.candle.begin.value
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.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 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++
}
}
}
+61 -544
View File
@@ -1,11 +1,13 @@
exports.newTradingExecution = function newTradingExecution(bot, logger, tradingEngineModule) {
/*
The Trading Execution modules manages the execution of orders against the exchanges.
The Trading Execution modules manages the execution nodes and execution algoritms.
*/
const MODULE_NAME = 'Trading Execution'
let thisObject = {
checkExecution: checkExecution,
mantain: mantain,
reset: reset,
runExecution: runExecution,
initialize: initialize,
finalize: finalize
}
@@ -14,11 +16,8 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE
let tradingSystem
let sessionParameters
const EXCHANGE_API_MODULE = require('./ExchangeAPI.js')
let exchangeAPIModule = EXCHANGE_API_MODULE.newExchangeAPI(bot, logger)
const ANNOUNCEMENTS_MODULE = require('./Announcements.js')
let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger)
const TRADING_ORDERS_MODULE = require('./TradingOrders.js')
let tradingOrdersModule = TRADING_ORDERS_MODULE.newTradingOrders(bot, logger, tradingEngineModule)
return thisObject
@@ -27,8 +26,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE
tradingEngine = bot.simulationState.tradingEngine
sessionParameters = bot.SESSION.parameters
exchangeAPIModule.initialize()
announcementsModule.initialize()
tradingOrdersModule.initialize()
}
function finalize() {
@@ -36,551 +34,70 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE
tradingEngine = undefined
sessionParameters = undefined
exchangeAPIModule.finalize()
exchangeAPIModule = undefined
announcementsModule.finalize()
announcementsModule = undefined
tradingOrdersModule.finalize()
tradingOrdersModule = undefined
}
async function checkExecution(executionNode, stageIsOpening, stageIsClosing, stageSizeLimit, stageOrdersSize, stageFilledSize) {
function mantain() {
tradingOrdersModule.mantain()
}
/* Enforcing Precision Limit */
stageSizeLimit.value = global.PRECISE(stageSizeLimit.value, 10)
stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10)
function reset() {
tradingOrdersModule.reset()
}
await checkExecutionAlgorithms(executionNode)
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) }
async function checkExecutionAlgorithms(executionNode) {
for (let i = 0; i < executionNode.executionAlgorithms.length; i++) {
let executionAlgorithm = executionNode.executionAlgorithms[i]
await checkOrders(executionAlgorithm.marketBuyOrders, executionAlgorithm, executionNode)
await checkOrders(executionAlgorithm.marketSellOrders, executionAlgorithm, executionNode)
await checkOrders(executionAlgorithm.limitBuyOrders, executionAlgorithm, executionNode)
await checkOrders(executionAlgorithm.limitSellOrders, executionAlgorithm, executionNode)
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)
}
}
}
async function checkOrders(orders, executionAlgorithm, executionNode) {
for (let i = 0; i < orders.length; i++) {
function badDefinitionUnhandledException(err, message, node) {
tradingSystem.errors.push([node.id, message])
let tradingSystemOrder = orders[i]
/* Trading System Validations */
if (tradingSystemOrder.config.positionSizePercentage === undefined) { continue }
if (tradingSystemOrder.referenceParent === undefined) { continue }
let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id)
/* Trading Engine Validations */
if (tradingEngineOrder.serialNumber === undefined) { continue }
if (tradingEngineOrder.identifier === undefined) { continue }
if (tradingEngineOrder.exchangeId === undefined) { continue }
if (tradingEngineOrder.begin === undefined) { continue }
if (tradingEngineOrder.end === undefined) { continue }
if (tradingEngineOrder.rate === undefined) { continue }
if (tradingEngineOrder.size === undefined) { continue }
if (tradingEngineOrder.status === undefined) { continue }
if (tradingEngineOrder.algorithmName === undefined) { continue }
if (tradingEngineOrder.orderCounters === undefined) { continue }
if (tradingEngineOrder.orderCounters.periods === undefined) { continue }
if (tradingEngineOrder.orderStatistics.days === undefined) { continue }
if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { continue }
if (tradingEngineOrder.orderStatistics.actualRate === undefined) { continue }
if (tradingEngineOrder.orderStatistics.feesPaid === undefined) { continue }
switch (tradingEngineOrder.status.value) {
case 'Not Open': {
{
/* Check if we need to Create this Order */
if (stageIsClosing === true) { continue }
let situationName = checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode)
if (situationName !== undefined) {
/* Open a new order */
await openOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName)
}
}
break
}
case 'Open': {
tradingEngineOrder.end.value = tradingEngine.current.candle.end.value
tradingEngineOrder.orderCounters.periods.value++
tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS
/* Simulate Events that happens at the Exchange, if needed. */
simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder)
/* Check Events that happens at the Exchange, if needed. */
await checkExchangeEvents(tradingSystemOrder, tradingEngineOrder)
/* If the stage is closing or the order is not still Open, we wont be cancelling orders based on defined events */
if (stageIsClosing !== true && tradingEngineOrder.status.value === 'Open') {
/* Check if we need to Cancel this Order */
let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode)
if (situationName !== undefined) {
simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event')
await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Cancel Event')
}
}
}
}
}
}
async function openOrder(executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) {
/* Order Size Calculation */
tradingEngineOrder.size.value = tradingSystem.formulas.get(executionAlgorithm.positionSize.formula.id) * tradingSystemOrder.config.positionSizePercentage / 100
if (stageOrdersSize.value + tradingEngineOrder.size.value > stageSizeLimit.value) {
/* We reduce the size to the remaining size of the position. */
tradingEngineOrder.size.value = stageSizeLimit.value - stageOrdersSize.value
tradingEngineOrder.size.value = global.PRECISE(tradingEngineOrder.size.value, 10)
}
/* Check Order Size */
if (tradingEngineOrder.size.value <= 0) { return }
/* Order Rate Calculation */
tradingEngineOrder.rate.value = tradingEngine.current.position.rate.value // By default this is the order rate.
if (tradingSystemOrder.positionRate !== undefined) {
if (tradingSystemOrder.positionRate.formula !== undefined) {
tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.positionRate.formula.id)
tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10)
}
}
/* Place Order at the Exchange */
let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder)
if (result !== true) { return }
/* Updating Episode Counters */
tradingEngine.episode.episodeCounters.orders.value++
/* Initialize this */
tradingEngine.current.distanceToEvent.createOrder.value = 1
/* Create Order Procedure */
tradingEngineOrder.status.value = 'Open'
tradingEngineOrder.identifier.value = global.UNIQUE_ID()
tradingEngineOrder.begin.value = tradingEngine.current.candle.begin.value
tradingEngineOrder.end.value = tradingEngine.current.candle.end.value
tradingEngineOrder.serialNumber.value = tradingEngine.episode.episodeCounters.orders.value
tradingEngineOrder.orderName.value = tradingSystemOrder.name
tradingEngineOrder.algorithmName.value = executionAlgorithm.name
tradingEngineOrder.situationName.value = situationName
/* Update Stage Orders Size */
stageOrdersSize.value = stageOrdersSize.value + tradingEngineOrder.size.value
stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10)
}
async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
return true
}
case 'Live Trading Session': {
break
}
case 'Fordward Testing Session': {
break
}
case 'Paper Trading Session': {
return true
}
}
let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder)
if (orderId !== undefined) {
tradingEngineOrder.exchangeId.value = orderId
return true
}
}
function simulateExchangeEvents(tradingSystemOrder, tradingEngineOrder) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
break
}
case 'Live Trading Session': {
return
}
case 'Fordward Testing Session': {
return
}
case 'Paper Trading Session': {
break
}
}
/* Filter by what is defined at the Strategy */
if (tradingSystemOrder.simulatedExchangeEvents === undefined) { return }
let previousSizeFilled = tradingEngineOrder.orderStatistics.sizeFilled.value
let previousPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value
let previousAmountReceived = tradingEngineOrder.orderStatistics.amountReceived.value
let previousFeesPaid = tradingEngineOrder.orderStatistics.feesPaid.value
actualRateSimulation()
feesPaidSimulation()
orderFillingSimulation()
sizeFilledSimulation()
amountReceivedSimulation()
doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid)
/* If the Stage is Closing and this order is still open, we need to cancel it now */
if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') {
simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage')
}
function actualRateSimulation() {
/* Actual Rate Simulation */
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) {
tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id)
}
}
} else {
switch (tradingEngineOrder.type) {
case 'Market Order': {
/* Actual Rate is simulated based on the Session Paremeters */
let slippageAmount = tradingEngineOrder.rate.value * bot.SESSION.parameters.slippage.config.positionRate / 100
switch (tradingSystemOrder.type) {
case 'Market Sell Order': {
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount
break
}
case 'Market Buy Order': {
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount
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
}
}
}
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
}
function feesPaidSimulation() {
/* Fees Paid Simulation */
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
if (tradingEngineOrder.orderStatistics.feesPaid.value === tradingEngineOrder.orderStatistics.feesPaid.config.initialValue) {
tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100
}
}
} else {
/* Fees are simulated based on the Session Paremeters */
switch (tradingEngineOrder.type) {
case 'Market Order': {
tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * bot.SESSION.parameters.feeStructure.config.taker / 100
break
}
case 'Limit Order': {
tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value * bot.SESSION.parameters.feeStructure.config.maker / 100
break
}
}
}
tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10)
}
function orderFillingSimulation() {
/* Order Filling Simulation */
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)
/* Check if we need to close */
if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = 'Filled'
/* Initialize this */
tradingEngine.current.distanceToEvent.closeOrder.value = 1
}
}
}
}
function sizeFilledSimulation() {
/* Size Filled */
tradingEngineOrder.orderStatistics.sizeFilled.value = tradingEngineOrder.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderStatistics.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.sizeFilled.value, 10)
}
function amountReceivedSimulation() {
/* Amount Received */
tradingEngineOrder.orderStatistics.amountReceived.value = tradingEngineOrder.orderStatistics.sizeFilled.value * tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate
tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10)
}
}
async function checkExchangeEvents(tradingSystemOrder, tradingEngineOrder) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
return true
}
case 'Live Trading Session': {
break
}
case 'Fordward Testing Session': {
break
}
case 'Paper Trading Session': {
return true
}
}
let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder)
if (order === undefined) { return }
const AT_EXCHANGE_STATUS = {
OPEN: 'open',
CLOSED: 'closed',
CANCELLED: 'canceled'
}
/* Status Checks */
if (order.remaining === 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = 'Filled'
/* Initialize this */
tradingEngine.current.distanceToEvent.closeOrder.value = 1
}
if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = 'Closed at the Exchange'
/* Initialize this */
tradingEngine.current.distanceToEvent.closeOrder.value = 1
}
if (order.status === AT_EXCHANGE_STATUS.CANCELLED) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = 'Cancelled at the Exchange'
/* Initialize this */
tradingEngine.current.distanceToEvent.closeOrder.value = 1
}
syncWithExchange(tradingSystemOrder, tradingEngineOrder, order)
/* Forced Cancellation Check */
if (stageIsClosing === true && tradingEngineOrder.status.value !== 'Closed') {
await exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, 'Closing Stage')
}
}
function syncWithExchange(tradingSystemOrder, tradingEngineOrder, order) {
let previousSizeFilled = tradingEngineOrder.orderStatistics.sizeFilled.value
let previousPercentageFilled = tradingEngineOrder.orderStatistics.percentageFilled.value
let previousAmountReceived = tradingEngineOrder.orderStatistics.amountReceived.value
let previousFeesPaid = tradingEngineOrder.orderStatistics.feesPaid.value
/* Fees Paid */
tradingEngineOrder.orderStatistics.feesPaid.value = tradingEngineOrder.size.value - order.amount
tradingEngineOrder.orderStatistics.feesPaid.value = global.PRECISE(tradingEngineOrder.orderStatistics.feesPaid.value, 10)
/* Percentage Filled */
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining)
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
/* Size Filled */
tradingEngineOrder.orderStatistics.sizeFilled.value = order.filled
tradingEngineOrder.orderStatistics.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.sizeFilled.value, 10)
/* Amount Received */
tradingEngineOrder.orderStatistics.amountReceived.value = order.cost
tradingEngineOrder.orderStatistics.amountReceived.value = global.PRECISE(tradingEngineOrder.orderStatistics.amountReceived.value, 10)
/* Actual Rate */
tradingEngineOrder.orderStatistics.actualRate.value = order.price
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid)
}
function doTheAccounting(tradingSystemOrder, tradingEngineOrder, previousSizeFilled, previousPercentageFilled, previousAmountReceived, previousFeesPaid) {
/* Unaccount for the previous Stage Filled Size */
stageFilledSize.value = stageFilledSize.value - tradingEngineOrder.size.value * previousPercentageFilled / 100
/* Account for the current Stage Filled Size */
stageFilledSize.value = stageFilledSize.value + tradingEngineOrder.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
stageFilledSize.value = global.PRECISE(stageFilledSize.value, 10)
/* Balances Update */
switch (true) {
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
/* Undo the previous accounting */
tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - previousFeesPaid - previousSizeFilled
/* Account the current filling and fees */
tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngineOrder.orderStatistics.sizeFilled.value + tradingEngineOrder.orderStatistics.feesPaid.value
/* Undo the previous accounting */
tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value + previousAmountReceived
/* Account the current filling and fees */
tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngineOrder.orderStatistics.amountReceived.value
break
}
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
/* Undo the previous accounting */
tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value + previousFeesPaid + previousSizeFilled
/* Account the current filling and fees */
tradingEngine.current.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value - tradingEngineOrder.orderStatistics.sizeFilled.value - tradingEngineOrder.orderStatistics.feesPaid.value
/* Undo the previous accounting */
tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value - previousAmountReceived
/* Account the current filling and fees */
tradingEngine.current.balance.quotedAsset.value = tradingEngine.current.balance.baseAsset.value + tradingEngineOrder.orderStatistics.amountReceived.value
break
}
}
}
function simulateCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
break
}
case 'Live Trading Session': {
return
}
case 'Fordward Testing Session': {
return
}
case 'Paper Trading Session': {
break
}
}
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = exitType
/* Initialize this */
tradingEngine.current.distanceToEvent.closeOrder.value = 1
/* Since the order was cancelled, we remove the unfilled amount of the order from here */
let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value
let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100
stageOrdersSize.value = stageOrdersSize.value - unfilledSize
stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10)
}
async function exchangeCancelOrder(tradingSystemOrder, tradingEngineOrder, exitType) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
return
}
case 'Live Trading Session': {
break
}
case 'Fordward Testing Session': {
break
}
case 'Paper Trading Session': {
return
}
}
/* Check if we can cancel the order at the Exchange. */
let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder)
if (result === true) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = exitType
/* Initialize this */
tradingEngine.current.distanceToEvent.closeOrder.value = 1
/* Since the order was cancelled, we remove the unfilled amount of the order from here */
let unfilledPercentage = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value
let unfilledSize = tradingEngineOrder.size.value * unfilledPercentage / 100
stageOrdersSize.value = stageOrdersSize.value - unfilledSize
stageOrdersSize.value = global.PRECISE(stageOrdersSize.value, 10)
/*
Perhaps the order was filled a bit more between the last time we checked and when it was cancelled.
To sync our accounting, we need to check the order one last time and if it changed, fix it.
*/
let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder)
if (order === undefined) { return }
syncWithExchange(tradingSystemOrder, tradingEngineOrder, order)
}
}
function checkOrderEvent(event, order, executionAlgorithm, executionNode) {
if (event !== undefined) {
for (let k = 0; k < event.situations.length; k++) {
let situation = event.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(event.id)
tradingSystem.highlights.push(order.id)
tradingSystem.highlights.push(executionAlgorithm.id)
tradingSystem.highlights.push(executionNode.id)
announcementsModule.makeAnnoucements(event)
return situation.name // if the event is triggered, we return the name of the situation that passed
}
}
}
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
+79 -122
View File
@@ -1,9 +1,8 @@
exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE) {
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 FULL_LOG = true
const MODULE_NAME = 'Trading Bot'
let thisObject = {
@@ -24,21 +23,13 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
commons = undefined
}
function initialize(callBackFunction) {
try {
logger.fileName = MODULE_NAME
logger.initialize()
function initialize() {
callBackFunction(global.DEFAULT_OK_RESPONSE)
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] initialize -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
}
}
function start(chart, timeFrame, timeFrameLabel, currentDay, callBackFunction) {
async function start(chart, timeFrame, timeFrameLabel, tradingProcessDate) {
try {
bot.processingDailyFiles
if (timeFrame > global.dailyFilePeriods[0][0]) {
bot.processingDailyFiles = false
} else {
@@ -47,37 +38,59 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
/* Preparing everything for the Simulation */
const TRADING_SIMULATION = require('./TradingSimulation.js')
let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES)
let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES)
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
let market = bot.market
let totalFilesToBeRead = 0
let totalFilesRead = 0
let totalFilesToBeWritten = 0
let totalFilesWritten = 0
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
let outputDatasetsMap = new Map()
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
/*
Here is where the Trading Engine and Trading Systems received are moved to the simulation state.
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.
*/
bot.simulationState.tradingEngine = bot.TRADING_ENGINE
bot.simulationState.tradingSystem = bot.TRADING_SYSTEM
initializeOutputs()
} else {
readFiles()
}
return
function initializeOutputs() {
if (bot.processingDailyFiles) {
initializeDailyFiles()
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
await initializeOutputs()
} else {
initializeMarketFiles()
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()
}
}
function initializeDailyFiles() {
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
@@ -86,10 +99,9 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
outputDatasetsMap.set(dataset.parentNode.config.codeName, [])
}
}
runSimulation()
}
function initializeMarketFiles() {
async function initializeMarketFiles() {
for (let i = 0; i < outputDatasets.length; i++) {
let outputDatasetNode = outputDatasets[i]
let dataset = outputDatasetNode.referenceParent
@@ -98,23 +110,22 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
outputDatasetsMap.set(dataset.parentNode.config.codeName, [])
}
}
runSimulation()
}
function readFiles() {
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) {
readDailyFiles()
await readDailyFiles()
} else {
readMarketFiles()
await readMarketFiles()
}
}
function readMarketFiles() {
async function readMarketFiles() {
for (let i = 0; i < outputDatasets.length; i++) {
let outputDatasetNode = outputDatasets[i]
let dataset = outputDatasetNode.referenceParent
@@ -124,86 +135,54 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
let fileName = 'Data.json'
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
}
}
}
function readDailyFiles() {
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 = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2);
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.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
}
}
}
function readOutputFile(fileName, filePath, productName) {
totalFilesToBeRead++
async function readOutputFile(fileName, filePath, productName) {
filePath += '/' + fileName
fileStorage.getTextFile(filePath, onFileRead, true)
function onFileRead(err, text) {
try {
let response = await fileStorage.asyncGetTextFile(filePath, true)
if (err.message === 'File does not exist.') {
outputDatasetsMap.set(productName, [])
anotherFileRead()
return
}
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath)
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset)
callBackFunction(err)
return
}
outputDatasetsMap.set(productName, JSON.parse(text))
anotherFileRead()
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
}
if (response.err.message === 'File does not exist.') {
outputDatasetsMap.set(productName, [])
return
}
}
function anotherFileRead() {
totalFilesRead++
if (totalFilesRead === totalFilesToBeRead) {
runSimulation()
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
throw(response.err)
}
outputDatasetsMap.set(productName, JSON.parse(response.text))
}
function runSimulation() {
tradingSimulation.runSimulation(
chart,
outputDatasetsMap,
writeFiles,
callBackFunction)
}
function writeFiles() {
async function writeFiles() {
/*
The output of files which were appended with information during the simulation execution, now needs to be saved.
*/
if (bot.processingDailyFiles) {
writeDailyFiles()
await writeDailyFiles()
} else {
writeMarketFiles()
await writeMarketFiles()
}
}
function writeMarketFiles() {
async function writeMarketFiles() {
for (let i = 0; i < outputDatasets.length; i++) {
let outputDatasetNode = outputDatasets[i]
let dataset = outputDatasetNode.referenceParent
@@ -213,63 +192,41 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
let fileName = 'Data.json'
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
}
}
}
function writeDailyFiles() {
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 = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2);
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.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
}
}
}
function writeOutputFile(fileName, filePath, productName) {
totalFilesToBeWritten++
async function writeOutputFile(fileName, filePath, productName) {
filePath += '/' + fileName
let fileContent = JSON.stringify(outputDatasetsMap.get(productName))
fileStorage.createTextFile(filePath, fileContent, onFileCreated)
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent)
function onFileCreated(err) {
try {
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> filePath = ' + filePath)
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> market = ' + market.baseAsset + '_' + market.quotedAsset)
callBackFunction(err)
return
}
anotherFileWritten()
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] start -> readMarketFiles -> readOutputFile -> onFileRead -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
}
}
}
function anotherFileWritten() {
totalFilesWritten++
if (totalFilesWritten === totalFilesToBeWritten) {
callBackFunction(global.DEFAULT_OK_RESPONSE)
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
throw(response.err)
}
}
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] start -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
throw(global.DEFAULT_FAIL_RESPONSE)
}
}
}
+282 -101
View File
@@ -4,55 +4,76 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
*/
const MODULE_NAME = 'Trading Position'
let thisObject = {
mantain: mantain,
reset: reset,
cycleBasedStatistics: cycleBasedStatistics,
openPosition: openPosition,
closingPosition: closingPosition,
closePosition: closePosition,
applyStopLossFormula: applyStopLossFormula,
applyTakeProfitFormula: applyTakeProfitFormula,
preventStopLossDistortion: preventStopLossDistortion,
applySlippageToStopLoss: applySlippageToStopLoss,
preventTakeProfitDistortion: preventTakeProfitDistortion,
applySlippageToTakeProfit: applySlippageToTakeProfit,
updateStopLoss: updateStopLoss,
updateTakeProfit: updateTakeProfit,
updateSizeAndRate: updateSizeAndRate,
initialTargets: initialTargets,
updateEnds: updateEnds,
updateStatus: updateStatus,
resetTradingEngineDataStructure: resetTradingEngineDataStructure,
updateCounters: updateCounters,
updateStatistics: updateStatistics,
resetPosition: resetPosition,
initialize: initialize,
finalize: finalize
}
let tradingEngine
let tradingSystem
let sessionParameters
return thisObject
function initialize() {
tradingSystem = bot.simulationState.tradingSystem
tradingEngine = bot.simulationState.tradingEngine
sessionParameters = bot.SESSION.parameters
}
function finalize() {
tradingEngine = undefined
tradingSystem = undefined
sessionParameters = undefined
}
function openPosition(situationName) {
tradingEngine.current.position.status.value = 'Open'
tradingEngine.current.position.serialNumber.value = tradingEngine.episode.episodeCounters.positions.value
tradingEngine.current.position.identifier.value = global.UNIQUE_ID()
tradingEngine.current.position.begin.value = tradingEngine.current.candle.begin.value
tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value
function mantain() {
updateCounters()
updateEnds()
}
function reset() {
resetTradingEngineDataStructure()
}
function openPosition(situationName) {
/* 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.begin.value = tradingEngine.current.episode.candle.end.value
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
tradingEngine.current.position.stopLoss.stopLossStage.value = 'Open Stage'
tradingEngine.current.position.takeProfit.takeProfitStage.value = 'Open Stage'
tradingEngine.current.position.stopLoss.stopLossPhase.value = 0
tradingEngine.current.position.takeProfit.takeProfitPhase.value = 0
/* 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) {
@@ -62,15 +83,31 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
function closePosition() {
tradingEngine.current.position.status.value = 'Closed'
tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value
tradingEngine.current.position.end.value = tradingEngine.current.episode.candle.end.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)
}
function resetPosition() {
tradingEngine.current.position.initialize(tradingEngine.current.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) {
@@ -81,97 +118,151 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
tradingEngine.current.position.takeProfit.value = formulas.get(formulaId)
}
function updateStopLoss(phase, stage) {
function updateStopLoss(phase) {
tradingEngine.current.position.stopLoss.stopLossPhase.value = phase
tradingEngine.current.position.stopLoss.stopLossStage.value = stage
}
function updateTakeProfit(phase, stage) {
function updateTakeProfit(phase) {
tradingEngine.current.position.takeProfit.takeProfitPhase.value = phase
tradingEngine.current.position.takeProfit.takeProfitStage.value = stage
}
function updateSizeAndRate(size, rate) {
tradingEngine.current.position.size.value = global.PRECISE(size, 10)
tradingEngine.current.position.rate.value = global.PRECISE(rate, 10)
function initialTargets(tradingSystemStageNode, tradingEngineStageNode) {
tradingEngine.current.position.beginRate.value = tradingEngine.current.position.rate.value
}
function preventStopLossDistortion() {
/*
Hit Point Validation
This prevents misscalculations when a formula places the stop loss in this case way beyond the market price.
If we take the stop loss value at those situation would be a huge distortion of facts.
*/
if (bot.sessionAndMarketBaseAssetsAreEqual) {
if (tradingEngine.current.position.stopLoss.value < tradingEngine.current.candle.min.value) {
tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.min.value
}
} else {
if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.candle.max.value) {
tradingEngine.current.position.stopLoss.value = tradingEngine.current.candle.max.value
}
}
}
function applySlippageToStopLoss() {
let slippedStopLoss = tradingEngine.current.position.stopLoss.value
/* Apply the Slippage */
let slippageAmount = slippedStopLoss * bot.SESSION.parameters.slippage.config.stopLoss / 100
if (bot.sessionAndMarketBaseAssetsAreEqual) {
slippedStopLoss = slippedStopLoss + slippageAmount
} else {
slippedStopLoss = slippedStopLoss - slippageAmount
if (tradingSystemStageNode.initialTargets === undefined) {
const message = 'Stage without Initial Targets node. Add one please.'
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode)
}
tradingEngine.current.position.endRate.value = slippedStopLoss
}
setTargetRate()
setTargetSize()
function preventTakeProfitDistortion() {
/*
Hit Point Validation:
This prevents misscalculations when a formula places the take profit in this case way beyond the market price.
If we take the stop loss value at those situation would be a huge distortion of facts.
*/
if (bot.sessionAndMarketBaseAssetsAreEqual) {
if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.candle.max.value) {
tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.max.value
function setTargetRate() {
if (tradingSystemStageNode.initialTargets.targetRate === undefined) {
const message = 'Target Rate Node not Found. Fix this please.'
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets)
}
} else {
if (tradingEngine.current.position.takeProfit.value < tradingEngine.current.candle.min.value) {
tradingEngine.current.position.takeProfit.value = tradingEngine.current.candle.min.value
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 applySlippageToTakeProfit() {
let slippedTakeProfit = tradingEngine.current.position.takeProfit.value
/* Apply the Slippage */
let slippageAmount = slippedTakeProfit * bot.SESSION.parameters.slippage.config.takeProfit / 100
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)
}
if (bot.sessionAndMarketBaseAssetsAreEqual) {
slippedTakeProfit = slippedTakeProfit + slippageAmount
} else {
slippedTakeProfit = slippedTakeProfit - slippageAmount
/* 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 === undefined) {
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 === undefined) {
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 errorText = 'You need to specify a Formula for this.'
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
}
}
}
tradingEngine.current.position.endRate.value = slippedTakeProfit
}
function updateEnds() {
if (tradingEngine.current.position.status.value === 'Open') {
tradingEngine.current.position.end.value = tradingEngine.current.candle.end.value
tradingEngine.current.position.end.value = tradingEngine.current.episode.candle.end.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 updateStatus() {
function resetTradingEngineDataStructure() {
if (tradingEngine.current.position.status.value === 'Closed') {
resetPosition()
tradingEngineModule.initializeNode(tradingEngine.current.position)
}
}
@@ -181,19 +272,109 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
}
}
function updateStatistics() {
if (bot.sessionAndMarketBaseAssetsAreEqual) {
tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.baseAsset.value - tradingEngine.previous.balance.baseAsset.value
tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.value
} else {
tradingEngine.current.position.positionStatistics.profitLoss.value = tradingEngine.current.balance.quotedAsset.value - tradingEngine.previous.balance.quotedAsset.value
tradingEngine.current.position.positionStatistics.ROI.value = tradingEngine.current.position.positionStatistics.profitLoss.value * 100 / tradingEngine.current.position.size.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'
}
}
tradingEngine.current.position.positionStatistics.days.value = tradingEngine.current.position.positionCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS
if (tradingEngine.current.position.positionStatistics.ROI.value > 0) {
tradingEngine.current.position.positionStatistics.hitFail.value = 'HIT'
} else {
tradingEngine.current.position.positionStatistics.hitFail.value = 'FAIL'
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.'
}
}
File diff suppressed because it is too large. Load diff
+102 -30
View File
@@ -37,7 +37,8 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
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 = bot.processNode.referenceParent.processOutput.outputDatasets
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
@@ -53,9 +54,17 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
}
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)
//pruneOutputFile(product, outputDatasetArray, tradingEngine.current.episode.candle.end.value)
/* Clean Open Records */
if (product.config.saveAsObjects === true) {
@@ -64,6 +73,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) {
/* Remove Open Records */
spliceOpenRecords(j, product, outputDatasetArray)
break
}
}
}
@@ -71,7 +81,12 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
/*
The product root can be a node or a node property of type array.
*/
let productRoot = eval(product.config.nodePath)
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') {
/*
@@ -84,7 +99,14 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
*/
let productRootNode = productRoot[index]
let record = scanRecordDefinition(product, productRootNode, index)
persistIndividualRecord(record, product, outputDatasetArray)
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 {
/*
@@ -96,7 +118,9 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
*/
let productRootNode = productRoot
let record = scanRecordDefinition(product, productRootNode)
persistIndividualRecord(record, product, outputDatasetArray)
if (record !== undefined) {
persistIndividualRecord(record, product, outputDatasetArray)
}
}
}
}
@@ -122,10 +146,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
try {
propertyRootNode = eval(recordProperty.config.nodePath)
} catch (err) {
logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.name = " + recordProperty.name);
logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> recordProperty.config.nodePath = " + recordProperty.config.nodePath);
logger.write(MODULE_NAME, "[ERROR] appendRecords -> scanRecordDefinition -> Error at Record Property Definition -> err.stack = " + err.stack);
throw 'Can not continue with a Definition Error like this.'
badDefinitionUnhandledException(err, 'Error Evaluation Record Property nodePath -> nodePath = ' + recordProperty.config.nodePath, product, recordProperty)
}
}
/*
@@ -133,7 +154,12 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
We will use the codeName of the Record Property to match it with
the any of the properties of the Root Node to get the Target Node.
*/
let targetNode = propertyRootNode[recordProperty.config.codeName]
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,
@@ -149,7 +175,11 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
we use the Index value we find at the configuration of the Record Property.
*/
if (recordProperty.config.index !== undefined) {
targetNode = targetNode[recordProperty.config.index]
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.
@@ -167,7 +197,11 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
value = targetNode.value
if (recordProperty.config.decimals !== undefined) {
value = Number(value.toFixed(recordProperty.config.decimals))
try {
value = Number(value.toFixed(recordProperty.config.decimals))
} catch (err) {
badDefinitionUnhandledException(err, 'Error applying configured decimals.', product, recordProperty)
}
}
} else {
/*
@@ -177,6 +211,13 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
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)
}
@@ -198,27 +239,36 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
let propertyValue = record[j]
if (bot.processingDailyFiles) {
/*
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.candle.end.value)
let nextDay = new Date(tradingEngine.current.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 (product.config.doNotCutObjectInDays !== true) {
/*
By default we will cut objects in days.
*/
if (propertyValue === product.config.propertyValueThatClosesObject) {
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
outputDatasetArray.push(record)
}
} else {
/*
When we are not at the end of the day, we will save the object normally, like in market files.
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.
*/
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
outputDatasetArray.push(record)
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)
}
}
}
}
@@ -273,7 +323,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
return value
}
function pruneOutputFile(product, outputFile) {
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.
@@ -287,9 +337,14 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
let recordProperty = product.record.properties[j]
if (recordProperty.config.codeName === 'end') {
let end = record[j]
if (end >= tradingEngine.current.candle.end.value) {
if (end >= currentEnd) {
outputFile.splice(i, 1)
pruneOutputFile(product, outputFile)
/*
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
}
}
@@ -318,4 +373,21 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
}
}
}
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.'
}
}
+334 -190
View File
@@ -1,4 +1,4 @@
exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) {
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.
@@ -19,11 +19,11 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
thisObject = undefined
}
function runSimulation(
async function runSimulation(
chart,
outputDatasetsMap,
callback,
callBackFunction) {
writeFiles,
) {
try {
let tradingSystem = bot.simulationState.tradingSystem
@@ -33,19 +33,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
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) }
/* Stop Loss Management TODO */
const MIN_STOP_LOSS_VALUE = 0.0000000001 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result.
const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER
/* Take Profit Management TODO */
const MIN_TAKE_PROFIT_VALUE = 0.0000000001 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result.
const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER
/* These are the Modules we will need to run the Simulation */
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
tradingEngineModule.initialize()
const TRADING_RECORDS_MODULE = require('./TradingRecords.js')
let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger)
tradingRecordsModule.initialize(outputDatasetsMap)
@@ -54,6 +42,10 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
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
@@ -61,163 +53,261 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
/* Variables needed for heartbeat functionality */
let heartBeatDate
let previousHeartBeatDate
let firstLoopExecution = true
/*
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
/* This determines where we are standing */
bot.sessionAndMarketBaseAssetsAreEqual = (sessionParameters.sessionBaseAsset.referenceParent.referenceParent.config.codeName === bot.market.baseAsset)
initializeLoop()
function initializeLoop() {
if (bot.FIRST_EXECUTION === false && tradingEngine.last.candle !== undefined) {
/* Estimate the Initial Candle based on the last candle saved at tradingEngine */
let firstBegin = candles[0].begin
let lastBegin = tradingEngine.last.candle.begin
let diff = lastBegin - firstBegin
let amount = diff / sessionParameters.timeFrame.config.value
tradingEngine.current.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle
if (tradingEngine.current.candle.index.value < 0 || tradingEngine.current.candle.index.value > candles.length - 1) {
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.')
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.candle.index.value = ' + tradingEngine.current.candle.index.value)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
return
}
} else {
/* 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
tradingEngine.current.candle.index.value = Math.trunc(amount)
if (tradingEngine.current.candle.index.value < 0) { tradingEngine.current.candle.index.value = 0 }
if (tradingEngine.current.candle.index.value > 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.
*/
tradingEngine.current.candle.index.value = candles.length - 1
}
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.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 {
loop()
/*
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
async function loop() {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.candle.index.value) }
let candle = candles[tradingEngine.current.candle.index.value] // This is the current candle the Simulation is working at.
/*
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.
if (checkIfWeAreAtTheHeadOfTheMarket() === false) {
afterLoop()
return
}
if (checkMinimunAndMaximunBalance() === false) {
afterLoop()
return
}
if (checkInitialAndFinalDatetime() === false) {
controlLoop()
return
}
/* We emit a heart beat so that the UI can now where we are at the overal process.*/
heartBeat()
if (firstLoopExecution === true) {
tradingEpisodeModule.openEpisode()
firstLoopExecution = false
}
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()
tradingSystemModule.reset(chart)
tradingSystemModule.mantainPositions()
tradingSystemModule.mantainStrategies()
tradingSystemModule.mantainOrders()
tradingEngineModule.updateEpisodeCountersAndStatistics()
tradingEngineModule.updateDistanceToEventsCounters()
/* The chart was recalculated based on the current candle. */
tradingSystemModule.updateChart(chart)
/* Run the Trigger Stage */
tradingSystemModule.triggerStage()
/*
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 Open Stage */
await tradingSystemModule.openStage()
/*
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()
/* Run the Manage Stage */
tradingSystemModule.manageStage()
/* Run the Close Stage */
await tradingSystemModule.closeStage()
/*
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()
controlLoop()
}
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()
function controlLoop() {
/* Checking if we should continue processing this loop or not. */
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.') }
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.')
afterLoop()
return
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()
}
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.') }
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.')
afterLoop()
return
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.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.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
}
}
tradingEngine.current.candle.index.value++
if (tradingEngine.current.candle.index.value < candles.length) {
setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed.
} else {
afterLoop()
function checkIfWeNeedToStopAfterBothCycles() {
if (checkNextCandle() === false) {
updateEpisode('All Candles Processed')
breakLoop = true
return
}
}
}
function afterLoop() {
tradingEngineModule.finalize()
tradingSystemModule.finalize()
tradingRecordsModule.finalize()
tradingSystemModule.finalize()
tradingRecordsModule.finalize()
tradingEpisodeModule.finalize()
tradingEngineModule = undefined
tradingSystemModule = undefined
tradingRecordsModule = undefined
tradingSystemModule = undefined
tradingRecordsModule = undefined
tradingEpisodeModule = undefined
callback()
await writeFiles()
function closeEpisode(exitType) {
tradingEpisodeModule.updateExitType(exitType)
tradingEpisodeModule.closeEpisode()
}
function updateEpisode(exitType) {
tradingEpisodeModule.updateExitType(exitType)
}
function heartBeat() {
/* We will produce a simulation level heartbeat in order to inform the user this is running. */
heartBeatDate = new Date(Math.trunc(tradingEngine.current.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS)
if (heartBeatDate.valueOf() !== previousHeartBeatDate) {
let processingDate = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2)
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. */
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.candle.index.value + ' @ ' + processingDate) }
heartBeatDate = new Date(Math.trunc(tradingEngine.current.episode.candle.begin.value / global.ONE_DAY_IN_MILISECONDS) * global.ONE_DAY_IN_MILISECONDS)
/* Telling the world we are alive and doing well */
let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime)
let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime)
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)
bot.processHeartBeat(currentDateString, percentage)
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)
if (global.areEqualDates(currentDate, new Date()) === false) {
logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage)
/*
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.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)
}
}
}
previousHeartBeatDate = heartBeatDate.valueOf()
}
function positionChartAtCurrentCandle() {
@@ -226,7 +316,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
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 = bot.processNode.referenceParent.processDependencies.dataDependencies
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) {
@@ -240,8 +331,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName)
thisChart[singularVariableName] = currentElement
if (thisChart[pluralVariableName] !== undefined) {
let currentElement = getElement(thisChart[pluralVariableName], 'Daily' + '-' + mapKey + '-' + pluralVariableName)
if (currentElement !== undefined) {
thisChart[singularVariableName] = currentElement
}
}
}
}
}
@@ -257,8 +352,12 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName)
thisChart[singularVariableName] = currentElement
if (thisChart[pluralVariableName] !== undefined) {
let currentElement = getElement(thisChart[pluralVariableName], 'Market' + '-' + mapKey + '-' + pluralVariableName)
if (currentElement !== undefined) {
thisChart[singularVariableName] = currentElement
}
}
}
}
@@ -287,15 +386,15 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
for (let i = 0; i < pArray.length; i++) {
element = pArray[i]
if (tradingEngine.current.candle.end.value === element.end) { // when there is an exact match at the end we take that element
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.candle.end.value
element.end > tradingEngine.current.episode.candle.end.value
) {
let previousElement = pArray[i - 1]
if (previousElement.end < tradingEngine.current.candle.end.value) {
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
@@ -307,43 +406,75 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
throw (global.DEFAULT_FAIL_RESPONSE)
}
}
function checkIfWeAreAtTheHeadOfTheMarket() {
function checkNextCandle() {
/*
We skip the candle at the head of the market because it has not closed yet. The procedure to determine if we are at the head of the market is different
when we are processing Market Files than when we are processing Daily Files. TODO Check that the procedure is good for Beta 6
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 (bot.processingDailyFiles) { // We are processing Daily Files
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 (tradingEngine.current.candle.index.value === candles.length - 1) {
if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') }
return false
/*
Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators.
if we skip these two periods, then there will be a hole in the file since the last period will be missing.
*/
}
}
} else { // We are processing Market Files
if (tradingEngine.current.candle.index.value === candles.length - 1) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') }
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 checkInitialAndFinalDatetime() {
/* Here we check that the current candle is not outside of user-defined time range at the session parameters.*/
if (tradingEngine.current.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
}
if (tradingEngine.current.candle.begin.value > sessionParameters.timeRange.config.finalDatetime) {
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
}
@@ -355,42 +486,55 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
if (
tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue
) {
/*
First thing to do is to check if we are below the minimum balance, or above the maximun balance.
If we are, we are not gooing to trigger on again.
/*
We will perform this check only when we are not inside a position,
because there the balances have shifted from their resting position.
*/
let minimumBalance
let maximumBalance
let balance
if (bot.sessionAndMarketBaseAssetsAreEqual) {
balance = tradingEngine.current.balance.baseAsset.value
minimumBalance = sessionParameters.sessionBaseAsset.config.minimumBalance
maximumBalance = sessionParameters.sessionBaseAsset.config.maximumBalance
} else {
balance = tradingEngine.current.balance.quotedAsset.value
minimumBalance = sessionParameters.sessionQuotedAsset.config.minimumBalance
maximumBalance = sessionParameters.sessionQuotedAsset.config.maximumBalance
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
}
}
let stopRunningDate = new Date(tradingEngine.current.candle.begin.value)
if (balance <= minimumBalance) {
tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString()
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) }
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 (balance >= maximumBalance) {
tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString()
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) }
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)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
throw (global.DEFAULT_FAIL_RESPONSE)
}
}
}
File diff suppressed because it is too large. Load diff
+24 -41
View File
@@ -4,13 +4,10 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
*/
const MODULE_NAME = 'Trading Strategy'
let thisObject = {
mantain: mantain,
reset: reset,
openStrategy: openStrategy,
closeStrategy: closeStrategy,
updateEnds: updateEnds,
updateStageStatus: updateStageStatus,
updateStatus: updateStatus,
updateCounters: updateCounters,
resetStrategy: resetStrategy,
initialize: initialize,
finalize: finalize
}
@@ -27,66 +24,52 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
tradingEngine = undefined
}
function mantain() {
updateCounters()
updateEnds()
}
function reset() {
resetTradingEngineDataStructure()
}
function openStrategy(index, situationName, strategyName) {
tradingEngine.current.strategy.status.value = 'Open'
tradingEngine.current.strategy.serialNumber.value = tradingEngine.episode.episodeCounters.strategies.value
tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value
tradingEngine.current.strategy.identifier.value = global.UNIQUE_ID()
tradingEngine.current.strategy.begin.value = tradingEngine.current.candle.begin.value
tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value
tradingEngine.current.strategy.beginRate.value = tradingEngine.current.candle.min.value
tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value
tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.candle.end.value
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value
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.candle.end.value
tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.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 resetStrategy() {
tradingEngine.current.strategy.initialize(tradingEngine.current.strategy)
}
function updateEnds() {
if (tradingEngine.current.strategy.status.value === 'Open') {
tradingEngine.current.strategy.end.value = tradingEngine.current.candle.end.value
tradingEngine.current.strategy.endRate.value = tradingEngine.current.candle.min.value
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value
tradingEngine.current.strategy.endRate.value = tradingEngine.current.episode.candle.close.value
}
}
function updateStageStatus(stage, status) {
switch (stage) {
case 'Trigger Stage': {
tradingEngine.current.strategy.triggerStageStatus.value = status
break
}
case 'Open Stage': {
tradingEngine.current.strategy.openStageStatus.value = status
break
}
case 'Manage Stage': {
tradingEngine.current.strategy.manageStageStatus.value = status
break
}
case 'Close Stage': {
tradingEngine.current.strategy.closeStageStatus.value = status
break
}
}
}
function updateStatus() {
function resetTradingEngineDataStructure() {
if (tradingEngine.current.strategy.status.value === 'Closed') {
resetStrategy()
tradingEngineModule.initializeNode(tradingEngine.current.strategy)
}
}
File diff suppressed because it is too large. Load diff
+49 -5
View File
@@ -276,9 +276,19 @@
}
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++) {
@@ -417,8 +427,6 @@
}
function generateOutput() {
const timeFrame = global.dailyFilePeriods[n][0]
const timeFrameLabel = global.dailyFilePeriods[n][1]
indicatorOutputModule.start(
dataFiles,
@@ -461,17 +469,18 @@
}
function writeDataRanges(callBack) {
let outputDatasets = global.NODE_BRANCH_TO_ARRAY (bot.processNode.referenceParent.processOutput, 'Output Dataset')
let outputDatasetIndex = -1;
controlLoop()
function productLoopBody() {
let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
writeDataRange(contextVariables.dateBeginOfMarket, bot.multiPeriodDailyProcessDatetime, productCodeName, controlLoop)
}
function controlLoop() {
outputDatasetIndex++
if (outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length) {
if (outputDatasetIndex < outputDatasets.length) {
productLoopBody()
} else {
callBack(global.DEFAULT_OK_RESPONSE)
@@ -482,7 +491,7 @@
function writeDataRange(pBegin, pEnd, productCodeName, callBack) {
let dataRange = {
begin: pBegin.valueOf(),
end: pEnd.valueOf()
end: pEnd.valueOf() + ONE_DAY_IN_MILISECONDS
};
let fileContent = JSON.stringify(dataRange)
let fileName = '/Data.Range.json';
@@ -502,6 +511,38 @@
}
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)
}
}
@@ -514,6 +555,9 @@
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;
+58 -1
View File
@@ -7,6 +7,7 @@
};
let utilities = UTILITIES.newCloudUtilities(logger)
let fileStorage = FILE_STORAGE.newFileStorage(logger)
let statusDependencies;
let dataDependenciesModule;
@@ -31,6 +32,7 @@
}
function finalize() {
fileStorage = undefined
dataFiles = undefined
statusDependencies = undefined
dataDependenciesModule = undefined
@@ -130,11 +132,66 @@
if (n < global.marketFilesPeriods.length) {
timeFramesLoopBody()
} else {
writeStatusReport(callBackFunction)
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)
+82 -44
View File
@@ -1,12 +1,9 @@
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
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
@@ -28,12 +25,8 @@
return thisObject;
function initialize(callBackFunction) {
function initialize(processConfig, callBackFunction) {
try {
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
let name = 'Not Depends on any Process'
logger.fileName = MODULE_NAME + "." + name;
@@ -46,63 +39,108 @@
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 (processThisDependsOn.config.monthlyInstances === true) {
logger.fileName = MODULE_NAME + "." + name;
}
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> " + currentProcessKey + " depends on " + 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.exchangeTasks.length; j++) {
let exchangeTasks = networkNode.dataMining.exchangeTasks[j]
for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {
let taskManager = exchangeTasks.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.marketReference !== undefined) {
if (process.marketReference.referenceParent !== undefined) {
let market = process.marketReference.referenceParent
let currentProcessMarket = bot.processNode.marketReference.referenceParent
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 + '.');
}
if (currentProcessMarket.id === market.id) {
if (process.referenceParent !== undefined) {
let processDefinition = process.referenceParent
if (processThisDependsOn.id === processDefinition.id) {
eventServerClient = EVENT_SERVER_CLIENT.newEventsServerClient(networkNode.config.host, networkNode.config.webSocketsPort)
eventServerClient.initialize(onConnected)
/* 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
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
}
}
}
+123 -48
View File
@@ -9,87 +9,90 @@
let bot = BOT;
let thisObject = {
raiseEvents, raiseEvents
raiseEvents: raiseEvents,
asyncRaiseEvents: asyncRaiseEvents
};
return thisObject;
function raiseEvents(lastFile, timeFrames, callBackFunction) {
try {
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] raiseEvents -> Entering function."); }
/* We will reaise the events for the datasets impacted by the process that just finished. */
if (bot.processNode.referenceParent.processOutput !== undefined) {
if (bot.processNode.referenceParent.processOutput.outputDatasets !== undefined) {
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
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]
for (let j = 0; j < outputDatasets.length; j++) {
let outputDataset = outputDatasets[j]
/* 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));
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')
}
/* 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 (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));
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 (outputDataset.referenceParent.parentNode.parentNode !== undefined) {
if (outputDataset.referenceParent.parentNode.parentNode.config.codeName === undefined) {
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Bot not defined. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode));
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
return
}
if (outputDataset.referenceParent.parentNode.parentNode.parentNode !== undefined) {
if (outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName === undefined) {
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Data Mine not defined. Data Mine = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode.parentNode));
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
return
}
} else {
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine. Bot = " + JSON.stringify(outputDataset.referenceParent.parentNode.parentNode));
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 -> Product not attached to a Bot. Product = " + JSON.stringify(outputDataset.referenceParent.parentNode));
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 -> Dataset Definition not attached to a Product. Dataset Definition = " + JSON.stringify(outputDataset.referenceParent));
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 -> Output Dataset with no reference to a Dataset Definition. Output Dataset = " + JSON.stringify(outputDataset));
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
}
/* All good, lets emit the event. */
let dataMine = outputDataset.referenceParent.parentNode.parentNode.parentNode.config.codeName
let botName = outputDataset.referenceParent.parentNode.parentNode.config.codeName
let product = outputDataset.referenceParent.parentNode.config.codeName
let dataset = outputDataset.referenceParent.config.codeName
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)
} 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
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)
}
}
@@ -100,4 +103,76 @@
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')
}
/* 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 Data Mine not defined. Data Mine = " + JSON.stringify(botNode.parentNode));
throw (global.DEFAULT_FAIL_RESPONSE);
}
} else {
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine or Trading 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
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)
}
}
}
};
+14 -39
View File
@@ -56,50 +56,29 @@
/* Global control of logging. */
global.LOG_CONTROL = {
"Assistant": {
logInfo: true,
logWarnings: false,
logErrors: true,
logContent: false,
intensiveLogging: false
},
"Exchange API": {
logInfo: true,
logWarnings: false,
logErrors: true,
logContent: false,
intensiveLogging: false
},
"Status Report": {
logInfo: true,
logInfo: false,
logWarnings: false,
logErrors: true,
logContent: false,
intensiveLogging: false
},
"Dataset": {
logInfo: true,
logWarnings: false,
logErrors: true,
logContent: false,
intensiveLogging: false
},
"Context": {
logInfo: true,
logInfo: false,
logWarnings: false,
logErrors: true,
logContent: false,
intensiveLogging: false
},
"Process Execution Events": {
logInfo: true,
logInfo: false,
logWarnings: false,
logErrors: true,
logContent: false,
intensiveLogging: false
},
"Process Output": {
logInfo: true,
logInfo: false,
logWarnings: false,
logErrors: true,
logContent: false,
@@ -161,7 +140,7 @@
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/' + global.TASK_NODE.bot.config.repo + '/this.bot.config.json';
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);
@@ -204,11 +183,11 @@
/* Simplifying the access to basic info */
botConfig.dataMine = global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode.config.codeName
botConfig.exchange = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode.name
botConfig.exchangeNode = global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode
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.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent.config.codeName,
quotedAsset: global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent.config.codeName
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
@@ -231,16 +210,12 @@
botConfig.marketTimeFrames = global.TASK_NODE.bot.timeFramesFilter.config.marketTimeFrames
}
if (botConfig.processNode) {
if (botConfig.processNode.marketReference) {
if (botConfig.processNode.marketReference.keyReference !== undefined) {
if (botConfig.processNode.marketReference.keyReference.referenceParent !== undefined) {
let key = botConfig.processNode.marketReference.keyReference.referenceParent
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
}
}
botConfig.KEY = key.config.codeName
botConfig.SECRET = key.config.secret
}
}
+4 -24
View File
@@ -34,7 +34,7 @@
return
}
let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName
let filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo + "/" + pProcessConfig.codeName
filePath += "/User.Bot.js"
fileStorage.getTextFile(filePath, onBotDownloaded);
@@ -56,7 +56,7 @@
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
}
filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo;
filePath = bot.dataMine + "/" + "bots" + "/" + bot.processNode.referenceParent.parentNode.config.repo;
filePath += "/Commons.js"
fileStorage.getTextFile(filePath, onCommonsDownloaded);
@@ -85,9 +85,6 @@
try {
let fixedTimeLoopIntervalHandle;
/* Errors sent to the UI */
bot.processError = processError
/* Heartbeats sent to the UI */
bot.processHeartBeat = processHeartBeat
@@ -148,7 +145,7 @@
function initializeProcessExecutionEvents() {
try {
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
processExecutionEvents.initialize(onInizialized);
processExecutionEvents.initialize(processConfig, onInizialized);
function onInizialized(err) {
try {
@@ -571,26 +568,9 @@
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
}
function processError(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.processKey, 'Error', event)
}
function processStopped() {
if (global.unexpectedError !== undefined) {
processError(undefined, "An unexpected error caused the Process to stop.")
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
} else {
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
}
+162 -58
View File
@@ -22,9 +22,9 @@
/* Set the folderName for early logging */
if (bot.processNode.session.config.folderName === undefined) {
bot.SESSION.folderName = bot.SESSION.id
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.SESSION.id
} else {
bot.SESSION.folderName = bot.processNode.session.config.folderName + "-" + bot.SESSION.id
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
}
/* Check if there is a session */
@@ -38,11 +38,15 @@
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, 'Run Session', undefined, bot.sessionKey, undefined, runSession)
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, stopSession)
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Session Status', undefined, bot.sessionKey, undefined, onSessionStatus)
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Session', undefined, bot.sessionKey, undefined, onSessionRun)
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Session', undefined, bot.sessionKey, undefined, onSessionStop)
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Resume Session', undefined, bot.sessionKey, undefined, onSessionResume)
/* Errors sent to the UI */
/* 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
@@ -50,23 +54,40 @@
callBackFunction(global.DEFAULT_OK_RESPONSE)
return
function runSession(message) {
function onSessionStatus() {
if (bot.SESSION_STATUS === 'Running') {
let event = {
status: 'Session Runnning'
}
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Status Response', event)
} else {
let event = {
status: 'Not Session Runnning'
}
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Status Response', event)
}
}
function onSessionRun(message) {
try {
if (bot.SESSION_STATUS === 'Idle' || bot.SESSION_STATUS === 'Running') { return } // This happens when the UI is reloaded, the session was running and tries to run it again.
/* 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.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema)
bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem)
bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine)
bot.SESSION = JSON.parse(message.event.session)
bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter)
bot.RESUME = message.event.resume
bot.RESUME = false
bot.FIRST_EXECUTION = true
bot.SESSION.stop = stopSession // stop function
setUpAppSchema()
setUpSessionFolderName()
/* We validate all parameters received and complete some that might be missing if needed. */
if (checkParemeters() === false) { return }
socialBotsModule.initialize()
@@ -95,44 +116,63 @@
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.SESSION.type + " '" + bot.SESSION.name + "' is starting.")
} catch (err) {
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> runSession -> err = " + err.stack);
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.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.SESSION.type + " '" + bot.SESSION.name + "' is resuming.")
} catch (err) {
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionResume -> err = " + err.stack);
}
}
function stopSession(commandOrigin) {
try {
if (commandOrigin === undefined) { commandOrigin = ' from the User Interface.' }
socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin)
socialBotsModule.finalize()
bot.STOP_SESSION = true
} catch (err) {
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> stopSession -> err = " + err.stack);
}
}
function setUpAppSchema() {
/* Setup the APP_SCHEMA_MAP based on the APP_SCHEMA_ARRAY */
bot.APP_SCHEMA_MAP = new Map()
for (let i = 0; i < bot.APP_SCHEMA_ARRAY.length; i++) {
let nodeDefinition = bot.APP_SCHEMA_ARRAY[i]
let key = nodeDefinition.type
bot.APP_SCHEMA_MAP.set(key, nodeDefinition)
}
socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.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.SESSION, commandOrigin)
}
function setUpSessionFolderName() {
/* Set the folderName for logging, reports, context and data output */
/*
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.SESSION.config !== undefined) {
config = bot.SESSION.config
if (config.folderName === undefined) {
bot.SESSION.folderName = bot.SESSION.id
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.SESSION.id
} else {
bot.SESSION.folderName = config.folderName + "-" + bot.SESSION.id
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
}
}
}
@@ -157,13 +197,29 @@
type: 'Time Range',
config: {
initialDatetime: (new Date()).valueOf(),
finalDatetime: (new Date()).valueOf()
finalDatetime: (new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS
}
}
} else {
/* Check that we received valid dates */
if (bot.SESSION.type === 'Backtesting Session') {
if (isNaN(new Date(bot.SESSION.parameters.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.SESSION.parameters, errorMessage)
return false
}
}
if (isNaN(new Date(bot.SESSION.parameters.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.SESSION.parameters, errorMessage)
return false
}
}
/* Session Type Forced Values */
let today = new Date((new Date()).valueOf())
let today = (new Date()).valueOf()
let aYearAgo = today - global.ONE_YEAR_IN_MILISECONDS
let aYearFromNow = today + global.ONE_YEAR_IN_MILISECONDS
switch (bot.SESSION.type) {
@@ -186,11 +242,25 @@
}
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.SESSION.parameters.timeRange.config.initialDatetime === undefined) {
bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault
if (bot.SESSION.type === 'Backtesting Session') {
if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined) {
bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault
} else {
bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()
}
} else {
bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()
/* Non backtest session can start from the past only if explicitly configured that way */
if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined || bot.SESSION.parameters.timeRange.config.allowStartingFromThePast !== true) {
bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault
} else {
bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()
}
}
/* Final Datetime */
@@ -215,6 +285,12 @@
return false
}
bot.SESSION.parameters.timeFrame.config.value = getTimeFrameFromLabel(bot.SESSION.parameters.timeFrame.config.label)
if (bot.SESSION.parameters.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.SESSION.parameters.timeFrame, errorMessage)
return false
}
/* Session Base Asset */
if (bot.SESSION.parameters.sessionBaseAsset === undefined) {
@@ -229,12 +305,6 @@
bot.sessionError(bot.SESSION.parameters.sessionBaseAsset, errorMessage)
return false
}
if (bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance === undefined) {
bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance = 0
}
if (bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance === undefined) {
bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance = Number.MAX_SAFE_INTEGER
}
/* Session Quoted Asset */
if (bot.SESSION.parameters.sessionQuotedAsset === undefined) {
@@ -249,12 +319,6 @@
bot.sessionError(bot.SESSION.parameters.sessionQuotedAsset, errorMessage)
return false
}
if (bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance === undefined) {
bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance = 0
}
if (bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance === undefined) {
bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance = Number.MAX_SAFE_INTEGER
}
/* Slippage */
if (bot.SESSION.parameters.slippage === undefined) { // if the Slippage is missing we create a default one.
@@ -300,7 +364,6 @@
}
function startBackTesting(message) {
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) // Here we initialize the date of the process.
return true
}
@@ -309,9 +372,6 @@
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> Key 'codeName' or 'secret' not provided. Plese check that and try again."); }
return false
}
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf())
pProcessConfig.liveWaitTime = bot.SESSION.parameters.timeFrame.config.value
return true
}
@@ -322,8 +382,6 @@
return false
}
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf())
/* Reduce the balance */
let balancePercentage = 1 // This is the default value
@@ -346,8 +404,6 @@
}
function startPaperTrading(message) {
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf())
pProcessConfig.normalWaitTime = bot.SESSION.parameters.timeFrame.config.value
return true
}
@@ -372,15 +428,18 @@
}
}
function sessionHeartBeat(processingDate) {
function sessionHeartBeat(processingDate, percentage, status) {
let event = {
seconds: (new Date()).getSeconds(),
processingDate: processingDate
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. ') }
}
}
@@ -402,6 +461,51 @@
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. ') }
}
}
-7
View File
@@ -18,11 +18,7 @@
return thisObject;
function initialize(callBackFunction) {
try {
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
/* Basic Valdidations */
if (bot.processNode.referenceParent.processDependencies !== undefined) {
if (bot.processNode.referenceParent.processDependencies.statusDependencies !== undefined) {
@@ -88,9 +84,6 @@
switch (err.message) {
case global.DEFAULT_OK_RESPONSE.message: {
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] initialize -> onLoad -> Execution finished well. -> Status Dependency = " + JSON.stringify(statusDependency.dependency)) }
addReport();
return;
}
+107 -44
View File
@@ -20,6 +20,7 @@
finalize: finalize,
load: load,
save: save,
asyncSave: asyncSave,
status: undefined
};
@@ -39,10 +40,7 @@
return thisObject;
function initialize(pStatusDependencyNode, callBackFunction) {
try {
if (global.LOG_CONTROL[MODULE_NAME].logInfo === true) { logger.write(MODULE_NAME, "[INFO] initialize -> Entering function."); }
statusDependencyNode = pStatusDependencyNode;
logger.fileName = MODULE_NAME + "." + statusDependencyNode.type + "." + statusDependencyNode.name + "." + statusDependencyNode.id;
@@ -85,7 +83,7 @@
function validationFailed(errorInNode, errorMessage) {
let nodeString = JSON.stringify(errorInNode)
logger.write(MODULE_NAME, "[ERROR] initialize -> " + errorMessage + ' -> nodeString = ' + nodeString)
bot.processError(errorInNode, errorMessage)
global.PROCESS_ERROR(bot.processKey, errorInNode, errorMessage)
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
}
@@ -113,50 +111,97 @@
for (let i = 0; i < network.networkNodes.length; i++) {
let networkNode = network.networkNodes[i]
if (checkThisBranch(networkNode.dataMining) === true) { return }
if (checkThisBranch(networkNode.testingEnvironment) === true) { return }
if (checkThisBranch(networkNode.productionEnvironment) === true) { return }
if (checkThisDataBranch(networkNode.dataMining) === true) { return }
if (checkThisTradingBranch(networkNode.testingEnvironment) === true) { return }
if (checkThisTradingBranch(networkNode.productionEnvironment) === true) { return }
function checkThisBranch(branch) {
function checkThisDataBranch(branch) {
if (branch === undefined) { return }
for (let j = 0; j < branch.exchangeTasks.length; j++) {
let exchangeTasks = branch.exchangeTasks[j]
for (let k = 0; k < exchangeTasks.taskManagers.length; k++) {
let taskManager = exchangeTasks.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.marketReference !== undefined) {
if (process.marketReference.referenceParent !== undefined) {
let market = process.marketReference.referenceParent
let currentProcessMarket = bot.processNode.marketReference.referenceParent
if (currentProcessMarket.id === market.id) {
if (process.referenceParent !== undefined) {
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
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
}
}
}
@@ -346,4 +391,22 @@
}
}
};
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)
}
}
-25
View File
@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "Project", "Project.njsproj", "{AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE0A3FE2-7021-4CC2-9922-A887CA5CDD7C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9128DF06-8CA5-497C-8BF7-B924571C5394}
EndGlobalSection
EndGlobal
+30 -92
View File
@@ -3,9 +3,6 @@
const MODULE_NAME = "Trading Bot";
const FULL_LOG = true;
let USER_BOT_MODULE;
let USER_BOT_COMMONS;
const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/TradingProcess.js');
const FILE_STORAGE = require('./FileStorage.js');
const SESSION = require(global.ROOT_DIR + 'Session');
@@ -33,53 +30,9 @@
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, onSessionInitialized)
function onSessionInitialized(err) {
callBackFunction(err);
}
return
session.initialize(processConfig, callBackFunction)
}
let filePath = bot.dataMine + "/" + "bots" + "/" + bot.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); // Use this for production
filePath = bot.dataMine + "/" + "bots" + "/" + bot.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.code || err.message);
callBackFunction(global.DEFAULT_OK_RESPONSE);
return;
}
USER_BOT_COMMONS = {}
USER_BOT_COMMONS.newCommons = eval(text); // Use this for production
session.initialize(onSessionInitialized)
function onSessionInitialized(err) {
callBackFunction(err);
}
}
}
} catch (err) {
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
@@ -91,9 +44,7 @@
/* Some initial values*/
let fixedTimeLoopIntervalHandle;
bot.STOP_SESSION = true;
/* Errors sent to the UI */
bot.processError = processError
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] run -> Stopping the Session now. ') }
/* Heartbeats sent to the UI */
bot.processHeartBeat = processHeartBeat
@@ -123,7 +74,7 @@
let nextWaitTime;
/* We tell the UI that we are running. */
processHeartBeat()
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');
@@ -143,14 +94,14 @@
/* Checking if we need to need to emit any event */
if (bot.SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) {
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Running')
bot.SESSION_STATUS = 'Running'
}
if (bot.SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) {
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped')
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) {
@@ -178,7 +129,7 @@
function initializeProcessExecutionEvents() {
try {
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
processExecutionEvents.initialize(onInizialized);
processExecutionEvents.initialize(processConfig, onInizialized);
function onInizialized(err) {
try {
@@ -337,7 +288,7 @@
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, USER_BOT_MODULE, USER_BOT_COMMONS);
processFramework = TRADING_PROCESS_MODULE.newTradingProcess(bot, logger, UTILITIES);
intitializeProcessFramework();
break;
}
@@ -572,7 +523,7 @@
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); }
/* We show we reached the end of the loop. */
processHeartBeat()
processHeartBeat(undefined, undefined, "Running...")
/* Here we check if we must stop the loop gracefully. */
shallWeStop(onStop, onContinue);
@@ -607,23 +558,27 @@
break
case 'Normal': {
let waitTime
switch (bot.SESSION.type) {
case 'Live Trading Session': {
waitTime = processConfig.liveTradingWaitTime
if (processConfig.waitsForExecutionFinishedEvent === true) {
waitTime = 0
} else {
switch (bot.SESSION.type) {
case 'Live Trading Session': {
waitTime = bot.SESSION.parameters.timeFrame.config.value
break
}
case 'Fordward Tessting Session': {
waitTime = bot.SESSION.parameters.timeFrame.config.value
break
}
case 'Paper Trading Session': {
waitTime = bot.SESSION.parameters.timeFrame.config.value
break
}
case 'Backtesting Session': {
waitTime = 0
break
}
}
break
case 'Fordward Tessting Session': {
waitTime = processConfig.fordwardTestingWaitTime
}
break
case 'Paper Trading Session': {
waitTime = processConfig.paperTradingWaitTime
}
break
case 'Backtesting Session': {
waitTime = processConfig.backtestingWaitTime
}
break
}
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s."); }
@@ -697,26 +652,9 @@
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
}
function processError(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.processKey, 'Error', event)
}
function processStopped() {
if (global.unexpectedError !== undefined) {
processError(undefined, "An unexpected error caused the Process to stop.")
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
} else {
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
}
+304 -26
View File
@@ -1,5 +1,6 @@
/* Callbacks default responses. */
/* Callbacks default responses. */
global.DEFAULT_OK_RESPONSE = {
result: "Ok",
message: "Operation Succeeded"
@@ -41,7 +42,260 @@ global.UNIQUE_ID = function () {
}
global.PRECISE = function (floatNumber, precision) {
return parseFloat(Number.parseFloat(floatNumber).toPrecision(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) {
@@ -172,7 +426,8 @@ function preLoader() {
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) {
global.TASK_NODE = message
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()
@@ -187,7 +442,8 @@ function preLoader() {
//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) {
global.TASK_NODE = message
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()
@@ -197,6 +453,16 @@ function preLoader() {
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() {
@@ -241,73 +507,85 @@ function bootLoader() {
/* Validate that the minimun amount of input required are defined. */
if (global.TASK_NODE.bot.processes[processIndex].marketReference === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Process Instance without a Market Reference. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex]));
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.bot.processes[processIndex].marketReference.referenceParent === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Market Reference without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference) + ", bot = " + global.TASK_NODE.bot.name);
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.bot.processes[processIndex].marketReference.referenceParent.parentNode === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Market without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent));
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.bot.processes[processIndex].marketReference.referenceParent.parentNode.parentNode === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Exchange Markets without a Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.parentNode));
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
}
if (global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Market without a Base Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent));
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.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Market without a Quoted Asset. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent));
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.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset.referenceParent === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Base Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.baseAsset));
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.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset.referenceParent === undefined) {
console.log("[WARN] Task Server -> server -> bootLoader -> Quoted Asset without a Reference Parent. This process will not be executed. -> Process Instance = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].marketReference.referenceParent.quotedAsset));
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("[WARN] 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]));
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("[WARN] Task Server -> server -> bootLoader -> Process Definition without parent Bot Definition. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent));
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("[WARN] Task Server -> server -> bootLoader -> Bot Definition without parent Data Mine. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode));
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("[WARN] Task Server -> server -> bootLoader -> Process Definition without a codeName defined. -> Process Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent));
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("[WARN] Task Server -> server -> bootLoader -> Bot Definition without a codeName defined. -> Bot Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode));
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("[WARN] Task Server -> server -> bootLoader -> Data Mine without a codeName defined. -> Data Mine Definition = " + JSON.stringify(global.TASK_NODE.bot.processes[processIndex].referenceParent.parentNode.parentNode));
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
}