Skip to main content

Local Order Book Sync

This system provides two depth channels that can be used together for efficient local order book maintenance:

  • depth@{symbol},{level}: Snapshot stream (200 ms throttled)
  • depth_update@{symbol}: Incremental stream (100 ms throttled)
  • GET /fapi/v1/depth?symbol=X&with_id=true: REST depth snapshot

Option 1: Snapshot Only (Simple Mode)

  1. Subscribe to depth@{symbol},20
  2. Replace the local order book every time a snapshot is received
  3. No incremental synchronization logic is required

Limitation: 200 ms throttling and limited depth levels mean intermediate changes may be missed.

Option 2: Snapshot + Delta (Accurate Mode)

Initialization Phase

Step 1: Subscribe to depth_update@{symbol} and start buffering delta events (do not apply yet)
Step 2: Obtain a snapshot using either of the following:
a) Subscribe to depth@{symbol},{level} and wait for the first push
b) Call REST GET /fapi/v1/depth?symbol={symbol}&with_id=true
Step 3: Record the snapshot lastUpdateId (field `u` or `id`)
Step 4: Initialize local ordered bids / asks with the snapshot data

Synchronization Phase

Step 5: Find the first buffered event satisfying U <= lastUpdateId+1 && u >= lastUpdateId+1
Discard all buffered events before it
Step 6: Apply this event and every subsequent delta event:
- quantity > 0 -> insert or update
- quantity = 0 -> delete
- update local lastUpdateId = event.u
Step 7: Validate continuity:
- U > local lastUpdateId+1 -> gap detected, reinitialize
- u <= local lastUpdateId -> stale event, discard
- otherwise apply normally

Exception Handling

ScenarioHandling
WS reconnectRe-run full initialization (Steps 1-4)
Gap in delta events (U > lastUpdateId + 1)Fetch a new snapshot and reinitialize
No delta push for a long timePeriodically verify with REST snapshot
Snapshot fetch failureRetry, or downgrade to Option 1

Sequence Diagram

Client Server
| |
|-- subscribe depth_update@X --->| Step 1
|-- GET /fapi/v1/depth?with_id ->| Step 2
|<-------- snapshot (id=100) ----| Step 3: lastUpdateId=100
|<-- depth_update U=99,u=101 ----| first valid event, apply
|<-- depth_update U=101,u=103 ---| apply
|<-- depth_update U=200,u=201 ---| gap detected, reinitialize

Complete Example

class OrderBook {
constructor(symbol) {
this.symbol = symbol;
this.bids = new Map();
this.asks = new Map();
this.lastUpdateId = 0;
this.buffer = [];
this.ready = false;
}

async init(ws) {
this.ready = false;
this.buffer = [];

ws.subscribe(`depth_update@${this.symbol}`, (data) => {
if (!this.ready) { this.buffer.push(data); return; }
this.applyUpdate(data);
});

const resp = await fetch(`/fapi/v1/depth?symbol=${this.symbol}&with_id=true`);
const snapshot = (await resp.json()).data;

this.bids.clear(); this.asks.clear();
for (const [p, q] of snapshot.bids) this.bids.set(p, q);
for (const [p, q] of snapshot.asks) this.asks.set(p, q);
this.lastUpdateId = snapshot.id;

for (const event of this.buffer) {
if (event.u <= this.lastUpdateId) continue;
if (event.U > this.lastUpdateId + 1) return this.init(ws);
this.applyUpdate(event);
}
this.buffer = [];
this.ready = true;
}

applyUpdate(event) {
if (event.u <= this.lastUpdateId) return;
if (event.U > this.lastUpdateId + 1) {
this.ready = false;
return;
}
for (const [p, q] of event.b) {
if (q === '0') this.bids.delete(p); else this.bids.set(p, q);
}
for (const [p, q] of event.a) {
if (q === '0') this.asks.delete(p); else this.asks.set(p, q);
}
this.lastUpdateId = event.u;
}
}