Polishing
This commit is contained in:
1 parent
569a1a129d
commit
61fb1bb053
10 files changed
+212
-90
No files matched your search
@@ -128,7 +128,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
at the Trading Engine Order the lock is Open or Closed.
|
||||
*/
|
||||
if (tradingSystemOrder.config.spawnMultipleOrders !== true) {
|
||||
if (tradingEngineOrder.identifier.value === 'Closed') {
|
||||
if (tradingEngineOrder.lock.value === 'Closed') {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -151,25 +151,23 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
/* During the Second cycle we can not cancel orders. That is reserved for the First cycle. */
|
||||
if (tradingEngine.current.episode.cycle.value === 'Second') { continue }
|
||||
/*
|
||||
In the previous steps, we might have discovered that the order was cancelled
|
||||
at the exchange, or filled, so the order might still not be Open.
|
||||
If the stage is closing or the order is not Open, we wont be cancelling orders
|
||||
based on defined events.
|
||||
|
||||
/* 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 (tradingEngineStage.status.value !== 'Closing' && tradingEngineOrder.status.value === 'Open') {
|
||||
|
||||
/* Check if we need to Cancel this Order */
|
||||
let situationName = 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')
|
||||
|
||||
updateEndsWithCycle(tradingEngineOrder)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,6 +175,30 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
}
|
||||
}
|
||||
|
||||
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 = 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')
|
||||
|
||||
updateEndsWithCycle(tradingEngineOrder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tradingSystemValidations(tradingSystemOrder) {
|
||||
/* Trading System Validations */
|
||||
if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) }
|
||||
@@ -397,65 +419,104 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
/* Actual Rate Simulation */
|
||||
let calculatedBasedOnTradingSystem = false
|
||||
|
||||
/* 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
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
|
||||
/*
|
||||
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)
|
||||
/*
|
||||
Recaulculate the Size Placed Based on the newly calculated Quoted Asset Size.
|
||||
*/
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
|
||||
previousQuotedAssetSize
|
||||
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)
|
||||
}
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
|
||||
tradingEngineOrder.orderQuotedAsset.size.value
|
||||
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)
|
||||
}
|
||||
|
||||
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.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() {
|
||||
|
||||
@@ -43,10 +43,34 @@ exports.newTradingOutput = function newTradingOutput(bot, logger, tradingEngineM
|
||||
let outputDatasets = bot.processNode.referenceParent.processOutput.outputDatasets
|
||||
let outputDatasetsMap = new Map()
|
||||
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
await initializeOutputs()
|
||||
if (bot.processingDailyFiles === true) {
|
||||
/*
|
||||
For Daily Files, we are going to initialize the outputs at the first execution
|
||||
if we are not resuming, but also at every following execution except when
|
||||
we are at the head of the market, because at the head of the market we need to
|
||||
append more data to the same output files that already exists.
|
||||
*/
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
await initializeOutputs()
|
||||
} else {
|
||||
if (bot.simulationState.tradingEngine.current.episode.headOfTheMarket.value === true) {
|
||||
await readFiles()
|
||||
} else {
|
||||
await initializeOutputs()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await readFiles()
|
||||
/*
|
||||
Form Market Files, we are only going to initialize the outputs at the first run
|
||||
and if we are not resuming execution. Subsequent runs means that we are at the
|
||||
head of the market. Remember that backtests finishes the session once the reach
|
||||
the final datetime.
|
||||
*/
|
||||
if (bot.FIRST_EXECUTION === true && bot.RESUME === false) {
|
||||
await initializeOutputs()
|
||||
} else {
|
||||
await readFiles()
|
||||
}
|
||||
}
|
||||
|
||||
await tradingSimulation.runSimulation(
|
||||
|
||||
@@ -557,7 +557,12 @@
|
||||
tradingProcessDate
|
||||
)
|
||||
|
||||
bot.FIRST_EXECUTION = false // From here on, all other loops executions wont be the first execution.
|
||||
/*
|
||||
From here on, all other loops executions wont be the first execution and also
|
||||
we will consider that it is not resuming a previous execution as well.
|
||||
*/
|
||||
bot.FIRST_EXECUTION = false
|
||||
bot.RESUME = false
|
||||
}
|
||||
|
||||
async function writeProcessFiles() {
|
||||
|
||||
@@ -148,6 +148,15 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
tradingEngineModule.setCurrentCycle('First')
|
||||
await runCycle()
|
||||
|
||||
/*
|
||||
We check if we need to stop before appending the records so that the stop
|
||||
reason is also propery recorded. Note also that we check this after the first
|
||||
cycle, where orders have not been submitted to the exchange yet, but we
|
||||
had the chance to check for the status of placed orders or even cancel
|
||||
the ones that needed cancellation.
|
||||
*/
|
||||
checkIfWeNeedToStop()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords()
|
||||
|
||||
@@ -163,12 +172,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
|
||||
tradingEngineModule.setCurrentCycle('Second')
|
||||
await runCycle()
|
||||
|
||||
/*
|
||||
We check if we need to stop before appending the records so that the stop
|
||||
reason is also propery recorded.
|
||||
*/
|
||||
checkIfWeNeedToStop()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords()
|
||||
|
||||
|
||||
@@ -891,11 +891,14 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
either on Base Asset or Quoted Asset.
|
||||
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.
|
||||
Note that the comparison is made with a Rounding Factor in order to avoid rounding problems.
|
||||
*/
|
||||
const ROUNDING_ERROR_CORRECTION_FACTOR = 1.0000001
|
||||
|
||||
switch (tradingEngineStage.stageDefinedIn.value) {
|
||||
case 'Base Asset': {
|
||||
if (
|
||||
tradingEngineStage.stageBaseAsset.sizeFilled.value
|
||||
tradingEngineStage.stageBaseAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
>=
|
||||
tradingEngineStage.stageBaseAsset.targetSize.value
|
||||
) {
|
||||
@@ -907,7 +910,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
case 'Quoted Asset': {
|
||||
if (
|
||||
tradingEngineStage.stageQuotedAsset.sizeFilled.value
|
||||
tradingEngineStage.stageQuotedAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR
|
||||
>=
|
||||
tradingEngineStage.stageQuotedAsset.targetSize.value
|
||||
) {
|
||||
|
||||
Reference in new issue
Block a user