Skip to content

Instantly share code, notes, and snippets.

@vitqst
Created October 22, 2025 17:16
Show Gist options
  • Save vitqst/4ce7b7c3471960a3b84757e8f78f6203 to your computer and use it in GitHub Desktop.
Save vitqst/4ce7b7c3471960a3b84757e8f78f6203 to your computer and use it in GitHub Desktop.
{
"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