Integrated MA gap analysis into signal quality evaluation pipeline: BACKEND SCORING (lib/trading/signal-quality.ts): - Added maGap?: number parameter to scoreSignalQuality interface - Implemented convergence/divergence scoring logic: * LONG: +15pts tight bullish (0-2%), +12pts converging (-2-0%), +8pts early momentum (-5--2%) * SHORT: +15pts tight bearish (-2-0%), +12pts converging (0-2%), +8pts early momentum (2-5%) * Penalties: -5pts for misaligned MA structure (>5% wrong direction) N8N PARSER (workflows/trading/parse_signal_enhanced.json): - Added MAGAP:([-\d.]+) regex pattern for negative number support - Extracts maGap from TradingView v9 alert messages - Returns maGap in parsed output (backward compatible with v8) - Updated comment to show v9 format API ENDPOINTS: - app/api/trading/check-risk/route.ts: Pass maGap to scoreSignalQuality (2 calls) - app/api/trading/execute/route.ts: Pass maGap to scoreSignalQuality (2 calls) FULL PIPELINE NOW COMPLETE: 1. TradingView v9 → Generates signal with MAGAP field 2. n8n webhook → Extracts maGap from alert message 3. Backend scoring → Evaluates MA gap convergence (+8 to +15 pts) 4. Quality threshold → Borderline signals (75-85) can reach 91+ 5. Execute decision → Only signals scoring ≥91 are executed MOTIVATION: Helps borderline quality signals reach execution threshold without overriding safety rules. Addresses Nov 25 missed opportunity where good signal had MA convergence but borderline quality score. TESTING REQUIRED: - Verify n8n parses MAGAP correctly from v9 alerts - Confirm backend receives maGap parameter - Validate MA gap scoring applied to quality calculation - Monitor first 10-20 v9 signals for scoring accuracy
25 lines
3.1 KiB
JSON
25 lines
3.1 KiB
JSON
{
|
|
"name": "Parse Signal Enhanced",
|
|
"nodes": [
|
|
{
|
|
"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)\\b/i);\nconst symbol = symbolMatch ? symbolMatch[1].toUpperCase() + '-PERP' : 'SOL-PERP';\n\nconst direction = body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long';\n\n// Enhanced timeframe extraction supporting multiple formats:\n// - \"buy 5\" → \"5\"\n// - \"buy 15\" → \"15\"\n// - \"buy 60\" or \"buy 1h\" → \"60\"\n// - \"buy 240\" or \"buy 4h\" → \"240\"\n// - \"buy D\" or \"buy 1d\" → \"D\"\n// - \"buy W\" → \"W\"\nconst timeframeMatch = body.match(/\\b(buy|sell)\\s+(\\d+|D|W|M|1h|4h|1d)\\b/i);\nlet timeframe = '5'; // Default to 5min\n\nif (timeframeMatch) {\n const tf = timeframeMatch[2];\n // Convert hour/day notation to minutes\n if (tf === '1h' || tf === '60') {\n timeframe = '60';\n } else if (tf === '4h' || tf === '240') {\n timeframe = '240';\n } else if (tf === '1d' || tf.toUpperCase() === 'D') {\n timeframe = 'D';\n } else if (tf.toUpperCase() === 'W') {\n timeframe = 'W';\n } else if (tf.toUpperCase() === 'M') {\n timeframe = 'M';\n } else {\n timeframe = tf;\n }\n}\n\n// Parse new context metrics from enhanced format:\n// \"SOLT.P buy 15 | ATR:0.65 | ADX:14.3 | RSI:51.3 | VOL:0.87 | POS:59.3 | MAGAP:-1.23 | IND:v9\"\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\n// V9: Parse MA gap (optional, backward compatible with v8)\nconst maGapMatch = body.match(/MAGAP:([-\\d.]+)/);\nconst maGap = maGapMatch ? parseFloat(maGapMatch[1]) : undefined;\n\n// Parse indicator version (optional, backward compatible)\nconst indicatorVersionMatch = body.match(/IND:(v\\d+)/i);\nconst indicatorVersion = indicatorVersionMatch ? indicatorVersionMatch[1] : 'v5';\n\nreturn {\n rawMessage: body,\n symbol,\n direction,\n timeframe,\n // Context fields\n atr,\n adx,\n rsi,\n volumeRatio,\n pricePosition,\n maGap, // V9 NEW\n // Version tracking (defaults to v5 for backward compatibility)\n indicatorVersion\n};"
|
|
},
|
|
"id": "parse-signal-enhanced",
|
|
"name": "Parse Signal Enhanced",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [600, 300]
|
|
}
|
|
],
|
|
"connections": {},
|
|
"settings": {
|
|
"executionOrder": "v1"
|
|
},
|
|
"staticData": null,
|
|
"tags": [],
|
|
"triggerCount": 0,
|
|
"updatedAt": "2025-10-30T00:00:00.000Z",
|
|
"versionId": "1"
|
|
}
|