Files
Superalgos/Trading/TradingSimulation.js
T
2020-06-04 13:55:09 +02:00

684 lines
36 KiB
JavaScript

exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILITIES) {
const FULL_LOG = true
const LOG_FILE_CONTENT = false
const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000
const MODULE_NAME = 'Trading Simulation -> ' + bot.SESSION.name
const GMT_SECONDS = ':00.000 GMT+0000'
let thisObject = {
finalize: finalize,
runSimulation: runSimulation
}
let utilities = UTILITIES.newCloudUtilities(bot, logger)
return thisObject
function finalize() {
thisObject = undefined
}
function runSimulation(
chart,
dataDependencies,
timeFrame,
timeFrameLabel,
currentDay,
variable,
exchangeAPI,
recordsArray,
conditionsArray,
strategiesArray,
positionsArray,
callback,
callBackFunction) {
try {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> Entering function.') }
let processingDailyFiles
if (timeFrame > global.dailyFilePeriods[0][0]) {
processingDailyFiles = false
} else {
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
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> sessionParameters.timeRange.config.initialDatetime = ' + sessionParameters.timeRange.config.initialDatetime) }
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> sessionParameters.timeRange.config.finalDatetime = ' + sessionParameters.timeRange.config.finalDatetime) }
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
/* Variables for this executioin only */
let takePositionNow = false
let closePositionNow = false
/* In some cases we need to know if we are positioned at the last candle of the calendar day, for that we need these variables. */
let lastInstantOfTheDay = 0
if (currentDay) {
lastInstantOfTheDay = currentDay.valueOf() + ONE_DAY_IN_MILISECONDS - 1
}
const TRADING_ENGINE_MODULE = require('./TradingEngine.js')
let tradingEngineModule = TRADING_ENGINE_MODULE.newTradingEngine(bot, logger)
tradingEngineModule.initialize()
const TRADING_ENGINE_MODULE = require('./TradingExecution.js')
let tradingExecutionModule = TRADING_ENGINE_MODULE.newTradingExecution(bot, logger)
tradingExecutionModule.initialize()
const TRADING_RECORDS_MODULE = require('./TradingRecords.js')
let tradingRecordsModule = TRADING_RECORDS_MODULE.newTradingRecords(bot, logger)
tradingRecordsModule.initialize()
const SNAPSHOTS_MODULE = require('./Snapshots.js')
let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger)
snapshotsModule.initialize()
const ANNOUNCEMENTS_MODULE = require('./Announcements.js')
let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger)
announcementsModule.initialize()
const TRADING_SYSTEM_MODULE = require('./TradingSystem.js')
let tradingSystemModule = TRADING_SYSTEM_MODULE.newTradingSystem(bot, logger)
tradingSystemModule.initialize()
/* Main Array and Maps */
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]
/* Main Simulation Loop: We go thourgh all the candles at this time period. */
let currentCandleIndex
/* For Loop Level heartbeat */
let loopingDay
let previousLoopingDay
initializeLoop()
function initializeLoop() {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> initializeLoop -> Entering function.') }
if (bot.RESUME === true && 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
currentCandleIndex = Math.trunc(amount) + 1 // Because we need to start from the next candle
if (currentCandleIndex < 0 || currentCandleIndex > candles.length - 1) {
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> Cannot resume simulation.')
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> currentCandleIndex = ' + currentCandleIndex)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> firstBegin = ' + firstBegin)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> lastBegin = ' + lastBegin)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> diff = ' + diff)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> initializeLoop -> amount = ' + amount)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
return
}
} else {
/* Estimate Initial Candle based on the timeRage configured for the session. */
let firstEnd = candles[0].end
let targetEnd = sessionParameters.timeRange.config.initialDatetime.valueOf()
let diff = targetEnd - firstEnd
let amount = diff / sessionParameters.timeFrame.config.value
currentCandleIndex = Math.trunc(amount)
if (currentCandleIndex < 0) { currentCandleIndex = 0 }
if (currentCandleIndex > 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.
*/
currentCandleIndex = candles.length - 1
}
}
loop()
}
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 # ' + currentCandleIndex) }
/* We are going to take snapshots of the values of indicators at key moments of the sumulation. */
let snapshotKeys = new Map()
snapshotLoopHeaders = []
snapshotDataRecord = []
saveAsLastTriggerOnSnapshot = false
saveAsLastTakePositionSnapshot = false
addToSnapshots = false
let announcementsToBeMade = []
let candle = candles[currentCandleIndex]
/* 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())
}
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
}
/* 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 # ' + currentCandleIndex + ' @ ' + 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 currentCandleIndex has not closed yet. */
let candlesPerDay = ONE_DAY_IN_MILISECONDS / sessionParameters.timeFrame.config.value
if (currentCandleIndex === 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 (currentCandleIndex === 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
}
}
tradingSystemModule.setCandle(candle)
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()
/* Stop Loss Management */
if (
(tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') &&
takePositionNow !== true
) {
tradingSystemModule.checkStopPhases()
tradingSystemModule.calculateStopLoss()
}
/* Take Profit Management */
if (
(tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') &&
takePositionNow !== true
) {
tradingSystemModule.checkTakeProfitPhases()
tradingSystemModule.calculateTakeProfit()
}
tradingEngineModule.updatePositionCounters()
tradingEngineModule.updateDistanceToEventsCounters()
/* Checking if Stop or Take Profit were hit */
if (
(tradingEngine.current.strategy.stageType.value === 'Open Stage' || tradingEngine.current.strategy.stageType.value === 'Manage Stage') &&
takePositionNow !== true
) {
closePositionNow = tradingSystemModule.checkStopLossOrTakeProfitWasHit()
}
/* Taking a Position */
if (
takePositionNow === true
) {
takePositionNow = false
tradingEngineModule.getReadyToTakePosition(candle)
tradingExecutionModule.takePosition()
tradingEngineModule.takePosition()
}
/* Closing a Position */
if (
closePositionNow === true
) {
closePositionNow = false
tradingEngineModule.getReadyToClosePosition(candle)
tradingExecutionModule.closePosition()
tradingEngineModule.closePosition()
}
/* Closing the Closing Stage */
if (tradingEngine.current.strategy.stageType.value === 'Close Stage') {
if (candle.begin - 5 * 60 * 1000 > timerToCloseStage) {
tradingEngine.current.strategy.end.value = candle.end
tradingEngine.current.strategy.endRate.value = candle.min
tradingEngine.current.strategy.status.value = 'Closed'
tradingEngine.current.strategy.index.value = -1
tradingEngine.current.strategy.stageType.value = 'No Stage'
timerToCloseStage = 0
tradingEngine.current.distanceToEvent.triggerOff.value = 1
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing the Closing Stage -> Exiting Close Stage.') }
} else {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Closing the Closing Stage -> Waiting for timer.') }
}
}
snapshotsModule.manageSnapshots()
tradingRecordsModule.addSimulationRecord()
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. */
makeAnnoucements()
controlLoop()
return
function checkAnnouncements(node, value) {
/*
Value is an optional parameter that represents the value that the announcement is monitoring for change (for numeric values only).
If we do receive this value, we will only make the annoucement if the variance is grater than the user pre-defined value
for this variance.
*/
if (node.announcements !== undefined) {
for (let i = 0; i < node.announcements.length; i++) {
let announcement = node.announcements[i]
let key = node.type + '-' + announcement.name + '-' + announcement.id
let lastPeriodAnnounced = -1
let newAnnouncementRecord = {}
for (let j = 0; j < variable.announcements.length; j++) {
let announcementRecord = variable.announcements[j]
if (announcementRecord.key === key) {
lastPeriodAnnounced = announcementRecord.periods
newAnnouncementRecord = announcementRecord
break
}
}
if (tradingEngine.episode.episodeCounters.periods > lastPeriodAnnounced) {
if (isNaN(value) === false) {
/* The Value Variation is what tells us how much the value already announced must change in order to annouce it again. */
let valueVariation
let config = announcement.config
valueVariation = config.valueVariation
if (newAnnouncementRecord.value !== undefined && valueVariation !== undefined) {
let upperLimit = newAnnouncementRecord.value + newAnnouncementRecord.value * valueVariation / 100
let lowerLimit = newAnnouncementRecord.value - newAnnouncementRecord.value * valueVariation / 100
if (value > lowerLimit && value < upperLimit) {
/* There is not enough variation to announce this again. */
return
}
}
}
/*
We store the announcement temporarily at an Array to differ its execution, becasue we need to evaulate its formula
and at this point in time the potential variables used at the formula are still not set.
*/
announcement.value = value
announcementsToBeMade.push(announcement)
/* Next, we will remmeber this announcement was already done, so that it is not announced again in further processing of the same day. */
if (newAnnouncementRecord.periods !== undefined) {
newAnnouncementRecord.periods = tradingEngine.episode.episodeCounters.periods
newAnnouncementRecord.value = value
} else {
newAnnouncementRecord = {
key: key,
periods: tradingEngine.episode.episodeCounters.periods,
value: value
}
variable.announcements.push(newAnnouncementRecord)
}
}
}
}
}
function makeAnnoucements() {
/* Here we go through all the annoucements that need to be done during this loop, and we just do them. */
for (let i = 0; i < announcementsToBeMade.length; i++) {
announcement = announcementsToBeMade[i]
/* Here we check if there is a formula attached to the annoucement, we evaluate it to get the annoucement text. */
let formulaValue
if (announcement.formula !== undefined) {
try {
let value = announcement.value
formulaValue = eval(announcement.formula.code)
} catch (err) {
announcement.formula.error = err.message
}
}
announcement.formulaValue = formulaValue
if (bot.SESSION.socialBots !== undefined) {
bot.SESSION.socialBots.announce(announcement)
}
}
}
function addCodeToSnapshot(code) {
if (code === undefined) { return }
try {
let instructionsArray = code.split(' ')
for (let i = 0; i < instructionsArray.length; i++) {
let instruction = instructionsArray[i]
instruction = instruction.replace('(', '')
instruction = instruction.replace(')', '')
instruction = instruction.replace(/</g, '')
instruction = instruction.replace(/>/g, '')
instruction = instruction.replace(/<=/g, '')
instruction = instruction.replace(/>=/g, '')
instruction = instruction.replace(/!=/g, '')
instruction = instruction.replace(/!==/g, '')
instruction = instruction.replace(/==/g, '')
instruction = instruction.replace(/===/g, '')
instruction = instruction.replace(/{/g, '')
instruction = instruction.replace(/}/g, '')
if (instruction.indexOf('chart') >= 0) {
let parts = instruction.split('.')
let timeFrame = parts[1]
let product = parts[2]
let property
checkPrevious(3)
function checkPrevious(index) {
property = parts[index]
if (property === 'previous') {
product = product + '.previous'
checkPrevious(index + 1)
}
}
// Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10
if (timeFrame !== 'atAnyvariable.timeFrame') {
timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7)
}
let key = timeFrame + '-' + product + '-' + property
let existingKey = snapshotKeys.get(key)
if (existingKey === undefined) { // means that at the current loop this property of this product was not used before.
snapshotKeys.set(key, key)
snapshotLoopHeaders.push(key)
let value = eval(instruction)
snapshotDataRecord.push(value)
}
}
}
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> code = ' + code)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> addCodeToSnapshot -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
}
}
}
function controlLoop() {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> Entering function.') }
/* Checking if we should continue processing this loop or not. */
if (bot.STOP_SESSION === true) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.') }
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this session.')
afterLoop()
return
}
if (global.STOP_TASK_GRACEFULLY === true) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.') }
console.log('[INFO] runSimulation -> controlLoop -> We are going to stop here bacause we were requested to stop processing this task.')
afterLoop()
return
}
currentCandleIndex++
if (currentCandleIndex < candles.length) {
setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed.
} else {
afterLoop()
}
}
function afterLoop() {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> afterLoop -> Entering function.') }
tradingEngineModule.finalize()
tradingExecutionModule.finalize()
tradingSystemModule.finalize()
tradingRecordsModule.finalize()
snapshotsModule.finalize()
announcementsModule.finalize()
callback(tradingSystem, snapshots.headers, snapshots.triggerOn, snapshots.takePosition)
}
function getElement(pArray, currentCandle, datasetName) {
if (pArray === undefined) { return }
try {
let element
for (let i = 0; i < pArray.length; i++) {
element = pArray[i]
if (currentCandle.end === element.end) { // when there is an exact match at the end we take that element
return element
} else {
if (
i > 0 &&
element.end > currentCandle.end
) {
let previousElement = pArray[i - 1]
if (previousElement.end < currentCandle.end) {
return previousElement // If one elements goes into the future of currentCandle, then we stop and take the previous element.
} else {
return
}
}
if (
i === pArray.length - 1 // If we reach the end of the array, then we return the last element.
&&
element.end < currentCandle.end
) {
return element
}
}
}
return
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> datasetName = ' + datasetName)
logger.write(MODULE_NAME, '[ERROR] runSimulation -> getElement -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
}
}
} catch (err) {
logger.write(MODULE_NAME, '[ERROR] runSimulation -> err = ' + err.stack)
callBackFunction(global.DEFAULT_FAIL_RESPONSE)
}
}
}