Created
October 22, 2025 17:07
-
-
Save vitqst/876e04fdde0562324f5fe98e6e28de2c 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 2 - RSI MACD Momentum", | |
| "nodes": [ | |
| { | |
| "parameters": {}, | |
| "id": "execute-workflow-trigger", | |
| "name": "When called by Layer 1", | |
| "type": "n8n-nodes-base.executeWorkflowTrigger", | |
| "typeVersion": 1, | |
| "position": [250, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "assignments": { | |
| "assignments": [ | |
| { | |
| "id": "layer1-data", | |
| "name": "layer1_data", | |
| "value": "={{ $json }}", | |
| "type": "object" | |
| }, | |
| { | |
| "id": "symbol", | |
| "name": "symbol", | |
| "value": "={{ $json.symbol }}", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "signal-from-layer1", | |
| "name": "signal_direction", | |
| "value": "={{ $json.signal }}", | |
| "type": "string" | |
| } | |
| ] | |
| } | |
| }, | |
| "id": "capture-layer1-data", | |
| "name": "Capture Layer 1 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": "15m" | |
| }, | |
| { | |
| "name": "limit", | |
| "value": "50" | |
| } | |
| ] | |
| }, | |
| "options": {} | |
| }, | |
| "id": "binance-15m-klines", | |
| "name": "Binance 15m Klines", | |
| "type": "n8n-nodes-base.httpRequest", | |
| "typeVersion": 4.2, | |
| "position": [690, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "mode": "runOnceForAllItems", | |
| "language": "javaScript", | |
| "jsCode": "// Get Layer 1 data\nconst layer1Data = $('Capture Layer 1 Data').first().json;\nconst symbol = layer1Data.symbol;\nconst signalDirection = layer1Data.signal_direction;\n\n// Get 15m klines - collect all items\nconst allItems = $input.all();\nconst klines = allItems.map(item => item.json);\n\nconsole.log('Layer 2: Processing', klines.length, 'candles for', symbol);\nconsole.log('Signal from Layer 1:', 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\n// ===== RSI CALCULATION (14-period) =====\nfunction calculateRSI(prices, period = 14) {\n if (prices.length < period + 1) return [];\n \n const results = [];\n let gains = [];\n let losses = [];\n \n // Calculate initial average gain/loss\n for (let i = 1; i <= period; i++) {\n const change = prices[i] - prices[i - 1];\n gains.push(change > 0 ? change : 0);\n losses.push(change < 0 ? Math.abs(change) : 0);\n }\n \n let avgGain = gains.reduce((a, b) => a + b, 0) / period;\n let avgLoss = losses.reduce((a, b) => a + b, 0) / period;\n \n // Calculate first RSI\n let rs = avgGain / avgLoss;\n let rsi = 100 - (100 / (1 + rs));\n results.push({ index: period, value: rsi, avgGain, avgLoss });\n \n // Calculate subsequent RSI values using smoothed averages\n for (let i = period + 1; i < prices.length; i++) {\n const change = prices[i] - prices[i - 1];\n const gain = change > 0 ? change : 0;\n const loss = change < 0 ? Math.abs(change) : 0;\n \n avgGain = (avgGain * (period - 1) + gain) / period;\n avgLoss = (avgLoss * (period - 1) + loss) / period;\n \n rs = avgGain / avgLoss;\n rsi = 100 - (100 / (1 + rs));\n results.push({ index: i, value: rsi, avgGain, avgLoss });\n }\n \n return results;\n}\n\n// ===== EMA CALCULATION =====\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 // First valid MACD with signal\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 indicators\nconst rsiValues = calculateRSI(closePrices, 14);\nconst macdData = calculateMACD(closePrices);\n\nconsole.log('RSI values calculated:', rsiValues.length);\nconsole.log('MACD values calculated:', macdData.histogram.length);\n\n// Validate we have enough data\nif (rsiValues.length < 2 || macdData.histogram.length < 2) {\n return {\n json: {\n layer: 2,\n symbol: symbol,\n timeframe: '15m',\n error: 'NOT_ENOUGH_DATA',\n message: `Need more data. RSI: ${rsiValues.length}, MACD: ${macdData.histogram.length}`,\n layer1_signal: signalDirection,\n momentum_confirmed: false\n }\n };\n}\n\n// Get current values\nconst currentRSI = rsiValues[rsiValues.length - 1];\nconst previousRSI = rsiValues[rsiValues.length - 2];\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};\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\nconsole.log('Current RSI:', currentRSI.value);\nconsole.log('Current MACD histogram:', currentMACD.histogram);\n\n// Calculate volume confirmation (15m volume > avg of last 10 periods)\nconst recentVolumes = volumes.slice(-10);\nconst avgVolume = recentVolumes.reduce((a, b) => a + b, 0) / 10;\nconst volumeConfirmed = candles[candles.length - 1].volume > avgVolume;\n\nconsole.log('Volume confirmed:', volumeConfirmed);\n\n// ===== MOMENTUM CONFIRMATION LOGIC =====\nlet momentumConfirmed = false;\nlet confirmationReason = [];\n\nif (signalDirection === 'LONG') {\n // For LONG: RSI oversold, MACD turning up\n const rsiOversold = currentRSI.value < 40; // Relaxed from 30\n const rsiRising = currentRSI.value > previousRSI.value;\n const macdCrossUp = previousMACD.histogram < 0 && currentMACD.histogram > 0;\n const macdRising = currentMACD.histogram > previousMACD.histogram;\n const macdBullish = currentMACD.macd > currentMACD.signal || macdRising;\n \n if ((rsiOversold || rsiRising) && (macdBullish || macdCrossUp) && volumeConfirmed) {\n momentumConfirmed = true;\n confirmationReason.push('RSI favorable for LONG');\n if (macdCrossUp) confirmationReason.push('MACD bullish crossover');\n if (macdRising) confirmationReason.push('MACD histogram rising');\n }\n \n console.log('LONG checks - RSI:', currentRSI.value, 'MACD rising:', macdRising, 'Volume:', volumeConfirmed);\n}\nelse if (signalDirection === 'SHORT') {\n // For SHORT: RSI overbought, MACD turning down\n const rsiOverbought = currentRSI.value > 60; // Relaxed from 70\n const rsiFalling = currentRSI.value < previousRSI.value;\n const macdCrossDown = previousMACD.histogram > 0 && currentMACD.histogram < 0;\n const macdFalling = currentMACD.histogram < previousMACD.histogram;\n const macdBearish = currentMACD.macd < currentMACD.signal || macdFalling;\n \n if ((rsiOverbought || rsiFalling) && (macdBearish || macdCrossDown) && volumeConfirmed) {\n momentumConfirmed = true;\n confirmationReason.push('RSI favorable for SHORT');\n if (macdCrossDown) confirmationReason.push('MACD bearish crossover');\n if (macdFalling) confirmationReason.push('MACD histogram falling');\n }\n \n console.log('SHORT checks - RSI:', currentRSI.value, 'MACD falling:', macdFalling, 'Volume:', volumeConfirmed);\n}\n\n// ===== DIVERGENCE DETECTION =====\nfunction detectDivergence(prices, indicator, lookback = 5) {\n if (prices.length < lookback || indicator.length < lookback) return null;\n \n const recentPrices = prices.slice(-lookback);\n const recentIndicator = indicator.slice(-lookback);\n \n const priceHigh = Math.max(...recentPrices);\n const priceLow = Math.min(...recentPrices);\n const priceHighIdx = recentPrices.indexOf(priceHigh);\n const priceLowIdx = recentPrices.indexOf(priceLow);\n \n const indHigh = Math.max(...recentIndicator);\n const indLow = Math.min(...recentIndicator);\n const indHighIdx = recentIndicator.indexOf(indHigh);\n const indLowIdx = recentIndicator.indexOf(indLow);\n \n // Bullish divergence: price makes lower low, indicator makes higher low\n if (priceLowIdx > indLowIdx && recentPrices[recentPrices.length - 1] > priceLow) {\n return 'bullish';\n }\n \n // Bearish divergence: price makes higher high, indicator makes lower high\n if (priceHighIdx > indHighIdx && recentPrices[recentPrices.length - 1] < priceHigh) {\n return 'bearish';\n }\n \n return null;\n}\n\nconst rsiDivergence = detectDivergence(\n closePrices.slice(-15),\n rsiValues.slice(-15).map(r => r.value),\n 5\n);\n\nif (rsiDivergence) {\n console.log('RSI Divergence detected:', rsiDivergence);\n confirmationReason.push(`RSI ${rsiDivergence} divergence`);\n}\n\nconsole.log('Momentum confirmed:', momentumConfirmed);\nconsole.log('Reasons:', confirmationReason);\n\n// Return result\nreturn {\n json: {\n layer: 2,\n symbol: symbol,\n timeframe: '15m',\n timestamp: candles[candles.length - 1].closeTime,\n layer1_signal: signalDirection,\n layer1_data: layer1Data.layer1_data,\n momentum_confirmed: momentumConfirmed,\n confirmation_reasons: confirmationReason,\n indicators: {\n rsi: {\n current: currentRSI.value.toFixed(2),\n previous: previousRSI.value.toFixed(2),\n oversold: currentRSI.value < 30,\n overbought: currentRSI.value > 70\n },\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 crossover: (previousMACD.histogram < 0 && currentMACD.histogram > 0) ? 'bullish' :\n (previousMACD.histogram > 0 && currentMACD.histogram < 0) ? 'bearish' : 'none'\n },\n divergence: rsiDivergence\n },\n volume: {\n current: candles[candles.length - 1].volume,\n average: avgVolume,\n confirmed: volumeConfirmed\n },\n price: {\n current: candles[candles.length - 1].close\n },\n ready_for_layer3: momentumConfirmed && volumeConfirmed\n }\n};" | |
| }, | |
| "id": "calculate-rsi-macd", | |
| "name": "Calculate RSI MACD Volume", | |
| "type": "n8n-nodes-base.code", | |
| "typeVersion": 2, | |
| "position": [910, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "conditions": { | |
| "options": { | |
| "caseSensitive": true, | |
| "leftValue": "", | |
| "typeValidation": "strict" | |
| }, | |
| "conditions": [ | |
| { | |
| "id": "momentum-confirmed", | |
| "leftValue": "={{ $json.momentum_confirmed }}", | |
| "rightValue": true, | |
| "operator": { | |
| "type": "boolean", | |
| "operation": "true" | |
| } | |
| }, | |
| { | |
| "id": "volume-confirmed", | |
| "leftValue": "={{ $json.volume.confirmed }}", | |
| "rightValue": true, | |
| "operator": { | |
| "type": "boolean", | |
| "operation": "true" | |
| } | |
| } | |
| ], | |
| "combinator": "and" | |
| }, | |
| "options": {} | |
| }, | |
| "id": "check-momentum", | |
| "name": "Check Momentum Confirmed", | |
| "type": "n8n-nodes-base.if", | |
| "typeVersion": 2.1, | |
| "position": [1130, 300] | |
| }, | |
| { | |
| "parameters": { | |
| "assignments": { | |
| "assignments": [ | |
| { | |
| "id": "status", | |
| "name": "layer2_status", | |
| "value": "MOMENTUM_CONFIRMED", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "proceed", | |
| "name": "proceed_to_layer3", | |
| "value": true, | |
| "type": "boolean" | |
| }, | |
| { | |
| "id": "message", | |
| "name": "alert_message", | |
| "value": "=Layer 2: Momentum CONFIRMED for {{ $json.layer1_signal }} on {{ $json.symbol }}. RSI: {{ $json.indicators.rsi.current }}, MACD: {{ $json.indicators.macd.crossover }}", | |
| "type": "string" | |
| } | |
| ] | |
| } | |
| }, | |
| "id": "momentum-confirmed", | |
| "name": "Momentum Confirmed ✅", | |
| "type": "n8n-nodes-base.set", | |
| "typeVersion": 3.4, | |
| "position": [1350, 200] | |
| }, | |
| { | |
| "parameters": { | |
| "assignments": { | |
| "assignments": [ | |
| { | |
| "id": "status", | |
| "name": "layer2_status", | |
| "value": "MOMENTUM_REJECTED", | |
| "type": "string" | |
| }, | |
| { | |
| "id": "no-proceed", | |
| "name": "proceed_to_layer3", | |
| "value": false, | |
| "type": "boolean" | |
| }, | |
| { | |
| "id": "message", | |
| "name": "alert_message", | |
| "value": "=Layer 2: Momentum REJECTED for {{ $json.layer1_signal }} on {{ $json.symbol }}. RSI: {{ $json.indicators.rsi.current }}", | |
| "type": "string" | |
| } | |
| ] | |
| } | |
| }, | |
| "id": "momentum-rejected", | |
| "name": "Momentum Rejected ❌", | |
| "type": "n8n-nodes-base.set", | |
| "typeVersion": 3.4, | |
| "position": [1350, 400] | |
| } | |
| ], | |
| "connections": { | |
| "When called by Layer 1": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Capture Layer 1 Data", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Capture Layer 1 Data": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Binance 15m Klines", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Binance 15m Klines": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Calculate RSI MACD Volume", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Calculate RSI MACD Volume": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Check Momentum Confirmed", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| }, | |
| "Check Momentum Confirmed": { | |
| "main": [ | |
| [ | |
| { | |
| "node": "Momentum Confirmed ✅", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ], | |
| [ | |
| { | |
| "node": "Momentum Rejected ❌", | |
| "type": "main", | |
| "index": 0 | |
| } | |
| ] | |
| ] | |
| } | |
| }, | |
| "pinData": {} | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment