# CryExc Backend Specification This document provides the complete specification for building a backend compatible with CryExc, a real-time cryptocurrency trading terminal. ## Overview CryExc frontend connects to a backend via: - **WebSocket** (`/ws`) - Real-time market data streams - **HTTP REST** - Configuration and initial data The frontend is built with C++/ImGui compiled to WebAssembly. It expects JSON messages over WebSocket and JSON responses from HTTP endpoints. --- ## Design Principles 1. **Per-stream granularity** - Subscribe, update, unsubscribe individual streams independently 2. **Self-contained streams** - Each stream message carries all required configuration 3. **Frontend owns lifecycle** - Frontend tracks which widgets need which streams 4. **Instance support** - Streams that can have multiple concurrent configurations use `instanceId` 5. **Clean separation** - No shared state or implicit defaults between streams --- ## WebSocket Protocol ### Connection - **Endpoint**: `ws://{host}:{port}/ws` or `wss://{host}:{port}/ws` - **Keepalive**: Client sends `{"type": "ping"}` every 30 seconds, server responds with `{"type": "pong"}` - **Reconnection**: Client implements exponential backoff on disconnect ### Message Format All messages are JSON objects with a `type` field. **Client → Server Messages:** | Type | Purpose | |------|---------| | `stream_subscribe` | Subscribe to a single stream | | `stream_subscribe_batch` | Subscribe to multiple streams at once | | `stream_update` | Update stream configuration (full replace) | | `stream_unsubscribe` | Unsubscribe from a stream | | `ping` | Keepalive | **Server → Client Messages:** | Type | Purpose | |------|---------| | `subscribed` | Confirms successful subscription | | `unsubscribed` | Confirms successful unsubscription | | `error` | Error message | | `pong` | Keepalive response | | `{stream_type}` | Live data for a stream | | `{stream_type}_historical` | Historical backfill on subscribe | | `{stream_type}_batch` | Batched live data (for high-frequency streams) | --- ## Stream Types | Stream | Instance-based | Historical | Description | |--------|----------------|------------|-------------| | `trade` | No | No | Real-time trades | | `orderbook` | No | No | Order book depth | | `liquidation` | No | No | Liquidation events | | `cvd` | No | Yes | Cumulative Volume Delta | | `orderbook_stats` | No | No | Aggregated orderbook statistics | | `dom` | No | No | Depth of Market ladder | | `footprint` | **Yes** | Yes | Footprint chart candles | | `orderbook_heatmap` | **Yes** | Yes | Orderbook heatmap overlay | | `pyth_price` | No | Yes | Pyth oracle prices | | `news` | No | Yes | News feed | **Instance-based streams**: Can have multiple concurrent subscriptions with different configurations. Each subscription requires a unique `instanceId`. **Historical streams**: Server automatically sends `{type}_historical` message after successful subscription. --- ## Client → Server Messages ### `stream_subscribe` Subscribe to a stream. For instance-based streams, include `instanceId`. **Standard stream:** ```json { "type": "stream_subscribe", "stream": "trade", "config": { "symbol": "BTCUSDT", "exchanges": [], "minNotional": 50000 } } ``` **Instance-based stream:** ```json { "type": "stream_subscribe", "stream": "footprint", "instanceId": "fp_0", "config": { "symbol": "BTCUSDT", "exchange": "binancef", "interval": "1m", "tickSize": 10.0, "timeRange": "1h" } } ``` ### `stream_subscribe_batch` Subscribe to multiple streams in a single message. Useful on initial connection. ```json { "type": "stream_subscribe_batch", "subscriptions": [ { "stream": "trade", "config": { "symbol": "BTCUSDT", "exchanges": [], "minNotional": 50000 } }, { "stream": "orderbook", "config": { "symbol": "BTCUSDT", "exchanges": ["binancef"], "tickSize": 0, "depth": 20 } }, { "stream": "footprint", "instanceId": "fp_0", "config": { "symbol": "BTCUSDT", "exchange": "binancef", "interval": "1m", "tickSize": 10.0, "timeRange": "1h" } } ] } ``` Server responds with individual `subscribed` confirmations for each stream. ### `stream_update` Update configuration for an existing subscription. **Replaces the entire config** (not a merge). **Standard stream:** ```json { "type": "stream_update", "stream": "trade", "config": { "symbol": "BTCUSDT", "exchanges": [], "minNotional": 100000 } } ``` **Instance-based stream:** ```json { "type": "stream_update", "stream": "footprint", "instanceId": "fp_0", "config": { "symbol": "BTCUSDT", "exchange": "binancef", "interval": "5m", "tickSize": 25.0, "timeRange": "4h" } } ``` ### `stream_unsubscribe` Unsubscribe from a stream. **Standard stream:** ```json { "type": "stream_unsubscribe", "stream": "trade" } ``` **Instance-based stream:** ```json { "type": "stream_unsubscribe", "stream": "footprint", "instanceId": "fp_0" } ``` ### `ping` ```json { "type": "ping" } ``` --- ## Server → Client Messages ### `subscribed` Confirms successful subscription. Echoes back the resolved configuration. ```json { "type": "subscribed", "stream": "trade", "config": { "symbol": "BTCUSDT", "exchanges": ["binancef", "bybit", "okx"], "minNotional": 50000 } } ``` For instance-based: ```json { "type": "subscribed", "stream": "footprint", "instanceId": "fp_0", "config": { ... } } ``` ### `unsubscribed` Confirms successful unsubscription. ```json { "type": "unsubscribed", "stream": "trade" } ``` For instance-based: ```json { "type": "unsubscribed", "stream": "footprint", "instanceId": "fp_0" } ``` ### `error` Error message. Can be in response to a specific action or a general error. ```json { "type": "error", "stream": "trade", "error": "Invalid symbol: INVALID" } ``` ### `pong` Response to ping. ```json { "type": "pong" } ``` --- ## Stream Configurations ### `trade` Real-time trade data from exchanges. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair (e.g., "BTCUSDT") | | `exchanges` | string[] | Yes | - | Exchange filter. Empty array = all exchanges | | `minNotional` | number | No | 0 | Minimum trade size in USD | | `side` | string | No | null | Filter by side: `"buy"`, `"sell"`, or null for both | **Server messages:** Single trade: ```json { "type": "trade", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "price": 42150.50, "qty": 0.5, "quoteQty": 21075.25, "isBuyerMaker": false, "timestamp": 1704067200123 } } ``` Batched trades (for high volume): ```json { "type": "trade_batch", "data": [ { "exchange": "binancef", "symbol": "BTCUSDT", "price": 42150.50, ... }, { "exchange": "bybit", "symbol": "BTCUSDT", "price": 42151.00, ... } ] } ``` **Notes:** - `isBuyerMaker: false` = taker bought (aggressive buy) - `isBuyerMaker: true` = taker sold (aggressive sell) --- ### `orderbook` Order book depth snapshots and delta updates. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair | | `exchanges` | string[] | Yes | - | Exchange filter. Empty = all | | `tickSize` | number | No | 0 | Price grouping. 0 = raw prices | | `depth` | number | No | 20 | Levels per side | **Server messages:** Full snapshot: ```json { "type": "orderbook", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "timestamp": 1704067200123, "bids": [ { "price": 42150.00, "qty": 5.5 }, { "price": 42149.00, "qty": 3.2 } ], "asks": [ { "price": 42151.00, "qty": 4.8 }, { "price": 42152.00, "qty": 2.1 } ] } } ``` Delta update (optional optimization): ```json { "type": "orderbook_delta", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "timestamp": 1704067200456, "bids": [{ "price": 42150.00, "qty": 6.2 }], "asks": [{ "price": 42153.00, "qty": 0.0 }] } } ``` **Notes:** - `qty: 0` in delta means remove that price level - Backend can send full snapshots only (simpler) or deltas (more efficient) --- ### `liquidation` Liquidation events. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair | | `exchanges` | string[] | Yes | - | Exchange filter. Empty = all | | `minNotional` | number | No | 0 | Minimum liquidation size in USD | **Server messages:** ```json { "type": "liquidation", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "side": "SELL", "price": 42100.00, "qty": 2.5, "quoteQty": 105250.00, "timestamp": 1704067200123 } } ``` **Notes:** - `side: "SELL"` = long position liquidated - `side: "BUY"` = short position liquidated --- ### `cvd` Cumulative Volume Delta per exchange. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair | | `exchanges` | string[] | Yes | - | Exchange filter. Empty = all | | `interval` | string | No | "raw" | Aggregation: `"raw"`, `"1m"`, `"5m"`, `"15m"`, `"1h"` | | `timeRange` | string | No | "1h" | Historical depth: `"15m"`, `"30m"`, `"1h"`, `"4h"`, `"12h"`, `"24h"` | **Server messages:** Historical (sent once on subscribe): ```json { "type": "cvd_historical", "data": { "exchange": "binancef", "points": [ { "timestamp": 1704060000000, "value": 150.5, "buyVolume": 500.2, "sellVolume": 349.7 }, { "timestamp": 1704063600000, "value": 280.3, "buyVolume": 750.8, "sellVolume": 470.5 } ] } } ``` Live update: ```json { "type": "cvd", "data": { "exchange": "binancef", "timestamp": 1704067200123, "value": 320.5, "buyVolume": 850.2, "sellVolume": 529.7 } } ``` --- ### `orderbook_stats` Aggregated orderbook statistics. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair | | `exchanges` | string[] | Yes | - | Exchange filter. Empty = all | **Server messages:** ```json { "type": "orderbook_stats", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "timestamp": 1704067200000, "bestBid": 42149.00, "bestAsk": 42150.00, "midPrice": 42149.50, "spread": 1.00, "bidQuantity_0_5pct": 150.5, "askQuantity_0_5pct": 140.2, "bidQuantity_2pct": 450.5, "askQuantity_2pct": 380.2, "bidQuantity_10pct": 2500.0, "askQuantity_10pct": 2200.0, "totalBidQuantity": 5000.0, "totalAskQuantity": 4500.0 } } ``` --- ### `dom` Depth of Market ladder with volume breakdown. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair | | `exchange` | string | Yes | - | Single exchange | | `tickSize` | number | Yes | - | Price level grouping | | `timeRange` | string | No | "1h" | Data accumulation: `"1h"`, `"4h"`, `"start_of_day"`, `"ny_open"` | **Server messages:** ```json { "type": "dom", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "midPrice": 42150.0, "dataStartTime": 1704060000000, "sessionHigh": 42500.0, "sessionLow": 41800.0, "levels": [ { "price": 42150.0, "bid": 5.5, "ask": 0.0, "sold": 12.3, "bought": 8.7, "delta": -3.6, "volume": 21.0 }, { "price": 42151.0, "bid": 0.0, "ask": 4.2, "sold": 6.1, "bought": 9.5, "delta": 3.4, "volume": 15.6 } ] } } ``` --- ### `footprint` (Instance-based) Footprint chart candles with volume by price level. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair | | `exchange` | string | Yes | - | Single exchange | | `interval` | string | Yes | - | Candle interval: `"1m"`, `"5m"`, `"15m"`, `"30m"`, `"1h"`, `"4h"` | | `tickSize` | number | Yes | - | Price level grouping | | `timeRange` | string | Yes | - | Historical depth: `"1h"`, `"4h"`, `"12h"`, `"24h"` | **Server messages:** Historical (sent once on subscribe): ```json { "type": "footprint_historical", "instanceId": "fp_0", "data": { "candles": [ { "openTime": 1704060000000, "open": 42100.0, "high": 42200.0, "low": 42050.0, "close": 42150.0, "volume": 125.5, "buyVolume": 70.2, "sellVolume": 55.3, "levels": [ { "price": 42150.0, "bidVolume": 5.5, "askVolume": 3.2, "delta": 2.3 }, { "price": 42140.0, "bidVolume": 8.1, "askVolume": 6.4, "delta": 1.7 } ] } ] } } ``` Live update (current candle): ```json { "type": "footprint", "instanceId": "fp_0", "data": { "candle": { "openTime": 1704067200000, "open": 42150.0, "high": 42180.0, "low": 42140.0, "close": 42175.0, "volume": 45.2, "buyVolume": 28.1, "sellVolume": 17.1, "levels": [...] } } } ``` **Notes:** - `bidVolume` = volume traded at bid (aggressive sells / taker sells) - `askVolume` = volume traded at ask (aggressive buys / taker buys) - `delta` = askVolume - bidVolume (positive = more buying) --- ### `orderbook_heatmap` (Instance-based) Orderbook depth snapshots for heatmap visualization. Tied to footprint chart instances. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbol` | string | Yes | - | Trading pair | | `exchange` | string | Yes | - | Single exchange | | `interval` | string | Yes | - | Snapshot interval (usually matches footprint) | | `tickSize` | number | Yes | - | Price level grouping | | `timeRange` | string | Yes | - | Historical depth | **Server messages:** Historical (sent once on subscribe): ```json { "type": "orderbook_heatmap_historical", "instanceId": "fp_0", "data": { "snapshots": [ { "timestamp": 1704060000000, "bids": [{ "price": 42000, "qty": 10.5 }], "asks": [{ "price": 42100, "qty": 8.2 }] } ] } } ``` Live update: ```json { "type": "orderbook_heatmap", "instanceId": "fp_0", "data": { "timestamp": 1704067200000, "bids": [{ "price": 42000, "qty": 12.0 }], "asks": [{ "price": 42100, "qty": 9.5 }] } } ``` --- ### `pyth_price` Prices from Pyth oracle network (macro indices, commodities). **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `symbols` | string[] | Yes | - | Pyth symbols: `"BTC"`, `"NASDAQ"`, `"DOW"`, `"SPY"`, `"GOLD"` | | `interval` | string | No | "raw" | Aggregation interval | | `timeRange` | string | No | "1h" | Historical depth | **Server messages:** Historical (sent once on subscribe): ```json { "type": "pyth_price_historical", "data": [ { "symbol": "BTC", "price": 43200.00, "confidence": 12.00, "timestamp": 1704060000000 }, { "symbol": "NASDAQ", "price": 14800.00, "confidence": 2.50, "timestamp": 1704060000000 } ] } ``` Live update: ```json { "type": "pyth_price", "data": { "symbol": "BTC", "price": 43250.50, "confidence": 15.25, "timestamp": 1704067200000 } } ``` --- ### `news` News feed items. **Config:** | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `sources` | string[] | No | [] | News sources filter. Empty = all | **Server messages:** Historical (sent once on subscribe): ```json { "type": "news_historical", "data": { "items": [ { "id": "news_123", "source": "coindesk", "title": "Bitcoin Reaches New High", "url": "https://...", "timestamp": 1704060000000, "sentiment": "positive" } ] } } ``` Live update: ```json { "type": "news", "data": { "id": "news_456", "source": "cointelegraph", "title": "ETF Approval Expected", "url": "https://...", "timestamp": 1704067200000, "sentiment": "positive" } } ``` --- ## HTTP REST Endpoints ### GET /api/exchanges Returns available exchanges and their capabilities. **Response:** ```json [ { "name": "binancef", "symbols": ["BTCUSDT", "ETHUSDT"], "capabilities": { "trades": true, "orderbook": true, "liquidations": true, "marketStats": true, "orderbookHeatmap": true, "screener": true } } ] ``` ### GET /api/features Returns which optional features are enabled. **Response:** ```json { "pythMarkets": true, "walletCohorts": true, "options": true } ``` **Feature Descriptions:** - `pythMarkets` - Pyth oracle price feeds (BTC, NASDAQ, DOW, SPY, GOLD correlations) - `walletCohorts` - Hyperliquid trader segment analysis (whales, dolphins, etc.) - `options` - Deribit options data (DVOL, max pain, options flow) ### GET /api/market-stats Returns market statistics. Polled every 5 seconds. **Response:** ```json [ { "exchange": "binancef", "symbol": "BTCUSDT", "fundingRate": 0.0001, "nextFundingTime": 1704067200000, "openInterest": 50000.5, "indexPrice": 42150.25, "markPrice": 42155.50 } ] ``` ### GET /api/wallet-cohorts Returns Hyperliquid wallet cohort data. Polled every 5 seconds. **Response:** ```json { "timestamp": 1704067200000, "countTraders": 15000, "countActiveTraders": 8500, "perpVolume": 125000000.0, "perpPositionsMetrics": { "count": 5000, "openInterest": 850000000.0 }, "segmentsAll": [ { "segmentId": 1, "name": "Whales", "category": "size", "countTradersInPosition": 45, "perpBias": 0.65, "positionValue": 125000000.0, "positionLongValue": 80000000.0, "positionShortValue": 45000000.0 } ], "biasHistory": [ { "segmentId": 1, "points": [ { "timestamp": 1704060000000, "bias": 0.62 }, { "timestamp": 1704063600000, "bias": 0.65 } ] } ] } ``` **Cohort Names (by position size):** - `Shrimp`, `Fish`, `Dolphin`, `Apex Predator`, `Small Whale`, `Whale`, `Tidal Whale`, `Leviathan` ### GET /api/options/aggregate Returns aggregated Deribit options data. Polled every 5 seconds. **Response:** ```json { "underlying": "BTC", "dvol": 52.5, "dvolChange24h": 2.3, "indexPrice": 42150.25, "forwardPrice30D": 42500.0, "totalPCRatioOI": 0.78, "totalPCRatioVol": 0.65, "maxPainStrike": 42000.0, "maxPainExpiry": "2024-01-26T08:00:00Z", "termStructure": [ { "expiryLabel": "26JAN24", "expiry": "2024-01-26T08:00:00Z", "daysToExpiry": 21, "atmIV": 48.5, "totalOI": 15000.5, "totalVol": 1200.0 } ] } ``` ### GET /api/options/chain?expiry=26JAN24 Returns options chain for a specific expiry. **Query params:** - `expiry` (required): Expiry label (e.g., "26JAN24") **Response:** ```json { "expiry": "2024-01-26T08:00:00Z", "expiryLabel": "26JAN24", "daysToExpiry": 21, "atmStrike": 42000.0, "forwardPrice": 42100.0, "indexPrice": 42150.0, "timestamp": 1704067200000, "options": [ { "strike": 40000, "callDelta": 0.72, "callGamma": 0.00002, "callIV": 48.5, "callBid": 2500.0, "callAsk": 2520.0, "callOI": 1500.5, "callVol": 125.0, "putDelta": -0.28, "putGamma": 0.00002, "putIV": 50.2, "putBid": 380.0, "putAsk": 395.0, "putOI": 980.2, "putVol": 85.0 } ] } ``` ### GET /api/orderbook-stats/historical Returns historical orderbook stats for charting. **Query params:** - `exchange` (required): Exchange name - `symbol` (required): Symbol - `interval` (optional): Data interval. Default: "1m" - `startTime` (optional): Start timestamp in ms. Default: 24h ago - `endTime` (optional): End timestamp in ms. Default: now - `limit` (optional): Max records. Default: 1000 **Response:** ```json { "exchange": "binancef", "symbol": "BTCUSDT", "startTime": 1703980800000, "endTime": 1704067200000, "stats": [ { "exchange": "binancef", "symbol": "BTCUSDT", "timestamp": 1704060000000, "bestBid": 42149.0, "bestAsk": 42150.0, "midPrice": 42149.5, "spread": 1.0, "bidQuantity_0_5pct": 150.5, "askQuantity_0_5pct": 140.2, "bidQuantity_2pct": 450.5, "askQuantity_2pct": 380.2, "bidQuantity_10pct": 2500.0, "askQuantity_10pct": 2200.0, "totalBidQuantity": 5000.0, "totalAskQuantity": 4500.0 } ] } ``` ### GET /screener Returns 24h ticker data for all symbols. **Response:** ```json [ { "exchange": "binancef", "symbol": "BTCUSDT", "lastPrice": 42150.5, "priceChange": 1050.25, "priceChangePercent": 2.56, "volume24h": 125000.5, "quoteVolume24h": 5500000000.0, "high24h": 42500.0, "low24h": 41000.0, "tradeCount": 850000, "timestamp": 1704067200000 } ] ``` ### GET /klines Returns OHLCV kline data. **Query params:** - `symbol` (required): Trading pair - `interval` (optional): Timeframe. Default: "1m" - `limit` (optional): Number of candles. Default: 500 **Response:** ```json [ { "timestamp": 1704067200000, "open": 42100.0, "high": 42200.0, "low": 42050.0, "close": 42150.0, "volume": 125.5 } ] ``` --- ## Widget → Stream Mapping | Widget | Required Streams | |--------|------------------| | Trades Table | `trade` | | Live Orderbook | `orderbook` | | Liquidations Table | `liquidation` | | CVD Chart | `cvd` | | Orderbook Stats | `orderbook_stats` | | DOM Ladder | `dom`, `orderbook` | | Footprint Chart | `footprint` (instance), optionally `orderbook_heatmap` (instance), `orderbook` | | Pyth Markets | `pyth_price` | | News Feed | `news` | | Symbol Summary | HTTP `/api/market-stats` | | Options Widget | HTTP `/api/options/*` | | Wallet Cohorts | HTTP `/api/wallet-cohorts` | | Screener Table | HTTP `/screener` | --- ## Example: Full Session ### 1. User opens app with Trades Table, Live Orderbook, Footprint Chart ```json {"type": "stream_subscribe", "stream": "trade", "config": {"symbol": "BTCUSDT", "exchanges": [], "minNotional": 50000}} {"type": "stream_subscribe", "stream": "orderbook", "config": {"symbol": "BTCUSDT", "exchanges": ["binancef"], "tickSize": 0, "depth": 20}} {"type": "stream_subscribe", "stream": "footprint", "instanceId": "fp_0", "config": {"symbol": "BTCUSDT", "exchange": "binancef", "interval": "1m", "tickSize": 10, "timeRange": "1h"}} {"type": "stream_subscribe", "stream": "orderbook_heatmap", "instanceId": "fp_0", "config": {"symbol": "BTCUSDT", "exchange": "binancef", "interval": "1m", "tickSize": 10, "timeRange": "1h"}} ``` ### 2. User changes trade filter to 100k minimum ```json {"type": "stream_update", "stream": "trade", "config": {"symbol": "BTCUSDT", "exchanges": [], "minNotional": 100000}} ``` ### 3. User changes footprint interval to 5m ```json {"type": "stream_update", "stream": "footprint", "instanceId": "fp_0", "config": {"symbol": "BTCUSDT", "exchange": "binancef", "interval": "5m", "tickSize": 25, "timeRange": "4h"}} {"type": "stream_update", "stream": "orderbook_heatmap", "instanceId": "fp_0", "config": {"symbol": "BTCUSDT", "exchange": "binancef", "interval": "5m", "tickSize": 25, "timeRange": "4h"}} ``` ### 4. User adds second footprint chart ```json {"type": "stream_subscribe", "stream": "footprint", "instanceId": "fp_1", "config": {"symbol": "BTCUSDT", "exchange": "binancef", "interval": "15m", "tickSize": 50, "timeRange": "12h"}} ``` ### 5. User closes first footprint chart ```json {"type": "stream_unsubscribe", "stream": "footprint", "instanceId": "fp_0"} {"type": "stream_unsubscribe", "stream": "orderbook_heatmap", "instanceId": "fp_0"} ``` ### 6. User closes Live Orderbook widget ```json {"type": "stream_unsubscribe", "stream": "orderbook"} ``` --- ## Exchange Identifiers Common exchange IDs used in the protocol: **Futures/Perpetuals:** - `binancef` - Binance Futures - `bybitf` - Bybit Futures - `okxf` - OKX Futures - `bitgetf` - Bitget Futures - `gatef` - Gate.io Futures - `htxf` - HTX Futures - `mexcf` - MEXC Futures - `bingxf` - BingX Futures - `deribitf` - Deribit Futures - `hyperliquidf` - Hyperliquid - `pacificaf` - Pacifica Finance **Spot:** - `binance` - Binance Spot - `bybit` - Bybit Spot - `okx` - OKX Spot - `coinbase` - Coinbase - `kraken` - Kraken --- ## Implementation Notes 1. **All timestamps are in milliseconds** (Unix epoch) 2. **All prices and quantities are floats/doubles** 3. **Empty exchanges array**: When `exchanges: []`, include data from all available exchanges 4. **Historical on subscribe**: For streams marked "Historical: Yes", send `{type}_historical` message immediately after `subscribed` confirmation 5. **Instance IDs**: For instance-based streams, the client manages `instanceId` strings. Backend tracks subscriptions per client + instanceId pair 6. **Batching**: High-frequency streams (trade) should batch messages to avoid overwhelming the connection. Recommended: batch every 100ms or 100 items 7. **Update behavior**: `stream_update` replaces the entire config. Client must send complete config, not just changed fields 8. **Error handling**: Send `error` message with relevant `stream` and `instanceId` if applicable 9. **Ping/pong**: Maintain connection health. If no pong received within 60 seconds, client should reconnect 10. **CORS**: Enable `Access-Control-Allow-Origin: *` for HTTP endpoints 11. **Compression**: WebSocket compression is supported and recommended --- ## Connecting from CryExc Frontend 1. Host your backend on a publicly accessible URL 2. Open CryExc and click the connection settings 3. Select "Self-Hosted Backend" 4. Enter your backend URL (e.g., `wss://your-server.com` or `your-server.com:8086`) 5. Click Connect The frontend will: - Fetch `/api/exchanges` to get available exchanges - Fetch `/api/features` to determine which optional widgets to enable - Connect to `/ws` for real-time data --- ## Critical Implementation Details These are important details discovered during implementation that aren't obvious from the message formats above: ### Timestamp Alignment All candle-based data (footprint, CVD, orderbook heatmap) **must use floor division** for timestamp bucketing: ``` candle_time = floor(timestamp / interval_ms) * interval_ms ``` Example: For a 1-minute interval (60000ms), a trade at timestamp `1704067245123` belongs to candle `1704067200000`. ### Trade Direction Semantics The `isBuyerMaker` field determines trade direction: - `isBuyerMaker: false` = **buy** (taker bought from maker's ask = aggressive buy) - `isBuyerMaker: true` = **sell** (taker sold to maker's bid = aggressive sell) This affects: - CVD calculation: `buyVolume` = sum of `!isBuyerMaker` trades, `sellVolume` = sum of `isBuyerMaker` trades - Footprint: `askVolume` (buys) = `!isBuyerMaker`, `bidVolume` (sells) = `isBuyerMaker` ### Footprint Data Format The frontend expects footprint levels as an **object with string price keys** (1 decimal place): ```json { "timestamp": 1704067200000, "open": 42000.0, "high": 42100.0, "low": 41900.0, "close": 42050.0, "volume": 500.5, "buyVolume": 300.2, "sellVolume": 200.3, "footprint": { "42000.0": { "bidVolume": 50.5, "askVolume": 80.2, "totalVolume": 130.7 }, "42010.0": { "bidVolume": 30.1, "askVolume": 45.3, "totalVolume": 75.4 } } } ``` **Critical:** Price keys must be formatted as strings with 1 decimal place: `"42000.0"` not `"42000"` or `42000`. ### Market Stats Required Fields The Symbol Summary widget requires these **calculated fields** in `/api/market-stats`: ```json { "exchange": "binancef", "symbol": "BTCUSDT", "timestamp": 1704067200000, "openInterest": 12345.67, "openInterestUsd": 518518500.0, "fundingRate": 0.0001, "nextFundingTime": 1704096000000, "markPrice": 42000.50, "indexPrice": 42000.25, "basis": 0.25, "basisBps": 0.59, "longShortRatio": 1.25, "longAccount": 0.556, "shortAccount": 0.444 } ``` **Calculations:** - `openInterestUsd` = `openInterest * markPrice` (displayed as "$X" in UI) - `basis` = `markPrice - indexPrice` - `basisBps` = `(basis / indexPrice) * 10000` - `longShortRatio`, `longAccount`, `shortAccount` from exchange's global L/S ratio API ### DOM Level Merging DOM data must **merge orderbook prices with trade prices**. Create levels for orderbook prices even if no trades occurred there: ```json { "type": "dom", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "timestamp": 1704067200000, "sessionHigh": 42500.0, "sessionLow": 41500.0, "sessionStart": 1704024000000, "levels": [ { "price": 42000.0, "bid": 25.5, "ask": 0, "sold": 0, "bought": 0, "delta": 0, "volume": 0 } ] } } ``` For each orderbook price not in trade history, include the level with `sold=0, bought=0, delta=0, volume=0`. ### Orderbook Heatmap Format Historical heatmap data format: ```json { "type": "orderbook_heatmap", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "historical": [ { "timestamp": 1704067200000, "bids": [{"price": 41990.0, "quantity": 25.5}, ...], "asks": [{"price": 42010.0, "quantity": 20.3}, ...] } ] }, "instanceId": "fp_0" } ``` **Critical:** - Store snapshots at **candle boundary timestamps** (aligned with footprint candles) - Sort `bids` **descending** by price (best bid first) - Sort `asks` **ascending** by price (best ask first) - Include `exchange` and `symbol` in the data object ### Orderbook Depth Config When `depth: 0` in orderbook subscription config, return **ALL** orderbook levels, not a limited subset. ### News Feed Format ```json { "type": "news", "data": { "id": "abc123", "source": "treeofalpha", "title": "Bitcoin breaks $42,000", "body": "Optional longer description", "url": "https://...", "timestamp": 1704067200000, "symbols": ["BTC", "BTCUSDT"] } } ``` Historical news on subscribe: ```json { "type": "news_historical", "data": [ { "id": "...", "source": "...", "title": "...", ... } ] } ``` ### Orderbook Stats Field Names The frontend expects specific field names: ```json { "type": "orderbook_stats", "data": { "exchange": "binancef", "symbol": "BTCUSDT", "timestamp": 1704067200000, "bestBid": 41999.0, "bestAsk": 42001.0, "midPrice": 42000.0, "spread": 2.0, "spreadBps": 0.48, "bidQty05Pct": 150.5, "askQty05Pct": 120.3, "bidQty2Pct": 500.2, "askQty2Pct": 480.1, "bidQty10Pct": 2000.5, "askQty10Pct": 1950.8, "totalBidQty": 5000.0, "totalAskQty": 4800.0 } } ``` ### Invalid Trade Protection Filter out trades with `price <= 0` or `quantity <= 0` before storing/processing. ### Tree of Alpha News Integration If implementing news via Tree of Alpha: - WebSocket URL: `wss://news.treeofalpha.com/ws` - Send `ping` every 10 seconds, expect `pong` response - Messages have fields: `_id`, `title`, `body`, `source`, `url`, `link`, `time`, `symbols` - `symbols` can be array of strings `["BTC"]` or array of objects `[{"symbol": "BTC"}]` --- ## Reference Implementation See the `example_backend/` directory for a complete Python (FastAPI + DuckDB) reference implementation that implements all streams and endpoints documented above.