Featured image of post Build a Crypto Price Alert Bot using REST + WebSockets

Build a Crypto Price Alert Bot using REST + WebSockets

A practical, language-agnostic guide with Python and Node.js examples for real-time price alerts.

Overview

In this tutorial, you’ll build a price alert bot that:

  • fetches the latest price over REST (sanity check / bootstrap),
  • subscribes to WebSocket price updates in real time,
  • triggers an alert when a threshold is crossed.

This is an educational example; use test/sandbox keys and never expose secrets. Not financial advice.

Prerequisites

  • Basic familiarity with REST/HTTP, JSON, and WebSockets.
  • An exchange (or mock) that offers:
    • REST price/ticker endpoint (e.g., /v1/market/price?symbol=BTC_USD)
    • WebSocket ticker stream (e.g., wss://stream.example.com/ticker)
  • Optional API key (if your provider requires it).
  • One of:
    • Python 3.10+ (requests, websockets, python-dotenv)
    • Node.js 18+ (axios, ws, dotenv)

Create a .env (do not commit this file):

1
2
3
4
5
API_BASE=https://api.example.com
WS_URL=wss://stream.example.com/ticker
SYMBOL=BTC_USD
API_KEY=your_api_key_if_required
THRESHOLD=65000

Architecture

Architecture Diagram

Option A — Python Implementation

Install deps

1
pip install requests websockets python-dotenv

bot.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import os, json, time, asyncio, requests, websockets
from dotenv import load_dotenv

load_dotenv()
API_BASE   = os.getenv("API_BASE")
WS_URL     = os.getenv("WS_URL")
SYMBOL     = os.getenv("SYMBOL", "BTC_USD")
API_KEY    = os.getenv("API_KEY", "")
THRESHOLD  = float(os.getenv("THRESHOLD", "65000"))

HEADERS = {"Authorization": f"Bearer {API_KEY}"} if API_KEY else {}

def get_last_price():
    # Example REST bootstrap; adjust path/params to your provider
    url = f"{API_BASE}/v1/market/price"
    r = requests.get(url, params={"symbol": SYMBOL}, headers=HEADERS, timeout=10)
    r.raise_for_status()
    data = r.json()
    # normalize to a float; adapt to your provider’s response shape
    return float(data.get("price") or data["data"]["price"])

async def stream_prices():
    # Example WS subscribe message; adjust to your provider
    subscribe_msg = json.dumps({"op": "subscribe", "channel": "ticker", "symbol": SYMBOL})
    backoff = 1

    while True:
        try:
            async with websockets.connect(WS_URL, ping_interval=20, ping_timeout=20) as ws:
                await ws.send(subscribe_msg)
                print(f"Subscribed to {SYMBOL} @ {WS_URL}")
                backoff = 1  # reset backoff on successful connect

                async for msg in ws:
                    payload = json.loads(msg)
                    # normalize to your provider’s schema
                    # e.g., {"type":"ticker","symbol":"BTC_USD","price":"65123.45"}
                    price = float(payload.get("price") or payload["data"]["price"])
                    print(f"Price: {price}")

                    if price >= THRESHOLD:
                        print(f"🚨 ALERT: {SYMBOL} crossed {THRESHOLD} (now {price})")
                        # TODO: send webhook/email/Slack here

        except Exception as e:
            print(f"WS error: {e}; reconnecting in {backoff}s")
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 60)

if __name__ == "__main__":
    try:
        p = get_last_price()
        print(f"Bootstrap {SYMBOL} price via REST: {p}")
    except Exception as e:
        print(f"REST bootstrap failed: {e}")

    asyncio.run(stream_prices())

Run

1
python bot.py

Option B — Node.js (TypeScript-friendly) Implementation

Install deps

1
2
npm init -y
npm install axios ws dotenv

bot.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
require("dotenv").config();
const axios = require("axios");
const WebSocket = require("ws");

const API_BASE  = process.env.API_BASE;
const WS_URL    = process.env.WS_URL;
const SYMBOL    = process.env.SYMBOL || "BTC_USD";
const API_KEY   = process.env.API_KEY || "";
const THRESHOLD = Number(process.env.THRESHOLD || "65000");

const headers = API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {};

async function getLastPrice() {
  const url = `${API_BASE}/v1/market/price`;
  const { data } = await axios.get(url, { params: { symbol: SYMBOL }, headers, timeout: 10_000 });
  return Number(data.price ?? data.data.price);
}

function connectWS() {
  let backoff = 1000;

  const open = () => {
    const ws = new WebSocket(WS_URL, { handshakeTimeout: 20_000 });

    ws.on("open", () => {
      backoff = 1000;
      const sub = JSON.stringify({ op: "subscribe", channel: "ticker", symbol: SYMBOL });
      ws.send(sub);
      console.log(`Subscribed to ${SYMBOL} @ ${WS_URL}`);
    });

    ws.on("message", (msg) => {
      try {
        const payload = JSON.parse(msg.toString());
        const price = Number(payload.price ?? payload.data.price);
        console.log(`Price: ${price}`);
        if (!Number.isNaN(price) && price >= THRESHOLD) {
          console.log(`🚨 ALERT: ${SYMBOL} crossed ${THRESHOLD} (now ${price})`);
          // TODO: send webhook/email/Slack here
        }
      } catch (e) {
        console.error("Parse error:", e.message);
      }
    });

    ws.on("close", () => {
      console.warn("WS closed; reconnecting…");
      setTimeout(open, backoff);
      backoff = Math.min(backoff * 2, 60_000);
    });

    ws.on("error", (err) => {
      console.error("WS error:", err.message);
      ws.close();
    });
  };

  open();
}

(async () => {
  try {
    const p = await getLastPrice();
    console.log(`Bootstrap ${SYMBOL} price via REST: ${p}`);
  } catch (e) {
    console.error("REST bootstrap failed:", e.message);
  }
  connectWS();
})();

Run

1
node bot.js

Alerts, Reliability & Security

Alerts

  • Slack webhook, email (SMTP), SMS (e.g., Twilio) — plug into the // TODO sections.

Reliability

  • Reconnect with exponential backoff (already included).
  • Add heartbeats/pings if your provider requires them.
  • Consider persisting last alert time to avoid spamming.

Security

  • Keep secrets in .env (never commit).
  • Rotate keys; use least-privilege API scopes.
  • If deploying, inject env vars via CI/CD secrets.

Extending the Bot

  • Multiple symbols (array + map per subscription).
  • Upper and lower thresholds; % change alerts.
  • Persist ticks for simple moving averages.
  • Expose a small /health HTTP endpoint for liveness checks.
  • Containerize with Docker and run under a process manager.

Repo Structure

1
2
3
4
5
6
7
crypto-alert-bot/
├─ .env.example
├─ README.md
├─ python/
│  └─ bot.py
└─ node/
   └─ bot.js

Troubleshooting

  • 401/403 → invalid/missing token or IP restrictions.
  • No messages → wrong channel name, symbol, or auth handshake.
  • Rate limiting → back off on REST; WS usually streams without limits.
  • JSON shape mismatch → normalize fields to your provider’s schema.
comments powered by Disqus