39 files changed
+4141
-1153
No files matched your search
+17
-11
@@ -99,6 +99,9 @@
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Trading Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Learning Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Product Definition not attached to a Bot. Product Definition = ");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
@@ -115,9 +118,18 @@
|
||||
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
|
||||
let learningMineNode = global.FIND_NODE_IN_NODE_MESH(outputDatasetNode, 'Learning Mine')
|
||||
if (learningMineNode === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Bot not attached to a Data Mine, Trading Mine or Learning Mine.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
} else {
|
||||
if (tradingMineNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Learning Mine witn no codeName defined.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (tradingMineNode.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Trading Mine witn no codeName defined.");
|
||||
@@ -380,12 +392,6 @@
|
||||
let product = {}
|
||||
product[variableName] = {}
|
||||
|
||||
/* Here is how we know if we are processing Yesterday. */
|
||||
let positionedAtYesterday = false
|
||||
if (processingDailyFiles) {
|
||||
positionedAtYesterday = (record.current.end < currentDay.valueOf())
|
||||
}
|
||||
|
||||
/* This is Loop Code */
|
||||
try {
|
||||
eval(dataBuildingProcedure.loop.javascriptCode.code)
|
||||
@@ -423,9 +429,9 @@
|
||||
results.push(product[variableName]);
|
||||
}
|
||||
|
||||
/* While we are positioned at Yesterday, we keep updating this data structure. */
|
||||
/* At the last instant of yesterday, we update the yesterday object. */
|
||||
if (processingDailyFiles) {
|
||||
if (positionedAtYesterday) {
|
||||
if (record.current.end + 1 === currentDay.valueOf()) {
|
||||
yesterday.variable = JSON.parse(JSON.stringify(variable))
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@
|
||||
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.");
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> onInitilized -> It is not possible not to have process dependencies, which means not data dependencies.");
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
}
|
||||
@@ -65,7 +65,7 @@
|
||||
let skipCount = 0
|
||||
|
||||
/*
|
||||
The current nodeArray that we have includes all the dependencies daclared in the Data Mine / Trading Mine
|
||||
The current nodeArray that we have includes all the dependencies daclared in the Data Mine | Trading Mine | Learning Mine
|
||||
process dependencies, minus the ones without reference parent, meaning that references Data Mines that are
|
||||
not present at the workspace. From all the remaining dependencies there will be others that we need to
|
||||
filter out, and they are the ones which the user does not have a data product anywhere on the network where
|
||||
|
||||
+4
-2
@@ -24,8 +24,8 @@ exports.newDataSet = function newDataSet(BOT, logger) {
|
||||
|
||||
try {
|
||||
thisObject.node = dataDependency.referenceParent;
|
||||
logger.fileName = MODULE_NAME + "." + thisObject.node.type + "." + thisObject.node.name + "." + thisObject.node.id;
|
||||
|
||||
logger.fileName = MODULE_NAME
|
||||
|
||||
/* Some very basic validations that we have all the information needed. */
|
||||
if (thisObject.node === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Data Dependency without Reference Parent -> dataDependency = " + JSON.stringify(dataDependency));
|
||||
@@ -33,6 +33,8 @@ exports.newDataSet = function newDataSet(BOT, logger) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.fileName = MODULE_NAME + "." + thisObject.node.type + "." + thisObject.node.name + "." + thisObject.node.id;
|
||||
|
||||
if (thisObject.node.config.codeName === undefined) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> Dataset witn no codeName defined -> Product Dataset = " + JSON.stringify(thisObject.node));
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
|
||||
+7
-3
@@ -80,10 +80,14 @@ exports.newDebugLog = function newDebugLog() {
|
||||
|
||||
let filePath = thisObject.bot.filePathRoot + "/Logs/" + thisObject.bot.process + "/"
|
||||
|
||||
if (thisObject.bot.SESSION !== undefined) {
|
||||
filePath = filePath + thisObject.bot.SESSION.folderName + "/" + executionDatetime;
|
||||
if (thisObject.bot.TRADING_SESSION !== undefined) {
|
||||
filePath = filePath + thisObject.bot.TRADING_SESSION.folderName + "/" + executionDatetime;
|
||||
} else {
|
||||
filePath = filePath + executionDatetime;
|
||||
if (thisObject.bot.LEARNING_SESSION !== undefined) {
|
||||
filePath = filePath + thisObject.bot.LEARNING_SESSION.folderName + "/" + executionDatetime;
|
||||
} else {
|
||||
filePath = filePath + executionDatetime;
|
||||
}
|
||||
}
|
||||
|
||||
if (thisObject.bot.debug.year !== undefined) {
|
||||
|
||||
+49
-35
@@ -1,7 +1,7 @@
|
||||
exports.newEventsServerClient = function newEventsServerClient(host, port) {
|
||||
|
||||
const MODULE = "Task Server "
|
||||
const ERROR_LOG = true
|
||||
|
||||
const INFO_LOG = false
|
||||
|
||||
let thisObject = {
|
||||
@@ -27,6 +27,8 @@
|
||||
port = process.env.WEB_SOCKETS_SERVER_PORT
|
||||
}
|
||||
|
||||
let messageCounter = 0
|
||||
let commandCounter = 0
|
||||
|
||||
return thisObject
|
||||
|
||||
@@ -39,25 +41,32 @@
|
||||
function setuptWebSockets(callBackFunction) {
|
||||
try {
|
||||
|
||||
if (INFO_LOG === true) {
|
||||
console.log('setuptWebSockets at ' + host + ':' + port)
|
||||
}
|
||||
WEB_SOCKETS_CLIENT = new WEB_SOCKET('ws://' + host + ':' + port )
|
||||
|
||||
WEB_SOCKETS_CLIENT.onerror = error => {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> On connection error -> error = ' + JSON.stringify(error))
|
||||
WEB_SOCKETS_CLIENT.onerror = err => {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> On connection error -> error = ' + err.stack)
|
||||
console.log('[ERROR] This could mean that the port '+ port +' is taken by some other app running at your system. To resolve this issue, please pick another port number and change it at the .ENV file inside the Superalgos folder AND at the .ENV file inside the TaskServer folder. After that run the app again. ')
|
||||
}
|
||||
WEB_SOCKETS_CLIENT.onopen = () => {
|
||||
if (INFO_LOG === true) {
|
||||
console.log('Websocket connection opened.')
|
||||
}
|
||||
|
||||
if (callBackFunction !== undefined) {
|
||||
callBackFunction()
|
||||
try {
|
||||
if (INFO_LOG === true) {
|
||||
console.log('Websocket connection opened.')
|
||||
}
|
||||
|
||||
if (callBackFunction !== undefined) {
|
||||
callBackFunction()
|
||||
}
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> onopen -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
WEB_SOCKETS_CLIENT.onmessage = e => {
|
||||
|
||||
try {
|
||||
if (INFO_LOG === true) {
|
||||
console.log('Websocket Message Received: ' + e.data)
|
||||
console.log('Websocket Message Received: ' + e.data.substring(0, 1000))
|
||||
}
|
||||
|
||||
let message = JSON.parse(e.data)
|
||||
@@ -84,11 +93,11 @@
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
if (ERROR_LOG === true) { logger.write('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> onmessage -> err = ' + err.stack) }
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> onmessage -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (ERROR_LOG === true) { logger.write('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> err = ' + err.stack) }
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,30 +118,35 @@
|
||||
}
|
||||
|
||||
function sendCommand(command, responseCallBack, eventsCallBack) {
|
||||
|
||||
if (command.action === 'listenToEvent') {
|
||||
let key
|
||||
if (command.callerId) {
|
||||
key = command.eventHandlerName + '-' + command.eventType + '-' + command.callerId
|
||||
try {
|
||||
if (command.action === 'listenToEvent') {
|
||||
let key
|
||||
if (command.callerId) {
|
||||
key = command.eventHandlerName + '-' + command.eventType + '-' + command.callerId
|
||||
} else {
|
||||
key = command.eventHandlerName + '-' + command.eventType
|
||||
}
|
||||
let handler = {
|
||||
eventHandlerName: command.eventHandlerName,
|
||||
eventType: command.eventType,
|
||||
callerId: command.callerId,
|
||||
callBack: eventsCallBack
|
||||
}
|
||||
eventListeners.set(key, handler)
|
||||
}
|
||||
if (command.callerId && responseCallBack) {
|
||||
responseWaiters.set(command.callerId, responseCallBack)
|
||||
}
|
||||
|
||||
if (WEB_SOCKETS_CLIENT.readyState === 1) { // 1 means connected and ready.
|
||||
|
||||
WEB_SOCKETS_CLIENT.send("Task Server" + "|*|" + "|*|" + JSON.stringify(command))
|
||||
|
||||
} else {
|
||||
key = command.eventHandlerName + '-' + command.eventType
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> sendCommand -> WebSocket message could not be sent because the connection was not ready. Message = ' + JSON.stringify(command))
|
||||
}
|
||||
let handler = {
|
||||
eventHandlerName: command.eventHandlerName,
|
||||
eventType: command.eventType,
|
||||
callerId: command.callerId,
|
||||
callBack: eventsCallBack
|
||||
}
|
||||
eventListeners.set(key, handler)
|
||||
}
|
||||
if (command.callerId && responseCallBack) {
|
||||
responseWaiters.set(command.callerId, responseCallBack)
|
||||
}
|
||||
|
||||
if (WEB_SOCKETS_CLIENT.readyState === 1) { // 1 means connected and ready.
|
||||
WEB_SOCKETS_CLIENT.send("Task Server" + "|*|" + "|*|" + JSON.stringify(command))
|
||||
} else {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> setuptWebSockets -> sendCommand -> WebSocket message could not be sent because the connection was not ready. Message = ' + JSON.stringify(command))
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> Event Server Client -> sendCommand -> err = ' + err.stack)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@
|
||||
|
||||
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);
|
||||
parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.message);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
|
||||
try {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> Entering function."); }
|
||||
|
||||
let products = {}
|
||||
let mainDependency = {}
|
||||
let processingDailyFiles
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
exports.newSession = function newSession(bot, parentLogger) {
|
||||
exports.newLearningSession = function newLearningSession(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Session"
|
||||
const MODULE_NAME = "Learning Session"
|
||||
const FULL_LOG = true;
|
||||
|
||||
let thisObject = {
|
||||
@@ -15,16 +15,16 @@
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
try {
|
||||
/* Initialize this info so that everything is logged propeerly */
|
||||
bot.SESSION = {
|
||||
bot.LEARNING_SESSION = {
|
||||
name: bot.processNode.session.name,
|
||||
id: bot.processNode.session.id
|
||||
}
|
||||
|
||||
/* Set the folderName for early logging */
|
||||
if (bot.processNode.session.config.folderName === undefined) {
|
||||
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.SESSION.id
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.LEARNING_SESSION.id
|
||||
} else {
|
||||
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
|
||||
/* Check if there is a session */
|
||||
@@ -38,10 +38,10 @@
|
||||
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, '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)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Learning Session Status', undefined, bot.sessionKey, undefined, onSessionStatus)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Run Learning Session', undefined, bot.sessionKey, undefined, onSessionRun)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Stop Learning Session', undefined, bot.sessionKey, undefined, onSessionStop)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.sessionKey, 'Resume Learning Session', undefined, bot.sessionKey, undefined, onSessionResume)
|
||||
|
||||
/* Connect this here so that it is accesible from other places */
|
||||
bot.sessionError = sessionError
|
||||
@@ -57,12 +57,12 @@
|
||||
function onSessionStatus() {
|
||||
if (bot.SESSION_STATUS === 'Running') {
|
||||
let event = {
|
||||
status: 'Session Runnning'
|
||||
status: 'Learning Session Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Status Response', event)
|
||||
} else {
|
||||
let event = {
|
||||
status: 'Not Session Runnning'
|
||||
status: 'Learning Session Not Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Status Response', event)
|
||||
}
|
||||
@@ -79,11 +79,11 @@
|
||||
/* We are going to run the Definition comming at the event. */
|
||||
bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem)
|
||||
bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine)
|
||||
bot.SESSION = JSON.parse(message.event.session)
|
||||
bot.LEARNING_SESSION = JSON.parse(message.event.session)
|
||||
bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter)
|
||||
bot.RESUME = false
|
||||
bot.FIRST_EXECUTION = true
|
||||
bot.SESSION.stop = stopSession // stop function
|
||||
bot.LEARNING_SESSION.stop = stopSession // stop function
|
||||
|
||||
setUpSessionFolderName()
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
socialBotsModule.initialize()
|
||||
|
||||
let allGood
|
||||
switch (bot.SESSION.type) {
|
||||
switch (bot.LEARNING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
allGood = startBackTesting(message)
|
||||
break
|
||||
@@ -119,7 +119,7 @@
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] onSessionRun -> Stopping the Session now. ') }
|
||||
}
|
||||
|
||||
socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is starting.")
|
||||
socialBotsModule.sendMessage(bot.LEARNING_SESSION.type + " '" + bot.LEARNING_SESSION.name + "' is starting.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionRun -> err = " + err.stack);
|
||||
}
|
||||
@@ -131,7 +131,7 @@
|
||||
|
||||
function onSessionResume(message) {
|
||||
try {
|
||||
if (bot.SESSION.stop === undefined) {
|
||||
if (bot.LEARNING_SESSION.stop === undefined) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session that have never be ran before. ")
|
||||
return
|
||||
}
|
||||
@@ -145,7 +145,7 @@
|
||||
bot.RESUME = true
|
||||
bot.STOP_SESSION = false
|
||||
|
||||
socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is resuming.")
|
||||
socialBotsModule.sendMessage(bot.LEARNING_SESSION.type + " '" + bot.LEARNING_SESSION.name + "' is resuming.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionResume -> err = " + err.stack);
|
||||
}
|
||||
@@ -153,11 +153,11 @@
|
||||
|
||||
function stopSession(commandOrigin) {
|
||||
|
||||
socialBotsModule.sendMessage(bot.SESSION.type + " '" + bot.SESSION.name + "' is stopping " + commandOrigin)
|
||||
socialBotsModule.sendMessage(bot.LEARNING_SESSION.type + " '" + bot.LEARNING_SESSION.name + "' is stopping " + commandOrigin)
|
||||
socialBotsModule.finalize()
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] stopSession -> Stopping the Session now. ') }
|
||||
sessionInfo(bot.SESSION, commandOrigin)
|
||||
sessionInfo(bot.LEARNING_SESSION, commandOrigin)
|
||||
}
|
||||
|
||||
function setUpSessionFolderName() {
|
||||
@@ -167,12 +167,12 @@
|
||||
Set the folderName for logging, reports, context and data output
|
||||
*/
|
||||
let config
|
||||
if (bot.SESSION.config !== undefined) {
|
||||
config = bot.SESSION.config
|
||||
if (bot.LEARNING_SESSION.config !== undefined) {
|
||||
config = bot.LEARNING_SESSION.config
|
||||
if (config.folderName === undefined) {
|
||||
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.SESSION.id
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.LEARNING_SESSION.id
|
||||
} else {
|
||||
bot.SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
bot.LEARNING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,16 +183,16 @@
|
||||
non critical is missing, we complete it with a default value.
|
||||
*/
|
||||
|
||||
if (bot.SESSION.parameters === undefined) {
|
||||
if (bot.LEARNING_SESSION.learningParameters === undefined) {
|
||||
let errorMessage = "Session Node with no Parameters."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Time Range */
|
||||
if (bot.SESSION.parameters.timeRange === undefined) { // if the Time Range is missing we create a default one.
|
||||
bot.SESSION.parameters.timeRange = {
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange === undefined) { // if the Time Range is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange = {
|
||||
name: 'Missing Time Range',
|
||||
type: 'Time Range',
|
||||
config: {
|
||||
@@ -202,18 +202,18 @@
|
||||
}
|
||||
} else {
|
||||
/* Check that we received valid dates */
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (isNaN(new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()) {
|
||||
if (bot.LEARNING_SESSION.type === 'Backtesting Session') {
|
||||
if (isNaN(new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (isNaN(new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf()) {
|
||||
if (isNaN(new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,7 @@
|
||||
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) {
|
||||
switch (bot.LEARNING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
useDefaultDatetimes(aYearAgo, today)
|
||||
break
|
||||
@@ -248,103 +248,103 @@
|
||||
*/
|
||||
|
||||
/* Initial Datetime */
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined) {
|
||||
bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault
|
||||
if (bot.LEARNING_SESSION.type === 'Backtesting Session') {
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime === undefined) {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = (new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
} else {
|
||||
/* Non backtest session can start from the past only if explicitly configured that way */
|
||||
if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined || bot.SESSION.parameters.timeRange.config.allowStartingFromThePast !== true) {
|
||||
bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime === undefined || bot.LEARNING_SESSION.learningParameters.timeRange.config.allowStartingFromThePast !== true) {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.SESSION.parameters.timeRange.config.initialDatetime = (new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime = (new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Final Datetime */
|
||||
if (bot.SESSION.parameters.timeRange.config.finalDatetime === undefined) {
|
||||
bot.SESSION.parameters.timeRange.config.finalDatetime = finalDefault
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime === undefined) {
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime = finalDefault
|
||||
} else {
|
||||
bot.SESSION.parameters.timeRange.config.finalDatetime = (new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf()
|
||||
bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime = (new Date(bot.LEARNING_SESSION.learningParameters.timeRange.config.finalDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Time Frame */
|
||||
if (bot.SESSION.parameters.timeFrame === undefined) {
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeFrame === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.SESSION.parameters.timeFrame.config.label === undefined) {
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeFrame.config.label === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame Label configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters.timeFrame, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
bot.SESSION.parameters.timeFrame.config.value = getTimeFrameFromLabel(bot.SESSION.parameters.timeFrame.config.label)
|
||||
if (bot.SESSION.parameters.timeFrame.config.value === undefined) {
|
||||
bot.LEARNING_SESSION.learningParameters.timeFrame.config.value = getTimeFrameFromLabel(bot.LEARNING_SESSION.learningParameters.timeFrame.config.label)
|
||||
if (bot.LEARNING_SESSION.learningParameters.timeFrame.config.value === undefined) {
|
||||
let errorMessage = "Config error: label value not recognized. Try 01-min or 01-hs for example."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters.timeFrame, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Base Asset */
|
||||
if (bot.SESSION.parameters.sessionBaseAsset === undefined) {
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionBaseAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Base Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.SESSION.parameters.sessionBaseAsset.config.initialBalance === undefined) {
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Base Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters.sessionBaseAsset, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.sessionBaseAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Quoted Asset */
|
||||
if (bot.SESSION.parameters.sessionQuotedAsset === undefined) {
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Quoted Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.SESSION.parameters.sessionQuotedAsset.config.initialBalance === undefined) {
|
||||
if (bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Quoted Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters.sessionQuotedAsset, errorMessage)
|
||||
bot.sessionError(bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Slippage */
|
||||
if (bot.SESSION.parameters.slippage === undefined) { // if the Slippage is missing we create a default one.
|
||||
bot.SESSION.parameters.timeRange = {
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage === undefined) { // if the Slippage is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage = {
|
||||
name: 'Missing Slippage',
|
||||
type: 'Slippage',
|
||||
config: {
|
||||
positionRate: 0,
|
||||
marketOrderRate: 0,
|
||||
stopLoss: 0,
|
||||
takeProfit: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.SESSION.parameters.slippage.config.positionRate === undefined) { // if the positionRate is missing we create a default one.
|
||||
bot.SESSION.parameters.slippage.config.positionRate = 0
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage.config.marketOrderRate === undefined) { // if the marketOrderRate is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage.config.marketOrderRate = 0
|
||||
}
|
||||
if (bot.SESSION.parameters.slippage.config.stopLoss === undefined) { // if the stopLoss is missing we create a default one.
|
||||
bot.SESSION.parameters.slippage.config.stopLoss = 0
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage.config.stopLoss === undefined) { // if the stopLoss is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage.config.stopLoss = 0
|
||||
}
|
||||
if (bot.SESSION.parameters.slippage.config.takeProfit === undefined) { // if the takeProfit is missing we create a default one.
|
||||
bot.SESSION.parameters.slippage.config.takeProfit = 0
|
||||
if (bot.LEARNING_SESSION.learningParameters.slippage.config.takeProfit === undefined) { // if the takeProfit is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.slippage.config.takeProfit = 0
|
||||
}
|
||||
|
||||
/* Fee Structure */
|
||||
if (bot.SESSION.parameters.feeStructure === undefined) { // if the Fee Structure is missing we create a default one.
|
||||
bot.SESSION.parameters.feeStructure = {
|
||||
if (bot.LEARNING_SESSION.learningParameters.feeStructure === undefined) { // if the Fee Structure is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.feeStructure = {
|
||||
name: 'Missing Fee Structure',
|
||||
type: 'Fee Structure',
|
||||
config: {
|
||||
@@ -353,11 +353,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.SESSION.parameters.feeStructure.config.maker === undefined) { // if the maker is missing we create a default one.
|
||||
bot.SESSION.parameters.feeStructure.config.maker = 0
|
||||
if (bot.LEARNING_SESSION.learningParameters.feeStructure.config.maker === undefined) { // if the maker is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.feeStructure.config.maker = 0
|
||||
}
|
||||
if (bot.SESSION.parameters.feeStructure.config.taker === undefined) { // if the taker is missing we create a default one.
|
||||
bot.SESSION.parameters.feeStructure.config.taker = 0
|
||||
if (bot.LEARNING_SESSION.learningParameters.feeStructure.config.taker === undefined) { // if the taker is missing we create a default one.
|
||||
bot.LEARNING_SESSION.learningParameters.feeStructure.config.taker = 0
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -369,7 +369,9 @@
|
||||
|
||||
function startLiveTrading(message) {
|
||||
if (bot.KEY === undefined || bot.SECRET === undefined) {
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> Key 'codeName' or 'secret' not provided. Plese check that and try again."); }
|
||||
let errorMessage = "Key 'codeName' or 'secret' not provided. Plese check that and try again."
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> " + errorMessage); }
|
||||
bot.sessionError(bot.LEARNING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -385,20 +387,20 @@
|
||||
/* Reduce the balance */
|
||||
let balancePercentage = 1 // This is the default value
|
||||
|
||||
if (bot.SESSION.config.balancePercentage !== undefined) {
|
||||
balancePercentage = bot.SESSION.config.balancePercentage
|
||||
if (bot.LEARNING_SESSION.config.balancePercentage !== undefined) {
|
||||
balancePercentage = bot.LEARNING_SESSION.config.balancePercentage
|
||||
}
|
||||
|
||||
bot.SESSION.parameters.sessionBaseAsset.config.initialBalance = bot.SESSION.parameters.sessionBaseAsset.config.initialBalance * balancePercentage / 100
|
||||
bot.SESSION.parameters.sessionQuotedAsset.config.initialBalance = bot.SESSION.parameters.sessionQuotedAsset.config.initialBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.initialBalance = bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.initialBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.initialBalance = bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.initialBalance * balancePercentage / 100
|
||||
|
||||
bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance = bot.SESSION.parameters.sessionBaseAsset.config.minimumBalance * balancePercentage / 100
|
||||
bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance = bot.SESSION.parameters.sessionQuotedAsset.config.minimumBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.minimumBalance = bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.minimumBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.minimumBalance = bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.minimumBalance * balancePercentage / 100
|
||||
|
||||
bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance = bot.SESSION.parameters.sessionBaseAsset.config.maximumBalance * balancePercentage / 100
|
||||
bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance = bot.SESSION.parameters.sessionQuotedAsset.config.maximumBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.maximumBalance = bot.LEARNING_SESSION.learningParameters.sessionBaseAsset.config.maximumBalance * balancePercentage / 100
|
||||
bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.maximumBalance = bot.LEARNING_SESSION.learningParameters.sessionQuotedAsset.config.maximumBalance * balancePercentage / 100
|
||||
|
||||
pProcessConfig.normalWaitTime = bot.SESSION.parameters.timeFrame.config.value
|
||||
pProcessConfig.normalWaitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -21,7 +21,7 @@ exports.newAnnouncements = function newAnnouncements(bot, logger) {
|
||||
function finalize() {
|
||||
tradingSystem = undefined
|
||||
tradingEngine = undefined
|
||||
bot.SESSION.messagesSent = undefined
|
||||
bot.TRADING_SESSION.messagesSent = undefined
|
||||
}
|
||||
|
||||
function makeAnnoucements(node) {
|
||||
@@ -44,8 +44,8 @@ exports.newAnnouncements = function newAnnouncements(bot, logger) {
|
||||
text = tradingSystem.formulas.get(announcement.formula.id)
|
||||
}
|
||||
|
||||
if (bot.SESSION.socialBots !== undefined) {
|
||||
bot.SESSION.socialBots.announce(text)
|
||||
if (bot.TRADING_SESSION.socialBots !== undefined) {
|
||||
bot.TRADING_SESSION.socialBots.announce(text)
|
||||
tradingSystem.announcements.push([announcement.id, text])
|
||||
} else {
|
||||
tradingSystem.errors.push([announcement.id, 'Could not announce because session does not have Social Bots.'])
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
let key = bot.KEY
|
||||
let secret = bot.SECRET
|
||||
|
||||
|
||||
const exchangeClass = ccxt[exchangeId]
|
||||
const exchangeConstructorParams = {
|
||||
'apiKey': key,
|
||||
@@ -129,7 +129,23 @@
|
||||
return order.id
|
||||
} catch (err) {
|
||||
tradingSystem.errors.push([tradingSystemOrder.id, "createOrder -> Error = " + err.message])
|
||||
logError("createOrder -> Error = " + err.message);
|
||||
logError("createOrder -> Error = " + err.message)
|
||||
if (err.message.indexOf('API-key format invalid') >= 0) {
|
||||
logError ('The exchange says the API key provided is not good or it is not in the correct format. This is what you are using:')
|
||||
errorFooter()
|
||||
}
|
||||
if (err.message.indexOf('Signature for this request is not valid.') >= 0) {
|
||||
logError ('The exchange says the secret provided is not good, incorrect or not in the correct format. This is what you are using:')
|
||||
errorFooter()
|
||||
}
|
||||
function errorFooter(){
|
||||
logError ('key: ->' + bot.KEY + '<-')
|
||||
logError ('secret: ->' + bot.SECRET + '<-')
|
||||
logError ('key.length:' + bot.KEY.length )
|
||||
logError ('secret.length:' + bot.SECRET.length )
|
||||
logError ('Double check that you copied the key at the codeName property and the secret at secret property without bringing any invisible caracter from the exchange web site, or leaving any character from the sample text. Also check that the keys are not disabled at the exchange ot that they are not restricted by IP.')
|
||||
logError ('As a remainder. Binance key and secret are 64 bytes in lenght each one. ' )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
|
||||
/*
|
||||
When we are live trading, we need to syncronize with the exchange.
|
||||
*/
|
||||
const MODULE_NAME = 'Orders Calculations'
|
||||
|
||||
let thisObject = {
|
||||
actualSizeCalculation: actualSizeCalculation,
|
||||
actualRateCalculation: actualRateCalculation,
|
||||
feesToBePaidCalculation: feesToBePaidCalculation,
|
||||
amountReceivedCalculation: amountReceivedCalculation,
|
||||
finapercentageFilledCalculationlize: percentageFilledCalculation,
|
||||
feesPaidCalculation: feesPaidCalculation,
|
||||
sizeFilledCalculation: sizeFilledCalculation,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
sessionParameters = undefined
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
}
|
||||
|
||||
async function actualSizeCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
When we submit the order to the exchange, we do it specifying the order size
|
||||
in Base Asset. It is mandatory to be in Base Asset as per CCXT Library API and
|
||||
probably as per the Exchange API too.
|
||||
|
||||
The exchange might take that size or might change it a little
|
||||
bit. For example, if it does not accept as many decimals as we are sending.
|
||||
|
||||
We have also seen that for Market Orders, the exchange usually does not take
|
||||
the size requested exactly. Sometimes is a little bit less or even a little bit
|
||||
more. For Limit orders it is usually the same.
|
||||
|
||||
If it happens that the size accepted (Actual Size) is different than the
|
||||
size requested (Size), we need to make several adjustments so that the
|
||||
accounting syncronizes with reality.
|
||||
*/
|
||||
|
||||
/* This calculation needs to happen only once, the first time the order is checked. */
|
||||
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
|
||||
|
||||
/* We receive the actual size from the exchange at the order.amount field. */
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = order.amount
|
||||
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
function recalculateActualSize() {
|
||||
/*
|
||||
We will recalculate the Quoted Asset Actual Size. This will also give to it an initial value.
|
||||
In the next formula, we are unsing the rate of the order because we dont know yet the Actual Rate.
|
||||
This rate might be replaced afteerwards for the Actual Rate when this is calculated again once
|
||||
the Actual Rate is known.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.rate.value
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.actualSize.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
}
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
/*
|
||||
We will also recalculate the Size Placed that was previously calculated with the order Size,
|
||||
and now we will do it with the order Actual Size.
|
||||
|
||||
We need to unaccount the previous size placed and correctly account
|
||||
for the the new actual sizes we now know.
|
||||
*/
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value -
|
||||
tradingEngineOrder.orderBaseAsset.size.value
|
||||
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
|
||||
tradingEngineOrder.orderQuotedAsset.size.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10)
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
|
||||
}
|
||||
}
|
||||
|
||||
async function actualRateCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
Actual Rate Calculation: Let's remember that the exchange may not take the rate
|
||||
we send it with the order (in Limit orders) and for any reason change it a little bit.
|
||||
For example we might have sent a rate with too many decimals and the exchange truncate
|
||||
them. Another thing that might happen is that we send a rate that is matching orders already
|
||||
at the order book. In this case we would get an actual rate not only different than the one
|
||||
we sent, but also, better since the trading engine inside the exchange would match orders
|
||||
with the first orders best by price.
|
||||
|
||||
In the case of Market Orders we don't even send a rate. For all those reason
|
||||
we get and store the Actual Rate at which the order was filled.
|
||||
*/
|
||||
if (order.average !== undefined) {
|
||||
/*
|
||||
We will use the average whenever is available. As of today it is not 100% clear when this is
|
||||
available, but it seems sometimes it is not. In those cases we use the price.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = order.average
|
||||
} else {
|
||||
/*
|
||||
We use the order.price when the average is not available.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = order.price
|
||||
}
|
||||
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
|
||||
/*
|
||||
If the Actual Rate happens to be exctly the same than the order rate, then there is
|
||||
nothing else to do here. Otherwise there are adjustments to be made.
|
||||
*/
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
|
||||
let previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
function recalculateActualSize() {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
/*
|
||||
Now we know the Actual Rate at which the order was filled. Since the actual rate
|
||||
is not the same as the Rate we defined for the order, we need to syncronize
|
||||
the Actual Order Size for Quoted Asset since it was calculated with the Order Size that we
|
||||
now know it is not the one really used at the exchange. We will recalculate the
|
||||
Actual Size in Quoted Asset and not in Base Asset, since the Actual Size in Base Asset
|
||||
is the one we accepted by the exchange, so that is fixed, regardless of how we initially
|
||||
got the Order Size in Base Asset, either from direct imput from the user or calculated
|
||||
from the Order Size in Quoted Asset input it by the user. So here we go.
|
||||
*/
|
||||
previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.actualRate.value
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderQuotedAsset.actualSize.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Actual Size (' + previousQuotedAssetActualSize + ') recalculated (' + tradingEngineOrder.orderQuotedAsset.actualSize.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
let previousStageQuotedAssetSizePlaced = tradingEngineStage.stageQuotedAsset.sizePlaced.value
|
||||
/*
|
||||
Since we changed the Actual Size in Quoted Asset, we need to fix the Size Placed in Quoted Asset
|
||||
that was previously calculated with the previous value of actualSize.
|
||||
*/
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
|
||||
previousQuotedAssetActualSize
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineStage.stageQuotedAsset.sizePlaced.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Size Placed (' + previousStageQuotedAssetSizePlaced + ') recalculated (' + tradingEngineStage.stageQuotedAsset.sizePlaced.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function feesToBePaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function percentageFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
Percentage Filled Calculation: The only relevant information we get from the exchange is the order.filled.
|
||||
We know this field will go up until reaching the Actual Size. Once it reaches that number
|
||||
the order will be 100% filled.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / tradingEngineOrder.orderBaseAsset.actualSize.value
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
|
||||
}
|
||||
|
||||
async function feesPaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
Within the order information received from the exchange we can not see the fees paid so we need to
|
||||
calculate them ourselves. Knwowing the Fees Paid is critical to later update the balances correctly,
|
||||
since at every trade we will receive less than just the Actual Size. In fact we will receive the Actual
|
||||
Size minus the Fees Paid.
|
||||
|
||||
The percentage at which the exchange will charge fees needs to be configured at the the Session Fees Parameters.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function sizeFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
CCXT returns order.filled with an amount denominated in Base Asset. We will
|
||||
take it from there for our Order Base Asset. The amount in order.filled does
|
||||
not reflect the Fees Paid, even if we are paying the fees in Base Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10)
|
||||
/*
|
||||
For Quoted Asset, CCXT does not return any field with the size filled, so we
|
||||
need to calculate that by ourselves.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
|
||||
}
|
||||
|
||||
async function amountReceivedCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
This calculation is for informational purposes, so that users do not have to calculate
|
||||
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
|
||||
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
|
||||
are selling then it will be in Quoted Asset.
|
||||
*/
|
||||
|
||||
switch (true) {
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Base Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderBaseAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Quoted Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
exports.newOrdersSimulations = function newOrdersSimulations(bot, logger) {
|
||||
/*
|
||||
When we are backtesting or paper trading, we need to simulate what would have happened at the exchange.
|
||||
*/
|
||||
const MODULE_NAME = 'Orders Simulations'
|
||||
|
||||
let thisObject = {
|
||||
actualSizeSimulation: actualSizeSimulation,
|
||||
actualRateSimulation: actualRateSimulation,
|
||||
feesToBePaidSimulation: feesToBePaidSimulation,
|
||||
amountReceivedSimulation: amountReceivedSimulation,
|
||||
percentageFilledSimulation: percentageFilledSimulation,
|
||||
feesPaidSimulation: feesPaidSimulation,
|
||||
sizeFilledSimulation: sizeFilledSimulation,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let tradingSystem
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
sessionParameters = undefined
|
||||
tradingEngine = undefined
|
||||
tradingSystem = undefined
|
||||
}
|
||||
|
||||
async function actualSizeSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
The Actual Size is the size the exchange accepts for the order,
|
||||
regarless of the size sent in the request to the exchange.
|
||||
|
||||
While backtesting and paper trading, we do not really need to
|
||||
simulate an Actual Size. We will just copy to that field the
|
||||
size of the order.
|
||||
|
||||
This calculation needs to happen only once, so as to set these values.
|
||||
*/
|
||||
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.size.value
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderQuotedAsset.size.value
|
||||
}
|
||||
|
||||
function actualRateSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/* Actual Rate Simulation */
|
||||
let previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) {
|
||||
/*
|
||||
The default value for the Actual Rate is the rate of the order.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value
|
||||
}
|
||||
|
||||
basedOnSessionParameters()
|
||||
basedOnTradingSystem()
|
||||
considerBestMatchWithOrderBook()
|
||||
recalculateActualSize()
|
||||
recalculateSizePlaced()
|
||||
|
||||
function basedOnSessionParameters() {
|
||||
/*
|
||||
The default way to calculate the Actual Rate is to apply the Slippage defined
|
||||
at the Session Parameters Definition. We are not going to apply the default if there
|
||||
is a definition for this order on how to calculate the actual rate.
|
||||
*/
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) {
|
||||
return
|
||||
}
|
||||
}
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
|
||||
/* Actual Rate is simulated based on the Session Paremeters */
|
||||
let slippageAmount = tradingEngineOrder.rate.value * sessionParameters.slippage.config.marketOrderRate / 100
|
||||
if (slippageAmount !== 0) {
|
||||
switch (tradingSystemOrder.type) {
|
||||
case 'Market Sell Order': {
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[sessionParameters.slippage.id, tradingEngineOrder.rate.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'slippage.config.marketOrderRate Slippage Amount (' + slippageAmount + ') substracted from Order Rate (' + tradingEngineOrder.rate.value + ') to get the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ')'
|
||||
]
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Market Buy Order': {
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[sessionParameters.slippage.id, tradingEngineOrder.rate.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'slippage.config.marketOrderRate Slippage Amount (' + slippageAmount + ') added to Order Rate (' + tradingEngineOrder.rate.value + ') to get the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ')'
|
||||
]
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
/* In Limit Orders the actual rate is the rate of the order, there is no slippage */
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function basedOnTradingSystem() {
|
||||
/*
|
||||
If the user specified a Formula to simulate the Actual Rate for this order,
|
||||
we use that formula here. Note that if this Formula exists, this will override
|
||||
the calculation based on Slippage.
|
||||
*/
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) {
|
||||
/* Calculate this only once for this order */
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) {
|
||||
let newValue = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id)
|
||||
newValue = global.PRECISE(newValue, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id,
|
||||
'Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') calculated with this Formula (' + newValue + ')'
|
||||
]
|
||||
)
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function considerBestMatchWithOrderBook() {
|
||||
/*
|
||||
Until here we have got the Actual Rate based on the formula definition or based on session parameters slippage.
|
||||
The last check is about watching what happened in the market. Let's remember that the exchange will
|
||||
fill the order with the best possible matches at it's order book. That means that if the rate
|
||||
we set for the order was too low (for a sale order) or too hight (for a buy order), the actual rate
|
||||
should be better than expected.
|
||||
|
||||
We don't know what it is at the order book, but whe know that the last candle includes trades that bounced between
|
||||
the min an max of the candle. That could tell us the above the max and below the min, the order would be matched
|
||||
with orders at the order book. For that reason, we do this:
|
||||
*/
|
||||
switch (true) {
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value > tradingEngine.current.episode.candle.max.value) {
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderStatistics.actualRate.id, tradingEngine.current.episode.candle.max.id],
|
||||
'Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') too high. Changed to candle.max (' + tradingEngine.current.episode.candle.max.value + ')'
|
||||
]
|
||||
)
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.max.value
|
||||
}
|
||||
break
|
||||
}
|
||||
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value < tradingEngine.current.episode.candle.min.value) {
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderStatistics.actualRate.id, tradingEngine.current.episode.candle.min.id],
|
||||
'Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') too low. Changed to candle.min (' + tradingEngine.current.episode.candle.max.value + ')'
|
||||
]
|
||||
)
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.min.value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
}
|
||||
|
||||
function recalculateActualSize() {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
/*
|
||||
Now that we have an Actual Rate, we need to adjust the Actual Size in Quoted Asset, since
|
||||
Base Asset is what the exchange accepts as input and give information at. Quotes Asset properties are always calculated.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.actualRate.value
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineOrder.orderQuotedAsset.actualSize.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Actual Size (' + previousQuotedAssetActualSize + ') recalculated (' + tradingEngineOrder.orderQuotedAsset.actualSize.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
function recalculateSizePlaced() {
|
||||
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
|
||||
/*
|
||||
Since the Actual Rate might have changed, we need to recalculate the Size Placed, where we accumulate all the Size Placed of
|
||||
all orders of a Stage. For Base Asset there is nothing to do, since the Actual Rate does not have an inpact on it.
|
||||
|
||||
For Quoted Asset, we need to first unaccount what this same orded added before to Size Placed (with the precious Actual Size) and
|
||||
account with the new Actual Size.
|
||||
*/
|
||||
let previousStageQuotedAssetSizePlaced = tradingEngineStage.stageQuotedAsset.sizePlaced.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
|
||||
previousQuotedAssetActualSize
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
|
||||
|
||||
tradingSystem.warnings.push(
|
||||
[
|
||||
[tradingEngineStage.stageQuotedAsset.sizePlaced.id, tradingEngineOrder.orderStatistics.actualRate.id],
|
||||
'Size Placed (' + previousStageQuotedAssetSizePlaced + ') recalculated (' + tradingEngineStage.stageQuotedAsset.sizePlaced.value + ') because the Actual Rate (' + tradingEngineOrder.orderStatistics.actualRate.value + ') is different than the Order Rate (' + tradingEngineOrder.rate.value + '))'
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function feesToBePaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
await applyFeePercentage(
|
||||
'feesToBePaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
100
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function percentageFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
When we are live, it is the exchange who knows which order was filled and in
|
||||
which amount. Since we are backtesting here, we need to simulate what happens
|
||||
at the exchange. We can not simulate that an order is filled if the candle we
|
||||
see did not touch the order.
|
||||
|
||||
For limit buy orders we are going to check if the price was below the order actual rate, and
|
||||
for limit sell orders we are going to check if the price was above the order actual rate.
|
||||
|
||||
If we have a partial filling defined specifically for this order,
|
||||
we are going to use it to estimate how much of the order we will consider filled.
|
||||
*/
|
||||
let orderWasHit
|
||||
switch (tradingSystemOrder.type) {
|
||||
case 'Limit Buy Order': {
|
||||
if (tradingEngine.current.episode.candle.min.value <= tradingEngineOrder.orderStatistics.actualRate.value) {
|
||||
orderWasHit = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Limit Sell Order': {
|
||||
if (tradingEngine.current.episode.candle.max.value >= tradingEngineOrder.orderStatistics.actualRate.value) {
|
||||
orderWasHit = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Market Buy Order': {
|
||||
orderWasHit = true
|
||||
break
|
||||
}
|
||||
case 'Market Sell Order': {
|
||||
orderWasHit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (orderWasHit === true) {
|
||||
/*
|
||||
Initially, we will assume that 100% of the order was filled. This might be overwritten
|
||||
if there is a configuration for this order that details how much is must be considered
|
||||
filled each time we check this.
|
||||
*/
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = 100
|
||||
|
||||
/* Based on the Trading System Definition */
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) {
|
||||
|
||||
/* Percentage Filled */
|
||||
let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100
|
||||
if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) {
|
||||
percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
}
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function feesPaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
The way the Fess Paid are calculated is as follows: From the order Actual Size we apply the % of fee,
|
||||
that would give us the total fees for that order. To that we apply the % filled of the orders,
|
||||
that give use the final result of the fees paid for the current % filled.
|
||||
|
||||
But before that, we will see if there is an specific definition on how to simulate the fees for
|
||||
this order, or if there is not, we use the Fees Structure defined at the Session Parameters.
|
||||
*/
|
||||
|
||||
/* Based on the Trading System Definition */
|
||||
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
|
||||
applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Another way to simulate the Fees Paid is by using the Session Paremeters configuration for Fees.
|
||||
*/
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.taker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
applyFeePercentage(
|
||||
'feesPaid',
|
||||
tradingEngineOrder,
|
||||
tradingSystemOrder,
|
||||
sessionParameters.feeStructure.config.maker,
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sizeFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
|
||||
/*
|
||||
Size Filled is the amount of Base Asset or Quoted Asset that at the exchange matches
|
||||
another counterparty order. We calculate this using the Percentage Filled we already simulated.
|
||||
*/
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value =
|
||||
tradingEngineOrder.orderBaseAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
|
||||
tradingEngineOrder.orderQuotedAsset.actualSize.value *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
|
||||
}
|
||||
|
||||
async function amountReceivedSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
|
||||
/*
|
||||
This simulation is for informational purposes, so that users do not have to calculate
|
||||
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
|
||||
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
|
||||
are selling then it will be in Quoted Asset.
|
||||
*/
|
||||
|
||||
switch (true) {
|
||||
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Base Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderBaseAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
|
||||
/*
|
||||
In this case the Amount Received is in Quoted Asset.
|
||||
*/
|
||||
tradingEngineOrder.orderQuotedAsset.amountReceived.value =
|
||||
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
|
||||
function initialize() {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
@@ -322,7 +322,7 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
|
||||
fileContent = "" + fileContent + "";
|
||||
|
||||
let fileName = pFileName + '.csv';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + SNAPSHOTS_FOLDER_NAME;
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + SNAPSHOTS_FOLDER_NAME;
|
||||
filePath += '/' + fileName
|
||||
|
||||
fileStorage.createTextFile(filePath, fileContent + '\n', onFileCreated);
|
||||
|
||||
@@ -25,7 +25,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
function initialize() {
|
||||
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
initializeNodeMap(tradingEngine)
|
||||
|
||||
@@ -66,6 +66,8 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
|
||||
function setCurrentCycle(cycle) {
|
||||
tradingEngine.current.episode.cycle.value = cycle
|
||||
tradingEngine.current.episode.cycle.lastBegin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
tradingEngine.current.episode.cycle.lastEnd.value = tradingEngine.current.episode.cycle.end.value
|
||||
switch (cycle) {
|
||||
case 'First': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
@@ -89,6 +91,17 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
break
|
||||
}
|
||||
}
|
||||
/*
|
||||
We can not have the last begin or last end to be zero, because that would prevent
|
||||
objects starting with lastBegin in zero to being saved on file. For that reason
|
||||
we will do this:
|
||||
*/
|
||||
if (tradingEngine.current.episode.cycle.lastBegin.value === 0) {
|
||||
tradingEngine.current.episode.cycle.lastBegin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
}
|
||||
if (tradingEngine.current.episode.cycle.lastEnd.value === 0) {
|
||||
tradingEngine.current.episode.cycle.lastEnd.value = tradingEngine.current.episode.cycle.end.value
|
||||
}
|
||||
}
|
||||
|
||||
function cloneValues(originNode, destinationNode) {
|
||||
|
||||
@@ -23,7 +23,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
@@ -47,24 +47,27 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
/*
|
||||
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.
|
||||
or the task / session was restarted. We will only execute if the episode was never opened before.
|
||||
*/
|
||||
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
|
||||
if (tradingEngine.current.episode.begin.value !== tradingEngine.current.episode.begin.config.initialValue) { return }
|
||||
|
||||
/* 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
|
||||
/* Starting begin and end */
|
||||
tradingEngine.current.episode.begin.value = tradingEngine.current.episode.candle.begin.value
|
||||
tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value
|
||||
|
||||
/* 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
|
||||
}
|
||||
/* Getting the begin Balance from the session configuration */
|
||||
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value = sessionParameters.sessionBaseAsset.config.initialBalance
|
||||
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
|
||||
|
||||
/* The current balance is also the begin balance, that is how this starts. */
|
||||
tradingEngine.current.episode.episodeBaseAsset.balance.value = sessionParameters.sessionBaseAsset.config.initialBalance
|
||||
tradingEngine.current.episode.episodeQuotedAsset.balance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
|
||||
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
tradingEngine.current.episode.status.value = 'Open'
|
||||
tradingEngine.current.episode.serialNumber.value = 1
|
||||
tradingEngine.current.episode.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.episode.beginRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
|
||||
function updateExitType(exitType) {
|
||||
@@ -85,7 +88,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.episode.status.value === 'Open') {
|
||||
tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.episode.end.value = tradingEngine.current.episode.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.episode.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.episode.episodeBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.episode.episodeQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
@@ -160,7 +163,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
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
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
tradingOrdersModule.initialize()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -3,7 +3,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM
|
||||
This module will load if necesary all the data outputs so that they can be appended with new
|
||||
records if needed. After running the simulation, it will save all the data outputs.
|
||||
*/
|
||||
const MODULE_NAME = 'Trading Bot'
|
||||
const MODULE_NAME = 'Trading Output'
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
@@ -133,7 +133,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
|
||||
await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
@@ -149,7 +149,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM
|
||||
|
||||
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
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
await readOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
@@ -190,7 +190,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM
|
||||
if (dataset.config.type === 'Market Files') {
|
||||
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel
|
||||
|
||||
await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
@@ -206,7 +206,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM
|
||||
|
||||
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
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.TRADING_SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
await writeOutputFile(fileName, filePath, dataset.parentNode.config.codeName)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
@@ -51,11 +51,14 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
|
||||
function openPosition(situationName) {
|
||||
|
||||
/* Starting begin and end */
|
||||
tradingEngine.current.position.begin.value = tradingEngine.current.episode.cycle.lastBegin.value
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
tradingEngine.current.position.status.value = 'Open'
|
||||
tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value + 1
|
||||
tradingEngine.current.position.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.position.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
|
||||
@@ -79,11 +82,20 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
function closingPosition(exitType) {
|
||||
tradingEngine.current.position.status.value = 'Closing'
|
||||
tradingEngine.current.position.exitType.value = exitType
|
||||
|
||||
/*
|
||||
By the time we figured out that the stop loss or take profit were hit, or
|
||||
for whatever reason the position needs to be closed, the take profit and
|
||||
stop loss values for the next candle have already been calculated. In order
|
||||
to avoid being plotted, we will put them in zero.
|
||||
*/
|
||||
tradingEngine.current.position.stopLoss.value = 0
|
||||
tradingEngine.current.position.takeProfit.value = 0
|
||||
}
|
||||
|
||||
function closePosition() {
|
||||
tradingEngine.current.position.status.value = 'Closed'
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
@@ -94,28 +106,68 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
tradingEngineModule.cloneValues(tradingEngine.current.position, tradingEngine.last.position)
|
||||
|
||||
cycleBasedStatistics()
|
||||
|
||||
|
||||
/* Updating Hits & Fails */
|
||||
if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) {
|
||||
tradingEngine.current.episode.episodeBaseAsset.hits.value++
|
||||
}
|
||||
}
|
||||
if (tradingEngine.current.position.positionBaseAsset.profitLoss.value < 0) {
|
||||
tradingEngine.current.episode.episodeBaseAsset.fails.value++
|
||||
}
|
||||
if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) {
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hits.value++
|
||||
}
|
||||
}
|
||||
if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value < 0) {
|
||||
tradingEngine.current.episode.episodeQuotedAsset.fails.value++
|
||||
}
|
||||
}
|
||||
|
||||
function applyStopLossFormula(formulas, formulaId) {
|
||||
updateStopLossTakeProfitFinalValue(tradingEngine.current.position.stopLoss)
|
||||
tradingEngine.current.position.stopLoss.value = formulas.get(formulaId)
|
||||
updateStopLossTakeProfitInitialValue(tradingEngine.current.position.stopLoss)
|
||||
updateStopLossTakeProfitBeginEnd(tradingEngine.current.position.stopLoss)
|
||||
}
|
||||
|
||||
function applyTakeProfitFormula(formulas, formulaId) {
|
||||
updateStopLossTakeProfitFinalValue(tradingEngine.current.position.takeProfit)
|
||||
tradingEngine.current.position.takeProfit.value = formulas.get(formulaId)
|
||||
updateStopLossTakeProfitInitialValue(tradingEngine.current.position.takeProfit)
|
||||
updateStopLossTakeProfitBeginEnd(tradingEngine.current.position.takeProfit)
|
||||
}
|
||||
|
||||
function updateStopLossTakeProfitInitialValue(node) {
|
||||
/*
|
||||
We store the first Stop Loss or Take Profit value in a separate node.
|
||||
To know if it is the first one we compare its current value to the initial value.
|
||||
*/
|
||||
if (node.initialValue.value === node.initialValue.config.initialValue) {
|
||||
node.initialValue.value = node.value
|
||||
}
|
||||
}
|
||||
|
||||
function updateStopLossTakeProfitFinalValue(node) {
|
||||
/*
|
||||
We set the final value just before calcualting the new value, since the new calculated
|
||||
value is going to be discarded if the position is closed after it is calculated, but
|
||||
during the same candle.
|
||||
*/
|
||||
node.finalValue.value = node.value
|
||||
}
|
||||
|
||||
function updateStopLossTakeProfitBeginEnd(node) {
|
||||
/*
|
||||
Both the Stop Loss and the Take Profit have their own Begin and End.
|
||||
Ther reason for this is because they represent targets to be checked
|
||||
at the next candle, so it does not apply that they share the begin and
|
||||
end of the position itself.
|
||||
*/
|
||||
node.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
node.end.value =
|
||||
tradingEngine.current.episode.candle.end.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
}
|
||||
|
||||
function updateStopLoss(phase) {
|
||||
@@ -186,7 +238,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
const message = 'Target Size In Base Asset cannot be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
}
|
||||
if (value === undefined) {
|
||||
if (value === 0) {
|
||||
const message = 'Target Size In Base Asset cannot be zero. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
}
|
||||
@@ -222,7 +274,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
const message = 'Target Size In Quoted Asset cannot be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
}
|
||||
if (value === undefined) {
|
||||
if (value === 0) {
|
||||
const message = 'Target Size In Quoted Asset cannot be zero. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
}
|
||||
@@ -244,8 +296,8 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
/* 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)
|
||||
const message = 'You need to specify a Formula for this.'
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +305,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.position.status.value === 'Open') {
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.position.end.value = tradingEngine.current.position.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.position.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.position.positionBaseAsset.endBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.endBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
@@ -305,7 +357,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
/* 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'
|
||||
}
|
||||
@@ -314,7 +366,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
}
|
||||
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'
|
||||
}
|
||||
@@ -348,7 +400,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
/* 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'
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
This variable tell us which day we are standing at, specially while working
|
||||
with Daily Files. From this Date is that we are going to load the Daily Files.
|
||||
*/
|
||||
bot.simulationState.tradingEngine.current.episode.processDate.value = global.REMOVE_TIME(bot.SESSION.parameters.timeRange.config.initialDatetime).valueOf()
|
||||
bot.simulationState.tradingEngine.current.episode.processDate.value = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime).valueOf()
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -119,7 +119,7 @@
|
||||
await processSingleFiles()
|
||||
|
||||
if (await processMarketFiles() === false) {
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
@@ -141,17 +141,17 @@
|
||||
With all the indicators data files loaded, we will build the chart object
|
||||
data structure that will be used in user-defied conditions and formulas.
|
||||
*/
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Waking up')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
buildCharts(chart)
|
||||
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Running')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Saving')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Sleeping')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date. No candles found at.')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
@@ -159,7 +159,7 @@
|
||||
} else {
|
||||
/* We are processing Daily Files */
|
||||
do {
|
||||
global.EMIT_SESSION_STATUS(bot.SESSION_STATUS, bot.sessionKey)
|
||||
global.EMIT_SESSION_STATUS(bot.TRADING_SESSION_STATUS, bot.TRADING_SESSIONKey)
|
||||
/*
|
||||
We update the Trading Process Date with the date calculated at the simulation.
|
||||
We will use this date to load indicator and output files. After that we will
|
||||
@@ -171,9 +171,9 @@
|
||||
if (checkStopTaskGracefully() === false) { break }
|
||||
if (checkStopProcessing() === false) { break }
|
||||
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Waking up')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
if (await processDailyFiles() === false) {
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
@@ -186,13 +186,13 @@
|
||||
The process of generating the output includes the trading simulation.
|
||||
*/
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Running')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Saving')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Sleeping')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date')
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
@@ -219,7 +219,7 @@
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
function checkThereAreCandles(chart) {
|
||||
let sessionParameters = bot.SESSION.parameters
|
||||
let sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
|
||||
let candles = chart[propertyName].candles
|
||||
|
||||
@@ -236,7 +236,7 @@
|
||||
let statusReport;
|
||||
|
||||
/* We are going to use the start date as beging of market date. */
|
||||
contextVariables.dateBeginOfMarket = global.REMOVE_TIME(bot.SESSION.parameters.timeRange.config.initialDatetime)
|
||||
contextVariables.dateBeginOfMarket = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)
|
||||
/*
|
||||
Here we get the status report from the bot who knows which is the end of the market.
|
||||
*/
|
||||
@@ -371,7 +371,7 @@
|
||||
|
||||
dataFiles = new Map();
|
||||
|
||||
if (bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel) {
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.marketFilesPeriods[n][0];
|
||||
currentTimeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
}
|
||||
@@ -384,7 +384,7 @@
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -431,8 +431,8 @@
|
||||
let dataRecord = dataFile[i]
|
||||
let begin = dataRecord[beginIndex]
|
||||
let end = dataRecord[endIndex]
|
||||
if (end + timeFrame < bot.SESSION.parameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element
|
||||
if (begin > bot.SESSION.parameters.timeRange.config.finalDatetime) { continue }
|
||||
if (end + timeFrame < bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element
|
||||
if (begin > bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime) { continue }
|
||||
result.push(dataRecord)
|
||||
}
|
||||
return result
|
||||
@@ -468,7 +468,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel) {
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.dailyFilePeriods[n][0];
|
||||
currentTimeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
}
|
||||
@@ -488,7 +488,7 @@
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.SESSION.parameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -653,7 +653,7 @@
|
||||
|
||||
let fileContent = JSON.stringify(dataRange);
|
||||
let fileName = '/Data.Range.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName;
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
|
||||
@@ -692,7 +692,7 @@
|
||||
let fileContent = JSON.stringify(timeFramesArray)
|
||||
let fileName = '/Time.Frames.json';
|
||||
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.SESSION.folderName + "/" + productCodeName + "/" + processType + fileName;
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + processType + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
@@ -728,12 +728,12 @@
|
||||
|
||||
function checkIfSessionMustStop() {
|
||||
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
/*
|
||||
Backtests needs only one execution of this process to complete.
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] checkIfSessionMustStop -> Backtesting Session Finished. Stopping the Session now. ') }
|
||||
bot.SESSION.stop('Backtesting Session Finished.')
|
||||
bot.TRADING_SESSION.stop('Backtesting Session Finished.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
function initialize(pOutputDatasetsMap) {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
outputDatasetsMap = pOutputDatasetsMap // These are the files turned into arrays, stored in a Map by Product codeName.
|
||||
}
|
||||
|
||||
@@ -143,16 +143,19 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
the Root Node specifically for this property only.
|
||||
*/
|
||||
if (recordProperty.config.nodePath !== undefined) {
|
||||
if (recordProperty.config.nodePath === "tradingEngine.exchangeOrders.marketBuyOrders.marketOrders[index].orderBaseAsset") {
|
||||
let a = tradingEngine
|
||||
}
|
||||
try {
|
||||
propertyRootNode = eval(recordProperty.config.nodePath)
|
||||
} catch (err) {
|
||||
badDefinitionUnhandledException(err, 'Error Evaluation Record Property nodePath -> nodePath = ' + recordProperty.config.nodePath, product, recordProperty)
|
||||
badDefinitionUnhandledException(err, 'Error Evaluating Record Property nodePath -> nodePath = ' + recordProperty.config.nodePath, product, recordProperty)
|
||||
}
|
||||
}
|
||||
/*
|
||||
The Target Node is the node from where we are going to exctract the value.
|
||||
We will use the codeName of the Record Property to match it with
|
||||
the any of the properties of the Root Node to get the Target Node.
|
||||
any of the properties of the Root Node to get the Target Node.
|
||||
*/
|
||||
let targetNode
|
||||
try {
|
||||
@@ -284,8 +287,31 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* When we are not dealing with objects, we add every record to the existing file. */
|
||||
outputDatasetArray.push(record)
|
||||
/*
|
||||
When we are not dealing with objects, we add every record to the existing file except
|
||||
for the ones that are filtered out at the Product Definition.
|
||||
*/
|
||||
if (product.config.codeName === "Trading-System-Errors") {
|
||||
let a = 1
|
||||
}
|
||||
if (product.config.propertyNameThatDefinesStatus !== undefined && product.config.propertyValueThatPreventsSavingObject !== undefined) {
|
||||
for (let j = 0; j < product.record.properties.length; j++) {
|
||||
let recordProperty = product.record.properties[j]
|
||||
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesStatus) {
|
||||
let propertyValue = record[j]
|
||||
|
||||
if (product.config.propertyValueThatPreventsSavingObject === "->Empty Array->" && propertyValue.length === 0) {
|
||||
break
|
||||
}
|
||||
if (propertyValue !== product.config.propertyValueThatPreventsSavingObject) {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outputDatasetArray.push(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
the execution at each loop cycle of the Trading System Protocol.
|
||||
*/
|
||||
const FULL_LOG = true
|
||||
const MODULE_NAME = 'Trading Simulation -> ' + bot.SESSION.name
|
||||
const MODULE_NAME = 'Trading Simulation -> ' + bot.TRADING_SESSION.name
|
||||
|
||||
let thisObject = {
|
||||
finalize: finalize,
|
||||
@@ -28,7 +28,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
|
||||
let tradingSystem = bot.simulationState.tradingSystem
|
||||
let tradingEngine = bot.simulationState.tradingEngine
|
||||
let sessionParameters = bot.SESSION.parameters
|
||||
let sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) }
|
||||
@@ -53,7 +53,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
/* Variables needed for heartbeat functionality */
|
||||
let heartBeatDate
|
||||
let previousHeartBeatDate
|
||||
let firstLoopExecution = true
|
||||
/*
|
||||
Estimation of the Initial Candle to Process in this Run.
|
||||
*/
|
||||
@@ -72,8 +71,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
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. ' ) }
|
||||
bot.TRADING_SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.')
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Data is not up-to-date enough. Stopping the Session now. ') }
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -107,7 +106,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
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]
|
||||
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()) }
|
||||
@@ -117,10 +116,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
/* 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
|
||||
}
|
||||
/* Opening the Episode, if needed. */
|
||||
tradingEpisodeModule.openEpisode()
|
||||
|
||||
if (checkInitialDatetime() === false) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Before the Initia Date Time @ ' + (new Date(candle.begin)).toLocaleString()) }
|
||||
@@ -213,16 +210,16 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
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. ' ) }
|
||||
bot.TRADING_SESSION.stop('Final Datetime Reached')
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Final Datetime Reached. Stopping the Session now. ') }
|
||||
return
|
||||
}
|
||||
|
||||
if (checkMinimunAndMaximunBalance() === false) {
|
||||
closeEpisode('Min or Max Balance Reached')
|
||||
breakLoop = true
|
||||
bot.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. ' ) }
|
||||
bot.TRADING_SESSION.stop('Min or Max Balance Reached')
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Min or Max Balance Reached. Stopping the Session now. ') }
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -279,7 +276,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
|
||||
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) }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.TRADING_SESSIONKey + ' Loop # ' + tradingEngine.current.episode.candle.index.value + ' @ ' + processingDate) }
|
||||
|
||||
/* Logging to console and disk */
|
||||
if (global.areEqualDates(currentDate, new Date()) === false) {
|
||||
@@ -287,23 +284,23 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
}
|
||||
|
||||
/* Date only hearbeat */
|
||||
if (sessionParameters.heartbeats.config.date === true && sessionParameters.heartbeats.config.candleIndex === false) {
|
||||
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 ) {
|
||||
if (sessionParameters.heartbeats.config.candleIndex === true) {
|
||||
if (sessionParameters.heartbeats.config.date === true) {
|
||||
hartbeatText = hartbeatText + currentDateString
|
||||
}
|
||||
hartbeatText = hartbeatText + ' Candle # ' + tradingEngine.current.episode.candle.index.value
|
||||
hartbeatText = hartbeatText + ' Candle # ' + tradingEngine.current.episode.candle.index.value
|
||||
bot.processHeartBeat(hartbeatText, percentage)
|
||||
}
|
||||
}
|
||||
@@ -420,7 +417,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
The second +1 is because we need to compare the next candle (remember that the loops allways avoid the
|
||||
last candle of the dataset available.)
|
||||
*/
|
||||
if (tradingEngine.current.episode.candle.index.value + 1 + 1 === candles.length ) {
|
||||
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
|
||||
|
||||
@@ -47,7 +47,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
tradingStrategyModule.initialize()
|
||||
tradingPositionModule.initialize()
|
||||
@@ -160,7 +160,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
announcementsModule.makeAnnoucements(triggerStage.triggerOn)
|
||||
announcementsModule.makeAnnoucements(triggerStage)
|
||||
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
snapshotsModule.strategyEntry()
|
||||
@@ -245,7 +245,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
announcementsModule.makeAnnoucements(triggerStage.takePosition)
|
||||
announcementsModule.makeAnnoucements(strategy.openStage)
|
||||
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.position === true) {
|
||||
snapshotsModule.positionEntry()
|
||||
@@ -393,6 +393,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
|
||||
function runManageStage() {
|
||||
|
||||
if (tradingEngine.current.episode.cycle.value !== 'First') { return }
|
||||
|
||||
runWhenStatusIsOpening()
|
||||
runWhenStatusIsOpen()
|
||||
|
||||
@@ -428,144 +430,110 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
/* Checking if Stop or Take Profit were hit */
|
||||
checkStopLossOrTakeProfitWasHit()
|
||||
}
|
||||
}
|
||||
|
||||
function calculateStopLoss() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
function calculateStopLoss() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1]
|
||||
}
|
||||
}
|
||||
|
||||
if (phase !== undefined) {
|
||||
if (phase.formula !== undefined) {
|
||||
let previousValue = tradingEngine.current.position.stopLoss.value
|
||||
tradingPositionModule.applyStopLossFormula(tradingSystem.formulas, phase.formula.id)
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.value !== previousValue) {
|
||||
announcementsModule.makeAnnoucements(phase)
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateTakeProfit() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
if (phase !== undefined) {
|
||||
if (phase.formula !== undefined) {
|
||||
let previousValue = tradingEngine.current.position.stopLoss.value
|
||||
tradingPositionModule.applyStopLossFormula(tradingSystem.formulas, phase.formula.id)
|
||||
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1]
|
||||
}
|
||||
}
|
||||
|
||||
if (phase !== undefined) {
|
||||
if (phase.formula !== undefined) {
|
||||
let previousValue = tradingEngine.current.position.takeProfit.value
|
||||
tradingPositionModule.applyTakeProfitFormula(tradingSystem.formulas, phase.formula.id)
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.value !== previousValue) {
|
||||
announcementsModule.makeAnnoucements(phase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateStopLossPosition() {
|
||||
/*
|
||||
The position of the Stop Loss (above or below the price) is needed in order to know
|
||||
later if the price hit the Stop Loss or not at every Simulation candle. When we get
|
||||
the first values of the simulation of the Stop Loss we check if it is above or below
|
||||
the Position Rate, and we assign the values Above or Below to it.
|
||||
*/
|
||||
if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) {
|
||||
if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.entryTargetRate.value) {
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above'
|
||||
} else {
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateTakeProfitPosition() {
|
||||
/*
|
||||
The position of the Take Profit (above or below the price) is needed in order to know
|
||||
later if the price hit the Take Profit or not at every Simulation candle. When we get
|
||||
the first values of the simulation of the Take Profits we check if it is above or below
|
||||
the Position Rates, and we assign the values Above or Below to it.
|
||||
*/
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) {
|
||||
if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.entryTargetRate.value) {
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above'
|
||||
} else {
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopPhasesEvents() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
let parentNode
|
||||
let phaseIndex
|
||||
let phase
|
||||
let stopLoss
|
||||
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1
|
||||
stopLoss = manageStage.managedStopLoss
|
||||
phase = stopLoss.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
|
||||
if (parentNode !== undefined) {
|
||||
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
|
||||
|
||||
/* Check the next Phase Event. */
|
||||
let nextPhaseEvent = phase.nextPhaseEvent
|
||||
if (nextPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
|
||||
let situation = nextPhaseEvent.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(nextPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(stopLoss.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1)
|
||||
|
||||
announcementsModule.makeAnnoucements(nextPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
if (tradingEngine.current.position.stopLoss.value !== previousValue) {
|
||||
announcementsModule.makeAnnoucements(phase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the Move to Phase Events. */
|
||||
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
|
||||
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
|
||||
if (moveToPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
|
||||
let situation = moveToPhaseEvent.situations[k]
|
||||
function calculateTakeProfit() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1]
|
||||
}
|
||||
}
|
||||
|
||||
if (phase !== undefined) {
|
||||
if (phase.formula !== undefined) {
|
||||
let previousValue = tradingEngine.current.position.takeProfit.value
|
||||
tradingPositionModule.applyTakeProfitFormula(tradingSystem.formulas, phase.formula.id)
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.value !== previousValue) {
|
||||
announcementsModule.makeAnnoucements(phase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateStopLossPosition() {
|
||||
/*
|
||||
The position of the Stop Loss (above or below the price) is needed in order to know
|
||||
later if the price hit the Stop Loss or not at every Simulation candle. When we get
|
||||
the first values of the simulation of the Stop Loss we check if it is above or below
|
||||
the Position Rate, and we assign the values Above or Below to it.
|
||||
*/
|
||||
if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) {
|
||||
if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.entryTargetRate.value) {
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above'
|
||||
} else {
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateTakeProfitPosition() {
|
||||
/*
|
||||
The position of the Take Profit (above or below the price) is needed in order to know
|
||||
later if the price hit the Take Profit or not at every Simulation candle. When we get
|
||||
the first values of the simulation of the Take Profits we check if it is above or below
|
||||
the Position Rates, and we assign the values Above or Below to it.
|
||||
*/
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) {
|
||||
if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.entryTargetRate.value) {
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above'
|
||||
} else {
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopPhasesEvents() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
let parentNode
|
||||
let phaseIndex
|
||||
let phase
|
||||
let stopLoss
|
||||
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1
|
||||
stopLoss = manageStage.managedStopLoss
|
||||
phase = stopLoss.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
|
||||
if (parentNode !== undefined) {
|
||||
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
|
||||
|
||||
/* Check the next Phase Event. */
|
||||
let nextPhaseEvent = phase.nextPhaseEvent
|
||||
if (nextPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
|
||||
let situation = nextPhaseEvent.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
@@ -576,95 +544,95 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(moveToPhaseEvent.id)
|
||||
tradingSystem.highlights.push(nextPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(stopLoss.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < stopLoss.phases.length; q++) {
|
||||
if (stopLoss.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateStopLoss(q + 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
|
||||
continue
|
||||
tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1)
|
||||
|
||||
announcementsModule.makeAnnoucements(nextPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the Move to Phase Events. */
|
||||
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
|
||||
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
|
||||
if (moveToPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
|
||||
let situation = moveToPhaseEvent.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
}
|
||||
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
passed = tradingSystem.checkConditions(situation, passed)
|
||||
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(moveToPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(stopLoss.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < stopLoss.phases.length; q++) {
|
||||
if (stopLoss.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateStopLoss(q + 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
|
||||
continue
|
||||
}
|
||||
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkTakeProfitPhaseEvents() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let parentNode
|
||||
let phaseIndex
|
||||
let phase
|
||||
let takeProfit
|
||||
function checkTakeProfitPhaseEvents() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let parentNode
|
||||
let phaseIndex
|
||||
let phase
|
||||
let takeProfit
|
||||
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1
|
||||
takeProfit = manageStage.managedTakeProfit
|
||||
phase = takeProfit.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
|
||||
if (parentNode !== undefined) {
|
||||
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
|
||||
|
||||
/* Check the next Phase Event. */
|
||||
let nextPhaseEvent = phase.nextPhaseEvent
|
||||
if (nextPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
|
||||
let situation = nextPhaseEvent.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(nextPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(takeProfit.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1)
|
||||
|
||||
announcementsModule.makeAnnoucements(nextPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1
|
||||
takeProfit = manageStage.managedTakeProfit
|
||||
phase = takeProfit.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the Move to Phase Events. */
|
||||
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
|
||||
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
|
||||
if (moveToPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
|
||||
let situation = moveToPhaseEvent.situations[k]
|
||||
if (parentNode !== undefined) {
|
||||
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
|
||||
|
||||
/* Check the next Phase Event. */
|
||||
let nextPhaseEvent = phase.nextPhaseEvent
|
||||
if (nextPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
|
||||
let situation = nextPhaseEvent.situations[k]
|
||||
let passed
|
||||
if (situation.conditions.length > 0) {
|
||||
passed = true
|
||||
@@ -675,84 +643,118 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingSystem.values.push([situation.id, passed])
|
||||
if (passed) {
|
||||
tradingSystem.highlights.push(situation.id)
|
||||
tradingSystem.highlights.push(moveToPhaseEvent.id)
|
||||
tradingSystem.highlights.push(nextPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(takeProfit.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < takeProfit.phases.length; q++) {
|
||||
if (takeProfit.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateTakeProfit(q + 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
|
||||
continue
|
||||
}
|
||||
tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1)
|
||||
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
announcementsModule.makeAnnoucements(nextPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the Move to Phase Events. */
|
||||
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
|
||||
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
|
||||
if (moveToPhaseEvent !== undefined) {
|
||||
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
|
||||
let situation = moveToPhaseEvent.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(moveToPhaseEvent.id)
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(takeProfit.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < takeProfit.phases.length; q++) {
|
||||
if (takeProfit.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateTakeProfit(q + 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
|
||||
continue
|
||||
}
|
||||
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopLossOrTakeProfitWasHit() {
|
||||
{
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
/*
|
||||
Checking what happened since the last execution. We need to know if the Stop Loss
|
||||
or our Take Profit were hit.
|
||||
*/
|
||||
function checkStopLossOrTakeProfitWasHit() {
|
||||
{
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
/*
|
||||
Checking what happened since the last execution. We need to know if the Stop Loss
|
||||
or our Take Profit were hit.
|
||||
*/
|
||||
|
||||
/* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */
|
||||
if (
|
||||
(
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Above' &&
|
||||
tradingEngine.current.episode.candle.max.value >= tradingEngine.current.position.stopLoss.value
|
||||
) ||
|
||||
(
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Below' &&
|
||||
tradingEngine.current.episode.candle.min.value <= tradingEngine.current.position.stopLoss.value
|
||||
)
|
||||
) {
|
||||
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.')
|
||||
/* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */
|
||||
if (
|
||||
(
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Above' &&
|
||||
tradingEngine.current.episode.candle.max.value >= tradingEngine.current.position.stopLoss.value
|
||||
) ||
|
||||
(
|
||||
tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Below' &&
|
||||
tradingEngine.current.episode.candle.min.value <= tradingEngine.current.position.stopLoss.value
|
||||
)
|
||||
) {
|
||||
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.')
|
||||
|
||||
tradingPositionModule.closingPosition('Stop Loss')
|
||||
changeStageStatus('Close Stage', 'Opening')
|
||||
changeStageStatus('Manage Stage', 'Closed')
|
||||
announcementsModule.makeAnnoucements(strategy.closeStage)
|
||||
tradingPositionModule.closingPosition('Stop Loss')
|
||||
changeStageStatus('Close Stage', 'Opening')
|
||||
changeStageStatus('Manage Stage', 'Closed')
|
||||
announcementsModule.makeAnnoucements(strategy.closeStage)
|
||||
return
|
||||
}
|
||||
|
||||
/* Take Profit condition: Here we verify if the Take Profit was hit or not. */
|
||||
if (
|
||||
(
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Below' &&
|
||||
tradingEngine.current.episode.candle.min.value <= tradingEngine.current.position.takeProfit.value
|
||||
) ||
|
||||
(
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Above' &&
|
||||
tradingEngine.current.episode.candle.max.value >= tradingEngine.current.position.takeProfit.value
|
||||
)
|
||||
) {
|
||||
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.')
|
||||
|
||||
tradingPositionModule.closingPosition('Take Profit')
|
||||
changeStageStatus('Close Stage', 'Opening')
|
||||
changeStageStatus('Manage Stage', 'Closed')
|
||||
announcementsModule.makeAnnoucements(strategy.closeStage)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/* Take Profit condition: Here we verify if the Take Profit was hit or not. */
|
||||
if (
|
||||
(
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Below' &&
|
||||
tradingEngine.current.episode.candle.min.value <= tradingEngine.current.position.takeProfit.value
|
||||
) ||
|
||||
(
|
||||
tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Above' &&
|
||||
tradingEngine.current.episode.candle.max.value >= tradingEngine.current.position.takeProfit.value
|
||||
)
|
||||
) {
|
||||
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.')
|
||||
|
||||
tradingPositionModule.closingPosition('Take Profit')
|
||||
changeStageStatus('Close Stage', 'Opening')
|
||||
changeStageStatus('Manage Stage', 'Closed')
|
||||
announcementsModule.makeAnnoucements(strategy.closeStage)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -786,12 +788,12 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
to be known before we start placing orders at the Close Stage.
|
||||
*/
|
||||
if (
|
||||
tradingEngine.current.strategyCloseStage.status.value === 'Open' &&
|
||||
(
|
||||
tradingEngine.current.strategyOpenStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyOpenStage.status.value == tradingEngine.current.strategyOpenStage.status.config.initialValue
|
||||
)
|
||||
) {
|
||||
tradingEngine.current.strategyCloseStage.status.value === 'Open' &&
|
||||
(
|
||||
tradingEngine.current.strategyOpenStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyOpenStage.status.value == tradingEngine.current.strategyOpenStage.status.config.initialValue
|
||||
)
|
||||
) {
|
||||
/*
|
||||
This will happen as long as the Close Stage is Open.
|
||||
*/
|
||||
@@ -854,7 +856,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
|
||||
function closePositionAndStrategy() {
|
||||
/* Taking Position Snapshot */
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.position === true) {
|
||||
snapshotsModule.positionExit()
|
||||
@@ -863,7 +865,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
/* Taking Strategy Snapshot */
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
if (sessionParameters.snapshots.config.strategy === true) {
|
||||
snapshotsModule.strategyExit()
|
||||
@@ -886,19 +888,28 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
|
||||
function checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, stageName) {
|
||||
/*
|
||||
The Stage is closed when the placedSize reaches the targetSize, and the fillSize
|
||||
+ feesPaid reaches the placedSize. This depends on how the user defined the stage:
|
||||
either on Base Asset or Quoted Asset.
|
||||
This can happens at any time when we update the sizeFilled and the feesPaid values
|
||||
when we see at the exchange that orders were filled.
|
||||
Note that the comparison is made with a Rounding Factor in order to avoid rounding problems.
|
||||
The Stage is closed when the fillSize + feesPaid reaches the targetSize.
|
||||
|
||||
The Target Size is defined by the end user either in Base Asset or Quoted Asset.
|
||||
Whatever the user chooses defines also the way we check if the Target was met.
|
||||
Why? Because the counterparty target is estimated with the Target Rate and it
|
||||
is not syncronized with the reality we later learn when the exchange tell us the
|
||||
actual rate each of the orders where executed at.
|
||||
|
||||
For the same reason that the user does not know exactly which is the rate the orders
|
||||
will actually be filled, then we can not expect that the total filled will really
|
||||
reach the target size, so we introduce a rounding factor so that when it is close
|
||||
enough we will consider the target to have been reached.
|
||||
*/
|
||||
const ROUNDING_ERROR_CORRECTION_FACTOR = 1.001
|
||||
const ROUNDING_ERROR_CORRECTION_FACTOR = 1.001
|
||||
|
||||
switch (tradingEngineStage.stageDefinedIn.value) {
|
||||
case 'Base Asset': {
|
||||
if (
|
||||
tradingEngineStage.stageBaseAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
(
|
||||
tradingEngineStage.stageBaseAsset.sizeFilled.value
|
||||
)
|
||||
* ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
>=
|
||||
tradingEngineStage.stageBaseAsset.targetSize.value
|
||||
) {
|
||||
@@ -910,7 +921,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
case 'Quoted Asset': {
|
||||
if (
|
||||
tradingEngineStage.stageQuotedAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
(
|
||||
tradingEngineStage.stageQuotedAsset.sizeFilled.value
|
||||
)
|
||||
* ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
>=
|
||||
tradingEngineStage.stageQuotedAsset.targetSize.value
|
||||
) {
|
||||
@@ -921,9 +935,11 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function positionFilled() {
|
||||
changeStageStatus(stageName, 'Closed', 'Position Size Filled')
|
||||
}
|
||||
|
||||
function checkCloseStageEvent(tradingSystemStage) {
|
||||
/* Check the Close Stage Event */
|
||||
tradingSystem.evalConditions(tradingSystemStage, 'Close Stage Event')
|
||||
@@ -990,35 +1006,50 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
closeStage(stage)
|
||||
}
|
||||
|
||||
/*
|
||||
The way the begining and end of the Stages object works is as follows:
|
||||
This object begins with the last cycle, that is the current cycle diplaced
|
||||
backwards to the current candle. And it ends at the end of the last cycle.
|
||||
During mantainance, we add the time frame of the candle, since we assume it
|
||||
will survive the 2 cycles without the end being updated. If it closes,
|
||||
then we update the end to the end of the last cycle, overrinding the
|
||||
mantainance if needed. If we assume that in the same cycle there will be
|
||||
no closing and opening of the same type of object, then there can be no
|
||||
overlap.
|
||||
*/
|
||||
function openStage(stage) {
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
stage.begin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
stage.begin.value = tradingEngine.current.episode.cycle.lastBegin.value
|
||||
stage.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
stage.beginRate.value = tradingEngine.current.episode.candle.close.value
|
||||
stage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
}
|
||||
|
||||
function closeStage(stage) {
|
||||
/* Recording the closing at the Trading Engine Data Structure */
|
||||
stage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
stage.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
stage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
}
|
||||
|
||||
function updateEnds() {
|
||||
/*
|
||||
Note that we can not use the cycle here because this is executed via mantain
|
||||
which in turn is executed before the first cycle for this candle is set.
|
||||
*/
|
||||
if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.strategyTriggerStage.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.strategyTriggerStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.strategyOpenStage.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.strategyOpenStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
if (tradingEngine.current.strategyManageStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.strategyManageStage.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.strategyManageStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
if (tradingEngine.current.strategyCloseStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.strategyCloseStage.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.strategyCloseStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,18 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
@@ -34,11 +37,14 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
|
||||
}
|
||||
|
||||
function openStrategy(index, situationName, strategyName) {
|
||||
/* Starting begin and end */
|
||||
tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.cycle.lastBegin.value
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
tradingEngine.current.strategy.status.value = 'Open'
|
||||
tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value
|
||||
tradingEngine.current.strategy.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.strategy.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
|
||||
@@ -52,7 +58,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
|
||||
function closeStrategy(exitType) {
|
||||
tradingEngine.current.strategy.status.value = 'Closed'
|
||||
tradingEngine.current.strategy.exitType.value = exitType
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.cycle.lastEnd.value
|
||||
tradingEngine.current.strategy.endRate.value = tradingEngine.current.episode.candle.min.value
|
||||
/*
|
||||
Now that the strategy is closed, it is the right time to move this strategy from current to last at the Trading Engine data structure.
|
||||
@@ -62,7 +68,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.strategy.status.value === 'Open') {
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.strategy.end.value + sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.strategy.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
let tradingSystem
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
let dynamicIndicators
|
||||
let dynamicIndicators
|
||||
|
||||
const TRADING_STAGES_MODULE = require('./TradingStages.js')
|
||||
let tradingStagesModule = TRADING_STAGES_MODULE.newTradingStages(bot, logger, tradingEngineModule)
|
||||
@@ -28,7 +28,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
function initialize() {
|
||||
tradingSystem = bot.simulationState.tradingSystem
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.SESSION.parameters
|
||||
sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
|
||||
tradingSystem.conditions = new Map()
|
||||
tradingSystem.formulas = new Map()
|
||||
@@ -131,8 +131,8 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
|
||||
for (let i = 0; i < tradingSystem.dynamicIndicators.indicatorFunctions.length; i++) {
|
||||
let indicatorFunction = tradingSystem.dynamicIndicators.indicatorFunctions[i]
|
||||
if (indicatorFunction.formula === undefined) { return}
|
||||
if (indicatorFunction.config.codeName === undefined) { return}
|
||||
if (indicatorFunction.formula === undefined) { return }
|
||||
if (indicatorFunction.config.codeName === undefined) { return }
|
||||
dynamicIndicators[indicatorFunction.config.codeName] = tradingSystem.formulas.get(indicatorFunction.formula.id)
|
||||
}
|
||||
}
|
||||
@@ -257,15 +257,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
will not be considered an error.
|
||||
*/
|
||||
value = false
|
||||
|
||||
if (code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1
|
||||
) {
|
||||
/*
|
||||
We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined.
|
||||
*/
|
||||
} else {
|
||||
error = err.message
|
||||
}
|
||||
error = err.message
|
||||
}
|
||||
|
||||
tradingSystem.conditions.set(node.id, value)
|
||||
@@ -297,19 +289,12 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
will not be considered an error.
|
||||
*/
|
||||
value = 0
|
||||
|
||||
if (node.code.indexOf('previous') > -1 && err.message.indexOf('of undefined') > -1
|
||||
) {
|
||||
/*
|
||||
We are not going to set an error for the casess we are using previous and the error is that the indicator is undefined.
|
||||
*/
|
||||
} else {
|
||||
error = err.message
|
||||
}
|
||||
error = err.message
|
||||
}
|
||||
|
||||
if (error !== undefined) {
|
||||
tradingSystem.errors.push([node.id, error])
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> error = ' + error)
|
||||
return
|
||||
}
|
||||
if (value !== undefined) {
|
||||
@@ -323,7 +308,6 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> value = ' + value)
|
||||
logger.write(MODULE_NAME, '[INFO] evalFormula -> error = ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -67,7 +67,6 @@
|
||||
function getContextVariables() {
|
||||
try {
|
||||
let thisReport;
|
||||
let reportKey;
|
||||
let statusReport;
|
||||
/*
|
||||
We look first for the bot who knows the begining of the market in order to get when the market starts.
|
||||
@@ -89,7 +88,7 @@
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.beginingOfMarket === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> reportKey = " + reportKey)
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport.beginingOfMarket === undefined ")
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> It is too early too run this process since the trade history of the market is not there yet.")
|
||||
|
||||
let customOK = {
|
||||
@@ -122,7 +121,7 @@
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.lastFile === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> reportKey = " + reportKey)
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport.lastFile === undefined")
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
@@ -139,7 +138,7 @@
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Self Reference")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Self Reference -> Status Report does not exist or Self Reference not defined. Retrying Later. ")
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Self Reference -> Status Report does not exist or Self Reference not defined or badly configured. Check that at the Status Dependency you have chosen Self Reference at the config. Retrying Later. ")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
@@ -157,7 +156,7 @@
|
||||
beginingOfMarket = new Date(thisReport.beginingOfMarket)
|
||||
|
||||
if (beginingOfMarket.valueOf() !== contextVariables.dateBeginOfMarket.valueOf()) { // Reset Mechanism for Begining of the Market
|
||||
logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Reset Mechanism for Begining of the Market Activated. -> reportKey = " + reportKey)
|
||||
logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Reset Mechanism for Begining of the Market Activated.")
|
||||
|
||||
beginingOfMarket = new Date(contextVariables.dateBeginOfMarket.valueOf())
|
||||
startFromBegining()
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
let thisObject = {
|
||||
networkNode: undefined,
|
||||
initialize, initialize,
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start,
|
||||
finish: finish
|
||||
|
||||
+10
-4
@@ -32,6 +32,9 @@
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Sensor Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Learning Bot')
|
||||
}
|
||||
|
||||
/* Some validations to verify that everything is in place. */
|
||||
if (outputDataset.referenceParent !== undefined) {
|
||||
@@ -86,7 +89,7 @@
|
||||
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 key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
lastFile: lastFile,
|
||||
timeFrames: timeFrames
|
||||
@@ -119,6 +122,9 @@
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Sensor Bot')
|
||||
}
|
||||
if (botNode === undefined) {
|
||||
botNode = global.FIND_NODE_IN_NODE_MESH(outputDataset, 'Learning Bot')
|
||||
}
|
||||
|
||||
/* Some validations to verify that everything is in place. */
|
||||
if (outputDataset.referenceParent !== undefined) {
|
||||
@@ -138,11 +144,11 @@
|
||||
}
|
||||
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));
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> codeName at Mine not defined. Mine = " + JSON.stringify(botNode.parentNode));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine or Trading Mine. Bot = " + JSON.stringify(botNode));
|
||||
logger.write(MODULE_NAME, "[ERROR] raiseEvents -> Bot not attached to a Data Mine | Trading Mine | Learning Mine. Bot = " + JSON.stringify(botNode));
|
||||
throw (global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
} else {
|
||||
@@ -165,7 +171,7 @@
|
||||
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 key = dataMine + "-" + botName + "-" + product + "-" + dataset + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
lastFile: lastFile,
|
||||
timeFrames: timeFrames
|
||||
|
||||
@@ -0,0 +1,685 @@
|
||||
exports.newLearningBot = function newLearningBot(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Learning Bot";
|
||||
const FULL_LOG = true;
|
||||
|
||||
const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/MachineLearningProcess.js');
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
const SESSION = require(global.ROOT_DIR + 'TradingSession');
|
||||
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(parentLogger);
|
||||
let session = SESSION.newLearningSession(bot, parentLogger)
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble.
|
||||
|
||||
let nextLoopTimeoutHandle;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize,
|
||||
run: run
|
||||
};
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
/* This function is exactly the same in the 3 modules representing the 2 different bot types loops. */
|
||||
try {
|
||||
processConfig = pProcessConfig;
|
||||
if (bot.definedByUI === true) {
|
||||
/* The code of the bot is defined at the UI. No need to load a file with the code. */
|
||||
session.initialize(processConfig, callBackFunction)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function run(callBackFunction) {
|
||||
try {
|
||||
/* Some initial values*/
|
||||
let fixedTimeLoopIntervalHandle;
|
||||
bot.STOP_SESSION = true;
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] run -> Stopping the Session now. ') }
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.processHeartBeat = processHeartBeat
|
||||
|
||||
loop();
|
||||
|
||||
function loop() {
|
||||
try {
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
/* For each loop we want to create a new log file. */
|
||||
if (logger !== undefined) {
|
||||
logger.finalize()
|
||||
}
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set(MODULE_NAME, logger)
|
||||
logger.bot = bot;
|
||||
logger.initialize();
|
||||
|
||||
bot.loopCounter++;
|
||||
bot.loopStartTime = new Date().valueOf();
|
||||
|
||||
let nextWaitTime;
|
||||
|
||||
/* We tell the UI that we are running. */
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
|
||||
/* We define here all the modules that the rest of the infraestructure, including the bots themselves can consume. */
|
||||
const UTILITIES = require(global.ROOT_DIR + 'CloudUtilities');
|
||||
const STATUS_REPORT = require(global.ROOT_DIR + 'StatusReport');
|
||||
const STATUS_DEPENDENCIES = require(global.ROOT_DIR + 'StatusDependencies');
|
||||
const DATA_DEPENDENCIES = require(global.ROOT_DIR + 'DataDependencies');
|
||||
const DATA_SET = require(global.ROOT_DIR + 'DataSet');
|
||||
const PROCESS_EXECUTION_EVENTS = require(global.ROOT_DIR + 'ProcessExecutionEvents');
|
||||
const PROCESS_OUTPUT = require(global.ROOT_DIR + 'ProcessOutput');
|
||||
|
||||
/* We define the datetime for the process that we are running now. This will be the official processing time for both the infraestructure and the bot. */
|
||||
bot.processDatetime = new Date(); // This will be considered the process date and time, so as to have it consistenly all over the execution.
|
||||
|
||||
/* High level log entry */
|
||||
console.log(new Date().toISOString() + " " + pad(bot.exchange, 20) + " " + pad(bot.market.baseAsset + '/' + bot.market.quotedAsset, 10) + " " + pad(bot.codeName, 30) + " " + pad(bot.process, 30)
|
||||
+ " Main Loop # " + pad(Number(bot.loopCounter), 8) + " " + bot.processNode.session.type + " " + bot.processNode.session.name)
|
||||
|
||||
/* Checking if we need to need to emit any event */
|
||||
if (bot.SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) {
|
||||
bot.SESSION_STATUS = 'Running'
|
||||
}
|
||||
|
||||
if (bot.SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) {
|
||||
bot.SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
|
||||
global.EMIT_SESSION_STATUS (bot.SESSION_STATUS, bot.sessionKey)
|
||||
|
||||
/* Checking if we should process this loop or not.*/
|
||||
if (bot.STOP_SESSION === true) {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run."); }
|
||||
|
||||
console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.process, 30)
|
||||
+ " Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ");
|
||||
|
||||
nextWaitTime = 'Waiting for Session';
|
||||
loopControl(nextWaitTime);
|
||||
return
|
||||
}
|
||||
|
||||
/* We will prepare first the infraestructure needed for the bot to run. There are 3 modules we need to sucessfullly initialize first. */
|
||||
|
||||
let processExecutionEvents
|
||||
let userBot;
|
||||
let processFramework;
|
||||
let statusDependencies;
|
||||
let dataDependencies;
|
||||
|
||||
initializeProcessExecutionEvents();
|
||||
|
||||
function initializeProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents = PROCESS_EXECUTION_EVENTS.newProcessExecutionEvents(bot, logger)
|
||||
processExecutionEvents.initialize(processConfig, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Execution finished well.");
|
||||
startProcessExecutionEvents()
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Retry Later. Requesting Execution Retry.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.start(onStarted);
|
||||
|
||||
function onStarted(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessExecutionEvents -> onStarted -> Execution finished well.");
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
loopControl()
|
||||
return
|
||||
}
|
||||
|
||||
initializeStatusDependencies();
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessExecutionEvents -> onStarted -> Retry Later. Requesting Execution Retry.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> onStarted -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeStatusDependencies() {
|
||||
try {
|
||||
statusDependencies = STATUS_DEPENDENCIES.newStatusDependencies(bot, logger, STATUS_REPORT, UTILITIES, PROCESS_OUTPUT);
|
||||
statusDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeStatusDependencies -> onInizialized -> Execution finished well.");
|
||||
initializeDataDependencies();
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeStatusDependencies -> onInizialized -> Retry Later. Requesting Execution Retry.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDataDependencies() {
|
||||
try {
|
||||
dataDependencies = DATA_DEPENDENCIES.newDataDependencies(bot, logger, DATA_SET);
|
||||
dataDependencies.initialize(onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); }
|
||||
switch (processConfig.framework.name) {
|
||||
case 'Low-Frequency-Trading-Process': {
|
||||
processFramework = TRADING_PROCESS_MODULE.newMachineLearningProcess(bot, logger, UTILITIES);
|
||||
intitializeProcessFramework();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework not Supported.");
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Process Framework Name = " + processConfig.framework.name);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> initializeDataDependencies -> onInizialized -> Retry Later. Requesting Execution Retry.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeDataDependencies -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function intitializeProcessFramework() {
|
||||
try {
|
||||
processFramework.initialize(processConfig, statusDependencies, dataDependencies, onInizialized);
|
||||
|
||||
function onInizialized(err) {
|
||||
try {
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> intitializeProcessFramework -> onInizialized -> Execution finished well.");
|
||||
startProcessFramework();
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> intitializeProcessFramework -> onInizialized -> Retry Later. Requesting Execution Retry.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized > Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> onInizialized -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> intitializeProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function startProcessFramework() {
|
||||
try {
|
||||
processFramework.start(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processFramework.finalize()
|
||||
processFramework = undefined
|
||||
dataDependencies.finalize()
|
||||
dataDependencies = undefined
|
||||
statusDependencies.finalize()
|
||||
statusDependencies = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> startProcessFramework -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
finishProcessExecutionEvents()
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Retry Later. Requesting Execution Retry.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
case global.CUSTOM_OK_RESPONSE.result: {
|
||||
|
||||
switch (err.message) {
|
||||
case "Dependency does not exist.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency does not exist. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case "Dependency not ready.": {
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> startProcessFramework -> onFinished -> Dependency not ready. This Loop will go to sleep.");
|
||||
nextWaitTime = 'Sleep';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled custom response received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> startProcessFramework -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function finishProcessExecutionEvents() {
|
||||
try {
|
||||
processExecutionEvents.finish(onFinished);
|
||||
|
||||
function onFinished(err) {
|
||||
try {
|
||||
processExecutionEvents.finalize()
|
||||
processExecutionEvents = undefined
|
||||
|
||||
switch (err.result) {
|
||||
case global.DEFAULT_OK_RESPONSE.result: {
|
||||
logger.write(MODULE_NAME, "[INFO] run -> loop -> finishProcessExecutionEvents -> onFinished -> Execution finished well.");
|
||||
nextWaitTime = 'Normal';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time.
|
||||
logger.write(MODULE_NAME, "[WARN] run -> loop -> finishProcessExecutionEvents -> onFinished -> Retry Later. Requesting Execution Retry.");
|
||||
nextWaitTime = 'Retry';
|
||||
loopControl(nextWaitTime);
|
||||
return;
|
||||
}
|
||||
case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle.
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Operation Failed. Aborting the process.");
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> Unhandled err.result received. -> err = " + err.message);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> onFinished -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> finishProcessExecutionEvents -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function loopControl(nextWaitTime) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> nextWaitTime = " + nextWaitTime); }
|
||||
|
||||
/* We show we reached the end of the loop. */
|
||||
processHeartBeat(undefined, undefined, "Running...")
|
||||
|
||||
/* Here we check if we must stop the loop gracefully. */
|
||||
shallWeStop(onStop, onContinue);
|
||||
|
||||
function onStop() {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> onStop -> Stopping the Loop Gracefully. See you next time!"); }
|
||||
|
||||
if (global.WRITE_LOGS_TO_FILES === 'true') {
|
||||
logger.persist();
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped')
|
||||
processStopped()
|
||||
return;
|
||||
}
|
||||
|
||||
function onContinue() {
|
||||
/* Indicator bots are going to be executed after a configured period of time after the last execution ended. This is to avoid overlapping executions. */
|
||||
switch (nextWaitTime) {
|
||||
case 'Waiting for Session': {
|
||||
let waitTime = processConfig.sessionRunWaitTime
|
||||
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
|
||||
let waitingTime = waitTime / 1000 / 60
|
||||
let label = 'minute/s'
|
||||
if (waitingTime < 1) {
|
||||
waitingTime = waitTime / 1000
|
||||
label = 'seconds'
|
||||
}
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ")
|
||||
logger.persist();
|
||||
}
|
||||
break
|
||||
case 'Normal': {
|
||||
let waitTime
|
||||
if (processConfig.waitsForExecutionFinishedEvent === true) {
|
||||
waitTime = 0
|
||||
} else {
|
||||
switch (bot.LEARNING_SESSION.type) {
|
||||
case 'Live Trading Session': {
|
||||
waitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Fordward Tessting Session': {
|
||||
waitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
waitTime = bot.LEARNING_SESSION.learningParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Backtesting Session': {
|
||||
waitTime = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (waitTime / 1000 / 60) + " minute/s."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, waitTime);
|
||||
let waitingTime = waitTime / 1000 / 60
|
||||
let label = 'minute/s'
|
||||
if (waitingTime < 1) {
|
||||
waitingTime = waitTime / 1000
|
||||
label = 'seconds'
|
||||
}
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Retry': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.retryWaitTime / 1000) + " seconds."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.retryWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Trying to recover from some problem. Waiting " + processConfig.retryWaitTime / 1000 + " seconds for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Sleep': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.sleepWaitTime / 60000) + " minutes."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.sleepWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.sleepWaitTime / 60000 + " minutes for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
case 'Coma': {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> loopControl -> Restarting Loop in " + (processConfig.comaWaitTime / 3600000) + " hours."); }
|
||||
nextLoopTimeoutHandle = setTimeout(loop, processConfig.comaWaitTime);
|
||||
processHeartBeat(undefined, undefined, "Waiting " + processConfig.comaWaitTime / 3600000 + " hours for next execution.")
|
||||
logger.persist();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shallWeStop(stopCallBack, continueCallBack) {
|
||||
try {
|
||||
/* IMPORTANT: This function is exactly the same on the 3 modules. */
|
||||
if (!global.STOP_TASK_GRACEFULLY) {
|
||||
continueCallBack();
|
||||
} else {
|
||||
stopCallBack();
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> shallWeStop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> loop -> err = " + err.stack);
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function processHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Heartbeat', event)
|
||||
}
|
||||
|
||||
function processStopped() {
|
||||
if (global.unexpectedError !== undefined) {
|
||||
global.PROCESS_ERROR(bot.processKey, undefined, "An unexpected error caused the Process to stop.")
|
||||
} else {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.processKey, 'Stopped')
|
||||
}
|
||||
sessionStopped()
|
||||
logger.persist();
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
if (global.unexpectedError !== undefined) {
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
|
||||
} else {
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function sessionStopped() {
|
||||
if (bot.SESSION_STATUS === 'Running') {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped')
|
||||
bot.SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] run -> err = " + err.stack);
|
||||
clearInterval(fixedTimeLoopIntervalHandle);
|
||||
clearTimeout(nextLoopTimeoutHandle);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,786 @@
|
||||
exports.newLearningProcess = function newLearningProcess(bot, logger, UTILITIES) {
|
||||
/*
|
||||
This Module will load all the process data dependencies from files and send them downstream.
|
||||
After execution, will save the time range and status report of the process.
|
||||
*/
|
||||
const FULL_LOG = true;
|
||||
const MODULE_NAME = "Learning Process";
|
||||
const GMT_SECONDS = ':00.000 GMT+0000';
|
||||
|
||||
thisObject = {
|
||||
initialize: initialize,
|
||||
finalize: finalize,
|
||||
start: start
|
||||
};
|
||||
|
||||
let utilities = UTILITIES.newCloudUtilities(logger);
|
||||
|
||||
let statusDependencies;
|
||||
let dataDependenciesModule;
|
||||
let dataFiles = new Map();
|
||||
let multiPeriodDataFiles = new Map();
|
||||
|
||||
const FILE_STORAGE = require('../FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger);
|
||||
|
||||
const MARKOV_DECISION_PROCESS_MODULE = require('./MarkovDecisionProcess.js')
|
||||
let markovDecisionProcessModule = MARKOV_DECISION_PROCESS_MODULE.newMarkovDecisionProcess(bot, logger)
|
||||
|
||||
let ALGORITHM_MODULE = require("./Algorithm")
|
||||
let algorithmModule = ALGORITHM_MODULE.newTradingOutput(bot, logger, markovDecisionProcessModule, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, pStatusDependencies, pDataDependencies, callBackFunction) {
|
||||
try {
|
||||
logger.fileName = MODULE_NAME
|
||||
logger.initialize()
|
||||
|
||||
statusDependencies = pStatusDependencies
|
||||
dataDependenciesModule = pDataDependencies
|
||||
processConfig = pProcessConfig
|
||||
|
||||
algorithmModule.initialize()
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
|
||||
markovDecisionProcessModule.finalize()
|
||||
markovDecisionProcessModule = undefined
|
||||
|
||||
algorithmModule.finalize()
|
||||
algorithmModule = undefined
|
||||
|
||||
dataFiles = undefined
|
||||
multiPeriodDataFiles = undefined
|
||||
statusDependencies = undefined
|
||||
dataDependenciesModule = undefined
|
||||
fileStorage = undefined
|
||||
processConfig = undefined
|
||||
thisObject = undefined
|
||||
}
|
||||
|
||||
async function start(callBackFunction) {
|
||||
try {
|
||||
let currentTimeFrame
|
||||
let currentTimeFrameLabel
|
||||
|
||||
/* Context Variables */
|
||||
let contextVariables = {
|
||||
lastFile: undefined, // Datetime of the last file files sucessfully produced by this process.
|
||||
dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history.
|
||||
dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process.
|
||||
};
|
||||
|
||||
getContextVariables()
|
||||
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
Here is where the Trading Engine and Trading Systems received are moved to the simulation state.
|
||||
*/
|
||||
bot.simulationState.tradingEngine = bot.TRADING_ENGINE
|
||||
bot.simulationState.tradingSystem = bot.TRADING_SYSTEM
|
||||
}
|
||||
|
||||
/* We set up the Trading Engine Module. */
|
||||
markovDecisionProcessModule.initialize()
|
||||
|
||||
/* Initializing the Trading Process Date */
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
This funcion is going to be called many times by the Trading Bot Loop.
|
||||
Only during the first execution and when the User is not resuming the execution
|
||||
of a stopped session / task; we are going to initialize the Process Date Time.
|
||||
This variable tell us which day we are standing at, specially while working
|
||||
with Daily Files. From this Date is that we are going to load the Daily Files.
|
||||
*/
|
||||
bot.simulationState.tradingEngine.current.episode.processDate.value = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime).valueOf()
|
||||
}
|
||||
|
||||
/*
|
||||
This is the Date that is going to be used across the execution of this Trading Process.
|
||||
We need this because it has a different life cycle than the processData stored at the
|
||||
Trading Engine data structure. This date has to remain the same during the whole execution
|
||||
of the Trading Process until the end, inclusind the writting of Data Ranges and Status Reports.
|
||||
The processDate of the Trading Engine data structure on the other hand can be changed during
|
||||
the simulation loop, once we discover that all candles from a certain date have benn processed.
|
||||
Here is the point where we sync one and the other.
|
||||
*/
|
||||
let LearningProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
await processSingleFiles()
|
||||
|
||||
if (await processMarketFiles() === false) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
This is the Data Structure used at the Simulation with all indicator data.
|
||||
We start creating it right here.
|
||||
*/
|
||||
let chart = {}
|
||||
/*
|
||||
Here we check if we need to get Daily Files or not. As an optimization, when
|
||||
we are running on a Time Frame of 1hs or above, we are not going to load
|
||||
dependencies on Daily Files. The way we recognize that is by checking if
|
||||
we alreaady set a value to currentTimeFrame. We are also not going to loop
|
||||
through days if we are processing market files.
|
||||
*/
|
||||
if (currentTimeFrame) {
|
||||
/* We are processing Market Files */
|
||||
/*
|
||||
With all the indicators data files loaded, we will build the chart object
|
||||
data structure that will be used in user-defied conditions and formulas.
|
||||
*/
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
buildCharts(chart)
|
||||
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date. No candles found at.')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
/* We are processing Daily Files */
|
||||
do {
|
||||
global.EMIT_SESSION_STATUS(bot.TRADING_SESSION_STATUS, bot.TRADING_SESSIONKey)
|
||||
/*
|
||||
We update the Trading Process Date with the date calculated at the simulation.
|
||||
We will use this date to load indicator and output files. After that we will
|
||||
use it to save Output Files and later the Data Ranges. This is the point where
|
||||
the date calculated by the Simulation is applied at the Trading Process Level.
|
||||
*/
|
||||
LearningProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
|
||||
if (checkStopTaskGracefully() === false) { break }
|
||||
if (checkStopProcessing() === false) { break }
|
||||
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waking up')
|
||||
if (await processDailyFiles() === false) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be run')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
With all the indicators data files loaded, we will build the chart object
|
||||
data structure that will be used in user-defied conditions and formulas.
|
||||
*/
|
||||
buildCharts(chart)
|
||||
/*
|
||||
The process of generating the output includes the trading simulation.
|
||||
*/
|
||||
if (checkThereAreCandles(chart) === true) {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Running')
|
||||
await generateOutput(chart)
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Saving')
|
||||
await writeProcessFiles()
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Sleeping')
|
||||
} else {
|
||||
bot.TRADING_SESSIONHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date')
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
|
||||
return
|
||||
}
|
||||
/*
|
||||
If for any reason the session was stopped, we will break this loop and exit the process.
|
||||
*/
|
||||
if (bot.STOP_SESSION === true) { break }
|
||||
/*
|
||||
When we get to the end of the market, we need to break this process loop in order
|
||||
to let time pass, new information be collected from the exchange, new data built
|
||||
into indicators, and eventually a new execution of this process.
|
||||
*/
|
||||
if (checkStopHeadOfTheMarket() === false) { break }
|
||||
|
||||
}
|
||||
while (true)
|
||||
}
|
||||
|
||||
checkIfSessionMustStop()
|
||||
/*
|
||||
Everything worked as expected. We return an OK code and wait for
|
||||
the Bot Loop to call us again later.
|
||||
*/
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
|
||||
function checkThereAreCandles(chart) {
|
||||
let sessionParameters = bot.TRADING_SESSION.tradingParameters
|
||||
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
|
||||
let candles = chart[propertyName].candles
|
||||
|
||||
if (candles.length === 0) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function getContextVariables() {
|
||||
try {
|
||||
let thisReport;
|
||||
let statusReport;
|
||||
|
||||
/* We are going to use the start date as beging of market date. */
|
||||
contextVariables.dateBeginOfMarket = global.REMOVE_TIME(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)
|
||||
/*
|
||||
Here we get the status report from the bot who knows which is the end of the market.
|
||||
*/
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Market Ending Point")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because dependecy Status Report is corrupt. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
|
||||
if (thisReport.lastFile === undefined) {
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Undefined Last File. -> thisReport = " + JSON.stringify(thisReport));
|
||||
|
||||
let customOK = {
|
||||
result: global.CUSTOM_OK_RESPONSE.result,
|
||||
message: "Dependency not ready."
|
||||
}
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> customOK = " + customOK.message);
|
||||
callBackFunction(customOK);
|
||||
return
|
||||
}
|
||||
|
||||
contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf());
|
||||
|
||||
/* Finally we get our own Status Report. */
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Self Reference")
|
||||
|
||||
if (statusReport === undefined) { // This means the status report does not exist, that could happen for instance at the begining of a month.
|
||||
logger.write(MODULE_NAME, "[WARN] start -> getContextVariables -> Status Report does not exist. Retrying Later. ");
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (statusReport.status === "Status Report is corrupt.") {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> Can not continue because self dependecy Status Report is corrupt. Aborting Process.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
thisReport = statusReport.file;
|
||||
bot.simulationState = thisReport.simulationState;
|
||||
if (bot.simulationState === undefined) { bot.simulationState = {} } // This should happen only when there is no status report
|
||||
|
||||
if (thisReport.lastFile !== undefined) {
|
||||
if (bot.RESUME !== true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining because bot has just started and resume execution was true."); }
|
||||
startFromBegining();
|
||||
return
|
||||
}
|
||||
contextVariables.lastFile = new Date(thisReport.lastFile);
|
||||
return
|
||||
|
||||
} else {
|
||||
/*
|
||||
We are here becuse either:
|
||||
1. There is no status report
|
||||
2. There is no Last File (this happens on Market Files)
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> Starting from the begining of the market because own status report not found or lastFile was undefined."); }
|
||||
startFromBegining();
|
||||
return
|
||||
}
|
||||
|
||||
function startFromBegining() {
|
||||
contextVariables.lastFile = new Date(contextVariables.dateBeginOfMarket.getUTCFullYear() + "-" + (contextVariables.dateBeginOfMarket.getUTCMonth() + 1) + "-" + contextVariables.dateBeginOfMarket.getUTCDate() + " " + "00:00" + GMT_SECONDS);
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> startFromBegining -> contextVariables.lastFile = " + contextVariables.lastFile); }
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> err = " + err.stack);
|
||||
if (err.message === "Cannot read property 'file' of undefined") {
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Check the bot configuration to see if all of its statusDependencies declarations are correct. ");
|
||||
logger.write(MODULE_NAME, "[HINT] start -> getContextVariables -> Dependencies loaded -> keys = " + JSON.stringify(statusDependencies.keys));
|
||||
}
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
async function processSingleFiles() {
|
||||
dataFiles = new Map();
|
||||
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (datasetModule.node.config.codeName !== "Single-File") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency('atAnyTimeFrame', datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
continue
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = datasetModule.node.parentNode.config.codeName + '/' + datasetModule.node.config.codeName;
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
}
|
||||
|
||||
let mapKey = "Single Files"
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
|
||||
async function processMarketFiles() {
|
||||
/*
|
||||
We do market files first since if the simulation is run on daily files, there will
|
||||
be a loop to get each of those files and we do not need that loop to reload market files.
|
||||
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
for (let n = 0; n < global.marketFilesPeriods.length; n++) {
|
||||
const timeFrame = global.marketFilesPeriods[n][0];
|
||||
const timeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
|
||||
dataFiles = new Map();
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.marketFilesPeriods[n][0];
|
||||
currentTimeFrameLabel = global.marketFilesPeriods[n][1];
|
||||
}
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Market") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let fileName = "Data.json";
|
||||
let filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel;
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.message === 'File does not exist.') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record)
|
||||
dataFiles.set(dependency.id, trimmedDataFile);
|
||||
|
||||
function trimDataFile(dataFile, recordDefinition) {
|
||||
/*
|
||||
Here we will discard all the records in a file that are outside of the current time range.
|
||||
We will include the las element previous to the begining of the time range. This is needed
|
||||
because during the simulation, the current period is not the open one, but the previous to
|
||||
the open, and if we do not include the previous to the initial datetime there will be no
|
||||
current objects at the begining of the simulation for many time frames.
|
||||
*/
|
||||
let beginIndex
|
||||
let endIndex
|
||||
let result = []
|
||||
for (let i = 0; i < recordDefinition.properties.length; i++) {
|
||||
let property = recordDefinition.properties[i]
|
||||
if (property.config.codeName === 'begin') {
|
||||
beginIndex = i
|
||||
}
|
||||
if (property.config.codeName === 'end') {
|
||||
endIndex = i
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < dataFile.length; i++) {
|
||||
let dataRecord = dataFile[i]
|
||||
let begin = dataRecord[beginIndex]
|
||||
let end = dataRecord[endIndex]
|
||||
if (end + timeFrame < bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime - 1) { continue } // /1 because we need the previous closed element
|
||||
if (begin > bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime) { continue }
|
||||
result.push(dataRecord)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
let mapKey = global.marketFilesPeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function processDailyFiles() {
|
||||
/* Telling the world we are alive and doing well and which date we are processing right now. */
|
||||
let processingDateString = LearningProcessDate.getUTCFullYear() + '-' + utilities.pad(LearningProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(LearningProcessDate.getUTCDate(), 2);
|
||||
bot.processHeartBeat(processingDateString, undefined, "Running...")
|
||||
|
||||
/*
|
||||
We will iterate through all posible timeFrames.
|
||||
*/
|
||||
for (let n = 0; n < global.dailyFilePeriods.length; n++) {
|
||||
const timeFrame = global.dailyFilePeriods[n][0];
|
||||
const timeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
|
||||
if (processConfig.framework.validtimeFrames !== undefined) {
|
||||
let validPeriod = false;
|
||||
for (let i = 0; i < processConfig.framework.validtimeFrames.length; i++) {
|
||||
let period = processConfig.framework.validtimeFrames[i];
|
||||
if (period === timeFrameLabel) { validPeriod = true }
|
||||
}
|
||||
if (validPeriod === false) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel) {
|
||||
currentTimeFrame = global.dailyFilePeriods[n][0];
|
||||
currentTimeFrameLabel = global.dailyFilePeriods[n][1];
|
||||
}
|
||||
|
||||
dataFiles = new Map();
|
||||
|
||||
/*
|
||||
We will iterate through all dependencies, in order to load the
|
||||
files that later will end up at the chart data structure.
|
||||
*/
|
||||
for (let dependencyIndex = 0; dependencyIndex < dataDependenciesModule.nodeArray.length; dependencyIndex++) {
|
||||
let dependency = dataDependenciesModule.nodeArray[dependencyIndex];
|
||||
let datasetModule = dataDependenciesModule.dataSetsModulesArray[dependencyIndex];
|
||||
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (dataDependenciesModule.isItADepenency(timeFrameLabel, datasetModule.node.parentNode.config.singularVariableName) !== true) {
|
||||
if (!(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === timeFrameLabel && datasetModule.node.parentNode.config.pluralVariableName === 'candles')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let dateForPath = LearningProcessDate.getUTCFullYear() + '/' + utilities.pad(LearningProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(LearningProcessDate.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath;
|
||||
} else {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + dateForPath;
|
||||
}
|
||||
let fileName = "Data.json";
|
||||
|
||||
/* We cut the async calls via callBacks at this point, so as to have a clearer code upstream */
|
||||
let response = await asyncGetDatasetFile(datasetModule, filePath, fileName)
|
||||
|
||||
if (response.err.message === 'File does not exist.') {
|
||||
return false
|
||||
}
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(response.text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
}
|
||||
|
||||
let mapKey = global.dailyFilePeriods[n][1];
|
||||
multiPeriodDataFiles.set(mapKey, dataFiles)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkStopHeadOfTheMarket() {
|
||||
/*
|
||||
We need to check if we have reached the head of the market in order to know
|
||||
when to break the Daily Files Process loop and give time for a new candles /
|
||||
indicators to be built and continue the processing once this process is called
|
||||
again.
|
||||
*/
|
||||
if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopTaskGracefully() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopProcessing() {
|
||||
/* Validation that we dont need to stop. */
|
||||
if (bot.STOP_SESSION === true) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function buildCharts(chart) {
|
||||
const COMMONS = require('../Commons.js')
|
||||
let commons = COMMONS.newCommons(bot, logger, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let mainDependency = {}
|
||||
|
||||
/* The first phase here is about checking that we have everything we need at the definition level. */
|
||||
let dataDependencies = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processDependencies, 'Data Dependency')
|
||||
/*
|
||||
We will filter ourt declared dependencies that are not present in the workspace.
|
||||
This will allow the user to have less Data Mines loaded at the workspace
|
||||
that the ones that a Trading Mine depends on.
|
||||
*/
|
||||
dataDependencies = global.FILTER_OUT_NODES_WITHOUT_REFERENCE_PARENT_FROM_NODE_ARRAY(dataDependencies)
|
||||
|
||||
if (commons.validateDataDependencies(dataDependencies, callBackFunction) !== true) { return }
|
||||
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
if (commons.validateOutputDatasets(outputDatasets, callBackFunction) !== true) { return }
|
||||
|
||||
/* The second phase is about transforming the inputs into a format that can be used to apply the user defined code. */
|
||||
for (let j = 0; j < global.marketFilesPeriods.length; j++) {
|
||||
let timeFrameLabel = marketFilesPeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
|
||||
let timeFrameLabel = global.dailyFilePeriods[j][1]
|
||||
let dataFiles = multiPeriodDataFiles.get(timeFrameLabel)
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'at' + timeFrameLabel.replace('-', '')
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
/* Single Files */
|
||||
let dataFiles = multiPeriodDataFiles.get('Single Files')
|
||||
let products = {}
|
||||
|
||||
if (dataFiles !== undefined) {
|
||||
commons.inflateDatafiles(dataFiles, dataDependencies, products, mainDependency, currentTimeFrame)
|
||||
|
||||
let propertyName = 'atAnyTimeFrame'
|
||||
chart[propertyName] = products
|
||||
}
|
||||
}
|
||||
|
||||
async function generateOutput(chart) {
|
||||
await algorithmModule.start(
|
||||
chart,
|
||||
currentTimeFrame,
|
||||
currentTimeFrameLabel,
|
||||
LearningProcessDate
|
||||
)
|
||||
|
||||
/*
|
||||
From here on, all other loops executions wont be the first execution and also
|
||||
we will consider that it is not resuming a previous execution as well.
|
||||
*/
|
||||
bot.FIRST_EXECUTION = false
|
||||
bot.RESUME = false
|
||||
}
|
||||
|
||||
async function writeProcessFiles() {
|
||||
let outputDatasets = global.NODE_BRANCH_TO_ARRAY(bot.processNode.referenceParent.processOutput, 'Output Dataset')
|
||||
|
||||
await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel)
|
||||
await writeDataRanges()
|
||||
|
||||
if (currentTimeFrame > global.dailyFilePeriods[0][0]) {
|
||||
await writeMarketStatusReport(currentTimeFrameLabel)
|
||||
} else {
|
||||
await writeDailyStatusReport(currentTimeFrameLabel)
|
||||
}
|
||||
|
||||
async function writeDataRanges() {
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeDataRange(productCodeName);
|
||||
}
|
||||
|
||||
async function writeDataRange(productCodeName) {
|
||||
let dataRange = {
|
||||
begin: contextVariables.dateBeginOfMarket.valueOf(),
|
||||
end: LearningProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS
|
||||
}
|
||||
|
||||
let fileContent = JSON.stringify(dataRange);
|
||||
let fileName = '/Data.Range.json';
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + 'Multi-Period-Daily' + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
|
||||
/*
|
||||
Raise the event that the Data Range was Updated.
|
||||
*/
|
||||
let key = bot.dataMine + "-" + bot.codeName + "-" + productCodeName + "-" + bot.exchange + "-" + bot.market.baseAsset + '/' + bot.market.quotedAsset
|
||||
let event = {
|
||||
dateRange: dataRange
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Data Range Updated', event)
|
||||
}
|
||||
}
|
||||
|
||||
async function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) {
|
||||
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily')
|
||||
await writeTimeFramesFile(currentTimeFrameLabel, productCodeName, 'Multi-Period-Market')
|
||||
|
||||
async function writeTimeFramesFile(currentTimeFrameLabel, productCodeName, processType) {
|
||||
|
||||
let timeFramesArray = []
|
||||
timeFramesArray.push(currentTimeFrameLabel)
|
||||
|
||||
let fileContent = JSON.stringify(timeFramesArray)
|
||||
let fileName = '/Time.Frames.json';
|
||||
|
||||
let filePath = bot.filePathRoot + "/Output/" + bot.TRADING_SESSION.folderName + "/" + productCodeName + "/" + processType + fileName;
|
||||
|
||||
let response = await fileStorage.asyncCreateTextFile(filePath, fileContent + '\n')
|
||||
if (response.err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
throw (response.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writeDailyStatusReport(currentTimeFrameLabel) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey)
|
||||
|
||||
thisReport.file.lastExecution = bot.currentDaytime
|
||||
thisReport.file.lastFile = LearningProcessDate
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
await thisReport.asyncSave()
|
||||
}
|
||||
|
||||
async function writeMarketStatusReport(currentTimeFrameLabel) {
|
||||
let reportKey = bot.dataMine + "-" + bot.codeName + "-" + bot.processNode.referenceParent.config.codeName
|
||||
let thisReport = statusDependencies.statusReports.get(reportKey);
|
||||
|
||||
thisReport.file.lastExecution = bot.processDatetime
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
|
||||
logger.newInternalLoop(bot.codeName, bot.process, LearningProcessDate)
|
||||
await thisReport.asyncSave()
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfSessionMustStop() {
|
||||
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
/*
|
||||
Backtests needs only one execution of this process to complete.
|
||||
*/
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] checkIfSessionMustStop -> Backtesting Session Finished. Stopping the Session now. ') }
|
||||
bot.TRADING_SESSION.stop('Backtesting Session Finished.')
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
/* An unhandled exception occured. in this case we return Fail and log the stack. */
|
||||
if (err.stack) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Unhandled Exception. Will Abort this process. err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* Some expected file was not found. We will return a RETRY code and move on. */
|
||||
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] File not Found. Will Retry the Process Loop.")
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
/* Some other handled exception occured. We return Fail and move on. */
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> Handled Exception. Will Abort this process. err = " + err.message);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pad(str, max) {
|
||||
str = str.toString();
|
||||
return str.length < max ? pad(" " + str, max) : str;
|
||||
}
|
||||
|
||||
async function asyncGetDatasetFile(datasetModule, filePath, fileName) {
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
|
||||
datasetModule.getTextFile(filePath, fileName, onFileReceived);
|
||||
function onFileReceived(err, text) {
|
||||
|
||||
let response = {
|
||||
err: err,
|
||||
text: text
|
||||
}
|
||||
resolve(response)
|
||||
}
|
||||
});
|
||||
|
||||
return promise
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
exports.newMarkovDecisionProcess = function newMarkovDecisionProcess(bot, logger) {
|
||||
/*
|
||||
We call the Trading Engine to the data structure that is needed in order to exevute the
|
||||
trading protocol with the specific rules defined at the Trading System.
|
||||
*/
|
||||
const MODULE_NAME = 'Markov Decision Process'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
getNodeById: getNodeById,
|
||||
cloneValues: cloneValues,
|
||||
setCurrentCandle: setCurrentCandle,
|
||||
setCurrentCycle: setCurrentCycle,
|
||||
initializeNode: initializeNode,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
}
|
||||
|
||||
let tradingEngine
|
||||
let sessionParameters
|
||||
let nodesMap = new Map()
|
||||
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
|
||||
tradingEngine = bot.simulationState.tradingEngine
|
||||
sessionParameters = bot.LEARNING_SESSION.learningParameters
|
||||
|
||||
initializeNodeMap(tradingEngine)
|
||||
|
||||
if (bot.FIRST_EXECUTION === true) {
|
||||
/*
|
||||
Here we will go through all the nodes in the Trading Engine hiriarchy and
|
||||
apply the initial value to the value property when needed
|
||||
*/
|
||||
initializeNode(tradingEngine)
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
tradingEngine = undefined
|
||||
sessionParameters = undefined
|
||||
nodesMap = undefined
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
}
|
||||
|
||||
function reset() {
|
||||
|
||||
}
|
||||
|
||||
function getNodeById(NodeId) {
|
||||
return nodesMap.get(NodeId)
|
||||
}
|
||||
|
||||
function setCurrentCandle(candle) {
|
||||
tradingEngine.current.episode.candle.begin.value = candle.begin
|
||||
tradingEngine.current.episode.candle.end.value = candle.end
|
||||
tradingEngine.current.episode.candle.open.value = candle.open
|
||||
tradingEngine.current.episode.candle.close.value = candle.close
|
||||
tradingEngine.current.episode.candle.min.value = candle.min
|
||||
tradingEngine.current.episode.candle.max.value = candle.max
|
||||
}
|
||||
|
||||
function setCurrentCycle(cycle) {
|
||||
tradingEngine.current.episode.cycle.value = cycle
|
||||
tradingEngine.current.episode.cycle.lastBegin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
tradingEngine.current.episode.cycle.lastEnd.value = tradingEngine.current.episode.cycle.end.value
|
||||
switch (cycle) {
|
||||
case 'First': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2 - 1
|
||||
break
|
||||
}
|
||||
case 'Second': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value * 4 / 4 - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cloneValues(originNode, destinationNode) {
|
||||
if (originNode === undefined) { return }
|
||||
if (destinationNode === undefined) { return }
|
||||
|
||||
/* Here copy the node value */
|
||||
destinationNode.value = originNode.value
|
||||
|
||||
/* Now we go down through all the children of the origin node, assuming the destination node has the same children structure*/
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(originNode.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
cloneValues(originNode[property.name], destinationNode[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (originNode[property.name] !== undefined && destinationNode[property.name] !== undefined) {
|
||||
let originNodePropertyArray = originNode[property.name]
|
||||
let destinationNodePropertyArray = destinationNode[property.name]
|
||||
for (let m = 0; m < originNodePropertyArray.length; m++) {
|
||||
cloneValues(originNodePropertyArray[m], destinationNodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNodeMap(node) {
|
||||
if (node === undefined) { return }
|
||||
/* Add to Node Map */
|
||||
nodesMap.set(node.id, node)
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
initializeNodeMap(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
initializeNodeMap(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initializeNode(node) {
|
||||
if (node === undefined) { return }
|
||||
|
||||
/* Here we initialize the node value */
|
||||
if (node.config !== undefined) {
|
||||
if (node.config.initialValue !== undefined) {
|
||||
node.value = node.config.initialValue
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we go down through all this node children */
|
||||
let nodeDefinition = global.APP_SCHEMA_MAP.get(node.type)
|
||||
if (nodeDefinition === undefined) { return }
|
||||
|
||||
if (nodeDefinition.properties !== undefined) {
|
||||
let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children.
|
||||
for (let i = 0; i < nodeDefinition.properties.length; i++) {
|
||||
let property = nodeDefinition.properties[i]
|
||||
|
||||
switch (property.type) {
|
||||
case 'node': {
|
||||
if (property.name !== previousPropertyName) {
|
||||
if (node[property.name] !== undefined) {
|
||||
initializeNode(node[property.name])
|
||||
}
|
||||
previousPropertyName = property.name
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (node[property.name] !== undefined) {
|
||||
let nodePropertyArray = node[property.name]
|
||||
for (let m = 0; m < nodePropertyArray.length; m++) {
|
||||
initializeNode(nodePropertyArray[m])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +266,10 @@
|
||||
runTradingBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Learning Bot': {
|
||||
runLearningBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected bot type. -> botConfig.type = " + botConfig.type);
|
||||
}
|
||||
@@ -296,6 +300,10 @@
|
||||
runTradingBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Learning Bot': {
|
||||
runLearningBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected bot type. -> botConfig.type = " + botConfig.type);
|
||||
}
|
||||
@@ -339,6 +347,10 @@
|
||||
runTradingBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
case 'Learning Bot': {
|
||||
runLearningBot(botConfig, processConfig);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> Unexpected bot type. -> botConfig.type = " + botConfig.type);
|
||||
}
|
||||
@@ -540,6 +552,71 @@
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
|
||||
function runLearningBot(pBotConfig, pProcessConfig) {
|
||||
|
||||
global.TOTAL_PROCESS_INSTANCES_CREATED++
|
||||
|
||||
try {
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger;
|
||||
|
||||
logger = DEBUG_MODULE.newDebugLog();
|
||||
global.LOGGER_MAP.set('runLearningBot', logger)
|
||||
logger.bot = pBotConfig;
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runLearningBot -> Entering function."); }
|
||||
|
||||
let learningBot = TRADING_BOT_MODULE.newLearningBot(pBotConfig, logger);
|
||||
learningBot.initialize(pProcessConfig, onInitializeReady);
|
||||
|
||||
function onInitializeReady(err) {
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
learningBot.run(whenRunFinishes);
|
||||
|
||||
function whenRunFinishes(err) {
|
||||
|
||||
pBotConfig.loopCounter = 0;
|
||||
|
||||
let botId;
|
||||
|
||||
botId = pBotConfig.dataMine + "." + pBotConfig.codeName + "." + pBotConfig.process;
|
||||
|
||||
if (err.result === global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished sucessfully.");
|
||||
logger.write(MODULE_NAME, "[INFO] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
|
||||
logger.persist();
|
||||
|
||||
} else {
|
||||
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Execution will be stopped. ");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bye.");
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot Id = " + botId);
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> whenStartFinishes -> Bot execution finished with errors. Please check the logs.");
|
||||
logger.persist();
|
||||
}
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> Failed to initialize the bot. ");
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runLearningBot -> onInitializeReady -> err = " + err.message);
|
||||
logger.persist();
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> runLearningBot -> err = " + err.stack);
|
||||
setTimeout(exitProcessInstance, WAIT_TIME_FOR_ALL_PROCESS_INSTANCES_TO_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(logDisplace + "Root : [ERROR] start -> bootingBot -> err = " + err.stack);
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@
|
||||
|
||||
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);
|
||||
parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.message);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
+16
-16
@@ -13,10 +13,10 @@ exports.newSocialBots = function newSocialBots(bot, logger) {
|
||||
return thisObject
|
||||
|
||||
function initialize() {
|
||||
if (bot.SESSION.socialBots !== undefined) {
|
||||
if (bot.SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.SESSION.socialBots.bots[i]
|
||||
if (bot.TRADING_SESSION.socialBots !== undefined) {
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
let config = socialBot.config
|
||||
socialBot.botInstance = TELEGRAM_BOT_MODULE.newTelegramBot(bot, logger)
|
||||
@@ -26,9 +26,9 @@ exports.newSocialBots = function newSocialBots(bot, logger) {
|
||||
}
|
||||
|
||||
function announce(text) {
|
||||
if (bot.SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.SESSION.socialBots.bots[i]
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
try {
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
socialBot.botInstance.telegramAPI.sendMessage(socialBot.botInstance.chatId, text).catch(err => logger.write(MODULE_NAME, "[WARN] initialize -> initializeSocialBots -> announce -> Telegram API error -> err = " + err))
|
||||
@@ -40,15 +40,15 @@ exports.newSocialBots = function newSocialBots(bot, logger) {
|
||||
}
|
||||
}
|
||||
|
||||
bot.SESSION.socialBots.announce = announce
|
||||
bot.TRADING_SESSION.socialBots.announce = announce
|
||||
}
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
if (bot.SESSION.socialBots === undefined) { return }
|
||||
if (bot.SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.SESSION.socialBots.bots[i]
|
||||
if (bot.TRADING_SESSION.socialBots === undefined) { return }
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
socialBot.botInstance.finalize()
|
||||
}
|
||||
@@ -57,10 +57,10 @@ exports.newSocialBots = function newSocialBots(bot, logger) {
|
||||
}
|
||||
|
||||
function sendMessage(message) {
|
||||
if (bot.SESSION.socialBots !== undefined) {
|
||||
if (bot.SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.SESSION.socialBots.bots[i]
|
||||
if (bot.TRADING_SESSION.socialBots !== undefined) {
|
||||
if (bot.TRADING_SESSION.socialBots.bots !== undefined) {
|
||||
for (let i = 0; i < bot.TRADING_SESSION.socialBots.bots.length; i++) {
|
||||
let socialBot = bot.TRADING_SESSION.socialBots.bots[i]
|
||||
if (socialBot.type === "Telegram Bot") {
|
||||
socialBot.botInstance.sendMessage(message)
|
||||
}
|
||||
|
||||
+7
-3
@@ -100,8 +100,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (bot.SESSION !== undefined && statusDependencyNode.bottype === "Trading Bot") {
|
||||
sessionPath = bot.SESSION.folderName + "/"
|
||||
if (bot.TRADING_SESSION !== undefined && statusDependencyNode.bottype === "Trading Bot") {
|
||||
sessionPath = bot.TRADING_SESSION.folderName + "/"
|
||||
}
|
||||
|
||||
if (bot.LEARNING_SESSION !== undefined && statusDependencyNode.bottype === "Learning Bot") {
|
||||
sessionPath = bot.LEARNING_SESSION.folderName + "/"
|
||||
}
|
||||
|
||||
/* Now we will see where do we need to fetch this status report from. */
|
||||
@@ -264,7 +268,7 @@
|
||||
On the contraty, if we are running a Sensor bot or an Indicator bot, we might, if necesary, use a previous version of a Status Report since
|
||||
there will be no big impact, just some reprocessing.
|
||||
*/
|
||||
if (bot.SESSION !== undefined) {
|
||||
if (bot.TRADING_SESSION !== undefined || bot.LEARNING_SESSION !== undefined) {
|
||||
canUserPrevious = false
|
||||
} else {
|
||||
canUserPrevious = true
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ exports.newTelegramBot = function newTelegramBot(bot, logger) {
|
||||
thisObject.telegramBot = new Telegraf(botToken)
|
||||
thisObject.telegramBot.start((ctx) => ctx.reply('Hi! I am the Telegram Bot that will tell you what happens with your trading session.'))
|
||||
thisObject.telegramBot.help((ctx) => ctx.reply('I can stop the session if you type STOP.'))
|
||||
thisObject.telegramBot.hears('STOP', (ctx) => bot.SESSION.stop(' by STOP command at Telegram.'))
|
||||
thisObject.telegramBot.hears('STOP', (ctx) => bot.TRADING_SESSION.stop(' by STOP command at Telegram.'))
|
||||
thisObject.telegramBot.launch()
|
||||
|
||||
const Telegram = require('telegraf/telegram')
|
||||
|
||||
+18
-19
@@ -5,10 +5,10 @@
|
||||
|
||||
const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/TradingProcess.js');
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
const SESSION = require(global.ROOT_DIR + 'Session');
|
||||
const SESSION = require(global.ROOT_DIR + 'TradingSession');
|
||||
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(parentLogger);
|
||||
let session = SESSION.newSession(bot, parentLogger)
|
||||
let session = SESSION.newTradingSession(bot, parentLogger)
|
||||
|
||||
const DEBUG_MODULE = require(global.ROOT_DIR + 'DebugLog');
|
||||
let logger; // We need this here in order for the loopHealth function to work and be able to rescue the loop when it gets in trouble.
|
||||
@@ -93,23 +93,23 @@
|
||||
+ " Main Loop # " + pad(Number(bot.loopCounter), 8) + " " + bot.processNode.session.type + " " + bot.processNode.session.name)
|
||||
|
||||
/* Checking if we need to need to emit any event */
|
||||
if (bot.SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) {
|
||||
bot.SESSION_STATUS = 'Running'
|
||||
if (bot.TRADING_SESSION_STATUS === 'Idle' && bot.STOP_SESSION === false) {
|
||||
bot.TRADING_SESSION_STATUS = 'Running'
|
||||
}
|
||||
|
||||
if (bot.SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) {
|
||||
bot.SESSION_STATUS = 'Stopped'
|
||||
if (bot.TRADING_SESSION_STATUS === 'Running' && bot.STOP_SESSION === true) {
|
||||
bot.TRADING_SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
|
||||
global.EMIT_SESSION_STATUS (bot.SESSION_STATUS, bot.sessionKey)
|
||||
global.EMIT_SESSION_STATUS (bot.TRADING_SESSION_STATUS, bot.TRADING_SESSIONKey)
|
||||
|
||||
/* Checking if we should process this loop or not.*/
|
||||
if (bot.STOP_SESSION === true) {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran."); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run."); }
|
||||
|
||||
console.log(new Date().toISOString() + " " + pad(bot.codeName, 20) + " " + pad(bot.process, 30)
|
||||
+ " Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran. ");
|
||||
+ " Waiting for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ");
|
||||
|
||||
nextWaitTime = 'Waiting for Session';
|
||||
loopControl(nextWaitTime);
|
||||
@@ -251,7 +251,6 @@
|
||||
global.unexpectedError = err.message
|
||||
processStopped()
|
||||
return
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
logger.write(MODULE_NAME, "[ERROR] run -> loop -> initializeStatusDependencies -> onInizialized -> Unhandled err.result received. -> err.result = " + err.result);
|
||||
@@ -535,7 +534,7 @@
|
||||
logger.persist();
|
||||
}
|
||||
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped')
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Stopped')
|
||||
processStopped()
|
||||
return;
|
||||
}
|
||||
@@ -552,7 +551,7 @@
|
||||
waitingTime = waitTime / 1000
|
||||
label = 'seconds'
|
||||
}
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be ran. ")
|
||||
processHeartBeat(undefined, undefined, "Waiting " + waitingTime + " " + label + " for " + bot.processNode.session.type + " " + bot.processNode.session.name + " to be run. ")
|
||||
logger.persist();
|
||||
}
|
||||
break
|
||||
@@ -561,17 +560,17 @@
|
||||
if (processConfig.waitsForExecutionFinishedEvent === true) {
|
||||
waitTime = 0
|
||||
} else {
|
||||
switch (bot.SESSION.type) {
|
||||
switch (bot.TRADING_SESSION.type) {
|
||||
case 'Live Trading Session': {
|
||||
waitTime = bot.SESSION.parameters.timeFrame.config.value
|
||||
waitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Fordward Tessting Session': {
|
||||
waitTime = bot.SESSION.parameters.timeFrame.config.value
|
||||
waitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
waitTime = bot.SESSION.parameters.timeFrame.config.value
|
||||
waitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
break
|
||||
}
|
||||
case 'Backtesting Session': {
|
||||
@@ -670,9 +669,9 @@
|
||||
}
|
||||
|
||||
function sessionStopped() {
|
||||
if (bot.SESSION_STATUS === 'Running') {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.sessionKey, 'Stopped')
|
||||
bot.SESSION_STATUS = 'Stopped'
|
||||
if (bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Stopped')
|
||||
bot.TRADING_SESSION_STATUS = 'Stopped'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
exports.newTradingSession = function newTradingSession(bot, parentLogger) {
|
||||
|
||||
const MODULE_NAME = "Trading Session"
|
||||
const FULL_LOG = true;
|
||||
|
||||
let thisObject = {
|
||||
initialize: initialize
|
||||
}
|
||||
|
||||
const SOCIAL_BOTS_MODULE = require('./SocialBots.js')
|
||||
let socialBotsModule = SOCIAL_BOTS_MODULE.newSocialBots(bot, parentLogger)
|
||||
|
||||
return thisObject;
|
||||
|
||||
function initialize(pProcessConfig, callBackFunction) {
|
||||
try {
|
||||
/* Initialize this info so that everything is logged propeerly */
|
||||
bot.TRADING_SESSION = {
|
||||
name: bot.processNode.session.name,
|
||||
id: bot.processNode.session.id
|
||||
}
|
||||
|
||||
/* Set the folderName for early logging */
|
||||
if (bot.processNode.session.config.folderName === undefined) {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.TRADING_SESSION.id
|
||||
} else {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
|
||||
/* Check if there is a session */
|
||||
if (bot.processNode.session === undefined) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> Cannot run without a Session.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Listen to event to start or stop the session. */
|
||||
bot.TRADING_SESSIONKey = bot.processNode.session.name + '-' + bot.processNode.session.type + '-' + bot.processNode.session.id
|
||||
global.SESSION_MAP.set(bot.TRADING_SESSIONKey, bot.TRADING_SESSIONKey)
|
||||
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Trading Session Status', undefined, bot.TRADING_SESSIONKey, undefined, onSessionStatus)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Run Trading Session', undefined, bot.TRADING_SESSIONKey, undefined, onSessionRun)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Stop Trading Session', undefined, bot.TRADING_SESSIONKey, undefined, onSessionStop)
|
||||
global.EVENT_SERVER_CLIENT.listenToEvent(bot.TRADING_SESSIONKey, 'Resume Trading Session', undefined, bot.TRADING_SESSIONKey, undefined, onSessionResume)
|
||||
|
||||
/* Connect this here so that it is accesible from other places */
|
||||
bot.TRADING_SESSIONError = sessionError
|
||||
bot.TRADING_SESSIONWarning = sessionWarning
|
||||
bot.TRADING_SESSIONInfo = sessionInfo
|
||||
|
||||
/* Heartbeats sent to the UI */
|
||||
bot.TRADING_SESSIONHeartBeat = sessionHeartBeat
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
return
|
||||
|
||||
function onSessionStatus() {
|
||||
if (bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
let event = {
|
||||
status: 'Trading Session Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Status Response', event)
|
||||
} else {
|
||||
let event = {
|
||||
status: 'Trading Session Not Runnning'
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Status Response', event)
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionRun(message) {
|
||||
try {
|
||||
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
|
||||
if (bot.TRADING_SESSION_STATUS === 'Idle' || bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionRun -> Event received to run the Session while it was already running. ")
|
||||
return
|
||||
}
|
||||
|
||||
/* We are going to run the Definition comming at the event. */
|
||||
bot.TRADING_SYSTEM = JSON.parse(message.event.tradingSystem)
|
||||
bot.TRADING_ENGINE = JSON.parse(message.event.tradingEngine)
|
||||
bot.TRADING_SESSION = JSON.parse(message.event.session)
|
||||
bot.DEPENDENCY_FILTER = JSON.parse(message.event.dependencyFilter)
|
||||
bot.RESUME = false
|
||||
bot.FIRST_EXECUTION = true
|
||||
bot.TRADING_SESSION.stop = stopSession // stop function
|
||||
|
||||
setUpSessionFolderName()
|
||||
|
||||
/* We validate all parameters received and complete some that might be missing if needed. */
|
||||
if (checkParemeters() === false) { return }
|
||||
|
||||
socialBotsModule.initialize()
|
||||
|
||||
let allGood
|
||||
switch (bot.TRADING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
allGood = startBackTesting(message)
|
||||
break
|
||||
}
|
||||
case 'Live Trading Session': {
|
||||
allGood = startLiveTrading(message)
|
||||
break
|
||||
}
|
||||
case 'Fordward Testing Session': {
|
||||
allGood = startFordwardTesting(message)
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
allGood = startPaperTrading(message)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allGood === true) {
|
||||
bot.TRADING_SESSION_STATUS = 'Idle'
|
||||
bot.STOP_SESSION = false
|
||||
} else {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] onSessionRun -> Stopping the Session now. ') }
|
||||
}
|
||||
|
||||
socialBotsModule.sendMessage(bot.TRADING_SESSION.type + " '" + bot.TRADING_SESSION.name + "' is starting.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionRun -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionStop() {
|
||||
stopSession('Session Stopped From the User Interface.')
|
||||
}
|
||||
|
||||
function onSessionResume(message) {
|
||||
try {
|
||||
if (bot.TRADING_SESSION.stop === undefined) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session that have never be ran before. ")
|
||||
return
|
||||
}
|
||||
|
||||
/* This happens when the UI is reloaded, the session was running and tries to run it again. */
|
||||
if (bot.TRADING_SESSION_STATUS === 'Idle' || bot.TRADING_SESSION_STATUS === 'Running') {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] onSessionResume -> Event received to resume the Session while it was already running. ")
|
||||
return
|
||||
}
|
||||
|
||||
bot.RESUME = true
|
||||
bot.STOP_SESSION = false
|
||||
|
||||
socialBotsModule.sendMessage(bot.TRADING_SESSION.type + " '" + bot.TRADING_SESSION.name + "' is resuming.")
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onSessionResume -> err = " + err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function stopSession(commandOrigin) {
|
||||
|
||||
socialBotsModule.sendMessage(bot.TRADING_SESSION.type + " '" + bot.TRADING_SESSION.name + "' is stopping " + commandOrigin)
|
||||
socialBotsModule.finalize()
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] stopSession -> Stopping the Session now. ') }
|
||||
sessionInfo(bot.TRADING_SESSION, commandOrigin)
|
||||
}
|
||||
|
||||
function setUpSessionFolderName() {
|
||||
/*
|
||||
The session object is overwritten when the session is run. For that reason we
|
||||
need to setup again the folder name at the Session level.
|
||||
Set the folderName for logging, reports, context and data output
|
||||
*/
|
||||
let config
|
||||
if (bot.TRADING_SESSION.config !== undefined) {
|
||||
config = bot.TRADING_SESSION.config
|
||||
if (config.folderName === undefined) {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.TRADING_SESSION.id
|
||||
} else {
|
||||
bot.TRADING_SESSION.folderName = bot.processNode.session.type.replace(' ', '-').replace(' ', '-') + '-' + bot.processNode.session.config.folderName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkParemeters() {
|
||||
/*
|
||||
Here we check all the Session Parameters received. If something critical is missing we abort returning false. If something
|
||||
non critical is missing, we complete it with a default value.
|
||||
*/
|
||||
|
||||
if (bot.TRADING_SESSION.tradingParameters === undefined) {
|
||||
let errorMessage = "Session Node with no Parameters."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Time Range */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange === undefined) { // if the Time Range is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange = {
|
||||
name: 'Missing Time Range',
|
||||
type: 'Time Range',
|
||||
config: {
|
||||
initialDatetime: (new Date()).valueOf(),
|
||||
finalDatetime: (new Date()).valueOf() + global.ONE_YEAR_IN_MILISECONDS
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Check that we received valid dates */
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (isNaN(new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (isNaN(new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* Session Type Forced Values */
|
||||
let today = (new Date()).valueOf()
|
||||
let aYearAgo = today - global.ONE_YEAR_IN_MILISECONDS
|
||||
let aYearFromNow = today + global.ONE_YEAR_IN_MILISECONDS
|
||||
switch (bot.TRADING_SESSION.type) {
|
||||
case 'Backtesting Session': {
|
||||
useDefaultDatetimes(aYearAgo, today)
|
||||
break
|
||||
}
|
||||
case 'Live Trading Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
case 'Fordward Testing Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
case 'Paper Trading Session': {
|
||||
useDefaultDatetimes(today, aYearFromNow)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function useDefaultDatetimes(initialDefault, finalDefault) {
|
||||
/*
|
||||
Note that inside the system, we are going to deal with these
|
||||
dates in their numeric value representation.
|
||||
*/
|
||||
|
||||
/* Initial Datetime */
|
||||
if (bot.TRADING_SESSION.type === 'Backtesting Session') {
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime === undefined) {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = (new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
} else {
|
||||
/* Non backtest session can start from the past only if explicitly configured that way */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime === undefined || bot.TRADING_SESSION.tradingParameters.timeRange.config.allowStartingFromThePast !== true) {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = initialDefault
|
||||
} else {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime = (new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.initialDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Final Datetime */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime === undefined) {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime = finalDefault
|
||||
} else {
|
||||
bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime = (new Date(bot.TRADING_SESSION.tradingParameters.timeRange.config.finalDatetime)).valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
/* Time Frame */
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.label === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Time Frame Label configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
bot.TRADING_SESSION.tradingParameters.timeFrame.config.value = getTimeFrameFromLabel(bot.TRADING_SESSION.tradingParameters.timeFrame.config.label)
|
||||
if (bot.TRADING_SESSION.tradingParameters.timeFrame.config.value === undefined) {
|
||||
let errorMessage = "Config error: label value not recognized. Try 01-min or 01-hs for example."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.timeFrame, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Base Asset */
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionBaseAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Base Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Base Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.sessionBaseAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Session Quoted Asset */
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset === undefined) {
|
||||
let errorMessage = "Session Parameters Node with no Session Quoted Asset."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.initialBalance === undefined) {
|
||||
let errorMessage = "Session Parameters Session Quoted Asset with no initialBalance configuration."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset, errorMessage)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Slippage */
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage === undefined) { // if the Slippage is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage = {
|
||||
name: 'Missing Slippage',
|
||||
type: 'Slippage',
|
||||
config: {
|
||||
marketOrderRate: 0,
|
||||
stopLoss: 0,
|
||||
takeProfit: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage.config.marketOrderRate === undefined) { // if the marketOrderRate is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage.config.marketOrderRate = 0
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage.config.stopLoss === undefined) { // if the stopLoss is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage.config.stopLoss = 0
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.slippage.config.takeProfit === undefined) { // if the takeProfit is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.slippage.config.takeProfit = 0
|
||||
}
|
||||
|
||||
/* Fee Structure */
|
||||
if (bot.TRADING_SESSION.tradingParameters.feeStructure === undefined) { // if the Fee Structure is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.feeStructure = {
|
||||
name: 'Missing Fee Structure',
|
||||
type: 'Fee Structure',
|
||||
config: {
|
||||
maker: 0,
|
||||
taker: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.feeStructure.config.maker === undefined) { // if the maker is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.feeStructure.config.maker = 0
|
||||
}
|
||||
if (bot.TRADING_SESSION.tradingParameters.feeStructure.config.taker === undefined) { // if the taker is missing we create a default one.
|
||||
bot.TRADING_SESSION.tradingParameters.feeStructure.config.taker = 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function startBackTesting(message) {
|
||||
return true
|
||||
}
|
||||
|
||||
function startLiveTrading(message) {
|
||||
if (bot.KEY === undefined || bot.SECRET === undefined) {
|
||||
let errorMessage = "Key 'codeName' or 'secret' not provided. Plese check that and try again."
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> " + errorMessage); }
|
||||
bot.TRADING_SESSIONError(bot.TRADING_SESSION, errorMessage)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function startFordwardTesting(message) {
|
||||
if (bot.KEY === undefined || bot.SECRET === undefined) {
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[WARN] initialize -> startFordwardTesting -> Key name or Secret not provided, not possible to run the process in Forward Testing mode."); }
|
||||
console.log("Key 'codeName' or 'secret' not provided. Plese check that and try again.")
|
||||
return false
|
||||
}
|
||||
|
||||
/* Reduce the balance */
|
||||
let balancePercentage = 1 // This is the default value
|
||||
|
||||
if (bot.TRADING_SESSION.config.balancePercentage !== undefined) {
|
||||
balancePercentage = bot.TRADING_SESSION.config.balancePercentage
|
||||
}
|
||||
|
||||
bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.initialBalance = bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.initialBalance * balancePercentage / 100
|
||||
bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.initialBalance = bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.initialBalance * balancePercentage / 100
|
||||
|
||||
bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.minimumBalance = bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.minimumBalance * balancePercentage / 100
|
||||
bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.minimumBalance = bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.minimumBalance * balancePercentage / 100
|
||||
|
||||
bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.maximumBalance = bot.TRADING_SESSION.tradingParameters.sessionBaseAsset.config.maximumBalance * balancePercentage / 100
|
||||
bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.maximumBalance = bot.TRADING_SESSION.tradingParameters.sessionQuotedAsset.config.maximumBalance * balancePercentage / 100
|
||||
|
||||
pProcessConfig.normalWaitTime = bot.TRADING_SESSION.tradingParameters.timeFrame.config.value
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function startPaperTrading(message) {
|
||||
return true
|
||||
}
|
||||
|
||||
function getTimeFrameFromLabel(timeFrameLabel) {
|
||||
|
||||
for (let i = 0; i < global.marketFilesPeriods.length; i++) {
|
||||
let value = global.marketFilesPeriods[i][0]
|
||||
let label = global.marketFilesPeriods[i][1]
|
||||
|
||||
if (timeFrameLabel === label) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < global.dailyFilePeriods.length; i++) {
|
||||
let value = global.dailyFilePeriods[i][0]
|
||||
let label = global.dailyFilePeriods[i][1]
|
||||
|
||||
if (timeFrameLabel === label) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sessionHeartBeat(processingDate, percentage, status) {
|
||||
let event = {
|
||||
seconds: (new Date()).getSeconds(),
|
||||
processingDate: processingDate,
|
||||
percentage: percentage,
|
||||
status: status
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Heartbeat', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionHeartBeat -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionError(node, errorMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
errorMessage: errorMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Error', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionError -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionWarning(node, warningMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
warningMessage: warningMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Warning', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionWarning -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
function sessionInfo(node, infoMessage) {
|
||||
let event
|
||||
if (node !== undefined) {
|
||||
event = {
|
||||
nodeName: node.name,
|
||||
nodeType: node.type,
|
||||
nodeId: node.id,
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
} else {
|
||||
event = {
|
||||
infoMessage: infoMessage
|
||||
}
|
||||
}
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(bot.TRADING_SESSIONKey, 'Info', event)
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
bot.STOP_SESSION = true
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, '[IMPORTANT] sessionInfo -> Stopping the Session now. ') }
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
|
||||
/* Callbacks default responses. */
|
||||
global.DEFAULT_OK_RESPONSE = {
|
||||
result: "Ok",
|
||||
@@ -31,8 +30,8 @@ global.ROOT_DIR = './';
|
||||
global.ONE_YEAR_IN_MILISECONDS = 365 * 24 * 60 * 60 * 1000
|
||||
global.ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000
|
||||
global.ONE_MIN_IN_MILISECONDS = 60 * 1000
|
||||
global.LOGGER_MAP = new Map()
|
||||
global.SESSION_MAP = new Map()
|
||||
global.LOGGER_MAP = new Map() // We will put all the loggers in a map, so that we can eventually finalize them.
|
||||
global.SESSION_MAP = new Map() // We will put all the sessions in a map, so that we can eventually finalize them.
|
||||
|
||||
global.UNIQUE_ID = function () {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
@@ -316,7 +315,9 @@ function finalizeLoggers() {
|
||||
global.LOGGER_MAP.forEach(forEachLogger)
|
||||
|
||||
function forEachLogger(logger) {
|
||||
logger.finalize()
|
||||
if (logger !== undefined) {
|
||||
logger.finalize()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,12 +376,14 @@ global.EXIT_NODE_PROCESS = function exitProcess() {
|
||||
/* Cleaning Before Exiting. */
|
||||
clearInterval(global.HEARTBEAT_INTERVAL_HANDLER)
|
||||
|
||||
for (let i = 0; i < global.TASK_NODE.bot.processes.length; i++) {
|
||||
let config = global.TASK_NODE.bot.processes[i].config
|
||||
let process = global.TASK_NODE.bot.processes[i]
|
||||
|
||||
key = process.name + '-' + process.type + '-' + process.id
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Stopped') // Meaning Process Stopped
|
||||
if (global.TASK_NODE !== undefined) {
|
||||
for (let i = 0; i < global.TASK_NODE.bot.processes.length; i++) {
|
||||
let config = global.TASK_NODE.bot.processes[i].config
|
||||
let process = global.TASK_NODE.bot.processes[i]
|
||||
|
||||
let key = process.name + '-' + process.type + '-' + process.id
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Stopped') // Meaning Process Stopped
|
||||
}
|
||||
}
|
||||
|
||||
finalizeLoggers()
|
||||
@@ -426,11 +429,15 @@ 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.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()
|
||||
try {
|
||||
global.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema)
|
||||
setUpAppSchema()
|
||||
global.TASK_NODE = JSON.parse(message.event.taskDefinition)
|
||||
global.TASK_NETWORK = JSON.parse(message.event.networkDefinition)
|
||||
bootLoader()
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> eventReceived -> ' + err.stack)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> global.TASK_NODE -> ' + err.stack)
|
||||
@@ -442,11 +449,16 @@ 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.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()
|
||||
try {
|
||||
global.APP_SCHEMA_ARRAY = JSON.parse(message.event.appSchema)
|
||||
setUpAppSchema()
|
||||
global.TASK_NODE = JSON.parse(message.event.taskDefinition)
|
||||
global.TASK_NETWORK = JSON.parse(message.event.networkDefinition)
|
||||
bootLoader()
|
||||
|
||||
} catch(err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> startDebugging -> ' + err.stack)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('[ERROR] Task Server -> server -> preLoader -> global.TASK_NODE -> ' + err.stack)
|
||||
@@ -502,6 +514,7 @@ function bootLoader() {
|
||||
global.EVENT_SERVER_CLIENT.raiseEvent(key, 'Error', event)
|
||||
}
|
||||
|
||||
|
||||
for (let processIndex = 0; processIndex < global.TASK_NODE.bot.processes.length; processIndex++) {
|
||||
let config = global.TASK_NODE.bot.processes[processIndex].config
|
||||
|
||||
|
||||
Reference in new issue
Block a user