AutomationFlowsSlack & Telegram › Crypto Rsi Alert System with Eodhd, Telegram and Tradingview Charts

Crypto Rsi Alert System with Eodhd, Telegram and Tradingview Charts

ByKevin Meneses @pythonia-kevin on n8n.io

How it works

Event trigger★★★★☆ complexity15 nodesHTTP RequestTelegram
Slack & Telegram Trigger: Event Nodes: 15 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #7997 — we link there as the canonical source.

This workflow follows the HTTP Request → Telegram recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "m1",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -520,
        -160
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "note_overview",
      "name": "Sticky Note \u2014 Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -540,
        -360
      ],
      "parameters": {
        "color": 6,
        "width": 860,
        "height": 260,
        "content": "## Crypto RSI Alert Bot (overview)\n- Runs on a schedule or manual trigger.\n- Iterates a **watchlist** (BTC/ETH/SOL).\n- Fetches **intraday 1h** OHLCV from **EODHD** for each symbol.\n- Code node computes **Wilder's RSI(14)** and detects **30/70** crossings.\n- On signal, sends a **Telegram** alert (HTML) + **View chart** button (TradingView BINANCE/USD).\n\nEnv vars required:\n- `EODHD_TOKEN`\n- `TELEGRAM_CHAT_ID`"
      },
      "typeVersion": 1
    },
    {
      "id": "set1",
      "name": "Edit Fields (watchlist)",
      "type": "n8n-nodes-base.set",
      "position": [
        -300,
        -160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "sym_arr",
              "name": "symbol",
              "type": "array",
              "value": "[\"BTC-USD.CC\",\"ETH-USD.CC\",\"SOL-USD.CC\"]"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "note_watchlist",
      "name": "Sticky Note \u2014 Watchlist",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -320,
        -300
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 160,
        "content": "### Edit Fields (watchlist)\n- Defines the **symbol array**.\n- Make sure the field type is **Array** (String[]), not a single String.\n- Example output: `{ symbol: [\"BTC-USD.CC\",\"ETH-USD.CC\",\"SOL-USD.CC\"] }`"
      },
      "typeVersion": 1
    },
    {
      "id": "split_out",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -80,
        -160
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "symbol"
      },
      "typeVersion": 1
    },
    {
      "id": "note_split",
      "name": "Sticky Note \u2014 Split Out",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -100,
        -300
      ],
      "parameters": {
        "color": 6,
        "width": 520,
        "height": 150,
        "content": "### Split Out\n- Explodes the array into **one item per symbol**.\n- Input: 1 item with array \u2192 Output: N items like `{ symbol: \"BTC-USD.CC\" }`."
      },
      "typeVersion": 1
    },
    {
      "id": "loop",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        140,
        -160
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "note_loop",
      "name": "Sticky Note \u2014 Loop",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        120,
        -300
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 120,
        "content": "### Loop Over Items\n- Processes **one symbol per pass** to avoid mixing BTC/ETH/SOL candles.\n- Wiring: **Loop \u2192 HTTP \u2192 Code \u2192 back to Loop**. **Done \u2192 IF**."
      },
      "typeVersion": 1
    },
    {
      "id": "http",
      "name": "HTTP Request (EODHD intraday 1h)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        360,
        -260
      ],
      "parameters": {
        "url": "=https://eodhd.com/api/intraday/{{ $json.symbol }}",
        "method": "GET",
        "options": {
          "redirect": {
            "redirect": {}
          },
          "response": {
            "response": {}
          },
          "splitIntoItems": true
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "interval",
              "value": "1h"
            },
            {
              "name": "fmt",
              "value": "json"
            },
            {
              "name": "api_token",
              "value": "={{ $env.EODHD_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "note_http",
      "name": "Sticky Note \u2014 HTTP",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        340,
        -420
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 160,
        "content": "### HTTP (EODHD)\n- Fetches **intraday 1h OHLCV** for current symbol.\n- Token via env var `EODHD_TOKEN` \u2192 no secret in JSON.\n- **Split Into Items** enabled: 1 candle = 1 item (~2\u20133k items)."
      },
      "typeVersion": 1
    },
    {
      "id": "code",
      "name": "Code (RSI + message)",
      "type": "n8n-nodes-base.code",
      "position": [
        580,
        -260
      ],
      "parameters": {
        "jsCode": "// n8n Code node \u2014 Run Once for All Items\\n// Computes RSI(14) (Wilder) on 1h candles and raises 30/70 cross alerts.\\n\\nconst PERIOD = 14;\\nconst OVERBOUGHT = 70;\\nconst OVERSOLD = 30;\\n\\n// Collect ALL candles from the HTTP node (1 item = 1 candle)\\nconst inputItems = $input.all();\\nlet candles = [];\\nfor (const it of inputItems) {\\n  if (Array.isArray(it.json)) candles.push(...it.json);\\n  else candles.push(it.json);\\n}\\n\\n// Normalize + sort by time (prefer numeric timestamp, fallback to datetime)\\ncandles = candles\\n  .filter(r => r && r.close !== undefined)\\n  .map(r => ({\\n    t: (Number.isFinite(+r.timestamp) ? +r.timestamp\\n       : (typeof r.datetime === 'number' ? r.datetime : Date.parse(r.datetime)/1000)),\\n    close: +r.close\\n  }))\\n  .filter(r => Number.isFinite(r.t) && Number.isFinite(r.close))\\n  .sort((a,b) => a.t - b.t);\\n\\nif (candles.length < PERIOD + 2) {\\n  return [{ json: { error: 'Not enough candles for RSI', count: candles.length } }];\\n}\\n\\nconst closes = candles.map(c => c.close);\\n\\n// Wilder RSI (full series)\\nfunction rsiSeries(values, period = 14) {\\n  const deltas = [];\\n  for (let i = 1; i < values.length; i++) deltas.push(values[i] - values[i - 1]);\\n  let gain = 0, loss = 0;\\n  for (let i = 0; i < period; i++) { const d = deltas[i]; if (d >= 0) gain += d; else loss -= d; }\\n  let avgGain = gain / period; let avgLoss = loss / period;\\n  const rsis = new Array(values.length).fill(null);\\n  rsis[period] = avgLoss === 0 ? 100 : 100 - (100 / (1 + (avgGain / avgLoss)));\\n  for (let i = period + 1; i < values.length; i++) {\\n    const d = deltas[i - 1];\\n    const up = Math.max(d, 0);\\n    const down = Math.max(-d, 0);\\n    avgGain = ((avgGain * (period - 1)) + up) / period;\\n    avgLoss = ((avgLoss * (period - 1)) + down) / period;\\n    const rs = avgLoss === 0 ? Infinity : (avgGain / avgLoss);\\n    rsis[i] = 100 - (100 / (1 + rs));\\n  }\\n  return rsis;\\n}\\n\\nconst rsis = rsiSeries(closes, PERIOD);\\nconst lastIdx = rsis.length - 1;\\nconst rsiNow = +rsis[lastIdx].toFixed(1);\\nconst rsiPrev = +rsis[lastIdx - 1].toFixed(1);\\nconst lastClose = +closes[lastIdx].toFixed(2);\\nconst lastTs = candles[lastIdx].t;\\n\\n// Signals\\nlet signal = null;\\nif (rsiPrev > OVERSOLD && rsiNow <= OVERSOLD) signal = 'enter_oversold';\\nelse if (rsiPrev < OVERBOUGHT && rsiNow >= OVERBOUGHT) signal = 'enter_overbought';\\nelse if (rsiPrev <= OVERSOLD && rsiNow > OVERSOLD) signal = 'exit_oversold';\\nelse if (rsiPrev >= OVERBOUGHT && rsiNow < OVERBOUGHT) signal = 'exit_overbought';\\n\\n// ======= TEST TOGGLE (set true only to test Telegram delivery) =======\\nconst FORCE_ALERT  = false;                 // keep false in production\\nconst FORCE_SIGNAL = 'enter_overbought';    // 'enter_oversold' | 'exit_oversold' | 'exit_overbought'\\nif (FORCE_ALERT) signal = FORCE_SIGNAL;\\n// YOUR_AWS_SECRET_KEY_HERE=============================\\n\\n// Current symbol from the loop item (fallback to input $json)\\nconst symbol = $('Split Out')?.item?.json?.symbol || $json.symbol || 'UNKNOWN';\\n\\nconst TF = '1h';\\nconst fmt = (n, d=2) => Number(n).toLocaleString('en-US',{minimumFractionDigits:d, maximumFractionDigits:d});\\nconst tsUTC = (ts) => new Date(ts*1000).toISOString().replace('T',' ').slice(0,16) + ' UTC';\\n\\nlet emoji = '\ud83d\udd14', headline = '';\\nif (signal === 'enter_oversold')        { emoji='\ud83d\udd3b'; headline = `enters <u>oversold</u> (RSI ${rsiNow} \u2264 30)`; }\\nelse if (signal === 'enter_overbought') { emoji='\ud83d\ude80'; headline = `enters <u>overbought</u> (RSI ${rsiNow} \u2265 70)`; }\\nelse if (signal === 'exit_oversold')    { emoji='\u2705'; headline = `exits <u>oversold</u> (RSI ${rsiNow})`; }\\nelse if (signal === 'exit_overbought')  { emoji='\u2705'; headline = `exits <u>overbought</u> (RSI ${rsiNow})`; }\\n\\nconst alertTextHtml = signal ? (\\n  `${emoji} <b>${symbol}</b> ${headline}\\n` +\\n  `Price: <b>$${fmt(lastClose)}</b> \u00b7 TF: <b>${TF}</b> \u00b7 ${tsUTC(lastTs)}\\n` +\\n  `RSI: <b>${rsiPrev} \u2192 ${rsiNow}</b> (30/70)\\n` +\\n  `\u2014 <i>RSI Heatwave</i>`\\n) : null;\\n\\nconst alertText = signal ? `${symbol} | ${headline.replace(/<[^>]*>/g,'')} | Price $${fmt(lastClose)} \u00b7 TF ${TF} \u00b7 ${tsUTC(lastTs)} | RSI ${rsiPrev}\u2192${rsiNow}` : null;\\n\\n// TradingView link (BINANCE + USD) from EODHD symbol (e.g., BTC-USD.CC \u2192 BTCUSD)\\nconst rawSymbol = $('Split Out')?.item?.json?.symbol ?? $json.symbol ?? symbol;\\nconst sym  = Array.isArray(rawSymbol) ? rawSymbol[0] : rawSymbol; \\nconst base = String(sym).split('-')[0].toUpperCase();\\nconst tradingViewUrl = `https://www.tradingview.com/symbols/${base}USD/?exchange=BINANCE`;\\n\\nreturn [{ json: { symbol, rsi: rsiNow, rsiPrev, period: PERIOD, lastClose, signal, timestamp: lastTs, alertText, alertTextHtml, tradingViewUrl } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "note_code",
      "name": "Sticky Note \u2014 Code",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        -420
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 170,
        "content": "### Code (RSI + message)\n- Sorts candles, computes **RSI(14)** (Wilder), detects 30/70 crossings.\n- Builds HTML message + TradingView URL (BINANCE/USD).\n- Testing: set `FORCE_ALERT = true`, then back to `false`."
      },
      "typeVersion": 1
    },
    {
      "id": "if",
      "name": "IF (has signal?)",
      "type": "n8n-nodes-base.if",
      "position": [
        140,
        40
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond1",
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.signal }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "tg",
      "name": "Send a text message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        360,
        40
      ],
      "parameters": {
        "text": "={{ $json.alertTextHtml }}",
        "chatId": "={{ $env.TELEGRAM_CHAT_ID }}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "View chart",
                    "additionalFields": {
                      "url": "={{ $json.tradingViewUrl }}"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "parse_mode": "HTML",
          "disable_web_page_preview": true
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "note_tg",
      "name": "Sticky Note \u2014 Telegram",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        340,
        180
      ],
      "parameters": {
        "color": 6,
        "width": 560,
        "height": 140,
        "content": "### Telegram (delivery)\n- Parse Mode: **HTML**.\n- Text: `{{$json.alertTextHtml}}`.\n- Button: **View chart** \u2192 `{{$json.tradingViewUrl}}`.\n- Chat ID via env var `TELEGRAM_CHAT_ID`.\n- Bot token stays in Credentials."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Split Out": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "IF (has signal?)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "HTTP Request (EODHD intraday 1h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF (has signal?)": {
      "main": [
        [
          {
            "node": "Send a text message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code (RSI + message)": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields (watchlist)": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request (EODHD intraday 1h)": {
      "main": [
        [
          {
            "node": "Code (RSI + message)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Edit Fields (watchlist)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

How it works

Source: https://n8n.io/workflows/7997/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

This n8n workflow automates the process of scraping Google Play Store reviews, analyzing app performance, and sending alerts for low-rated applications. It integrates with Bright Data for web scraping

Form Trigger, HTTP Request, Google Sheets +1
Slack & Telegram

⚠️ Heads up: this is satire. The "Hell Yeah!" workflow is a parody of "automate your whole life with AI agents" grindset content. The API endpoints are fictional and the function nodes are illustrativ

HTTP Request, Salesforce, Telegram +4
Slack & Telegram

This n8n workflow provides automated monitoring of Public Key Infrastructure (PKI) components including CA certificates, Certificate Revocation Lists (CRLs), and associated web services. It extracts c

Write Binary File, Execute Command, HTTP Request
Slack & Telegram

This workflow continuously monitors the Meta Ads Library for new creatives from a specific competitor pages, logs them into Google Sheets, and sends a concise Telegram notification with the number of

HTTP Request, Telegram, Google Sheets +1
Slack & Telegram

Track all n8n workflow failures with automatic error capture, severity classification, duplicate detection, Slack alerting, performance metrics, and log retention.

Error Trigger, HTTP Request, Slack