Complete Review of Trading Orders
This commit is contained in:
1 parent
7cacff86d0
commit
d3da5c220b
16 files changed
+915
-748
No files matched your search
+8
-1
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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->' +
|
||||
|
||||
File renamed without changes.
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user