{
  "id": "HrTD222kWpvKsy2j",
  "name": "Binance SM Indicators Webhook Tool",
  "tags": [],
  "nodes": [
    {
      "id": "61cb3bd7-60ba-4dcf-a080-146b7612a99b",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1080,
        0
      ],
      "parameters": {
        "url": "https://api.binance.com/api/v3/klines",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "symbol",
              "value": "={{$json.body.symbol}}"
            },
            {
              "name": "interval",
              "value": "15m"
            },
            {
              "name": "limit",
              "value": "40"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "b372f57e-917a-42d2-964c-25a296ea982d",
      "name": "Webhook 15m Indicators",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1460,
        0
      ],
      "parameters": {
        "path": "39cc366c-af5f-472a-9d48-bbe30a4fe3ea",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "46b937d5-7338-457f-b2f0-eb85ac44a8bb",
      "name": "Merge Into 1 Array",
      "type": "n8n-nodes-base.code",
      "position": [
        -740,
        0
      ],
      "parameters": {
        "jsCode": "const klines = $input.all().map(item => item.json);\nreturn [{ json: { klines } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "5b8c1049-bcf4-41d4-bba3-5f76e940bd3e",
      "name": "Calculate Bollinger Bands",
      "type": "n8n-nodes-base.code",
      "position": [
        -280,
        -460
      ],
      "parameters": {
        "jsCode": "// Get raw klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20;\nconst stdMultiplier = 2;\n\n// Validate type\nif (!Array.isArray(klines)) {\n  throw new Error(\"klines is not an array\");\n}\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Ensure enough data\nif (closes.length < period) {\n  throw new Error(`Not enough data: got ${closes.length}, expected at least ${period}`);\n}\n\n// BB calculation logic\nfunction calculateBB(prices, period) {\n  const result = [];\n  for (let i = period - 1; i < prices.length; i++) {\n    const slice = prices.slice(i - period + 1, i + 1);\n    const mean = slice.reduce((sum, val) => sum + val, 0) / period;\n    const variance = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / period;\n    const stdDev = Math.sqrt(variance);\n\n    result.push({\n      close: prices[i],\n      basis: mean,\n      upper: mean + stdMultiplier * stdDev,\n      lower: mean - stdMultiplier * stdDev,\n      timestamp: klines[i][0]\n    });\n  }\n  return result;\n}\n\n// Calculate bands\nconst bands = calculateBB(closes, period);\n\n// Format output\nreturn bands.map(b => ({\n  json: {\n    timestamp: b.timestamp,\n    close: b.close,\n    bb_basis: b.basis,\n    bb_upper: b.upper,\n    bb_lower: b.lower\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "b0391e28-03a3-40f0-8182-72df0928ca2c",
      "name": "Calculate RSI",
      "type": "n8n-nodes-base.code",
      "position": [
        -280,
        -240
      ],
      "parameters": {
        "jsCode": "// Pull klines array from JSON\nconst klines = $input.first().json.klines;\n\n// === CONFIGURATION ===\nconst period = 14; // RSI period\n\n// === Extract closing prices ===\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// === Ensure enough data ===\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for RSI. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// === RSI Calculation ===\nfunction calculateRSI(prices, period) {\n  const result = [];\n\n  for (let i = period; i < prices.length; i++) {\n    let gains = 0;\n    let losses = 0;\n\n    for (let j = i - period + 1; j <= i; j++) {\n      const change = prices[j] - prices[j - 1];\n      if (change >= 0) gains += change;\n      else losses -= change;\n    }\n\n    const avgGain = gains / period;\n    const avgLoss = losses / period;\n\n    const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;\n    const rsi = avgLoss === 0 ? 100 : 100 - (100 / (1 + rs));\n\n    result.push({\n      timestamp: klines[i][0],\n      close: prices[i],\n      rsi: rsi\n    });\n  }\n\n  return result;\n}\n\n// === Run RSI ===\nconst rsiSeries = calculateRSI(closes, period);\n\n// === Return formatted RSI output ===\nreturn rsiSeries.map(r => ({\n  json: {\n    timestamp: r.timestamp,\n    close: r.close,\n    rsi: r.rsi\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "2c598960-630c-4687-91c0-bfae10da1943",
      "name": "Calculate MACD",
      "type": "n8n-nodes-base.code",
      "position": [
        -280,
        -40
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Parameters\nconst fastPeriod = 12;\nconst slowPeriod = 26;\nconst signalPeriod = 9;\n\n// Validate data length\nconst minRequired = slowPeriod + signalPeriod;\nif (closes.length < minRequired) {\n  throw new Error(`Not enough data for MACD. Need at least ${minRequired}, got ${closes.length}`);\n}\n\n// === Helper: EMA function ===\nfunction calculateEMA(prices, period) {\n  const k = 2 / (period + 1);\n  const ema = [prices.slice(0, period).reduce((a, b) => a + b, 0) / period];\n\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 Core Calculation ===\nconst slowEMA = calculateEMA(closes, slowPeriod);\nconst fastEMA = calculateEMA(closes.slice(slowPeriod - fastPeriod), fastPeriod);\n\n// Align lengths\nconst alignedFastEMA = fastEMA.slice(fastEMA.length - slowEMA.length);\nconst macdLine = alignedFastEMA.map((val, i) => val - slowEMA[i]);\n\n// Signal line\nconst signalLine = calculateEMA(macdLine, signalPeriod);\n\n// Histogram\nconst histogram = macdLine.slice(signalPeriod - 1).map((macd, i) => macd - signalLine[i]);\n\n// Final output start index\nconst startIndex = closes.length - histogram.length;\n\nconst result = [];\n\nfor (let i = 0; i < histogram.length; i++) {\n  const idx = startIndex + i;\n  result.push({\n    timestamp: klines[idx]?.[0] || null,\n    close: closes[idx],\n    macd: macdLine[i + signalPeriod - 1],\n    signal: signalLine[i],\n    histogram: histogram[i]\n  });\n}\n\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "c50f7fa5-2707-47a4-9b32-1d582bf5d600",
      "name": "Calculate SMA",
      "type": "n8n-nodes-base.code",
      "position": [
        -280,
        200
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 50, 100, 200, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute SMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// Calculate SMA values\nconst result = [];\n\nfor (let i = period - 1; i < closes.length; i++) {\n  const slice = closes.slice(i - period + 1, i + 1);\n  const sum = slice.reduce((a, b) => a + b, 0);\n  const average = sum / period;\n\n  result.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    sma: average\n  });\n}\n\n// Return formatted output\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "25ef2964-c9b1-4b9e-b6ce-3766b215d163",
      "name": "Calculate EMA",
      "type": "n8n-nodes-base.code",
      "position": [
        -280,
        420
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 9, 12, 26, 50, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute EMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// EMA Calculation\nconst k = 2 / (period + 1);\nconst ema = [];\n\n// Start with SMA of first period\nlet sma = closes.slice(0, period).reduce((a, b) => a + b, 0) / period;\nema.push({\n  timestamp: klines[period - 1][0],\n  close: closes[period - 1],\n  ema: sma\n});\n\n// Continue EMA calculation\nfor (let i = period; i < closes.length; i++) {\n  const value = closes[i] * k + ema[ema.length - 1].ema * (1 - k);\n  ema.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    ema: value\n  });\n}\n\n// Return result\nreturn ema.map(e => ({\n  json: e\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "df6546be-3778-4fba-801a-870c0fe883c3",
      "name": "Calculate ADX",
      "type": "n8n-nodes-base.code",
      "position": [
        -280,
        660
      ],
      "parameters": {
        "jsCode": "// Get kline array from merged item\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 14;\n\n// Parse high, low, close arrays\nconst highs = klines.map(k => parseFloat(k[2]));\nconst lows = klines.map(k => parseFloat(k[3]));\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Validation\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for ADX. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// Initialize arrays\nconst tr = [];\nconst plusDM = [];\nconst minusDM = [];\n\n// Step 1: Calculate TR, +DM, -DM\nfor (let i = 1; i < klines.length; i++) {\n  const highDiff = highs[i] - highs[i - 1];\n  const lowDiff = lows[i - 1] - lows[i];\n  const upMove = highDiff > 0 && highDiff > lowDiff ? highDiff : 0;\n  const downMove = lowDiff > 0 && lowDiff > highDiff ? lowDiff : 0;\n\n  const trueRange = Math.max(\n    highs[i] - lows[i],\n    Math.abs(highs[i] - closes[i - 1]),\n    Math.abs(lows[i] - closes[i - 1])\n  );\n\n  tr.push(trueRange);\n  plusDM.push(upMove);\n  minusDM.push(downMove);\n}\n\n// Step 2: Smooth TR, +DM, -DM and calculate DI\nconst smoothedTR = [tr.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedPlusDM = [plusDM.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedMinusDM = [minusDM.slice(0, period).reduce((a, b) => a + b, 0)];\n\nfor (let i = period; i < tr.length; i++) {\n  smoothedTR.push(smoothedTR[smoothedTR.length - 1] - smoothedTR[smoothedTR.length - 1] / period + tr[i]);\n  smoothedPlusDM.push(smoothedPlusDM[smoothedPlusDM.length - 1] - smoothedPlusDM[smoothedPlusDM.length - 1] / period + plusDM[i]);\n  smoothedMinusDM.push(smoothedMinusDM[smoothedMinusDM.length - 1] - smoothedMinusDM[smoothedMinusDM.length - 1] / period + minusDM[i]);\n}\n\nconst plusDI = smoothedPlusDM.map((val, i) => 100 * val / smoothedTR[i]);\nconst minusDI = smoothedMinusDM.map((val, i) => 100 * val / smoothedTR[i]);\n\n// Step 3: Calculate DX\nconst dx = plusDI.map((val, i) => {\n  const diff = Math.abs(plusDI[i] - minusDI[i]);\n  const sum = plusDI[i] + minusDI[i];\n  return sum === 0 ? 0 : 100 * (diff / sum);\n});\n\n// Step 4: Smooth DX into ADX\nconst adx = [];\nconst firstADX = dx.slice(0, period).reduce((a, b) => a + b, 0) / period;\nadx.push(firstADX);\n\nfor (let i = period; i < dx.length; i++) {\n  const newADX = (adx[adx.length - 1] * (period - 1) + dx[i]) / period;\n  adx.push(newADX);\n}\n\n// Step 5: Final result formatting\nconst output = [];\n\nfor (let i = 0; i < adx.length; i++) {\n  const index = i + (2 * period); // account for offset\n  if (klines[index]) {\n    output.push({\n      timestamp: klines[index][0],\n      close: closes[index],\n      adx: adx[i],\n      plusDI: plusDI[i + period],\n      minusDI: minusDI[i + period]\n    });\n  }\n}\n\nreturn output.map(r => ({ json: r }));"
      },
      "typeVersion": 2
    },
    {
      "id": "5ea4756f-a20a-444c-8471-57d209457823",
      "name": "Merge 15 min Indicators",
      "type": "n8n-nodes-base.merge",
      "position": [
        180,
        -80
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "0a46ea7c-a85d-4fc6-b20c-7e65ab962ea6",
      "name": "Respond to 15m Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        560,
        -20
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "40fcbba9-489b-4452-baf8-9a41ef0f9b07",
      "name": "Webhook 1h Indicators",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -3220,
        1760
      ],
      "parameters": {
        "path": "78948764-5cdb-4808-8ef9-2155f10dd721",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "39e6b058-d800-4751-b75b-5e62be379c61",
      "name": "Respond to 1h Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1060,
        1740
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "af471a36-1107-4ccd-be14-efa06ba27330",
      "name": "Merge 1h Indicators",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1460,
        1680
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "d7d9bb31-6986-42e6-9f0c-7d7f00d66ee1",
      "name": "Merge Into 1 Array 1h",
      "type": "n8n-nodes-base.code",
      "position": [
        -2540,
        1760
      ],
      "parameters": {
        "jsCode": "const klines = $input.all().map(item => item.json);\nreturn [{ json: { klines } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "a2e9d933-47d7-47f5-8d49-224860d8600a",
      "name": "Calculate Bollinger Bands(1h)",
      "type": "n8n-nodes-base.code",
      "position": [
        -2060,
        1220
      ],
      "parameters": {
        "jsCode": "// Get raw klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20;\nconst stdMultiplier = 2;\n\n// Validate type\nif (!Array.isArray(klines)) {\n  throw new Error(\"klines is not an array\");\n}\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Ensure enough data\nif (closes.length < period) {\n  throw new Error(`Not enough data: got ${closes.length}, expected at least ${period}`);\n}\n\n// BB calculation logic\nfunction calculateBB(prices, period) {\n  const result = [];\n  for (let i = period - 1; i < prices.length; i++) {\n    const slice = prices.slice(i - period + 1, i + 1);\n    const mean = slice.reduce((sum, val) => sum + val, 0) / period;\n    const variance = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / period;\n    const stdDev = Math.sqrt(variance);\n\n    result.push({\n      close: prices[i],\n      basis: mean,\n      upper: mean + stdMultiplier * stdDev,\n      lower: mean - stdMultiplier * stdDev,\n      timestamp: klines[i][0]\n    });\n  }\n  return result;\n}\n\n// Calculate bands\nconst bands = calculateBB(closes, period);\n\n// Format output\nreturn bands.map(b => ({\n  json: {\n    timestamp: b.timestamp,\n    close: b.close,\n    bb_basis: b.basis,\n    bb_upper: b.upper,\n    bb_lower: b.lower\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "702e8bc8-d8f0-4fb4-b44f-fd19479c8b03",
      "name": "Calculate RSI(1h)",
      "type": "n8n-nodes-base.code",
      "position": [
        -2060,
        1500
      ],
      "parameters": {
        "jsCode": "// Pull klines array from JSON\nconst klines = $input.first().json.klines;\n\n// === CONFIGURATION ===\nconst period = 14; // RSI period\n\n// === Extract closing prices ===\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// === Ensure enough data ===\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for RSI. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// === RSI Calculation ===\nfunction calculateRSI(prices, period) {\n  const result = [];\n\n  for (let i = period; i < prices.length; i++) {\n    let gains = 0;\n    let losses = 0;\n\n    for (let j = i - period + 1; j <= i; j++) {\n      const change = prices[j] - prices[j - 1];\n      if (change >= 0) gains += change;\n      else losses -= change;\n    }\n\n    const avgGain = gains / period;\n    const avgLoss = losses / period;\n\n    const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;\n    const rsi = avgLoss === 0 ? 100 : 100 - (100 / (1 + rs));\n\n    result.push({\n      timestamp: klines[i][0],\n      close: prices[i],\n      rsi: rsi\n    });\n  }\n\n  return result;\n}\n\n// === Run RSI ===\nconst rsiSeries = calculateRSI(closes, period);\n\n// === Return formatted RSI output ===\nreturn rsiSeries.map(r => ({\n  json: {\n    timestamp: r.timestamp,\n    close: r.close,\n    rsi: r.rsi\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "d16b7829-59ca-45ca-a5ae-4e315168c96d",
      "name": "Calculate MACD(1h)",
      "type": "n8n-nodes-base.code",
      "position": [
        -2060,
        1720
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Parameters\nconst fastPeriod = 12;\nconst slowPeriod = 26;\nconst signalPeriod = 9;\n\n// Validate data length\nconst minRequired = slowPeriod + signalPeriod;\nif (closes.length < minRequired) {\n  throw new Error(`Not enough data for MACD. Need at least ${minRequired}, got ${closes.length}`);\n}\n\n// === Helper: EMA function ===\nfunction calculateEMA(prices, period) {\n  const k = 2 / (period + 1);\n  const ema = [prices.slice(0, period).reduce((a, b) => a + b, 0) / period];\n\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 Core Calculation ===\nconst slowEMA = calculateEMA(closes, slowPeriod);\nconst fastEMA = calculateEMA(closes.slice(slowPeriod - fastPeriod), fastPeriod);\n\n// Align lengths\nconst alignedFastEMA = fastEMA.slice(fastEMA.length - slowEMA.length);\nconst macdLine = alignedFastEMA.map((val, i) => val - slowEMA[i]);\n\n// Signal line\nconst signalLine = calculateEMA(macdLine, signalPeriod);\n\n// Histogram\nconst histogram = macdLine.slice(signalPeriod - 1).map((macd, i) => macd - signalLine[i]);\n\n// Final output start index\nconst startIndex = closes.length - histogram.length;\n\nconst result = [];\n\nfor (let i = 0; i < histogram.length; i++) {\n  const idx = startIndex + i;\n  result.push({\n    timestamp: klines[idx]?.[0] || null,\n    close: closes[idx],\n    macd: macdLine[i + signalPeriod - 1],\n    signal: signalLine[i],\n    histogram: histogram[i]\n  });\n}\n\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "15bf5c3b-de07-412b-988c-f5c59fa7a06f",
      "name": "Calculate SMA(1h)",
      "type": "n8n-nodes-base.code",
      "position": [
        -2060,
        1960
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 50, 100, 200, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute SMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// Calculate SMA values\nconst result = [];\n\nfor (let i = period - 1; i < closes.length; i++) {\n  const slice = closes.slice(i - period + 1, i + 1);\n  const sum = slice.reduce((a, b) => a + b, 0);\n  const average = sum / period;\n\n  result.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    sma: average\n  });\n}\n\n// Return formatted output\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "83fa487a-1a67-4059-9007-d5298d8bb105",
      "name": "Calculate EMA(1h)",
      "type": "n8n-nodes-base.code",
      "position": [
        -2060,
        2220
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 9, 12, 26, 50, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute EMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// EMA Calculation\nconst k = 2 / (period + 1);\nconst ema = [];\n\n// Start with SMA of first period\nlet sma = closes.slice(0, period).reduce((a, b) => a + b, 0) / period;\nema.push({\n  timestamp: klines[period - 1][0],\n  close: closes[period - 1],\n  ema: sma\n});\n\n// Continue EMA calculation\nfor (let i = period; i < closes.length; i++) {\n  const value = closes[i] * k + ema[ema.length - 1].ema * (1 - k);\n  ema.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    ema: value\n  });\n}\n\n// Return result\nreturn ema.map(e => ({\n  json: e\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "7301ba32-ab0c-4c80-b041-cd398f7daa2c",
      "name": "Calculate ADX1",
      "type": "n8n-nodes-base.code",
      "position": [
        -2060,
        2420
      ],
      "parameters": {
        "jsCode": "// Get kline array from merged item\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 14;\n\n// Parse high, low, close arrays\nconst highs = klines.map(k => parseFloat(k[2]));\nconst lows = klines.map(k => parseFloat(k[3]));\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Validation\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for ADX. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// Initialize arrays\nconst tr = [];\nconst plusDM = [];\nconst minusDM = [];\n\n// Step 1: Calculate TR, +DM, -DM\nfor (let i = 1; i < klines.length; i++) {\n  const highDiff = highs[i] - highs[i - 1];\n  const lowDiff = lows[i - 1] - lows[i];\n  const upMove = highDiff > 0 && highDiff > lowDiff ? highDiff : 0;\n  const downMove = lowDiff > 0 && lowDiff > highDiff ? lowDiff : 0;\n\n  const trueRange = Math.max(\n    highs[i] - lows[i],\n    Math.abs(highs[i] - closes[i - 1]),\n    Math.abs(lows[i] - closes[i - 1])\n  );\n\n  tr.push(trueRange);\n  plusDM.push(upMove);\n  minusDM.push(downMove);\n}\n\n// Step 2: Smooth TR, +DM, -DM and calculate DI\nconst smoothedTR = [tr.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedPlusDM = [plusDM.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedMinusDM = [minusDM.slice(0, period).reduce((a, b) => a + b, 0)];\n\nfor (let i = period; i < tr.length; i++) {\n  smoothedTR.push(smoothedTR[smoothedTR.length - 1] - smoothedTR[smoothedTR.length - 1] / period + tr[i]);\n  smoothedPlusDM.push(smoothedPlusDM[smoothedPlusDM.length - 1] - smoothedPlusDM[smoothedPlusDM.length - 1] / period + plusDM[i]);\n  smoothedMinusDM.push(smoothedMinusDM[smoothedMinusDM.length - 1] - smoothedMinusDM[smoothedMinusDM.length - 1] / period + minusDM[i]);\n}\n\nconst plusDI = smoothedPlusDM.map((val, i) => 100 * val / smoothedTR[i]);\nconst minusDI = smoothedMinusDM.map((val, i) => 100 * val / smoothedTR[i]);\n\n// Step 3: Calculate DX\nconst dx = plusDI.map((val, i) => {\n  const diff = Math.abs(plusDI[i] - minusDI[i]);\n  const sum = plusDI[i] + minusDI[i];\n  return sum === 0 ? 0 : 100 * (diff / sum);\n});\n\n// Step 4: Smooth DX into ADX\nconst adx = [];\nconst firstADX = dx.slice(0, period).reduce((a, b) => a + b, 0) / period;\nadx.push(firstADX);\n\nfor (let i = period; i < dx.length; i++) {\n  const newADX = (adx[adx.length - 1] * (period - 1) + dx[i]) / period;\n  adx.push(newADX);\n}\n\n// Step 5: Final result formatting\nconst output = [];\n\nfor (let i = 0; i < adx.length; i++) {\n  const index = i + (2 * period); // account for offset\n  if (klines[index]) {\n    output.push({\n      timestamp: klines[index][0],\n      close: closes[index],\n      adx: adx[i],\n      plusDI: plusDI[i + period],\n      minusDI: minusDI[i + period]\n    });\n  }\n}\n\nreturn output.map(r => ({ json: r }));"
      },
      "typeVersion": 2
    },
    {
      "id": "9ec777b1-eb23-43b0-8bb3-726adcc3418f",
      "name": "HTTP Request 1h",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2880,
        1760
      ],
      "parameters": {
        "url": "https://api.binance.com/api/v3/klines",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "symbol",
              "value": "={{$json.body.symbol}}"
            },
            {
              "name": "interval",
              "value": "1h"
            },
            {
              "name": "limit",
              "value": "40"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "c4a718f8-f2d6-464b-8120-1e4067d760ad",
      "name": "Webhook 4h Indicators",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -40,
        1680
      ],
      "parameters": {
        "path": "55fd9665-ed4a-4e1a-8062-890cad0fb6ac",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "bbb81a2a-b1c4-4c1e-80e9-a4990c45fae8",
      "name": "HTTP Request 4h",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        300,
        1680
      ],
      "parameters": {
        "url": "https://api.binance.com/api/v3/klines",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "symbol",
              "value": "={{$json.body.symbol}}"
            },
            {
              "name": "interval",
              "value": "4h"
            },
            {
              "name": "limit",
              "value": "40"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "20e58706-035f-46ef-b235-78fe468875bc",
      "name": "Merge Into 1 Array 4h",
      "type": "n8n-nodes-base.code",
      "position": [
        640,
        1680
      ],
      "parameters": {
        "jsCode": "const klines = $input.all().map(item => item.json);\nreturn [{ json: { klines } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1675168b-97ab-4be4-92d2-734d075e2222",
      "name": "Calculate Bollinger Bands(4h)",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        1180
      ],
      "parameters": {
        "jsCode": "// Get raw klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20;\nconst stdMultiplier = 2;\n\n// Validate type\nif (!Array.isArray(klines)) {\n  throw new Error(\"klines is not an array\");\n}\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Ensure enough data\nif (closes.length < period) {\n  throw new Error(`Not enough data: got ${closes.length}, expected at least ${period}`);\n}\n\n// BB calculation logic\nfunction calculateBB(prices, period) {\n  const result = [];\n  for (let i = period - 1; i < prices.length; i++) {\n    const slice = prices.slice(i - period + 1, i + 1);\n    const mean = slice.reduce((sum, val) => sum + val, 0) / period;\n    const variance = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / period;\n    const stdDev = Math.sqrt(variance);\n\n    result.push({\n      close: prices[i],\n      basis: mean,\n      upper: mean + stdMultiplier * stdDev,\n      lower: mean - stdMultiplier * stdDev,\n      timestamp: klines[i][0]\n    });\n  }\n  return result;\n}\n\n// Calculate bands\nconst bands = calculateBB(closes, period);\n\n// Format output\nreturn bands.map(b => ({\n  json: {\n    timestamp: b.timestamp,\n    close: b.close,\n    bb_basis: b.basis,\n    bb_upper: b.upper,\n    bb_lower: b.lower\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "d1c2fcc7-65ac-446c-8f59-c4cd8ac7d795",
      "name": "Calculate RSI(4h)",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        1420
      ],
      "parameters": {
        "jsCode": "// Pull klines array from JSON\nconst klines = $input.first().json.klines;\n\n// === CONFIGURATION ===\nconst period = 14; // RSI period\n\n// === Extract closing prices ===\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// === Ensure enough data ===\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for RSI. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// === RSI Calculation ===\nfunction calculateRSI(prices, period) {\n  const result = [];\n\n  for (let i = period; i < prices.length; i++) {\n    let gains = 0;\n    let losses = 0;\n\n    for (let j = i - period + 1; j <= i; j++) {\n      const change = prices[j] - prices[j - 1];\n      if (change >= 0) gains += change;\n      else losses -= change;\n    }\n\n    const avgGain = gains / period;\n    const avgLoss = losses / period;\n\n    const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;\n    const rsi = avgLoss === 0 ? 100 : 100 - (100 / (1 + rs));\n\n    result.push({\n      timestamp: klines[i][0],\n      close: prices[i],\n      rsi: rsi\n    });\n  }\n\n  return result;\n}\n\n// === Run RSI ===\nconst rsiSeries = calculateRSI(closes, period);\n\n// === Return formatted RSI output ===\nreturn rsiSeries.map(r => ({\n  json: {\n    timestamp: r.timestamp,\n    close: r.close,\n    rsi: r.rsi\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "df32e55c-2dbc-44d6-8161-f70db651cfcd",
      "name": "Calculate MACD(4h)",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        1660
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Parameters\nconst fastPeriod = 12;\nconst slowPeriod = 26;\nconst signalPeriod = 9;\n\n// Validate data length\nconst minRequired = slowPeriod + signalPeriod;\nif (closes.length < minRequired) {\n  throw new Error(`Not enough data for MACD. Need at least ${minRequired}, got ${closes.length}`);\n}\n\n// === Helper: EMA function ===\nfunction calculateEMA(prices, period) {\n  const k = 2 / (period + 1);\n  const ema = [prices.slice(0, period).reduce((a, b) => a + b, 0) / period];\n\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 Core Calculation ===\nconst slowEMA = calculateEMA(closes, slowPeriod);\nconst fastEMA = calculateEMA(closes.slice(slowPeriod - fastPeriod), fastPeriod);\n\n// Align lengths\nconst alignedFastEMA = fastEMA.slice(fastEMA.length - slowEMA.length);\nconst macdLine = alignedFastEMA.map((val, i) => val - slowEMA[i]);\n\n// Signal line\nconst signalLine = calculateEMA(macdLine, signalPeriod);\n\n// Histogram\nconst histogram = macdLine.slice(signalPeriod - 1).map((macd, i) => macd - signalLine[i]);\n\n// Final output start index\nconst startIndex = closes.length - histogram.length;\n\nconst result = [];\n\nfor (let i = 0; i < histogram.length; i++) {\n  const idx = startIndex + i;\n  result.push({\n    timestamp: klines[idx]?.[0] || null,\n    close: closes[idx],\n    macd: macdLine[i + signalPeriod - 1],\n    signal: signalLine[i],\n    histogram: histogram[i]\n  });\n}\n\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "93a115f7-e876-43f0-8ecb-70a47a06f41c",
      "name": "Calculate SMA(4h)",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        1880
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 50, 100, 200, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute SMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// Calculate SMA values\nconst result = [];\n\nfor (let i = period - 1; i < closes.length; i++) {\n  const slice = closes.slice(i - period + 1, i + 1);\n  const sum = slice.reduce((a, b) => a + b, 0);\n  const average = sum / period;\n\n  result.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    sma: average\n  });\n}\n\n// Return formatted output\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "17bf4e9f-7053-4a04-b439-3e23fe5cb7f9",
      "name": "Calculate EMA(4h)",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        2100
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 9, 12, 26, 50, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute EMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// EMA Calculation\nconst k = 2 / (period + 1);\nconst ema = [];\n\n// Start with SMA of first period\nlet sma = closes.slice(0, period).reduce((a, b) => a + b, 0) / period;\nema.push({\n  timestamp: klines[period - 1][0],\n  close: closes[period - 1],\n  ema: sma\n});\n\n// Continue EMA calculation\nfor (let i = period; i < closes.length; i++) {\n  const value = closes[i] * k + ema[ema.length - 1].ema * (1 - k);\n  ema.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    ema: value\n  });\n}\n\n// Return result\nreturn ema.map(e => ({\n  json: e\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "d61db420-9def-4ec1-bf8a-94afeabda186",
      "name": "Calculate ADX (4h)",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        2360
      ],
      "parameters": {
        "jsCode": "// Get kline array from merged item\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 14;\n\n// Parse high, low, close arrays\nconst highs = klines.map(k => parseFloat(k[2]));\nconst lows = klines.map(k => parseFloat(k[3]));\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Validation\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for ADX. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// Initialize arrays\nconst tr = [];\nconst plusDM = [];\nconst minusDM = [];\n\n// Step 1: Calculate TR, +DM, -DM\nfor (let i = 1; i < klines.length; i++) {\n  const highDiff = highs[i] - highs[i - 1];\n  const lowDiff = lows[i - 1] - lows[i];\n  const upMove = highDiff > 0 && highDiff > lowDiff ? highDiff : 0;\n  const downMove = lowDiff > 0 && lowDiff > highDiff ? lowDiff : 0;\n\n  const trueRange = Math.max(\n    highs[i] - lows[i],\n    Math.abs(highs[i] - closes[i - 1]),\n    Math.abs(lows[i] - closes[i - 1])\n  );\n\n  tr.push(trueRange);\n  plusDM.push(upMove);\n  minusDM.push(downMove);\n}\n\n// Step 2: Smooth TR, +DM, -DM and calculate DI\nconst smoothedTR = [tr.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedPlusDM = [plusDM.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedMinusDM = [minusDM.slice(0, period).reduce((a, b) => a + b, 0)];\n\nfor (let i = period; i < tr.length; i++) {\n  smoothedTR.push(smoothedTR[smoothedTR.length - 1] - smoothedTR[smoothedTR.length - 1] / period + tr[i]);\n  smoothedPlusDM.push(smoothedPlusDM[smoothedPlusDM.length - 1] - smoothedPlusDM[smoothedPlusDM.length - 1] / period + plusDM[i]);\n  smoothedMinusDM.push(smoothedMinusDM[smoothedMinusDM.length - 1] - smoothedMinusDM[smoothedMinusDM.length - 1] / period + minusDM[i]);\n}\n\nconst plusDI = smoothedPlusDM.map((val, i) => 100 * val / smoothedTR[i]);\nconst minusDI = smoothedMinusDM.map((val, i) => 100 * val / smoothedTR[i]);\n\n// Step 3: Calculate DX\nconst dx = plusDI.map((val, i) => {\n  const diff = Math.abs(plusDI[i] - minusDI[i]);\n  const sum = plusDI[i] + minusDI[i];\n  return sum === 0 ? 0 : 100 * (diff / sum);\n});\n\n// Step 4: Smooth DX into ADX\nconst adx = [];\nconst firstADX = dx.slice(0, period).reduce((a, b) => a + b, 0) / period;\nadx.push(firstADX);\n\nfor (let i = period; i < dx.length; i++) {\n  const newADX = (adx[adx.length - 1] * (period - 1) + dx[i]) / period;\n  adx.push(newADX);\n}\n\n// Step 5: Final result formatting\nconst output = [];\n\nfor (let i = 0; i < adx.length; i++) {\n  const index = i + (2 * period); // account for offset\n  if (klines[index]) {\n    output.push({\n      timestamp: klines[index][0],\n      close: closes[index],\n      adx: adx[i],\n      plusDI: plusDI[i + period],\n      minusDI: minusDI[i + period]\n    });\n  }\n}\n\nreturn output.map(r => ({ json: r }));"
      },
      "typeVersion": 2
    },
    {
      "id": "7940bc3f-c5f4-4b4b-b542-8737f17afbbf",
      "name": "Merge 4h Indicators",
      "type": "n8n-nodes-base.merge",
      "position": [
        1720,
        1600
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "8585a269-b70d-4af7-b264-db9f638c8a77",
      "name": "Respond to 4h Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2120,
        1660
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "91a05310-bd36-4ee0-8381-b3d16c3346be",
      "name": "Webhook 1d Indicators",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1600,
        3140
      ],
      "parameters": {
        "path": "23c8ec04-5aec-49c6-9c2c-c352ccec11b4",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "29121e2e-f6e2-4d42-8e5b-e8eea1e61ec8",
      "name": "HTTP Request 1d",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1260,
        3140
      ],
      "parameters": {
        "url": "https://api.binance.com/api/v3/klines",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "symbol",
              "value": "={{$json.body.symbol}}"
            },
            {
              "name": "interval",
              "value": "1d"
            },
            {
              "name": "limit",
              "value": "40"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "1be26316-3d08-4983-9d12-6bc36465e76e",
      "name": "Merge Into 1 Array 1d",
      "type": "n8n-nodes-base.code",
      "position": [
        -900,
        3140
      ],
      "parameters": {
        "jsCode": "const klines = $input.all().map(item => item.json);\nreturn [{ json: { klines } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "6e9c7976-737b-4377-b412-633a1a67097b",
      "name": "Calculate Bollinger Bands(1d)",
      "type": "n8n-nodes-base.code",
      "position": [
        -440,
        2620
      ],
      "parameters": {
        "jsCode": "// Get raw klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20;\nconst stdMultiplier = 2;\n\n// Validate type\nif (!Array.isArray(klines)) {\n  throw new Error(\"klines is not an array\");\n}\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Ensure enough data\nif (closes.length < period) {\n  throw new Error(`Not enough data: got ${closes.length}, expected at least ${period}`);\n}\n\n// BB calculation logic\nfunction calculateBB(prices, period) {\n  const result = [];\n  for (let i = period - 1; i < prices.length; i++) {\n    const slice = prices.slice(i - period + 1, i + 1);\n    const mean = slice.reduce((sum, val) => sum + val, 0) / period;\n    const variance = slice.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / period;\n    const stdDev = Math.sqrt(variance);\n\n    result.push({\n      close: prices[i],\n      basis: mean,\n      upper: mean + stdMultiplier * stdDev,\n      lower: mean - stdMultiplier * stdDev,\n      timestamp: klines[i][0]\n    });\n  }\n  return result;\n}\n\n// Calculate bands\nconst bands = calculateBB(closes, period);\n\n// Format output\nreturn bands.map(b => ({\n  json: {\n    timestamp: b.timestamp,\n    close: b.close,\n    bb_basis: b.basis,\n    bb_upper: b.upper,\n    bb_lower: b.lower\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "ac84877a-75cf-4ef2-bad1-8d0f444ecead",
      "name": "Calculate RSI(1d)",
      "type": "n8n-nodes-base.code",
      "position": [
        -440,
        2880
      ],
      "parameters": {
        "jsCode": "// Pull klines array from JSON\nconst klines = $input.first().json.klines;\n\n// === CONFIGURATION ===\nconst period = 14; // RSI period\n\n// === Extract closing prices ===\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// === Ensure enough data ===\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for RSI. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// === RSI Calculation ===\nfunction calculateRSI(prices, period) {\n  const result = [];\n\n  for (let i = period; i < prices.length; i++) {\n    let gains = 0;\n    let losses = 0;\n\n    for (let j = i - period + 1; j <= i; j++) {\n      const change = prices[j] - prices[j - 1];\n      if (change >= 0) gains += change;\n      else losses -= change;\n    }\n\n    const avgGain = gains / period;\n    const avgLoss = losses / period;\n\n    const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;\n    const rsi = avgLoss === 0 ? 100 : 100 - (100 / (1 + rs));\n\n    result.push({\n      timestamp: klines[i][0],\n      close: prices[i],\n      rsi: rsi\n    });\n  }\n\n  return result;\n}\n\n// === Run RSI ===\nconst rsiSeries = calculateRSI(closes, period);\n\n// === Return formatted RSI output ===\nreturn rsiSeries.map(r => ({\n  json: {\n    timestamp: r.timestamp,\n    close: r.close,\n    rsi: r.rsi\n  }\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "a5da65c5-db5a-4f9d-a6cf-228f081ffb3c",
      "name": "Calculate MACD(1d)",
      "type": "n8n-nodes-base.code",
      "position": [
        -440,
        3120
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Parameters\nconst fastPeriod = 12;\nconst slowPeriod = 26;\nconst signalPeriod = 9;\n\n// Validate data length\nconst minRequired = slowPeriod + signalPeriod;\nif (closes.length < minRequired) {\n  throw new Error(`Not enough data for MACD. Need at least ${minRequired}, got ${closes.length}`);\n}\n\n// === Helper: EMA function ===\nfunction calculateEMA(prices, period) {\n  const k = 2 / (period + 1);\n  const ema = [prices.slice(0, period).reduce((a, b) => a + b, 0) / period];\n\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 Core Calculation ===\nconst slowEMA = calculateEMA(closes, slowPeriod);\nconst fastEMA = calculateEMA(closes.slice(slowPeriod - fastPeriod), fastPeriod);\n\n// Align lengths\nconst alignedFastEMA = fastEMA.slice(fastEMA.length - slowEMA.length);\nconst macdLine = alignedFastEMA.map((val, i) => val - slowEMA[i]);\n\n// Signal line\nconst signalLine = calculateEMA(macdLine, signalPeriod);\n\n// Histogram\nconst histogram = macdLine.slice(signalPeriod - 1).map((macd, i) => macd - signalLine[i]);\n\n// Final output start index\nconst startIndex = closes.length - histogram.length;\n\nconst result = [];\n\nfor (let i = 0; i < histogram.length; i++) {\n  const idx = startIndex + i;\n  result.push({\n    timestamp: klines[idx]?.[0] || null,\n    close: closes[idx],\n    macd: macdLine[i + signalPeriod - 1],\n    signal: signalLine[i],\n    histogram: histogram[i]\n  });\n}\n\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "5b1f34b3-e675-46b1-a754-679080680058",
      "name": "Calculate SMA(1d)",
      "type": "n8n-nodes-base.code",
      "position": [
        -440,
        3340
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 50, 100, 200, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute SMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// Calculate SMA values\nconst result = [];\n\nfor (let i = period - 1; i < closes.length; i++) {\n  const slice = closes.slice(i - period + 1, i + 1);\n  const sum = slice.reduce((a, b) => a + b, 0);\n  const average = sum / period;\n\n  result.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    sma: average\n  });\n}\n\n// Return formatted output\nreturn result.map(r => ({\n  json: r\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "0b3278a6-5793-4d70-8954-70562c43a3eb",
      "name": "Calculate EMA(1d)",
      "type": "n8n-nodes-base.code",
      "position": [
        -440,
        3580
      ],
      "parameters": {
        "jsCode": "// Get klines array\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 20; // Change to 9, 12, 26, 50, etc. as needed\n\n// Extract closing prices\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Check for sufficient data\nif (closes.length < period) {\n  throw new Error(`Not enough data to compute EMA. Need at least ${period}, got ${closes.length}`);\n}\n\n// EMA Calculation\nconst k = 2 / (period + 1);\nconst ema = [];\n\n// Start with SMA of first period\nlet sma = closes.slice(0, period).reduce((a, b) => a + b, 0) / period;\nema.push({\n  timestamp: klines[period - 1][0],\n  close: closes[period - 1],\n  ema: sma\n});\n\n// Continue EMA calculation\nfor (let i = period; i < closes.length; i++) {\n  const value = closes[i] * k + ema[ema.length - 1].ema * (1 - k);\n  ema.push({\n    timestamp: klines[i][0],\n    close: closes[i],\n    ema: value\n  });\n}\n\n// Return result\nreturn ema.map(e => ({\n  json: e\n}));"
      },
      "typeVersion": 2
    },
    {
      "id": "28dd6fb1-a161-49ed-9c14-740e88e2195d",
      "name": "Calculate ADX (1d)",
      "type": "n8n-nodes-base.code",
      "position": [
        -440,
        3780
      ],
      "parameters": {
        "jsCode": "// Get kline array from merged item\nconst klines = $input.first().json.klines;\n\n// Parameters\nconst period = 14;\n\n// Parse high, low, close arrays\nconst highs = klines.map(k => parseFloat(k[2]));\nconst lows = klines.map(k => parseFloat(k[3]));\nconst closes = klines.map(k => parseFloat(k[4]));\n\n// Validation\nif (closes.length < period + 1) {\n  throw new Error(`Not enough data for ADX. Need at least ${period + 1}, got ${closes.length}`);\n}\n\n// Initialize arrays\nconst tr = [];\nconst plusDM = [];\nconst minusDM = [];\n\n// Step 1: Calculate TR, +DM, -DM\nfor (let i = 1; i < klines.length; i++) {\n  const highDiff = highs[i] - highs[i - 1];\n  const lowDiff = lows[i - 1] - lows[i];\n  const upMove = highDiff > 0 && highDiff > lowDiff ? highDiff : 0;\n  const downMove = lowDiff > 0 && lowDiff > highDiff ? lowDiff : 0;\n\n  const trueRange = Math.max(\n    highs[i] - lows[i],\n    Math.abs(highs[i] - closes[i - 1]),\n    Math.abs(lows[i] - closes[i - 1])\n  );\n\n  tr.push(trueRange);\n  plusDM.push(upMove);\n  minusDM.push(downMove);\n}\n\n// Step 2: Smooth TR, +DM, -DM and calculate DI\nconst smoothedTR = [tr.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedPlusDM = [plusDM.slice(0, period).reduce((a, b) => a + b, 0)];\nconst smoothedMinusDM = [minusDM.slice(0, period).reduce((a, b) => a + b, 0)];\n\nfor (let i = period; i < tr.length; i++) {\n  smoothedTR.push(smoothedTR[smoothedTR.length - 1] - smoothedTR[smoothedTR.length - 1] / period + tr[i]);\n  smoothedPlusDM.push(smoothedPlusDM[smoothedPlusDM.length - 1] - smoothedPlusDM[smoothedPlusDM.length - 1] / period + plusDM[i]);\n  smoothedMinusDM.push(smoothedMinusDM[smoothedMinusDM.length - 1] - smoothedMinusDM[smoothedMinusDM.length - 1] / period + minusDM[i]);\n}\n\nconst plusDI = smoothedPlusDM.map((val, i) => 100 * val / smoothedTR[i]);\nconst minusDI = smoothedMinusDM.map((val, i) => 100 * val / smoothedTR[i]);\n\n// Step 3: Calculate DX\nconst dx = plusDI.map((val, i) => {\n  const diff = Math.abs(plusDI[i] - minusDI[i]);\n  const sum = plusDI[i] + minusDI[i];\n  return sum === 0 ? 0 : 100 * (diff / sum);\n});\n\n// Step 4: Smooth DX into ADX\nconst adx = [];\nconst firstADX = dx.slice(0, period).reduce((a, b) => a + b, 0) / period;\nadx.push(firstADX);\n\nfor (let i = period; i < dx.length; i++) {\n  const newADX = (adx[adx.length - 1] * (period - 1) + dx[i]) / period;\n  adx.push(newADX);\n}\n\n// Step 5: Final result formatting\nconst output = [];\n\nfor (let i = 0; i < adx.length; i++) {\n  const index = i + (2 * period); // account for offset\n  if (klines[index]) {\n    output.push({\n      timestamp: klines[index][0],\n      close: closes[index],\n      adx: adx[i],\n      plusDI: plusDI[i + period],\n      minusDI: minusDI[i + period]\n    });\n  }\n}\n\nreturn output.map(r => ({ json: r }));"
      },
      "typeVersion": 2
    },
    {
      "id": "660dfcb4-ed63-4c53-806b-cba96c105fee",
      "name": "Merge 1d Indicators",
      "type": "n8n-nodes-base.merge",
      "position": [
        160,
        3060
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "939665ce-7a50-4a6d-9b37-62ad800350b0",
      "name": "Respond to 1d Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        560,
        3120
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "2f44a150-0c5a-4b74-92b5-fe941fccb86d",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        100,
        -340
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## Technical Indicator API Merge \n\n**Binance API** for 6 indicators:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "fbf6424d-8bfc-469b-ae56-c9824e657e75",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1640,
        1340
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## Technical Indicator API Merge \n\n**Binance API** for 6 indicators:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "fba15cd9-e7a0-4a03-813e-444dd2cd25f7",
      "name": "Sticky Note14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1540,
        1380
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## Technical Indicator API Merge \n\n**Binance API** for 6 indicators:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "ab87eb1b-3048-4717-af97-b7fa92c0f260",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        2760
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## Technical Indicator API Merge \n\n**Binance API** for 6 indicators:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "b3abceb1-3605-4cca-8b4e-1f85f792fff2",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1540,
        -500
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (15minData)\n\n\u2022 **Entry point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "2143ce8a-8746-4187-bc43-c9cc294da7b8",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3300,
        1240
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (1hourData)\n\n\u2022 **Entry point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "d9150fb0-42b5-48e8-b123-e588672a653f",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -100,
        1200
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (4hourData)\n\n\u2022 **Entry point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "50737e51-ef67-42af-bf15-ac228c750f4d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1680,
        2640
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (1dayData)\n\n\u2022 **Entry point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "61972dcf-2bd5-454e-880b-0d3e97c82f98",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -480
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (15minData)\n\n\u2022 **Exit point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "c545e86b-8f90-4e3b-8e72-25f56de0b214",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2020,
        1200
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (4hourData)\n\n\u2022 **Exit point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "9e838383-1c81-45c6-98f1-f735dc5789df",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1140,
        1260
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (1hourData)\n\n\u2022 **Exit point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "f05d2def-9593-46c5-862d-378a5d1f64d4",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        2660
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (1dayData)\n\n\u2022 **Exit point** for external workflows calling indicator batches\n\n\u2022 Orchestrates trigger for **indicator API pull + formatter nodes**\n\n\u2022 Returns **cleaned batch** back via Respond to Webhook node\n\n\ud83d\udcce Notes:\n\n\u2022 Must pass through correct **webhook** path\n\n\u2022 Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "ceb5f8e0-e51f-4554-a752-a319033c14c4",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1160,
        -300
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 520,
        "content": "## Binance API Calls 15m \n\n\u2022 Triggers **Binance API** for k-line data:\n\n\n\u2022 Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "8e1aeb88-3232-4ea7-85c3-49a75097341b",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2940,
        1480
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 500,
        "content": "## Binance API Calls 1h \n\n\u2022 Triggers **Binance API** for k-line data:\n\n\n\u2022 Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "53996473-bdd7-44d3-9060-96a2f072077c",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        1420
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 500,
        "content": "## Binance API Calls 4h \n\n\u2022 Triggers **Binance API** for k-line data:\n\n\n\u2022 Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "6b138f55-5f0e-4844-a570-47919c8888b5",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1340,
        2920
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 420,
        "content": "## Binance API Calls 1d \n\n\u2022 Triggers **Binance API** for k-line data:\n\n\n\u2022 Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "8d8bd17f-c437-4e57-a517-3b6eb44eeff7",
      "name": "Sticky Note16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2120,
        720
      ],
      "parameters": {
        "color": 2,
        "height": 1860,
        "content": "## Calculate Technical Indicators \n \u2003\n\n\u2022 Ensures consistent **downstream format for merge and reasoning**\n\n\ud83d\udcce Notes:\n\n\u2022 **Format logic** is customized per indicator\n\n\u2022 Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "ee6e6788-7b9d-4d29-9cdd-0af5256289ef",
      "name": "Sticky Note17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -360,
        -860
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1720,
        "content": "## Calculate Technical Indicators \n \u2003\n\n\u2022 Ensures consistent **downstream format for merge and reasoning**\n\n\ud83d\udcce Notes:\n\n\u2022 **Format logic** is customized per indicator\n\n\u2022 Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "e53bcd92-c603-43f3-b25f-0d7dd46afca0",
      "name": "Sticky Note18",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        720
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1820,
        "content": "## Calculate Technical Indicators \n \u2003\n\n\u2022 Ensures consistent **downstream format for merge and reasoning**\n\n\ud83d\udcce Notes:\n\n\u2022 **Format logic** is customized per indicator\n\n\u2022 Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "7d27698c-bbc5-4faf-aa41-4c4332abb484",
      "name": "Sticky Note19",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -520,
        2180
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1800,
        "content": "## Calculate Technical Indicators \n \u2003\n\n\u2022 Ensures consistent **downstream format for merge and reasoning**\n\n\ud83d\udcce Notes:\n\n\u2022 **Format logic** is customized per indicator\n\n\u2022 Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "309f22d1-aeb2-4d48-a7c4-deaad51070e3",
      "name": "Sticky Note20",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -820,
        -240
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 460,
        "content": "## API Merge \n\n**Binance API** for k-line Data to calculate:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "5a27535c-5bc9-4718-b895-5a8eccf37974",
      "name": "Sticky Note21",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2600,
        1560
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 420,
        "content": "## API Merge \n\n**Binance API** for k-line Data to calculate:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "52180c40-a1d3-4c41-aea6-73f62f7c98ee",
      "name": "Sticky Note22",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -980,
        2920
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 460,
        "content": "## API Merge \n\n**Binance API** for k-line Data to calculate:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "08697498-181c-417f-8027-8d6de1471bed",
      "name": "Sticky Note23",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        1480
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 440,
        "content": "## API Merge \n\n**Binance API** for k-line Data to calculate:\n\n\u2022 **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "773a2b4f-f716-41ab-ba18-1412e2dd50e5",
      "name": "Sticky Note24",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2800,
        -60
      ],
      "parameters": {
        "width": 1460,
        "height": 3020,
        "content": "# \ud83e\uddea Binance SM Indicators Webhook Tool \u2013 Documentation\n\nA backend webhook processor that performs **real-time indicator calculations** for 15m, 1h, 4h, and 1d Binance candlestick data. This workflow powers all the indicator sub-agents and is essential for maintaining modular and scalable logic across timeframes.\n\n---\n\n## \ud83c\udfaf Purpose\n\n* Accepts HTTP POST requests from the 15m, 1h, 4h, or 1d sub-agents\n* Pulls raw OHLCV kline data for the symbol and interval\n* Calculates technical indicators:\n\n  * RSI, MACD, Bollinger Bands, SMA, EMA, ADX\n* Returns structured JSON with labeled values (e.g., `\"MACD_Cross\": \"Bullish\"`)\n\nThis tool ensures **centralized logic reuse** across all timeframe agents while improving speed and maintainability.\n\n---\n\n## \u2699\ufe0f Key Components\n\n| Node Name              | Description                                               |\n| ---------------------- | --------------------------------------------------------- |\n| `Webhook Trigger`      | Handles POST at `/15m-indicators`, `/1h-indicators`, etc. |\n| `Extract Symbol`       | Validates input payload (must include `symbol`)           |\n| `Fetch Binance Klines` | Gets latest 100 candles using `/api/v3/klines`            |\n| `RSI Calculator`       | Computes 14-period RSI from closing prices                |\n| `MACD Calculator`      | Computes 12, 26, 9 MACD with signal and histogram         |\n| `BBANDS Calculator`    | Calculates 20-period bands using std. deviation           |\n| `SMA/EMA Node`         | Computes 20-period simple and exponential MAs             |\n| `ADX Calculator`       | Computes 14-period trend strength (DI+/DI\u2212 included)      |\n| `Prepare Output`       | Returns JSON with all labeled indicators                  |\n\n---\n\n## \ud83d\udce5 Expected Input (from sub-agent)\n\n```json\n{\n  \"symbol\": \"BTCUSDT\"\n}\n```\n\nMust be a valid Binance Spot trading pair. Interval is auto-routed based on webhook endpoint:\n\n| Endpoint Path     | Interval |\n| ----------------- | -------- |\n| `/15m-indicators` | 15m      |\n| `/1h-indicators`  | 1h       |\n| `/4h-indicators`  | 4h       |\n| `/1d-indicators`  | 1d       |\n\n---\n\n## \ud83d\udcca Sample Output JSON\n\n```json\n{\n  \"symbol\": \"BTCUSDT\",\n  \"interval\": \"4h\",\n  \"rsi\": 65.1,\n  \"macd\": {\n    \"value\": 24.3,\n    \"signal\": 21.7,\n    \"histogram\": 2.6,\n    \"cross\": \"Bullish\"\n  },\n  \"bb\": {\n    \"basis\": 62600,\n    \"upper\": 63500,\n    \"lower\": 61700,\n    \"status\": \"Expanding\"\n  },\n  \"sma\": 62000,\n  \"ema\": 62400,\n  \"adx\": {\n    \"value\": 32.8,\n    \"strength\": \"Strong\"\n  }\n}\n```\n\n---\n\n## \ud83e\udde9 Use Case Scenarios\n\n| Scenario                                   | Result                                                   |\n| ------------------------------------------ | -------------------------------------------------------- |\n| 15m tool POSTs to `/15m-indicators`        | Webhook responds with 15m indicator set                  |\n| Financial Analyst Tool requests 1h signals | This workflow handles the actual math for each indicator |\n| 1d tool needs RSI + MACD + ADX             | Centralized logic avoids duplication across agents       |\n\n---\n\n## \ud83d\udee0\ufe0f Installation Instructions\n\n### 1. Import & Activate\n\n* Import JSON into n8n\n* Ensure webhook is live and accessible\n* Test each endpoint (e.g., `/1h-indicators`) via POST\n\n### 2. Connect to Binance API\n\n* Public API: no credentials required\n* Ensure the HTTP request node for klines uses:\n\n  ```\n  https://api.binance.com/api/v3/klines\n  ```\n\n### 3. Link to Sub-Agent Workflows\n\n* 15m, 1h, 4h, 1d tools all depend on this for logic\n* Must POST `symbol` to the correct interval endpoint\n\n---\n\n## \ud83d\udce6 Example Workflow Integration\n\nAll the following tools rely on this:\n\n* `Binance SM 15min Indicators Tool`\n* `Binance SM 1hour Indicators Tool`\n* `Binance SM 4hour Indicators Tool`\n* `Binance SM 1day Indicators Tool`\n\nThis acts as the math engine behind each.\n\n---\n\n## \ud83d\udd10 Licensing & Support\n\n\ud83d\udd17 **Don Jayamaha \u2013 LinkedIn**\n[http://linkedin.com/in/donjayamahajr](http://linkedin.com/in/donjayamahajr)\n\n\u00a9 2025 Treasurium Capital Limited Company. All rights reserved.\nThe design, architecture, and indicator calculation code within this system are proprietary. Reuse, resale, or modification without license is strictly prohibited.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f7032997-bb7f-4e2a-9982-7ee18c95cfbe",
  "connections": {
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Merge Into 1 Array",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate ADX": {
      "main": [
        [
          {
            "node": "Merge 15 min Indicators",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "Calculate EMA": {
      "main": [
        [
          {
            "node": "Merge 15 min Indicators",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Calculate RSI": {
      "main": [
        [
          {
            "node": "Merge 15 min Indicators",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Calculate SMA": {
      "main": [
        [
          {
            "node": "Merge 15 min Indicators",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Calculate ADX1": {
      "main": [
        [
          {
            "node": "Merge 1h Indicators",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "Calculate MACD": {
      "main": [
        [
          {
            "node": "Merge 15 min Indicators",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "HTTP Request 1d": {
      "main": [
        [
          {
            "node": "Merge Into 1 Array 1d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request 1h": {
      "main": [
        [
          {
            "node": "Merge Into 1 Array 1h",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request 4h": {
      "main": [
        [
          {
            "node": "Merge Into 1 Array 4h",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate EMA(1d)": {
      "main": [
        [
          {
            "node": "Merge 1d Indicators",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Calculate EMA(1h)": {
      "main": [
        [
          {
            "node": "Merge 1h Indicators",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Calculate EMA(4h)": {
      "main": [
        [
          {
            "node": "Merge 4h Indicators",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Calculate RSI(1d)": {
      "main": [
        [
          {
            "node": "Merge 1d Indicators",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Calculate RSI(1h)": {
      "main": [
        [
          {
            "node": "Merge 1h Indicators",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Calculate RSI(4h)": {
      "main": [
        [
          {
            "node": "Merge 4h Indicators",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Calculate SMA(1d)": {
      "main": [
        [
          {
            "node": "Merge 1d Indicators",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Calculate SMA(1h)": {
      "main": [
        [
          {
            "node": "Merge 1h Indicators",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Calculate SMA(4h)": {
      "main": [
        [
          {
            "node": "Merge 4h Indicators",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Calculate ADX (1d)": {
      "main": [
        [
          {
            "node": "Merge 1d Indicators",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "Calculate ADX (4h)": {
      "main": [
        [
          {
            "node": "Merge 4h Indicators",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "Calculate MACD(1d)": {
      "main": [
        [
          {
            "node": "Merge 1d Indicators",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Calculate MACD(1h)": {
      "main": [
        [
          {
            "node": "Merge 1h Indicators",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Calculate MACD(4h)": {
      "main": [
        [
          {
            "node": "Merge 4h Indicators",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Merge Into 1 Array": {
      "main": [
        [
          {
            "node": "Calculate Bollinger Bands",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate RSI",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate MACD",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate SMA",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate EMA",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate ADX",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge 1d Indicators": {
      "main": [
        [
          {
            "node": "Respond to 1d Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge 1h Indicators": {
      "main": [
        [
          {
            "node": "Respond to 1h Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge 4h Indicators": {
      "main": [
        [
          {
            "node": "Respond to 4h Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Into 1 Array 1d": {
      "main": [
        [
          {
            "node": "Calculate Bollinger Bands(1d)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate RSI(1d)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate MACD(1d)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate SMA(1d)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate EMA(1d)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate ADX (1d)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Into 1 Array 1h": {
      "main": [
        [
          {
            "node": "Calculate Bollinger Bands(1h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate RSI(1h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate MACD(1h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate SMA(1h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate EMA(1h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate ADX1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Into 1 Array 4h": {
      "main": [
        [
          {
            "node": "Calculate Bollinger Bands(4h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate RSI(4h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate MACD(4h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate SMA(4h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate EMA(4h)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Calculate ADX (4h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook 1d Indicators": {
      "main": [
        [
          {
            "node": "HTTP Request 1d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook 1h Indicators": {
      "main": [
        [
          {
            "node": "HTTP Request 1h",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook 4h Indicators": {
      "main": [
        [
          {
            "node": "HTTP Request 4h",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook 15m Indicators": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge 15 min Indicators": {
      "main": [
        [
          {
            "node": "Respond to 15m Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Bollinger Bands": {
      "main": [
        [
          {
            "node": "Merge 15 min Indicators",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Bollinger Bands(1d)": {
      "main": [
        [
          {
            "node": "Merge 1d Indicators",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Bollinger Bands(1h)": {
      "main": [
        [
          {
            "node": "Merge 1h Indicators",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Bollinger Bands(4h)": {
      "main": [
        [
          {
            "node": "Merge 4h Indicators",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}