Files
Superalgos/TaskServer/LowFrequencyTrading/TradingOrders.js
T
2020-09-05 11:49:40 +02:00

1293 lines
67 KiB
JavaScript

exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineModule) {
/*
The Trading Orders modules manages the execution of orders against the exchanges.
*/
const MODULE_NAME = 'Trading Orders'
let thisObject = {
mantain: mantain,
reset: reset,
checkOrders: checkOrders,
initialize: initialize,
finalize: finalize
}
let tradingEngine
let tradingSystem
let sessionParameters
const EXCHANGE_API_MODULE = require('./ExchangeAPI.js')
let exchangeAPIModule = EXCHANGE_API_MODULE.newExchangeAPI(bot, logger)
const ANNOUNCEMENTS_MODULE = require('./Announcements.js')
let announcementsModule = ANNOUNCEMENTS_MODULE.newAnnouncements(bot, logger)
return thisObject
function initialize() {
tradingSystem = bot.simulationState.tradingSystem
tradingEngine = bot.simulationState.tradingEngine
sessionParameters = bot.SESSION.parameters
exchangeAPIModule.initialize()
announcementsModule.initialize()
}
function finalize() {
tradingSystem = undefined
tradingEngine = undefined
sessionParameters = undefined
exchangeAPIModule.finalize()
exchangeAPIModule = undefined
announcementsModule.finalize()
announcementsModule = undefined
}
function mantain() {
mantainReset(true, false)
}
function reset() {
mantainReset(false, true)
}
function mantainReset(mantain, reset) {
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
processExecutionNode(executionNode, tradingEngine.current.strategyOpenStage.status.value)
stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
executionNode = stageNode.closeExecution
processExecutionNode(executionNode, tradingEngine.current.strategyCloseStage.status.value)
function processExecutionNode(executionNode, stageStatus) {
preocessExecutionAlgorithms(executionNode)
function preocessExecutionAlgorithms(executionNode) {
for (let i = 0; i < executionNode.executionAlgorithms.length; i++) {
let executionAlgorithm = executionNode.executionAlgorithms[i]
processOrders(executionAlgorithm.marketBuyOrders)
processOrders(executionAlgorithm.marketSellOrders)
processOrders(executionAlgorithm.limitBuyOrders)
processOrders(executionAlgorithm.limitSellOrders)
}
}
function processOrders(orders) {
for (let i = 0; i < orders.length; i++) {
let tradingSystemOrder = orders[i]
if (tradingSystemOrder.referenceParent === undefined) {
badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder)
}
let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id)
if (tradingEngineOrder.status === undefined) {
badDefinitionUnhandledException(undefined, 'tradingEngineOrder.status === undefined', tradingEngineOrder)
}
if (mantain === true) {
updateCounters(tradingEngineOrder)
updateEnds(tradingEngineOrder)
updateStatistics(tradingEngineOrder)
}
if (reset === true) {
resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus)
}
}
}
}
}
async function checkOrders(tradingEngineStage, orders, executionAlgorithm, executionNode) {
for (let i = 0; i < orders.length; i++) {
let tradingSystemOrder = orders[i]
tradingSystemValidations(tradingSystemOrder)
let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id)
tradingEngineValidations(tradingEngineOrder)
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 }
/*
Check if we can create an order based on the config value for spawnMultipleOrders.
Trading System Orders that cannot spawn more than one Trading Engine Order needs to check if
at the Trading Engine Order the lock is Open or Closed.
*/
if (tradingSystemOrder.config.spawnMultipleOrders !== true) {
if (tradingEngineOrder.lock.value === 'Closed') {
tradingSystem.infos.push(['Order ' + tradingSystemOrder.name + ' skipped because lock was closed.'])
continue
}
}
/* Check if we need to Create this Order */
let situationName = await checkOrderEvent(tradingSystemOrder.createOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode)
if (situationName !== undefined) {
/* Open a new order */
await tryToOpenOrder(tradingEngineStage, executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName)
}
}
break
}
case 'Open': {
/* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */
if (tradingEngine.current.episode.cycle.value === 'Second') { continue }
/* Simulate Events that happens at the Exchange, if needed. */
simulateExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder)
/* Check Events that happens at the Exchange, if needed. */
let allGood = await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder)
if (allGood !== true) {
/*
For some reason we could not check the order at the exchange, so we will not even check if we
need to cancel it, since we could end up with inconsisten information at the accounting level.
*/
if (tradingSystemOrder.cancelOrderEvent !== undefined) {
tradingSystem.warnings.push([tradingSystemOrder.cancelOrderEvent.id, 'Skipping Cancel Event Checking because cheking the order at the exchange failed.'])
}
return
}
/* Check if we need to cancel the order */
await checkCancelOrderEvent(tradingEngineStage, executionAlgorithm, executionNode, tradingEngineOrder, tradingSystemOrder)
/*
If by this time the order is closed, we need to clone it and get the close
to the Last node at the Trading Engine data structure.
*/
if (tradingEngineOrder.status.value === 'Closed') {
switch (tradingEngineOrder.type) {
case 'Market Order': {
tradingEngineModule.cloneValues(tradingEngineOrder, tradingEngine.last.marketOrders)
break
}
case 'Limit Order': {
tradingEngineModule.cloneValues(tradingEngineOrder, tradingEngine.last.limitOrders)
break
}
}
}
}
}
}
}
async function checkCancelOrderEvent(tradingEngineStage, executionAlgorithm, executionNode, tradingEngineOrder, tradingSystemOrder) {
/*
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') {
/* Check if we need to Cancel this Order */
let situationName = await checkOrderEvent(tradingSystemOrder.cancelOrderEvent, tradingSystemOrder, executionAlgorithm, executionNode)
if (situationName !== undefined) {
/* Simulate Order Cancelation, if needed. */
simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event')
/* Cancel the order at the Exchange, if needed. */
await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event')
await updateEndsWithCycle(tradingEngineOrder)
}
}
}
function tradingSystemValidations(tradingSystemOrder) {
/* Trading System Validations */
if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) }
if (tradingSystemOrder.referenceParent === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder) }
}
function tradingEngineValidations(tradingEngineOrder) {
/* Trading Engine Order Validations */
if (tradingEngineOrder.serialNumber === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.serialNumber === undefined', tradingEngineOrder) }
if (tradingEngineOrder.identifier === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.identifier === undefined', tradingEngineOrder) }
if (tradingEngineOrder.exchangeId === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.exchangeId === undefined', tradingEngineOrder) }
if (tradingEngineOrder.begin === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.begin === undefined', tradingEngineOrder) }
if (tradingEngineOrder.end === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.end === undefined', tradingEngineOrder) }
if (tradingEngineOrder.rate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.rate === undefined', tradingEngineOrder) }
if (tradingEngineOrder.status === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.status === undefined', tradingEngineOrder) }
if (tradingEngineOrder.algorithmName === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.algorithmName === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderCounters === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderCounters.periods === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderCounters.periods === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderStatistics.days === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.days === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderStatistics.percentageFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.percentageFilled === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderStatistics.actualRate === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderStatistics.actualRate === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderBaseAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.size === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderBaseAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.sizeFilled === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderBaseAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.feesPaid === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderQuotedAsset.size === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.size === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.sizeFilled === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderQuotedAsset.feesPaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.feesPaid === undefined', tradingEngineOrder) }
}
async function tryToOpenOrder(tradingEngineStage, executionAlgorithm, tradingSystemOrder, tradingEngineOrder, situationName) {
await calculateOrderRate()
await calculateOrderSize()
/* Check Size: We are not going to create Orders which size is equal or less to zero. */
if (tradingEngineOrder.orderBaseAsset.size.value <= 0 || tradingEngineOrder.orderQuotedAsset.size.value <= 0) {
tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not open this order because its size would be zero.'])
return
}
/* Place Order at the Exchange, if needed. */
let result = await createOrderAtExchange(tradingSystemOrder, tradingEngineOrder)
if (result !== true) {
tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not open this order because something failed placing the order at the Exchange.'])
return
}
/* Update Stage Placed Size */
tradingEngineStage.stageBaseAsset.sizePlaced.value = tradingEngineStage.stageBaseAsset.sizePlaced.value + tradingEngineOrder.orderBaseAsset.size.value
tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10)
tradingEngineStage.stageQuotedAsset.sizePlaced.value = tradingEngineStage.stageQuotedAsset.sizePlaced.value + tradingEngineOrder.orderQuotedAsset.size.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
/* Updating Episode Counters */
tradingEngine.current.episode.episodeCounters.orders.value++
/* Initialize this */
tradingEngine.current.episode.distanceToEvent.createOrder.value = 1
/* Create Order Procedure */
tradingEngineOrder.status.value = 'Open'
tradingEngineOrder.identifier.value = global.UNIQUE_ID()
tradingEngineOrder.begin.value = tradingEngine.current.episode.cycle.begin.value
tradingEngineOrder.end.value = tradingEngine.current.episode.cycle.end.value
tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value
tradingEngineOrder.orderName.value = tradingSystemOrder.name
tradingEngineOrder.algorithmName.value = executionAlgorithm.name
tradingEngineOrder.situationName.value = situationName
async function calculateOrderRate() {
/* Optional Rate Definition */
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)
/* 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)
} 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
}
}
async function calculateOrderSize() {
/* Validate that this config exists */
if (executionAlgorithm.config.percentageOfStageTargetSize === undefined) { badDefinitionUnhandledException(undefined, 'executionAlgorithm.config.percentageOfStageTargetSize === undefined', executionAlgorithm) }
if (isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(executionAlgorithm.config.percentageOfStageTargetSize) === true', executionAlgorithm) }
let algorithmSizeInBaseAsset = tradingEngineStage.stageBaseAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100
let algorithmSizeInQuotedAsset = tradingEngineStage.stageQuotedAsset.targetSize.value * executionAlgorithm.config.percentageOfStageTargetSize / 100
/* Validate that this config exists */
if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) }
if (isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true', tradingSystemOrder) }
/*
The Size calculation depends on how the user defined the size of the position.
The user could have defined the size of the position in Base Asset or Quoted Asset.
*/
switch (tradingEngineStage.stageDefinedIn.value) {
case 'Base Asset': {
/* Size in Base Asset */
tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100
/* Check that the Size calculated would not surpass Stage Target Size */
if (
tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value >
tradingEngineStage.stageBaseAsset.targetSize.value
) {
tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value
}
/* Size in Quoted Asset */
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value
break
}
case 'Quoted Asset': {
/* 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
) {
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value
}
/* Size in Base Asset */
tradingEngineOrder.orderBaseAsset.size.value = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value
break
}
}
tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10)
tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10)
}
async function createOrderAtExchange(tradingSystemOrder, tradingEngineOrder) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
return true
}
case 'Live Trading Session': {
break
}
case 'Fordward Testing Session': {
break
}
case 'Paper Trading Session': {
return true
}
}
let orderId = await exchangeAPIModule.createOrder(tradingSystemOrder, tradingEngineOrder)
if (orderId !== undefined) {
tradingEngineOrder.exchangeId.value = orderId
return true
}
}
}
function simulateExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
break
}
case 'Live Trading Session': {
return
}
case 'Fordward Testing Session': {
return
}
case 'Paper Trading Session': {
break
}
}
let previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value
let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value
let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value
let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value
let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value
actualRateSimulation()
percentageFilledSimulation()
feesPaidSimulation()
sizeFilledSimulation()
doTheAccounting(
tradingEngineStage,
tradingSystemOrder,
tradingEngineOrder,
previousBaseAssetSizeFilled,
previousQuotedAssetSizeFilled,
previousBaseAssetFeesPaid,
previousQuotedAssetFeesPaid
)
/* If the Stage is Closing and this order is still open, we need to cancel it now */
if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') {
simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Closing Stage')
}
function actualRateSimulation() {
/* Actual Rate Simulation */
let calculatedBasedOnTradingSystem = false
basedOnTradingSystem()
basedOnSessionParameters()
considerBestMatchWithOrderBook()
recalculateQuotedAsset()
recalculateSizePlaced()
function basedOnTradingSystem() {
/* Based on the Trading System Definition */
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula !== undefined) {
/* Calculate this only once for this order */
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.orderStatistics.actualRate.config.initialValue) {
tradingEngineOrder.orderStatistics.actualRate.value = tradingSystem.formulas.get(tradingSystemOrder.simulatedExchangeEvents.simulatedActualRate.formula.id)
if (tradingEngineOrder.orderStatistics.actualRate.value !== undefined) {
calculatedBasedOnTradingSystem = true
}
}
}
}
}
}
function basedOnSessionParameters() {
/* Based on the Session Parameters Definition */
if (calculatedBasedOnTradingSystem === false) {
switch (tradingEngineOrder.type) {
case 'Market Order': {
/* Actual Rate is simulated based on the Session Paremeters */
let slippageAmount = tradingEngineOrder.rate.value * sessionParameters.slippage.config.positionRate / 100
switch (tradingSystemOrder.type) {
case 'Market Sell Order': {
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value - slippageAmount
break
}
case 'Market Buy Order': {
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value + slippageAmount
break
}
}
break
}
case 'Limit Order': {
/* In Limit Orders the actual rate is the rate of the order, there is no slippage */
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngineOrder.rate.value
break
}
}
}
}
function considerBestMatchWithOrderBook() {
/*
Until here we have got the rate based on the formula definition or based on session parameters slippage.
THe last check is about watching what happened in the market. Let's remember that the exchange will
fill the order with the best possible matches at it's order book. That means that if the rate
we set for the order was too low (for a sale order) or too hight (for a buy order), the actual rate
should be better than expected.
*/
switch (true) {
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
if (tradingEngineOrder.orderStatistics.actualRate.value > tradingEngine.current.episode.candle.max.value) {
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.max.value
}
break
}
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
if (tradingEngineOrder.orderStatistics.actualRate.value < tradingEngine.current.episode.candle.min.value) {
tradingEngineOrder.orderStatistics.actualRate.value = tradingEngine.current.episode.candle.min.value
}
break
}
}
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
}
function recalculateQuotedAsset() {
/*
Recalculate the Quouted Asset Size based on the newly calculated Actual Rate.
*/
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.orderStatistics.actualRate.value
tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10)
}
function recalculateSizePlaced() {
/*
Recaulculate the Size Placed Based on the newly calculated Quoted Asset Size.
*/
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetSize
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.size.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
}
function percentageFilledSimulation() {
/*
Order Filling Simulation: the way we are going to handle this is the following.
For buy orders we are going to check if the price was below the order rate, and
for sell orders we are going to check if the price was above the order rate.
If we have a partial filling defined we are going to use it to estimate how much
of the order we will consider filled.
*/
let orderWasHit
switch (tradingSystemOrder.type) {
case 'Limit Buy Order': {
if (tradingEngine.current.episode.candle.min.value <= tradingEngineOrder.rate.value) {
orderWasHit = true
}
break
}
case 'Limit Sell Order': {
if (tradingEngine.current.episode.candle.max.value >= tradingEngineOrder.rate.value) {
orderWasHit = true
}
break
}
case 'Market Buy Order': {
orderWasHit = true
break
}
case 'Market Sell Order': {
orderWasHit = true
break
}
}
if (orderWasHit === true) {
let calculatedBasedOnTradingSystem = false
/* Based on the Trading System Definition */
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability !== undefined) {
/* Percentage Filled */
let percentageFilled = tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill.config.fillProbability * 100
if (tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled > 100) {
percentageFilled = 100 - tradingEngineOrder.orderStatistics.percentageFilled.value
}
tradingEngineOrder.orderStatistics.percentageFilled.value = tradingEngineOrder.orderStatistics.percentageFilled.value + percentageFilled
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
calculatedBasedOnTradingSystem = true
}
}
}
/*
If there is no definition for this at the trading system we will assume orders
are 100% filled every single time.
*/
if (calculatedBasedOnTradingSystem === false) {
/* We will assume that 100% of the order was filled */
tradingEngineOrder.orderStatistics.percentageFilled.value = 100
}
/* Check if we need to close the order */
if (tradingEngineOrder.orderStatistics.percentageFilled.value === 100) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = 'Filled'
/* Initialize this */
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
updateEndsWithCycle(tradingEngineOrder)
tradingSystem.infos.push([tradingSystemOrder.id, 'percentageFilledSimulation -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value])
}
}
}
function feesPaidSimulation() {
/*
The way the Fess Paid are calculated is as follows: From the order size we apply the % of fee,
that would give us the total fees for that order. To that we apply the % filled of the orders,
that give use the final result of the fees paid for the current % filled.
But before that, we will see if there is an specific definition on how to simulate the fees for
this order, or if there is not, we use the fees session parameters definitions.
*/
/* Based on the Trading System Definition */
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
applyFeePercentage(tradingEngineOrder, tradingSystemOrder, tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage)
return
}
}
}
/*
Another way to simulate the Fees Paid is by using the Session Paremeters configuration for Fees.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.taker)
break
}
case 'Limit Order': {
applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.maker)
break
}
}
}
function sizeFilledSimulation() {
/* Size Filled */
tradingEngineOrder.orderBaseAsset.sizeFilled.value =
tradingEngineOrder.orderBaseAsset.size.value *
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
tradingEngineOrder.orderQuotedAsset.size.value *
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10)
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
}
}
async function checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
return true
}
case 'Live Trading Session': {
break
}
case 'Fordward Testing Session': {
break
}
case 'Paper Trading Session': {
return true
}
}
let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder)
if (order === undefined) {
tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not verify the status of this order at the exchange.'])
return false
}
const AT_EXCHANGE_STATUS = {
OPEN: 'open',
CLOSED: 'closed',
CANCELLED: 'canceled'
}
/* Status Checks */
if (order.remaining === 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = 'Filled'
/* Initialize this */
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
await updateEndsWithCycle(tradingEngineOrder)
tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value])
await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order)
return
}
if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = 'Closed at the Exchange'
/* Initialize this */
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
await updateEndsWithCycle(tradingEngineOrder)
tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value])
await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order)
await recalculateStageSize(tradingEngineStage, tradingEngineOrder)
return
}
if (order.status === AT_EXCHANGE_STATUS.CANCELLED) {
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
/*
We must be carefull here not to overide an already defined exitType. It can happen
for instance that the order was cancellerd from the bot, but veryfing the cancellation
was not possible because of a connection to the exchange problem. In that case
the exit type was defined but the order was kept open until the verification could be done.
*/
if (tradingEngineOrder.exitType.value === tradingEngineOrder.exitType.config.initialValue) {
tradingEngineOrder.exitType.value = 'Cancelled at the Exchange'
}
/* Initialize this */
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
await updateEndsWithCycle(tradingEngineOrder)
tradingSystem.infos.push([tradingSystemOrder.id, 'checkExchangeEvents -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value])
await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order)
await recalculateStageSize(tradingEngineStage, tradingEngineOrder)
return
}
/*
If the order happens to be at least partially filled, there is a syncronization work
we need to do, that includes discovering which is the Actual Rate the order is being filled,
the Fees Paid and many other thing we need to account for.
*/
if (order.filled > 0) {
await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order)
}
/*
Forced Cancellation Check: Here we check if we need to cancel this order because the
stage is closing.
*/
if (tradingEngineStage.status.value === 'Closing' && tradingEngineOrder.status.value !== 'Closed') {
await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Closing Stage')
}
return true
}
async function syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) {
let previousBaseAssetSize = tradingEngineOrder.orderBaseAsset.size.value
let previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value
let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value
let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value
let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value
let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value
await actualSizeCalculation()
await actualRateCalculation()
await percentageFilledCalculation()
await feesPaidCalculation()
await sizeFilledCalculation()
await doTheAccounting(
tradingEngineStage,
tradingSystemOrder,
tradingEngineOrder,
previousBaseAssetSizeFilled,
previousQuotedAssetSizeFilled,
previousBaseAssetFeesPaid,
previousQuotedAssetFeesPaid
)
return
async function actualSizeCalculation() {
/*
When we submit the order to the exchange, we do it specifying the order size
in Base Asset. The exchange might take that size or might change it a little
bit if for example, if it does not accept as many decimals as we are sending.
If we identify the situation in which the size accepted is different than the
size we have accounted for, we need to make several adjustments so that the
accounting syncronizes with reality.
*/
if (tradingEngineOrder.orderBaseAsset.size.value === order.amount) { return }
/*
We will sync the Base Asset size, and recalculate the Quoted Asset size too.
*/
tradingEngineOrder.orderBaseAsset.size.value = order.amount
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value
tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10)
tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10)
/*
Finaly we need to unaccount the previous size placed and correctly account
for the the new sizes we now have.
*/
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value -
previousBaseAssetSize
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value +
tradingEngineOrder.orderBaseAsset.size.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetSize
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.size.value
tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10)
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
previousBaseAssetSize = tradingEngineOrder.orderBaseAsset.size.value
previousQuotedAssetSize = tradingEngineOrder.orderQuotedAsset.size.value
}
async function actualRateCalculation() {
/*
Actual Rate Calculation: Let's remember that the exchange may not take the rate
we send it with the order (in Limit orders) and for any reason change it a little bit.
For example we might have sent a rate with too many decimals and the exchange truncate
them. Another thing that might happen is that we send a rate that is matching orders already
at the order book. In this case we would get an actual rate not only different than the one
we sent, but also, better since the trading engine inside the exchange would match out orders
with the first orders best by price.
In the case of Market Orders we don't even send a rate. For all those reason
we get and store the Actual Rate at which the order was filled.
*/
if (order.average !== undefined) {
/*
We will use the average whenever is available.
*/
tradingEngineOrder.orderStatistics.actualRate.value = order.average
} else {
/*
Otherwise we use the order.price.
*/
tradingEngineOrder.orderStatistics.actualRate.value = order.price
}
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
/*
Now we know the Actual Rate at which the order was filled. Since the actual rate
is not the same as the Rate we defined for the order, we need to syncronize
the Order Size for Quoted Asset since it was calculated with the Order Size that we
now know it is not the once really used at the exchange. We will recalculate the
Order Size in Quoted Asset and not in Base Asset, since the Order Size in Base Asset
is the one we sent to the exchange, so that is fixed, regardless of how we initially
got the Order Size in Base Asset, eigher from direct imput from the user or calculated
from the Order Size in Quoted Asset input it by the user. So here we go.
*/
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.orderStatistics.actualRate.value
tradingEngineOrder.orderQuotedAsset.size.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.size.value, 10)
/*
Great, this up-to-date order size in Base Asset and the Actual Rate can now be used for example to calculate
the orders fees in Quoted Assets, and it is perfectly accurate. The next problem
we need to fix is that the sizePlaced for Quoted Asset that we already have calculated
was based on the Order Rate, not the Actual Rate, so we need to fix that by anuncounting
the previous size and accounting the new one, all this only for Quoted Asset.
*/
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetSize
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.size.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
async function percentageFilledCalculation() {
/* Percentage Filled Calculation */
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (order.filled + order.remaining)
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
}
async function feesPaidCalculation() {
/*
The exchange fees are taken from the Base Asset or the Quoted Asset depending if we
are buying or selling. Within the order information received we can not see the fees paid so we need to
calculate them by ourselves. To do this it is needed to be used the Session Fees Parameters.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.taker)
break
}
case 'Limit Order': {
await applyFeePercentage(tradingEngineOrder, tradingSystemOrder, sessionParameters.feeStructure.config.maker)
break
}
}
}
async function sizeFilledCalculation() {
/*
CCXT returns order.filled with an amount denominated in Base Asset. We will
take it from there for our Order Base Asset. The amount in order.filled does
not account for the fees, which is exactly what we need in this case.
*/
tradingEngineOrder.orderBaseAsset.sizeFilled.value = order.filled
tradingEngineOrder.orderBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.sizeFilled.value, 10)
/*
For Quoted Asset, CCXT does not return any field with the size filled, so we
need to calculate that by ourselves. Since we know the Size in Quoted Asset thet
is already adjusted with the Actual Rate and the Percentage Filled, we will use all
this in this formula to get the Size Filled in Quoted Asset.
*/
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
tradingEngineOrder.orderQuotedAsset.size.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
}
}
async function doTheAccounting(
tradingEngineStage,
tradingSystemOrder,
tradingEngineOrder,
previousBaseAssetSizeFilled,
previousQuotedAssetSizeFilled,
previousBaseAssetFeesPaid,
previousQuotedAssetFeesPaid
) {
await updateStageAssets()
await updateBalances()
async function updateStageAssets() {
/* Stage Base Asset: Undo the previous accounting */
tradingEngineStage.stageBaseAsset.sizeFilled.value =
tradingEngineStage.stageBaseAsset.sizeFilled.value -
previousBaseAssetSizeFilled
tradingEngineStage.stageBaseAsset.feesPaid.value =
tradingEngineStage.stageBaseAsset.feesPaid.value -
previousBaseAssetFeesPaid
/* Stage Base Asset: Account the current filling and fees */
tradingEngineStage.stageBaseAsset.sizeFilled.value =
tradingEngineStage.stageBaseAsset.sizeFilled.value +
tradingEngineOrder.orderBaseAsset.sizeFilled.value
tradingEngineStage.stageBaseAsset.feesPaid.value =
tradingEngineStage.stageBaseAsset.feesPaid.value +
tradingEngineOrder.orderBaseAsset.feesPaid.value
/* Stage Quote Asset: Undo the previous accounting */
tradingEngineStage.stageQuotedAsset.sizeFilled.value =
tradingEngineStage.stageQuotedAsset.sizeFilled.value -
previousQuotedAssetSizeFilled
tradingEngineStage.stageQuotedAsset.feesPaid.value =
tradingEngineStage.stageQuotedAsset.feesPaid.value -
previousQuotedAssetFeesPaid
/* Stage Quote Asset: Account the current filling and fees */
tradingEngineStage.stageQuotedAsset.sizeFilled.value =
tradingEngineStage.stageQuotedAsset.sizeFilled.value +
tradingEngineOrder.orderQuotedAsset.sizeFilled.value
tradingEngineStage.stageQuotedAsset.feesPaid.value =
tradingEngineStage.stageQuotedAsset.feesPaid.value +
tradingEngineOrder.orderQuotedAsset.feesPaid.value
tradingEngineStage.stageBaseAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizeFilled.value, 10)
tradingEngineStage.stageBaseAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageBaseAsset.feesPaid.value, 10)
tradingEngineStage.stageQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizeFilled.value, 10)
tradingEngineStage.stageQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.feesPaid.value, 10)
}
async function updateBalances() {
/* Balances Update */
switch (true) {
/*
Let's remember that for Buy orders, the fees are paid in Base Asset. So we
actually don't receive the whole Size Filled in Base Asset, but the Size
Filled minus the Fees Paid. For the case of Quoted Asset, we actually
need to susbstract the whole Size Filled from the balance.
*/
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
/* Balance Base Asset: Undo the previous accounting */
tradingEngine.current.episode.episodeBaseAsset.balance.value =
tradingEngine.current.episode.episodeBaseAsset.balance.value -
previousBaseAssetSizeFilled +
previousBaseAssetFeesPaid
/* Balance Base Asset: Account the current filling and fees */
tradingEngine.current.episode.episodeBaseAsset.balance.value =
tradingEngine.current.episode.episodeBaseAsset.balance.value +
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
tradingEngineOrder.orderBaseAsset.feesPaid.value
/* Balance Quoted Asset: Undo the previous accounting */
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
tradingEngine.current.episode.episodeQuotedAsset.balance.value +
previousQuotedAssetSizeFilled
/* Balance Quoted Asset: Account the current filling and fees */
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
tradingEngineOrder.orderQuotedAsset.sizeFilled.value
break
}
/*
For Sell orders, the fees are being paid in Quote Asset, so we never
acutally receive the whole Filled Size in Quote Asset, but the Filled
Size minus the Fees Paid. But we do expend the whole Filled Size in
Base Asset.
*/
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
/* Balance Base Asset: Undo the previous accounting */
tradingEngine.current.episode.episodeBaseAsset.balance.value =
tradingEngine.current.episode.episodeBaseAsset.balance.value +
previousBaseAssetSizeFilled
/* Balance Base Asset: Account the current filling and fees */
tradingEngine.current.episode.episodeBaseAsset.balance.value =
tradingEngine.current.episode.episodeBaseAsset.balance.value -
tradingEngineOrder.orderBaseAsset.sizeFilled.value
/* Balance Quoted Asset: Undo the previous accounting */
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
tradingEngine.current.episode.episodeQuotedAsset.balance.value -
previousQuotedAssetSizeFilled +
previousQuotedAssetFeesPaid
/* Balance Quoted Asset: Account the current filling and fees */
tradingEngine.current.episode.episodeQuotedAsset.balance.value =
tradingEngine.current.episode.episodeQuotedAsset.balance.value +
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
tradingEngineOrder.orderQuotedAsset.feesPaid.value
break
}
}
tradingEngine.current.episode.episodeBaseAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.balance.value, 10)
tradingEngine.current.episode.episodeQuotedAsset.balance.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.balance.value, 10)
}
}
function simulateCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, exitType) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
break
}
case 'Live Trading Session': {
return
}
case 'Fordward Testing Session': {
return
}
case 'Paper Trading Session': {
break
}
}
/* Close this Order */
tradingEngineOrder.status.value = 'Closed'
tradingEngineOrder.exitType.value = exitType
/* Initialize this */
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
updateEndsWithCycle(tradingEngineOrder)
tradingSystem.infos.push([tradingSystemOrder.id, 'simulateCancelOrder -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value])
recalculateStageSize(tradingEngineStage, tradingEngineOrder)
}
async function exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, exitType) {
/* Filter by Session Type */
switch (bot.SESSION.type) {
case 'Backtesting Session': {
return
}
case 'Live Trading Session': {
break
}
case 'Fordward Testing Session': {
break
}
case 'Paper Trading Session': {
return
}
}
/* Check if we can cancel the order at the Exchange. */
let result = await exchangeAPIModule.cancelOrder(tradingSystemOrder, tradingEngineOrder)
if (result === true) {
/* At this point we know which is the exit type for this order */
tradingEngineOrder.exitType.value = exitType
/*
Perhaps the order was filled a bit more between the last time we checked and when it was cancelled.
To sync our accounting, we need to check the order one last time and if it changed, fix it.
*/
let order = await exchangeAPIModule.getOrder(tradingSystemOrder, tradingEngineOrder)
if (order === undefined) {
tradingSystem.warnings.push([tradingSystemOrder.id, 'Could not verify the status of this order at the exchange, and syncronize the accounting.'])
return false
}
/*
Close this Order. Note that we are not closing the order until we have the exchange
response with the order details that we can use to syncronize with our accoounting.
Otherwise if the connection to the exchange fails, we would have a closed order not
accounted in any way.
*/
tradingEngineOrder.status.value = 'Closed'
/* Initialize this */
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
await updateEndsWithCycle(tradingEngineOrder)
tradingSystem.infos.push([tradingSystemOrder.id, 'exchangeCancelOrder -> Closing Order with Exit Type ' + tradingEngineOrder.exitType.value])
await syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order)
await recalculateStageSize(tradingEngineStage, tradingEngineOrder)
}
}
async function recalculateStageSize(tradingEngineStage, tradingEngineOrder) {
/*
Since the order is Cancelled, we need to adjust the stage sizePlaced. Remember that the Stage
Size Placed accumulates for each asset, the order size placed at the exchange. A cancelation means that
only the part filled can be considered placed, so we need to substract from the stage size
the remainder. To achieve this with the information we currently have, we are going first
to unaccount the order size, and the account only the sizeFilled + the feesPaid.
*/
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value -
tradingEngineOrder.orderBaseAsset.size.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
tradingEngineOrder.orderQuotedAsset.size.value
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value +
tradingEngineOrder.orderBaseAsset.sizeFilled.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.sizeFilled.value
tradingEngineStage.stageBaseAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageBaseAsset.sizePlaced.value, 10)
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
async function applyFeePercentage(tradingEngineOrder, tradingSystemOrder, feePercentage) {
switch (true) {
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
/*
In this case the fees are taken from the Base Asset you receive.
*/
tradingEngineOrder.orderBaseAsset.feesPaid.value =
tradingEngineOrder.orderBaseAsset.size.value *
feePercentage / 100 *
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
break
}
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
/*
In this case the fees are taken from the Quoted Asset you receive.
*/
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
tradingEngineOrder.orderQuotedAsset.size.value *
feePercentage / 100 *
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
break
}
}
tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10)
tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10)
}
async function checkOrderEvent(event, order, executionAlgorithm, executionNode) {
if (event !== undefined) {
for (let k = 0; k < event.situations.length; k++) {
let situation = event.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(event.id)
tradingSystem.highlights.push(order.id)
tradingSystem.highlights.push(executionAlgorithm.id)
tradingSystem.highlights.push(executionNode.id)
announcementsModule.makeAnnoucements(event)
return situation.name // if the event is triggered, we return the name of the situation that passed
}
}
}
}
function updateEnds(tradingEngineOrder) {
if (tradingEngineOrder.status.value === 'Open') {
tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value
}
}
async function updateEndsWithCycle(tradingEngineOrder) {
tradingEngineOrder.end.value = tradingEngine.current.episode.cycle.end.value
}
function updateCounters(tradingEngineOrder) {
if (tradingEngineOrder.status.value === 'Open') {
tradingEngineOrder.orderCounters.periods.value++
}
}
function updateStatistics(tradingEngineOrder) {
tradingEngineOrder.orderStatistics.days.value = tradingEngineOrder.orderCounters.periods.value * sessionParameters.timeFrame.config.value / global.ONE_DAY_IN_MILISECONDS
tradingEngineOrder.orderStatistics.days.value = global.PRECISE(tradingEngineOrder.orderStatistics.days.value, 10)
}
function resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus) {
if (tradingEngineOrder.status.value === 'Closed') {
/* We reset the order data structure inside the Trading Engine to its initial value */
tradingEngineModule.initializeNode(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 badDefinitionUnhandledException(err, message, node) {
tradingSystem.errors.push([node.id, message])
logger.write(MODULE_NAME, "[ERROR] -> " + message);
logger.write(MODULE_NAME, "[ERROR] -> node.name = " + node.name);
logger.write(MODULE_NAME, "[ERROR] -> node.type = " + node.type);
logger.write(MODULE_NAME, "[ERROR] -> node.config = " + JSON.stringify(node.config));
if (err !== undefined) {
logger.write(MODULE_NAME, "[ERROR] -> err.stack = " + err.stack);
}
throw 'Please fix the problem and try again.'
}
}