Overview
The Market Depth endpoint provides real-time order book data with support for both one-time queries and subscription-based updates.
Update Behavior
After successful subscription, the server sends a full snapshot of the order book as the first depth_update message. All subsequent messages are incremental updates containing only the changes since the last update.
Understanding Updates
- First message (
past_update_id is absent): Full order book snapshot with all price levels
- Subsequent messages (
past_update_id is present): Only changed price levels
- Removed levels: Shown as
[price, "0"] - when amount is “0”, remove that price level from your local order book
If 10 seconds elapse without any order book changes, the server will push a full snapshot again as a keepalive mechanism.
Maintaining a Local Order Book
To maintain an accurate local order book, follow this algorithm:
- Subscribe to depth updates
- Receive the first message (full snapshot) - initialize your order book
- For each incremental update:
- If amount is “0” → remove that price level
- If amount is not “0” → update existing level or insert new level at the correct sorted position
- Keep order book sorted: asks ascending, bids descending
- Truncate to your desired limit after each update
Code Examples
type IDepth = [string, string];
interface OrderBook {
asks: IDepth[];
bids: IDepth[];
}
const ws = new WebSocket("wss://api.whitebit.com/ws");
const orderBook: OrderBook = { asks: [], bids: [] };
const LIMIT = 100;
ws.addEventListener("open", () => {
ws.send(
JSON.stringify({
id: 1,
method: "depth_subscribe",
params: ["ETH_BTC", LIMIT, "0", true]
}),
);
});
ws.addEventListener("message", (event: MessageEvent) => {
const message = JSON.parse(event.data.toString());
if (message.method === "depth_update") {
const updateData = message.params[0] as Partial<OrderBook & { past_update_id?: number }>;
const isFirstMessage = !updateData.past_update_id;
if (isFirstMessage) {
// First message or keepalive snapshot is a full snapshot - replace order book
orderBook.asks = updateData.asks ?? [];
orderBook.bids = updateData.bids ?? [];
} else {
// Subsequent messages are incremental updates
applyUpdates(orderBook.asks, updateData.asks, "ask");
applyUpdates(orderBook.bids, updateData.bids, "bid");
truncateOrderBook(orderBook.asks);
truncateOrderBook(orderBook.bids);
}
}
});
function applyUpdates(orderBookSide: IDepth[], updates: IDepth[] | undefined, side: "ask" | "bid") {
if (updates === undefined) return;
for (const [price, amount] of updates) {
// Find the index of an entry in orderBookSide that matches the given price.
const priceIndex = orderBookSide.findIndex((level) => level[0] === price);
// If the amount is '0', it means this price level should be removed from the orderBookSide.
if (amount === "0") {
if (priceIndex !== -1) {
// Remove the existing price level since the amount is '0'.
orderBookSide.splice(priceIndex, 1);
}
} else {
// If the amount is not '0', either update the existing price level or add a new one.
if (priceIndex === -1) {
// Find the position where the new price level should be inserted.
const insertIndex = orderBookSide.findIndex((level) =>
side === "ask" ? level[0] > price : level[0] < price
);
if (insertIndex === -1) {
// Add to the end if there's no higher price level.
orderBookSide.push([price, amount]);
} else {
// Insert at the correct sorted position.
orderBookSide.splice(insertIndex, 0, [price, amount]);
}
} else {
// Update the amount for the existing price level.
orderBookSide[priceIndex][1] = amount;
}
}
}
}
function truncateOrderBook(orderBookSide: IDepth[]) {
if (orderBookSide.length > LIMIT) {
// Only truncate if the length exceeds the LIMIT
orderBookSide.splice(LIMIT);
}
}