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)
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
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(
trade.entryPrice,
currentPrice,
trade.direction
)
const accountPnL = profitPercent * trade.leverage
const realizedPnL = (trade.currentSize * accountPnL) / 100
const realizedPnL = (sizeBeforeClosure * accountPnL) / 100
// Update database
const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000)

View File

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