The optimal solution for Binance WebSocket real-time market data is using the wss://stream.binance.com:9443/stream?streams= combined stream endpoint. A single connection can subscribe to up to 1024 streams with a message latency of less than 50ms and almost zero weight consumption. This article provides complete Python and Node.js code for single streams, combined streams, dynamic subscriptions, local order book depth merging, and reconnection logic—all ready to run. Before you begin, if you haven't registered a Binance account yet, you can visit the Binance Official gateway or complete your account opening via Free Registration.
1. WebSocket Endpoints Overview
| Category | URL | Purpose |
|---|---|---|
| Spot Single | wss://stream.binance.com:9443/ws/{stream} | Single stream only |
| Spot Combined | wss://stream.binance.com:9443/stream?streams={s1/s2/...} | Multiple streams; includes stream name in message |
| Spot Backup | wss://data-stream.binance.vision/ws/{stream} | Official backup line |
| Futures (USD-M) | wss://fstream.binance.com/stream?streams= | Perpetual Futures |
| Futures (COIN-M) | wss://dstream.binance.com/stream?streams= | Coin-margined Futures |
| Testnet | wss://stream.testnet.binance.vision/ws | Testnet environment |
Connection Limits: Maximum 300 WebSocket connections per IP. A single connection can subscribe to up to 1024 streams. Connections are automatically disconnected every 24 hours, so clients must implement reconnection logic.
2. Stream Type List
Binance provides 11 primary market data streams:
| Stream | Format | Update Speed | Description |
|---|---|---|---|
| aggTrade | {symbol}@aggTrade |
Real-time | Aggregated trades |
| trade | {symbol}@trade |
Real-time | Individual raw trades |
| kline_1m | {symbol}@kline_1m |
1s | K-line candles (1m/5m/1h/...) |
| miniTicker | {symbol}@miniTicker |
1000ms | Compact ticker statistics |
| ticker | {symbol}@ticker |
1000ms | Full 24h ticker statistics |
| bookTicker | {symbol}@bookTicker |
Real-time | Best bid/ask price and quantity |
| depth | {symbol}@depth |
1000ms | Full depth (20/50/100 levels) |
| depth@100ms | {symbol}@depth@100ms |
100ms | Depth diff (incremental updates) |
| !miniTicker@arr | !miniTicker@arr |
1000ms | Compact ticker for all symbols |
| !ticker@arr | !ticker@arr |
1000ms | Full ticker for all symbols |
| !bookTicker | !bookTicker |
Real-time | Best bid/ask for all symbols |
Naming Convention: All lowercase separated by @, e.g., btcusdt@ticker instead of BTCUSDT@ticker.
3. Python asyncio Single Stream Subscription
import asyncio, json
import websockets
async def subscribe_ticker(symbol: str):
url = f"wss://stream.binance.com:9443/ws/{symbol.lower()}@ticker"
async with websockets.connect(url, ping_interval=180) as ws:
async for message in ws:
data = json.loads(message)
print(f"{data['s']}: Last Price {data['c']}, "
f"24h Change {data['P']}%, "
f"Volume {data['v']}")
asyncio.run(subscribe_ticker("BTCUSDT"))
Example output:
BTCUSDT: Last Price 68420.12, 24h Change 2.35%, Volume 45231.2
BTCUSDT: Last Price 68421.00, 24h Change 2.36%, Volume 45231.8
4. Combined Stream for Multiple Trading Pairs
One connection to subscribe to tickers for 10 pairs + K-lines for 5 pairs:
import asyncio, json
import websockets
STREAMS = [
"btcusdt@ticker", "ethusdt@ticker", "bnbusdt@ticker",
"solusdt@ticker", "xrpusdt@ticker", "adausdt@ticker",
"dogeusdt@ticker", "maticusdt@ticker", "dotusdt@ticker",
"avaxusdt@ticker",
"btcusdt@kline_1m", "ethusdt@kline_1m",
"bnbusdt@kline_1m", "solusdt@kline_1m", "xrpusdt@kline_1m"
]
async def combined_stream():
streams = "/".join(STREAMS)
url = f"wss://stream.binance.com:9443/stream?streams={streams}"
async with websockets.connect(url, ping_interval=180) as ws:
async for message in ws:
payload = json.loads(message)
stream = payload["stream"]
data = payload["data"]
if "@ticker" in stream:
print(f"[{stream}] {data['s']}: {data['c']}")
elif "@kline" in stream:
k = data["k"]
if k["x"]: # Print only when K-line closes
print(f"[{stream}] {k['s']} {k['i']} "
f"O:{k['o']} H:{k['h']} L:{k['l']} C:{k['c']}")
asyncio.run(combined_stream())
5. Dynamic Subscribe/Unsubscribe (SUBSCRIBE Method)
After the connection is established, you can dynamically add or remove subscriptions via JSON messages:
import asyncio, json
import websockets
async def dynamic_sub():
url = "wss://stream.binance.com:9443/ws"
async with websockets.connect(url) as ws:
# Subscribe to bookTicker for BTC and ETH
await ws.send(json.dumps({
"method": "SUBSCRIBE",
"params": ["btcusdt@bookTicker", "ethusdt@bookTicker"],
"id": 1
}))
count = 0
async for msg in ws:
data = json.loads(msg)
if "result" in data:
print(f"Subscription confirmed: {data}")
continue
count += 1
print(f"{data['s']} Best Bid {data['b']} Best Ask {data['a']}")
# After 50 messages, unsubscribe ETH and keep only BTC
if count == 50:
await ws.send(json.dumps({
"method": "UNSUBSCRIBE",
"params": ["ethusdt@bookTicker"],
"id": 2
}))
print("ETH subscription cancelled")
asyncio.run(dynamic_sub())
6. Depth Increment Merging (Maintaining Local Order Book)
High-frequency strategies often require a local order book at the matching engine level. The process is:
- Subscribe to the
@depth@100msdiff stream and buffer messages. - Make a REST call to
GET /api/v3/depth?symbol=X&limit=1000to fetch a snapshot. - Discard buffered messages where
u < lastUpdateId. - Apply the first message where
U <= lastUpdateId+1 <= u. - Apply subsequent messages directly; the
puof the new message must equal theuof the previous one.
import asyncio, json, aiohttp
import websockets
from sortedcontainers import SortedDict
class OrderBook:
def __init__(self, symbol):
self.symbol = symbol.lower()
self.bids = SortedDict() # Price -> Quantity
self.asks = SortedDict()
self.last_update_id = 0
self.buffer = []
def apply_update(self, bids, asks):
for price, qty in bids:
price, qty = float(price), float(qty)
if qty == 0:
self.bids.pop(price, None)
else:
self.bids[price] = qty
for price, qty in asks:
price, qty = float(price), float(qty)
if qty == 0:
self.asks.pop(price, None)
else:
self.asks[price] = qty
async def load_snapshot(self):
async with aiohttp.ClientSession() as s:
url = f"https://api.binance.com/api/v3/depth?symbol={self.symbol.upper()}&limit=1000"
async with s.get(url) as r:
snap = await r.json()
self.last_update_id = snap["lastUpdateId"]
self.apply_update(snap["bids"], snap["asks"])
async def run(self):
url = f"wss://stream.binance.com:9443/ws/{self.symbol}@depth@100ms"
async with websockets.connect(url) as ws:
# Step 1: Collect diffs for a short period
asyncio.create_task(self._collect(ws))
await asyncio.sleep(1)
# Step 2: Fetch snapshot
await self.load_snapshot()
# Step 3: Apply valid diffs from buffer
for diff in self.buffer:
if diff["u"] < self.last_update_id:
continue
self.apply_update(diff["b"], diff["a"])
self.last_update_id = diff["u"]
self.buffer = None
# Step 4: Apply new messages directly
async for msg in ws:
diff = json.loads(msg)
self.apply_update(diff["b"], diff["a"])
self.last_update_id = diff["u"]
best_bid = self.bids.keys()[-1] if self.bids else 0
best_ask = self.asks.keys()[0] if self.asks else 0
print(f"Best Bid {best_bid} Best Ask {best_ask} Spread {best_ask-best_bid:.2f}")
async def _collect(self, ws):
if self.buffer is None:
return
async for msg in ws:
if self.buffer is None:
break
self.buffer.append(json.loads(msg))
ob = OrderBook("BTCUSDT")
asyncio.run(ob.run())
7. Reconnection and Heartbeat
Binance WebSocket requires clients to respond to a ping within 3 minutes, and connections are forced closed after 24 hours:
import asyncio, json, websockets, logging
async def resilient_ws(url, on_message, max_retries=1000):
retry = 0
while retry < max_retries:
try:
async with websockets.connect(
url,
ping_interval=180, # Ping every 3 minutes
ping_timeout=10,
close_timeout=5
) as ws:
retry = 0 # Reset retry on success
async for message in ws:
await on_message(json.loads(message))
except (websockets.ConnectionClosed, asyncio.TimeoutError) as e:
retry += 1
wait = min(2 ** retry, 60) # Exponential backoff, max 60s
logging.warning(f"WS Disconnected {e}, reconnecting in {wait}s (Attempt {retry})")
await asyncio.sleep(wait)
8. Node.js WebSocket Example
const WebSocket = require('ws');
const streams = ['btcusdt@bookTicker', 'ethusdt@bookTicker'].join('/');
const url = `wss://stream.binance.com:9443/stream?streams=${streams}`;
function connect() {
const ws = new WebSocket(url);
ws.on('open', () => console.log('Connection successful'));
ws.on('message', (raw) => {
const { stream, data } = JSON.parse(raw);
console.log(`${data.s} Best Bid ${data.b} Best Ask ${data.a}`);
});
ws.on('close', () => {
console.log('Disconnected, reconnecting in 3s');
setTimeout(connect, 3000);
});
ws.on('error', (err) => console.error('Error', err.message));
}
connect();
9. Frequently Asked Questions (FAQ)
Q1: Does WebSocket consume weight limits?
A: Establishing a connection consumes 2 weight units; subsequent message pushes consume zero weight. However, connections exceeding 300 per IP will be rejected.
Q2: Why am I not receiving any messages after subscribing?
A: Three common reasons: 1) Stream names must be all lowercase (e.g., btcusdt, not BTCUSDT); 2) Trading pair format is wrong (e.g., using BTC-USDT instead of BTCUSDT); 3) Subscription messages must be sent to the wss://.../ws endpoint, not the /stream endpoint.
Q3: What is the difference between @depth and @depth@100ms?
A: @depth pushes a full snapshot (20 levels) every 1000ms. @depth@100ms pushes diffs (incremental updates) every 100ms. Use @100ms for maintaining a local order book; @depth is sufficient for display purposes.
Q4: Can I place orders via WebSocket?
A: Yes. Binance provides a WebSocket API (wss://ws-api.binance.com:443/ws-api/v3) for signed order placement, which has 30-50ms lower latency than REST. However, since the ecosystem is less mature, most strategies still use REST for orders and WS for listening.
Q5: How do I handle the 24-hour automatic disconnection seamlessly?
A: Dual Connection Rolling Strategy: At the 23rd hour, establish a new connection B. Wait for B to receive messages stably before closing the old connection A to avoid data loss. Production-grade frameworks like python-binance have this logic built-in.
After exploring the WebSocket solution, return to the Categories page to view more SDK tutorials in the "API Integration" category.