Merge branch 'develop' of https://github.com/Superalgos/Superalgos into develop
This commit is contained in:
commit
f7e299084e
18 files changed
+842
-804
No files matched your search
+114
-223
@@ -777,6 +777,9 @@
|
||||
"editors": {
|
||||
"config": true
|
||||
},
|
||||
"title": [
|
||||
"Use Reference Parent"
|
||||
],
|
||||
"initialValues": {
|
||||
"config": "{ \n\"offsetX\": 0,\n \"offsetY\": 0\n }"
|
||||
},
|
||||
@@ -963,6 +966,9 @@
|
||||
"editors": {
|
||||
"config": true
|
||||
},
|
||||
"title": [
|
||||
"Use Reference Parent"
|
||||
],
|
||||
"initialValues": {
|
||||
"config": "{ \n\"offsetX\": 0,\n \"offsetY\": 0\n }"
|
||||
},
|
||||
@@ -2687,6 +2693,12 @@
|
||||
"childType": "Episode Statistics",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "distanceToEvent",
|
||||
"type": "node",
|
||||
"childType": "Distance To Event",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "candle",
|
||||
"type": "node",
|
||||
@@ -2694,9 +2706,9 @@
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "distanceToEvent",
|
||||
"name": "cycle",
|
||||
"type": "node",
|
||||
"childType": "Distance To Event",
|
||||
"childType": "Cycle",
|
||||
"autoAdd": true
|
||||
}
|
||||
]
|
||||
@@ -3214,12 +3226,6 @@
|
||||
"childType": "Hit Fail",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "hitRatio",
|
||||
"type": "node",
|
||||
"childType": "Hit Ratio",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "days",
|
||||
"type": "node",
|
||||
@@ -3227,9 +3233,9 @@
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "anualizedRateOfReturn",
|
||||
"name": "annualizedRateOfReturn",
|
||||
"type": "node",
|
||||
"childType": "Anualized Rate Of Return",
|
||||
"childType": "Annualized Rate Of Return",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
@@ -3280,12 +3286,6 @@
|
||||
"icon": "shapes-polygon-body",
|
||||
"propertyNameAtParent": "stopLoss",
|
||||
"properties": [
|
||||
{
|
||||
"name": "stopLossStage",
|
||||
"type": "node",
|
||||
"childType": "Stop Loss Stage",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "stopLossPhase",
|
||||
"type": "node",
|
||||
@@ -3340,12 +3340,6 @@
|
||||
"icon": "shapes-polygon-body",
|
||||
"propertyNameAtParent": "stopLoss",
|
||||
"properties": [
|
||||
{
|
||||
"name": "takeProfitStage",
|
||||
"type": "node",
|
||||
"childType": "Take Profit Stage",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "takeProfitPhase",
|
||||
"type": "node",
|
||||
@@ -3360,40 +3354,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Stop Loss Stage",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Configure This",
|
||||
"label": "Configure This",
|
||||
"iconPathOn": "settings",
|
||||
"iconPathOff": "settings",
|
||||
"actionFunction": "uiObject.configEditor.activate"
|
||||
},
|
||||
{
|
||||
"action": "Delete UI Object",
|
||||
"askConfirmation": true,
|
||||
"confirmationLabel": "Confirm to Delete",
|
||||
"label": "Delete This",
|
||||
"iconPathOn": "delete",
|
||||
"iconPathOff": "delete",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
}
|
||||
],
|
||||
"editors": {
|
||||
"config": true
|
||||
},
|
||||
"initialValues": {
|
||||
"config": "{\n\"initialValue\": \"\"\n}"
|
||||
},
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Stop Loss->"
|
||||
},
|
||||
"icon": "volume",
|
||||
"propertyNameAtParent": "stopLossStage"
|
||||
},
|
||||
{
|
||||
"type": "Stop Loss Phase",
|
||||
"menuItems": [
|
||||
@@ -3428,40 +3388,6 @@
|
||||
"icon": "volume",
|
||||
"propertyNameAtParent": "stopLossPhase"
|
||||
},
|
||||
{
|
||||
"type": "Take Profit Stage",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Configure This",
|
||||
"label": "Configure This",
|
||||
"iconPathOn": "settings",
|
||||
"iconPathOff": "settings",
|
||||
"actionFunction": "uiObject.configEditor.activate"
|
||||
},
|
||||
{
|
||||
"action": "Delete UI Object",
|
||||
"askConfirmation": true,
|
||||
"confirmationLabel": "Confirm to Delete",
|
||||
"label": "Delete This",
|
||||
"iconPathOn": "delete",
|
||||
"iconPathOff": "delete",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
}
|
||||
],
|
||||
"editors": {
|
||||
"config": true
|
||||
},
|
||||
"initialValues": {
|
||||
"config": "{\n\"initialValue\": \"\"\n}"
|
||||
},
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Take Profit->"
|
||||
},
|
||||
"icon": "bitmex",
|
||||
"propertyNameAtParent": "takeProfitStage"
|
||||
},
|
||||
{
|
||||
"type": "Take Profit Phase",
|
||||
"menuItems": [
|
||||
@@ -3811,9 +3737,9 @@
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "anualizedRateOfReturn",
|
||||
"name": "annualizedRateOfReturn",
|
||||
"type": "node",
|
||||
"childType": "Anualized Rate Of Return",
|
||||
"childType": "Annualized Rate Of Return",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
@@ -3900,9 +3826,9 @@
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "anualizedRateOfReturn",
|
||||
"name": "annualizedRateOfReturn",
|
||||
"type": "node",
|
||||
"childType": "Anualized Rate Of Return",
|
||||
"childType": "Annualized Rate Of Return",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
@@ -4321,7 +4247,7 @@
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Episode Statistics->Episode Base Asset->Episode Quoted Asset->"
|
||||
"compatibleTypes": "->Episode Base Asset->Episode Quoted Asset->"
|
||||
},
|
||||
"icon": "histogram",
|
||||
"propertyNameAtParent": "hitRatio"
|
||||
@@ -4531,7 +4457,7 @@
|
||||
"propertyNameAtParent": "ROI"
|
||||
},
|
||||
{
|
||||
"type": "Anualized Rate Of Return",
|
||||
"type": "Annualized Rate Of Return",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Configure This",
|
||||
@@ -4562,7 +4488,7 @@
|
||||
"compatibleTypes": "->Episode Statistics->Episode Base Asset->Episode Quoted Asset->"
|
||||
},
|
||||
"icon": "bitmex",
|
||||
"propertyNameAtParent": "anualizedRateOfReturn"
|
||||
"propertyNameAtParent": "annualizedRateOfReturn"
|
||||
},
|
||||
{
|
||||
"type": "Hit Fail",
|
||||
@@ -5353,6 +5279,12 @@
|
||||
"childType": "End Rate",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "stageDefinedIn",
|
||||
"type": "node",
|
||||
"childType": "Stage Defined In",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "stageBaseAsset",
|
||||
"type": "node",
|
||||
@@ -5430,6 +5362,12 @@
|
||||
"childType": "End Rate",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "stageDefinedIn",
|
||||
"type": "node",
|
||||
"childType": "Stage Defined In",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "stageBaseAsset",
|
||||
"type": "node",
|
||||
@@ -5922,7 +5860,7 @@
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Episode->Strategy->Position->Candle->Market Order->Limit Order->Strategy Trigger Stage->Strategy Open Stage->Strategy Manage Stage->Strategy Close Stage->"
|
||||
"compatibleTypes": "->Episode->Strategy->Position->Candle->Cycle->Market Order->Limit Order->Strategy Trigger Stage->Strategy Open Stage->Strategy Manage Stage->Strategy Close Stage->"
|
||||
},
|
||||
"icon": "monero",
|
||||
"propertyNameAtParent": "begin"
|
||||
@@ -5956,7 +5894,7 @@
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Episode->Strategy->Position->Candle->Market Order->Limit Order->Strategy Trigger Stage->Strategy Open Stage->Strategy Manage Stage->Strategy Close Stage->"
|
||||
"compatibleTypes": "->Episode->Strategy->Position->Candle->Cycle->Market Order->Limit Order->Strategy Trigger Stage->Strategy Open Stage->Strategy Manage Stage->Strategy Close Stage->"
|
||||
},
|
||||
"icon": "upbit",
|
||||
"propertyNameAtParent": "end"
|
||||
@@ -6346,6 +6284,40 @@
|
||||
"icon": "pantone",
|
||||
"propertyNameAtParent": "exitTargetRate"
|
||||
},
|
||||
{
|
||||
"type": "Stage Defined In",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Configure This",
|
||||
"label": "Configure This",
|
||||
"iconPathOn": "settings",
|
||||
"iconPathOff": "settings",
|
||||
"actionFunction": "uiObject.configEditor.activate"
|
||||
},
|
||||
{
|
||||
"action": "Delete UI Object",
|
||||
"askConfirmation": true,
|
||||
"confirmationLabel": "Confirm to Delete",
|
||||
"label": "Delete This",
|
||||
"iconPathOn": "delete",
|
||||
"iconPathOff": "delete",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
}
|
||||
],
|
||||
"editors": {
|
||||
"config": true
|
||||
},
|
||||
"initialValues": {
|
||||
"config": "{\n\"initialValue\": \"\"\n}"
|
||||
},
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Strategy Open Stage->Strategy Close Stage->"
|
||||
},
|
||||
"icon": "pantone",
|
||||
"propertyNameAtParent": "stageDefinedIn"
|
||||
},
|
||||
{
|
||||
"type": "Size",
|
||||
"menuItems": [
|
||||
@@ -6664,6 +6636,47 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Cycle",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Add Missing Children",
|
||||
"label": "Add Missing Children",
|
||||
"relatedUiObject": "Parameters",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
},
|
||||
{
|
||||
"action": "Delete UI Object",
|
||||
"askConfirmation": true,
|
||||
"confirmationLabel": "Confirm to Delete",
|
||||
"label": "Delete This",
|
||||
"iconPathOn": "delete",
|
||||
"iconPathOff": "delete",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
}
|
||||
],
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Episode->"
|
||||
},
|
||||
"icon": "qtum",
|
||||
"propertyNameAtParent": "cycle",
|
||||
"properties": [
|
||||
{
|
||||
"name": "begin",
|
||||
"type": "node",
|
||||
"childType": "Begin",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "end",
|
||||
"type": "node",
|
||||
"childType": "End",
|
||||
"autoAdd": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Exchange Orders",
|
||||
"menuItems": [
|
||||
@@ -9604,12 +9617,6 @@
|
||||
"childType": "Initial Targets",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "initialDefinition",
|
||||
"type": "node",
|
||||
"childType": "Initial Definition",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "openExecution",
|
||||
"type": "node",
|
||||
@@ -9676,48 +9683,6 @@
|
||||
],
|
||||
"propertyNameAtParent": "closeStageEvent"
|
||||
},
|
||||
{
|
||||
"type": "Initial Definition",
|
||||
"docURL": "https://docs.superalgos.org/suite-hierarchy-trading-system.html#initial-definition",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Add Missing Children",
|
||||
"label": "Add Missing Items",
|
||||
"relatedUiObject": "Initial Definition",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
},
|
||||
{
|
||||
"action": "Delete UI Object",
|
||||
"askConfirmation": true,
|
||||
"confirmationLabel": "Confirm to Delete",
|
||||
"label": "Delete This",
|
||||
"iconPathOn": "delete",
|
||||
"iconPathOff": "delete",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
}
|
||||
],
|
||||
"addLeftIcons": true,
|
||||
"level": 2,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Open Stage->"
|
||||
},
|
||||
"icon": "initial-definition",
|
||||
"properties": [
|
||||
{
|
||||
"name": "initialStopLoss",
|
||||
"type": "node",
|
||||
"childType": "Initial Stop",
|
||||
"autoAdd": true
|
||||
},
|
||||
{
|
||||
"name": "initialTakeProfit",
|
||||
"type": "node",
|
||||
"childType": "Initial Take Profit",
|
||||
"autoAdd": true
|
||||
}
|
||||
],
|
||||
"propertyNameAtParent": "initialDefinition"
|
||||
},
|
||||
{
|
||||
"type": "Initial Targets",
|
||||
"menuItems": [
|
||||
@@ -9801,43 +9766,6 @@
|
||||
],
|
||||
"propertyNameAtParent": "managedStopLoss"
|
||||
},
|
||||
{
|
||||
"type": "Initial Stop",
|
||||
"docURL": "https://docs.superalgos.org/suite-hierarchy-trading-system.html#initial-stop",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Add UI Object",
|
||||
"label": "Add Phase",
|
||||
"relatedUiObject": "Phase",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
},
|
||||
{
|
||||
"action": "Delete UI Object",
|
||||
"askConfirmation": true,
|
||||
"confirmationLabel": "Confirm to Delete",
|
||||
"label": "Delete This",
|
||||
"iconPathOn": "delete",
|
||||
"iconPathOff": "delete",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
}
|
||||
],
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Manage Stage->Initial Definition->"
|
||||
},
|
||||
"icon": "stop",
|
||||
"properties": [
|
||||
{
|
||||
"name": "phases",
|
||||
"type": "array",
|
||||
"maxItems": 1,
|
||||
"childType": "Phase",
|
||||
"autoAdd": true
|
||||
}
|
||||
],
|
||||
"propertyNameAtParent": "initialStopLoss"
|
||||
},
|
||||
{
|
||||
"type": "Phase",
|
||||
"docURL": "https://docs.superalgos.org/suite-hierarchy-trading-system.html#phase-0",
|
||||
@@ -9884,7 +9812,7 @@
|
||||
"level": 1,
|
||||
"chainedToSameType": true,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Stop->Take Profit->Initial Stop->Initial Take Profit->Phase->"
|
||||
"compatibleTypes": "->Managed Stop->Managed Take Profit->Phase->"
|
||||
},
|
||||
"icon": "placeholder",
|
||||
"properties": [
|
||||
@@ -10045,43 +9973,6 @@
|
||||
],
|
||||
"propertyNameAtParent": "managedTakeProfit"
|
||||
},
|
||||
{
|
||||
"type": "Initial Take Profit",
|
||||
"docURL": "https://docs.superalgos.org/suite-hierarchy-trading-system.html#initial-take-profit",
|
||||
"menuItems": [
|
||||
{
|
||||
"action": "Add UI Object",
|
||||
"label": "Add Phase",
|
||||
"relatedUiObject": "Phase",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
},
|
||||
{
|
||||
"action": "Delete UI Object",
|
||||
"askConfirmation": true,
|
||||
"confirmationLabel": "Confirm to Delete",
|
||||
"label": "Delete This",
|
||||
"iconPathOn": "delete",
|
||||
"iconPathOff": "delete",
|
||||
"actionFunction": "payload.onMenuItemClick"
|
||||
}
|
||||
],
|
||||
"addLeftIcons": true,
|
||||
"level": 3,
|
||||
"chainAttachesTo": {
|
||||
"compatibleTypes": "->Manage Stage->Initial Definition->"
|
||||
},
|
||||
"icon": "stage-open-take-profit",
|
||||
"properties": [
|
||||
{
|
||||
"name": "phases",
|
||||
"type": "array",
|
||||
"maxItems": 1,
|
||||
"childType": "Phase",
|
||||
"autoAdd": true
|
||||
}
|
||||
],
|
||||
"propertyNameAtParent": "initialTakeProfit"
|
||||
},
|
||||
{
|
||||
"type": "Target Size In Base Asset",
|
||||
"menuItems": [
|
||||
@@ -10914,7 +10805,7 @@
|
||||
}
|
||||
],
|
||||
"initialValues": {
|
||||
"config": "{ \n\"percentage\": 0.01\n}"
|
||||
"config": "{ \n\"percentage\": 0.1\n}"
|
||||
},
|
||||
"editors": {
|
||||
"config": true
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"Trading Engine","name":"Low Frequency","current":{"type":"Current","name":"New Current","episode":{"type":"Episode","name":"New Episode","serialNumber":{"type":"Serial Number","name":"New Serial Number","config":"{\n\"initialValue\": 0\n}","id":"5eb83933-432a-4029-8a3e-e4d8d760dd27","savedPayload":{"position":{"x":32628.925931291356,"y":29764.991484231054},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"identifier":{"type":"Identifier","name":"New Identifier","config":"{\n\"initialValue\": \"\"\n}","id":"8ba7fe0c-c5df-408e-be8a-ebde90f2a50d","savedPayload":{"position":{"x":32744.541202314154,"y":29624.04899186301},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"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}","id":"d7faf864-3fa8-4280-b7d3-a022338b0dfe","savedPayload":{"position":{"x":32885.35243415358,"y":29508.47178182837},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"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}","id":"f6722d05-77f9-4a68-ba69-38f56cdc7e91","savedPayload":{"position":{"x":33045.99145646664,"y":29422.602168012847},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"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}","id":"613903ae-45e4-4eee-a178-d6ec049490ec","savedPayload":{"position":{"x":33220.29145920026,"y":29369.7257306039},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"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}","id":"b2bfb20d-64d5-4c98-b0cd-a466654c1e6a","savedPayload":{"position":{"x":33401.452087304264,"y":29351.87056129139},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"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}","id":"2615490b-e968-4607-a6e5-009c3ae78957","savedPayload":{"position":{"x":33582.621371994755,"y":29369.721550636412},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"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}","id":"6bcc264b-a189-4aca-8397-d3520bdf4078","savedPayload":{"position":{"x":33756.91970975856,"y":29422.591759304833},"targetPosition":{"x":33401.448976596075,"y":30280.9349218038},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"episodeBaseAsset":{"type":"Episode Base Asset","name":"New Episode Base Asset","balance":{"type":"Balance","name":"Base Asset","config":"{\n\"initialValue\": 0\n}","id":"72f6a6b6-f35b-4d80-b8ad-4a4ed55f2387","savedPayload":{"position":{"x":33866.52652515162,"y":28582.72931076452},"targetPosition":{"x":33916.72751479778,"y":29509.897829277033},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"beginBalance":{"type":"Begin Balance","name":"New Begin Balance","config":"{\n\"initialValue\": 0\n}","id":"0176b616-7628-42b1-8c06-4cfb1bfd05aa","savedPayload":{"position":{"x":33999.24564731282,"y":28585.12649092692},"targetPosition":{"x":33916.72751479778,"y":29509.897829277033},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"endBalance":{"type":"End Balance","name":"New End Balance","config":"{\n\"initialValue\": 0\n}","id":"d3844a95-57a8-48f5-bfa9-e093bf815f57","savedPayload":{"position":{"x":34130.0563129769,"y":28606.19458048408},"targetPosition":{"x":33916.72751479778,"y":29509.897829277033},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":3,"distanceToParent":3,"arrangementStyle":0},"uiObject":{}}},"hits":{"type":"Hits","name":"New Hits","config":"{\n\"initialValue\": 0\n}","id":"c5eff46f-38bc-4ab9-9d07-ed6c5e161895","savedPLine truncated
|
||||
@@ -28,7 +28,6 @@ function newSessionFunctions() {
|
||||
'Trigger Stage->Trigger On Event->Trigger Off Event->Take Position Event->' +
|
||||
'Announcement->Announcement Formula->' +
|
||||
'Open Stage->' +
|
||||
'Initial Definition->Initial Stop->Initial Take Profit->' +
|
||||
'Manage Stage->' +
|
||||
'Managed Stop Loss->Managed Take Profit->' +
|
||||
'Phase->Formula->Next Phase Event->Move To Phase Event->Phase->' +
|
||||
@@ -54,8 +53,8 @@ function newSessionFunctions() {
|
||||
'Episode->' +
|
||||
'Episode Base Asset->Episode Quoted Asset->Episode Counters->Episode Statistics->' +
|
||||
'Strategies->Positions->Orders->Hits->Fails->' +
|
||||
'Profit Loss->Hit Ratio->Hit Fail->Days->ROI->Anualized Rate Of Return->User Defined Statistic->' +
|
||||
'Candle->' +
|
||||
'Profit Loss->Hit Ratio->Hit Fail->Days->ROI->Annualized Rate Of Return->User Defined Statistic->' +
|
||||
'Candle->Cycle->' +
|
||||
'Begin->End->Open->Close->Min->Max->Index->' +
|
||||
'Distance To Event->' +
|
||||
'Trigger On->Trigger Off->Take Position->Close Position->Next Phase->Move To Phase->Create Order->Cancel Order->Close Order->' +
|
||||
@@ -63,8 +62,8 @@ function newSessionFunctions() {
|
||||
'Strategy Counters->' +
|
||||
'Position->' +
|
||||
'Entry Target Rate->Exit Target Rate->' +
|
||||
'Stop Loss->Stop Loss Stage->Stop Loss Phase->Stop Loss Position->' +
|
||||
'Take Profit->Take Profit Stage->Take Profit Phase->Take Profit Position->' +
|
||||
'Stop Loss->Stop Loss Phase->Stop Loss Position->' +
|
||||
'Take Profit->Take Profit Phase->Take Profit Position->' +
|
||||
'Position Counters->' +
|
||||
'Position Statistics->Days->User Defined Statistic->' +
|
||||
'Position Base Asset->Position Quoted Asset->Entry Target Size->Exit Target Size->' +
|
||||
@@ -77,7 +76,7 @@ function newSessionFunctions() {
|
||||
'Size->Size Filled->Fees 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->' +
|
||||
'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->' +
|
||||
'Serial Number->Identifier->Begin->End->Begin Rate->End Rate->Strategy Name->Status->Exit Type->' +
|
||||
'Balance->Begin Balance->End Balance->' +
|
||||
'Index->Situation Name->Formula->Periods->'
|
||||
|
||||
@@ -94,6 +94,7 @@ function newPlotter() {
|
||||
|
||||
function initialize(pStorage, pDatetime, pTimeFrame, pCoordinateSystem, callBackFunction, pProductDefinition) {
|
||||
try {
|
||||
|
||||
/* Store the information received. */
|
||||
marketFiles = pStorage.marketFiles[0]
|
||||
dailyFiles = pStorage.dailyFiles[0]
|
||||
@@ -300,10 +301,8 @@ function newPlotter() {
|
||||
jsonifiedArray.push(record)
|
||||
previous = record
|
||||
|
||||
if (record.begin === undefined || record.end === undefined) { console.log('Could not find the property begin or end which are needed for the plotter to work.') }
|
||||
if (datetime.valueOf() >= record.begin && datetime.valueOf() <= record.end) {
|
||||
thisObject.currentRecord = record
|
||||
thisObject.container.eventHandler.raiseEvent('Current Record Changed', thisObject.currentRecord)
|
||||
if (record.begin === undefined || record.end === undefined) {
|
||||
console.log('Could not find the property begin or end which are needed for the plotter to work. productDefinition.config.codeName = ' + productDefinition.config.codeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -425,15 +424,6 @@ function newPlotter() {
|
||||
currentDate = new Date(currentDate.valueOf() + ONE_DAY_IN_MILISECONDS)
|
||||
}
|
||||
|
||||
/* Lests check if all the visible screen is going to be covered by candle-record. */
|
||||
let lowerEnd = leftDate.valueOf()
|
||||
let upperEnd = rightDate.valueOf()
|
||||
|
||||
if (records.length > 0) {
|
||||
if (records[0].begin > lowerEnd || records[records.length - 1].end < upperEnd) {
|
||||
setTimeout(recalculate, 2000)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (ERROR_LOG === true) { logger.write('[ERROR] recalculateUsingDailyFiles -> err = ' + err.stack) }
|
||||
}
|
||||
@@ -899,7 +889,9 @@ function newPlotter() {
|
||||
}
|
||||
|
||||
function onDragFinished() {
|
||||
recalculate()
|
||||
if (canvas.chartingSpace.visible === true) {
|
||||
recalculate()
|
||||
}
|
||||
}
|
||||
|
||||
function onDisplace(event) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"type":"Data Mine","name":"Masters-Trading","config":"{ \n\"codeName\": \"Masters-Trading\"\n}","sensorBots":[],"indicatorBots":[],"tradingBots":[{"type":"Trading Bot","name":"Low Frequency","config":"{\n\"codeName\": \"Low-Frequency\"\n}","processes":[{"type":"Process Definition","name":"Trading","config":"{\n \"codeName\": \"Trading\",\n \"liveTradingWaitTime\": 0,\n \"paperTradingWaitTime\": 0,\n \"fordwardTestingWaitTime\": 0,\n \"backtestingWaitTime\": 0,\n \"sessionRunWaitTime\": 5000,\n \"retryWaitTime\": 10000,\n \"framework\": {\n \"name\": \"Low-Frequency-Trading-Process\"\n }\n }","processOutput":{"type":"Process Output","name":"New Process Output","outputDatasets":[{"type":"Output Dataset","name":"Multi-Period-Market Episode Counters","id":"6fc3054e-5034-4fc7-bddf-02360e0a6acf","savedPayload":{"position":{"x":15126.709841795062,"y":14115.844903867037},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"db063d7b-db05-4bf6-941f-999e1ccb38fc"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Counters","id":"c3c2240f-514b-40de-825f-674037117d0b","savedPayload":{"position":{"x":15060.50960648652,"y":13958.543096746436},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"0764aaaf-d970-4f23-9df8-55fe5b7dd273"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Statistics","id":"1dfdfc0d-842f-4e63-a591-256ac8a06b0f","savedPayload":{"position":{"x":15000.170158581612,"y":13798.909615340754},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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","id":"39360c55-5ba8-413b-b0bd-cac3789ebf5f","savedPayload":{"position":{"x":14945.771166150153,"y":13637.156840896287},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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 Current Candle","id":"ba614415-6e4f-4979-97a5-34c72a59e67c","savedPayload":{"position":{"x":14897.386505301713,"y":13473.814926987687},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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 Current Candle","id":"1a2e3461-33e6-45b1-bcc1-c56a8c5d275c","savedPayload":{"position":{"x":14855.08212849534,"y":13309.762335468542},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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","id":"567e4dc9-4deb-41e4-9ac9-dde943fb91ba","savedPayload":{"position":{"x":14818.915848183393,"y":13144.277773603595},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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","id":"e94b9bb3-9f0c-4550-94e1-09de444780a8","savedPayload":{"position":{"x":14788.936996026083,"y":12977.582492132336},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"213b8fe8-f772-41ea-a3f5-97867be555f7"}}},{"type":"Output Dataset","name":"Multi-Period-Market Distance To Event","id":"a30e7306-7be9-41b6-Line truncated
|
||||
{"type":"Data Mine","name":"Masters-Trading","config":"{ \n\"codeName\": \"Masters-Trading\"\n}","sensorBots":[],"indicatorBots":[],"tradingBots":[{"type":"Trading Bot","name":"Low Frequency","config":"{\n\"codeName\": \"Low-Frequency\"\n}","processes":[{"type":"Process Definition","name":"Trading","config":"{\n \"codeName\": \"Trading\",\n \"liveTradingWaitTime\": 0,\n \"paperTradingWaitTime\": 0,\n \"fordwardTestingWaitTime\": 0,\n \"backtestingWaitTime\": 0,\n \"sessionRunWaitTime\": 5000,\n \"retryWaitTime\": 10000,\n \"framework\": {\n \"name\": \"Low-Frequency-Trading-Process\"\n }\n }","processOutput":{"type":"Process Output","name":"New Process Output","outputDatasets":[{"type":"Output Dataset","name":"Multi-Period-Market Episode Counters","id":"6fc3054e-5034-4fc7-bddf-02360e0a6acf","savedPayload":{"position":{"x":15126.709841795062,"y":14115.844903867037},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Market","id":"db063d7b-db05-4bf6-941f-999e1ccb38fc"}}},{"type":"Output Dataset","name":"Multi-Period-Daily Episode Counters","id":"c3c2240f-514b-40de-825f-674037117d0b","savedPayload":{"position":{"x":15060.50960648652,"y":13958.543096746436},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"0764aaaf-d970-4f23-9df8-55fe5b7dd273"}}},{"type":"Output Dataset","name":"Multi-Period-Market Episode Statistics","id":"1dfdfc0d-842f-4e63-a591-256ac8a06b0f","savedPayload":{"position":{"x":15000.170158581612,"y":13798.909615340754},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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","id":"39360c55-5ba8-413b-b0bd-cac3789ebf5f","savedPayload":{"position":{"x":14945.771166150153,"y":13637.156840896287},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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 Current Candle","id":"ba614415-6e4f-4979-97a5-34c72a59e67c","savedPayload":{"position":{"x":14897.386505301713,"y":13473.814926987687},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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 Current Candle","id":"1a2e3461-33e6-45b1-bcc1-c56a8c5d275c","savedPayload":{"position":{"x":14855.08212849534,"y":13309.762335468542},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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","id":"567e4dc9-4deb-41e4-9ac9-dde943fb91ba","savedPayload":{"position":{"x":14818.915848183393,"y":13144.277773603595},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"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","id":"e94b9bb3-9f0c-4550-94e1-09de444780a8","savedPayload":{"position":{"x":14788.936996026083,"y":12977.582492132336},"targetPosition":{"x":19338.91372893721,"y":12256.290080293493},"floatingObject":{"isPinned":false,"isFrozen":false,"isCollapsed":false,"angleToParent":2,"distanceToParent":3,"arrangementStyle":0},"uiObject":{},"referenceParent":{"type":"Dataset Definition","name":"Multi-Period-Daily","id":"213b8fe8-f772-41ea-a3f5-97867be555f7"}}},{"type":"Output Dataset","name":"Multi-Period-Market Distance To Event","id":"a30e7306-7be9-41b6-Line truncated
|
||||
@@ -442,7 +442,7 @@ function newFileCursor() {
|
||||
if (beginDateRange !== undefined) {
|
||||
let compareBeginDate = removeTime(beginDateRange)
|
||||
|
||||
if (compareTargetDate.valueOf() < compareBeginDate) {
|
||||
if (compareTargetDate.valueOf() < compareBeginDate.valueOf()) {
|
||||
controlLoop()
|
||||
return
|
||||
}
|
||||
@@ -451,7 +451,7 @@ function newFileCursor() {
|
||||
if (endDateRange !== undefined) {
|
||||
let compareEndDate = removeTime(endDateRange)
|
||||
|
||||
if (compareTargetDate.valueOf() > compareEndDate) {
|
||||
if (compareTargetDate.valueOf() >= compareEndDate.valueOf()) {
|
||||
controlLoop()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
const MODULE_NAME = 'Trading Engine'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
getNodeById: getNodeById,
|
||||
cloneValues: cloneValues,
|
||||
setCurrentCandle: setCurrentCandle,
|
||||
setCurrentCycle: setCurrentCycle,
|
||||
initializeNode: initializeNode,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
@@ -43,7 +45,10 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
updateDistanceToEventsCounters()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
|
||||
}
|
||||
|
||||
function getNodeById(NodeId) {
|
||||
@@ -59,6 +64,32 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
tradingEngine.current.episode.candle.max.value = candle.max
|
||||
}
|
||||
|
||||
function setCurrentCycle(cycle) {
|
||||
switch (cycle) {
|
||||
case 'First': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2 - 1
|
||||
break
|
||||
}
|
||||
case 'Second': {
|
||||
tradingEngine.current.episode.cycle.begin.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value / 2
|
||||
tradingEngine.current.episode.cycle.end.value =
|
||||
tradingEngine.current.episode.candle.begin.value +
|
||||
sessionParameters.timeFrame.config.value +
|
||||
sessionParameters.timeFrame.config.value * 4 / 4 - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cloneValues(originNode, destinationNode) {
|
||||
if (originNode === undefined) { return }
|
||||
if (destinationNode === undefined) { return }
|
||||
@@ -180,63 +211,5 @@ exports.newTradingEngine = function newTradingEngine(bot, logger) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateDistanceToEventsCounters() {
|
||||
/* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOn.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOn.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOff.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOff.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.closePosition.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.closePosition.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.createOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.createOrder.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.cancelOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.cancelOrder.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
const MODULE_NAME = 'Trading Episode'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
openEpisode: openEpisode,
|
||||
updateExitType: updateExitType,
|
||||
closeEpisode: closeEpisode,
|
||||
@@ -33,10 +34,14 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
resetTradingEngineDataStructure()
|
||||
updateCounters()
|
||||
updateStatistics()
|
||||
updateEnds()
|
||||
updateDistanceToEventsCounters()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resetTradingEngineDataStructure()
|
||||
}
|
||||
|
||||
function openEpisode() {
|
||||
@@ -132,12 +137,12 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
|
||||
tradingEngine.current.episode.episodeStatistics.ROI.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.ROI.value, 10)
|
||||
|
||||
/* Updating Anualized Rate Of Return */
|
||||
tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value =
|
||||
/* Updating Annualized Rate Of Return */
|
||||
tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value =
|
||||
tradingEngine.current.episode.episodeStatistics.ROI.value /
|
||||
tradingEngine.current.episode.episodeStatistics.days.value * 365
|
||||
|
||||
tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.anualizedRateOfReturn.value, 10)
|
||||
tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeStatistics.annualizedRateOfReturn.value, 10)
|
||||
|
||||
/* Updating Hit or Fail */
|
||||
if (tradingEngine.current.episode.episodeStatistics.profitLoss.value > 0) {
|
||||
@@ -197,17 +202,17 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
tradingEngine.current.episode.episodeBaseAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.hitRatio.value, 10)
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.hitRatio.value, 10)
|
||||
|
||||
/* Updating Anualized Rate Of Return */
|
||||
tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value =
|
||||
/* Updating Annualized Rate Of Return */
|
||||
tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value =
|
||||
tradingEngine.current.episode.episodeBaseAsset.ROI.value /
|
||||
tradingEngine.current.episode.episodeStatistics.days.value * 365
|
||||
|
||||
tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value =
|
||||
tradingEngine.current.episode.episodeQuotedAsset.ROI.value /
|
||||
tradingEngine.current.episode.episodeStatistics.days.value * 365
|
||||
|
||||
tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.anualizedRateOfReturn.value, 10)
|
||||
tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.anualizedRateOfReturn.value, 10)
|
||||
tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeBaseAsset.annualizedRateOfReturn.value, 10)
|
||||
tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value = global.PRECISE(tradingEngine.current.episode.episodeQuotedAsset.annualizedRateOfReturn.value, 10)
|
||||
|
||||
/* Updating Hit or Fail */
|
||||
if (tradingEngine.current.episode.episodeBaseAsset.profitLoss.value > 0) {
|
||||
@@ -221,4 +226,61 @@ exports.newTradingEpisode = function newTradingEpisode(bot, logger, tradingEngin
|
||||
tradingEngine.current.episode.episodeQuotedAsset.hitFail.value = 'Fail'
|
||||
}
|
||||
}
|
||||
|
||||
function updateDistanceToEventsCounters() {
|
||||
/* Keeping Distance Counters Up-to-date while avoinding counting before the first event happens. */
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOn.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOn.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOff.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.triggerOff.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.closePosition.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.closePosition.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.nextPhase.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.createOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.createOrder.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.cancelOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.cancelOrder.value++
|
||||
}
|
||||
|
||||
if (
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value > 0
|
||||
) {
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE
|
||||
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
runExecution: runExecution,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
@@ -41,6 +42,10 @@ exports.newTradingExecution = function newTradingExecution(bot, logger, tradingE
|
||||
tradingOrdersModule.mantain()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
tradingOrdersModule.reset()
|
||||
}
|
||||
|
||||
async function runExecution(
|
||||
executionNode,
|
||||
tradingEngineStage
|
||||
|
||||
@@ -6,6 +6,7 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
checkOrders: checkOrders,
|
||||
initialize: initialize,
|
||||
finalize: finalize
|
||||
@@ -45,6 +46,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
mantainReset(true, false)
|
||||
}
|
||||
|
||||
function reset() {
|
||||
mantainReset(false, true)
|
||||
}
|
||||
|
||||
function mantainReset(mantain, reset) {
|
||||
|
||||
if (tradingEngine.current.strategy.index.value === tradingEngine.current.strategy.index.config.initialValue) { return }
|
||||
|
||||
@@ -53,42 +62,46 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
executionNode = stageNode.openExecution
|
||||
mantainExecutionNode(executionNode, tradingEngine.current.strategyOpenStage.status.value)
|
||||
processExecutionNode(executionNode, tradingEngine.current.strategyOpenStage.status.value)
|
||||
|
||||
stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
|
||||
executionNode = stageNode.closeExecution
|
||||
mantainExecutionNode(executionNode, tradingEngine.current.strategyCloseStage.status.value)
|
||||
processExecutionNode(executionNode, tradingEngine.current.strategyCloseStage.status.value)
|
||||
|
||||
function mantainExecutionNode(executionNode, stageStatus) {
|
||||
function processExecutionNode(executionNode, stageStatus) {
|
||||
|
||||
mantainExecutionAlgorithms(executionNode)
|
||||
preocessExecutionAlgorithms(executionNode)
|
||||
|
||||
function mantainExecutionAlgorithms(executionNode) {
|
||||
function preocessExecutionAlgorithms(executionNode) {
|
||||
for (let i = 0; i < executionNode.executionAlgorithms.length; i++) {
|
||||
let executionAlgorithm = executionNode.executionAlgorithms[i]
|
||||
mantainOrders(executionAlgorithm.marketBuyOrders)
|
||||
mantainOrders(executionAlgorithm.marketSellOrders)
|
||||
mantainOrders(executionAlgorithm.limitBuyOrders)
|
||||
mantainOrders(executionAlgorithm.limitSellOrders)
|
||||
processOrders(executionAlgorithm.marketBuyOrders)
|
||||
processOrders(executionAlgorithm.marketSellOrders)
|
||||
processOrders(executionAlgorithm.limitBuyOrders)
|
||||
processOrders(executionAlgorithm.limitSellOrders)
|
||||
}
|
||||
}
|
||||
|
||||
function mantainOrders(orders) {
|
||||
function processOrders(orders) {
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
let tradingSystemOrder = orders[i]
|
||||
if (tradingSystemOrder.referenceParent === undefined) { continue }
|
||||
if (tradingSystemOrder.referenceParent === undefined) {
|
||||
badDefinitionUnhandledException(undefined, 'tradingSystemOrder.referenceParent === undefined', tradingSystemOrder)
|
||||
}
|
||||
let tradingEngineOrder = tradingEngineModule.getNodeById(tradingSystemOrder.referenceParent.id)
|
||||
try {
|
||||
if (tradingEngineOrder.status === undefined) { continue }
|
||||
} catch (err) {
|
||||
console.log(err.stack)
|
||||
|
||||
if (tradingEngineOrder.status === undefined) {
|
||||
badDefinitionUnhandledException(undefined, 'tradingEngineOrder.status === undefined', tradingEngineOrder)
|
||||
}
|
||||
|
||||
|
||||
updateCounters(tradingEngineOrder)
|
||||
updateEnds(tradingEngineOrder)
|
||||
updateStatistics(tradingEngineOrder)
|
||||
resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus)
|
||||
if (mantain === true) {
|
||||
updateCounters(tradingEngineOrder)
|
||||
updateEnds(tradingEngineOrder)
|
||||
updateStatistics(tradingEngineOrder)
|
||||
}
|
||||
if (reset === true) {
|
||||
resetTradingEngineDataStructure(tradingEngineOrder, tradingSystemOrder, stageStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,6 +164,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
/* Cancel the order at the Exchange, if needed. */
|
||||
await exchangeCancelOrder(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, 'Cancel Event')
|
||||
|
||||
updateEndsWithCycle(tradingEngineOrder)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -216,8 +231,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
/* Create Order Procedure */
|
||||
tradingEngineOrder.status.value = 'Open'
|
||||
tradingEngineOrder.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngineOrder.begin.value = tradingEngine.current.episode.candle.begin.value
|
||||
tradingEngineOrder.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngineOrder.begin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
tradingEngineOrder.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngineOrder.serialNumber.value = tradingEngine.current.episode.episodeCounters.orders.value
|
||||
tradingEngineOrder.orderName.value = tradingSystemOrder.name
|
||||
tradingEngineOrder.algorithmName.value = executionAlgorithm.name
|
||||
@@ -253,24 +268,42 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
if (tradingSystemOrder.config.percentageOfAlgorithmSize === undefined) { badDefinitionUnhandledException(undefined, 'tradingSystemOrder.config.percentageOfAlgorithmSize === undefined', tradingSystemOrder) }
|
||||
if (isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true) { badDefinitionUnhandledException(undefined, 'isNaN(tradingSystemOrder.config.percentageOfAlgorithmSize) === true', tradingSystemOrder) }
|
||||
|
||||
/* Size in Base Asset */
|
||||
tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100
|
||||
/*
|
||||
The Size calculation depends on how the user defined the size of the position.
|
||||
The user could have defined the size of the position in Base Asset or Quoted Asset.
|
||||
*/
|
||||
switch (tradingEngineStage.stageDefinedIn.value) {
|
||||
case 'Base Asset': {
|
||||
/* Size in Base Asset */
|
||||
tradingEngineOrder.orderBaseAsset.size.value = algorithmSizeInBaseAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100
|
||||
|
||||
/* Size in Quoted Asset */
|
||||
tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100
|
||||
/* Check that the Size calculated would not surpass Stage Target Size */
|
||||
if (
|
||||
tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value >
|
||||
tradingEngineStage.stageBaseAsset.targetSize.value
|
||||
) {
|
||||
tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value
|
||||
}
|
||||
|
||||
/* Check that the Size calculated would not surpass Stage Target Size */
|
||||
if (
|
||||
tradingEngineOrder.orderBaseAsset.size.value + tradingEngineStage.stageBaseAsset.sizePlaced.value >
|
||||
tradingEngineStage.stageBaseAsset.targetSize.value
|
||||
) {
|
||||
tradingEngineOrder.orderBaseAsset.size.value = tradingEngineStage.stageBaseAsset.targetSize.value - tradingEngineStage.stageBaseAsset.sizePlaced.value
|
||||
}
|
||||
if (
|
||||
tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value >
|
||||
tradingEngineStage.stageQuotedAsset.targetSize.value
|
||||
) {
|
||||
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value
|
||||
/* Size in Quoted Asset */
|
||||
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineOrder.orderBaseAsset.size.value * tradingEngineOrder.rate.value
|
||||
break
|
||||
}
|
||||
case 'Quoted Asset': {
|
||||
/* Size in Quoted Asset */
|
||||
tradingEngineOrder.orderQuotedAsset.size.value = algorithmSizeInQuotedAsset * tradingSystemOrder.config.percentageOfAlgorithmSize / 100
|
||||
|
||||
if (
|
||||
tradingEngineOrder.orderQuotedAsset.size.value + tradingEngineStage.stageQuotedAsset.sizePlaced.value >
|
||||
tradingEngineStage.stageQuotedAsset.targetSize.value
|
||||
) {
|
||||
tradingEngineOrder.orderQuotedAsset.size.value = tradingEngineStage.stageQuotedAsset.targetSize.value - tradingEngineStage.stageQuotedAsset.sizePlaced.value
|
||||
}
|
||||
|
||||
/* Size in Base Asset */
|
||||
tradingEngineOrder.orderBaseAsset.size.value = tradingEngineOrder.orderQuotedAsset.size.value / tradingEngineOrder.rate.value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.size.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.size.value, 10)
|
||||
@@ -333,8 +366,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
let previousQuotedAssetFeesPaid = tradingEngineOrder.orderQuotedAsset.feesPaid.value
|
||||
|
||||
actualRateSimulation()
|
||||
feesPaidSimulation()
|
||||
percentageFilledSimulation()
|
||||
feesPaidSimulation()
|
||||
sizeFilledSimulation()
|
||||
|
||||
doTheAccounting(
|
||||
@@ -397,67 +430,6 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
tradingEngineOrder.orderStatistics.actualRate.value = global.PRECISE(tradingEngineOrder.orderStatistics.actualRate.value, 10)
|
||||
}
|
||||
|
||||
function feesPaidSimulation() {
|
||||
/* Fees Paid Simulation */
|
||||
let calculatedBasedOnTradingSystem = false
|
||||
|
||||
/* Based on the Trading System Definition */
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
|
||||
if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) {
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderBaseAsset.size.value *
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100
|
||||
|
||||
calculatedBasedOnTradingSystem = true
|
||||
}
|
||||
|
||||
if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) {
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderQuotedAsset.size.value *
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100
|
||||
|
||||
calculatedBasedOnTradingSystem = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Based on the Session Parameters Definition */
|
||||
if (calculatedBasedOnTradingSystem === false) {
|
||||
/* Fees are simulated based on the Session Paremeters */
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderBaseAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.taker / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderQuotedAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.taker / 100
|
||||
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderBaseAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.maker / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderQuotedAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.maker / 100
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10)
|
||||
}
|
||||
|
||||
function percentageFilledSimulation() {
|
||||
/*
|
||||
Order Filling Simulation: the way we are going to handle this is the following.
|
||||
@@ -481,6 +453,14 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Market Buy Order': {
|
||||
orderWasHit = true
|
||||
break
|
||||
}
|
||||
case 'Market Sell Order': {
|
||||
orderWasHit = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (orderWasHit === true) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedPartialFill !== undefined) {
|
||||
@@ -507,10 +487,86 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
/* Initialize this */
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
|
||||
|
||||
updateEndsWithCycle(tradingEngineOrder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function feesPaidSimulation() {
|
||||
/*
|
||||
The way the fess paid is 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.
|
||||
*/
|
||||
let calculatedBasedOnTradingSystem = false
|
||||
|
||||
/* Based on the Trading System Definition */
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid !== undefined) {
|
||||
if (tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage !== undefined) {
|
||||
if (tradingEngineOrder.orderBaseAsset.feesPaid.value === tradingEngineOrder.orderBaseAsset.feesPaid.config.initialValue) {
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderBaseAsset.size.value *
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
calculatedBasedOnTradingSystem = true
|
||||
}
|
||||
|
||||
if (tradingEngineOrder.orderQuotedAsset.feesPaid.value === tradingEngineOrder.orderQuotedAsset.feesPaid.config.initialValue) {
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderQuotedAsset.size.value *
|
||||
tradingSystemOrder.simulatedExchangeEvents.simulatedFeesPaid.config.percentage / 100 *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
calculatedBasedOnTradingSystem = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Based on the Session Parameters Definition */
|
||||
if (calculatedBasedOnTradingSystem === false) {
|
||||
/* Fees are simulated based on the Session Paremeters */
|
||||
switch (tradingEngineOrder.type) {
|
||||
case 'Market Order': {
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderBaseAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.taker / 100 *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderQuotedAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.taker / 100 *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
break
|
||||
}
|
||||
case 'Limit Order': {
|
||||
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderBaseAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.maker / 100 *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value =
|
||||
tradingEngineOrder.orderQuotedAsset.size.value *
|
||||
bot.SESSION.parameters.feeStructure.config.maker / 100 *
|
||||
tradingEngineOrder.orderStatistics.percentageFilled.value / 100
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
tradingEngineOrder.orderBaseAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderBaseAsset.feesPaid.value, 10)
|
||||
tradingEngineOrder.orderQuotedAsset.feesPaid.value = global.PRECISE(tradingEngineOrder.orderQuotedAsset.feesPaid.value, 10)
|
||||
}
|
||||
|
||||
function sizeFilledSimulation() {
|
||||
/* Size Filled */
|
||||
tradingEngineOrder.orderBaseAsset.sizeFilled.value =
|
||||
@@ -563,6 +619,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
/* Initialize this */
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
|
||||
|
||||
updateEndsWithCycle(tradingEngineOrder)
|
||||
}
|
||||
if (order.remaining > 0 && order.status === AT_EXCHANGE_STATUS.CLOSED) {
|
||||
|
||||
@@ -572,6 +630,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
/* Initialize this */
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
|
||||
|
||||
updateEndsWithCycle(tradingEngineOrder)
|
||||
}
|
||||
if (order.status === AT_EXCHANGE_STATUS.CANCELLED) {
|
||||
|
||||
@@ -581,6 +641,8 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
|
||||
/* Initialize this */
|
||||
tradingEngine.current.episode.distanceToEvent.closeOrder.value = 1
|
||||
|
||||
updateEndsWithCycle(tradingEngineOrder)
|
||||
}
|
||||
|
||||
syncWithExchange(tradingEngineStage, tradingSystemOrder, tradingEngineOrder, order)
|
||||
@@ -891,6 +953,10 @@ exports.newTradingOrders = function newTradingOrders(bot, logger, tradingEngineM
|
||||
}
|
||||
}
|
||||
|
||||
function updateEndsWithCycle(tradingEngineOrder) {
|
||||
tradingEngineOrder.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
}
|
||||
|
||||
function updateCounters(tradingEngineOrder) {
|
||||
if (tradingEngineOrder.status.value === 'Open') {
|
||||
tradingEngineOrder.orderCounters.periods.value++
|
||||
|
||||
@@ -5,6 +5,7 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
const MODULE_NAME = 'Trading Position'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
openPosition: openPosition,
|
||||
closingPosition: closingPosition,
|
||||
closePosition: closePosition,
|
||||
@@ -39,34 +40,39 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
resetTradingEngineDataStructure()
|
||||
updateCounters()
|
||||
updateEnds()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resetTradingEngineDataStructure()
|
||||
}
|
||||
|
||||
function openPosition(situationName) {
|
||||
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
tradingEngine.current.position.status.value = 'Open'
|
||||
tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value
|
||||
tradingEngine.current.position.serialNumber.value = tradingEngine.current.episode.episodeCounters.positions.value + 1
|
||||
tradingEngine.current.position.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.position.begin.value = tradingEngine.current.episode.candle.begin.value
|
||||
tradingEngine.current.position.begin.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.position.beginRate.value = tradingEngine.current.episode.candle.close.value
|
||||
tradingEngine.current.position.positionBaseAsset.beginBalance.value = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.beginBalance.value = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
tradingEngine.current.position.situationName.value = situationName
|
||||
|
||||
/* Initializing Stop and Take Profit Stage / Phase */
|
||||
tradingEngine.current.position.stopLoss.stopLossStage.value = 'Open Stage'
|
||||
tradingEngine.current.position.takeProfit.takeProfitStage.value = 'Open Stage'
|
||||
tradingEngine.current.position.stopLoss.stopLossPhase.value = 0
|
||||
tradingEngine.current.position.takeProfit.takeProfitPhase.value = 0
|
||||
/* Initializing Stop and Take Phase */
|
||||
tradingEngine.current.position.stopLoss.stopLossPhase.value = 1
|
||||
tradingEngine.current.position.takeProfit.takeProfitPhase.value = 1
|
||||
|
||||
/* Updating Episode Counters */
|
||||
tradingEngine.current.episode.episodeCounters.positions.value++
|
||||
|
||||
/* Inicializing this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.takePosition.value = 1
|
||||
|
||||
/* Remember the balance we had before taking the position to later calculate profit or loss */
|
||||
tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
}
|
||||
|
||||
function closingPosition(exitType) {
|
||||
@@ -98,47 +104,45 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
tradingEngine.current.position.takeProfit.value = formulas.get(formulaId)
|
||||
}
|
||||
|
||||
function updateStopLoss(phase, stage) {
|
||||
function updateStopLoss(phase) {
|
||||
tradingEngine.current.position.stopLoss.stopLossPhase.value = phase
|
||||
tradingEngine.current.position.stopLoss.stopLossStage.value = stage
|
||||
}
|
||||
|
||||
function updateTakeProfit(phase, stage) {
|
||||
function updateTakeProfit(phase) {
|
||||
tradingEngine.current.position.takeProfit.takeProfitPhase.value = phase
|
||||
tradingEngine.current.position.takeProfit.takeProfitStage.value = stage
|
||||
}
|
||||
|
||||
function initialTargets(stageNode) {
|
||||
function initialTargets(tradingSystemStageNode, tradingEngineStageNode) {
|
||||
|
||||
if (stageNode.initialTargets === undefined) {
|
||||
if (tradingSystemStageNode.initialTargets === undefined) {
|
||||
const message = 'Stage without Initial Targets node. Add one please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode)
|
||||
}
|
||||
|
||||
setTargetRate()
|
||||
setTargetSize()
|
||||
|
||||
function setTargetRate() {
|
||||
if (stageNode.initialTargets.targetRate === undefined) {
|
||||
if (tradingSystemStageNode.initialTargets.targetRate === undefined) {
|
||||
const message = 'Target Rate Node not Found. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets)
|
||||
}
|
||||
if (stageNode.initialTargets.targetRate.formula === undefined) {
|
||||
if (tradingSystemStageNode.initialTargets.targetRate.formula === undefined) {
|
||||
const message = 'Formula of Target Rate Node not Found. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate)
|
||||
}
|
||||
|
||||
let value = tradingSystem.formulas.get(stageNode.initialTargets.targetRate.formula.id)
|
||||
let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetRate.formula.id)
|
||||
if (value === undefined) {
|
||||
const message = 'Target Rate can not be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate.formula)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula)
|
||||
}
|
||||
if (isNaN(value)) {
|
||||
const message = 'Target Rate must be a number. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetRate.formula)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetRate.formula)
|
||||
}
|
||||
|
||||
switch (stageNode.type) {
|
||||
switch (tradingSystemStageNode.type) {
|
||||
case 'Open Stage': {
|
||||
tradingEngine.current.position.entryTargetRate.value = global.PRECISE(value, 10)
|
||||
break
|
||||
@@ -153,27 +157,27 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
function setTargetSize() {
|
||||
/* Basic Validation */
|
||||
if (
|
||||
stageNode.initialTargets.targetSizeInBaseAsset !== undefined &&
|
||||
stageNode.initialTargets.targetSizeInQuotedAsset !== undefined
|
||||
tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined &&
|
||||
tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined
|
||||
) {
|
||||
const message = 'Only Target Size In Base Asset or Target Size In Quoted Asset is allowed. Remove one of them please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode)
|
||||
}
|
||||
|
||||
/* Position In Base Asset */
|
||||
if (stageNode.initialTargets.targetSizeInBaseAsset !== undefined) {
|
||||
if (stageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) {
|
||||
let value = tradingSystem.formulas.get(stageNode.initialTargets.targetSizeInBaseAsset.formula.id)
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset !== undefined) {
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula !== undefined) {
|
||||
let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula.id)
|
||||
if (value === undefined) {
|
||||
const message = 'Target Size In Base Asset cannot be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
}
|
||||
if (value === undefined) {
|
||||
const message = 'Target Size In Base Asset cannot be zero. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset.formula)
|
||||
}
|
||||
|
||||
switch (stageNode.type) {
|
||||
switch (tradingSystemStageNode.type) {
|
||||
case 'Open Stage': {
|
||||
tradingEngine.current.position.positionBaseAsset.entryTargetSize.value = global.PRECISE(value, 10)
|
||||
tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value =
|
||||
@@ -187,25 +191,28 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember how the end user defined this stage. */
|
||||
tradingEngineStageNode.stageDefinedIn.value = 'Base Asset'
|
||||
} else {
|
||||
const message = 'You need to specify a Formula for this.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInBaseAsset)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInBaseAsset)
|
||||
}
|
||||
}
|
||||
|
||||
/* Position In Quoted Asset */
|
||||
if (stageNode.initialTargets.targetSizeInQuotedAsset !== undefined) {
|
||||
if (stageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) {
|
||||
let value = tradingSystem.formulas.get(stageNode.initialTargets.targetSizeInQuotedAsset.formula.id)
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset !== undefined) {
|
||||
if (tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula !== undefined) {
|
||||
let value = tradingSystem.formulas.get(tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula.id)
|
||||
if (value === undefined) {
|
||||
const message = 'Target Size In Quoted Asset cannot be undefined. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
}
|
||||
if (value === undefined) {
|
||||
const message = 'Target Size In Quoted Asset cannot be zero. Fix this please.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
}
|
||||
switch (stageNode.type) {
|
||||
switch (tradingSystemStageNode.type) {
|
||||
case 'Open Stage': {
|
||||
tradingEngine.current.position.positionQuotedAsset.entryTargetSize.value = global.PRECISE(value, 10)
|
||||
tradingEngine.current.position.positionBaseAsset.entryTargetSize.value =
|
||||
@@ -219,9 +226,12 @@ exports.newTradingPosition = function newTradingPosition(bot, logger, tradingEng
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember how the end user defined this stage. */
|
||||
tradingEngineStageNode.stageDefinedIn.value = 'Quoted Asset'
|
||||
} else {
|
||||
const errorText = 'You need to specify a Formula for this.'
|
||||
badDefinitionUnhandledException(undefined, message, stageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
badDefinitionUnhandledException(undefined, message, tradingSystemStageNode.initialTargets.targetSizeInQuotedAsset.formula)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
outputDatasetsMap = undefined
|
||||
}
|
||||
|
||||
function appendRecords() {
|
||||
function appendRecords(cycle) {
|
||||
/*
|
||||
Here we add records to the output files. At the product config property nodePath
|
||||
we have a pointer to the node that have the information we need to extract.
|
||||
@@ -53,6 +53,14 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
}
|
||||
|
||||
function persistRecords() {
|
||||
/*
|
||||
There are a few products configured to be saved only at an specific cycle.
|
||||
*/
|
||||
if (product.config.saveAtCycle !== undefined) {
|
||||
if (cycle !== product.config.saveAtCycle) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean the file from information of previous executions */
|
||||
pruneOutputFile(product, outputDatasetArray)
|
||||
@@ -329,7 +337,12 @@ exports.newTradingRecords = function newTradingRecords(bot, logger) {
|
||||
let end = record[j]
|
||||
if (end >= tradingEngine.current.episode.candle.end.value) {
|
||||
outputFile.splice(i, 1)
|
||||
pruneOutputFile(product, outputFile)
|
||||
/*
|
||||
This will execute the next prune call in the next iteration of the NodeJs event loop
|
||||
allowing for other callbacks to be executed. It also prevents the error
|
||||
'Maximum call stack size exceeded', since the call is not placed at the call stack.
|
||||
*/
|
||||
setImmediate(pruneOutputFile, product, outputFile)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,9 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
if (FULL_LOG === true) { logger.write(MODULE_NAME, '[INFO] runSimulation -> loop -> Candle End @ ' + (new Date(candle.end)).toLocaleString()) }
|
||||
|
||||
tradingEngineModule.setCurrentCandle(candle) // We move the current candle we are standing at, to the trading engine data structure to make it available to anyone, including conditions and formulas.
|
||||
|
||||
/* We emit a heart beat so that the UI can now where we are at the overal process.*/
|
||||
heartBeat()
|
||||
|
||||
if (firstLoopExecution === true) {
|
||||
tradingEpisodeModule.openEpisode()
|
||||
@@ -131,22 +134,48 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
return
|
||||
}
|
||||
|
||||
heartBeat()
|
||||
positionChartAtCurrentCandle()
|
||||
|
||||
/* The chart was recalculated based on the current candle. */
|
||||
tradingSystemModule.updateChart(chart)
|
||||
|
||||
/* Do the stuff needed previous to the run */
|
||||
/*
|
||||
Do the stuff needed previous to the run like
|
||||
Episode Counters and Statistics update
|
||||
*/
|
||||
tradingSystemModule.mantain()
|
||||
|
||||
/* Episode Counters and Statistics update */
|
||||
tradingEpisodeModule.mantain()
|
||||
|
||||
/* Run your maintaince procedure */
|
||||
tradingEngineModule.mantain()
|
||||
|
||||
/* Run one cycle of the Trading System*/
|
||||
/* Reset Data Structures */
|
||||
tradingSystemModule.reset()
|
||||
tradingEpisodeModule.reset()
|
||||
tradingEngineModule.reset()
|
||||
|
||||
/*
|
||||
Run the first cycle of the Trading System. In this first cycle we
|
||||
give some room so that orders can be canceled or filled and we can
|
||||
write those records into the output memory.
|
||||
*/
|
||||
tradingEngineModule.setCurrentCycle('First')
|
||||
await tradingSystemModule.run()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords('First')
|
||||
|
||||
/* Reset Data Structures */
|
||||
tradingSystemModule.reset()
|
||||
tradingEpisodeModule.reset()
|
||||
tradingEngineModule.reset()
|
||||
|
||||
/*
|
||||
Run the second cycle of the Trading System. During this second run
|
||||
some new orders might be created at slots freed up during the first
|
||||
run. This allows for example for an Limit Order to be cancelled during the
|
||||
first run, and the same Limit Order definition to spawn a new order
|
||||
without the need to wait until the next candle.
|
||||
*/
|
||||
tradingEngineModule.setCurrentCycle('Second')
|
||||
await tradingSystemModule.run()
|
||||
|
||||
controlLoop()
|
||||
@@ -171,11 +200,16 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
if (tradingEngine.current.episode.candle.index.value + 1 < candles.length) {
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords()
|
||||
tradingRecordsModule.appendRecords('Second')
|
||||
|
||||
tradingEngine.current.episode.candle.index.value++
|
||||
|
||||
setImmediate(loop) // This will execute the next loop in the next iteration of the NodeJs event loop allowing for other callbacks to be executed.
|
||||
/*
|
||||
This will execute the next loop in the next iteration of the NodeJs event loop
|
||||
allowing for other callbacks to be executed. It also prevents the error
|
||||
'Maximum call stack size exceeded', since the call is not placed at the call stack.
|
||||
*/
|
||||
setImmediate(loop)
|
||||
} else {
|
||||
updateEpisode('All Candles Processed')
|
||||
}
|
||||
@@ -186,7 +220,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
tradingEpisodeModule.closeEpisode()
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords()
|
||||
tradingRecordsModule.appendRecords('Second')
|
||||
|
||||
afterLoop()
|
||||
}
|
||||
@@ -195,7 +229,7 @@ exports.newTradingSimulation = function newTradingSimulation(bot, logger, UTILIT
|
||||
tradingEpisodeModule.updateExitType(exitType)
|
||||
|
||||
/* Add new records to the process output */
|
||||
tradingRecordsModule.appendRecords()
|
||||
tradingRecordsModule.appendRecords('Second')
|
||||
|
||||
afterLoop()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
let thisObject = {
|
||||
updateChart: updateChart,
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
runTriggerStage: runTriggerStage,
|
||||
runOpenStage: runOpenStage,
|
||||
runManageStage: runManageStage,
|
||||
@@ -88,11 +89,18 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingStrategyModule.mantain()
|
||||
tradingExecutionModule.mantain()
|
||||
|
||||
resetTradingEngineDataStructure()
|
||||
updateCounters()
|
||||
updateEnds()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
tradingPositionModule.reset()
|
||||
tradingStrategyModule.reset()
|
||||
tradingExecutionModule.reset()
|
||||
|
||||
resetTradingEngineDataStructure()
|
||||
}
|
||||
|
||||
function runTriggerStage() {
|
||||
/*
|
||||
We check if we will be triggering on, off or taking position.
|
||||
@@ -251,149 +259,119 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
async function runOpenStage() {
|
||||
/* Abort Open Stage Check */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') {
|
||||
/*
|
||||
if the Close stage is opened while the open stage is still open that means that
|
||||
we need to stop placing orders, check what happened to the orders already placed,
|
||||
and cancel all unfilled limit orders. We call this the Closing status of the Open Stage.
|
||||
*/
|
||||
changeStageStatus('Open Stage', 'Closing')
|
||||
}
|
||||
|
||||
/* Opening Status Procedure */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') {
|
||||
checkIfWeNeedToAbortTheStage()
|
||||
runWhenStatusIsOpening()
|
||||
await runWhenStatusIsOpen()
|
||||
await runWhenStatusIsClosing()
|
||||
|
||||
/* This procedure is intended to run only once */
|
||||
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
let executionNode = stageNode.openExecution
|
||||
|
||||
/* Reset the Exchange Orders data structure to its initial value */
|
||||
tradingEngineModule.initializeNode(tradingEngine.exchangeOrders)
|
||||
|
||||
tradingSystem.evalConditions(stageNode, 'Initial Definition')
|
||||
tradingSystem.evalFormulas(stageNode, 'Initial Definition')
|
||||
|
||||
/* Remember the balance we had before taking the position to later calculate profit or loss */
|
||||
tradingEngine.current.position.positionBaseAsset.beginBalance = tradingEngine.current.episode.episodeBaseAsset.balance.value
|
||||
tradingEngine.current.position.positionQuotedAsset.beginBalance = tradingEngine.current.episode.episodeQuotedAsset.balance.value
|
||||
|
||||
/* Entry Position size and rate */
|
||||
tradingSystem.evalFormulas(stageNode, 'Initial Targets')
|
||||
tradingPositionModule.initialTargets(stageNode)
|
||||
initializeStageTargetSize()
|
||||
|
||||
/* Check Execution at open stage node */
|
||||
tradingSystem.evalConditions(stageNode, 'Open Execution')
|
||||
tradingSystem.evalFormulas(stageNode, 'Open Execution')
|
||||
|
||||
await tradingExecutionModule.runExecution(
|
||||
executionNode,
|
||||
tradingEngine.current.strategyOpenStage
|
||||
)
|
||||
|
||||
/* From here on, the state is officialy Open */
|
||||
changeStageStatus('Open Stage', 'Open')
|
||||
return
|
||||
}
|
||||
|
||||
/* Open Status Procedure */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Open') {
|
||||
/*
|
||||
While the Open Stage is Open, we do our regular stuff: place orders and check
|
||||
what happened to the orders already placed.
|
||||
*/
|
||||
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
let executionNode = stageNode.openExecution
|
||||
|
||||
/* Every Loop Cycle Activity */
|
||||
tradingSystem.evalConditions(stageNode, 'Open Execution')
|
||||
tradingSystem.evalFormulas(stageNode, 'Open Execution')
|
||||
|
||||
await tradingExecutionModule.runExecution(
|
||||
executionNode,
|
||||
tradingEngine.current.strategyOpenStage
|
||||
)
|
||||
/*
|
||||
The Open is finished when the placedSize reaches the targetSize in any of the
|
||||
stage assets, and the fillSize + feesPaid reaches the placedSize also in any
|
||||
of the stage Assets.
|
||||
This can happens at any time when we update the sizeFilled and the feesPaid values
|
||||
when we see at the exchange that orders were filled.
|
||||
*/
|
||||
if (
|
||||
(
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced >=
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.targetSize
|
||||
|
||||
||
|
||||
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced >=
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.targetSize
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value
|
||||
|
||||
||
|
||||
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value
|
||||
)
|
||||
) {
|
||||
changeStageStatus('Open Stage', 'Closed', 'Position Size Filled')
|
||||
} else {
|
||||
/* Check the Close Stage Event */
|
||||
tradingSystem.evalConditions(stageNode, 'Close Stage Event')
|
||||
if (checkStopStageEvent(stageNode) === true) {
|
||||
changeStageStatus('Open Stage', 'Closing', 'Close Stage Event')
|
||||
}
|
||||
function checkIfWeNeedToAbortTheStage() {
|
||||
/* Abort Open Stage Check */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Open' && tradingEngine.current.strategyCloseStage.status.value === 'Open') {
|
||||
/*
|
||||
if the Close stage is opened while the open stage is still open that means that
|
||||
we need to stop placing orders, check what happened to the orders already placed,
|
||||
and cancel all unfilled limit orders. We call this the Closing status of the Open Stage.
|
||||
*/
|
||||
changeStageStatus('Open Stage', 'Closing')
|
||||
}
|
||||
}
|
||||
|
||||
/* Closing Status Procedure */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') {
|
||||
/*
|
||||
During the closing stage status, we do not place new orders, just check
|
||||
if the ones placed were filled, and cancel the ones not filled.
|
||||
*/
|
||||
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
let executionNode = stageNode.openExecution
|
||||
function runWhenStatusIsOpening() {
|
||||
/* Opening Status Procedure */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Opening') {
|
||||
|
||||
/*
|
||||
Check if there are unfilled orders, we will check if they were executed,
|
||||
and cancel the ones that were not.
|
||||
*/
|
||||
tradingSystem.evalConditions(stageNode, 'Open Execution')
|
||||
tradingSystem.evalFormulas(stageNode, 'Open Execution')
|
||||
/* This procedure is intended to run only once */
|
||||
let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
let tradingEngineStage = tradingEngine.current.strategyOpenStage
|
||||
|
||||
await tradingExecutionModule.runExecution(
|
||||
executionNode,
|
||||
tradingEngine.current.strategyOpenStage
|
||||
)
|
||||
/* Reset the Exchange Orders data structure to its initial value */
|
||||
tradingEngineModule.initializeNode(tradingEngine.exchangeOrders)
|
||||
|
||||
/*
|
||||
The Closing is finished when the fillSize + feesPaid reaches the ordersSize.
|
||||
This can happens either because we update the sizeFilled value when we see
|
||||
at the exchange that orders were filled, or we reduce the ordersSize when
|
||||
we cancel not yet filled orders. Here it does not matter the targetSize since
|
||||
this status represent a force closure of this stage.
|
||||
*/
|
||||
if (
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value
|
||||
/* Entry Position size and rate */
|
||||
tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets')
|
||||
tradingPositionModule.initialTargets(tradingSystemStage, tradingEngineStage)
|
||||
initializeStageTargetSize()
|
||||
|
||||
||
|
||||
/* From here on, the stage is officialy Open */
|
||||
changeStageStatus('Open Stage', 'Open')
|
||||
}
|
||||
}
|
||||
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value
|
||||
) {
|
||||
changeStageStatus('Open Stage', 'Closed')
|
||||
async function runWhenStatusIsOpen() {
|
||||
/* Open Status Procedure */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Open') {
|
||||
/*
|
||||
While the Open Stage is Open, we do our regular stuff: place orders and check
|
||||
what happened to the orders already placed.
|
||||
*/
|
||||
let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
let tradingEngineStage = tradingEngine.current.strategyOpenStage
|
||||
let executionNode = tradingSystemStage.openExecution
|
||||
|
||||
/* Evaluate conditions and formulas so they are ready during the execution run */
|
||||
tradingSystem.evalConditions(executionNode, 'Open Execution')
|
||||
tradingSystem.evalFormulas(executionNode, 'Open Execution')
|
||||
|
||||
await tradingExecutionModule.runExecution(
|
||||
executionNode,
|
||||
tradingEngineStage
|
||||
)
|
||||
checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, 'Open Stage')
|
||||
}
|
||||
}
|
||||
|
||||
async function runWhenStatusIsClosing() {
|
||||
/* Closing Status Procedure */
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Closing') {
|
||||
/*
|
||||
During the closing stage status, we do not place new orders, just check
|
||||
if the ones placed were filled, and cancel the ones not filled.
|
||||
*/
|
||||
let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
let executionNode = tradingSystemStage.openExecution
|
||||
|
||||
/*
|
||||
Check if there are unfilled orders, we will check if they were executed,
|
||||
and cancel the ones that were not.
|
||||
*/
|
||||
tradingSystem.evalConditions(tradingSystemStage, 'Open Execution')
|
||||
tradingSystem.evalFormulas(tradingSystemStage, 'Open Execution')
|
||||
|
||||
await tradingExecutionModule.runExecution(
|
||||
executionNode,
|
||||
tradingEngine.current.strategyOpenStage
|
||||
)
|
||||
|
||||
/*
|
||||
The Closing is finished when the sizeFilled + feesPaid reaches the sizePlaced.
|
||||
This can happens either because we update the sizeFilled value when we see
|
||||
at the exchange that orders were filled, or we reduce the sizePlaced when
|
||||
we cancel not yet filled orders. Here it does not matter the targetSize since
|
||||
this status represent a force closure of this stage.
|
||||
*/
|
||||
switch (tradingEngine.current.strategyOpenStage.stageDefinedIn.value) {
|
||||
case 'Base Asset': {
|
||||
if (
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyOpenStage.stageBaseAsset.sizePlaced.value
|
||||
) {
|
||||
changeStageStatus('Open Stage', 'Closed')
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Quoted Asset': {
|
||||
if (
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyOpenStage.stageQuotedAsset.sizePlaced.value
|
||||
) {
|
||||
changeStageStatus('Open Stage', 'Closed')
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,57 +388,50 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
function runManageStage() {
|
||||
/* Opening Status Procedure */
|
||||
if (tradingEngine.current.strategyManageStage.status.value === 'Opening') {
|
||||
/* We jump the management at the loop cycle where the position is being taken. */
|
||||
changeStageStatus('Manage Stage', 'Open')
|
||||
return
|
||||
|
||||
runWhenStatusIsOpening()
|
||||
runWhenStatusIsOpen()
|
||||
|
||||
function runWhenStatusIsOpening() {
|
||||
/* Opening Status Procedure */
|
||||
if (tradingEngine.current.strategyManageStage.status.value === 'Opening') {
|
||||
|
||||
/* Now we switch to the Open status. */
|
||||
changeStageStatus('Manage Stage', 'Open')
|
||||
}
|
||||
}
|
||||
|
||||
/* Open Status Procedure */
|
||||
if (tradingEngine.current.strategyManageStage.status.value === 'Open') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
tradingSystem.evalConditions(manageStage, 'Manage Stage')
|
||||
tradingSystem.evalFormulas(manageStage, 'Manage Stage')
|
||||
function runWhenStatusIsOpen() {
|
||||
/* Open Status Procedure */
|
||||
if (tradingEngine.current.strategyManageStage.status.value === 'Open') {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let manageStage = strategy.manageStage
|
||||
|
||||
if (
|
||||
tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' ||
|
||||
tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage') {
|
||||
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].openStage
|
||||
tradingSystem.evalConditions(stageNode, 'Initial Definition')
|
||||
tradingSystem.evalFormulas(stageNode, 'Initial Definition')
|
||||
/* Evaluate all the stage conditions and formulas to have them ready */
|
||||
tradingSystem.evalConditions(manageStage, 'Manage Stage')
|
||||
tradingSystem.evalFormulas(manageStage, 'Manage Stage')
|
||||
|
||||
/* Stop Loss Management */
|
||||
checkStopPhasesEvents()
|
||||
calculateStopLoss()
|
||||
calculateStopLossPosition()
|
||||
|
||||
/* Take Profit Management */
|
||||
checkTakeProfitPhaseEvents()
|
||||
calculateTakeProfit()
|
||||
calculateTakeProfitPosition()
|
||||
|
||||
/* Checking if Stop or Take Profit were hit */
|
||||
checkStopLossOrTakeProfitWasHit()
|
||||
}
|
||||
|
||||
/* Stop Loss Management */
|
||||
checkStopPhasesEvents()
|
||||
calculateStopLoss()
|
||||
calculateStopLossPosition()
|
||||
|
||||
/* Take Profit Management */
|
||||
checkTakeProfitPhaseEvents()
|
||||
calculateTakeProfit()
|
||||
calculateTakeProfitPosition()
|
||||
|
||||
/* Checking if Stop or Take Profit were hit */
|
||||
checkStopLossOrTakeProfitWasHit()
|
||||
}
|
||||
|
||||
function calculateStopLoss() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialStopLoss !== undefined) {
|
||||
phase = openStage.initialDefinition.initialStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
phase = manageStage.managedStopLoss.phases[tradingEngine.current.position.stopLoss.stopLossPhase.value - 1]
|
||||
}
|
||||
@@ -480,19 +451,10 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
|
||||
function calculateTakeProfit() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let phase
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialTakeProfit !== undefined) {
|
||||
phase = openStage.initialDefinition.initialTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
phase = manageStage.managedTakeProfit.phases[tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1]
|
||||
}
|
||||
@@ -513,7 +475,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
function calculateStopLossPosition() {
|
||||
/*
|
||||
The position of the Stop Loss (above or below the price) is needed in order to know
|
||||
later if the price hit the Stop Loss or not at every Simulation cycle. When we get
|
||||
later if the price hit the Stop Loss or not at every Simulation candle. When we get
|
||||
the first values of the simulation of the Stop Loss we check if it is above or below
|
||||
the Position Rate, and we assign the values Above or Below to it.
|
||||
*/
|
||||
@@ -529,7 +491,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
|
||||
later if the price hit the Take Profit or not at every Simulation cycle. When we get
|
||||
later if the price hit the Take Profit or not at every Simulation candle. When we get
|
||||
the first values of the simulation of the Take Profits we check if it is above or below
|
||||
the Position Rates, and we assign the values Above or Below to it.
|
||||
*/
|
||||
@@ -544,28 +506,13 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
|
||||
function checkStopPhasesEvents() {
|
||||
let strategy = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value]
|
||||
let openStage = strategy.openStage
|
||||
let manageStage = strategy.manageStage
|
||||
let parentNode
|
||||
let phaseIndex
|
||||
let phase
|
||||
let stopLoss
|
||||
let stage
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
stage = openStage
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialStopLoss !== undefined) {
|
||||
parentNode = openStage.initialDefinition
|
||||
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value
|
||||
stopLoss = openStage.initialDefinition.initialStopLoss
|
||||
phase = stopLoss.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.stopLoss.stopLossStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
stage = manageStage
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedStopLoss !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.stopLoss.stopLossPhase.value - 1
|
||||
@@ -596,11 +543,14 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(stopLoss.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
tradingPositionModule.updateStopLoss(tradingEngine.current.position.stopLoss.stopLossPhase.value + 1, 'Manage Stage')
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -626,13 +576,13 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(stopLoss.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < stopLoss.phases.length; q++) {
|
||||
if (stopLoss.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateStopLoss(q + 1, 'Manage Stage')
|
||||
tradingPositionModule.updateStopLoss(q + 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -641,6 +591,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
@@ -657,22 +610,8 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
let phaseIndex
|
||||
let phase
|
||||
let takeProfit
|
||||
let stage
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Open Stage' && openStage !== undefined) {
|
||||
stage = openStage
|
||||
if (openStage.initialDefinition !== undefined) {
|
||||
if (openStage.initialDefinition.initialTakeProfit !== undefined) {
|
||||
parentNode = openStage.initialDefinition
|
||||
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value
|
||||
takeProfit = openStage.initialDefinition.initialTakeProfit
|
||||
phase = takeProfit.phases[phaseIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tradingEngine.current.position.takeProfit.takeProfitStage.value === 'Manage Stage' && manageStage !== undefined) {
|
||||
stage = manageStage
|
||||
if (manageStage !== undefined) {
|
||||
if (manageStage.managedTakeProfit !== undefined) {
|
||||
parentNode = manageStage
|
||||
phaseIndex = tradingEngine.current.position.takeProfit.takeProfitPhase.value - 1
|
||||
@@ -703,11 +642,14 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(takeProfit.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
tradingPositionModule.updateTakeProfit(tradingEngine.current.position.takeProfit.takeProfitPhase.value + 1, 'Manage Stage')
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -733,13 +675,13 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingSystem.highlights.push(phase.id)
|
||||
tradingSystem.highlights.push(takeProfit.id)
|
||||
tradingSystem.highlights.push(parentNode.id)
|
||||
tradingSystem.highlights.push(stage.id)
|
||||
tradingSystem.highlights.push(manageStage.id)
|
||||
|
||||
let moveToPhase = moveToPhaseEvent.referenceParent
|
||||
if (moveToPhase !== undefined) {
|
||||
for (let q = 0; q < takeProfit.phases.length; q++) {
|
||||
if (takeProfit.phases[q].id === moveToPhase.id) {
|
||||
tradingPositionModule.updateTakeProfit(q + 1, 'Manage Stage')
|
||||
tradingPositionModule.updateTakeProfit(q + 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -748,6 +690,9 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
announcementsModule.makeAnnoucements(moveToPhaseEvent)
|
||||
|
||||
/* Reset this counter */
|
||||
tradingEngine.current.episode.distanceToEvent.moveToPhase.value = 1
|
||||
return // only one event can pass at the time
|
||||
}
|
||||
}
|
||||
@@ -809,105 +754,87 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
async function runCloseStage() {
|
||||
/* Opening Status Procedure */
|
||||
if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') {
|
||||
/*
|
||||
This will happen only once, as soon as the Take Profit or Stop was hit.
|
||||
We wil not be placing orders at this time because we do not know
|
||||
the total filled size of the Open Stage. By skiping execution now
|
||||
we allow the open stage to get a final value for sizeFilled that we can use.
|
||||
*/
|
||||
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
|
||||
|
||||
/* Exit Position size and rate */
|
||||
tradingSystem.evalFormulas(stageNode, 'Initial Targets')
|
||||
tradingPositionModule.initialTargets(stageNode)
|
||||
runWhenStatusIsOpening()
|
||||
await runWhenStatusIsOpen()
|
||||
|
||||
initializeStageTargetSize()
|
||||
changeStageStatus('Close Stage', 'Open')
|
||||
return
|
||||
}
|
||||
function runWhenStatusIsOpening() {
|
||||
/* Opening Status Procedure */
|
||||
if (tradingEngine.current.strategyCloseStage.status.value === 'Opening') {
|
||||
/*
|
||||
This will happen only once, as soon as the Take Profit or Stop was hit.
|
||||
*/
|
||||
let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
|
||||
|
||||
/* Open Status Procedure */
|
||||
if (tradingEngine.current.strategyCloseStage.status.value === 'Open') {
|
||||
/*
|
||||
This will happen as long as the Close Stage is Open.
|
||||
*/
|
||||
let stageNode = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
|
||||
let executionNode = stageNode.closeExecution
|
||||
/* Exit Position size and rate */
|
||||
tradingSystem.evalFormulas(tradingSystemStage, 'Initial Targets')
|
||||
tradingPositionModule.initialTargets(tradingSystemStage, tradingEngine.current.strategyCloseStage)
|
||||
|
||||
tradingSystem.evalConditions(stageNode, 'Close Execution')
|
||||
tradingSystem.evalFormulas(stageNode, 'Close Execution')
|
||||
|
||||
await tradingExecutionModule.runExecution(
|
||||
executionNode,
|
||||
tradingEngine.current.strategyCloseStage
|
||||
)
|
||||
|
||||
/*
|
||||
The Open is finished when the placedSize reaches the targetSize in any of the
|
||||
stage assets, and the fillSize + feesPaid reaches the placedSize also in any
|
||||
of the stage Assets.
|
||||
This can happens at any time when we update the sizeFilled and the feesPaid values
|
||||
when we see at the exchange that orders were filled.
|
||||
*/
|
||||
if (
|
||||
(
|
||||
tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced >=
|
||||
tradingEngine.current.strategyCloseStage.stageBaseAsset.targetSize
|
||||
|
||||
||
|
||||
|
||||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced >=
|
||||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyCloseStage.stageBaseAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyCloseStage.stageBaseAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyCloseStage.stageBaseAsset.sizePlaced.value
|
||||
|
||||
||
|
||||
|
||||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizeFilled.value +
|
||||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.feesPaid.value >=
|
||||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.sizePlaced.value
|
||||
)
|
||||
) {
|
||||
changeStageStatus('Close Stage', 'Closed', 'Size Placed Filled')
|
||||
} else {
|
||||
/* Check the Close Stage Event */
|
||||
tradingSystem.evalConditions(stageNode, 'Close Stage Event')
|
||||
if (checkStopStageEvent(stageNode) === true) {
|
||||
changeStageStatus('Close Stage', 'Closing', 'Close Stage Event')
|
||||
}
|
||||
initializeStageTargetSize()
|
||||
changeStageStatus('Close Stage', 'Open')
|
||||
}
|
||||
}
|
||||
|
||||
/* Exit Position Validation */
|
||||
if (
|
||||
async function runWhenStatusIsOpen() {
|
||||
/*
|
||||
The following code will run only when the Open Stage is closed.
|
||||
The reason for that is that we need the final filled size of the Open Stage
|
||||
to be known before we start placing orders at the Close Stage.
|
||||
*/
|
||||
if (
|
||||
tradingEngine.current.strategyCloseStage.status.value === 'Open' &&
|
||||
(
|
||||
tradingEngine.current.strategyTriggerStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyTriggerStage.status.value === tradingEngine.current.strategyTriggerStage.status.config.initialValue
|
||||
tradingEngine.current.strategyOpenStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyOpenStage.status.value == tradingEngine.current.strategyOpenStage.status.config.initialValue
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyOpenStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyOpenStage.status.value === tradingEngine.current.strategyOpenStage.status.config.initialValue
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyManageStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyManageStage.status.value === tradingEngine.current.strategyManageStage.status.config.initialValue
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyCloseStage.status.value === 'Closed'
|
||||
)
|
||||
) {
|
||||
) {
|
||||
/*
|
||||
This will happen as long as the Close Stage is Open.
|
||||
*/
|
||||
let tradingSystemStage = tradingSystem.tradingStrategies[tradingEngine.current.strategy.index.value].closeStage
|
||||
let tradingEngineStage = tradingEngine.current.strategyCloseStage
|
||||
let executionNode = tradingSystemStage.closeExecution
|
||||
|
||||
/* Exiting Everything now. */
|
||||
exitPositionAndStrategy()
|
||||
tradingSystem.evalConditions(tradingSystemStage, 'Close Execution')
|
||||
tradingSystem.evalFormulas(tradingSystemStage, 'Close Execution')
|
||||
|
||||
await tradingExecutionModule.runExecution(
|
||||
executionNode,
|
||||
tradingEngine.current.strategyCloseStage
|
||||
)
|
||||
|
||||
checkIfStageNeedsToBeClosed(tradingEngineStage, tradingSystemStage, 'Close Stage')
|
||||
}
|
||||
|
||||
/*
|
||||
Exit Position Validation: we need al the previous stages to be either closed
|
||||
or at their initial default value. The last is becasue after Closed objects are
|
||||
initialized to their defualts.
|
||||
*/
|
||||
if (
|
||||
(
|
||||
tradingEngine.current.strategyTriggerStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyTriggerStage.status.value === tradingEngine.current.strategyTriggerStage.status.config.initialValue
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyOpenStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyOpenStage.status.value === tradingEngine.current.strategyOpenStage.status.config.initialValue
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyManageStage.status.value === 'Closed' ||
|
||||
tradingEngine.current.strategyManageStage.status.value === tradingEngine.current.strategyManageStage.status.config.initialValue
|
||||
)
|
||||
&&
|
||||
(
|
||||
tradingEngine.current.strategyCloseStage.status.value === 'Closed'
|
||||
)
|
||||
) {
|
||||
|
||||
/* Closing Everything now. */
|
||||
closePositionAndStrategy()
|
||||
}
|
||||
}
|
||||
|
||||
function initializeStageTargetSize() {
|
||||
@@ -921,8 +848,7 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value = global.PRECISE(tradingEngine.current.strategyCloseStage.stageQuotedAsset.targetSize.value, 10)
|
||||
}
|
||||
|
||||
function exitPositionAndStrategy() {
|
||||
|
||||
function closePositionAndStrategy() {
|
||||
/* Taking Position Snapshot */
|
||||
if (bot.SESSION.type === 'Backtesting Session') {
|
||||
if (sessionParameters.snapshots !== undefined) {
|
||||
@@ -957,8 +883,53 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopStageEvent(stage) {
|
||||
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.
|
||||
*/
|
||||
switch (tradingEngineStage.stageDefinedIn.value) {
|
||||
case 'Base Asset': {
|
||||
if (
|
||||
tradingEngineStage.stageBaseAsset.sizeFilled.value +
|
||||
tradingEngineStage.stageBaseAsset.feesPaid.value >=
|
||||
tradingEngineStage.stageBaseAsset.targetSize.value
|
||||
) {
|
||||
positionFilled()
|
||||
} else {
|
||||
checkCloseStageEvent(tradingSystemStage)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Quoted Asset': {
|
||||
if (
|
||||
tradingEngineStage.stageQuotedAsset.sizeFilled.value +
|
||||
tradingEngineStage.stageQuotedAsset.feesPaid.value >=
|
||||
tradingEngineStage.stageQuotedAsset.targetSize.value
|
||||
) {
|
||||
positionFilled()
|
||||
} else {
|
||||
checkCloseStageEvent(tradingSystemStage)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
function positionFilled() {
|
||||
changeStageStatus(stageName, 'Closed', 'Position Size Filled')
|
||||
}
|
||||
function checkCloseStageEvent(tradingSystemStage) {
|
||||
/* Check the Close Stage Event */
|
||||
tradingSystem.evalConditions(tradingSystemStage, 'Close Stage Event')
|
||||
if (checkStopStageEvent(tradingSystemStage) === true) {
|
||||
changeStageStatus(stageName, 'Closing', 'Close Stage Event')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkStopStageEvent(stage) {
|
||||
/* Check the Close Stage Event. */
|
||||
let stopStageEvent = stage.stopStageEvent
|
||||
if (stopStageEvent !== undefined) {
|
||||
@@ -1017,33 +988,33 @@ exports.newTradingStages = function newTradingStages(bot, logger, tradingEngineM
|
||||
|
||||
function openStage(stage) {
|
||||
/* Recording the opening at the Trading Engine Data Structure */
|
||||
stage.begin.value = tradingEngine.current.episode.candle.begin.value
|
||||
stage.begin.value = tradingEngine.current.episode.cycle.begin.value
|
||||
stage.beginRate.value = tradingEngine.current.episode.candle.close.value
|
||||
stage.end.value = tradingEngine.current.episode.candle.end.value
|
||||
stage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
}
|
||||
|
||||
function closeStage(stage) {
|
||||
/* Recording the closing at the Trading Engine Data Structure */
|
||||
stage.end.value = tradingEngine.current.episode.candle.end.value
|
||||
stage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
stage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
}
|
||||
|
||||
function updateEnds() {
|
||||
if (tradingEngine.current.strategyTriggerStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategyTriggerStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyTriggerStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
if (tradingEngine.current.strategyOpenStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategyOpenStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyOpenStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
if (tradingEngine.current.strategyManageStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategyManageStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyManageStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
if (tradingEngine.current.strategyCloseStage.status.value === 'Open') {
|
||||
tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategyCloseStage.end.value = tradingEngine.current.episode.cycle.end.value
|
||||
tradingEngine.current.strategyCloseStage.endRate.value = tradingEngine.current.episode.candle.close.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
|
||||
const MODULE_NAME = 'Trading Strategy'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
openStrategy: openStrategy,
|
||||
closeStrategy: closeStrategy,
|
||||
initialize: initialize,
|
||||
@@ -24,16 +25,19 @@ exports.newTradingStrategy = function newTradingStrategy(bot, logger, tradingEng
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
resetTradingEngineDataStructure()
|
||||
updateCounters()
|
||||
updateEnds()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
resetTradingEngineDataStructure()
|
||||
}
|
||||
|
||||
function openStrategy(index, situationName, strategyName) {
|
||||
tradingEngine.current.strategy.status.value = 'Open'
|
||||
tradingEngine.current.strategy.serialNumber.value = tradingEngine.current.episode.episodeCounters.strategies.value
|
||||
tradingEngine.current.strategy.identifier.value = global.UNIQUE_ID()
|
||||
tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.candle.begin.value
|
||||
tradingEngine.current.strategy.begin.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategy.end.value = tradingEngine.current.episode.candle.end.value
|
||||
tradingEngine.current.strategy.beginRate.value = tradingEngine.current.episode.candle.min.value
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
const MODULE_NAME = 'Trading System'
|
||||
let thisObject = {
|
||||
mantain: mantain,
|
||||
reset: reset,
|
||||
run: run,
|
||||
updateChart: updateChart,
|
||||
initialize: initialize,
|
||||
@@ -97,19 +98,19 @@ exports.newTradingSystem = function newTradingSystem(bot, logger, tradingEngineM
|
||||
}
|
||||
|
||||
function mantain() {
|
||||
reset()
|
||||
tradingStagesModule.mantain()
|
||||
}
|
||||
|
||||
function reset() {
|
||||
/* This function helps reset data structures at every cycle of the simulation loop/=. */
|
||||
tradingSystem.highlights = []
|
||||
tradingSystem.errors = []
|
||||
tradingSystem.values = []
|
||||
tradingSystem.status = []
|
||||
tradingSystem.progress = []
|
||||
tradingSystem.running = []
|
||||
tradingSystem.announcements = []
|
||||
}
|
||||
function reset() {
|
||||
tradingStagesModule.reset()
|
||||
|
||||
tradingSystem.highlights = []
|
||||
tradingSystem.errors = []
|
||||
tradingSystem.values = []
|
||||
tradingSystem.status = []
|
||||
tradingSystem.progress = []
|
||||
tradingSystem.running = []
|
||||
tradingSystem.announcements = []
|
||||
}
|
||||
|
||||
function updateChart(pChart) {
|
||||
|
||||
@@ -160,6 +160,20 @@
|
||||
finalDatetime: (new Date()).valueOf()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Check that we received valid dates */
|
||||
if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.initialDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
if(isNaN(new Date(bot.SESSION.parameters.timeRange.config.finalDatetime)).valueOf()) {
|
||||
let errorMessage = "sessionParameters.timeRange.config.initialDatetime is not a valid date."
|
||||
parentLogger.write(MODULE_NAME, "[ERROR] initialize -> checkParemeters -> " + errorMessage)
|
||||
bot.sessionError(bot.SESSION.parameters, errorMessage)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* Session Type Forced Values */
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
/* Callbacks default responses. */
|
||||
global.DEFAULT_OK_RESPONSE = {
|
||||
result: "Ok",
|
||||
|
||||
Reference in new issue
Block a user