Orderbook Updates
Note: In other exchanges this is sometime called orderBook.
Receive live changes to market depth, price levels, and liquidity for specific trading pairs.
Endpoint
ws.send() wss://api.synthetix.io/v1/ws/subscriptionsSubscription Request
Subscribe to Orderbook Updates
Authenticate before subscribing:
{
"jsonrpc": "2.0",
"id": "auth-1",
"method": "post",
"params": {
"action": "authenticate",
"subAccountId": "1867542890123456789",
"nonce": 1704067200000,
"signature": {
"v": 28,
"r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
}
}
}EIP-712 Domain:
{ "name": "Synthetix", "version": "1", "chainId": 1, "verifyingContract": "0x0000000000000000000000000000000000000000" }
{
"jsonrpc": "2.0",
"id": "sub-1",
"method": "subscribe",
"params": {
"type": "orderbook",
"symbol": "BTC-USDT"
}
}Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Client-generated unique request identifier |
method | string | Yes | Must be "subscribe" |
params.type | string | Yes | Must be "orderbook" |
params.symbol | string | Yes | Trading pair symbol (e.g., "BTC-USDT") |
Orderbook Update Messages
Initial Snapshot
Upon subscription, you'll receive a full orderbook snapshot:
{
"method": "orderbook_depth_update",
"data": {
"symbol": "BTC-USDT",
"timestamp": "2025-01-01T00:00:00.000Z",
"bids": [
{"price": "50000.00", "quantity": "1.5"},
{"price": "49999.50", "quantity": "2.0"},
{"price": "49999.00", "quantity": "0.8"}
],
"asks": [
{"price": "50001.00", "quantity": "1.2"},
{"price": "50001.50", "quantity": "3.1"},
{"price": "50002.00", "quantity": "0.9"}
]
}
}Incremental Updates
After the snapshot, you'll receive incremental updates with the same structure:
{
"method": "orderbook_depth_update",
"data": {
"symbol": "BTC-USDT",
"timestamp": "2025-01-01T00:00:01.000Z",
"bids": [
{"price": "50000.00", "quantity": "0.0"}, // Removed (quantity = 0)
{"price": "49998.50", "quantity": "1.0"} // Added/Updated
],
"asks": [
{"price": "50002.00", "quantity": "1.5"}, // Updated quantity
{"price": "50003.00", "quantity": "0.5"} // Added new level
]
}
}Price Level Format
Each price level is represented as an object with price and quantity fields:
{"price": "50000.50", "quantity": "1.25"}Where:
price(string): Price levelquantity(string): Total quantity at this level
Special Case: When quantity is "0.0", the price level should be removed from the orderbook.
Managing Local Orderbook
Initial Setup
class OrderbookManager {
constructor() {
this.orderbooks = new Map();
}
processOrderbookUpdate(data) {
const { symbol } = data;
// If orderbook doesn't exist, initialize it
if (!this.orderbooks.has(symbol)) {
this.initializeOrderbook(symbol, data);
} else {
this.updateOrderbook(symbol, data);
}
}
initializeOrderbook(symbol, data) {
const orderbook = {
symbol,
bids: new Map(),
asks: new Map(),
timestamp: data.timestamp
};
// Load initial bids
data.bids.forEach(({price, quantity}) => {
orderbook.bids.set(price, quantity);
});
// Load initial asks
data.asks.forEach(({price, quantity}) => {
orderbook.asks.set(price, quantity);
});
this.orderbooks.set(symbol, orderbook);
console.log(`Orderbook initialized for ${symbol}`);
}
updateOrderbook(symbol, data) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) {
console.error(`No orderbook found for ${symbol}`);
return;
}
// Apply bid updates
data.bids?.forEach(({price, quantity}) => {
if (quantity === "0.0") {
orderbook.bids.delete(price);
} else {
orderbook.bids.set(price, quantity);
}
});
// Apply ask updates
data.asks?.forEach(({price, quantity}) => {
if (quantity === "0.0") {
orderbook.asks.delete(price);
} else {
orderbook.asks.set(price, quantity);
}
});
orderbook.timestamp = data.timestamp;
this.notifyOrderbookChange(symbol, orderbook);
}
getOrderbook(symbol) {
return this.orderbooks.get(symbol);
}
getBestBidAsk(symbol) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) return null;
const bestBid = Math.max(...Array.from(orderbook.bids.keys()).map(parseFloat));
const bestAsk = Math.min(...Array.from(orderbook.asks.keys()).map(parseFloat));
return {
bestBid: bestBid.toString(),
bestAsk: bestAsk.toString(),
spread: (bestAsk - bestBid).toString(),
spreadPercent: ((bestAsk - bestBid) / bestBid * 100).toFixed(4)
};
}
getDepth(symbol, levels = 10) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) return null;
// Sort and limit bids (highest first)
const bids = Array.from(orderbook.bids.entries())
.sort(([a], [b]) => parseFloat(b) - parseFloat(a))
.slice(0, levels);
// Sort and limit asks (lowest first)
const asks = Array.from(orderbook.asks.entries())
.sort(([a], [b]) => parseFloat(a) - parseFloat(b))
.slice(0, levels);
return { bids, asks };
}
notifyOrderbookChange(symbol, orderbook) {
// Emit events for UI updates
this.emit('orderbookChange', {
symbol,
orderbook,
bestBidAsk: this.getBestBidAsk(symbol),
timestamp: Date.now()
});
}
}Usage Example
const orderbookManager = new OrderbookManager();
// First authenticate with EIP-712 signature if required
const domain = {
name: "Synthetix",
version: "1",
chainId: 1,
verifyingContract: "0x0000000000000000000000000000000000000000"
};
// Note: Orderbook updates are public data and typically don't require authentication
// However, some implementations may require authentication for rate limiting purposes
// Subscribe to orderbook updates
const subscription = {
id: "sub-1",
method: "subscribe",
params: {
type: "orderbook",
symbol: "BTC-USDT"
}
};
ws.send(JSON.stringify(subscription));
// Handle incoming messages
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.method === "orderbook_depth_update") {
orderbookManager.processOrderbookUpdate(message.data);
}
};
// Listen for orderbook changes
orderbookManager.on('orderbookChange', (event) => {
const { symbol, bestBidAsk } = event;
console.log(`${symbol} Best: ${bestBidAsk.bestBid}/${bestBidAsk.bestAsk}`);
// Update UI
updateOrderbookDisplay(symbol, orderbookManager.getDepth(symbol, 20));
});Performance Optimization
Efficient Data Structures
// Use optimized data structure for large orderbooks
class OptimizedOrderbook {
constructor() {
this.bids = new Map(); // Could use sorted tree for very large books
this.asks = new Map();
this.bidsSorted = []; // Cache sorted arrays
this.asksSorted = [];
this.sortedDirty = true;
}
updateLevel(side, price, quantity) {
const book = side === 'bid' ? this.bids : this.asks;
if (quantity === "0.0") {
book.delete(price);
} else {
book.set(price, quantity);
}
this.sortedDirty = true;
}
getSortedLevels(side, limit = 10) {
if (this.sortedDirty) {
this.updateSortedCache();
}
const sorted = side === 'bid' ? this.bidsSorted : this.asksSorted;
return sorted.slice(0, limit);
}
updateSortedCache() {
this.bidsSorted = Array.from(this.bids.entries())
.sort(([a], [b]) => parseFloat(b) - parseFloat(a));
this.asksSorted = Array.from(this.asks.entries())
.sort(([a], [b]) => parseFloat(a) - parseFloat(b));
this.sortedDirty = false;
}
}Rate Limiting Updates
class ThrottledOrderbookManager extends OrderbookManager {
constructor(throttleMs = 50) {
super();
this.throttleMs = throttleMs;
this.pendingUpdates = new Map();
this.updateTimers = new Map();
}
updateOrderbook(symbol, data) {
// Store latest update
this.pendingUpdates.set(symbol, data);
// Clear existing timer
if (this.updateTimers.has(symbol)) {
clearTimeout(this.updateTimers.get(symbol));
}
// Set new timer
const timer = setTimeout(() => {
const latestUpdate = this.pendingUpdates.get(symbol);
if (latestUpdate) {
super.updateOrderbook(symbol, latestUpdate);
this.pendingUpdates.delete(symbol);
this.updateTimers.delete(symbol);
}
}, this.throttleMs);
this.updateTimers.set(symbol, timer);
}
}Authentication
While orderbook updates are public data and typically don't require authentication, some implementations may require EIP-712 authentication for rate limiting purposes.
EIP-712 Domain Separator
All authenticated requests use the following domain separator:
{
"name": "Synthetix",
"version": "1",
"chainId": 1,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}Error Handling
Connection Recovery
handleConnectionError(symbol) {
const orderbook = this.orderbooks.get(symbol);
if (!orderbook) {
console.error(`No orderbook for ${symbol}, requesting snapshot`);
this.resubscribe(symbol);
return;
}
// Clear existing orderbook and resubscribe
this.orderbooks.delete(symbol);
this.resubscribe(symbol);
}
resubscribe(symbol) {
// Re-subscribe to get fresh snapshot
const subscription = {
id: "resub-" + Date.now(),
method: "subscribe",
params: {
type: "orderbook",
symbol
}
};
this.ws.send(JSON.stringify(subscription));
}Common Issues
| Error | Description | Solution |
|---|---|---|
| Connection lost | WebSocket disconnection | Reconnect and resubscribe |
| Invalid symbol | Symbol not available | Check supported markets |
| Stale data | Orderbook out of sync | Clear and resubscribe |
Use Cases
Market Making
orderbookManager.on('orderbookChange', (event) => {
const { symbol, bestBidAsk } = event;
const spread = parseFloat(bestBidAsk.spread);
if (spread > 1.0) { // Wide spread opportunity
console.log(`Wide spread detected on ${symbol}: ${spread}`);
// Consider placing maker orders
}
});Liquidity Analysis
function analyzeLiquidity(symbol, priceRange = 0.01) {
const orderbook = orderbookManager.getOrderbook(symbol);
const bestBidAsk = orderbookManager.getBestBidAsk(symbol);
const midPrice = (parseFloat(bestBidAsk.bestBid) + parseFloat(bestBidAsk.bestAsk)) / 2;
const rangeMin = midPrice * (1 - priceRange);
const rangeMax = midPrice * (1 + priceRange);
let bidLiquidity = 0;
let askLiquidity = 0;
// Calculate liquidity within price range
orderbook.bids.forEach((quantity, price) => {
const priceNum = parseFloat(price);
if (priceNum >= rangeMin) {
bidLiquidity += parseFloat(quantity) * priceNum;
}
});
orderbook.asks.forEach((quantity, price) => {
const priceNum = parseFloat(price);
if (priceNum <= rangeMax) {
askLiquidity += parseFloat(quantity) * priceNum;
}
});
return {
bidLiquidity,
askLiquidity,
totalLiquidity: bidLiquidity + askLiquidity,
liquidityImbalance: (bidLiquidity - askLiquidity) / (bidLiquidity + askLiquidity)
};
}Implementation Notes
- Initial State: On subscription, you receive a full orderbook snapshot
- Incremental Updates: All subsequent messages contain changes to apply
- Price Level Management: Remove levels when quantity is "0.0"
- Efficient Storage: Use appropriate data structures for your use case
- Rate Limiting: Throttle UI updates for high-frequency markets
- Error Recovery: Implement robust error handling and recovery
Related Endpoints
- Get Orderbook - Get orderbook snapshot via REST
- Market Price Updates - Track price changes
- Get Last Trades - Track trade executions via REST endpoint