Complete Review of Trading Orders

This commit is contained in:
Luis Fernando Molina committed 2020-10-25 13:09:10 +01:00
1 parent 7cacff86d0
commit d3da5c220b
16 files changed
+915 -748

No files matched your search

+8 -1
View File
@@ -158,7 +158,7 @@ function newCanvas() {
}
}
/* Space Animation */
/* Space Animation Physics */
for (let j = 0; j < projectDefinition.spaces.length; j++) {
let spaceInstance = spaceAnimationMap.get(j)
let spaceDefinition = spaceDefinitionMap.get(j)
@@ -166,6 +166,13 @@ function newCanvas() {
if (spaceInstance.physics !== undefined) {
thisObject.animation.addCallBackFunction(spaceDefinition.name + ' ' + 'Physics', spaceInstance.physics)
}
}
/* Space Animation Drawing*/
for (let j = 0; j < projectDefinition.spaces.length; j++) {
let spaceInstance = spaceAnimationMap.get(j)
let spaceDefinition = spaceDefinitionMap.get(j)
if (spaceInstance === undefined || spaceDefinition === undefined) { continue }
if (spaceInstance.draw !== undefined) {
thisObject.animation.addCallBackFunction(spaceDefinition.name + ' ' + 'Draw', spaceInstance.draw)
}
+2
View File
@@ -76,6 +76,8 @@ function newEventsServerClient(networkNode) {
}
function sendToWebSocketServer(command) {
if (commandsWaitingConfirmation === undefined) {return} // object already finalized
nonce++
let stringNonce = nonce.toString()
let timestamp = (new Date()).valueOf()
@@ -103,7 +103,7 @@ function newSessionFunctions() {
'Exchange Id->Rate->Order Name->Algorithm Name->Lock->' +
'Order Counters->' +
'Order Base Asset->Order Quoted Asset->' +
'Actual Size->Size->Size Filled->Size To Be Filled->Fees Paid->Fees To Be Paid->' +
'Actual Size->Size->Size Filled->Amount Received->Fees Paid->Fees To Be Paid->' +
'Order Statistics->Percentage Filled->Actual Rate->Days->User Defined Statistic->' +
'Strategy Trigger Stage->Strategy Open Stage->Strategy Manage Stage->Strategy Close Stage->' +
'Begin->End->Exit Type->Status->Begin Rate->End Rate->Stage Base Asset->Stage Quoted Asset->Size Placed->Target Size->Size Filled->Fees Paid->Stage Defined In->' +
@@ -1 +1 @@
{"type":"Trading Engine","name":"Low-Frequency","project":"Superalgos","current":{"type":"Current","name":"New Current","project":"Superalgos","episode":{"type":"Episode","name":"New Episode","project":"Superalgos","serialNumber":{"type":"Serial Number","name":"New Serial Number","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"5eb83933-432a-4029-8a3e-e4d8d760dd27","savedPayload":{"position":{"x":25079.57302866531,"y":38997.25347206287},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"identifier":{"type":"Identifier","name":"New Identifier","config":"{\n\"initialValue\": \"\"\n}","project":"Superalgos","id":"8ba7fe0c-c5df-408e-be8a-ebde90f2a50d","savedPayload":{"position":{"x":25151.200052242173,"y":38903.9073175564},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"begin":{"type":"Begin","name":"New Begin","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"d7faf864-3fa8-4280-b7d3-a022338b0dfe","savedPayload":{"position":{"x":25237.94829000812,"y":38824.41720300039},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"end":{"type":"End","name":"New End","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"f6722d05-77f9-4a68-ba69-38f56cdc7e91","savedPayload":{"position":{"x":25337.18194065536,"y":38761.19839530167},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"beginRate":{"type":"Begin Rate","name":"New Begin Rate","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"613903ae-45e4-4eee-a178-d6ec049490ec","savedPayload":{"position":{"x":25445.885839923623,"y":38716.171765941916},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"endRate":{"type":"End Rate","name":"New End Rate","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"b2bfb20d-64d5-4c98-b0cd-a466654c1e6a","savedPayload":{"position":{"x":25560.757074840472,"y":38690.70542626965},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"status":{"type":"Status","name":"New Status","config":"{\n\"initialValue\": \"Not Open\"\n}","project":"Superalgos","id":"2615490b-e968-4607-a6e5-009c3ae78957","savedPayload":{"position":{"x":25678.305341060608,"y":38685.57315812922},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"exitType":{"type":"Exit Type","name":"New Exit Type","config":"{\n\"initialValue\": \"No Exit\"\n}","project":"Superalgos","id":"6bcc264b-a189-4aca-8397-d3520bdf4078","savedPayload":{"position":{"x":25794.958993997225,"y":38700.930902891014},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"headOfTheMarket":{"type":"Head Of The Market","name":"New Head Of The Market","config":"{\n\"initialValue\": false\n}","project":"Superalgos","id":"f7e00135-13b1-4265-95da-dd3a2c763b6f","savedPayload":{"position":{"x":25907.173571435444,"y":38736.312023251856},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"processDate":{"type":"Process Date","name":"New Process Date","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"d9163f6e-0d50-4ddd-b13b-14f7b4124ec8","savedPayload":{"position":{"x":26011.539490223113,"y":38790.64148177327},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"episodeBaseAsset":{"type":"Episode Base Asset","name":"New Episode Base Asset","project":"Superalgos","balance":{"type":"Balance","name":"Base Asset","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"72f6a6b6-f35b-4d80-b8ad-4a4ed55f2387","savedPayload":{"position":{"x":26631.Line truncated
{"type":"Trading Engine","name":"Low-Frequency","project":"Superalgos","current":{"type":"Current","name":"New Current","project":"Superalgos","episode":{"type":"Episode","name":"New Episode","project":"Superalgos","serialNumber":{"type":"Serial Number","name":"New Serial Number","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"5eb83933-432a-4029-8a3e-e4d8d760dd27","savedPayload":{"position":{"x":25079.57302866531,"y":38997.25347206287},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"identifier":{"type":"Identifier","name":"New Identifier","config":"{\n\"initialValue\": \"\"\n}","project":"Superalgos","id":"8ba7fe0c-c5df-408e-be8a-ebde90f2a50d","savedPayload":{"position":{"x":25151.200052242173,"y":38903.9073175564},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"begin":{"type":"Begin","name":"New Begin","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"d7faf864-3fa8-4280-b7d3-a022338b0dfe","savedPayload":{"position":{"x":25237.94829000812,"y":38824.41720300039},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"end":{"type":"End","name":"New End","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"f6722d05-77f9-4a68-ba69-38f56cdc7e91","savedPayload":{"position":{"x":25337.18194065536,"y":38761.19839530167},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"beginRate":{"type":"Begin Rate","name":"New Begin Rate","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"613903ae-45e4-4eee-a178-d6ec049490ec","savedPayload":{"position":{"x":25445.885839923623,"y":38716.171765941916},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"endRate":{"type":"End Rate","name":"New End Rate","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"b2bfb20d-64d5-4c98-b0cd-a466654c1e6a","savedPayload":{"position":{"x":25560.757074840472,"y":38690.70542626965},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"status":{"type":"Status","name":"New Status","config":"{\n\"initialValue\": \"Not Open\"\n}","project":"Superalgos","id":"2615490b-e968-4607-a6e5-009c3ae78957","savedPayload":{"position":{"x":25678.305341060608,"y":38685.57315812922},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"exitType":{"type":"Exit Type","name":"New Exit Type","config":"{\n\"initialValue\": \"No Exit\"\n}","project":"Superalgos","id":"6bcc264b-a189-4aca-8397-d3520bdf4078","savedPayload":{"position":{"x":25794.958993997225,"y":38700.930902891014},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"headOfTheMarket":{"type":"Head Of The Market","name":"New Head Of The Market","config":"{\n\"initialValue\": false\n}","project":"Superalgos","id":"f7e00135-13b1-4265-95da-dd3a2c763b6f","savedPayload":{"position":{"x":25907.173571435444,"y":38736.312023251856},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"processDate":{"type":"Process Date","name":"New Process Date","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"d9163f6e-0d50-4ddd-b13b-14f7b4124ec8","savedPayload":{"position":{"x":26011.539490223113,"y":38790.64148177327},"targetPosition":{"x":25648.862254589007,"y":39359.93070769697},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"episodeBaseAsset":{"type":"Episode Base Asset","name":"New Episode Base Asset","project":"Superalgos","balance":{"type":"Balance","name":"Base Asset","config":"{\n\"initialValue\": 0\n}","project":"Superalgos","id":"72f6a6b6-f35b-4d80-b8ad-4a4ed55f2387","savedPayload":{"position":{"x":26631.Line truncated
@@ -1 +1 @@
{"type":"Trading Mine","name":"Masters","config":"{ \n\"codeName\": \"Masters\"\n}","project":"Superalgos","tradingBots":[{"type":"Trading Bot","name":"Low Frequency","config":"{\n\"codeName\": \"Low-Frequency\",\n\"repo\": \"Exchange-Raw-Data-Sensor-Bot\"\n}","project":"Superalgos","processes":[{"type":"Process Definition","name":"Trading","config":"{\n \"codeName\": \"Trading\",\n \"sessionRunWaitTime\": 5000,\n \"retryWaitTime\": 10000,\n \"framework\": {\n \"name\": \"Low-Frequency-Trading-Process\"\n }\n }","project":"Superalgos","processOutput":{"type":"Process Output","name":"New Process Output","project":"Superalgos","outputDatasets":[],"outputDatasetFolders":[{"type":"Output Dataset Folder","name":"Trading Engine","project":"Superalgos","outputDatasets":[],"outputDatasetFolders":[{"type":"Output Dataset Folder","name":"Episode","project":"Superalgos","outputDatasets":[{"type":"Output Dataset","name":"Multi-Period-Market Episode Counters","project":"Superalgos","id":"ec8d74ba-a4ad-417a-b479-6bae95b8a1fe","savedPayload":{"position":{"x":24916.97457254189,"y":18457.46644293786},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"3ed03a02-381d-43fe-9faa-761728c53bd8"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Counters","project":"Superalgos","id":"22b0af33-8c2f-4194-839b-affd18f298d5","savedPayload":{"position":{"x":24840.601232363748,"y":18490.07023931273},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"15f5b150-f1cf-403c-8cb9-2c7c4e1db8e8"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Statistics","project":"Superalgos","id":"23ab91ae-2e78-4993-bb69-855f69cef1ce","savedPayload":{"position":{"x":24759.53736157276,"y":18508.085329665024},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"fc9dc0f7-35e0-4548-bb91-f920825d694f"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Statistics","project":"Superalgos","id":"d0a4515a-279d-4c27-8de2-168108861242","savedPayload":{"position":{"x":24676.543493063306,"y":18510.89823169951},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"91953362-8751-4440-a898-21245cd47c15"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Candle","project":"Superalgos","id":"7fc80977-5b37-413e-8107-0a89a16e08b0","savedPayload":{"position":{"x":24594.445883487308,"y":18498.413155411115},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"17fb41f5-61b5-4433-9a10-950ff8216a7e"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Candle","project":"Superalgos","id":"a6888750-1043-420c-9253-633e1399c5e4","savedPayload":{"position":{"x":24516.040268473647,"y":18471.05526509864},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"bbc9467d-28bf-432b-9e49-582d1bcabfb9"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Balances","project":"Superalgos","id":"0ad114df-f076-4a86-8c62-4c4a146da73c","savedPayload":{"position":{"x":24443.996657204443,"y":18429.756200904503},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"acddff59-b51b-4b45-b58f-71d4077755ee"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Balances","project":"Superalgos","id":"04b51302-957d-4365-ad55-9f89d60e261c","savedPayload":{"position":{"x":24380.76840845468,"y":18375.922352927064},"targetPosition":{"x":24702.86240363471,"y":18061.6685410663Line truncated
{"type":"Trading Mine","name":"Masters","config":"{ \n\"codeName\": \"Masters\"\n}","project":"Superalgos","tradingBots":[{"type":"Trading Bot","name":"Low Frequency","config":"{\n\"codeName\": \"Low-Frequency\",\n\"repo\": \"Exchange-Raw-Data-Sensor-Bot\"\n}","project":"Superalgos","processes":[{"type":"Process Definition","name":"Trading","config":"{\n \"codeName\": \"Trading\",\n \"sessionRunWaitTime\": 5000,\n \"retryWaitTime\": 10000,\n \"framework\": {\n \"name\": \"Low-Frequency-Trading-Process\"\n }\n }","project":"Superalgos","processOutput":{"type":"Process Output","name":"New Process Output","project":"Superalgos","outputDatasets":[],"outputDatasetFolders":[{"type":"Output Dataset Folder","name":"Trading Engine","project":"Superalgos","outputDatasets":[],"outputDatasetFolders":[{"type":"Output Dataset Folder","name":"Episode","project":"Superalgos","outputDatasets":[{"type":"Output Dataset","name":"Multi-Period-Market Episode Counters","project":"Superalgos","id":"ec8d74ba-a4ad-417a-b479-6bae95b8a1fe","savedPayload":{"position":{"x":24916.97457254189,"y":18457.46644293786},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"3ed03a02-381d-43fe-9faa-761728c53bd8"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Counters","project":"Superalgos","id":"22b0af33-8c2f-4194-839b-affd18f298d5","savedPayload":{"position":{"x":24840.601232363748,"y":18490.07023931273},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"15f5b150-f1cf-403c-8cb9-2c7c4e1db8e8"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Statistics","project":"Superalgos","id":"23ab91ae-2e78-4993-bb69-855f69cef1ce","savedPayload":{"position":{"x":24759.53736157276,"y":18508.085329665024},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"fc9dc0f7-35e0-4548-bb91-f920825d694f"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Statistics","project":"Superalgos","id":"d0a4515a-279d-4c27-8de2-168108861242","savedPayload":{"position":{"x":24676.543493063306,"y":18510.89823169951},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"91953362-8751-4440-a898-21245cd47c15"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Candle","project":"Superalgos","id":"7fc80977-5b37-413e-8107-0a89a16e08b0","savedPayload":{"position":{"x":24594.445883487308,"y":18498.413155411115},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"17fb41f5-61b5-4433-9a10-950ff8216a7e"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Candle","project":"Superalgos","id":"a6888750-1043-420c-9253-633e1399c5e4","savedPayload":{"position":{"x":24516.040268473647,"y":18471.05526509864},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"bbc9467d-28bf-432b-9e49-582d1bcabfb9"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Balances","project":"Superalgos","id":"0ad114df-f076-4a86-8c62-4c4a146da73c","savedPayload":{"position":{"x":24443.996657204443,"y":18429.756200904503},"targetPosition":{"x":24702.86240363471,"y":18061.668541066385},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"acddff59-b51b-4b45-b58f-71d4077755ee"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Balances","project":"Superalgos","id":"04b51302-957d-4365-ad55-9f89d60e261c","savedPayload":{"position":{"x":24380.76840845468,"y":18375.922352927064},"targetPosition":{"x":24702.86240363471,"y":18061.6685410663Line truncated
@@ -0,0 +1 @@
[object Promise]
@@ -4475,9 +4475,9 @@
"autoAdd": true
},
{
"name": "sizeToBeFilled",
"name": "amountReceived",
"type": "node",
"childType": "Size To Be Filled",
"childType": "Amount Received",
"autoAdd": true
},
{
@@ -4539,9 +4539,9 @@
"autoAdd": true
},
{
"name": "sizeToBeFilled",
"name": "amountReceived",
"type": "node",
"childType": "Size To Be Filled",
"childType": "Amount Received",
"autoAdd": true
},
{
@@ -7843,7 +7843,7 @@
"propertyNameAtParent": "actualSize"
},
{
"type": "Size To Be Filled",
"type": "Amount Received",
"menuItems": [
{
"action": "Configure",
@@ -7894,7 +7894,7 @@
"chainAttachesTo": {
"compatibleTypes": "->Order Base Asset->Order Quoted Asset->"
},
"propertyNameAtParent": "sizeToBeFilled"
"propertyNameAtParent": "amountReceived"
},
{
"type": "Fees To Be Paid",
@@ -11256,7 +11256,6 @@
"actionFunction": "payload.executeAction"
}
],
"isTitleAllwaysVisible": true,
"isHierarchyHead": true,
"addLeftIcons": true,
"level": 0,
@@ -0,0 +1,298 @@
exports.newOrdersCalculations = function newOrdersCalculations(bot, logger) {
/*
When we are live trading, we need to syncronize with the exchange.
*/
const MODULE_NAME = 'Orders Calculations'
let thisObject = {
actualSizeCalculation: actualSizeCalculation,
actualRateCalculation: actualRateCalculation,
feesToBePaidCalculation: feesToBePaidCalculation,
amountReceivedCalculation: amountReceivedCalculation,
finapercentageFilledCalculationlize: percentageFilledCalculation,
feesPaidCalculation: feesPaidCalculation,
sizeFilledCalculation: sizeFilledCalculation,
initialize: initialize,
finalize: finalize
}
let tradingEngine
let tradingSystem
let sessionParameters
return thisObject
function initialize() {
sessionParameters = bot.SESSION.parameters
tradingEngine = bot.simulationState.tradingEngine
tradingSystem = bot.simulationState.tradingSystem
}
function finalize() {
sessionParameters = undefined
tradingEngine = undefined
tradingSystem = undefined
}
async function actualSizeCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
When we submit the order to the exchange, we do it specifying the order size
in Base Asset. It is mandatory to be in Base Asset as per CCXT Library API and
probably as per the Exchange API too.
The exchange might take that size or might change it a little
bit. For example, if it does not accept as many decimals as we are sending.
We have also seen that for Market Orders, the exchange usually does not take
the size requested exactly. Sometimes is a little bit less or even a little bit
more. For Limit orders it is usually the same.
If it happens that the size accepted (Actual Size) is different than the
size requested (Size), we need to make several adjustments so that the
accounting syncronizes with reality.
*/
/* This calculation needs to happen only once, the first time the order is checked. */
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
/* We receive the actual size from the exchange at the order.amount field. */
tradingEngineOrder.orderBaseAsset.actualSize.value = order.amount
recalculateActualSize()
recalculateSizePlaced()
function recalculateActualSize() {
/*
We will recalculate the Quoted Asset Actual Size. This will also give to it an initial value the first time we do this.
In the next formula, we are unsing the rate of the order because we dont know yet the Actual Rate.
This rate might be replaced afteerwards for the Actual Rate when this is calculated again once
the Actual Rate is known.
*/
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.rate.value
tradingEngineOrder.orderBaseAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.actualSize.value, 10)
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
}
function recalculateSizePlaced() {
/*
We will also recalculate the Size Placed that was previously calculated with the order Size,
and now we will do it with the order Actual Size.
We need to unaccount the previous size placed and correctly account
for the the new actual sizes we now know.
*/
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value -
tradingEngineOrder.orderBaseAsset.size.value
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value +
tradingEngineOrder.orderBaseAsset.actualSize.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
tradingEngineOrder.orderQuotedAsset.size.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.actualSize.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 actualRateCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
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 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. As of today it is not 100% clear when this is
available, but it seems sometimes it is not. In those cases we use the price.
*/
tradingEngineOrder.orderStatistics.actualRate.value = order.average
} else {
/*
We use the order.price when the average is not available.
*/
tradingEngineOrder.orderStatistics.actualRate.value = order.price
}
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
/*
If the Actual Rate happens to be exctly the same than the order rate, then there is
nothing else to do here. Otherwise there are adjustments to be made.
*/
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
let previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
recalculateActualSize()
recalculateSizePlaced()
function recalculateActualSize() {
/*
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 Actual Order Size for Quoted Asset since it was calculated with the Order Size that we
now know it is not the one really used at the exchange. We will recalculate the
Actual Size in Quoted Asset and not in Base Asset, since the Actual Size in Base Asset
is the one we accepted by the exchange, so that is fixed, regardless of how we initially
got the Order Size in Base Asset, either 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.actualSize.value =
tradingEngineOrder.orderBaseAsset.actualSize.value *
tradingEngineOrder.orderStatistics.actualRate.value
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
}
function recalculateSizePlaced() {
/*
Since we changed the Actual Size in Quoted Asset, we need to fix the Size Placed in Quoted Asset
that was previously calculated with the previous value of actualSize.
*/
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetActualSize
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.actualSize.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
}
async function feesToBePaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
'feesToBePaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
100
)
break
}
case 'Limit Order': {
await applyFeePercentage(
'feesToBePaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
100
)
break
}
}
}
async function percentageFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
Percentage Filled Calculation: The only relevant information we get from the exchange is the order.filled.
We know this field will go up until reaching the Actual Size. Once it reaches that number
the order will be 100% filled.
*/
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / tradingEngineOrder.orderBaseAsset.actualSize.value
tradingEngineOrder.orderStatistics.percentageFilled.value = global.PRECISE(tradingEngineOrder.orderStatistics.percentageFilled.value, 10)
}
async function feesPaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
Within the order information received from the exchange we can not see the fees paid so we need to
calculate them ourselves. Knwowing the Fees Paid is critical to later update the balances correctly,
since at every trade we will receive less than just the Actual Size. In fact we will receive the Actual
Size minus the Fees Paid.
The percentage at which the exchange will charge fees needs to be configured at the the Session Fees Parameters.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
'feesPaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
break
}
case 'Limit Order': {
await applyFeePercentage(
'feesPaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
break
}
}
}
async function sizeFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
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 reflect the Fees Paid, even if we are paying the fees in Base Asset.
*/
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.
*/
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
tradingEngineOrder.orderQuotedAsset.sizeFilled.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
}
async function amountReceivedCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
This calculation is for informational purposes, so that users do not have to calculate
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
are selling then it will be in Quoted Asset.
*/
switch (true) {
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
/*
In this case the Amount Received is in Base Asset.
*/
tradingEngineOrder.orderBaseAsset.amountReceived.value.value =
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
tradingEngineOrder.orderBaseAsset.feesPaid.value
break
}
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
/*
In this case the Amount Received is in Quoted Asset.
*/
tradingEngineOrder.orderQuotedAsset.amountReceived.value.value =
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
tradingEngineOrder.orderQuotedAsset.feesPaid.value
break
}
}
}
}
@@ -0,0 +1,366 @@
exports.newOrdersSimulations = function newOrdersSimulations(bot, logger) {
/*
When we are backtesting or paper trading, we need to simulate what would have happened at the exchange.
*/
const MODULE_NAME = 'Orders Simulations'
let thisObject = {
actualSizeSimulation: actualSizeSimulation,
actualRateSimulation: actualRateSimulation,
feesToBePaidSimulation: feesToBePaidSimulation,
amountReceivedSimulation: amountReceivedSimulation,
percentageFilledSimulation: percentageFilledSimulation,
feesPaidSimulation: feesPaidSimulation,
sizeFilledSimulation: sizeFilledSimulation,
initialize: initialize,
finalize: finalize
}
let tradingEngine
let tradingSystem
let sessionParameters
return thisObject
function initialize() {
sessionParameters = bot.SESSION.parameters
tradingEngine = bot.simulationState.tradingEngine
tradingSystem = bot.simulationState.tradingSystem
}
function finalize() {
sessionParameters = undefined
tradingEngine = undefined
tradingSystem = undefined
}
async function actualSizeSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
/*
The Actual Size is the size the exchange accepts for the order,
regarless of the size sent in the request to the exchange.
While backtesting and paper trading, we do not really need to
simulate an Actual Size. We will just copy to that field the
size of the order.
This calculation needs to happen only once, so as to set these values.
*/
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
tradingEngineOrder.orderBaseAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.size.value
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderQuotedAsset.size.value
}
function actualRateSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
/* Actual Rate Simulation */
let previousQuotedAssetActualSize
basedOnSessionParameters()
basedOnTradingSystem()
considerBestMatchWithOrderBook()
recalculateActualSize()
recalculateSizePlaced()
function basedOnSessionParameters() {
/*
The default way to calculate the Actual Rate is to apply the Slippage defined
at the Session Parameters Definition.
*/
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 basedOnTradingSystem() {
/*
If the user specified a Formula to simulate the Actual Rate for this order,
we use that formula here. Note that if this Formula exists, this will override
the calculation based on Slippage.
*/
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)
}
}
}
}
}
function considerBestMatchWithOrderBook() {
/*
Until here we have got the Actual 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.
We don't know what it is at the order book, but whe know that the last candle includes trades that bounced between
the min an max of the candle. That could tell us the above the max and below the min, the order would be matched
with orders at the order book. For that reason, we do this:
*/
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
tradingSystem.warnings.push([tradingSystemOrder.id, 'Actual Rate was adjusted to what it might be at the order book.'])
}
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
tradingSystem.warnings.push([tradingSystemOrder.id, 'Actual Rate was adjusted to what it might be at the order book.'])
}
break
}
}
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
}
function recalculateActualSize() {
/*
Now that we have an Actual Rate, we need to adjust the Actual Size in Quoted Asset, since
Base Asset is what the exchange accepts as input and give information at. Quotes Asset properties are always calculated.
*/
previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
tradingEngineOrder.orderQuotedAsset.actualSize.value =
tradingEngineOrder.orderBaseAsset.actualSize.value *
tradingEngineOrder.orderStatistics.actualRate.value
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
}
function recalculateSizePlaced() {
/*
Since the Actual Rate might have changed, we need to recalculate the Size Placed, where we accumulate all the Size Placed of
all orders of a Stage. For Base Asset there is nothing to do, since the Actual Rate does not have an inpact on it.
For Quoted Asset, we need to first unaccount what this same orded added before to Size Placed (with the precious Actual Size) and
account with the new Actual Size.
*/
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetActualSize
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.actualSize.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
}
async function feesToBePaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
/*
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
'feesToBePaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
100
)
break
}
case 'Limit Order': {
await applyFeePercentage(
'feesToBePaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
100
)
break
}
}
}
function percentageFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
/*
When we are live, it is the exchange who knows which order was filled and in
which amount. Since we are backtesting here, we need to simulate what happens
at the exchange. We can not simulate that an order is filled if the candle we
see did not touch the order.
For limit buy orders we are going to check if the price was below the order actual rate, and
for limit sell orders we are going to check if the price was above the order actual rate.
If we have a partial filling defined specifically for this order,
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.actualRate.value) {
orderWasHit = true
}
break
}
case 'Limit Sell Order': {
if (tradingEngine.current.episode.candle.max.value >= tradingEngineOrder.actualRate.value) {
orderWasHit = true
}
break
}
case 'Market Buy Order': {
orderWasHit = true
break
}
case 'Market Sell Order': {
orderWasHit = true
break
}
}
if (orderWasHit === true) {
/*
Initially, we will assume that 100% of the order was filled. This might be overwritten
if there is a configuration for this order that details how much is must be considered
filled each time we check this.
*/
tradingEngineOrder.orderStatistics.percentageFilled.value = 100
/* 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)
}
}
}
}
}
function feesPaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
/*
The way the Fess Paid are calculated is as follows: From the order Actual 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 Structure defined at the Session Parameters.
*/
/* Based on the Trading System Definition */
if (tradingSystemOrder.simulatedExchangeEvents !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
applyFeePercentage(
'feesPaid',
tradingEngineOrder,
tradingSystemOrder,
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
return
}
}
}
/*
Another way to simulate the Fees Paid is by using the Session Paremeters configuration for Fees.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
applyFeePercentage(
'feesPaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
break
}
case 'Limit Order': {
applyFeePercentage(
'feesPaid',
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
break
}
}
}
function sizeFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage) {
/*
Size Filled is the amount of Base Asset or Quoted Asset that at the exchange matches
another counterparty order. We calculate this using the Percentage Filled we already simulated.
*/
tradingEngineOrder.orderBaseAsset.sizeFilled.value =
tradingEngineOrder.orderBaseAsset.actualSize.value *
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
tradingEngineOrder.orderQuotedAsset.actualSize.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 amountReceivedSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage) {
/*
This simulation is for informational purposes, so that users do not have to calculate
it by themselves. The Amount Received out of the trade depends if we are Buying or Selling
the Base Asset. If we are Buying, then the Amount Receiced will be in Base Asset. If we
are selling then it will be in Quoted Asset.
*/
switch (true) {
case tradingSystemOrder.type === 'Market Buy Order' || tradingSystemOrder.type === 'Limit Buy Order': {
/*
In this case the Amount Received is in Base Asset.
*/
tradingEngineOrder.orderBaseAsset.amountReceived.value.value =
tradingEngineOrder.orderBaseAsset.sizeFilled.value -
tradingEngineOrder.orderBaseAsset.feesPaid.value
break
}
case tradingSystemOrder.type === 'Market Sell Order' || tradingSystemOrder.type === 'Limit Sell Order': {
/*
In this case the Amount Received is in Quoted Asset.
*/
tradingEngineOrder.orderQuotedAsset.amountReceived.value.value =
tradingEngineOrder.orderQuotedAsset.sizeFilled.value -
tradingEngineOrder.orderQuotedAsset.feesPaid.value
break
}
}
}
}
@@ -47,27 +47,27 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
/*
This function is called each time the simulation starts. That does not mean the Episode
must be opened there, since it might happen that it is looping at the end of the market
or the task / session was restarted.
or the task / session was restarted. We will only execute if the episode was never opened before.
*/
if (bot.FIRST_EXECUTION === true) {
/* Starting begin and end */
tradingEngine.current.episode.begin.value = tradingEngine.current.episode.candle.begin.value
tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value
if (tradingEngine.current.episode.begin.value !== tradingEngine.current.episode.begin.config.initialValue) { return }
/* Getting the begin Balance from the session configuration */
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value = sessionParameters.sessionBaseAsset.config.initialBalance
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
/* Starting begin and end */
tradingEngine.current.episode.begin.value = tradingEngine.current.episode.candle.begin.value
tradingEngine.current.episode.end.value = tradingEngine.current.episode.candle.end.value
/* The current balance is also the begin balance, that is how this starts. */
tradingEngine.current.episode.episodeBaseAsset.balance.value = sessionParameters.sessionBaseAsset.config.initialBalance
tradingEngine.current.episode.episodeQuotedAsset.balance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
/* Getting the begin Balance from the session configuration */
tradingEngine.current.episode.episodeBaseAsset.beginBalance.value = sessionParameters.sessionBaseAsset.config.initialBalance
tradingEngine.current.episode.episodeQuotedAsset.beginBalance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
/* Recording the opening at the Trading Engine Data Structure */
tradingEngine.current.episode.status.value = 'Open'
tradingEngine.current.episode.serialNumber.value = 1
tradingEngine.current.episode.identifier.value = global.UNIQUE_ID()
tradingEngine.current.episode.beginRate.value = tradingEngine.current.episode.candle.close.value
}
/* The current balance is also the begin balance, that is how this starts. */
tradingEngine.current.episode.episodeBaseAsset.balance.value = sessionParameters.sessionBaseAsset.config.initialBalance
tradingEngine.current.episode.episodeQuotedAsset.balance.value = sessionParameters.sessionQuotedAsset.config.initialBalance
/* Recording the opening at the Trading Engine Data Structure */
tradingEngine.current.episode.status.value = 'Open'
tradingEngine.current.episode.serialNumber.value = 1
tradingEngine.current.episode.identifier.value = global.UNIQUE_ID()
tradingEngine.current.episode.beginRate.value = tradingEngine.current.episode.candle.close.value
}
function updateExitType(exitType) {
+135 -650
View File
@@ -148,7 +148,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
if (tradingEngine.current.episode.cycle.value === 'Second') { continue }
/* Simulate Events that happens at the Exchange, if needed. */
simulateExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder)
simulateCheckExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder)
/* Check Events that happens at the Exchange, if needed. */
let allGood = await checkExchangeEvents(tradingEngineStage, tradingSystemOrder, tradingEngineOrder)
@@ -237,14 +237,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
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.orderBaseAsset.actualSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.actualSize === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderBaseAsset.sizeToBeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.sizeToBeFilled === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderBaseAsset.amountReceived === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.amountReceived === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderBaseAsset.feesToBePaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderBaseAsset.feesToBePaid === 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) }
if (tradingEngineOrder.orderQuotedAsset.actualSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.actualSize === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderQuotedAsset.sizeToBeFilled === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.sizeToBeFilled === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderQuotedAsset.amountReceived === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.amountReceived === undefined', tradingEngineOrder) }
if (tradingEngineOrder.orderQuotedAsset.feesToBePaid === undefined) { badDefinitionUnhandledException(undefined, 'tradingEngineOrder.orderQuotedAsset.feesToBePaid === undefined', tradingEngineOrder) }
}
@@ -395,367 +395,6 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
}
}
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 previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value
let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value
let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value
let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value
actualSizeSimulation()
actualRateSimulation()
feesToBePaidSimulation()
sizeToBeFilledSimulation()
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')
}
async function actualSizeSimulation() {
/*
While backtesting and paper trading, we do not really need to change
simulate an Actual Size. We will just copy to that field the
size of the order.
*/
tradingEngineOrder.orderBaseAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.size.value
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderQuotedAsset.size.value
}
function actualRateSimulation() {
/* Actual Rate Simulation */
let calculatedBasedOnTradingSystem = false
let previousQuotedAssetActualSize
basedOnTradingSystem()
basedOnSessionParameters()
considerBestMatchWithOrderBook()
recalculateActualSize()
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 recalculateActualSize() {
/*
The Quoted Asset Actual Size in a backtest is equal to the Quoted Asset Size, which is related to
the Base Asset Size by the Order Rate. Since now we have the Actual Rate, we will need to recalculate
the Quoted Asset Actual Size using the Actual Rate.
*/
previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.orderStatistics.actualRate.value
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
}
function recalculateSizePlaced() {
/*
For the Quoted Asset, the Size Placed was previously calculated with the previous Actual Size (or Size when these were equal).
Since we have just recaclcualted the Actual Size in Quoted Asset, we need to fix the Size Placed for the stage which was
calculated with the previous value.
*/
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetActualSize
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.actualSize.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
}
async function feesToBePaidSimulation() {
/*
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
tradingEngineOrder.orderBaseAsset.feesToBePaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
100
)
break
}
case 'Limit Order': {
await applyFeePercentage(
tradingEngineOrder.orderBaseAsset.feesToBePaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
100
)
break
}
}
}
async function sizeToBeFilledSimulation() {
/*
The Size to Be Filled is the Actual Size minus the Fees to be Paid.
*/
tradingEngineOrder.orderBaseAsset.sizeToBeFilled.value =
tradingEngineOrder.orderBaseAsset.actualSize.value -
tradingEngineOrder.orderBaseAsset.feesToBePaid.value
tradingEngineOrder.orderQuotedAsset.sizeToBeFilled.value =
tradingEngineOrder.orderQuotedAsset.actualSize.value -
tradingEngineOrder.orderQuotedAsset.feesToBePaid.value
}
function percentageFilledSimulation() {
/*
When we are live, it is the exchange who knows which order was filled and in
which amount. Since we are backtesting here, we need to simulate what happens
at the exchange. We can not simulate that an order is filled if the candle we
can see did not touch the order.
For buy orders we are going to check if the price was below the order actual rate, and
for sell orders we are going to check if the price was above the order actual rate.
If we have a partial filling defined specifically for this order,
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.actualRate.value) {
orderWasHit = true
}
break
}
case 'Limit Sell Order': {
if (tradingEngine.current.episode.candle.max.value >= tradingEngineOrder.actualRate.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 they are hit, which is the most likely thing to happen.
*/
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.orderBaseAsset.feesPaid,
tradingEngineOrder,
tradingSystemOrder,
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
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.orderBaseAsset.feesPaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
break
}
case 'Limit Order': {
applyFeePercentage(
tradingEngineOrder.orderBaseAsset.feesPaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
break
}
}
}
function sizeFilledSimulation() {
/*
Size Filled is the amount of Base Asset or Quoted Asset that at the exchange matches
another counterparty order. We alredy have simulated the Percentage Filled and the
Size To Be Filled, which is the order Actual Size minus the Fees To Be Paid.
*/
tradingEngineOrder.orderBaseAsset.sizeFilled.value =
tradingEngineOrder.orderBaseAsset.sizeToBeFilled.value *
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderQuotedAsset.sizeFilled.value =
tradingEngineOrder.orderQuotedAsset.sizeToBeFilled.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 */
@@ -861,6 +500,78 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
return true
}
function simulateCheckExchangeEvents(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
}
}
simulateSyncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder)
/* 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])
}
/* 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 simulateSyncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder) {
let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value
let previousQuotedAssetSizeFilled = tradingEngineOrder.orderQuotedAsset.sizeFilled.value
let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value
let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value
const ORDERS_SIMULATIONS_MODULE = require('./OrdersSimulations.js')
let ordersSimulationsModule = ORDERS_SIMULATIONS_MODULE.newOrdersSimulations(bot, logger)
ordersSimulationsModule.initialize()
ordersSimulationsModule.actualSizeSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage)
ordersSimulationsModule.actualRateSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage)
ordersSimulationsModule.feesToBePaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage)
ordersSimulationsModule.percentageFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage)
ordersSimulationsModule.feesPaidSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage)
ordersSimulationsModule.sizeFilledSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage)
ordersSimulationsModule.amountReceivedSimulation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, applyFeePercentage)
ordersSimulationsModule.finalize()
doTheAccounting(
tradingEngineStage,
tradingSystemOrder,
tradingEngineOrder,
previousBaseAssetSizeFilled,
previousQuotedAssetSizeFilled,
previousBaseAssetFeesPaid,
previousQuotedAssetFeesPaid
)
}
async function syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order) {
let previousBaseAssetSizeFilled = tradingEngineOrder.orderBaseAsset.sizeFilled.value
@@ -868,13 +579,19 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
let previousBaseAssetFeesPaid = tradingEngineOrder.orderBaseAsset.feesPaid.value
let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value
await actualSizeCalculation()
await actualRateCalculation()
await feesToBePaidCalculation()
await sizeToBeFilledCalculation()
await percentageFilledCalculation()
await feesPaidCalculation()
await sizeFilledCalculation()
const ORDERS_CALCULATIONS_MODULE = require('./OrdersCalculations.js')
let ordersCalculationsModule = ORDERS_CALCULATIONS_MODULE.newOrdersCalculations(bot, logger)
ordersCalculationsModule.initialize()
await ordersCalculationsModule.actualSizeCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage)
await ordersCalculationsModule.actualRateCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage)
await ordersCalculationsModule.feesToBePaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage)
await ordersCalculationsModule.percentageFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage)
await ordersCalculationsModule.feesPaidCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage)
await ordersCalculationsModule.sizeFilledCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage)
await ordersCalculationsModule.amountReceivedCalculation(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order, applyFeePercentage)
ordersCalculationsModule.finalize()
await doTheAccounting(
tradingEngineStage,
@@ -885,240 +602,6 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
previousBaseAssetFeesPaid,
previousQuotedAssetFeesPaid
)
return
async function actualSizeCalculation() {
/*
When we submit the order to the exchange, we do it specifying the order size
in Base Asset. It is mandatory to be in Base Asset as per CCXT Library API.
The exchange might take that size or might change it a little
bit. 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.
*/
/* This calculation only happens once, the first time the order is checked. */
if (tradingEngineOrder.orderBaseAsset.actualSize.value !== tradingEngineOrder.orderBaseAsset.actualSize.config.initialValue) { return }
/* We receive the actual size from the exchange at the order.amount field. */
tradingEngineOrder.orderBaseAsset.actualSize.value = order.amount
/*
If the exchange accepted the size sent on the request to create the order,
we don't need to do anything else here.
*/
if (tradingEngineOrder.orderBaseAsset.size.value === tradingEngineOrder.orderBaseAsset.actualSize.value) { return }
/*
If not, we will calculate the Quoted Asset Actual Size too. Note that the amount received
is in Base Asset only. In the next formula, we are useing the rate of the order.
This rate might be replaced afteerwards for an actual rate. When that happens we will
need to adjust these results.
*/
tradingEngineOrder.orderQuotedAsset.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.rate.value
tradingEngineOrder.orderBaseAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.actualSize.value, 10)
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
recalculateSizePlaced()
function recalculateSizePlaced() {
/*
Wee also need to unaccount the previous size placed and correctly account
for the the new actual sizes we now have.
*/
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value -
tradingEngineOrder.orderBaseAsset.size.value
tradingEngineStage.stageBaseAsset.sizePlaced.value =
tradingEngineStage.stageBaseAsset.sizePlaced.value +
tradingEngineOrder.orderBaseAsset.actualSize.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
tradingEngineOrder.orderQuotedAsset.size.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.actualSize.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 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 the Actual Rate happens to be exctly the same than the order rate, then there is
nothing else to do here. Otherwise there are adjustments to be made.
*/
if (tradingEngineOrder.orderStatistics.actualRate.value === tradingEngineOrder.rate.value) { return }
let previousQuotedAssetActualSize = tradingEngineOrder.orderQuotedAsset.actualSize.value
recalculateActualSize()
recalculateSizePlaced()
function recalculateActualSize() {
/*
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 Actual Order Size for Quoted Asset since it was calculated with the Order Size that we
now know it is not the one really used at the exchange. We will recalculate the
Actual Size in Quoted Asset and not in Base Asset, since the Actual Size in Base Asset
is the one we accepted by the exchange, so that is fixed, regardless of how we initially
got the Order Size in Base Asset, either 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.actualSize.value = tradingEngineOrder.orderBaseAsset.actualSize.value * tradingEngineOrder.orderStatistics.actualRate.value
tradingEngineOrder.orderQuotedAsset.actualSize.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.actualSize.value, 10)
}
function recalculateSizePlaced() {
/*
Since we changed the Actual Size in Quoted Asset, we need to fix the Size Placed in Quoted Asset
that was previously calculated with the previous value of actualSize.
*/
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value -
previousQuotedAssetActualSize
tradingEngineStage.stageQuotedAsset.sizePlaced.value =
tradingEngineStage.stageQuotedAsset.sizePlaced.value +
tradingEngineOrder.orderQuotedAsset.actualSize.value
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
}
async function feesToBePaidCalculation() {
/*
The Fees To Be Paid are the fees that will be paid once the order is 100% Filled.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
tradingEngineOrder.orderBaseAsset.feesToBePaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
100
)
break
}
case 'Limit Order': {
await applyFeePercentage(
tradingEngineOrder.orderBaseAsset.feesToBePaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
100
)
break
}
}
}
async function sizeToBeFilledCalculation() {
/*
The Size to Be Filled is the Actual Size minus the Fees to be Paid.
*/
tradingEngineOrder.orderBaseAsset.sizeToBeFilled.value =
tradingEngineOrder.orderBaseAsset.actualSize.value -
tradingEngineOrder.orderBaseAsset.feesToBePaid.value
tradingEngineOrder.orderQuotedAsset.sizeToBeFilled.value =
tradingEngineOrder.orderQuotedAsset.actualSize.value -
tradingEngineOrder.orderQuotedAsset.feesToBePaid.value
}
async function percentageFilledCalculation() {
/*
Percentage Filled Calculation: The only information we get from the exchange is the order.filled.
We know this field will go up until reaching the Size To Be Filled. Once it reaches that number
the order will be 100% filled.
*/
tradingEngineOrder.orderStatistics.percentageFilled.value = order.filled * 100 / (tradingEngineOrder.orderBaseAsset.sizeToBeFilled.value)
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 from the exchange we can not see the fees paid so we need to
calculate them ourselves. To do this it is needed to be used the Session Fees Parameters.
*/
switch (tradingEngineOrder.type) {
case 'Market Order': {
await applyFeePercentage(
tradingEngineOrder.orderBaseAsset.feesPaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.taker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
break
}
case 'Limit Order': {
await applyFeePercentage(
tradingEngineOrder.orderBaseAsset.feesPaid,
tradingEngineOrder,
tradingSystemOrder,
sessionParameters.feeStructure.config.maker,
tradingEngineOrder.orderStatistics.percentageFilled.value
)
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.sizeToBeFilled.value * tradingEngineOrder.orderStatistics.percentageFilled.value / 100
tradingEngineOrder.orderQuotedAsset.sizeFilled.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.sizeFilled.value, 10)
}
}
async function doTheAccounting(
@@ -1182,65 +665,59 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
/* Balances Update */
switch (true) {
/*
Let's remember that for Buy orders, the fees are paid in Base Asset.So we
need to substract from the balance of Quoted Asset both the size filled and
the fees, and we will receive at the balance of Base Asset just the size filled,
bacuase the exchange gets the fees in Base Asset for them.
*/
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
/* 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
}
/*
For Sell orders, the fees are being paid in Quote Asset, so we will substract
from the Base Asset balance the size filled and the fees, and we will add
to the Quoted Asset balance only the size filled, because the exchange
get those fees.
For Sell orders, the fees are being paid in Quote 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 +
previousBaseAssetFeesPaid
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 -
tradingEngineOrder.orderBaseAsset.feesPaid.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
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.sizeFilled.value -
tradingEngineOrder.orderQuotedAsset.feesPaid.value
break
}
/*
Let's remember that for Buy orders, the fees are paid in Base Asset.
*/
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
}
}
@@ -1365,12 +842,19 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
tradingEngineStage.stageQuotedAsset.sizePlaced.value = global.PRECISE(tradingEngineStage.stageQuotedAsset.sizePlaced.value, 10)
}
async function applyFeePercentage(feesNode, tradingEngineOrder, tradingSystemOrder, feePercentage, percentageFilled) {
async function applyFeePercentage(feesNodePropertyName, tradingEngineOrder, tradingSystemOrder, feePercentage, percentageFilled) {
/*
The exchange fees are taken from the Base Asset or the Quoted Asset depending if we
are buying or selling.
*/
let feesNode
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 as a result of the trade at the exchange.
*/
feesNode = tradingEngineOrder.orderBaseAsset[feesNodePropertyName]
feesNode.value =
tradingEngineOrder.orderBaseAsset.actualSize.value *
feePercentage / 100 *
@@ -1381,7 +865,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
/*
In this case the fees are taken from the Quoted Asset you receive as a result of the trade at the exchange.
*/
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
feesNode = tradingEngineOrder.orderQuotedAsset[feesNodePropertyName]
feesNode.value =
tradingEngineOrder.orderQuotedAsset.actualSize.value *
feePercentage / 100 *
percentageFilled / 100
@@ -1389,8 +874,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
}
}
tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10)
tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10)
feesNode.value = global.PRECISE(feesNode.value, 10)
feesNode.value = global.PRECISE(feesNode.value, 10)
}
async function checkOrderEvent(event, order, executionAlgorithm, executionNode) {
@@ -151,7 +151,7 @@
await writeProcessFiles()
bot.sessionHeartBeat(undefined, undefined, 'Sleeping')
} else {
bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date')
bot.sessionHeartBeat(undefined, undefined, 'Waiting for Data Mining to be up to date. No candles found at.')
callBackFunction(global.DEFAULT_RETRY_RESPONSE)
return
}
@@ -53,7 +53,6 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
/* Variables needed for heartbeat functionality */
let heartBeatDate
let previousHeartBeatDate
let firstLoopExecution = true
/*
Estimation of the Initial Candle to Process in this Run.
*/
@@ -73,7 +72,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
meaning that the dataSet needs to be updated with more up-to-date data.
*/
bot.SESSION.stop('Data is not up-to-date enough. Please start the Masters Data Mining Operation.')
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Data is not up-to-date enough. Stopping the Session now. ' ) }
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Data is not up-to-date enough. Stopping the Session now. ') }
return
}
} else {
@@ -107,7 +106,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
tradingEngine.current.episode.candle.index.value = i
/* This is the current candle the Simulation is working at. */
let candle = candles[tradingEngine.current.episode.candle.index.value]
let candle = candles[tradingEngine.current.episode.candle.index.value]
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Begin @ ' + (new Date(candle.begin)).toLocaleString()) }
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) }
@@ -117,10 +116,8 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
/* We emit a heart beat so that the UI can now where we are at the overal process.*/
heartBeat()
if (firstLoopExecution === true) {
tradingEpisodeModule.openEpisode()
firstLoopExecution = false
}
/* Opening the Episode, if needed. */
tradingEpisodeModule.openEpisode()
if (checkInitialDatetime() === false) {
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle Before the Initia Date Time @ ' + (new Date(candle.begin)).toLocaleString()) }
@@ -214,7 +211,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
closeEpisode('Final Datetime Reached')
breakLoop = true
bot.SESSION.stop('Final Datetime Reached')
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Final Datetime Reached. Stopping the Session now. ' ) }
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Final Datetime Reached. Stopping the Session now. ') }
return
}
@@ -222,7 +219,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
closeEpisode('Min or Max Balance Reached')
breakLoop = true
bot.SESSION.stop('Min or Max Balance Reached')
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Min or Max Balance Reached. Stopping the Session now. ' ) }
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[IMPORTANT] runSimulation -> Min or Max Balance Reached. Stopping the Session now. ') }
return
}
}
@@ -287,23 +284,23 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
}
/* Date only hearbeat */
if (sessionParameters.heartbeats.config.date === true && sessionParameters.heartbeats.config.candleIndex === false) {
if (sessionParameters.heartbeats.config.date === true && sessionParameters.heartbeats.config.candleIndex === false) {
hartbeatText = hartbeatText + currentDateString
bot.processHeartBeat(hartbeatText, percentage)
return
}
}
/*
When the Candle Index nees to be shown, then we can not send the hearbet
only when the dates changes, we have to send it for every candle.
It might also contain the date information.
*/
if (sessionParameters.heartbeats.config.candleIndex === true ) {
if (sessionParameters.heartbeats.config.date === true ) {
if (sessionParameters.heartbeats.config.candleIndex === true) {
if (sessionParameters.heartbeats.config.date === true) {
hartbeatText = hartbeatText + currentDateString
}
hartbeatText = hartbeatText + ' Candle # ' + tradingEngine.current.episode.candle.index.value
hartbeatText = hartbeatText + ' Candle # ' + tradingEngine.current.episode.candle.index.value
bot.processHeartBeat(hartbeatText, percentage)
}
}
@@ -420,7 +417,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, tradin
The second +1 is because we need to compare the next candle (remember that the loops allways avoid the
last candle of the dataset available.)
*/
if (tradingEngine.current.episode.candle.index.value + 1 + 1 === candles.length ) {
if (tradingEngine.current.episode.candle.index.value + 1 + 1 === candles.length) {
/*
When processing daily files, we need a mechanism to turn from one day to the next one.
That mechanism is the one implemented here. If we detect that the next candle is the last candle of
+63 -53
View File
@@ -435,48 +435,48 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let manageStage = strategy.manageStage
let phase
if (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 manageStage = strategy.manageStage
let phase
if (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
@@ -492,7 +492,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
}
}
}
function calculateTakeProfitPosition() {
/*
The position of the Take Profit (above or below the price) is needed in order to know
@@ -508,7 +508,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
}
}
}
function checkStopPhasesEvents() {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let manageStage = strategy.manageStage
@@ -516,7 +516,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
let phaseIndex
let phase
let stopLoss
if (manageStage !== undefined) {
if (manageStage.managedStopLoss !== undefined) {
parentNode = manageStage
@@ -525,10 +525,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
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) {
@@ -538,9 +538,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
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)
@@ -549,18 +549,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
tradingSystem.highlights.push(stopLoss.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(manageStage.id)
tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1)
announcementsModule.makeAnnoucements(nextPhaseEvent)
/* Reset this counter */
tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1
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]
@@ -571,9 +571,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
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)
@@ -582,7 +582,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
tradingSystem.highlights.push(stopLoss.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(manageStage.id)
let moveToPhase = moveToPhaseEvent.referenceParent
if (moveToPhase !== undefined) {
for (let q = 0; q < stopLoss.phases.length; q++) {
@@ -594,9 +594,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
continue
}
announcementsModule.makeAnnoucements(moveToPhaseEvent)
/* Reset this counter */
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
return // only one event can pass at the time
@@ -606,7 +606,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
}
}
}
function checkTakeProfitPhaseEvents() {
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
let openStage = strategy.openStage
@@ -615,7 +615,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
let phaseIndex
let phase
let takeProfit
if (manageStage !== undefined) {
if (manageStage.managedTakeProfit !== undefined) {
parentNode = manageStage
@@ -624,10 +624,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
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) {
@@ -637,9 +637,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
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)
@@ -648,18 +648,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
tradingSystem.highlights.push(takeProfit.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(manageStage.id)
tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1)
announcementsModule.makeAnnoucements(nextPhaseEvent)
/* Reset this counter */
tradingEngine.current.episode.distanceToEvent.nextPhase.value = 1
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]
@@ -670,9 +670,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
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)
@@ -681,7 +681,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
tradingSystem.highlights.push(takeProfit.id)
tradingSystem.highlights.push(parentNode.id)
tradingSystem.highlights.push(manageStage.id)
let moveToPhase = moveToPhaseEvent.referenceParent
if (moveToPhase !== undefined) {
for (let q = 0; q < takeProfit.phases.length; q++) {
@@ -693,9 +693,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
tradingSystem.errors.push([moveToPhaseEvent.id, 'This Node needs to reference a Phase.'])
continue
}
announcementsModule.makeAnnoucements(moveToPhaseEvent)
/* Reset this counter */
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
return // only one event can pass at the time
@@ -705,7 +705,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
}
}
}
function checkStopLossOrTakeProfitWasHit() {
{
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
@@ -713,7 +713,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
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 (
(
@@ -726,14 +726,14 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
)
) {
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 (
(
@@ -746,7 +746,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
)
) {
logger.write(MODULE_NAME, '[INFO] checkStopLossOrTakeProfitWasHit -> Take Profit was hit.')
tradingPositionModule.closingPosition('Take Profit')
changeStageStatus('Close Stage', 'Opening')
changeStageStatus('Manage Stage', 'Closed')
@@ -888,21 +888,28 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
function checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, stageName) {
/*
The Stage is closed when the placedSize reaches the targetSize, and the fillSize
+ feesPaid reaches the placedSize. This depends on how the user defined the stage:
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.
The Stage is closed when the fillSize + feesPaid reaches the targetSize.
Note that the comparison is made with a Rounding Factor in order to avoid rounding problems.
The Target Size is defined by the end user either in Base Asset or Quoted Asset.
Whatever the user chooses defines also the way we check if the Target was met.
Why? Because the counterparty target is estimated with the Target Rate and it
is not syncronized with the reality we later learn when the exchange tell us the
actual rate each of the orders where executed at.
For the same reason that the user does not know exactly which is the rate the orders
will actually be filled, then we can not expect that the total filled will really
reach the target size, so we introduce a rounding factor so that when it is close
enough we will consider the target to have been reached.
*/
const ROUNDING_ERROR_CORRECTION_FACTOR = 1.001
switch (tradingEngineStage.stageDefinedIn.value) {
case 'Base Asset': {
if (
tradingEngineStage.stageBaseAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR
(
tradingEngineStage.stageBaseAsset.sizeFilled.value
)
* ROUNDING_ERROR_CORRECTION_FACTOR
>=
tradingEngineStage.stageBaseAsset.targetSize.value
) {
@@ -914,7 +921,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
}
case 'Quoted Asset': {
if (
tradingEngineStage.stageQuotedAsset.sizeFilled.value * ROUNDING_ERROR_CORRECTION_FACTOR
(
tradingEngineStage.stageQuotedAsset.sizeFilled.value
)
* ROUNDING_ERROR_CORRECTION_FACTOR
>=
tradingEngineStage.stageQuotedAsset.targetSize.value
) {
+3 -1
View File
@@ -369,7 +369,9 @@
function startLiveTrading(message) {
if (bot.KEY === undefined || bot.SECRET === undefined) {
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> Key 'codeName' or 'secret' not provided. Plese check that and try again."); }
let errorMessage = "Key 'codeName' or 'secret' not provided. Plese check that and try again."
if (FULL_LOG === true) { parentLogger.write(MODULE_NAME, "[ERROR] initialize -> startLiveTrading -> " + errorMessage); }
bot.sessionError(bot.SESSION, errorMessage)
return false
}
return true