◈   ⌘ api · Intermediate

Binance Futures WebSocket Python: Real-Time Data Guide

Learn how to connect to Binance Futures WebSocket in Python, stream live market data, and build real-time trading tools with working code examples.

Uncle Solieditor · voc · 06.05.2026 ·views 71
◈   Contents
  1. → Why WebSocket Over REST for Futures Trading
  2. → Setting Up Your Python Environment
  3. → Your First Binance Futures WebSocket Connection
  4. → Streaming Order Book Depth and Aggregated Trades
  5. → Authenticated User Data Streams
  6. → Production-Grade Reconnection and Error Handling
  7. → Frequently Asked Questions
  8. → What to Build Next

WebSocket connections are the backbone of any serious trading system. REST APIs are fine for placing orders or fetching historical data, but when you need live price feeds — millisecond-accurate order book updates, real-time trade streams, or instant liquidation alerts — WebSocket is the only practical choice. Binance Futures offers one of the most comprehensive WebSocket APIs in crypto, and Python makes it surprisingly straightforward to tap into. This guide walks you through everything from your first connection to handling reconnects and parsing real data.

Why WebSocket Over REST for Futures Trading

REST polling has a ceiling. If you're hitting Binance's REST endpoint every second to check BTC/USDT price, you're burning rate limits and still getting stale data. Binance enforces strict IP-level rate limits — exceed them and you get temporarily banned. WebSocket flips this model: you open one persistent connection, subscribe to streams, and Binance pushes updates to you the moment they happen. For futures traders specifically, this matters enormously. A 200ms delay in spotting a large liquidation or an aggressive bid can be the difference between a good entry and chasing a move that's already done.

Platforms like Bybit and OKX offer similar WebSocket infrastructure, but Binance Futures remains the highest-volume venue for perpetual contracts, which means tighter spreads, deeper order books, and more meaningful market microstructure data flowing through those streams.

Setting Up Your Python Environment

You need two core libraries: websockets for the connection layer and asyncio for non-blocking I/O. Optionally, install python-binance if you want a higher-level wrapper, but for raw WebSocket work the minimal setup gives you more control.

pip install websockets asyncio python-dotenv

For public market data streams — price tickers, trade streams, order book depth — you don't need API keys at all. Authentication is only required when subscribing to user-specific streams like account balance updates or order fills. Store your keys in a .env file and never hardcode them.

# .env file
BINANCE_API_KEY=your_api_key_here
BINANCE_SECRET_KEY=your_secret_key_here

Your First Binance Futures WebSocket Connection

The Binance Futures WebSocket base URL is wss://fstream.binance.com. Each stream has a specific path you append to that base. The mark price stream for BTCUSDT perpetual, for example, is /ws/btcusdt@markPrice. Here's a minimal working example that connects and prints mark price updates:

import asyncio
import json
import websockets

BASE_URL = "wss://fstream.binance.com"

async def stream_mark_price(symbol: str):
    stream_path = f"/ws/{symbol.lower()}@markPrice"
    url = BASE_URL + stream_path

    async with websockets.connect(url) as ws:
        print(f"Connected to {url}")
        while True:
            try:
                message = await ws.recv()
                data = json.loads(message)
                mark_price = float(data["p"])
                funding_rate = float(data["r"])
                print(f"{symbol} Mark Price: {mark_price:.2f} | Funding: {funding_rate:.6f}")
            except websockets.ConnectionClosed:
                print("Connection closed, reconnecting...")
                break

if __name__ == "__main__":
    asyncio.run(stream_mark_price("BTCUSDT"))
Binance Futures WebSocket connections time out after 24 hours. Your production code must handle reconnection automatically — a one-time connection will silently die overnight.

Streaming Order Book Depth and Aggregated Trades

Mark price is useful, but order book depth and aggregated trade streams are where the real signal lives. The @depth20@100ms stream gives you the top 20 bids and asks updated every 100 milliseconds. The @aggTrade stream pushes every aggregated trade as it happens, including whether the buyer or seller was the market aggressor — a key input for order flow analysis.

import asyncio
import json
import websockets

BASE_URL = "wss://fstream.binance.com"

async def stream_order_book_and_trades(symbol: str):
    # Combine multiple streams using the combined stream endpoint
    streams = [
        f"{symbol.lower()}@depth20@100ms",
        f"{symbol.lower()}@aggTrade"
    ]
    combined_path = "/stream?streams=" + "/".join(streams)
    url = BASE_URL + combined_path

    async with websockets.connect(url) as ws:
        print(f"Subscribed to {len(streams)} streams for {symbol}")
        while True:
            try:
                message = await ws.recv()
                envelope = json.loads(message)
                stream_name = envelope["stream"]
                data = envelope["data"]

                if "depth" in stream_name:
                    best_bid = float(data["b"][0][0])  # price
                    best_bid_qty = float(data["b"][0][1])  # quantity
                    best_ask = float(data["a"][0][0])
                    best_ask_qty = float(data["a"][0][1])
                    spread = best_ask - best_bid
                    print(f"[Book] Bid: {best_bid} ({best_bid_qty}) | Ask: {best_ask} ({best_ask_qty}) | Spread: {spread:.2f}")

                elif "aggTrade" in stream_name:
                    price = float(data["p"])
                    qty = float(data["q"])
                    is_buyer_maker = data["m"]  # True = sell aggressor
                    side = "SELL" if is_buyer_maker else "BUY"
                    print(f"[Trade] {side} {qty} @ {price}")

            except websockets.ConnectionClosed as e:
                print(f"Connection closed: {e}")
                break
            except json.JSONDecodeError:
                continue

if __name__ == "__main__":
    asyncio.run(stream_order_book_and_trades("BTCUSDT"))

The combined stream endpoint is one of Binance's most useful features — a single WebSocket connection can carry up to 1024 streams simultaneously, dramatically reducing connection overhead compared to opening a separate socket per stream.

Authenticated User Data Streams

To receive account-level events — order fills, position updates, balance changes — you need a user data stream. The flow is different from public streams: you first call a REST endpoint to get a listenKey, then connect to that key's WebSocket URL. The listenKey expires every 60 minutes unless you send a keepalive ping.

import asyncio
import json
import time
import hmac
import hashlib
import httpx
import websockets
from dotenv import load_dotenv
import os

load_dotenv()

API_KEY = os.getenv("BINANCE_API_KEY")
SECRET_KEY = os.getenv("BINANCE_SECRET_KEY")
REST_BASE = "https://fapi.binance.com"
WS_BASE = "wss://fstream.binance.com"

def get_listen_key() -> str:
    headers = {"X-MBX-APIKEY": API_KEY}
    resp = httpx.post(f"{REST_BASE}/fapi/v1/listenKey", headers=headers)
    resp.raise_for_status()
    return resp.json()["listenKey"]

def keepalive_listen_key(listen_key: str):
    headers = {"X-MBX-APIKEY": API_KEY}
    httpx.put(
        f"{REST_BASE}/fapi/v1/listenKey",
        headers=headers,
        params={"listenKey": listen_key}
    )

async def periodic_keepalive(listen_key: str):
    while True:
        await asyncio.sleep(30 * 60)  # every 30 minutes
        keepalive_listen_key(listen_key)
        print("ListenKey renewed")

async def stream_user_data():
    listen_key = get_listen_key()
    print(f"Got listenKey: {listen_key[:16]}...")

    url = f"{WS_BASE}/ws/{listen_key}"

    # Run keepalive in background
    asyncio.create_task(periodic_keepalive(listen_key))

    async with websockets.connect(url) as ws:
        print("User data stream connected")
        while True:
            try:
                message = await ws.recv()
                event = json.loads(message)
                event_type = event.get("e")

                if event_type == "ORDER_TRADE_UPDATE":
                    order = event["o"]
                    print(f"Order update: {order['s']} {order['S']} {order['q']} @ {order['p']} | Status: {order['X']}")

                elif event_type == "ACCOUNT_UPDATE":
                    balances = event["a"]["B"]
                    for b in balances:
                        if float(b["wb"]) > 0:
                            print(f"Balance: {b['a']} = {b['wb']}")

            except websockets.ConnectionClosed:
                print("User stream disconnected — refreshing listenKey")
                listen_key = get_listen_key()
                break

if __name__ == "__main__":
    asyncio.run(stream_user_data())
Never log your listenKey to shared systems. It grants read access to your account event stream without requiring your API secret — treat it like a session token.

Production-Grade Reconnection and Error Handling

The examples above work great for learning, but production trading systems need robust reconnection logic. Networks drop. Binance occasionally restarts stream servers. Your bot needs to survive all of it without manual intervention. Here's a battle-tested reconnect wrapper:

import asyncio
import json
import websockets
import logging
from typing import Callable, Awaitable

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("ws_client")

async def resilient_stream(
    url: str,
    handler: Callable[[dict], Awaitable[None]],
    max_retries: int = 10,
    base_backoff: float = 1.0
):
    retries = 0
    while retries < max_retries:
        try:
            async with websockets.connect(
                url,
                ping_interval=20,
                ping_timeout=10,
                close_timeout=5
            ) as ws:
                log.info(f"Connected: {url}")
                retries = 0  # reset on successful connection
                async for raw in ws:
                    try:
                        data = json.loads(raw)
                        await handler(data)
                    except json.JSONDecodeError:
                        log.warning("Received non-JSON message, skipping")
                    except Exception as e:
                        log.error(f"Handler error: {e}")

        except (websockets.ConnectionClosed, OSError, asyncio.TimeoutError) as e:
            retries += 1
            wait = min(base_backoff * (2 ** retries), 60)  # exponential backoff, max 60s
            log.warning(f"Connection lost ({e}). Retry {retries}/{max_retries} in {wait:.1f}s")
            await asyncio.sleep(wait)

    log.error("Max retries reached. Giving up.")

# Usage example
async def my_handler(data: dict):
    price = float(data.get("p", 0))
    if price > 0:
        print(f"Price update: {price}")

async def main():
    url = "wss://fstream.binance.com/ws/btcusdt@markPrice"
    await resilient_stream(url, my_handler)

if __name__ == "__main__":
    asyncio.run(main())

Exponential backoff is critical here. If Binance has a brief outage and hundreds of bots simultaneously hammer reconnections at fixed 1-second intervals, they all hit rate limits together. Spreading reconnects out protects both your access and the exchange's infrastructure.

This pattern applies equally well when building similar systems on Bybit or OKX — both use WebSocket-based streaming with comparable reconnection requirements. Gate.io and KuCoin also offer futures WebSocket APIs, though their message formats differ enough that you'll want separate handler logic per exchange.

If you're looking for pre-processed signals rather than raw streams, VoiceOfChain aggregates real-time market data across major futures venues and surfaces actionable alerts — useful as a complement to your own WebSocket pipeline when you want cross-exchange confirmation before acting.

Key Binance Futures WebSocket Streams
StreamPathUpdate SpeedAuth Required
Mark Price@markPrice1s or 3sNo
Aggregated Trades@aggTradeReal-timeNo
Order Book Depth@depth{N}@{ms}100ms / 250ms / 500msNo
Candlestick (Kline)@kline_{interval}Per close/updateNo
User Order UpdateslistenKeyReal-timeYes
Liquidation Orders@forceOrderReal-timeNo

Frequently Asked Questions

Do I need a Binance API key to use the Futures WebSocket?
For public market data streams — prices, trades, order book depth, liquidations — no API key is required. You only need authentication for user data streams that show your account balance, order fills, and position changes. Public streams are completely open.
How many WebSocket streams can I subscribe to at once on Binance?
Using the combined stream endpoint (/stream?streams=), you can subscribe to up to 1024 streams per connection. Binance also allows up to 300 connections per IP address. For most trading bots, a single connection with multiple stream subscriptions is the cleanest approach.
Why does my Binance WebSocket connection keep dropping?
Binance closes connections that have been idle for more than 60 minutes, or after 24 hours regardless. Make sure your code sends periodic pings (the websockets library handles this with ping_interval) and implements automatic reconnection with backoff logic. The listenKey for user streams also requires a keepalive REST call every 30-60 minutes.
What's the difference between Binance Spot WebSocket and Futures WebSocket?
The base URL differs: Spot uses wss://stream.binance.com:9443 while Futures uses wss://fstream.binance.com. The stream names are similar but Futures-specific streams like mark price, funding rate, and liquidation orders only exist on the Futures endpoint. Always check which environment your code is targeting.
Can I use this Python code on Bybit or OKX instead of Binance?
The asyncio and websockets library approach works universally — WebSocket is a standard protocol. What changes are the base URLs, authentication flow, and message formats. Bybit Futures uses wss://stream.bybit.com/v5/public/linear and OKX uses wss://ws.okx.com:8443/ws/v5/public, each with their own subscription message structure.
Is there a rate limit on Binance Futures WebSocket connections?
Binance limits incoming messages to 5 per second per connection and allows up to 300 WebSocket connections per IP. The connections themselves are rate-limited only at the establishment level — once connected, high-frequency data flows freely. Exceeding connection limits temporarily blocks your IP.

What to Build Next

With a working WebSocket pipeline you have the foundation for a serious range of trading tools. Real-time order flow imbalance detectors, funding rate monitors that alert when rates spike on Binance or Bybit, liquidation heatmaps, spread arbitrage monitors across Bitget and OKX — all of these start from exactly the code patterns above. The key architectural principle is separating your data ingestion layer (the WebSocket handlers) from your signal generation and execution logic. Keep them decoupled so you can swap in different data sources or trading logic without rewriting everything. A message queue like asyncio.Queue or Redis pub/sub between layers is the standard approach as systems grow. From there, integrating signals from VoiceOfChain or your own derived indicators becomes a straightforward pipeline addition rather than a monolithic rebuild.

◈   more on this topic
◉ basics Mastering the ccxt library documentation for crypto traders ⌂ exchanges Mastering the Binance CCXT Library for Crypto Traders ⌬ bots Best Crypto Trading Bots 2025: Profitable AI-Powered Strategies