The normalization problem in crypto market data
Ask five exchanges for "the latest trade" and you get five different answers: different field names, different timestamp units, different ideas of what a symbol is called. Every quant desk, terminal, and dashboard ends up maintaining the same unglamorous translation layer — exchange dialect in, clean schema out — before any real work begins.
A normalized crypto WebSocket feed removes that layer. One subscribe message, one JSON response shape, whether the tick is BTC spot, a continuous future, or an FX pair. You write the consumer once and point it at any market.
What TIQEX's normalized stream looks like
From the official docs (https://tiqex.io/developers), the entire streaming contract is two lines:
wss://api.tiqex.io/api/v1/market/stream
{ "action": "subscribe", "market": "crypto", "symbols": ["BTCUSDT"] }
That's it. Send a subscribe JSON with market and symbols; the stream pushes ticks back in a consistent JSON shape. The docs explicitly call out:
- Millisecond delivery for terminals, dashboards, and strategy runtimes
- Batch multi-symbol subscriptions — one socket, many symbols
- REST + WebSocket pairing — "Use REST for snapshots and historical candles; WebSocket for live ticks. Same API key. Stream handshake counts once; pushed ticks are not re-billed."
Authentication is the same X-API-Key header pattern as REST:
curl https://api.tiqex.io/api/v1/market/crypto/quote?symbols=BTCUSDT \
-H "X-API-Key: YOUR_API_KEY"
Keep keys server-side; never ship them in public frontend bundles.
Putting it to work: a normalized multi-market consumer
Because the response shape is consistent, one handler serves every subscription:
import json, websocket
WS_URL = "wss://api.tiqex.io/api/v1/market/stream"
def on_message(ws, raw):
msg = json.loads(raw)
# same fields whether this is crypto, futures, FX, or equities —
# field names here are illustrative; confirm against the endpoint
# reference in the docs before production use
handle_tick(market=msg.get("market"), symbol=msg.get("symbol"),
price=float(msg.get("price", 0)), ts=msg.get("ts"))
def handle_tick(market, symbol, price, ts):
# your normalization layer is now ~5 lines instead of 5 files
store(market, symbol, price, ts)
def on_open(ws):
# subscribe only after the socket is connected —
# sending before run_forever() raises WebSocketConnectionClosedException
ws.send(json.dumps({"action": "subscribe", "market": "crypto",
"symbols": ["BTCUSDT", "ETHUSDT"]}))
ws = websocket.WebSocketApp(WS_URL,
header={"X-API-Key": "YOUR_API_KEY"},
on_open=on_open,
on_message=on_message)
ws.run_forever()
The docs also provide heartbeat and reconnect guidance — for a strategy runtime, wire on_close to exponential-backoff resubscribe rather than crashing the loop.
Honest limits (from the docs FAQ)
- Plan-gated: "Upgrade for WebSocket, lower latency, higher quotas, or more markets in production." Check console entitlements before assuming stream access on a free key.
- Varies by market: "Latency, field granularity, and history depth vary by market and plan."
- Consolidated, not raw: "Output may not match any single exchange tick-for-tick." If your strategy depends on exchange-specific sequencing, this is a feature summary feed, not a replacement for the exchange's native socket.
- Symbol codes: crypto uses
BTCUSDT-style codes; other markets accept product short codes or TV symbols — see each endpoint's parameter table and the console symbol lists rather than guessing.
When normalization pays for itself
- Cross-venue dashboards: one charting component, N markets, zero per-exchange parsers.
- Strategy portability: develop against crypto ticks, deploy the same consumer on futures or FX by changing the
marketfield. - Backtest/live parity: REST candles for history plus WebSocket ticks for live, in one schema — your offline features and online features finally agree.
One key, one subscribe shape, one JSON schema. Grab a key at tiqex.io and replace your exchange-dialect translation layer with five lines of code.