A small, robust async reader for the Binance USDⓈ-M Futures market-data
WebSocket. One dependency (websockets), ~150 lines, battle-tested running
24/7 on a live trading bot.
Most "connect to Binance WS" snippets work for five minutes in a notebook and then silently die in production. This handles the boring, painful parts:
- Silent dead connections — relies on the WS ping/pong protocol, with a
recvtimeout longer than the ping interval so a slow-but-alive stream doesn't trigger a false reconnect. - Reconnect forever, with backoff — exponential, capped, and reset once a connection actually delivers data.
- Streams that lie — defaults to the streams that actually deliver on fstream (see GOTCHAS).
pip install websockets
git clone https://github.com/Makeph/binance-fapi-wsimport asyncio
from binance_fapi_ws import FapiStream, Trade, BookTicker
async def main():
cvd = 0.0
async for ev in FapiStream("BTCUSDT").__aiter__():
if isinstance(ev, Trade):
cvd += ev.signed_qty # +qty for buys, -qty for sells
elif isinstance(ev, BookTicker):
print(f"mid={ev.mid:,.2f} spread={ev.spread_bps:.2f}bps CVD={cvd:+.3f}")
asyncio.run(main())Multiple symbols, or your own stream set:
FapiStream(["BTCUSDT", "ETHUSDT"]) # trade + bookTicker each
FapiStream("BTCUSDT", streams=["btcusdt@kline_1m"]) # whatever you wantRunnable demo: examples/print_mid_and_cvd.py.
Depending on region, the aggregated futures streams (
<sym>@aggTrade,<sym>@markPrice@1s) connect successfully and then send nothing — no error, no close, just silence. A naive client looks "connected" forever and your strategy starves.The fix: use the raw streams, which always deliver:
@trade— every individual print. Has the samep/q/mfields, so CVD and trade-flow math are unchanged (just per-trade instead of pre-aggregated).@bookTicker— top-of-book bid/ask; usemid = (bid+ask)/2in place of the mark price.This client defaults to
@trade+@bookTickerfor exactly this reason.
Other things baked in from production pain:
| Symptom | Cause | What this does |
|---|---|---|
| Reconnect storm every few seconds | recv timeout < ping interval |
clamps recv_timeout ≥ 1.5 × ping_interval |
| "Connected" but frozen | TCP open, server silent | ping/pong + recv timeout force a reconnect |
| Hammering the API after an outage | fixed-delay retry | exponential backoff capped at 30s, reset on data |
| Truncated large frames | default max_size |
max_size=None |
FapiStream(symbols, streams=None, base_url=..., ping_interval=20, ping_timeout=20, recv_timeout=90, max_backoff=30)— async-iterate to getTrade/BookTickerevents; call.stop()to end.Trade(symbol, price, qty, buyer_is_maker, ts_ms)with.signed_qty.BookTicker(symbol, bid, ask, bid_qty, ask_qty)with.mid,.spread_bps.parse_message(raw)— pure, network-free frame parser (unit-tested).
python -m pytest -q # 8 tests, no network requiredMarket data only — no orders, no authentication, no account streams. It's a read-only feed reader. Not affiliated with Binance. MIT licensed.