fix(n8n): CRITICAL - Add quality score validation to old workflow path
PROBLEM: - Trades with quality score 35 and 45 were executed (threshold: 60) - Position opened without risk management after signal flips - "Parse Signal" node didn't extract ATR/ADX/RSI/volumeRatio/pricePosition - "Check Risk" node only sent symbol+direction, skipped quality validation - "Execute Trade" node didn't forward metrics to backend ROOT CAUSE: n8n workflow had TWO paths: 1. NEW: Parse Signal Enhanced → Check Risk1 → Execute Trade1 (working) 2. OLD: Parse Signal → Check Risk → Execute Trade (broken) Old path bypassed quality check because check-risk endpoint saw hasContextMetrics=false and allowed trade without validation. FIX: 1. Changed "Parse Signal" from 'set' to 'code' node with metric extraction 2. Updated "Check Risk" to send atr/adx/rsi/volumeRatio/pricePosition 3. Updated "Execute Trade" to forward all metrics to backend IMPACT: - All trades now validated against quality score threshold (60) - Low-quality signals properly blocked before execution - Prevents positions opening without proper risk management Evidence from database showed 3 trades in 2 hours with scores <60: - 10:00:31 SOL LONG - Score 35 (phantom detected) - 09:55:30 SOL SHORT - Score 35 (executed) - 09:35:14 SOL LONG - Score 45 (executed) All three should have been blocked. Fix prevents future bypasses.
This commit is contained in:
@@ -19,32 +19,12 @@
|
||||
},
|
||||
{
|
||||
"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": {}
|
||||
"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};"
|
||||
},
|
||||
"id": "97d5b0ad-d078-411f-8f34-c9a81d18d921",
|
||||
"name": "Parse Signal",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-760,
|
||||
580
|
||||
@@ -91,7 +71,7 @@
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"symbol\": \"{{ $json.symbol }}\",\n \"direction\": \"{{ $json.direction }}\"\n}",
|
||||
"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}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "c1165de4-2095-4f5f-b9b1-18e76fd8c47b",
|
||||
@@ -150,7 +130,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}",
|
||||
"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}",
|
||||
"options": {
|
||||
"timeout": 120000
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user