Overall Simulation Structure done

This commit is contained in:
Luis Fernando Molina committed 2020-06-05 12:31:21 +02:00
1 parent f0b58645ff
commit f8eb06b486
2 files changed
+203 -206

No files matched your search

+13 -5
View File
@@ -11,8 +11,17 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
let tradingEngine
/* We are going to take snapshots of the values of indicators at key moments of the sumulation. */
/* Snapshots of Trigger On and Take Positions */
let snapshots = {
headers: undefined,
triggerOn: [],
takePosition: [],
lastTriggerOn: undefined,
lastTakePosition: undefined
}
/* We are going to take snapshots of the values of indicators at key moments of the sumulation. */
let snapshotKeys = new Map()
snapshotLoopHeaders = []
snapshotDataRecord = []
@@ -68,10 +77,9 @@ exports.newSnapshots = function newSnapshots(bot, logger) {
}
if (positionedAtYesterday === false) {
snapshots.triggerOn.push(closeValues.concat(snapshots.lastTriggerOn))
snapshots.takePosition.push(closeValues.concat(snapshots.lastTakePosition))
}
snapshots.triggerOn.push(closeValues.concat(snapshots.lastTriggerOn))
snapshots.takePosition.push(closeValues.concat(snapshots.lastTakePosition))
snapshots.lastTriggerOn = undefined
snapshots.lastTakePosition = undefined
addToSnapshots = false
+190 -201
View File
@@ -42,15 +42,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
processingDailyFiles = true
}
/* Snapshots of Trigger On and Take Positions */
let snapshots = {
headers: undefined,
triggerOn: [],
takePosition: [],
lastTriggerOn: undefined,
lastTakePosition: undefined
}
let tradingSystem = bot.TRADING_SYSTEM
let tradingEngine = bot.TRADING_ENGINE
let sessionParameters = bot.SESSION.parameters
@@ -61,12 +52,10 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
let timerToCloseStage = 0
/* Stop Loss Management */
const MIN_STOP_LOSS_VALUE = 0.0000000001 // We can not let the stop be zero to avoid division by 0 error or infinity numbers as a result.
const MAX_STOP_LOSS_VALUE = Number.MAX_SAFE_INTEGER
/* Take Profit Management */
const MIN_TAKE_PROFIT_VALUE = 0.0000000001 // We can not let the buy order be zero to avoid division by 0 error or infinity numbers as a result.
const MAX_TAKE_PROFIT_VALUE = Number.MAX_SAFE_INTEGER
@@ -80,6 +69,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1
}
/* 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()
@@ -104,17 +94,16 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger)
tradingSystemModule.initialize()
/* Main Array and Maps */
/* Setting up the candles array: The whole simulation is based on the array of candles at the time-frame defined at the session parameters. */
let propertyName = 'at' + sessionParameters.timeFrame.config.label.replace('-', '')
let candles = chart[propertyName].candles
let currentChart = chart[propertyName]
/* Last Candle */
let lastCandle = candles[candles.length - 1]
let lastCandle = candles[candles.length - 1] // TODO Maybe remove this
/* For Loop Level heartbeat */
let loopingDay
let previousLoopingDay
/* Variable needed for heartbeat functionality */
let heartBeatDate
let previousheartBeatDate
initializeLoop()
@@ -162,191 +151,38 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
}
function loop() {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Entering function.') }
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Processing candle # ' + tradingEngine.current.candle.index.value) }
let candle = candles[tradingEngine.current.candle.index.value]
tradingEngineModule.setCurrentCandle(candle)
/* Not processing while out of user-defined time range */
if (candle.end < sessionParameters.timeRange.config.initialDatetime.valueOf()) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') }
controlLoop()
return
}
if (candle.begin > sessionParameters.timeRange.config.finalDatetime.valueOf()) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') }
afterLoop()
return
}
/* Here we update at the chart data structure the objects for each product representing where we are currently standing at the simulation loop */
if (processingDailyFiles) {
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
let mapKey = dailyFilePeriods[j][1]
let propertyName = 'at' + mapKey.replace('-', '')
let thisChart = chart[propertyName]
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let currentElement = getElement(thisChart[pluralVariableName], candle, 'Daily' + '-' + mapKey + '-' + pluralVariableName)
thisChart[singularVariableName] = currentElement
}
}
}
/* Finding the Current Element on Market Files */
for (let j = 0; j < global.marketFilesPeriods.length; j++) {
let mapKey = marketFilesPeriods[j][1]
let propertyName = 'at' + mapKey.replace('-', '')
let thisChart = chart[propertyName]
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let currentElement = getElement(thisChart[pluralVariableName], candle, 'Market' + '-' + mapKey + '-' + pluralVariableName)
thisChart[singularVariableName] = currentElement
}
}
/* Finding the Current Element on Single Files */
function isItInside(elementWithTimestamp, elementWithBeginEnd) {
if (elementWithTimestamp.timestamp >= elementWithBeginEnd.begin && elementWithTimestamp.timestamp <= elementWithBeginEnd.end) {
return true
} else {
return false
}
}
let propertyName = 'atAnyTimeFrame'
let thisChart = chart[propertyName]
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Single-File') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let elementArray = thisChart[pluralVariableName]
let currentElement
if (elementArray !== undefined) {
currentElement = elementArray[elementArray.length - 1]
}
thisChart[singularVariableName] = currentElement
}
/* While we are processing the previous day. */
let positionedAtYesterday = false
if (currentDay) {
positionedAtYesterday = (candle.end < currentDay.valueOf())
}
let candle = candles[tradingEngine.current.candle.index.value] // This is the current candle the Simulation is working at.
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()) }
let ticker = {
bid: candle.close,
ask: candle.close,
last: candle.close
if (checkIfWeAreAtTheHeadOfTheMarket() === false) {
afterLoop()
return
}
/* We will produce a simulation level heartbeat in order to inform the user this is running. */
loopingDay = new Date(Math.trunc(candle.begin / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS)
if (loopingDay.valueOf() !== previousLoopingDay) {
let processingDate = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2)
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Simulation ' + bot.sessionKey + ' Loop # ' + tradingEngine.current.candle.index.value + ' @ ' + processingDate) }
/* Telling the world we are alive and doing well */
let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime.valueOf())
let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime.valueOf())
let currentDateString = loopingDay.getUTCFullYear() + '-' + utilities.pad(loopingDay.getUTCMonth() + 1, 2) + '-' + utilities.pad(loopingDay.getUTCDate(), 2)
let currentDate = new Date(loopingDay)
let percentage = global.getPercentage(fromDate, currentDate, lastDate)
bot.processHeartBeat(currentDateString, percentage)
if (global.areEqualDates(currentDate, new Date()) === false) {
logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage)
}
}
previousLoopingDay = loopingDay.valueOf()
tradingEngine.episode.episodeCounters.periods++
tradingEngine.episode.episodeStatistics.days = tradingEngine.episode.episodeCounters.periods * sessionParameters.timeFrame.config.value / ONE_DAY_IN_MILISECONDS
if (processingDailyFiles) {
/* We skip the candle at the head of the market because tradingEngine.current.candle.index.value has not closed yet. */
let candlesPerDay = ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value
if (tradingEngine.current.candle.index.value === candles.length - 1) {
if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) {
/* We are at the head of the market, thus we skip the last candle because it has not close yet. */
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet.') }
controlLoop()
return
/* 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 tradingEngine.episode.episodeCounters.periods, then there will be a hole in the file since the last period will be missing. */
}
}
} else { // We are processing Market Files
if (tradingEngine.current.candle.index.value === candles.length - 1) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Skipping Candle because it is the last one and has not been closed yet.') }
controlLoop()
return
}
if (checkInitialAndFinalDatetime() === false) {
afterLoop()
return
}
if (checkMinimunAndMaximunBalance() === false) {
afterLoop()
return
}
heartBeat()
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.
positionChartAtCurrentCandle()
tradingEngineModule.updateEpisodeCountersAndStatistics()
tradingSystemModule.setChart(chart)
tradingSystemModule.evalConditions()
tradingSystemModule.evalFormulas()
/* Checks for Minimun and Maximun Balance. We do the check while not inside any strategy only. */
if (
tradingEngine.current.strategy.stageType.value === 'No Stage' &&
tradingEngine.current.strategy.index.value === -1
) {
/*
First thing to do is to check if we are below the minimum balance, or above the maximun balance.
If we are, we are not gooing to trigger on again.
*/
let minimumBalance
let maximumBalance
let balance
if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) {
balance = tradingEngine.current.balance.baseAsset.value
minimumBalance = sessionParameters.sessionBaseAsset.config.minimumBalance
maximumBalance = sessionParameters.sessionBaseAsset.config.maximumBalance
} else {
balance = tradingEngine.current.balance.quotedAsset.value
minimumBalance = sessionParameters.sessionQuotedAsset.config.minimumBalance
maximumBalance = sessionParameters.sessionQuotedAsset.config.maximumBalance
}
let stopRunningDate = new Date(candle.begin)
if (balance <= minimumBalance) {
tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString()
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> ' + tradingSystem.error) }
afterLoop()
return
}
if (balance >= maximumBalance) {
tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString()
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> ' + tradingSystem.error) }
afterLoop()
return
}
}
tradingSystemModule.checkTriggerOn()
tradingSystemModule.checkTriggerOff()
takePositionNow = tradingSystemModule.checkTakePosition()
@@ -369,8 +205,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
tradingSystemModule.calculateTakeProfit()
}
tradingEngineModule.updatePositionCounters()
tradingEngineModule.updatePositionCountersAndStatistics()
tradingEngineModule.updateDistanceToEventsCounters()
/* Checking if Stop or Take Profit were hit */
@@ -381,8 +216,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
closePositionNow = tradingSystemModule.checkStopLossOrTakeProfitWasHit()
}
/* Taking a Position */
if (
takePositionNow === true
@@ -403,7 +236,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
tradingEngineModule.closePosition()
}
/* Closing the Closing Stage */
/* Closing the Closing Stage TODO This should die once we have execution working. */
if (tradingEngine.current.strategy.stageType.value === 'Close Stage') {
if (candle.begin - 5 * 60 * 1000 > timerToCloseStage) {
tradingEngine.current.strategy.end.value = candle.end
@@ -427,13 +260,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
tradingRecordsModule.addConditionsRecord()
tradingRecordsModule.addStrategyRecord()
tradingRecordsModule.addPositionRecord()
/* We will remmember the last candle processed, so that if it is the last one of this run we can use it to continue from there next time. */
tradingEngine.last.candle.begin = candle.begin
tradingEngine.last.candle.end = candle.end
/* After everything at the simulation level was done, we will do the annoucements that are pending. */
annoucementsModule.makeAnnoucements()
annoucementsModule.makeAnnoucements() // After everything at the simulation level was done, we will do the annoucements that are pending.
controlLoop()
}
@@ -515,6 +342,168 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
}
}
function heartBeat() {
/* We will produce a simulation level heartbeat in order to inform the user this is running. */
heartBeatDate = new Date(Math.trunc(candle.begin / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS)
if (heartBeatDate.valueOf() !== previousheartBeatDate) {
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.candle.index.value + ' @ ' + processingDate) }
/* Telling the world we are alive and doing well */
let fromDate = new Date(sessionParameters.timeRange.config.initialDatetime.valueOf())
let lastDate = new Date(sessionParameters.timeRange.config.finalDatetime.valueOf())
let currentDateString = heartBeatDate.getUTCFullYear() + '-' + utilities.pad(heartBeatDate.getUTCMonth() + 1, 2) + '-' + utilities.pad(heartBeatDate.getUTCDate(), 2)
let currentDate = new Date(heartBeatDate)
let percentage = global.getPercentage(fromDate, currentDate, lastDate)
bot.processHeartBeat(currentDateString, percentage)
if (global.areEqualDates(currentDate, new Date()) === false) {
logger.newInternalLoop(bot.codeName, bot.process, currentDate, percentage)
}
}
previousheartBeatDate = heartBeatDate.valueOf()
}
function positionChartAtCurrentCandle() {
/*
In conditions and Formulas, we want users to have an easy sintax to refer to indicators. In order to achieve that, we need the user to have
easy access to the current candle for instance, or the current bollinger band, meaning the one the Simulation is currently standing at.
For that reason we do the following processing, to have at the chart data structure the current objects of each indicator / time frame.
*/
/* Finding the Current Element on Market Files */
if (processingDailyFiles) {
for (let j = 0; j < global.dailyFilePeriods.length; j++) {
let mapKey = dailyFilePeriods[j][1]
let propertyName = 'at' + mapKey.replace('-', '')
let thisChart = chart[propertyName]
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Daily') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let currentElement = getElement(thisChart[pluralVariableName], candle, 'Daily' + '-' + mapKey + '-' + pluralVariableName)
thisChart[singularVariableName] = currentElement
}
}
}
/* Finding the Current Element on Market Files */
for (let j = 0; j < global.marketFilesPeriods.length; j++) {
let mapKey = marketFilesPeriods[j][1]
let propertyName = 'at' + mapKey.replace('-', '')
let thisChart = chart[propertyName]
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Multi-Period-Market') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let currentElement = getElement(thisChart[pluralVariableName], candle, 'Market' + '-' + mapKey + '-' + pluralVariableName)
thisChart[singularVariableName] = currentElement
}
}
/* Finding the Current Element At Single Files*/
let propertyName = 'atAnyTimeFrame'
let thisChart = chart[propertyName]
for (let k = 0; k < dataDependencies.length; k++) {
let dataDependencyNode = dataDependencies[k]
if (dataDependencyNode.referenceParent.config.codeName !== 'Single-File') { continue }
let singularVariableName = dataDependencyNode.referenceParent.parentNode.config.singularVariableName
let pluralVariableName = dataDependencyNode.referenceParent.parentNode.config.pluralVariableName
let elementArray = thisChart[pluralVariableName]
let currentElement
if (elementArray !== undefined) {
currentElement = elementArray[elementArray.length - 1]
}
thisChart[singularVariableName] = currentElement
}
}
function checkIfWeAreAtTheHeadOfTheMarket() {
/*
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
*/
if (processingDailyFiles) { // We are processing Daily Files
let candlesPerDay = ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value
if (tradingEngine.current.candle.index.value === candles.length - 1) {
if ((candles.length < candlesPerDay) || (candles.length > candlesPerDay && candles.length < candlesPerDay * 2)) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') }
return false
/*
Note here that in the last candle of the first day or the second day it will use an incomplete candle and partially calculated indicators.
if we skip these two periods, then there will be a hole in the file since the last period will be missing.
*/
}
}
} else { // We are processing Market Files
if (tradingEngine.current.candle.index.value === candles.length - 1) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkIfWeAreAtTheHeadOfTheMarket -> Skipping Candle because it is the last one and has not been closed yet.') }
return false
}
}
return true
}
function checkInitialAndFinalDatetime() {
/* Here we check that the current candle is not outside of user-defined time range at the session parameters.*/
if (candle.end < sessionParameters.timeRange.config.initialDatetime.valueOf()) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle before the sessionParameters.timeRange.config.initialDatetime.') }
return false
}
if (candle.begin > sessionParameters.timeRange.config.finalDatetime.valueOf()) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkInitialAndFinalDatetime -> Skipping Candle after the sessionParameters.timeRange.config.finalDatetime.') }
return false
}
return true
}
function checkMinimunAndMaximunBalance() {
/* Checks for Minimun and Maximun Balance. We do the check while not inside any strategy only. */
if (
tradingEngine.current.strategy.stageType.value === 'No Stage' &&
tradingEngine.current.strategy.index.value === -1
) {
/*
First thing to do is to check if we are below the minimum balance, or above the maximun balance.
If we are, we are not gooing to trigger on again.
*/
let minimumBalance
let maximumBalance
let balance
if (sessionParameters.sessionBaseAsset.name === bot.market.marketBaseAsset) {
balance = tradingEngine.current.balance.baseAsset.value
minimumBalance = sessionParameters.sessionBaseAsset.config.minimumBalance
maximumBalance = sessionParameters.sessionBaseAsset.config.maximumBalance
} else {
balance = tradingEngine.current.balance.quotedAsset.value
minimumBalance = sessionParameters.sessionQuotedAsset.config.minimumBalance
maximumBalance = sessionParameters.sessionQuotedAsset.config.maximumBalance
}
let stopRunningDate = new Date(candle.begin)
if (balance <= minimumBalance) {
tradingSystem.error = 'Min Balance @ ' + stopRunningDate.toLocaleString()
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) }
return false
}
if (balance >= maximumBalance) {
tradingSystem.error = 'Max Balance @ ' + stopRunningDate.toLocaleString()
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> checkMinimunAndMaximunBalance -> ' + tradingSystem.error) }
return false
}
}
return true
}
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)