Fix P&L calculation and signal flip detection
- Fix external closure P&L using tp1Hit flag instead of currentSize - Add direction change detection to prevent false TP1 on signal flips - Signal flips now recorded with accurate P&L as 'manual' exits - Add retry logic with exponential backoff for Solana RPC rate limits - Create /api/trading/cancel-orders endpoint for manual cleanup - Improves data integrity for win/loss statistics
This commit is contained in:
@@ -19,12 +19,32 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get the body - it might be a string or nested in an object\nlet body = $json.body || $json.query?.body || JSON.stringify($json);\n\n// If body is an object, stringify it\nif (typeof body === 'object') {\n body = JSON.stringify(body);\n}\n\n// Parse basic signal (existing logic)\nconst symbolMatch = body.match(/\\b(SOL|BTC|ETH)/i);\nconst symbol = symbolMatch ? symbolMatch[1].toUpperCase() + '-PERP' : 'SOL-PERP';\n\nconst direction = body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long';\n\n// Updated regex to match new format: \"ETH buy 15\" (no .P)\nconst timeframeMatch = body.match(/\\b(buy|sell)\\s+(\\d+|D|W|M)\\b/i);\nconst timeframe = timeframeMatch ? timeframeMatch[2] : '5';\n\n// Parse new context metrics from enhanced format:\n// \"ETH buy 15 | ATR:1.85 | ADX:28.3 | RSI:62.5 | VOL:1.45 | POS:75.3\"\nconst atrMatch = body.match(/ATR:([\\d.]+)/);\nconst atr = atrMatch ? parseFloat(atrMatch[1]) : 0;\n\nconst adxMatch = body.match(/ADX:([\\d.]+)/);\nconst adx = adxMatch ? parseFloat(adxMatch[1]) : 0;\n\nconst rsiMatch = body.match(/RSI:([\\d.]+)/);\nconst rsi = rsiMatch ? parseFloat(rsiMatch[1]) : 0;\n\nconst volumeMatch = body.match(/VOL:([\\d.]+)/);\nconst volumeRatio = volumeMatch ? parseFloat(volumeMatch[1]) : 0;\n\nconst pricePositionMatch = body.match(/POS:([\\d.]+)/);\nconst pricePosition = pricePositionMatch ? parseFloat(pricePositionMatch[1]) : 0;\n\nreturn {\n rawMessage: body,\n symbol,\n direction,\n timeframe,\n // New context fields\n atr,\n adx,\n rsi,\n volumeRatio,\n pricePosition\n};"
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "rawMessage",
|
||||
"stringValue": "={{ $json.body }}"
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"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.match(/\\b(sell|short)\\b/i) ? 'short' : 'long' }}"
|
||||
},
|
||||
{
|
||||
"name": "timeframe",
|
||||
"stringValue": "={{ $json.body.match(/\\.P\\s+(\\d+)/)?.[1] || '15' }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "97d5b0ad-d078-411f-8f34-c9a81d18d921",
|
||||
"name": "Parse Signal",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
-760,
|
||||
580
|
||||
@@ -71,7 +91,7 @@
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"symbol\": \"{{ $json.symbol }}\",\n \"direction\": \"{{ $json.direction }}\",\n \"atr\": {{ $json.atr || 0 }},\n \"adx\": {{ $json.adx || 0 }},\n \"rsi\": {{ $json.rsi || 0 }},\n \"volumeRatio\": {{ $json.volumeRatio || 0 }},\n \"pricePosition\": {{ $json.pricePosition || 0 }}\n}",
|
||||
"jsonBody": "={\n \"symbol\": \"{{ $json.symbol }}\",\n \"direction\": \"{{ $json.direction }}\"\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c1165de4-2095-4f5f-b9b1-18e76fd8c47b",
|
||||
@@ -130,7 +150,7 @@
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal').item.json.timeframe }}\",\n \"signalStrength\": \"strong\",\n \"atr\": {{ $('Parse Signal').item.json.atr }},\n \"adx\": {{ $('Parse Signal').item.json.adx }},\n \"rsi\": {{ $('Parse Signal').item.json.rsi }},\n \"volumeRatio\": {{ $('Parse Signal').item.json.volumeRatio }},\n \"pricePosition\": {{ $('Parse Signal').item.json.pricePosition }}\n}",
|
||||
"jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal').item.json.timeframe }}\",\n \"signalStrength\": \"strong\"\n}",
|
||||
"options": {
|
||||
"timeout": 120000
|
||||
}
|
||||
@@ -402,7 +422,7 @@
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal Enhanced').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal Enhanced').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal Enhanced').item.json.timeframe }}\",\n \"signalStrength\": \"strong\",\n \"atr\": {{ $('Parse Signal Enhanced').item.json.atr }},\n \"adx\": {{ $('Parse Signal Enhanced').item.json.adx }},\n \"rsi\": {{ $('Parse Signal Enhanced').item.json.rsi }},\n \"volumeRatio\": {{ $('Parse Signal Enhanced').item.json.volumeRatio }},\n \"pricePosition\": {{ $('Parse Signal Enhanced').item.json.pricePosition }},\n \"qualityScore\": {{ $input.first().json.qualityScore }}\n}",
|
||||
"jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal Enhanced').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal Enhanced').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal Enhanced').item.json.timeframe }}\",\n \"signalStrength\": \"{{ $('Parse Signal Enhanced').item.json.signalStrength }}\",\n \"atr\": {{ $('Parse Signal Enhanced').item.json.atr }},\n \"adx\": {{ $('Parse Signal Enhanced').item.json.adx }},\n \"rsi\": {{ $('Parse Signal Enhanced').item.json.rsi }},\n \"volumeRatio\": {{ $('Parse Signal Enhanced').item.json.volumeRatio }},\n \"pricePosition\": {{ $('Parse Signal Enhanced').item.json.pricePosition }}\n}",
|
||||
"options": {
|
||||
"timeout": 120000
|
||||
}
|
||||
@@ -460,6 +480,21 @@
|
||||
"name": "Header Auth account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"path": "c034de5f-bcd5-4470-a193-8a16fbfb73eb",
|
||||
"options": {}
|
||||
},
|
||||
"id": "ff525977-6e95-4e45-b742-cedb5f36b4b4",
|
||||
"name": "Webhook1",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-1020,
|
||||
860
|
||||
],
|
||||
"webhookId": "c034de5f-bcd5-4470-a193-8a16fbfb73eb"
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
@@ -633,7 +668,7 @@
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "1ec420e9-a965-48bd-8f29-e912aa569431",
|
||||
"versionId": "955bd768-0c3b-490a-9c6b-5c01bc2f6d44",
|
||||
"id": "gUDqTiHyHSfRUXv6",
|
||||
"meta": {
|
||||
"instanceId": "e766d4f0b5def8ee8cb8561cd9d2b9ba7733e1907990b6987bca40175f82c379"
|
||||
|
||||
32
workflows/trading/market_data_alert.pine
Normal file
32
workflows/trading/market_data_alert.pine
Normal file
@@ -0,0 +1,32 @@
|
||||
//@version=5
|
||||
indicator("Market Data Alert Helper", overlay=false)
|
||||
|
||||
// This indicator fires an alert on every bar close
|
||||
// Used to send market data to trading bot
|
||||
|
||||
// Calculate metrics
|
||||
atr_value = ta.atr(14)
|
||||
adx_value = ta.dmi(14, 14)
|
||||
rsi_value = ta.rsi(close, 14)
|
||||
volume_ratio = volume / ta.sma(volume, 20)
|
||||
price_position = (close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100
|
||||
|
||||
// Plot a simple line (just so indicator shows on chart)
|
||||
plot(1, "Alert Signal", color=color.green)
|
||||
|
||||
// Display values on chart for verification
|
||||
var table infoTable = table.new(position.top_right, 2, 6)
|
||||
if barstate.islast
|
||||
table.cell(infoTable, 0, 0, "ATR", text_color=color.white)
|
||||
table.cell(infoTable, 1, 0, str.tostring(atr_value, "#.##"), text_color=color.white)
|
||||
table.cell(infoTable, 0, 1, "ADX", text_color=color.white)
|
||||
table.cell(infoTable, 1, 1, str.tostring(adx_value, "#.#"), text_color=color.white)
|
||||
table.cell(infoTable, 0, 2, "RSI", text_color=color.white)
|
||||
table.cell(infoTable, 1, 2, str.tostring(rsi_value, "#.#"), text_color=color.white)
|
||||
table.cell(infoTable, 0, 3, "Vol Ratio", text_color=color.white)
|
||||
table.cell(infoTable, 1, 3, str.tostring(volume_ratio, "#.##"), text_color=color.white)
|
||||
table.cell(infoTable, 0, 4, "Price Pos", text_color=color.white)
|
||||
table.cell(infoTable, 1, 4, str.tostring(price_position, "#.#"), text_color=color.white)
|
||||
|
||||
// Alert condition - triggers on every bar close
|
||||
alertcondition(true, title="Market Data Update", message='{"action":"market_data","symbol":"{{ticker}}","timeframe":"{{interval}}","atr":' + str.tostring(atr_value) + ',"adx":' + str.tostring(adx_value) + ',"rsi":' + str.tostring(rsi_value) + ',"volumeRatio":' + str.tostring(volume_ratio) + ',"pricePosition":' + str.tostring(price_position) + ',"currentPrice":' + str.tostring(close) + '}')
|
||||
159
workflows/trading/market_data_handler.json
Normal file
159
workflows/trading/market_data_handler.json
Normal file
@@ -0,0 +1,159 @@
|
||||
{
|
||||
"name": "Market Data Handler - Import This",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "tradingview-bot-v4",
|
||||
"options": {}
|
||||
},
|
||||
"id": "webhook-main",
|
||||
"name": "Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
240,
|
||||
300
|
||||
],
|
||||
"webhookId": "tradingview-bot-v4"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"string": [
|
||||
{
|
||||
"value1": "={{ $json.body.action }}",
|
||||
"operation": "equals",
|
||||
"value2": "market_data"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "check-if-market-data",
|
||||
"name": "Is Market Data?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
460,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://trading-bot-v4:3000/api/trading/market-data",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ $json.body }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "forward-to-bot",
|
||||
"name": "Forward to Bot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [
|
||||
680,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "json",
|
||||
"responseBody": "={ \"success\": true, \"cached\": true }",
|
||||
"options": {}
|
||||
},
|
||||
"id": "respond-success",
|
||||
"name": "Respond Success",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
900,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "rawMessage",
|
||||
"stringValue": "={{ $json.body }}"
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"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.match(/\\b(sell|short)\\b/i) ? 'short' : 'long' }}"
|
||||
},
|
||||
{
|
||||
"name": "timeframe",
|
||||
"stringValue": "={{ $json.body.match(/\\.P\\s+(\\d+)/)?.[1] || '15' }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "parse-trading-signal",
|
||||
"name": "Parse Trading Signal",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
680,
|
||||
400
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Is Market Data?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Is Market Data?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Forward to Bot",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Parse Trading Signal",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Forward to Bot": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond Success",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"staticData": null,
|
||||
"tags": [],
|
||||
"triggerCount": 0,
|
||||
"updatedAt": "2025-11-08T00:00:00.000Z",
|
||||
"versionId": "market-data-handler-v1"
|
||||
}
|
||||
Reference in New Issue
Block a user