Paper Trading Fixed
This commit is contained in:
1 parent
da25a3b8f6
commit
7469c58e0e
13 files changed
+678
-590
No files matched your search
@@ -65,6 +65,7 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
}
|
||||
|
||||
function setCurrentCycle(cycle) {
|
||||
tradingEngine.current.episode.cycle.value = cycle
|
||||
switch (cycle) {
|
||||
case 'First': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
|
||||
@@ -269,7 +269,6 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateDistanceToEventsCounters() {
|
||||
/* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */
|
||||
if (
|
||||
|
||||
@@ -117,7 +117,9 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
switch (tradingEngineOrder.status.value) {
|
||||
case 'Not Open': {
|
||||
{
|
||||
{
|
||||
/* During the First cycle we can not create new orders. That is reserved for the Second cycle. */
|
||||
if (tradingEngine.current.episode.cycle.value === 'First') {continue}
|
||||
/* When the stage is closing we can not create new orders */
|
||||
if (tradingEngineStage.status.value === 'Closing') { continue }
|
||||
/*
|
||||
@@ -147,13 +149,15 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
/* Check Events that happens at the Exchange, if needed. */
|
||||
await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder)
|
||||
|
||||
/* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */
|
||||
if (tradingEngine.current.episode.cycle.value === 'Second') {continue}
|
||||
/*
|
||||
In the previous steps, we might have discovered that the order was cancelled
|
||||
at the exchange, or filled, so the order might still not be Open.
|
||||
If the stage is closing or the order is not Open, we wont be cancelling orders
|
||||
based on defined events.
|
||||
*/
|
||||
if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') {
|
||||
if (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') {
|
||||
|
||||
/* Check if we need to Cancel this Order */
|
||||
let situationName = checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode)
|
||||
@@ -239,20 +243,27 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
tradingEngineOrder.situationName.value = situationName
|
||||
|
||||
function calculateOrderRate() {
|
||||
/* By default this is the order rate and it is the rate that applies to Market Orders */
|
||||
tradingEngineOrder.rate.value = tradingEngine.current.episode.candle.close.value
|
||||
|
||||
/* Optional Rate Definition */
|
||||
if (tradingSystemOrder.orderRate !== undefined) {
|
||||
if (tradingSystemOrder.orderRate.formula !== undefined) {
|
||||
tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id)
|
||||
if (tradingSystemOrder.type === 'Limit Buy Order' || tradingSystemOrder.type === 'Limit Sell Order') {
|
||||
if (tradingSystemOrder.orderRate === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.orderRate === undefined', tradingSystemOrder) }
|
||||
if (tradingSystemOrder.orderRate.formula === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.orderRate.formula === undefined', tradingSystemOrder) }
|
||||
|
||||
/* Extract the rate value from the user-defined formula */
|
||||
tradingEngineOrder.rate.value = tradingSystem.formulas.get(tradingSystemOrder.orderRate.formula.id)
|
||||
|
||||
if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingEngineOrder) }
|
||||
if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingEngineOrder) }
|
||||
if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingEngineOrder) }
|
||||
/* Final rate validations */
|
||||
if (tradingEngineOrder.rate.value === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value === undefined', tradingSystemOrder) }
|
||||
if (isNaN(tradingEngineOrder.rate.value) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingEngineOrder.rate.value) === true', tradingSystemOrder) }
|
||||
if (tradingEngineOrder.rate.value <= 0) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate.value <= 0', tradingSystemOrder) }
|
||||
|
||||
tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10)
|
||||
}
|
||||
tradingEngineOrder.rate.value = global.PRECISE(tradingEngineOrder.rate.value, 10)
|
||||
} else {
|
||||
/*
|
||||
For Market Orders, the rate is irrelevant, since it is not sent to the Exchange.
|
||||
We store at this field the last know price as a reference.
|
||||
*/
|
||||
tradingEngineOrder.rate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +304,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
/* Size in Quoted Asset */
|
||||
tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100
|
||||
|
||||
/* Check that the Size calculated would not surpass Stage Target Size */
|
||||
if (
|
||||
tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value >
|
||||
tradingEngineStage.stageQuotedAsset.targetSize.value
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE) {
|
||||
exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineModule, UTILITIES, FILE_STORAGE) {
|
||||
/*
|
||||
This module will load if necesary all the data outputs so that they can be appended with new
|
||||
records if needed. After running the simulation, it will save all the data outputs.
|
||||
@@ -27,7 +27,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
|
||||
|
||||
}
|
||||
|
||||
async function start(chart, timeFrame, timeFrameLabel, currentDay) {
|
||||
async function start(chart, timeFrame, timeFrameLabel, tradingProcessDate) {
|
||||
try {
|
||||
|
||||
if (timeFrame > global.dailyFilePeriods[0][0]) {
|
||||
@@ -38,18 +38,12 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
|
||||
|
||||
/* Preparing everything for the Simulation */
|
||||
const TRADING_SIMULATION = require('./TradingSimulation.js')
|
||||
let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, UTILITIES)
|
||||
let tradingSimulation = TRADING_SIMULATION.newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES)
|
||||
|
||||
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
|
||||
let market = bot.market
|
||||
let outputDatasetsMap = new Map()
|
||||
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
Here is where the Trading Engine and Trading Systems received are moved to the simulation state.
|
||||
*/
|
||||
bot.simulationState.tradingEngine = bot.TRADING_ENGINE
|
||||
bot.simulationState.tradingSystem = bot.TRADING_SYSTEM
|
||||
await initializeOutputs()
|
||||
} else {
|
||||
await readFiles()
|
||||
@@ -60,6 +54,8 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
|
||||
outputDatasetsMap,
|
||||
writeFiles
|
||||
)
|
||||
|
||||
tradingSimulation.finalize()
|
||||
return
|
||||
|
||||
async function initializeOutputs() {
|
||||
@@ -127,7 +123,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
|
||||
let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2);
|
||||
let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
@@ -184,7 +180,7 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, UTILITIES, FIL
|
||||
|
||||
if (dataset.config.type === 'Daily Files') {
|
||||
|
||||
let dateForPath = currentDay.getUTCFullYear() + '/' + utilities.pad(currentDay.getUTCMonth() + 1, 2) + '/' + utilities.pad(currentDay.getUTCDate(), 2);
|
||||
let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
let fileName = 'Data.json'
|
||||
let filePath = bot.filePathRoot + '/Output/' + bot.SESSION.folderName + '/' + dataset.parentNode.config.codeName + '/' + dataset.config.codeName + '/' + timeFrameLabel + "/" + dateForPath
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
exports.newTradingProcess = function newTradingProcess(bot, logger, UTILITIES, TRADING_OUTPUT_MODULE, USER_BOT_COMMONS) {
|
||||
exports.newTradingProcess = function newTradingProcess(bot, logger, UTILITIES) {
|
||||
/*
|
||||
This Module will load all the process data dependencies from files and send them downstream.
|
||||
After execution, will save the time range and status report of the process.
|
||||
@@ -20,11 +20,15 @@
|
||||
let dataFiles = new Map();
|
||||
let multiPeriodDataFiles = new Map();
|
||||
|
||||
let tradingOutputModule;
|
||||
|
||||
const FILE_STORAGE = require('../FileStorage.js');
|
||||
let fileStorage = FILE_STORAGE.newFileStorage(logger);
|
||||
|
||||
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
|
||||
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
|
||||
|
||||
let TRADING_OUTPUT_MODULE = require("./TradingOutput")
|
||||
let tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, tradingEngineModule, UTILITIES, FILE_STORAGE)
|
||||
|
||||
let processConfig;
|
||||
|
||||
return thisObject;
|
||||
@@ -38,8 +42,6 @@
|
||||
dataDependenciesModule = pDataDependencies
|
||||
processConfig = pProcessConfig
|
||||
|
||||
let TRADING_OUTPUT_MODULE = require("./TradingOutput")
|
||||
tradingOutputModule = TRADING_OUTPUT_MODULE.newTradingOutput(bot, logger, UTILITIES, FILE_STORAGE)
|
||||
tradingOutputModule.initialize()
|
||||
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE)
|
||||
@@ -51,12 +53,17 @@
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
|
||||
tradingEngineModule.finalize()
|
||||
tradingEngineModule = undefined
|
||||
|
||||
tradingOutputModule.finalize()
|
||||
tradingOutputModule = undefined
|
||||
|
||||
dataFiles = undefined
|
||||
multiPeriodDataFiles = undefined
|
||||
statusDependencies = undefined
|
||||
dataDependenciesModule = undefined
|
||||
tradingOutputModule.finalize()
|
||||
tradingOutputModule = undefined
|
||||
fileStorage = undefined
|
||||
processConfig = undefined
|
||||
thisObject = undefined
|
||||
@@ -66,22 +73,58 @@
|
||||
try {
|
||||
let currentTimeFrame
|
||||
let currentTimeFrameLabel
|
||||
let market = bot.market;
|
||||
|
||||
/* Context Variables */
|
||||
|
||||
let contextVariables = {
|
||||
lastFile: undefined, // Datetime of the last file files sucessfully produced by this process.
|
||||
dateBeginOfMarket: undefined, // Datetime of the first trade file in the whole market history.
|
||||
dateEndOfMarket: undefined // Datetime of the last file available to be used as an input of this process.
|
||||
};
|
||||
|
||||
let previousDay; // Holds the date of the previous day relative to the processing date.
|
||||
|
||||
getContextVariables()
|
||||
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
Here is where the Trading Engine and Trading Systems received are moved to the simulation state.
|
||||
*/
|
||||
bot.simulationState.tradingEngine = bot.TRADING_ENGINE
|
||||
bot.simulationState.tradingSystem = bot.TRADING_SYSTEM
|
||||
}
|
||||
|
||||
/* We set up the Trading Engine Module. */
|
||||
tradingEngineModule.initialize()
|
||||
|
||||
/* Initializing the Trading Process Date */
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/*
|
||||
This funcion is going to be called many times by the Trading Bot Loop.
|
||||
Only during the first execution and when the User is not resuming the execution
|
||||
of a stopped session / task; we are going to initialize the Process Date Time.
|
||||
This variable tell us which day we are standing at, specially while working
|
||||
with Daily Files. From this Date is that we are going to load the Daily Files.
|
||||
*/
|
||||
bot.simulationState.tradingEngine.current.episode.processDate.value = global.REMOVE_TIME(bot.SESSION.parameters.timeRange.config.initialDatetime).valueOf()
|
||||
}
|
||||
|
||||
/*
|
||||
This is the Date that is going to be used across the execution of this Trading Process.
|
||||
We need this because it has a different life cycle than the processData stored at the
|
||||
Trading Engine data structure. This date has to remain the same during the whole execution
|
||||
of the Trading Process until the end, inclusind the writting of Data Ranges and Status Reports.
|
||||
The processDate of the Trading Engine data structure on the other hand can be changed during
|
||||
the simulation loop, once we discover that all candles from a certain date have benn processed.
|
||||
Here is the point where we sync one and the other.
|
||||
*/
|
||||
let tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
|
||||
await processSingleFiles()
|
||||
await processMarketFiles()
|
||||
|
||||
/*
|
||||
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
|
||||
@@ -89,54 +132,56 @@
|
||||
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 */
|
||||
|
||||
let chart = {}
|
||||
buildCharts(chart)
|
||||
|
||||
await generateOutput(chart)
|
||||
await writeProcessFiles()
|
||||
|
||||
checkIfSessionMustStop()
|
||||
|
||||
} else {
|
||||
/* We are processing Daily Files */
|
||||
|
||||
/*
|
||||
Go back one day to start well when we advance time at the begining of the loop.
|
||||
*/
|
||||
bot.tradingProcessDate = new Date(contextVariables.lastFile.valueOf() - global.ONE_DAY_IN_MILISECONDS);
|
||||
do {
|
||||
if (advanceTime() === false) {break}
|
||||
if (checkStopTaskGracefully() === false) {break}
|
||||
if (checkStopProcessing() === false) {break}
|
||||
|
||||
|
||||
/*
|
||||
We update the Trading Process Date with the date calculated at the simulation.
|
||||
We will use this date to load indicator and output files. After that we will
|
||||
use it to save Output Files and later the Data Ranges. This is the point where
|
||||
the date calculated by the Simulation is applied at the Trading Process Level.
|
||||
*/
|
||||
tradingProcessDate = global.REMOVE_TIME(bot.simulationState.tradingEngine.current.episode.processDate.value)
|
||||
|
||||
await processDailyFiles()
|
||||
|
||||
let chart = {}
|
||||
buildCharts(chart)
|
||||
|
||||
await generateOutput(chart)
|
||||
await writeProcessFiles()
|
||||
|
||||
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 getContextVariables() {
|
||||
try {
|
||||
let thisReport;
|
||||
let reportKey;
|
||||
let statusReport;
|
||||
|
||||
/* We are going to use the start date as beging of market date. */
|
||||
contextVariables.dateBeginOfMarket = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)
|
||||
|
||||
contextVariables.dateBeginOfMarket = global.REMOVE_TIME(bot.SESSION.parameters.timeRange.config.initialDatetime)
|
||||
/*
|
||||
Here we get the status report from the bot who knows which is the end of the market.
|
||||
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")
|
||||
|
||||
@@ -168,17 +213,6 @@
|
||||
|
||||
contextVariables.dateEndOfMarket = new Date(thisReport.lastFile.valueOf());
|
||||
|
||||
/* Validation that the data is not up-to-date. */
|
||||
if (bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS > contextVariables.dateEndOfMarket.valueOf() && bot.SESSION.type !== "Backtesting Session") {
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> getContextVariables -> " + "Head of the market is @ " + contextVariables.dateEndOfMarket); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[ERROR] start -> getContextVariables -> You need to UPDATE your datasets in order to run a " + bot.SESSION.type) }
|
||||
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
/* Finally we get our own Status Report. */
|
||||
statusReport = statusDependencies.reportsByMainUtility.get("Self Reference")
|
||||
|
||||
@@ -254,29 +288,13 @@
|
||||
|
||||
/* 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)
|
||||
onFileReceived(response.err, response.text)
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
try {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
if (err.message === 'File does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.');
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> onFileReceived -> err = " + err.message);
|
||||
callBackFunction(err);
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(text);
|
||||
dataFiles.set(dependency.id, dataFile);
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processSingleFiles -> onFileReceived -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
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"
|
||||
@@ -319,30 +337,14 @@
|
||||
|
||||
/* 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)
|
||||
onFileReceived(response.err, response.text)
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
try {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
if (err.message === 'File does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.');
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> onFileReceived -> err = " + err.message);
|
||||
callBackFunction(err);
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let dataFile = JSON.parse(text);
|
||||
let trimmedDataFile = trimDataFile(dataFile, datasetModule.node.parentNode.record)
|
||||
dataFiles.set(dependency.id, trimmedDataFile);
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processMarketFiles -> onFileReceived -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
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) {
|
||||
/*
|
||||
@@ -379,8 +381,8 @@
|
||||
|
||||
async function processDailyFiles() {
|
||||
/* Telling the world we are alive and doing well and which date we are processing right now. */
|
||||
let processingDate = bot.tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2);
|
||||
bot.processHeartBeat(processingDate)
|
||||
let processingDateString = tradingProcessDate.getUTCFullYear() + '-' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
bot.processHeartBeat(processingDateString)
|
||||
|
||||
/*
|
||||
We will iterate through all posible timeFrames.
|
||||
@@ -407,10 +409,13 @@
|
||||
|
||||
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];
|
||||
let file
|
||||
|
||||
if (dependency.referenceParent.config.codeName !== "Multi-Period-Daily") {
|
||||
continue
|
||||
@@ -422,7 +427,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
let dateForPath = bot.tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(bot.tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(bot.tradingProcessDate.getUTCDate(), 2);
|
||||
let dateForPath = tradingProcessDate.getUTCFullYear() + '/' + utilities.pad(tradingProcessDate.getUTCMonth() + 1, 2) + '/' + utilities.pad(tradingProcessDate.getUTCDate(), 2);
|
||||
let filePath
|
||||
if (dependency.referenceParent.config.codeName === "Multi-Period-Daily") {
|
||||
filePath = dependency.referenceParent.parentNode.config.codeName + '/' + dependency.referenceParent.config.codeName + "/" + timeFrameLabel + "/" + dateForPath;
|
||||
@@ -433,66 +438,32 @@
|
||||
|
||||
/* 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)
|
||||
onFileReceived(response.err, response.text)
|
||||
|
||||
function onFileReceived(err, text) {
|
||||
try {
|
||||
if ((err.result === "Fail Because" && err.message === "File does not exist.") || err.code === 'The specified key does not exist.') {
|
||||
logger.write(MODULE_NAME, "[ERROR] The file " + filePath + '/' + fileName + ' does not exist and it is required to continue. This process will retry to read it in a while. In the meantime make yourself sure that the process that generates it has ran properly.');
|
||||
callBackFunction(global.DEFAULT_RETRY_RESPONSE);
|
||||
return
|
||||
}
|
||||
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> onFileReceived -> Not Ok -> err = " + err.code);
|
||||
callBackFunction(err);
|
||||
return
|
||||
}
|
||||
|
||||
file = JSON.parse(text);
|
||||
dataFiles.set(dependency.id, file);
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> onFileReceived -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (currentTimeFrame !== undefined) {
|
||||
buildCharts(advanceTime);
|
||||
} else {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> processDailyFiles -> Time Frame not Recognized. Can not continue.");
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
function advanceTime() {
|
||||
bot.tradingProcessDate = new Date(bot.tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS);
|
||||
previousDay = new Date(bot.tradingProcessDate.valueOf() - global.ONE_DAY_IN_MILISECONDS);
|
||||
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> bot.tradingProcessDate = " + bot.tradingProcessDate); }
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> previousDay = " + previousDay); }
|
||||
|
||||
/* Validation that we are not going past the user defined end date. */
|
||||
if (bot.tradingProcessDate.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) {
|
||||
const logText = "User Defined End Datetime reached @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> " + logText); }
|
||||
|
||||
bot.SESSION.stop(logText)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Validation that we are not going past the head of the market. */
|
||||
if (bot.tradingProcessDate.valueOf() > contextVariables.dateEndOfMarket.valueOf() + global.ONE_DAY_IN_MILISECONDS - 1) {
|
||||
const logText = "Head of the market found @ " + previousDay.getUTCFullYear() + "/" + (previousDay.getUTCMonth() + 1) + "/" + previousDay.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> advanceTime -> " + logText); }
|
||||
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) {
|
||||
console.log('YES at the head of the market')
|
||||
return false
|
||||
}
|
||||
console.log('NOT at the head of the market')
|
||||
}
|
||||
|
||||
function checkStopTaskGracefully() {
|
||||
@@ -566,7 +537,7 @@
|
||||
chart,
|
||||
currentTimeFrame,
|
||||
currentTimeFrameLabel,
|
||||
bot.tradingProcessDate
|
||||
tradingProcessDate
|
||||
)
|
||||
|
||||
bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution.
|
||||
@@ -575,24 +546,28 @@
|
||||
async function writeProcessFiles() {
|
||||
|
||||
await writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel)
|
||||
await writeDataRanges(currentTimeFrameLabel)
|
||||
await writeDataRanges()
|
||||
|
||||
if (currentTimeFrame > global.dailyFilePeriods[0][0]) {
|
||||
await writeMarketStatusReport(currentTimeFrameLabel)
|
||||
} else {
|
||||
await writeDailyStatusReport(bot.tradingProcessDate, currentTimeFrameLabel)
|
||||
await writeDailyStatusReport(currentTimeFrameLabel)
|
||||
}
|
||||
|
||||
async function writeDataRanges(currentTimeFrameLabel) {
|
||||
for (let outputDatasetIndex = 0; outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; outputDatasetIndex++) {
|
||||
async function writeDataRanges() {
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeDataRange(contextVariables.dateBeginOfMarket, bot.tradingProcessDate, productCodeName, currentTimeFrameLabel);
|
||||
await writeDataRange(productCodeName);
|
||||
}
|
||||
|
||||
async function writeDataRange(pBegin, pEnd, productCodeName, currentTimeFrameLabel) {
|
||||
async function writeDataRange(productCodeName) {
|
||||
let dataRange = {
|
||||
begin: pBegin.valueOf(),
|
||||
end: pEnd.valueOf() + global.ONE_DAY_IN_MILISECONDS
|
||||
begin: contextVariables.dateBeginOfMarket.valueOf(),
|
||||
end: tradingProcessDate.valueOf() + global.ONE_DAY_IN_MILISECONDS
|
||||
}
|
||||
|
||||
let fileContent = JSON.stringify(dataRange);
|
||||
@@ -619,12 +594,16 @@
|
||||
|
||||
async function writeTimeFramesFiles(currentTimeFrame, currentTimeFrameLabel) {
|
||||
|
||||
for (let outputDatasetIndex = 0; outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length; outputDatasetIndex++) {
|
||||
for (
|
||||
let outputDatasetIndex = 0;
|
||||
outputDatasetIndex < bot.processNode.referenceParent.processOutput.outputDatasets.length;
|
||||
outputDatasetIndex++
|
||||
) {
|
||||
let productCodeName = bot.processNode.referenceParent.processOutput.outputDatasets[outputDatasetIndex].referenceParent.parentNode.config.codeName;
|
||||
await writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily')
|
||||
await writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, 'Multi-Period-Market')
|
||||
await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Daily')
|
||||
await writeTimeFramesFile( currentTimeFrameLabel, productCodeName, 'Multi-Period-Market')
|
||||
|
||||
async function writeTimeFramesFile(currentTimeFrame, currentTimeFrameLabel, productCodeName, processType) {
|
||||
async function writeTimeFramesFile(currentTimeFrameLabel, productCodeName, processType) {
|
||||
|
||||
let timeFramesArray = []
|
||||
timeFramesArray.push(currentTimeFrameLabel)
|
||||
@@ -642,12 +621,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function writeDailyStatusReport(lastFileDate, currentTimeFrameLabel) {
|
||||
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 = lastFileDate
|
||||
thisReport.file.lastFile = tradingProcessDate
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
await thisReport.asyncSave()
|
||||
@@ -661,33 +640,54 @@
|
||||
thisReport.file.simulationState = bot.simulationState
|
||||
thisReport.file.timeFrames = currentTimeFrameLabel
|
||||
|
||||
logger.newInternalLoop(bot.codeName, bot.process, bot.tradingProcessDate)
|
||||
logger.newInternalLoop(bot.codeName, bot.process, tradingProcessDate)
|
||||
await thisReport.asyncSave()
|
||||
}
|
||||
}
|
||||
|
||||
function checkIfSessionMustStop() {
|
||||
/*
|
||||
The natural way for the session to stop is when we reached the
|
||||
user defined end datetime.
|
||||
*/
|
||||
const now = new Date()
|
||||
if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) {
|
||||
const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeProcessFiles -> onMarketStatusReport -> " + logText); }
|
||||
|
||||
bot.SESSION.stop('because it just finished.')
|
||||
return
|
||||
}
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
bot.SESSION.stop('Backtesting Session over Market Files Finished.')
|
||||
return
|
||||
/*
|
||||
Backtests needs only one execution of this process to complete.
|
||||
*/
|
||||
bot.SESSION.stop('Backtesting Session Finished.')
|
||||
} else {
|
||||
/*
|
||||
The natural way for the session to stop is when we reached the
|
||||
user defined end datetime.
|
||||
*/
|
||||
const now = new Date()
|
||||
if (now.valueOf() >= bot.SESSION.parameters.timeRange.config.finalDatetime) {
|
||||
const logText = "User Defined End Datetime reached @ " + now.getUTCFullYear() + "/" + (now.getUTCMonth() + 1) + "/" + now.getUTCDate() + ".";
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] start -> writeProcessFiles -> onMarketStatusReport -> " + logText); }
|
||||
|
||||
bot.SESSION.stop('Final Datetime Reached.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.write(MODULE_NAME, "[ERROR] start -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
/* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
outputDatasetsMap = undefined
|
||||
}
|
||||
|
||||
function appendRecords(cycle) {
|
||||
function appendRecords() {
|
||||
/*
|
||||
Here we add records to the output files. At the product config property nodePath
|
||||
we have a pointer to the node that have the information we need to extract.
|
||||
@@ -57,13 +57,13 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
There are a few products configured to be saved only at an specific cycle.
|
||||
*/
|
||||
if (product.config.saveAtCycle !== undefined) {
|
||||
if (cycle !== product.config.saveAtCycle) {
|
||||
if (tradingEngine.current.episode.cycle.value !== product.config.saveAtCycle) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean the file from information of previous executions */
|
||||
pruneOutputFile(product, outputDatasetArray, tradingEngine.current.episode.candle.end.value)
|
||||
//pruneOutputFile(product, outputDatasetArray, tradingEngine.current.episode.candle.end.value)
|
||||
|
||||
/* Clean Open Records */
|
||||
if (product.config.saveAsObjects === true) {
|
||||
@@ -72,6 +72,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
if (recordProperty.config.codeName === product.config.propertyNameThatDefinesObject) {
|
||||
/* Remove Open Records */
|
||||
spliceOpenRecords(j, product, outputDatasetArray)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) {
|
||||
exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradingEngineModule, UTILITIES) {
|
||||
/*
|
||||
This Module represents the trading simulacion. Escentially a loop through a set of candles and
|
||||
the execution at each loop cycle of the Trading System Protocol.
|
||||
@@ -23,7 +23,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
chart,
|
||||
outputDatasetsMap,
|
||||
writeFiles,
|
||||
) {
|
||||
) {
|
||||
try {
|
||||
|
||||
let tradingSystem = bot.simulationState.tradingSystem
|
||||
@@ -34,10 +34,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) }
|
||||
|
||||
/* These are the Modules we will need to run the Simulation */
|
||||
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
|
||||
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
|
||||
tradingEngineModule.initialize()
|
||||
|
||||
const TRADING_RECORDS_MODULE = require('./TradingRecords.js')
|
||||
let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger)
|
||||
tradingRecordsModule.initialize(outputDatasetsMap)
|
||||
@@ -58,55 +54,59 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
let heartBeatDate
|
||||
let previousHeartBeatDate
|
||||
let firstLoopExecution = true
|
||||
|
||||
/*
|
||||
Loop Initialization
|
||||
Estimation of the Initial Candle to Process in this Run.
|
||||
*/
|
||||
if (bot.FIRST_EXECUTION === false && tradingEngine.last.candle !== undefined) {
|
||||
/* Estimate the Initial Candle based on the last candle saved at tradingEngine */
|
||||
let firstBegin = candles[0].begin
|
||||
let lastBegin = tradingEngine.last.candle.begin
|
||||
let diff = lastBegin - firstBegin
|
||||
let amount = diff / sessionParameters.timeFrame.config.value
|
||||
|
||||
tradingEngine.current.episode.candle.index.value = Math.trunc(amount) + 1 // Because we need to start from the next candle
|
||||
if (tradingEngine.current.episode.candle.index.value < 0 || tradingEngine.current.episode.candle.index.value > candles.length - 1) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.')
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> tradingEngine.current.episode.candle.index.value = ' + tradingEngine.current.episode.candle.index.value)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount)
|
||||
|
||||
throw(global.DEFAULT_FAIL_RESPONSE)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
let initialCandle
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
/* Estimate Initial Candle based on the timeRage configured for the session. */
|
||||
let firstEnd = candles[0].end
|
||||
let targetEnd = sessionParameters.timeRange.config.initialDatetime
|
||||
let diff = targetEnd - firstEnd
|
||||
let amount = diff / sessionParameters.timeFrame.config.value
|
||||
|
||||
tradingEngine.current.episode.candle.index.value = Math.trunc(amount)
|
||||
if (tradingEngine.current.episode.candle.index.value < 0) { tradingEngine.current.episode.candle.index.value = 0 }
|
||||
if (tradingEngine.current.episode.candle.index.value > candles.length - 1) {
|
||||
initialCandle = Math.trunc(amount)
|
||||
if (initialCandle < 0) { initialCandle = 0 }
|
||||
if (initialCandle > candles.length - 1) {
|
||||
/*
|
||||
This will happen when the sessionParameters.timeRange.config.initialDatetime is beyond the last candle available,
|
||||
meaning that the dataSet needs to be updated with more up-to-date data.
|
||||
*/
|
||||
bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.')
|
||||
return
|
||||
bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.')
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
/*
|
||||
In this case we already have at the last candle index the next candle to be
|
||||
processed. We will just continue with this candle.
|
||||
*/
|
||||
initialCandle = tradingEngine.current.episode.candle.index.value
|
||||
}
|
||||
/*
|
||||
Main Simulation Loop
|
||||
*/
|
||||
let breakLoop = false
|
||||
for( let i = tradingEngine.current.episode.candle.index.value; i < candles.length; i++) {
|
||||
console.log('initialCandle = ' + initialCandle)
|
||||
/* We are going to use this to exit the loop if needed. */
|
||||
let breakLoop = false
|
||||
|
||||
/*
|
||||
We will assume that we are at the head of the market here. We do this
|
||||
because the loop could be empty and no validation is going to run. If the
|
||||
loop is not empty, then the lascCandle() check will override this value
|
||||
depending on if we really are at the head of the market or not.
|
||||
*/
|
||||
tradingEngine.current.episode.headOfTheMarket.value = true
|
||||
/*
|
||||
This is the main simulation loop. It will go through the initial candle
|
||||
until one less than the last candle available. We will never process the last
|
||||
candle available since it is not considered a closed candle, but a candle
|
||||
that still can change. So effectively will be processing all closed candles.
|
||||
*/
|
||||
for (let i = initialCandle; i < candles.length - 1; i++) {
|
||||
|
||||
tradingEngine.current.episode.candle.index.value = i
|
||||
console.log('index = ' + tradingEngine.current.episode.candle.index.value)
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.episode.candle.index.value) }
|
||||
let candle = candles[tradingEngine.current.episode.candle.index.value] // This is the current candle the Simulation is working at.
|
||||
|
||||
@@ -114,7 +114,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) }
|
||||
|
||||
tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading engine data structure to make it available to anyone, including conditions and formulas.
|
||||
|
||||
|
||||
/* We emit a heart beat so that the UI can now where we are at the overal process.*/
|
||||
heartBeat()
|
||||
|
||||
@@ -124,6 +124,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
}
|
||||
|
||||
if (checkInitialDatetime() === false) {
|
||||
console.log(' Esta candle esta dentras del Initial Datetime')
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -134,85 +135,96 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
|
||||
/*
|
||||
Do the stuff needed previous to the run like
|
||||
Episode Counters and Statistics update
|
||||
Episode Counters and Statistics update. Mantaince is done
|
||||
once per simulation candle.
|
||||
*/
|
||||
tradingSystemModule.mantain()
|
||||
tradingEpisodeModule.mantain()
|
||||
tradingEngineModule.mantain()
|
||||
|
||||
/* Reset Data Structures */
|
||||
tradingSystemModule.reset()
|
||||
tradingEpisodeModule.reset()
|
||||
tradingEngineModule.reset()
|
||||
|
||||
/*
|
||||
Run the first cycle of the Trading System. In this first cycle we
|
||||
give some room so that orders can be canceled or filled and we can
|
||||
write those records into the output memory.
|
||||
write those records into the output memory. During this cycle new
|
||||
orders can not be created, since otherwise the could be cancelled at
|
||||
the second cycle without spending real time at the order book.
|
||||
*/
|
||||
tradingEngineModule.setCurrentCycle('First')
|
||||
await tradingSystemModule.run()
|
||||
await runCycle()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords('First')
|
||||
|
||||
/* Reset Data Structures */
|
||||
tradingSystemModule.reset()
|
||||
tradingEpisodeModule.reset()
|
||||
tradingEngineModule.reset()
|
||||
tradingRecordsModule.appendRecords()
|
||||
|
||||
if (breakLoop === true) { break }
|
||||
/*
|
||||
Run the second cycle of the Trading System. During this second run
|
||||
some new orders might be created at slots freed up during the first
|
||||
run. This allows for example for an Limit Order to be cancelled during the
|
||||
run. This allows for example for a Limit Order to be cancelled during the
|
||||
first run, and the same Limit Order definition to spawn a new order
|
||||
without the need to wait until the next candle.
|
||||
without the need to wait until the next candle. Orders can not be cancelled
|
||||
during the second cycle.
|
||||
*/
|
||||
tradingEngineModule.setCurrentCycle('Second')
|
||||
await tradingSystemModule.run()
|
||||
await runCycle()
|
||||
|
||||
/* Checking if we should continue processing this loop or not. */
|
||||
if (bot.STOP_SESSION === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') }
|
||||
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.')
|
||||
updateEpisode('Session Stopped')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') }
|
||||
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.')
|
||||
updateEpisode('Task Stopped')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (checkFinalDatetime() === false) {
|
||||
closeEpisode('Final Datetime Reached')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (checkLastCandle() === false) {
|
||||
updateEpisode('All Available Candles Processed')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (checkMinimunAndMaximunBalance() === false) {
|
||||
closeEpisode('Min or Max Balance Reached')
|
||||
breakLoop = true
|
||||
}
|
||||
/*
|
||||
We check if we need to stop before appending the records so that the stop
|
||||
reason is also propery recorded.
|
||||
*/
|
||||
checkIfWeNeedToStop()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords('Second')
|
||||
tradingRecordsModule.appendRecords()
|
||||
|
||||
if (breakLoop === true) {break}
|
||||
if (breakLoop === true) { break }
|
||||
|
||||
async function runCycle() {
|
||||
|
||||
console.log(new Date(), 'Function runCycle', ' Cycle = ' + tradingEngine.current.episode.cycle.value)
|
||||
|
||||
/* Reset Data Structures */
|
||||
tradingSystemModule.reset()
|
||||
tradingEpisodeModule.reset()
|
||||
tradingEngineModule.reset()
|
||||
|
||||
await tradingSystemModule.run()
|
||||
}
|
||||
|
||||
function checkIfWeNeedToStop() {
|
||||
/* Checking if we should continue processing this loop or not. */
|
||||
if (bot.STOP_SESSION === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') }
|
||||
updateEpisode('Session Stopped')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (global.STOP_TASK_GRACEFULLY === true) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') }
|
||||
updateEpisode('Task Stopped')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (checkFinalDatetime() === false) {
|
||||
closeEpisode('Final Datetime Reached')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (checkNextCandle() === false) {
|
||||
updateEpisode('All Available Candles Processed')
|
||||
breakLoop = true
|
||||
}
|
||||
|
||||
if (checkMinimunAndMaximunBalance() === false) {
|
||||
closeEpisode('Min or Max Balance Reached')
|
||||
breakLoop = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tradingEngineModule.finalize()
|
||||
tradingSystemModule.finalize()
|
||||
tradingRecordsModule.finalize()
|
||||
tradingEpisodeModule.finalize()
|
||||
|
||||
tradingEngineModule = undefined
|
||||
tradingSystemModule = undefined
|
||||
tradingRecordsModule = undefined
|
||||
tradingEpisodeModule = undefined
|
||||
@@ -347,34 +359,61 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName)
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack)
|
||||
throw(global.DEFAULT_FAIL_RESPONSE)
|
||||
throw (global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
|
||||
function checkLastCandle() {
|
||||
function checkNextCandle() {
|
||||
/*
|
||||
We skip the candle at the head of the market because it has not closed yet. The procedure to determine if we are at the head of the market is different
|
||||
when we are processing Market Files than when we are processing Daily Files. TODO Check that the procedure is good for Beta 6
|
||||
We need to check that the candle we have just processed it is not the last candle.
|
||||
The candle at the head of the market is already skipped from the loop because it has not closed yet.
|
||||
Note: for Daily Files, this means that the last candle of each day will never be processed.
|
||||
*/
|
||||
if (bot.processingDailyFiles) { // We are processing Daily Files
|
||||
if (tradingEngine.current.episode.candle.index.value === candles.length - 1) {
|
||||
/*
|
||||
When processing daily files, we need a mechanism to turn from one day to the next one.
|
||||
That mechanism is the one implemented here. If we detect that the next candle is the last candle of
|
||||
the day, we will advance current process day one day. By doing so, during the next execution, the
|
||||
simulation will receive the candles and indicators files of the next day.
|
||||
|
||||
The first +1 is because array indexes are based on 0.
|
||||
The second +1 is because we need to compare the next candle (remember that the loops allways avoid the
|
||||
last candle of the dataset available.)
|
||||
*/
|
||||
let candlesPerDay = global.ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value
|
||||
if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) {
|
||||
if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') }
|
||||
return false
|
||||
/*
|
||||
Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators.
|
||||
if we skip these two periods, then there will be a hole in the file since the last period will be missing.
|
||||
*/
|
||||
}
|
||||
}
|
||||
} else { // We are processing Market Files
|
||||
if (tradingEngine.current.episode.candle.index.value + 1 === candles.length - 1) {
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkLastCandle -> Skipping Candle because it is the last one and has not been closed yet.') }
|
||||
if (
|
||||
bot.processingDailyFiles &&
|
||||
tradingEngine.current.episode.candle.index.value + 1 + 1 === candlesPerDay
|
||||
) {
|
||||
/*
|
||||
Here we found that the next candle of the dataset is the last candle of the day.
|
||||
It is time to move to the next day so as to receive at the next execution, the indicator files from
|
||||
the next day. At the same time we will reset the index to be pointing to
|
||||
the first candle of the new dataset we shall receive. The first candle of the next day starts
|
||||
at index 0, so we will position the index now at zero.
|
||||
*/
|
||||
tradingEngine.current.episode.candle.index.value = 0
|
||||
tradingEngine.current.episode.processDate.value =
|
||||
tradingEngine.current.episode.processDate.value + global.ONE_DAY_IN_MILISECONDS
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
We reached the head of the market but we are not at the last candle of a day during
|
||||
Daily Files processing. We will advance to the next candle index anyways because in the
|
||||
next execution it will likely have more candles at the dataset. And if it does not,
|
||||
it will just wait there until it does.
|
||||
*/
|
||||
tradingEngine.current.episode.headOfTheMarket.value = true
|
||||
tradingEngine.current.episode.candle.index.value++
|
||||
return false
|
||||
} else {
|
||||
|
||||
/* Wd did not reach the head of the market */
|
||||
tradingEngine.current.episode.headOfTheMarket.value = false
|
||||
tradingEngine.current.episode.candle.index.value++
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function checkInitialDatetime() {
|
||||
@@ -448,7 +487,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
}
|
||||
} catch (err) {
|
||||
logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack)
|
||||
throw(global.DEFAULT_FAIL_RESPONSE)
|
||||
throw (global.DEFAULT_FAIL_RESPONSE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
setUpAppSchema()
|
||||
setUpSessionFolderName()
|
||||
|
||||
/* We validate all parameters received and complete some that might be missing if needed. */
|
||||
if (checkParemeters() === false) { return }
|
||||
|
||||
socialBotsModule.initialize()
|
||||
@@ -208,6 +209,11 @@
|
||||
}
|
||||
|
||||
function useDefaultDatetimes(initialDefault, finalDefault) {
|
||||
/*
|
||||
Note that inside the system, we are going to deal with these
|
||||
dates in their numeric value representation.
|
||||
*/
|
||||
|
||||
/* Initial Datetime */
|
||||
if (bot.SESSION.parameters.timeRange.config.initialDatetime === undefined) {
|
||||
bot.SESSION.parameters.timeRange.config.initialDatetime = initialDefault
|
||||
@@ -310,7 +316,6 @@
|
||||
}
|
||||
|
||||
function startBackTesting(message) {
|
||||
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf()) // Here we initialize the date of the process.
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -319,8 +324,6 @@
|
||||
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> Key 'codeName' or 'secret' not provided. Plese check that and try again."); }
|
||||
return false
|
||||
}
|
||||
|
||||
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf())
|
||||
pProcessConfig.liveWaitTime = bot.SESSION.parameters.timeFrame.config.value
|
||||
return true
|
||||
}
|
||||
@@ -332,8 +335,6 @@
|
||||
return false
|
||||
}
|
||||
|
||||
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf())
|
||||
|
||||
/* Reduce the balance */
|
||||
let balancePercentage = 1 // This is the default value
|
||||
|
||||
@@ -356,7 +357,6 @@
|
||||
}
|
||||
|
||||
function startPaperTrading(message) {
|
||||
bot.tradingProcessDate = new Date(bot.SESSION.parameters.timeRange.config.initialDatetime.valueOf())
|
||||
pProcessConfig.normalWaitTime = bot.SESSION.parameters.timeFrame.config.value
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
const MODULE_NAME = "Trading Bot";
|
||||
const FULL_LOG = true;
|
||||
|
||||
let USER_BOT_MODULE;
|
||||
let USER_BOT_COMMONS;
|
||||
|
||||
const TRADING_PROCESS_MODULE = require(global.ROOT_DIR + '/LowFrequencyTrading/TradingProcess.js');
|
||||
const FILE_STORAGE = require('./FileStorage.js');
|
||||
const SESSION = require(global.ROOT_DIR + 'Session');
|
||||
@@ -33,53 +30,9 @@
|
||||
processConfig = pProcessConfig;
|
||||
if (bot.definedByUI === true) {
|
||||
/* The code of the bot is defined at the UI. No need to load a file with the code. */
|
||||
session.initialize(processConfig, onSessionInitialized)
|
||||
|
||||
function onSessionInitialized(err) {
|
||||
callBackFunction(err);
|
||||
}
|
||||
return
|
||||
session.initialize(processConfig, callBackFunction)
|
||||
}
|
||||
|
||||
let filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo + "/" + pProcessConfig.codeName
|
||||
filePath += "/User.Bot.js"
|
||||
|
||||
fileStorage.getTextFile(filePath, onBotDownloaded);
|
||||
|
||||
function onBotDownloaded(err, text) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> err = " + err.message);
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> onInizialized -> onBotDownloaded -> filePath = " + filePath);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
USER_BOT_MODULE = {}
|
||||
USER_BOT_MODULE.newUserBot = eval(text); // Use this for production
|
||||
|
||||
filePath = bot.dataMine + "/" + "bots" + "/" + bot.repo;
|
||||
filePath += "/Commons.js"
|
||||
|
||||
fileStorage.getTextFile(filePath, onCommonsDownloaded);
|
||||
|
||||
function onCommonsDownloaded(err, text) {
|
||||
if (err.result !== global.DEFAULT_OK_RESPONSE.result) {
|
||||
parentLogger.write(MODULE_NAME, "[WARN] initialize -> onBotDownloaded -> onCommonsDownloaded -> Commons not found: " + err.code || err.message);
|
||||
callBackFunction(global.DEFAULT_OK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
USER_BOT_COMMONS = {}
|
||||
USER_BOT_COMMONS.newCommons = eval(text); // Use this for production
|
||||
|
||||
session.initialize(onSessionInitialized)
|
||||
|
||||
function onSessionInitialized(err) {
|
||||
callBackFunction(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> err = " + err.stack);
|
||||
callBackFunction(global.DEFAULT_FAIL_RESPONSE);
|
||||
@@ -337,7 +290,7 @@
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, "[INFO] run -> loop -> initializeDataDependencies -> onInizialized -> Execution finished well."); }
|
||||
switch (processConfig.framework.name) {
|
||||
case 'Low-Frequency-Trading-Process': {
|
||||
processFramework = TRADING_PROCESS_MODULE.newTradingProcess(bot, logger, UTILITIES, USER_BOT_MODULE, USER_BOT_COMMONS);
|
||||
processFramework = TRADING_PROCESS_MODULE.newTradingProcess(bot, logger, UTILITIES);
|
||||
intitializeProcessFramework();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,12 @@ global.PRECISE = function (floatNumber, precision) {
|
||||
return this.parseFloat(floatNumber.toFixed(precision))
|
||||
}
|
||||
|
||||
global.REMOVE_TIME = function (datetime) {
|
||||
const GMT_SECONDS = ':00.000 GMT+0000';
|
||||
let date = new Date(datetime)
|
||||
return new Date(date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + " " + "00:00" + GMT_SECONDS);
|
||||
}
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.log('[ERROR] Task Server -> server -> uncaughtException -> err.message = ' + err.message)
|
||||
console.log('[ERROR] Task Server -> server -> uncaughtException -> err.stack = ' + err.stack)
|
||||
|
||||
Reference in new issue
Block a user