Skip to content

Real‑Time Binance OHLCV Streaming with Node.js Native WebSocket & TypeScript

Published: at 09:47 AM

Binance offers several WebSocket endpoints that let you stream market data in real time. In this article, we’ll focus on retrieving 24‑hour mini ticker data—which includes open, high, low, close, and volume details (i.e. OHLCV) for selected trading pairs—using Binance’s combined stream endpoint. We’ll build a robust TypeScript solution that handles errors, reconnections, and graceful shutdowns using Node’s native WebSocket (available in Node.js v20 or later).

Note: Binance provides two primary mini ticker endpoints: • The Combined Stream (via query parameters) returns individual messages in the format:

{
  "stream": "<streamName>",
  "data": { "e": "24hrMiniTicker", "E": 123456789, "s": "BTCUSDT", "c": "xxxx", ... }
}

• The All Market Mini Tickers Stream (wss://stream.binance.com:9443/ws/!miniTicker@arr) sends a single message that is an array of mini ticker objects.

In this tutorial we work with a combined stream for a fixed set of tickers, which provides the “stream” and “data” keys.


1. Overview

The Binance 24hr Mini Ticker Stream delivers a brief update about market statistics. Each mini ticker message includes:

When using Binance’s combined stream, you can subscribe to multiple symbols by constructing a URL query parameter with each stream—for example, for BTCUSDT, ETHUSDT, and BNBUSDT:

wss://stream.binance.com:9443/stream?streams=btcusdt@miniTicker/ethusdt@miniTicker/bnbusdt@miniTicker

Each message from this combined stream will have the structure shown above.


2. Environment Setup

We will build our solution in TypeScript, leveraging Node.js’ native WebSocket. (Node.js v20 or later will have a stable built-in WebSocket API; if you are on Node v18, you may need to enable experimental features.)

  1. Initialize your project:

    npm init -y
    npm install --save-dev typescript @types/node
    npx tsc --init
    
  2. No external WebSocket library is required since we’re using the native WebSocket API.


3. Implementation in TypeScript

Below is the full code sample. This example:

// List of tickers to subscribe to – note: Binance commonly uses USDT pairs.
const tickers = ["BTCUSDT", "ETHUSDT", "BNBUSDT"];

// Build the stream query string by making the tickers lowercase and appending the mini ticker event.
const streams = tickers.map((ticker) => `${ticker.toLowerCase()}@miniTicker`).join("/");
const WS_URL = `wss://stream.binance.com:9443/stream?streams=${streams}`;

// Global variables to manage our connection and reconnection timeout.
let ws: WebSocket | null = null;
let reconnectTimeout: NodeJS.Timeout | null = null;

/**
 * Establish (or re-establish) a connection to Binance’s combined stream.
 */
function connect() {
  // Create a new WebSocket connection using the native API.
  ws = new WebSocket(WS_URL);

  ws.onopen = () => {
    console.log("Connected to Binance WebSocket API");
  };

  ws.onmessage = (event: MessageEvent) => {
    try {
      // For combined streams, Binance sends messages as an object with:
      // - "stream": the stream name (e.g., "btcusdt@miniTicker")
      // - "data": the mini ticker payload with OHLCV information.
      const message = JSON.parse(event.data.toString());
      const { stream, data: payload } = message;
      console.log(`Received update from ${stream}:`, payload);

      // You can process the payload here:
      // For example: using payload.o (open), payload.h (high), payload.l (low), payload.c (close),
      // payload.v (volume), payload.q (quote volume), etc.
    } catch (err) {
      console.error("Error parsing incoming message:", err);
    }
  };

  ws.onerror = (event: Event) => {
    console.error("WebSocket error occurred:", event);
    cleanupAndReconnect();
  };

  ws.onclose = (event: CloseEvent) => {
    console.warn(`WebSocket closed. Code: ${event.code}, Reason: ${event.reason}`);
    // If not a normal closure (code 1000), attempt to reconnect.
    if (event.code !== 1000) {
      console.log("Attempting to reconnect in 5 seconds...");
      reconnectTimeout = setTimeout(connect, 5000);
    }
  };
}

/**
 * Clean up the connection and schedule a reconnection.
 */
function cleanupAndReconnect() {
  if (ws) {
    // Nullify callbacks to avoid duplicate handling.
    ws.onopen = null;
    ws.onmessage = null;
    ws.onerror = null;
    ws.onclose = null;

    // Close the connection if it is still active.
    if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
      ws.close(1001, "Reconnecting"); // 1001 means "Going Away".
    }
  }
  if (reconnectTimeout) {
    clearTimeout(reconnectTimeout);
  }
  reconnectTimeout = setTimeout(connect, 5000);
}

connect();

/**
 * Gracefully shut down the connection on process termination.
 */
function shutdown() {
  console.log("Shutting down gracefully...");
  if (reconnectTimeout) {
    clearTimeout(reconnectTimeout);
  }
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.close(1000, "Process terminated"); // 1000: Normal Closure.
  }
  process.exit(0);
}

process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);

/**
 * Optional: Dynamic Subscription Management
 *
 * If you wish to subscribe or unsubscribe dynamically using a single WebSocket connection
 * (instead of using a combined URL), Binance supports sending these JSON messages:
 *
 * // To subscribe to a new ticker (e.g., BNBUSDT mini ticker):
 * ws.send(JSON.stringify({
 *   method: "SUBSCRIBE",
 *   params: ["bnbusdt@miniTicker"],
 *   id: 1
 * }));
 *
 * // To unsubscribe:
 * ws.send(JSON.stringify({
 *   method: "UNSUBSCRIBE",
 *   params: ["bnbusdt@miniTicker"],
 *   id: 2
 * }));
 *
 * Note that when you rely on a combined stream URL, the subscription is fixed by the URL.
 */

4. Explanation


5. Summary

In this tutorial, we demonstrated how to:

Using this setup, you can build applications that rely on accurate, live market data streamed directly from Binance. Happy coding!


Feel free to extend or modify this example—for instance, by adding exponential backoff for reconnections or further processing of the mini ticker data—to suit your project’s requirements.


Previous Post
Real‑Time Coinbase OHLCV Streaming with Node.js Native WebSocket & TypeScript
Next Post
My .cursorrules configuration for full-stack TS/Next.js development