Fix: Calculate P&L correctly for external closures

- Save currentSize before it becomes 0 in external closure detection
- Use sizeBeforeClosure for P&L calculation instead of trade.currentSize
- Prevents /bin/bash.00 P&L for TP2 exits when position closes externally
- Ensures win/loss analytics counts TP trades correctly
This commit is contained in:
mindesbunister
2025-10-28 20:10:38 +01:00
parent 27f78748cf
commit fe4d9bc954
2 changed files with 86 additions and 68 deletions

View File

@@ -282,6 +282,9 @@ export class PositionManager {
// Position closed externally (by on-chain TP/SL order) // Position closed externally (by on-chain TP/SL order)
console.log(`⚠️ Position ${trade.symbol} was closed externally (by on-chain order)`) console.log(`⚠️ Position ${trade.symbol} was closed externally (by on-chain order)`)
// Save currentSize before it becomes 0
const sizeBeforeClosure = trade.currentSize
// Determine exit reason based on price // Determine exit reason based on price
let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL' let exitReason: 'TP1' | 'TP2' | 'SL' | 'SOFT_SL' | 'HARD_SL' = 'SL'
@@ -304,14 +307,14 @@ export class PositionManager {
} }
} }
// Calculate final P&L // Calculate final P&L using size BEFORE closure
const profitPercent = this.calculateProfitPercent( const profitPercent = this.calculateProfitPercent(
trade.entryPrice, trade.entryPrice,
currentPrice, currentPrice,
trade.direction trade.direction
) )
const accountPnL = profitPercent * trade.leverage const accountPnL = profitPercent * trade.leverage
const realizedPnL = (trade.currentSize * accountPnL) / 100 const realizedPnL = (sizeBeforeClosure * accountPnL) / 100
// Update database // Update database
const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000) const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000)

View File

@@ -4,18 +4,18 @@
{ {
"parameters": { "parameters": {
"httpMethod": "POST", "httpMethod": "POST",
"path": "3371ad7c-0866-4161-90a4-f251de4aceb8", "path": "tradingview-bot-v4",
"options": {} "options": {}
}, },
"id": "35b54214-9761-49dc-97b6-df39543f0a7b", "id": "c762618c-fac7-4689-9356-8a78fc7160a8",
"name": "Webhook", "name": "Webhook",
"type": "n8n-nodes-base.webhook", "type": "n8n-nodes-base.webhook",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
-840, -980,
660 680
], ],
"webhookId": "3371ad7c-0866-4161-90a4-f251de4aceb8" "webhookId": "tradingview-bot-v4"
}, },
{ {
"parameters": { "parameters": {
@@ -27,27 +27,48 @@
}, },
{ {
"name": "symbol", "name": "symbol",
"stringValue": "={{ ($json.body || '').toString().match(/\\bSOL\\b/i) ? 'SOL-PERP' : (($json.body || '').toString().match(/\\bBTC\\b/i) ? 'BTC-PERP' : (($json.body || '').toString().match(/\\bETH\\b/i) ? 'ETH-PERP' : 'SOL-PERP')) }}" "stringValue": "={{ $json.body.match(/\\bSOL\\b/i) ? 'SOL-PERP' : ($json.body.match(/\\bBTC\\b/i) ? 'BTC-PERP' : ($json.body.match(/\\bETH\\b/i) ? 'ETH-PERP' : 'SOL-PERP')) }}"
}, },
{ {
"name": "direction", "name": "direction",
"stringValue": "={{ ($json.body || '').toString().match(/\\b(sell|short)\\b/i) ? 'short' : 'long' }}" "stringValue": "={{ $json.body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long' }}"
}, },
{ {
"name": "timeframe", "name": "timeframe",
"stringValue": "5" "stringValue": "={{ $json.body.match(/\\.P\\s+(\\d+)/)?.[1] || '15' }}"
} }
] ]
}, },
"options": {} "options": {}
}, },
"id": "99336995-2326-4575-9970-26afcf957132", "id": "97d5b0ad-d078-411f-8f34-c9a81d18d921",
"name": "Parse Signal", "name": "Parse Signal",
"type": "n8n-nodes-base.set", "type": "n8n-nodes-base.set",
"typeVersion": 3.2, "typeVersion": 3.2,
"position": [ "position": [
-660, -780,
660 680
]
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.timeframe }}",
"operation": "equals",
"value2": "15"
}
]
}
},
"id": "2e0bf241-9fb6-40bd-89f6-2dceafe34ef9",
"name": "15min Chart Only?",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
-560,
540
] ]
}, },
{ {
@@ -74,12 +95,12 @@
"jsonBody": "={\n \"symbol\": \"{{ $json.symbol }}\",\n \"direction\": \"{{ $json.direction }}\"\n}", "jsonBody": "={\n \"symbol\": \"{{ $json.symbol }}\",\n \"direction\": \"{{ $json.direction }}\"\n}",
"options": {} "options": {}
}, },
"id": "d42e7897-eadd-4202-8565-ac60759b46e1", "id": "c1165de4-2095-4f5f-b9b1-18e76fd8c47b",
"name": "Check Risk", "name": "Check Risk",
"type": "n8n-nodes-base.httpRequest", "type": "n8n-nodes-base.httpRequest",
"typeVersion": 4, "typeVersion": 4,
"position": [ "position": [
-340, -280,
660 660
], ],
"credentials": { "credentials": {
@@ -100,12 +121,12 @@
] ]
} }
}, },
"id": "a60bfecb-d2f4-4165-a609-e6ed437aa2aa", "id": "b9fa2b47-2acd-4be0-9d50-3f0348e04ec6",
"name": "Risk Passed?", "name": "Risk Passed?",
"type": "n8n-nodes-base.if", "type": "n8n-nodes-base.if",
"typeVersion": 1, "typeVersion": 1,
"position": [ "position": [
-140, -80,
660 660
] ]
}, },
@@ -135,7 +156,7 @@
"timeout": 120000 "timeout": 120000
} }
}, },
"id": "95c46846-4b6a-4f9e-ad93-be223b73a618", "id": "c2ec5f8c-42d1-414f-bdd6-0a440bc8fea9",
"name": "Execute Trade", "name": "Execute Trade",
"type": "n8n-nodes-base.httpRequest", "type": "n8n-nodes-base.httpRequest",
"typeVersion": 4, "typeVersion": 4,
@@ -161,7 +182,7 @@
] ]
} }
}, },
"id": "18342642-e76f-484f-b532-d29846536a9c", "id": "16dbf434-a07c-4666-82f2-cdc8814fe216",
"name": "Trade Success?", "name": "Trade Success?",
"type": "n8n-nodes-base.if", "type": "n8n-nodes-base.if",
"typeVersion": 1, "typeVersion": 1,
@@ -182,7 +203,7 @@
}, },
"options": {} "options": {}
}, },
"id": "9da40e3d-b855-4c65-a032-c6fcf88245d4", "id": "79ab6122-cbd3-4aac-97d7-6b54f64e29b5",
"name": "Format Success", "name": "Format Success",
"type": "n8n-nodes-base.set", "type": "n8n-nodes-base.set",
"typeVersion": 3.2, "typeVersion": 3.2,
@@ -203,7 +224,7 @@
}, },
"options": {} "options": {}
}, },
"id": "500751c7-21bb-4351-8a6a-d43a1bfb9eaa", "id": "41a0a8be-5004-4e6d-bdc5-9c7edf04eb51",
"name": "Format Error", "name": "Format Error",
"type": "n8n-nodes-base.set", "type": "n8n-nodes-base.set",
"typeVersion": 3.2, "typeVersion": 3.2,
@@ -224,7 +245,7 @@
}, },
"options": {} "options": {}
}, },
"id": "dec6cbc4-7550-40d3-9195-c4cc4f787b9b", "id": "da462967-0548-4d57-a6de-cb783c96ac07",
"name": "Format Risk", "name": "Format Risk",
"type": "n8n-nodes-base.set", "type": "n8n-nodes-base.set",
"typeVersion": 3.2, "typeVersion": 3.2,
@@ -241,7 +262,7 @@
"appendAttribution": false "appendAttribution": false
} }
}, },
"id": "6267b604-d39b-4cb7-98a5-2342cdced33b", "id": "254280fd-f547-4302-97a5-30b44d851e12",
"name": "Telegram Success", "name": "Telegram Success",
"type": "n8n-nodes-base.telegram", "type": "n8n-nodes-base.telegram",
"typeVersion": 1.1, "typeVersion": 1.1,
@@ -259,12 +280,12 @@
{ {
"parameters": { "parameters": {
"chatId": "579304651", "chatId": "579304651",
"text": "{{ `🟢 TRADE OPENED\\n\\n📊 Symbol: ${$('Parse Signal').item.json.symbol}\\n${$('Parse Signal').item.json.direction === 'long' ? '📈' : '📉'} Direction: ${$('Parse Signal').item.json.direction.toUpperCase()}\\n\\n💰 Entry: $${$json.entryPrice.toFixed(4)}\\n🎯 TP1: $${$json.takeProfit1.toFixed(4)} (${$json.tp1Percent}%)\\n🎯 TP2: $${$json.takeProfit2.toFixed(4)} (${$json.tp2Percent}%)\\n🛑 SL: $${$json.stopLoss.toFixed(4)} (${$json.stopLossPercent}%)\\n\\n⏰ ${$now.toFormat('HH:mm:ss')}\\n✅ Position monitored` }}", "text": "={{ $json.message }}",
"additionalFields": { "additionalFields": {
"appendAttribution": false "appendAttribution": false
} }
}, },
"id": "88224fac-ef7a-41ec-b68a-e4bc1a5e3f31", "id": "4ea066c9-4971-408f-b6e2-7d704c13ef55",
"name": "Telegram Error", "name": "Telegram Error",
"type": "n8n-nodes-base.telegram", "type": "n8n-nodes-base.telegram",
"typeVersion": 1.1, "typeVersion": 1.1,
@@ -287,7 +308,7 @@
"appendAttribution": false "appendAttribution": false
} }
}, },
"id": "4eccaca4-a5e7-407f-aab9-663a98a8323b", "id": "ee6be7be-1735-4fa3-bd33-6b3fde9414d3",
"name": "Telegram Risk", "name": "Telegram Risk",
"type": "n8n-nodes-base.telegram", "type": "n8n-nodes-base.telegram",
"typeVersion": 1.1, "typeVersion": 1.1,
@@ -304,46 +325,23 @@
}, },
{ {
"parameters": { "parameters": {
"chatId": "579304651", "conditions": {
"text": "={{ $json.signal.startsWith(\"Buy\") ? \"🟢 \" + $json.signal : \"🔴 \" + $json.signal }}\n", "string": [
"additionalFields": {
"appendAttribution": false
}
},
"id": "5a8eda4d-8945-4144-8672-022c9ee68bf6",
"name": "Telegram",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.1,
"position": [
-340,
840
],
"credentials": {
"telegramApi": {
"id": "Csk5cg4HtaSqP5jJ",
"name": "Telegram account"
}
}
},
{ {
"parameters": { "value1": "={{ $json.timeframe }}",
"fields": { "operation": "equals",
"values": [ "value2": "5"
{
"name": "signal",
"stringValue": "={{ $json.body.split('|')[0].trim() }}"
} }
] ]
}
}, },
"options": {} "id": "8c680565-120d-47dc-83b2-58dcd397168b",
}, "name": "5min Chart Only?1",
"id": "cce16424-fbb1-4191-b719-79ccfd59ec12", "type": "n8n-nodes-base.if",
"name": "Edit Fields", "typeVersion": 1,
"type": "n8n-nodes-base.set",
"typeVersion": 3.2,
"position": [ "position": [
-660, -560,
840 800
] ]
} }
], ],
@@ -364,12 +362,12 @@
"main": [ "main": [
[ [
{ {
"node": "Check Risk", "node": "15min Chart Only?",
"type": "main", "type": "main",
"index": 0 "index": 0
}, },
{ {
"node": "Telegram", "node": "5min Chart Only?1",
"type": "main", "type": "main",
"index": 0 "index": 0
} }
@@ -467,18 +465,35 @@
] ]
] ]
}, },
"Edit Fields": { "15min Chart Only?": {
"main": [ "main": [
[] [
{
"node": "Check Risk",
"type": "main",
"index": 0
}
]
]
},
"5min Chart Only?1": {
"main": [
[
{
"node": "Check Risk",
"type": "main",
"index": 0
}
]
] ]
} }
}, },
"active": false, "active": true,
"settings": { "settings": {
"executionOrder": "v1" "executionOrder": "v1"
}, },
"versionId": "2cc10693-953a-4b97-8c86-750b3063096b", "versionId": "1376fb3b-08fb-4d96-a038-371249d36eda",
"id": "xTCaxlyI02bQLxun", "id": "gUDqTiHyHSfRUXv6",
"meta": { "meta": {
"instanceId": "e766d4f0b5def8ee8cb8561cd9d2b9ba7733e1907990b6987bca40175f82c379" "instanceId": "e766d4f0b5def8ee8cb8561cd9d2b9ba7733e1907990b6987bca40175f82c379"
}, },