Files
Superalgos/LowFrequencyTrading/TradingStages.js
T
2020-08-07 09:21:30 +02:00

1257 lines
60 KiB
JavaScript

exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineModule) {
/*
This module packages all functions related to Stages.
*/
const MODULE_NAME = 'Trading Stages'
let thisObject = {
updateChart: updateChart,
mantain: mantain,
runTriggerStage: runTriggerStage,
runOpenStage: runOpenStage,
runManageStage: runManageStage,
runCloseStage: runCloseStage,
updateEnds: updateEnds,
updateStatus: updateStatus,
updateCounters: updateCounters,
updateStatistics: updateStatistics,
resetStage: resetStage,
initialize: initialize,
finalize: finalize
}
const TRADING_STRATEGY_MODULE = require('./TradingStrategy.js')
let tradingStrategyModule = TRADING_STRATEGY_MODULE.newTradingStrategy(bot, logger, tradingEngineModule)
const TRADING_POSITION_MODULE = require('./TradingPosition.js')
let tradingPositionModule = TRADING_POSITION_MODULE.newTradingPosition(bot, logger, tradingEngineModule)
const TRADING_ENGINE_EXECUTION = require('./TradingExecution.js')
let tradingExecutionModule = TRADING_ENGINE_EXECUTION.newTradingExecution(bot, logger, tradingEngineModule)
const ANNOUNCEMENTS_MODULE = require('./Announcements.js')
let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger)
const SNAPSHOTS_MODULE = require('./Snapshots.js')
let snapshotsModule = SNAPSHOTS_MODULE.newSnapshots(bot, logger)
let tradingEngine
let tradingSystem
let sessionParameters
return thisObject
function initialize() {
tradingSystem = bot.simulationState.tradingSystem
tradingEngine = bot.simulationState.tradingEngine
sessionParameters = bot.SESSION.parameters
tradingStrategyModule.initialize()
tradingPositionModule.initialize()
announcementsModule.initialize()
snapshotsModule.initialize()
tradingExecutionModule.initialize()
}
function finalize() {
tradingEngine = undefined
tradingSystem = undefined
sessionParameters = undefined
tradingStrategyModule.finalize()
tradingStrategyModule = undefined
tradingPositionModule.finalize()
tradingPositionModule = undefined
announcementsModule.finalize()
announcementsModule = undefined
snapshotsModule.finalize()
snapshotsModule = undefined
tradingExecutionModule.finalize()
tradingExecutionModule = undefined
}
function updateChart(pChart) {
snapshotsModule.updateChart(pChart)
}
function mantain() {
mantainPositions()
mantainStrategies()
mantainStages()
mantainOrders()
function mantainStrategies() {
tradingStrategyModule.updateStatus()
tradingStrategyModule.updateCounters()
tradingStrategyModule.updateEnds()
}
function mantainStages() {
updateStatus()
updateCounters()
updateEnds()
}
function mantainPositions() {
tradingPositionModule.updateStatus()
tradingPositionModule.updateCounters()
tradingPositionModule.updateEnds()
}
function mantainOrders() {
/* Reset All Exchange Orders that are on Closed status, to their initial value */
if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return }
let stageNode
let executionNode
stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
executionNode = stageNode.openExecution
resetExecution(executionNode, tradingEngine.current.strategyOpenStage.status.value)
stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
executionNode = stageNode.closeExecution
resetExecution(executionNode, tradingEngine.current.strategyCloseStage.status.value)
function resetExecution(executionNode, stageStatus) {
resetExecutionAlgorithms(executionNode)
function resetExecutionAlgorithms(executionNode) {
for (let i = 0; i < executionNode.executionAlgorithms.length; i++) {
let executionAlgorithm = executionNode.executionAlgorithms[i]
resetOrders(executionAlgorithm.marketBuyOrders)
resetOrders(executionAlgorithm.marketSellOrders)
resetOrders(executionAlgorithm.limitBuyOrders)
resetOrders(executionAlgorithm.limitSellOrders)
}
}
function resetOrders(orders) {
for (let i = 0; i < orders.length; i++) {
let tradingSystemOrder = orders[i]
if (tradingSystemOrder.referenceParent === undefined) { continue }
let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id)
if (tradingEngineOrder.status === undefined) { continue }
if (tradingEngineOrder.status.value === 'Closed') {
/* We reset the order data structure inside the Trading Engine to its initial value */
tradingEngineOrder.initialize(tradingEngineOrder)
if (tradingSystemOrder.config.spawnMultipleOrders !== true) {
/*
We close the lock so as to prevent this data structure to be used again during this same stage execution.
*/
if (stageStatus === 'Open') {
tradingEngineOrder.lock.value = 'Closed'
}
}
}
}
}
}
}
}
function runTriggerStage() {
/*
We check if we will be triggering on, off or taking position.
*/
checkTriggerOn()
checkTriggerOff()
checkTakePosition()
function checkTriggerOn() {
if (
tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue
) {
/*
To pick a new strategy we will check that any of the situations of the trigger on is true.
Once we enter into one strategy, we will ignore market conditions for others. However there is also
a strategy trigger off which can be hit before taking a position. If hit, we would
be outside a strategy again and looking for the conditions to enter all over again.
*/
tradingSystem.evalConditions(tradingSystem, 'Trigger On Event')
for (let j = 0; j < tradingSystem.tradingStrategies.length; j++) {
if ( // If a strategy was already picked during the loop, we exit the loop
tradingEngine.current.strategy.index.value !== tradingEngine.current.strategy.index.config.initialValue
) { continue }
let strategy = tradingSystem.tradingStrategies[j]
let triggerStage = strategy.triggerStage
if (triggerStage !== undefined) {
if (triggerStage.triggerOn !== undefined) {
for (let k = 0; k < triggerStage.triggerOn.situations.length; k++) {
let situation = triggerStage.triggerOn.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(triggerStage.triggerOn.id)
tradingSystem.highlights.push(triggerStage.id)
tradingStrategyModule.openStrategy(j, situation.name, strategy.name)
/* Updating Episode Counters */
tradingEngine.current.episode.episodeCounters.strategies.value++
/* Initialize this */
tradingEngine.current.distanceToEvent.triggerOn.value = 1
announcementsModule.makeAnnoucements(triggerStage.triggerOn)
announcementsModule.makeAnnoucements(triggerStage)
if (bot.SESSION.type === 'Backtesting Session') {
if (sessionParameters.snapshots !== undefined) {
if (sessionParameters.snapshots.config.strategy === true) {
snapshotsModule.strategyEntry()
}
}
}
changeStageStatus('Trigger Stage', 'Open')
}
}
}
}
}
}
}
function checkTriggerOff() {
if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let triggerStage = strategy.triggerStage
tradingSystem.evalConditions(strategy, 'Trigger Off Event')
if (triggerStage !== undefined) {
if (triggerStage.triggerOff !== undefined) {
for (let k = 0; k < triggerStage.triggerOff.situations.length; k++) {
let situation = triggerStage.triggerOff.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(triggerStage.triggerOff.id)
tradingSystem.highlights.push(triggerStage.id)
tradingEngine.current.distanceToEvent.triggerOff.value = 1
announcementsModule.makeAnnoucements(triggerStage.triggerOff)
changeStageStatus('Trigger Stage', 'Closed')
tradingStrategyModule.closeStrategy('Trigger Off')
}
}
}
}
}
}
function checkTakePosition() {
if (
tradingEngine.current.strategyTriggerStage.status.value === 'Open'
) {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let triggerStage = strategy.triggerStage
tradingSystem.evalConditions(strategy, 'Take Position Event')
tradingSystem.evalFormulas(strategy, 'Take Position Event')
if (triggerStage !== undefined) {
if (triggerStage.takePosition !== undefined) {
for (let k = 0; k < triggerStage.takePosition.situations.length; k++) {
let situation = triggerStage.takePosition.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(triggerStage.takePosition.id)
tradingSystem.highlights.push(triggerStage.id)
tradingPositionModule.openPosition(situation.name)
announcementsModule.makeAnnoucements(triggerStage.takePosition)
announcementsModule.makeAnnoucements(strategy.openStage)
if (bot.SESSION.type === 'Backtesting Session') {
if (sessionParameters.snapshots !== undefined) {
if (sessionParameters.snapshots.config.position === true) {
snapshotsModule.positionEntry()
}
}
}
changeStageStatus('Trigger Stage', 'Closed')
changeStageStatus('Open Stage', 'Opening')
changeStageStatus('Manage Stage', 'Opening')
}
}
}
}
}
}
}
async function runOpenStage() {
/* Abort Open Stage Check */
if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') {
/*
if the Close stage is opened while the open stage is still open that means that
we need to stop placing orders, check what happened to the orders already placed,
and cancel all unfilled limit orders. We call this the Closing status of the Open Stage.
*/
changeStageStatus('Open Stage', 'Closing')
}
/* Opening Status Procedure */
if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') {
/* This procedure is intended to run only once */
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
let executionNode = stageNode.openExecution
/* Reset the Exchange Orders data structure to its initial value */
tradingEngine.exchangeOrders.initialize(tradingEngine.exchangeOrders)
tradingSystem.evalConditions(stageNode, 'Initial Definition')
tradingSystem.evalFormulas(stageNode, 'Initial Definition')
/* Remember the balance we had before taking the position to later calculate profit or loss */
tradingEngine.previous.balance.baseAsset.value = tradingEngine.current.balance.baseAsset.value
tradingEngine.previous.balance.quotedAsset.value = tradingEngine.current.balance.quotedAsset.value
/* Entry Position size and rate */
tradingSystem.evalFormulas(stageNode, 'Initial Targets')
tradingPositionModule.initialTargets(stageNode)
initializeStageTargetSize()
/* Check Execution at open stage node */
tradingSystem.evalConditions(stageNode, 'Open Execution')
tradingSystem.evalFormulas(stageNode, 'Open Execution')
await tradingExecutionModule.runExecution(
executionNode,
false,
tradingEngine.current.strategyOpenStage
)
/* From here on, the state is officialy Open */
changeStageStatus('Open Stage', 'Open')
return
}
/* Open Status Procedure */
if (tradingEngine.current.strategyOpenStage.status.value === 'Open') {
/*
While the Open Stage is Open, we do our regular stuff: place orders and check
what happened to the orders already placed.
*/
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
let executionNode = stageNode.openExecution
/* Every Loop Cycle Activity */
tradingSystem.evalConditions(stageNode, 'Open Execution')
tradingSystem.evalFormulas(stageNode, 'Open Execution')
await tradingExecutionModule.runExecution(
executionNode,
false,
tradingEngine.current.strategyOpenStage
)
/*
The Open is finished when the placedSize reaches the targetSize in any of the
stage assets, and the fillSize + feesPaid reaches the placedSize also in any
of the stage Assets.
This can happens at any time when we update the sizeFilled and the feesPaid values
when we see at the exchange that orders were filled.
*/
if (
(
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced >=
tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize
||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced >=
tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize
)
&&
(
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value +
tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >=
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value
||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value +
tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >=
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value
)
) {
changeStageStatus('Open Stage', 'Closed', 'Position Size Filled')
} else {
/* Check the Close Stage Event */
tradingSystem.evalConditions(stageNode, 'Close Stage Event')
if (checkStopStageEvent(stageNode) === true) {
changeStageStatus('Open Stage', 'Closing', 'Close Stage Event')
}
}
}
/* Closing Status Procedure */
if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') {
/*
During the closing stage status, we do not place new orders, just check
if the ones placed were filled, and cancel the ones not filled.
*/
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
let executionNode = stageNode.openExecution
/*
Check if there are unfilled orders, we will check if they were executed,
and cancel the ones that were not.
*/
tradingSystem.evalConditions(stageNode, 'Open Execution')
tradingSystem.evalFormulas(stageNode, 'Open Execution')
await tradingExecutionModule.runExecution(
executionNode,
true,
tradingEngine.current.strategyOpenStage
)
/*
The Closing is finished when the fillSize + feesPaid reaches the ordersSize.
This can happens either because we update the sizeFilled value when we see
at the exchange that orders were filled, or we reduce the ordersSize when
we cancel not yet filled orders. Here it does not matter the targetSize since
this status represent a force closure of this stage.
*/
if (
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value +
tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >=
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value
||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value +
tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >=
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value
) {
changeStageStatus('Open Stage', 'Closed')
}
}
function initializeStageTargetSize() {
/*
Here we will transform the position size into targets for each asset of the stage.
*/
tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.entryTargetSize.value
tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value
tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize.value, 10)
tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize.value, 10)
}
}
function runManageStage() {
/* Opening Status Procedure */
if (tradingEngine.current.strategyManageStage.status.value === 'Opening') {
/* We jump the management at the loop cycle where the position is being taken. */
changeStageStatus('Manage Stage', 'Open')
return
}
/* Open Status Procedure */
if (tradingEngine.current.strategyManageStage.status.value === 'Open') {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let manageStage = strategy.manageStage
tradingSystem.evalConditions(manageStage, 'Manage Stage')
tradingSystem.evalFormulas(manageStage, 'Manage Stage')
if (
tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' ||
tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage') {
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
tradingSystem.evalConditions(stageNode, 'Initial Definition')
tradingSystem.evalFormulas(stageNode, 'Initial Definition')
}
/* Stop Loss Management */
checkStopPhasesEvents()
calculateStopLoss()
calculateStopLossPosition()
/* Take Profit Management */
checkTakeProfitPhaseEvents()
calculateTakeProfit()
calculateTakeProfitPosition()
/* Checking if Stop or Take Profit were hit */
checkStopLossOrTakeProfitWasHit()
}
function calculateStopLoss() {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let openStage = strategy.openStage
let manageStage = strategy.manageStage
let phase
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) {
if (openStage.initialDefinition !== undefined) {
if (openStage.initialDefinition.initialStopLoss !== undefined) {
phase = openStage.initialDefinition.initialStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value]
}
}
}
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) {
if (manageStage.managedStopLoss !== undefined) {
phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1]
}
}
if (phase !== undefined) {
if (phase.formula !== undefined) {
let previousValue = tradingEngine.current.position.stopLoss.value
tradingPositionModule.applyStopLossFormula(tradingSystem.formulas, phase.formula.id)
if (tradingEngine.current.position.stopLoss.value !== previousValue) {
announcementsModule.makeAnnoucements(phase)
}
}
}
}
function calculateTakeProfit() {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let openStage = strategy.openStage
let manageStage = strategy.manageStage
let phase
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) {
if (openStage.initialDefinition !== undefined) {
if (openStage.initialDefinition.initialTakeProfit !== undefined) {
phase = openStage.initialDefinition.initialTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value]
}
}
}
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) {
if (manageStage.managedTakeProfit !== undefined) {
phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1]
}
}
if (phase !== undefined) {
if (phase.formula !== undefined) {
let previousValue = tradingEngine.current.position.takeProfit.value
tradingPositionModule.applyTakeProfitFormula(tradingSystem.formulas, phase.formula.id)
if (tradingEngine.current.position.takeProfit.value !== previousValue) {
announcementsModule.makeAnnoucements(phase)
}
}
}
}
function calculateStopLossPosition() {
/*
The position of the Stop Loss (above or below the price) is needed in order to know
later if the price hit the Stop Loss or not at every Simulation cycle. When we get
the first values of the simulation of the Stop Loss we check if it is above or below
the Position Rate, and we assign the values Above or Below to it.
*/
if (tradingEngine.current.position.stopLoss.stopLossPosition.value === tradingEngine.current.position.stopLoss.stopLossPosition.config.initialValue) {
if (tradingEngine.current.position.stopLoss.value > tradingEngine.current.position.entryTargetRate.value) {
tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Above'
} else {
tradingEngine.current.position.stopLoss.stopLossPosition.value = 'Below'
}
}
}
function calculateTakeProfitPosition() {
/*
The position of the Take Profit (above or below the price) is needed in order to know
later if the price hit the Take Profit or not at every Simulation cycle. When we get
the first values of the simulation of the Take Profits we check if it is above or below
the Position Rates, and we assign the values Above or Below to it.
*/
if (tradingEngine.current.position.takeProfit.takeProfitPosition.value === tradingEngine.current.position.takeProfit.takeProfitPosition.config.initialValue) {
if (tradingEngine.current.position.takeProfit.value > tradingEngine.current.position.entryTargetRate.value) {
tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Above'
} else {
tradingEngine.current.position.takeProfit.takeProfitPosition.value = 'Below'
}
}
}
function checkStopPhasesEvents() {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let openStage = strategy.openStage
let manageStage = strategy.manageStage
let parentNode
let phaseIndex
let phase
let stopLoss
let stage
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) {
stage = openStage
if (openStage.initialDefinition !== undefined) {
if (openStage.initialDefinition.initialStopLoss !== undefined) {
parentNode = openStage.initialDefinition
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value
stopLoss = openStage.initialDefinition.initialStopLoss
phase = stopLoss.phases[phaseIndex]
}
}
}
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) {
stage = manageStage
if (manageStage.managedStopLoss !== undefined) {
parentNode = manageStage
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1
stopLoss = manageStage.managedStopLoss
phase = stopLoss.phases[phaseIndex]
}
}
if (parentNode !== undefined) {
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
/* Check the next Phase Event. */
let nextPhaseEvent = phase.nextPhaseEvent
if (nextPhaseEvent !== undefined) {
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
let situation = nextPhaseEvent.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(nextPhaseEvent.id)
tradingSystem.highlights.push(phase.id)
tradingSystem.highlights.push(stopLoss.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(stage.id)
tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage')
announcementsModule.makeAnnoucements(nextPhaseEvent)
return // only one event can pass at the time
}
}
}
/* Check the Move to Phase Events. */
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
if (moveToPhaseEvent !== undefined) {
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
let situation = moveToPhaseEvent.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(moveToPhaseEvent.id)
tradingSystem.highlights.push(phase.id)
tradingSystem.highlights.push(stopLoss.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(stage.id)
let moveToPhase = moveToPhaseEvent.referenceParent
if (moveToPhase !== undefined) {
for (let q = 0; q < stopLoss.phases.length; q++) {
if (stopLoss.phases[q].id === moveToPhase.id) {
tradingPositionModule.updateStopLoss(q + 1, 'Manage Stage')
}
}
} else {
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
continue
}
announcementsModule.makeAnnoucements(moveToPhaseEvent)
return // only one event can pass at the time
}
}
}
}
}
}
function checkTakeProfitPhaseEvents() {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let openStage = strategy.openStage
let manageStage = strategy.manageStage
let parentNode
let phaseIndex
let phase
let takeProfit
let stage
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) {
stage = openStage
if (openStage.initialDefinition !== undefined) {
if (openStage.initialDefinition.initialTakeProfit !== undefined) {
parentNode = openStage.initialDefinition
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value
takeProfit = openStage.initialDefinition.initialTakeProfit
phase = takeProfit.phases[phaseIndex]
}
}
}
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) {
stage = manageStage
if (manageStage.managedTakeProfit !== undefined) {
parentNode = manageStage
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1
takeProfit = manageStage.managedTakeProfit
phase = takeProfit.phases[phaseIndex]
}
}
if (parentNode !== undefined) {
if (phase === undefined) { return } // trying to jump to a phase that does not exists.
/* Check the next Phase Event. */
let nextPhaseEvent = phase.nextPhaseEvent
if (nextPhaseEvent !== undefined) {
for (let k = 0; k < nextPhaseEvent.situations.length; k++) {
let situation = nextPhaseEvent.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(nextPhaseEvent.id)
tradingSystem.highlights.push(phase.id)
tradingSystem.highlights.push(takeProfit.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(stage.id)
tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage')
announcementsModule.makeAnnoucements(nextPhaseEvent)
return // only one event can pass at the time
}
}
}
/* Check the Move to Phase Events. */
for (let n = 0; n < phase.moveToPhaseEvents.length; n++) {
let moveToPhaseEvent = phase.moveToPhaseEvents[n]
if (moveToPhaseEvent !== undefined) {
for (let k = 0; k < moveToPhaseEvent.situations.length; k++) {
let situation = moveToPhaseEvent.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(moveToPhaseEvent.id)
tradingSystem.highlights.push(phase.id)
tradingSystem.highlights.push(takeProfit.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(stage.id)
let moveToPhase = moveToPhaseEvent.referenceParent
if (moveToPhase !== undefined) {
for (let q = 0; q < takeProfit.phases.length; q++) {
if (takeProfit.phases[q].id === moveToPhase.id) {
tradingPositionModule.updateTakeProfit(q + 1, 'Manage Stage')
}
}
} else {
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
continue
}
announcementsModule.makeAnnoucements(moveToPhaseEvent)
return // only one event can pass at the time
}
}
}
}
}
}
function checkStopLossOrTakeProfitWasHit() {
{
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
/*
Checking what happened since the last execution. We need to know if the Stop Loss
or our Take Profit were hit.
*/
/* Stop Loss condition: Here we verify if the Stop Loss was hitted or not. */
if (
(
tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Above' &&
tradingEngine.current.candle.max.value >= tradingEngine.current.position.stopLoss.value
) ||
(
tradingEngine.current.position.stopLoss.stopLossPosition.value === 'Below' &&
tradingEngine.current.candle.min.value <= tradingEngine.current.position.stopLoss.value
)
) {
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Stop Loss was hit.')
tradingPositionModule.closingPosition('Stop Loss')
changeStageStatus('Close Stage', 'Opening')
changeStageStatus('Manage Stage', 'Closed')
announcementsModule.makeAnnoucements(strategy.closeStage)
return
}
/* Take Profit condition: Here we verify if the Take Profit was hit or not. */
if (
(
tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Below' &&
tradingEngine.current.candle.min.value <= tradingEngine.current.position.takeProfit.value
) ||
(
tradingEngine.current.position.takeProfit.takeProfitPosition.value === 'Above' &&
tradingEngine.current.candle.max.value >= tradingEngine.current.position.takeProfit.value
)
) {
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.')
tradingPositionModule.closingPosition('Take Profit')
changeStageStatus('Close Stage', 'Opening')
changeStageStatus('Manage Stage', 'Closed')
announcementsModule.makeAnnoucements(strategy.closeStage)
return
}
return
}
}
}
async function runCloseStage() {
/* Opening Status Procedure */
if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') {
/*
This will happen only once, as soon as the Take Profit or Stop was hit.
We wil not be placing orders at this time because we do not know
the total filled size of the Open Stage. By skiping execution now
we allow the open stage to get a final value for sizeFilled that we can use.
*/
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
/* Exit Position size and rate */
tradingSystem.evalFormulas(stageNode, 'Initial Targets')
tradingPositionModule.initialTargets(stageNode)
initializeStageTargetSize()
changeStageStatus('Close Stage', 'Open')
return
}
/* Open Status Procedure */
if (tradingEngine.current.strategyCloseStage.status.value === 'Open') {
/*
This will happen as long as the Close Stage is Open.
*/
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
let executionNode = stageNode.closeExecution
tradingSystem.evalConditions(stageNode, 'Close Execution')
tradingSystem.evalFormulas(stageNode, 'Close Execution')
await tradingExecutionModule.runExecution(
executionNode,
false,
tradingEngine.current.strategyCloseStage
)
/*
The Open is finished when the placedSize reaches the targetSize in any of the
stage assets, and the fillSize + feesPaid reaches the placedSize also in any
of the stage Assets.
This can happens at any time when we update the sizeFilled and the feesPaid values
when we see at the exchange that orders were filled.
*/
if (
(
tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced >=
tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize
||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced >=
tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize
)
&&
(
tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value +
tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >=
tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced.value
||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizeFilled.value +
tradingEngine.current.strategyCloseStage.stageQuotedAsset.feesPaid.value >=
tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced.value
)
) {
changeStageStatus('Close Stage', 'Closed', 'Size Placed Filled')
} else {
/* Check the Close Stage Event */
tradingSystem.evalConditions(stageNode, 'Close Stage Event')
if (checkStopStageEvent(stageNode) === true) {
changeStageStatus('Close Stage', 'Closing', 'Close Stage Event')
}
}
}
/* Exit Position Validation */
if (
(
tradingEngine.current.strategyTriggerStage.status.value === 'Closed' ||
tradingEngine.current.strategyTriggerStage.status.value === tradingEngine.current.strategyTriggerStage.status.config.initialValue
)
&&
(
tradingEngine.current.strategyOpenStage.status.value === 'Closed' ||
tradingEngine.current.strategyOpenStage.status.value === tradingEngine.current.strategyOpenStage.status.config.initialValue
)
&&
(
tradingEngine.current.strategyManageStage.status.value === 'Closed' ||
tradingEngine.current.strategyManageStage.status.value === tradingEngine.current.strategyManageStage.status.config.initialValue
)
&&
(
tradingEngine.current.strategyCloseStage.status.value === 'Closed'
)
) {
/* Exiting Everything now. */
exitPositionAndStrategy()
}
function initializeStageTargetSize() {
/*
Here we will transform the position size into targets for each asset of the stage.
*/
tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value = tradingEngine.current.position.positionBaseAsset.exitTargetSize.value
tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = tradingEngine.current.position.positionQuotedAsset.exitTargetSize.value
tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize.value, 10)
tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value, 10)
}
function exitPositionAndStrategy() {
/* Taking Position Snapshot */
if (bot.SESSION.type === 'Backtesting Session') {
if (sessionParameters.snapshots !== undefined) {
if (sessionParameters.snapshots.config.position === true) {
snapshotsModule.positionExit()
}
}
}
/* Taking Strategy Snapshot */
if (bot.SESSION.type === 'Backtesting Session') {
if (sessionParameters.snapshots !== undefined) {
if (sessionParameters.snapshots.config.strategy === true) {
snapshotsModule.strategyExit()
}
}
}
/* Close the Position */
tradingPositionModule.closePosition()
updatingEpisodeAssets()
updatingEpisodeStatistics()
/* Close the Strategy */
tradingStrategyModule.closeStrategy('Position Closed')
/* Distance to Events Updates */
tradingEngine.current.distanceToEvent.closePosition.value = 1
tradingEngine.current.distanceToEvent.triggerOff.value = 1
function updatingEpisodeAssets() {
/* Updating Hits & Fails */
if (tradingEngine.current.position.positionBaseAsset.profitLoss.value > 0) {
tradingEngine.current.episode.episodeBaseAsset.hits.value++
} else {
tradingEngine.current.episode.episodeBaseAsset.fails.value++
}
if (tradingEngine.current.position.positionQuotedAsset.profitLoss.value > 0) {
tradingEngine.current.episode.episodeQuotedAsset.hits.value++
} else {
tradingEngine.current.episode.episodeQuotedAsset.fails.value++
}
/* Updating Profit Loss */
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value =
tradingEngine.current.balance.baseAsset.value -
sessionParameters.sessionBaseAsset.config.initialBalance
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value =
tradingEngine.current.balance.quotedAsset.value -
sessionParameters.sessionQuotedAsset.config.initialBalance
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.profitLoss.value, 10)
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value, 10)
/* Updating ROI */
tradingEngine.current.episode.episodeBaseAsset.ROI.value =
(sessionParameters.sessionBaseAsset.config.initialBalance + tradingEngine.current.episode.episodeBaseAsset.profitLoss.value) /
sessionParameters.sessionBaseAsset.config.initialBalance - 1
tradingEngine.current.episode.episodeQuotedAsset.ROI.value =
(sessionParameters.sessionQuotedAsset.config.initialBalance + tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value) /
sessionParameters.sessionQuotedAsset.config.initialBalance - 1
tradingEngine.current.episode.episodeBaseAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.ROI.value, 10)
tradingEngine.current.episode.episodeQuotedAsset.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.ROI.value, 10)
/* Updating Hit Ratio */
tradingEngine.current.episode.episodeBaseAsset.hitRatio.value =
tradingEngine.current.episode.episodeBaseAsset.hits.value /
tradingEngine.current.episode.episodeCounters.positions.value
tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value =
tradingEngine.current.episode.episodeQuotedAsset.hits.value /
tradingEngine.current.episode.episodeCounters.positions.value
tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10)
tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10)
/* Updating Anualized Rate Of Return */
tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value =
tradingEngine.current.episode.episodeBaseAsset.ROI.value /
tradingEngine.current.episode.episodeStatistics.days.value * 365
tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value =
tradingEngine.current.episode.episodeQuotedAsset.ROI.value /
tradingEngine.current.episode.episodeStatistics.days.value * 365
tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10)
tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10)
/* Updating Hit or Fail */
if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) {
tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Hit'
} else {
tradingEngine.current.episode.episodeBaseAsset.hitFail.value = 'Fail'
}
if (tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value > 0) {
tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Hit'
} else {
tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail'
}
}
function updatingEpisodeStatistics() {
/* Updating Profit Loss */
tradingEngine.current.episode.episodeStatistics.profitLoss.value =
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value +
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value
tradingEngine.current.episode.episodeStatistics.profitLoss.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.profitLoss.value, 10)
/* Updating ROI */
tradingEngine.current.episode.episodeStatistics.ROI.value =
(
sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value +
tradingEngine.current.episode.episodeBaseAsset.profitLoss.value * tradingEngine.current.candle.close.value +
sessionParameters.sessionQuotedAsset.config.initialBalance +
tradingEngine.current.episode.episodeQuotedAsset.profitLoss.value
) / (
sessionParameters.sessionBaseAsset.config.initialBalance * tradingEngine.current.candle.close.value +
sessionParameters.sessionQuotedAsset.config.initialBalance
) - 1
tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10)
/* Updating Anualized Rate Of Return */
tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value =
tradingEngine.current.episode.episodeStatistics.ROI.value /
tradingEngine.current.episode.episodeStatistics.days.value * 365
tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value, 10)
/* Updating Hit or Fail */
if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) {
tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Hit'
} else {
tradingEngine.current.episode.episodeStatistics.hitFail.value = 'Fail'
}
}
}
}
function checkStopStageEvent(stage) {
/* Check the Close Stage Event. */
let stopStageEvent = stage.stopStageEvent
if (stopStageEvent !== undefined) {
for (let k = 0; k < stopStageEvent.situations.length; k++) {
let situation = stopStageEvent.situations[k]
let passed
if (situation.conditions.length > 0) {
passed = true
}
passed = tradingSystem.checkConditions(situation, passed)
tradingSystem.values.push([situation.id, passed])
if (passed) {
tradingSystem.highlights.push(situation.id)
tradingSystem.highlights.push(stopStageEvent.id)
tradingSystem.highlights.push(stage.id)
announcementsModule.makeAnnoucements(stopStageEvent)
return true
}
}
}
}
function changeStageStatus(stageName, newStatus, exitType) {
let stage
switch (stageName) {
case 'Trigger Stage': {
stage = tradingEngine.current.strategyTriggerStage
break
}
case 'Open Stage': {
stage = tradingEngine.current.strategyOpenStage
break
}
case 'Manage Stage': {
stage = tradingEngine.current.strategyManageStage
break
}
case 'Close Stage': {
stage = tradingEngine.current.strategyCloseStage
break
}
}
stage.status.value = newStatus
if (exitType !== undefined) {
stage.exitType.value = exitType
}
if (stage.status.value === 'Open') {
openStage(stage)
}
if (stage.status.value === 'Closed') {
closeStage(stage)
}
function openStage(stage) {
/* Recording the opening at the Trading Engine Data Structure */
stage.begin.value = tradingEngine.current.candle.begin.value
stage.beginRate.value = tradingEngine.current.candle.close.value
stage.end.value = tradingEngine.current.candle.end.value
}
function closeStage(stage) {
/* Recording the closing at the Trading Engine Data Structure */
stage.end.value = tradingEngine.current.candle.end.value
stage.endRate.value = tradingEngine.current.candle.close.value
}
}
function resetStage(stage) {
stage.initialize(stage)
}
function updateEnds() {
if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') {
tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.candle.end.value
}
if (tradingEngine.current.strategyOpenStage.status.value === 'Open') {
tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.candle.end.value
}
if (tradingEngine.current.strategyManageStage.status.value === 'Open') {
tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.candle.end.value
}
if (tradingEngine.current.strategyCloseStage.status.value === 'Open') {
tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.candle.end.value
}
}
function updateStatus() {
if (tradingEngine.current.strategyTriggerStage.status.value === 'Closed') {
resetStage(tradingEngine.current.strategyTriggerStage.status)
}
if (tradingEngine.current.strategyOpenStage.status.value === 'Closed') {
resetStage(tradingEngine.current.strategyOpenStage.status)
}
if (tradingEngine.current.strategyManageStage.status.value === 'Closed') {
resetStage(tradingEngine.current.strategyManageStage.status)
}
if (tradingEngine.current.strategyCloseStage.status.value === 'Closed') {
resetStage(tradingEngine.current.strategyTriggerStage)
resetStage(tradingEngine.current.strategyOpenStage)
resetStage(tradingEngine.current.strategyManageStage)
resetStage(tradingEngine.current.strategyCloseStage)
}
}
function updateCounters() {
}
function updateStatistics() {
}
}