Skip to content

Market Data Service

The Market Data Service is a Go microservice responsible for ingesting raw trade/book events from the matching engine, maintaining canonical market data (order books, candles, tickers, mark/index/funding prices), persisting to ClickHouse, and distributing real-time feeds to clients via a multiplexed WebSocket.

  • Data Ingestion: Consume trade and order book delta events from Kafka.
  • Order Book Management: Maintain per-symbol Level 2 (L2) order books in memory, with delta compression and periodic snapshots.
  • OHLCV Aggregation: Roll up trades into candlesticks for multiple timeframes (1m, 5m, 15m, 1h, 4h, 1d).
  • Pricing Services: Compute index prices (from external exchanges), mark prices (fair value), and funding rates (perpetuals).
  • Ticker Computation: Calculate 24h rolling ticker statistics (open, high, low, close, volume, change).
  • Data Persistence: Store trades and candles in ClickHouse; order book snapshots in PostgreSQL.
  • WebSocket Distribution: Single multiplexed WebSocket endpoint with subscribe/unsubscribe channels.
  • REST & gRPC APIs: Snapshot endpoints for clients and internal services.
  • External Integration: Track spot prices from Binance, Coinbase, Kraken, etc. via ccxt.
graph TB ME[Matching Engine] -->|engine.event.v1| Kafka[Kafka] MetaSvc[Metadata Service] -->|instrument events| Kafka Kafka --> MDS[Market Data Service] MDS --> Books[In-Memory Order Books] MDS --> OHLCV[OHLCV Aggregator] MDS --> Pricing[Pricing Workers] MDS --> Ticker[Ticker Worker] ExternalEx[External Exchanges] -->|ccxt| MDS MDS -->|WebSocket| Clients[WebSocket Clients] MDS -->|REST| RestClients[REST Clients] MDS -->|gRPC| InternalSvc[Internal Services] MDS --> PG[(PostgreSQL)] MDS --> CH[(ClickHouse)] MDS --> Redis[(Redis Cache)]
FieldTypeDescription
trade_idUUIDUnique trade identifier
symbolSTRINGInstrument symbol
priceDECIMALTrade price
quantityDECIMALTrade quantity
taker_sideENUM(buy, sell)Taker’s side
maker_order_idUUIDMaker’s order ID
taker_order_idUUIDTaker’s order ID
timestampINT64Epoch milliseconds
sequenceINT64Engine sequence number
FieldTypeDescription
priceDECIMALPrice level
quantityDECIMALTotal quantity at price (0 = removed)
FieldTypeDescription
symbolSTRINGInstrument symbol
bidsArray of BookLevelBid levels (descending)
asksArray of BookLevelAsk levels (ascending)
sequenceINT64Snapshot sequence
timestampINT64Snapshot time
FieldTypeDescription
symbolSTRINGInstrument symbol
intervalENUM1m, 5m, 15m, 1h, 4h, 1d
open_timeINT64Candle open timestamp
close_timeINT64Candle close timestamp
openDECIMALOpening price
highDECIMALHighest price
lowDECIMALLowest price
closeDECIMALClosing price
volumeDECIMALBase volume
quote_volumeDECIMALQuote volume
trade_countINTNumber of trades
FieldTypeDescription
symbolSTRINGInstrument symbol
last_priceDECIMALLast traded price
price_changeDECIMAL24h price change
price_change_pctDECIMAL24h change percentage
high_24hDECIMAL24h high
low_24hDECIMAL24h low
volume_24hDECIMAL24h base volume
quote_volume_24hDECIMAL24h quote volume
best_bidDECIMALCurrent best bid
best_askDECIMALCurrent best ask
open_interestDECIMALOpen interest (perpetuals)
FieldTypeDescription
symbolSTRINGInstrument symbol
mark_priceDECIMALFair value mark price
index_priceDECIMALUnderlying index price
premiumDECIMALBasis (mark - index)
timestampINT64Computation time
FieldTypeDescription
symbolSTRINGIndex symbol (e.g., BTCUSDT-INDEX)
priceDECIMALWeighted average price
sourcesArrayIndividual exchange prices and weights
timestampINT64Computation time
FieldTypeDescription
symbolSTRINGPerpetual symbol
funding_rateDECIMALCurrent funding rate
next_funding_timeINT64Next settlement time
countdown_secondsINTSeconds until next funding
mark_priceDECIMALCurrent mark price
index_priceDECIMALCurrent index price

Single multiplexed WebSocket: ws://host/ws

{ "op": "subscribe", "channel": "book", "symbol": "BTCUSDT-PERP" }
{ "op": "unsubscribe", "channel": "trades", "symbol": "BTCUSDT-PERP" }
{ "op": "config", "prec": 0.5 }
{ "op": "ping" }
ChannelDataUpdate Frequency
bookOrder book deltas (full snapshot on subscribe, then deltas)On every change
tradesIndividual trade ticksOn every trade
ticker24h ticker statisticsEvery 1 second
kline:{interval}OHLCV candles (e.g., kline:1m, kline:5m)On close / in-progress update
markMark price updatesEvery 1 second
indexIndex price updatesEvery 1 second
fundingFunding rate with countdownEvery 1 second
  • On subscribe to book: server sends a full snapshot
  • Subsequent messages are incremental deltas (price levels that changed)
  • Client reconstructs book by applying deltas to snapshot
  • Full snapshot re-sent every 30 seconds to correct any drift
  • Sequence numbers included for gap detection
  • 100 messages/second per client connection
  • Configurable via WS_RATE_MAX_MSGS_PER_SEC
MethodEndpointDescription
GET/v1/symbolsList trading symbols
GET/v1/trades?symbol={symbol}&limit={n}Get recent trades
GET/v1/depth?symbol={symbol}&depth={n}Get order book depth
GET/v1/ticker/24h?symbol={symbol}Get 24h ticker
GET/v1/klines?symbol={symbol}&interval={i}&start={t}&end={t}Get candlesticks
GET/v1/mark?symbol={symbol}Get mark price
GET/v1/index?symbol={symbol}Get index price
GET/v1/funding?symbol={symbol}Get funding rate
RPCDescriptionCalled By
GetOrderBook(symbol, depth)Get current order book snapshotRisk Service, MM Service
GetMarkPrice(symbol)Get current mark pricePosition Service, Funding Service
ReplayStream(symbol, channel, start_time, end_time)Stream historical dataAnalytics
TopicDescription
engine.event.v1Matching engine events (trades, book deltas)
engine.snapshot.v1Full order book snapshots
engine.bookhash.v1Order book integrity hashes
md.instrument.created.v1New instrument notifications
md.instrument.updated.v1Instrument metadata changes
md.instrument.halt.v1Trading halt events
md.instrument.resume.v1Trading resume events
TopicDescription
md.trades.v1.{symbol}Normalized trade events (per symbol)
md.orderbook.delta.v1Order book deltas
md.orderbook.snap.v1Order book snapshots
md.ohlcv.v1Candlestick updates
md.ticker24h.v124h ticker updates
md.mark.v1Mark price updates
md.index.v1Index price updates
md.funding.v1Funding rate updates
  • Runs on a timer per timeframe (1m, 5m, 15m, 1h, 4h, 1d)
  • On each tick: close completed candle, start new one
  • Updates are also triggered in real-time as trades arrive
  • Persists completed candles to PostgreSQL
  • Computes mark price: mark = index + EMA(premium)
  • Computes funding rate: rate = clamp(premium_rate + interest_rate, -0.75%, +0.75%)
  • Publishes to Kafka and broadcasts via WebSocket
  • Recalculates 24h rolling statistics every second
  • Uses sliding window over trade history
  • Publishes to WebSocket ticker channel
  • Fetches spot prices from external exchanges via ccxt library
  • Exchanges: Binance, Coinbase, Kraken, OKX (configurable)
  • Weighted average with configurable weights per source
  • Stale source detection: excludes sources that haven’t updated within threshold
  • Persists raw spot prices to ClickHouse for audit trail
  • Trades: All trade events with high-cardinality indexing
  • Raw spot prices: External exchange prices for index computation
  • Order book snapshots: Historical snapshots for analytics
  • Optimized for batch writes with buffering
  • Candles: OHLCV data for all timeframes
  • Order book snapshots: Latest snapshots for REST queries
  • Pricing data: Mark, index, and funding rate history
  • Latest order book: Per-symbol book state for quick REST queries
  • Latest ticker: 24h ticker cache
  • Latest prices: Mark, index, funding cache
  • md_ingest_lag_ms{topic} — Event processing lag
  • md_seq_gap_total{symbol} — Sequence gaps detected
  • md_ws_clients — Active WebSocket connections
  • md_snapshot_latency_ms{symbol} — Snapshot generation latency
  • md_kafka_messages_consumed_total{topic} — Kafka messages consumed
  • md_db_query_duration_ms{repository} — Database query duration
  • md_ohlcv_candles_closed_total{symbol,interval} — Candles closed
  • md_index_price_source_staleness_ms{exchange} — External source staleness

Spans: md.process_trade, md.process_book_delta, md.compute_mark, md.compute_index, md.broadcast_ws, md.persist_candle

Structured JSON via zap: symbol, channel, event_type, sequence, latency_us, trace_id.

MetricTarget
Trade-to-WebSocket latency (p99)< 150 ms
Order book update latency (p99)< 50 ms
Candle accuracy100% (no missed trades)
WebSocket availability>= 99.9%
Index price freshness< 2 seconds
VariableDescriptionDefault
KAFKA_BROKERSKafka broker addressesRequired
SCHEMA_REGISTRY_URLAvro schema registryRequired
REDIS_URLRedis connection stringRequired
POSTGRES_URLPostgreSQL connection stringRequired
CLICKHOUSE_URLClickHouse connection stringRequired
HTTP_PORTREST + WebSocket port8080
GRPC_PORTgRPC server port50051
WS_RATE_MAX_MSGS_PER_SECWebSocket rate limit per client100
  • Language: Go 1.21+
  • WebSocket: gorilla/websocket
  • Database: PostgreSQL (SQLC), ClickHouse
  • Migrations: Atlas
  • Kafka: confluent-kafka-go with Avro
  • External Exchanges: ccxt library
  • Metrics: Prometheus client_golang
  • Logging: zap structured logging
  • gRPC: google.golang.org/grpc