Created
October 22, 2025 17:16
-
-
Save vitqst/4ce7b7c3471960a3b84757e8f78f6203 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "Layer 3 - Final MACD Confirmation", | |
| "nodes": [ | |
| { | |
| "parameters": {}, | |
| "id": "execute-workflow-trigger-layer3", | |
| "name": "When called by Layer 2", | |
| "type": "n8n-nodes-base.executeWorkflowTrigger", | |
| "typeVersion": 1, | |
| "position": [250, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "assignments": { | |
| "assignments": [ | |
| { | |
| "id": "layer2-data", | |
| "name": "layer2_data", | |
| "value": "={{ $json }}", | |
| "type": "object" | |
| }, | |
| { | |
| "id": "symbol", | |
| "name": "symbol", | |
| "value": "={{ $json.symbol }}", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "signal-direction", | |
| "name": "signal_direction", | |
| "value": "={{ $json.layer1_signal }}", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "layer1-data", | |
| "name": "layer1_data", | |
| "value": "={{ $json.layer1_data }}", | |
| "type": "object" | |
| } | |
| ] | |
| } | |
| }, | |
| "id": "capture-layer2-data", | |
| "name": "Capture Layer 2 Data", | |
| "type": "n8n-nodes-base.set", | |
| "typeVersion": 3.4, | |
| "position": [470, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "method": "GET", | |
| "url": "https://api.binance.com/api/v3/klines", | |
| "authentication": "none", | |
| "sendQuery": true, | |
| "queryParameters": { | |
| "parameters": [ | |
| { | |
| "name": "symbol", | |
| "value": "={{ $json.symbol }}" | |
| }, | |
| { | |
| "name": "interval", | |
| "value": "5m" | |
| }, | |
| { | |
| "name": "limit", | |
| "value": "50" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "id": "binance-5m-klines", | |
| "name": "Binance 5m Klines", | |
| "type": "n8n-nodes-base.httpRequest", | |
| "typeVersion": 4.2, | |
| "position": [690, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "mode": "runOnceForAllItems", | |
| "language": "javaScript", | |
| "jsCode": "// Get Layer 2 data\nconst layer2Data = $('Capture Layer 2 Data').first().json;\nconst symbol = layer2Data.symbol;\nconst signalDirection = layer2Data.signal_direction;\nconst layer1Data = layer2Data.layer1_data;\n\n// Get 5m klines - collect all items\nconst allItems = $input.all();\nconst klines = allItems.map(item => item.json);\n\nconsole.log('Layer 3: Processing', klines.length, 'candles for', symbol);\nconsole.log('Signal direction:', signalDirection);\n\n// Parse candles\nconst candles = klines.map(k => ({\n openTime: parseInt(k[0]),\n open: parseFloat(k[1]),\n high: parseFloat(k[2]),\n low: parseFloat(k[3]),\n close: parseFloat(k[4]),\n volume: parseFloat(k[5]),\n closeTime: parseInt(k[6])\n}));\n\nconsole.log('Candles parsed:', candles.length);\n\n// ===== EMA CALCULATION (reuse from Layer 2) =====\nfunction calculateEMA(prices, period) {\n const k = 2 / (period + 1);\n const ema = [];\n \n // Start with SMA\n let sum = 0;\n for (let i = 0; i < period; i++) {\n sum += prices[i];\n }\n ema.push(sum / period);\n \n // Calculate EMA\n for (let i = period; i < prices.length; i++) {\n ema.push(prices[i] * k + ema[ema.length - 1] * (1 - k));\n }\n \n return ema;\n}\n\n// ===== MACD CALCULATION (12, 26, 9) =====\nfunction calculateMACD(prices) {\n const ema12 = calculateEMA(prices, 12);\n const ema26 = calculateEMA(prices, 26);\n \n // MACD line = EMA12 - EMA26\n const macdLine = [];\n const offset = 26 - 12;\n for (let i = 0; i < ema26.length; i++) {\n macdLine.push(ema12[i + offset] - ema26[i]);\n }\n \n // Signal line = 9-period EMA of MACD line\n const signalLine = calculateEMA(macdLine, 9);\n \n // Histogram = MACD - Signal\n const histogram = [];\n const signalOffset = macdLine.length - signalLine.length;\n for (let i = 0; i < signalLine.length; i++) {\n histogram.push(macdLine[i + signalOffset] - signalLine[i]);\n }\n \n return {\n macdLine,\n signalLine,\n histogram,\n startIndex: 26 + 9 - 1\n };\n}\n\n// Extract close prices and volumes\nconst closePrices = candles.map(c => c.close);\nconst volumes = candles.map(c => c.volume);\n\n// Calculate MACD on 5m timeframe\nconst macdData = calculateMACD(closePrices);\n\nconsole.log('MACD values calculated:', macdData.histogram.length);\n\n// Validate we have enough data\nif (macdData.histogram.length < 3) {\n return {\n json: {\n layer: 3,\n symbol: symbol,\n timeframe: '5m',\n error: 'NOT_ENOUGH_DATA',\n message: `Need more candles for MACD calculation. Got ${macdData.histogram.length} values`,\n signal_direction: signalDirection,\n final_confirmation: false\n }\n };\n}\n\n// Get current and previous MACD values\nconst currentMACD = {\n macd: macdData.macdLine[macdData.macdLine.length - 1],\n signal: macdData.signalLine[macdData.signalLine.length - 1],\n histogram: macdData.histogram[macdData.histogram.length - 1]\n};\n\nconst previousMACD = {\n macd: macdData.macdLine[macdData.macdLine.length - 2],\n signal: macdData.signalLine[macdData.signalLine.length - 2],\n histogram: macdData.histogram[macdData.histogram.length - 2]\n};\n\nconst twoPeriodsAgoMACD = {\n histogram: macdData.histogram[macdData.histogram.length - 3]\n};\n\nconsole.log('Current MACD:', currentMACD.macd.toFixed(4));\nconsole.log('Current Signal:', currentMACD.signal.toFixed(4));\nconsole.log('Current Histogram:', currentMACD.histogram.toFixed(4));\nconsole.log('Previous Histogram:', previousMACD.histogram.toFixed(4));\n\n// Calculate volume surge (5m volume > average of last 20 periods)\nconst recentVolumes = volumes.slice(-20);\nconst avgVolume = recentVolumes.reduce((a, b) => a + b, 0) / 20;\nconst currentVolume = candles[candles.length - 1].volume;\nconst volumeSurge = currentVolume > avgVolume;\nconst volumeMultiplier = (currentVolume / avgVolume).toFixed(2);\n\nconsole.log('Volume surge:', volumeSurge);\nconsole.log('Current volume:', currentVolume);\nconsole.log('Avg volume:', avgVolume);\nconsole.log('Volume multiplier:', volumeMultiplier);\n\n// ===== HISTOGRAM SLOPE CALCULATION =====\n// Check if histogram is increasing or decreasing\nconst histogramSlope = currentMACD.histogram - previousMACD.histogram;\nconst histogramTrend = histogramSlope > 0 ? 'increasing' : 'decreasing';\nconst histogramAccelerating = Math.abs(currentMACD.histogram - previousMACD.histogram) > \n Math.abs(previousMACD.histogram - twoPeriodsAgoMACD.histogram);\n\nconsole.log('Histogram slope:', histogramSlope.toFixed(4));\nconsole.log('Histogram trend:', histogramTrend);\nconsole.log('Histogram accelerating:', histogramAccelerating);\n\n// ===== FINAL VALIDATION LOGIC =====\nlet finalConfirmed = false;\nlet confirmationReasons = [];\nlet rejectionReasons = [];\n\nif (signalDirection === 'LONG') {\n // For LONG: MACD histogram increasing + MACD line above signal line + volume surge\n const histogramIncreasing = currentMACD.histogram > previousMACD.histogram;\n const macdAboveSignal = currentMACD.macd > currentMACD.signal;\n const histogramPositive = currentMACD.histogram > 0;\n \n console.log('LONG checks:');\n console.log('- Histogram increasing:', histogramIncreasing);\n console.log('- MACD above signal:', macdAboveSignal);\n console.log('- Histogram positive:', histogramPositive);\n console.log('- Volume surge:', volumeSurge);\n \n // All conditions must be true\n if (histogramIncreasing && volumeSurge) {\n if (macdAboveSignal || histogramPositive) {\n finalConfirmed = true;\n confirmationReasons.push('MACD histogram increasing');\n if (macdAboveSignal) confirmationReasons.push('MACD line above signal line');\n if (histogramPositive) confirmationReasons.push('MACD histogram positive');\n if (histogramAccelerating) confirmationReasons.push('Momentum accelerating');\n confirmationReasons.push('Volume surge confirmed');\n } else {\n rejectionReasons.push('MACD line below signal line');\n }\n } else {\n if (!histogramIncreasing) rejectionReasons.push('MACD histogram not increasing');\n if (!volumeSurge) rejectionReasons.push('No volume surge');\n }\n}\nelse if (signalDirection === 'SHORT') {\n // For SHORT: MACD histogram decreasing + MACD line below signal line + volume surge\n const histogramDecreasing = currentMACD.histogram < previousMACD.histogram;\n const macdBelowSignal = currentMACD.macd < currentMACD.signal;\n const histogramNegative = currentMACD.histogram < 0;\n \n console.log('SHORT checks:');\n console.log('- Histogram decreasing:', histogramDecreasing);\n console.log('- MACD below signal:', macdBelowSignal);\n console.log('- Histogram negative:', histogramNegative);\n console.log('- Volume surge:', volumeSurge);\n \n // All conditions must be true\n if (histogramDecreasing && volumeSurge) {\n if (macdBelowSignal || histogramNegative) {\n finalConfirmed = true;\n confirmationReasons.push('MACD histogram decreasing');\n if (macdBelowSignal) confirmationReasons.push('MACD line below signal line');\n if (histogramNegative) confirmationReasons.push('MACD histogram negative');\n if (histogramAccelerating) confirmationReasons.push('Momentum accelerating');\n confirmationReasons.push('Volume surge confirmed');\n } else {\n rejectionReasons.push('MACD line above signal line');\n }\n } else {\n if (!histogramDecreasing) rejectionReasons.push('MACD histogram not decreasing');\n if (!volumeSurge) rejectionReasons.push('No volume surge');\n }\n}\n\nconsole.log('Final confirmation:', finalConfirmed);\nconsole.log('Confirmation reasons:', confirmationReasons);\nconsole.log('Rejection reasons:', rejectionReasons);\n\n// ===== CALCULATE ENTRY/EXIT LEVELS =====\nconst currentPrice = candles[candles.length - 1].close;\nconst atr = calculateATR(candles, 14);\n\n// Simple ATR calculation\nfunction calculateATR(candles, period) {\n const trueRanges = [];\n for (let i = 1; i < candles.length; i++) {\n const high = candles[i].high;\n const low = candles[i].low;\n const prevClose = candles[i-1].close;\n const tr = Math.max(\n high - low,\n Math.abs(high - prevClose),\n Math.abs(low - prevClose)\n );\n trueRanges.push(tr);\n }\n const recentTR = trueRanges.slice(-period);\n return recentTR.reduce((a, b) => a + b, 0) / period;\n}\n\nconst stopLossDistance = atr * 2;\nconst takeProfitDistance = atr * 3;\n\nlet entryPrice, stopLoss, takeProfit1, takeProfit2;\n\nif (signalDirection === 'LONG') {\n entryPrice = currentPrice;\n stopLoss = currentPrice - stopLossDistance;\n takeProfit1 = currentPrice + takeProfitDistance;\n takeProfit2 = currentPrice + (takeProfitDistance * 2);\n} else if (signalDirection === 'SHORT') {\n entryPrice = currentPrice;\n stopLoss = currentPrice + stopLossDistance;\n takeProfit1 = currentPrice - takeProfitDistance;\n takeProfit2 = currentPrice - (takeProfitDistance * 2);\n}\n\nconst riskRewardRatio = Math.abs(takeProfit1 - entryPrice) / Math.abs(entryPrice - stopLoss);\n\nconsole.log('Entry price:', entryPrice);\nconsole.log('Stop loss:', stopLoss);\nconsole.log('Take profit 1:', takeProfit1);\nconsole.log('Risk/Reward ratio:', riskRewardRatio.toFixed(2));\n\n// ===== RETURN COMPLETE SIGNAL =====\nreturn {\n json: {\n // Layer 3 info\n layer: 3,\n symbol: symbol,\n timeframe: '5m',\n timestamp: candles[candles.length - 1].closeTime,\n \n // Signal info\n signal_direction: signalDirection,\n final_confirmation: finalConfirmed,\n confirmation_reasons: confirmationReasons,\n rejection_reasons: rejectionReasons,\n \n // Layer 3 indicators\n layer3_indicators: {\n macd: {\n macdLine: currentMACD.macd.toFixed(4),\n signalLine: currentMACD.signal.toFixed(4),\n histogram: currentMACD.histogram.toFixed(4),\n previousHistogram: previousMACD.histogram.toFixed(4),\n trend: histogramTrend,\n accelerating: histogramAccelerating\n },\n volume: {\n current: currentVolume,\n average: avgVolume,\n multiplier: volumeMultiplier,\n surge: volumeSurge\n }\n },\n \n // Trading levels\n trading_setup: {\n entry_price: entryPrice,\n stop_loss: stopLoss,\n take_profit_1: takeProfit1,\n take_profit_2: takeProfit2,\n risk_reward_ratio: riskRewardRatio.toFixed(2),\n atr: atr.toFixed(2)\n },\n \n // All layer data\n layer1_data: layer1Data,\n layer2_data: layer2Data.layer2_data,\n \n // Summary\n trade_quality: finalConfirmed ? 'HIGH' : 'REJECTED',\n all_layers_confirmed: finalConfirmed,\n confidence_score: finalConfirmed ? '3/3' : 'FAILED'\n }\n};" | |
| }, | |
| "id": "calculate-macd-volume-layer3", | |
| "name": "Calculate 5m MACD & Volume", | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [910, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": true, | |
| "leftValue": "", | |
| "typeValidation": "strict" | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "final-confirmed", | |
| "leftValue": "={{ $json.final_confirmation }}", | |
| "rightValue": true, | |
| "operator": { | |
| "type": "boolean", | |
| "operation": "true" | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "options": {} | |
| }, | |
| "id": "check-final-confirmation", | |
| "name": "Check Final Confirmation", | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.1, | |
| "position": [1130, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "assignments": { | |
| "assignments": [ | |
| { | |
| "id": "status", | |
| "name": "status", | |
| "value": "TRADE_SIGNAL_CONFIRMED", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "action", | |
| "name": "action", | |
| "value": "=EXECUTE_{{ $json.signal_direction }}_TRADE", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "message", | |
| "name": "message", | |
| "value": "=π― TRADE SIGNAL CONFIRMED!\n\nDirection: {{ $json.signal_direction }}\nSymbol: {{ $json.symbol }}\nConfidence: {{ $json.confidence_score }}\n\nπ Entry Setup:\nEntry: {{ $json.trading_setup.entry_price }}\nStop Loss: {{ $json.trading_setup.stop_loss }}\nTP1: {{ $json.trading_setup.take_profit_1 }}\nTP2: {{ $json.trading_setup.take_profit_2 }}\nR:R Ratio: {{ $json.trading_setup.risk_reward_ratio }}\n\nβ Confirmations:\n{{ $json.confirmation_reasons.join('\\n') }}\n\nβ° Time: {{ new Date().toISOString() }}", | |
| "type": "string" | |
| } | |
| ] | |
| } | |
| }, | |
| "id": "trade-confirmed", | |
| "name": "π― TRADE SIGNAL CONFIRMED", | |
| "type": "n8n-nodes-base.set", | |
| "typeVersion": 3.4, | |
| "position": [1350, 200] | |
| }, | |
| { | |
| "parameters": { | |
| "assignments": { | |
| "assignments": [ | |
| { | |
| "id": "status", | |
| "name": "status", | |
| "value": "TRADE_REJECTED", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "message", | |
| "name": "message", | |
| "value": "=β Trade Rejected at Layer 3\n\nSymbol: {{ $json.symbol }}\nDirection: {{ $json.signal_direction }}\n\nReasons:\n{{ $json.rejection_reasons.join('\\n') }}\n\nβ° Time: {{ new Date().toISOString() }}", | |
| "type": "string" | |
| } | |
| ] | |
| } | |
| }, | |
| "id": "trade-rejected", | |
| "name": "β Trade Rejected", | |
| "type": "n8n-nodes-base.set", | |
| "typeVersion": 3.4, | |
| "position": [1350, 400] | |
| } | |
| ], | |
| "connections": { | |
| "When called by Layer 2": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Capture Layer 2 Data", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Capture Layer 2 Data": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Binance 5m Klines", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Binance 5m Klines": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Calculate 5m MACD & Volume", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Calculate 5m MACD & Volume": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Check Final Confirmation", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Check Final Confirmation": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "π― TRADE SIGNAL CONFIRMED", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "β Trade Rejected", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| } | |
| }, | |
| "pinData": {} | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment