Outil de Webhook pour les indicateurs techniques Binance

Avancé

Ceci est unFinance, AIworkflow d'automatisation du domainecontenant 69 nœuds.Utilise principalement des nœuds comme Code, Merge, Webhook, HttpRequest, RespondToWebhook, combinant la technologie d'intelligence artificielle pour une automatisation intelligente. Outil de Webhook pour indicateurs techniques Binance

Prérequis
  • Point de terminaison HTTP Webhook (généré automatiquement par n8n)
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "id": "HrTD222kWpvKsy2j",
  "meta": {
    "instanceId": "a5283507e1917a33cc3ae615b2e7d5ad2c1e50955e6f831272ddd5ab816f3fb6"
  },
  "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": "Indicateurs Webhook 15m",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1460,
        0
      ],
      "webhookId": "39cc366c-af5f-472a-9d48-bbe30a4fe3ea",
      "parameters": {
        "path": "39cc366c-af5f-472a-9d48-bbe30a4fe3ea",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "46b937d5-7338-457f-b2f0-eb85ac44a8bb",
      "name": "Fusionner en 1 tableau",
      "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": "Calculer les Bandes de Bollinger",
      "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": "Calculer le 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": "Calculer le 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": "Calculer la 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": "Calculer l'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": "Calculer l'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": "Fusionner les indicateurs 15 min",
      "type": "n8n-nodes-base.merge",
      "position": [
        180,
        -80
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "0a46ea7c-a85d-4fc6-b20c-7e65ab962ea6",
      "name": "Répondre à Webhook 15m",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        560,
        -20
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "40fcbba9-489b-4452-baf8-9a41ef0f9b07",
      "name": "Indicateurs Webhook 1h",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -3220,
        1760
      ],
      "webhookId": "78948764-5cdb-4808-8ef9-2155f10dd721",
      "parameters": {
        "path": "78948764-5cdb-4808-8ef9-2155f10dd721",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "39e6b058-d800-4751-b75b-5e62be379c61",
      "name": "Répondre à Webhook 1h",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1060,
        1740
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "af471a36-1107-4ccd-be14-efa06ba27330",
      "name": "Fusionner les indicateurs 1h",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1460,
        1680
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "d7d9bb31-6986-42e6-9f0c-7d7f00d66ee1",
      "name": "Fusionner en 1 tableau 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": "Calculer les Bandes de Bollinger(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": "Calculer le 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": "Calculer le 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": "Calculer la 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": "Calculer l'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": "Calculer l'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": "Indicateurs Webhook 4h",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -40,
        1680
      ],
      "webhookId": "55fd9665-ed4a-4e1a-8062-890cad0fb6ac",
      "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": "Fusionner en 1 tableau 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": "Calculer les Bandes de Bollinger(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": "Calculer le 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": "Calculer le 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": "Calculer la 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": "Calculer l'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": "Calculer l'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": "Fusionner les indicateurs 4h",
      "type": "n8n-nodes-base.merge",
      "position": [
        1720,
        1600
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "8585a269-b70d-4af7-b264-db9f638c8a77",
      "name": "Répondre à Webhook 4h",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2120,
        1660
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "91a05310-bd36-4ee0-8381-b3d16c3346be",
      "name": "Indicateurs Webhook 1d",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1600,
        3140
      ],
      "webhookId": "23c8ec04-5aec-49c6-9c2c-c352ccec11b4",
      "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": "Fusionner en 1 tableau 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": "Calculer les Bandes de Bollinger(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": "Calculer le 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": "Calculer le 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": "Calculer la 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": "Calculer l'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": "Calculer l'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": "Fusionner les indicateurs 1d",
      "type": "n8n-nodes-base.merge",
      "position": [
        160,
        3060
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "939665ce-7a50-4a6d-9b37-62ad800350b0",
      "name": "Répondre à Webhook 1d",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        560,
        3120
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "2f44a150-0c5a-4b74-92b5-fe941fccb86d",
      "name": "Note adhésive13",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "fbf6424d-8bfc-469b-ae56-c9824e657e75",
      "name": "Note adhésive",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "fba15cd9-e7a0-4a03-813e-444dd2cd25f7",
      "name": "Note adhésive14",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "ab87eb1b-3048-4717-af97-b7fa92c0f260",
      "name": "Note adhésive15",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "b3abceb1-3605-4cca-8b4e-1f85f792fff2",
      "name": "Note adhésive1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1540,
        -500
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (15minData)\n\n• **Entry point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "2143ce8a-8746-4187-bc43-c9cc294da7b8",
      "name": "Note adhésive2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3300,
        1240
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (1hourData)\n\n• **Entry point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "d9150fb0-42b5-48e8-b123-e588672a653f",
      "name": "Note adhésive3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -100,
        1200
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (4hourData)\n\n• **Entry point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "50737e51-ef67-42af-bf15-ac228c750f4d",
      "name": "Note adhésive4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1680,
        2640
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks (1dayData)\n\n• **Entry point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "61972dcf-2bd5-454e-880b-0d3e97c82f98",
      "name": "Note adhésive5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -480
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (15minData)\n\n• **Exit point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "c545e86b-8f90-4e3b-8e72-25f56de0b214",
      "name": "Note adhésive6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2020,
        1200
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (4hourData)\n\n• **Exit point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "9e838383-1c81-45c6-98f1-f735dc5789df",
      "name": "Note adhésive7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1140,
        1260
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (1hourData)\n\n• **Exit point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "f05d2def-9593-46c5-862d-378a5d1f64d4",
      "name": "Note adhésive8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        2660
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks Respond (1dayData)\n\n• **Exit point** for external workflows calling indicator batches\n\n• Orchestrates trigger for **indicator API pull + formatter nodes**\n\n• Returns **cleaned batch** back via Respond to Webhook node\n\n📎 Notes:\n\n• Must pass through correct **webhook** path\n\n• Responds after **merge with all 6 formatted outputs** per timeframe"
      },
      "typeVersion": 1
    },
    {
      "id": "ceb5f8e0-e51f-4554-a752-a319033c14c4",
      "name": "Note adhésive9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1160,
        -300
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 520,
        "content": "## Binance API Calls 15m \n\n• Triggers **Binance API** for k-line data:\n\n\n• Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "8e1aeb88-3232-4ea7-85c3-49a75097341b",
      "name": "Note adhésive10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2940,
        1480
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 500,
        "content": "## Binance API Calls 1h \n\n• Triggers **Binance API** for k-line data:\n\n\n• Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "53996473-bdd7-44d3-9060-96a2f072077c",
      "name": "Note adhésive11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        1420
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 500,
        "content": "## Binance API Calls 4h \n\n• Triggers **Binance API** for k-line data:\n\n\n• Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "6b138f55-5f0e-4844-a570-47919c8888b5",
      "name": "Note adhésive12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1340,
        2920
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 420,
        "content": "## Binance API Calls 1d \n\n• Triggers **Binance API** for k-line data:\n\n\n• Each node pulls and returns **raw API data** for formatting"
      },
      "typeVersion": 1
    },
    {
      "id": "8d8bd17f-c437-4e57-a517-3b6eb44eeff7",
      "name": "Note adhésive16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2120,
        720
      ],
      "parameters": {
        "color": 2,
        "height": 1860,
        "content": "## Calculate Technical Indicators \n  \n\n• Ensures consistent **downstream format for merge and reasoning**\n\n📎 Notes:\n\n• **Format logic** is customized per indicator\n\n• Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "ee6e6788-7b9d-4d29-9cdd-0af5256289ef",
      "name": "Note adhésive17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -360,
        -860
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1720,
        "content": "## Calculate Technical Indicators \n  \n\n• Ensures consistent **downstream format for merge and reasoning**\n\n📎 Notes:\n\n• **Format logic** is customized per indicator\n\n• Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "e53bcd92-c603-43f3-b25f-0d7dd46afca0",
      "name": "Note adhésive18",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        720
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1820,
        "content": "## Calculate Technical Indicators \n  \n\n• Ensures consistent **downstream format for merge and reasoning**\n\n📎 Notes:\n\n• **Format logic** is customized per indicator\n\n• Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "7d27698c-bbc5-4faf-aa41-4c4332abb484",
      "name": "Note adhésive19",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -520,
        2180
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1800,
        "content": "## Calculate Technical Indicators \n  \n\n• Ensures consistent **downstream format for merge and reasoning**\n\n📎 Notes:\n\n• **Format logic** is customized per indicator\n\n• Essential for **standardizing multi-timeframe data ingestion**"
      },
      "typeVersion": 1
    },
    {
      "id": "309f22d1-aeb2-4d48-a7c4-deaad51070e3",
      "name": "Note adhésive20",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "5a27535c-5bc9-4718-b895-5a8eccf37974",
      "name": "Note adhésive21",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "52180c40-a1d3-4c41-aea6-73f62f7c98ee",
      "name": "Note adhésive22",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "08697498-181c-417f-8027-8d6de1471bed",
      "name": "Note adhésive23",
      "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• **RSI, MACD, BBANDS, SMA, EMA, ADX**"
      },
      "typeVersion": 1
    },
    {
      "id": "773a2b4f-f716-41ab-ba18-1412e2dd50e5",
      "name": "Note adhésive24",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2800,
        -60
      ],
      "parameters": {
        "width": 1460,
        "height": 3020,
        "content": "# 🧪 Binance SM Indicators Webhook Tool – 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## 🎯 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## ⚙️ 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− included)      |\n| `Prepare Output`       | Returns JSON with all labeled indicators                  |\n\n---\n\n## 📥 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## 📊 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## 🧩 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## 🛠️ 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## 📦 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## 🔐 Licensing & Support\n\n🔗 **Don Jayamaha – LinkedIn**\n[http://linkedin.com/in/donjayamahajr](http://linkedin.com/in/donjayamahajr)\n\n© 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,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f7032997-bb7f-4e2a-9982-7ee18c95cfbe",
  "connections": {
    "61cb3bd7-60ba-4dcf-a080-146b7612a99b": {
      "main": [
        [
          {
            "node": "46b937d5-7338-457f-b2f0-eb85ac44a8bb",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "df6546be-3778-4fba-801a-870c0fe883c3": {
      "main": [
        [
          {
            "node": "5ea4756f-a20a-444c-8471-57d209457823",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "25ef2964-c9b1-4b9e-b6ce-3766b215d163": {
      "main": [
        [
          {
            "node": "5ea4756f-a20a-444c-8471-57d209457823",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "b0391e28-03a3-40f0-8182-72df0928ca2c": {
      "main": [
        [
          {
            "node": "5ea4756f-a20a-444c-8471-57d209457823",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "c50f7fa5-2707-47a4-9b32-1d582bf5d600": {
      "main": [
        [
          {
            "node": "5ea4756f-a20a-444c-8471-57d209457823",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "7301ba32-ab0c-4c80-b041-cd398f7daa2c": {
      "main": [
        [
          {
            "node": "af471a36-1107-4ccd-be14-efa06ba27330",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "2c598960-630c-4687-91c0-bfae10da1943": {
      "main": [
        [
          {
            "node": "5ea4756f-a20a-444c-8471-57d209457823",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "29121e2e-f6e2-4d42-8e5b-e8eea1e61ec8": {
      "main": [
        [
          {
            "node": "1be26316-3d08-4983-9d12-6bc36465e76e",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9ec777b1-eb23-43b0-8bb3-726adcc3418f": {
      "main": [
        [
          {
            "node": "d7d9bb31-6986-42e6-9f0c-7d7f00d66ee1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "bbb81a2a-b1c4-4c1e-80e9-a4990c45fae8": {
      "main": [
        [
          {
            "node": "20e58706-035f-46ef-b235-78fe468875bc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0b3278a6-5793-4d70-8954-70562c43a3eb": {
      "main": [
        [
          {
            "node": "660dfcb4-ed63-4c53-806b-cba96c105fee",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "83fa487a-1a67-4059-9007-d5298d8bb105": {
      "main": [
        [
          {
            "node": "af471a36-1107-4ccd-be14-efa06ba27330",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "17bf4e9f-7053-4a04-b439-3e23fe5cb7f9": {
      "main": [
        [
          {
            "node": "7940bc3f-c5f4-4b4b-b542-8737f17afbbf",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "ac84877a-75cf-4ef2-bad1-8d0f444ecead": {
      "main": [
        [
          {
            "node": "660dfcb4-ed63-4c53-806b-cba96c105fee",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "702e8bc8-d8f0-4fb4-b44f-fd19479c8b03": {
      "main": [
        [
          {
            "node": "af471a36-1107-4ccd-be14-efa06ba27330",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "d1c2fcc7-65ac-446c-8f59-c4cd8ac7d795": {
      "main": [
        [
          {
            "node": "7940bc3f-c5f4-4b4b-b542-8737f17afbbf",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "5b1f34b3-e675-46b1-a754-679080680058": {
      "main": [
        [
          {
            "node": "660dfcb4-ed63-4c53-806b-cba96c105fee",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "15bf5c3b-de07-412b-988c-f5c59fa7a06f": {
      "main": [
        [
          {
            "node": "af471a36-1107-4ccd-be14-efa06ba27330",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "93a115f7-e876-43f0-8ecb-70a47a06f41c": {
      "main": [
        [
          {
            "node": "7940bc3f-c5f4-4b4b-b542-8737f17afbbf",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "28dd6fb1-a161-49ed-9c14-740e88e2195d": {
      "main": [
        [
          {
            "node": "660dfcb4-ed63-4c53-806b-cba96c105fee",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "d61db420-9def-4ec1-bf8a-94afeabda186": {
      "main": [
        [
          {
            "node": "7940bc3f-c5f4-4b4b-b542-8737f17afbbf",
            "type": "main",
            "index": 5
          }
        ]
      ]
    },
    "a5da65c5-db5a-4f9d-a6cf-228f081ffb3c": {
      "main": [
        [
          {
            "node": "660dfcb4-ed63-4c53-806b-cba96c105fee",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "d16b7829-59ca-45ca-a5ae-4e315168c96d": {
      "main": [
        [
          {
            "node": "af471a36-1107-4ccd-be14-efa06ba27330",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "df32e55c-2dbc-44d6-8161-f70db651cfcd": {
      "main": [
        [
          {
            "node": "7940bc3f-c5f4-4b4b-b542-8737f17afbbf",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "46b937d5-7338-457f-b2f0-eb85ac44a8bb": {
      "main": [
        [
          {
            "node": "5b8c1049-bcf4-41d4-bba3-5f76e940bd3e",
            "type": "main",
            "index": 0
          },
          {
            "node": "b0391e28-03a3-40f0-8182-72df0928ca2c",
            "type": "main",
            "index": 0
          },
          {
            "node": "2c598960-630c-4687-91c0-bfae10da1943",
            "type": "main",
            "index": 0
          },
          {
            "node": "c50f7fa5-2707-47a4-9b32-1d582bf5d600",
            "type": "main",
            "index": 0
          },
          {
            "node": "25ef2964-c9b1-4b9e-b6ce-3766b215d163",
            "type": "main",
            "index": 0
          },
          {
            "node": "df6546be-3778-4fba-801a-870c0fe883c3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "660dfcb4-ed63-4c53-806b-cba96c105fee": {
      "main": [
        [
          {
            "node": "939665ce-7a50-4a6d-9b37-62ad800350b0",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "af471a36-1107-4ccd-be14-efa06ba27330": {
      "main": [
        [
          {
            "node": "39e6b058-d800-4751-b75b-5e62be379c61",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7940bc3f-c5f4-4b4b-b542-8737f17afbbf": {
      "main": [
        [
          {
            "node": "8585a269-b70d-4af7-b264-db9f638c8a77",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1be26316-3d08-4983-9d12-6bc36465e76e": {
      "main": [
        [
          {
            "node": "6e9c7976-737b-4377-b412-633a1a67097b",
            "type": "main",
            "index": 0
          },
          {
            "node": "ac84877a-75cf-4ef2-bad1-8d0f444ecead",
            "type": "main",
            "index": 0
          },
          {
            "node": "a5da65c5-db5a-4f9d-a6cf-228f081ffb3c",
            "type": "main",
            "index": 0
          },
          {
            "node": "5b1f34b3-e675-46b1-a754-679080680058",
            "type": "main",
            "index": 0
          },
          {
            "node": "0b3278a6-5793-4d70-8954-70562c43a3eb",
            "type": "main",
            "index": 0
          },
          {
            "node": "28dd6fb1-a161-49ed-9c14-740e88e2195d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "d7d9bb31-6986-42e6-9f0c-7d7f00d66ee1": {
      "main": [
        [
          {
            "node": "a2e9d933-47d7-47f5-8d49-224860d8600a",
            "type": "main",
            "index": 0
          },
          {
            "node": "702e8bc8-d8f0-4fb4-b44f-fd19479c8b03",
            "type": "main",
            "index": 0
          },
          {
            "node": "d16b7829-59ca-45ca-a5ae-4e315168c96d",
            "type": "main",
            "index": 0
          },
          {
            "node": "15bf5c3b-de07-412b-988c-f5c59fa7a06f",
            "type": "main",
            "index": 0
          },
          {
            "node": "83fa487a-1a67-4059-9007-d5298d8bb105",
            "type": "main",
            "index": 0
          },
          {
            "node": "7301ba32-ab0c-4c80-b041-cd398f7daa2c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "20e58706-035f-46ef-b235-78fe468875bc": {
      "main": [
        [
          {
            "node": "1675168b-97ab-4be4-92d2-734d075e2222",
            "type": "main",
            "index": 0
          },
          {
            "node": "d1c2fcc7-65ac-446c-8f59-c4cd8ac7d795",
            "type": "main",
            "index": 0
          },
          {
            "node": "df32e55c-2dbc-44d6-8161-f70db651cfcd",
            "type": "main",
            "index": 0
          },
          {
            "node": "93a115f7-e876-43f0-8ecb-70a47a06f41c",
            "type": "main",
            "index": 0
          },
          {
            "node": "17bf4e9f-7053-4a04-b439-3e23fe5cb7f9",
            "type": "main",
            "index": 0
          },
          {
            "node": "d61db420-9def-4ec1-bf8a-94afeabda186",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "91a05310-bd36-4ee0-8381-b3d16c3346be": {
      "main": [
        [
          {
            "node": "29121e2e-f6e2-4d42-8e5b-e8eea1e61ec8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "40fcbba9-489b-4452-baf8-9a41ef0f9b07": {
      "main": [
        [
          {
            "node": "9ec777b1-eb23-43b0-8bb3-726adcc3418f",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "c4a718f8-f2d6-464b-8120-1e4067d760ad": {
      "main": [
        [
          {
            "node": "bbb81a2a-b1c4-4c1e-80e9-a4990c45fae8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "b372f57e-917a-42d2-964c-25a296ea982d": {
      "main": [
        [
          {
            "node": "61cb3bd7-60ba-4dcf-a080-146b7612a99b",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5ea4756f-a20a-444c-8471-57d209457823": {
      "main": [
        [
          {
            "node": "0a46ea7c-a85d-4fc6-b20c-7e65ab962ea6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5b8c1049-bcf4-41d4-bba3-5f76e940bd3e": {
      "main": [
        [
          {
            "node": "5ea4756f-a20a-444c-8471-57d209457823",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6e9c7976-737b-4377-b412-633a1a67097b": {
      "main": [
        [
          {
            "node": "660dfcb4-ed63-4c53-806b-cba96c105fee",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "a2e9d933-47d7-47f5-8d49-224860d8600a": {
      "main": [
        [
          {
            "node": "af471a36-1107-4ccd-be14-efa06ba27330",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1675168b-97ab-4be4-92d2-734d075e2222": {
      "main": [
        [
          {
            "node": "7940bc3f-c5f4-4b4b-b542-8737f17afbbf",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Avancé - Finance, Intelligence Artificielle

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Avancé
Nombre de nœuds69
Catégorie2
Types de nœuds6
Description de la difficulté

Adapté aux utilisateurs avancés, avec des workflows complexes contenant 16+ nœuds

Auteur
Don Jayamaha Jr

Don Jayamaha Jr

@don-the-gem-dealer

With 12 years of experience as a Blockchain Strategist and Web3 Architect, I specialize in bridging the gap between traditional industries and decentralized technologies. My expertise spans tokenized assets, crypto payment integrations, and blockchain-driven market solutions.

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34