8
n8n 中文网amn8n.com

币安技术指标 Webhook 工具

高级

这是一个Finance, AI领域的自动化工作流,包含 69 个节点。主要使用 Code, Merge, Webhook, HttpRequest, RespondToWebhook 等节点,结合人工智能技术实现智能自动化。 币安技术指标 Webhook 工具

前置要求
  • HTTP Webhook 端点(n8n 会自动生成)
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "HrTD222kWpvKsy2j",
  "meta": {
    "instanceId": "a5283507e1917a33cc3ae615b2e7d5ad2c1e50955e6f831272ddd5ab816f3fb6"
  },
  "name": "币安技术指标 Webhook 工具",
  "tags": [],
  "nodes": [
    {
      "id": "61cb3bd7-60ba-4dcf-a080-146b7612a99b",
      "name": "HTTP 请求",
      "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 15分钟指标",
      "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": "合并为1个数组",
      "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": "计算布林带",
      "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": "计算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": "计算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": "计算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": "计算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": "计算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": "合并15分钟指标",
      "type": "n8n-nodes-base.merge",
      "position": [
        180,
        -80
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "0a46ea7c-a85d-4fc6-b20c-7e65ab962ea6",
      "name": "响应15分钟Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        560,
        -20
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "40fcbba9-489b-4452-baf8-9a41ef0f9b07",
      "name": "Webhook 1小时指标",
      "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": "响应1小时Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -1060,
        1740
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "af471a36-1107-4ccd-be14-efa06ba27330",
      "name": "合并1小时指标",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1460,
        1680
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "d7d9bb31-6986-42e6-9f0c-7d7f00d66ee1",
      "name": "合并为1个数组(1小时)",
      "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": "计算布林带(1小时)",
      "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": "计算RSI(1小时)",
      "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": "计算MACD(1小时)",
      "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": "计算SMA(1小时)",
      "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": "计算EMA(1小时)",
      "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": "计算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请求 1小时",
      "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 4小时指标",
      "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请求 4小时",
      "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": "合并为1个数组(4小时)",
      "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": "计算布林带(4小时)",
      "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": "计算RSI(4小时)",
      "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": "计算MACD(4小时)",
      "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": "计算SMA(4小时)",
      "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": "计算EMA(4小时)",
      "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": "计算ADX(4小时)",
      "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": "合并4小时指标",
      "type": "n8n-nodes-base.merge",
      "position": [
        1720,
        1600
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "8585a269-b70d-4af7-b264-db9f638c8a77",
      "name": "响应4小时Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2120,
        1660
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "91a05310-bd36-4ee0-8381-b3d16c3346be",
      "name": "Webhook 1日指标",
      "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请求 1日",
      "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": "合并为1个数组(1日)",
      "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": "计算布林带(1日)",
      "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": "计算RSI(1日)",
      "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": "计算MACD(1日)",
      "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": "计算SMA(1日)",
      "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": "计算EMA(1日)",
      "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": "计算ADX(1日)",
      "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": "合并1日指标",
      "type": "n8n-nodes-base.merge",
      "position": [
        160,
        3060
      ],
      "parameters": {
        "numberInputs": 6
      },
      "typeVersion": 3.1
    },
    {
      "id": "939665ce-7a50-4a6d-9b37-62ad800350b0",
      "name": "响应1日Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        560,
        3120
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.3
    },
    {
      "id": "2f44a150-0c5a-4b74-92b5-fe941fccb86d",
      "name": "便签 13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        100,
        -340
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## 技术指标API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "fbf6424d-8bfc-469b-ae56-c9824e657e75",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1640,
        1340
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## 技术指标API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "fba15cd9-e7a0-4a03-813e-444dd2cd25f7",
      "name": "便签14",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1540,
        1380
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## 技术指标API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "ab87eb1b-3048-4717-af97-b7fa92c0f260",
      "name": "便签15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        2760
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 620,
        "content": "## 技术指标API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "b3abceb1-3605-4cca-8b4e-1f85f792fff2",
      "name": "便签 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1540,
        -500
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks(15分钟数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "2143ce8a-8746-4187-bc43-c9cc294da7b8",
      "name": "便签 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3300,
        1240
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks(1小时数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "d9150fb0-42b5-48e8-b123-e588672a653f",
      "name": "便签 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -100,
        1200
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks(4小时数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "50737e51-ef67-42af-bf15-ac228c750f4d",
      "name": "便签 4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1680,
        2640
      ],
      "parameters": {
        "color": 4,
        "height": 680,
        "content": "## Webhooks(1日数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "61972dcf-2bd5-454e-880b-0d3e97c82f98",
      "name": "便签 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -480
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks响应(15分钟数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "c545e86b-8f90-4e3b-8e72-25f56de0b214",
      "name": "便签6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2020,
        1200
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks响应(4小时数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "9e838383-1c81-45c6-98f1-f735dc5789df",
      "name": "便签7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1140,
        1260
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks响应(1小时数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "f05d2def-9593-46c5-862d-378a5d1f64d4",
      "name": "便签8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        2660
      ],
      "parameters": {
        "color": 4,
        "width": 280,
        "height": 640,
        "content": "## Webhooks响应(1日数据)"
      },
      "typeVersion": 1
    },
    {
      "id": "ceb5f8e0-e51f-4554-a752-a319033c14c4",
      "name": "便签9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1160,
        -300
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 520,
        "content": "## 币安API调用 15分钟"
      },
      "typeVersion": 1
    },
    {
      "id": "8e1aeb88-3232-4ea7-85c3-49a75097341b",
      "name": "便签 10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2940,
        1480
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 500,
        "content": "## 币安API调用 1小时"
      },
      "typeVersion": 1
    },
    {
      "id": "53996473-bdd7-44d3-9060-96a2f072077c",
      "name": "便签11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        240,
        1420
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 500,
        "content": "## 币安API调用 4小时"
      },
      "typeVersion": 1
    },
    {
      "id": "6b138f55-5f0e-4844-a570-47919c8888b5",
      "name": "便签 12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1340,
        2920
      ],
      "parameters": {
        "color": 6,
        "width": 260,
        "height": 420,
        "content": "## 币安API调用 1日"
      },
      "typeVersion": 1
    },
    {
      "id": "8d8bd17f-c437-4e57-a517-3b6eb44eeff7",
      "name": "便签 16",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2120,
        720
      ],
      "parameters": {
        "color": 2,
        "height": 1860,
        "content": "## 计算技术指标"
      },
      "typeVersion": 1
    },
    {
      "id": "ee6e6788-7b9d-4d29-9cdd-0af5256289ef",
      "name": "便签 17",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -360,
        -860
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1720,
        "content": "## 计算技术指标"
      },
      "typeVersion": 1
    },
    {
      "id": "e53bcd92-c603-43f3-b25f-0d7dd46afca0",
      "name": "便签 18",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        720
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1820,
        "content": "## 计算技术指标"
      },
      "typeVersion": 1
    },
    {
      "id": "7d27698c-bbc5-4faf-aa41-4c4332abb484",
      "name": "便签19",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -520,
        2180
      ],
      "parameters": {
        "color": 2,
        "width": 260,
        "height": 1800,
        "content": "## 计算技术指标"
      },
      "typeVersion": 1
    },
    {
      "id": "309f22d1-aeb2-4d48-a7c4-deaad51070e3",
      "name": "便签20",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -820,
        -240
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 460,
        "content": "## API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "5a27535c-5bc9-4718-b895-5a8eccf37974",
      "name": "便签21",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2600,
        1560
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 420,
        "content": "## API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "52180c40-a1d3-4c41-aea6-73f62f7c98ee",
      "name": "便签22",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -980,
        2920
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 460,
        "content": "## API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "08697498-181c-417f-8027-8d6de1471bed",
      "name": "便签23",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        1480
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 440,
        "content": "## API合并"
      },
      "typeVersion": 1
    },
    {
      "id": "773a2b4f-f716-41ab-ba18-1412e2dd50e5",
      "name": "便签24",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2800,
        -60
      ],
      "parameters": {
        "width": 1460,
        "height": 3020,
        "content": "# 🧪 币安技术指标 Webhook 工具 – 文档"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "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
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 财务, 人工智能

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量69
分类2
节点类型6
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
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.

外部链接
在 n8n.io 查看

分享此工作流