0
# Order Book Management
1
2
The OrderBook class provides automated maintenance of real-time order book state by processing WebSocket messages from Coinbase Pro's feed. It maintains accurate bid/ask data and handles the complexity of order book updates, making it easy to access current market depth.
3
4
## Capabilities
5
6
### Order Book Initialization
7
8
Create an OrderBook instance to track real-time order book state for a specific product.
9
10
```python { .api }
11
class OrderBook:
12
def __init__(self, product_id: str = 'BTC-USD', log_to = None):
13
"""
14
Initialize order book for real-time tracking of a specific product.
15
16
Parameters:
17
- product_id (str): Product to track (e.g., 'BTC-USD', 'ETH-USD')
18
- log_to: Optional file-like object for logging order book messages
19
"""
20
```
21
22
**Usage Example:**
23
```python
24
import cbpro
25
26
# Create order book for BTC-USD
27
order_book = cbpro.OrderBook(product_id='BTC-USD')
28
29
# With logging to file
30
with open('orderbook.log', 'wb') as log_file:
31
order_book = cbpro.OrderBook(product_id='BTC-USD', log_to=log_file)
32
```
33
34
### Message Processing
35
36
Process WebSocket messages to maintain accurate order book state with automatic synchronization.
37
38
```python { .api }
39
def process_message(self, message: dict):
40
"""
41
Process a WebSocket message to update the order book state.
42
43
Handles different message types:
44
- 'open': Add new order to book
45
- 'done': Remove completed order from book
46
- 'match': Update order sizes after trade execution
47
- 'change': Update order size changes
48
49
Parameters:
50
- message (dict): WebSocket message from Coinbase Pro feed
51
52
Automatically handles:
53
- Message sequence validation
54
- Order book resyncing on sequence gaps
55
- Initial book loading from REST API
56
"""
57
58
def reset_book(self):
59
"""
60
Reset and reload the order book from Coinbase Pro REST API.
61
62
Automatically called when:
63
- First message is processed
64
- Message sequence gaps are detected
65
- Manual reset is needed
66
"""
67
68
def on_sequence_gap(self, gap_start: int, gap_end: int):
69
"""
70
Called when a sequence gap is detected in WebSocket messages.
71
72
Override this method to customize gap handling behavior. Default
73
implementation calls reset_book() to resync.
74
75
Parameters:
76
- gap_start (int): Last processed sequence number
77
- gap_end (int): Next received sequence number
78
"""
79
```
80
81
**Usage Example:**
82
```python
83
# Integration with WebSocket client
84
class OrderBookWebsocket(cbpro.WebsocketClient):
85
def __init__(self, product_id):
86
super().__init__(
87
products=[product_id],
88
channels=['full'], # Full channel for complete order book updates
89
should_print=False
90
)
91
self.order_book = cbpro.OrderBook(product_id=product_id)
92
93
def on_message(self, msg):
94
# Process each message through order book
95
self.order_book.process_message(msg)
96
97
# Start tracking BTC-USD order book
98
ws_client = OrderBookWebsocket('BTC-USD')
99
ws_client.start()
100
101
# Access order book data
102
time.sleep(5) # Let it initialize
103
current_book = ws_client.order_book.get_current_book()
104
print(f"Bids: {len(current_book['bids'])}")
105
print(f"Asks: {len(current_book['asks'])}")
106
```
107
108
### Order Book Data Access
109
110
Retrieve current order book state and market data with convenient access methods.
111
112
```python { .api }
113
def get_current_book(self) -> dict:
114
"""
115
Get complete current order book state.
116
117
Returns:
118
dict: Complete order book containing:
119
- sequence: Current message sequence number
120
- bids: List of [price, size, order_id] for buy orders
121
- asks: List of [price, size, order_id] for sell orders
122
"""
123
124
def get_ask(self):
125
"""
126
Get the best (lowest) ask price.
127
128
Returns:
129
Decimal: Best ask price, or None if no asks available
130
"""
131
132
def get_bid(self):
133
"""
134
Get the best (highest) bid price.
135
136
Returns:
137
Decimal: Best bid price, or None if no bids available
138
"""
139
140
def get_asks(self, price) -> list:
141
"""
142
Get all ask orders at a specific price level.
143
144
Parameters:
145
- price: Price level to query
146
147
Returns:
148
list: List of orders at that price level, or None if no orders
149
"""
150
151
def get_bids(self, price) -> list:
152
"""
153
Get all bid orders at a specific price level.
154
155
Parameters:
156
- price: Price level to query
157
158
Returns:
159
list: List of orders at that price level, or None if no orders
160
"""
161
162
def get_current_ticker(self) -> dict:
163
"""
164
Get the most recent trade information.
165
166
Returns:
167
dict: Last trade data from 'match' messages, or None if no trades yet
168
"""
169
```
170
171
**Usage Example:**
172
```python
173
# Get current market data
174
best_bid = order_book.get_bid()
175
best_ask = order_book.get_ask()
176
177
if best_bid and best_ask:
178
spread = float(best_ask) - float(best_bid)
179
mid_price = (float(best_bid) + float(best_ask)) / 2
180
spread_bps = (spread / mid_price) * 10000
181
182
print(f"Best Bid: ${best_bid}")
183
print(f"Best Ask: ${best_ask}")
184
print(f"Spread: ${spread:.2f} ({spread_bps:.1f} bps)")
185
186
# Get market depth at specific levels
187
bid_orders_at_43000 = order_book.get_bids(43000.00)
188
if bid_orders_at_43000:
189
total_size = sum(float(order['size']) for order in bid_orders_at_43000)
190
print(f"Total size at $43,000 bid: {total_size} BTC")
191
192
# Get complete order book snapshot
193
book_snapshot = order_book.get_current_book()
194
print(f"Order book sequence: {book_snapshot['sequence']}")
195
print(f"Total bid levels: {len(book_snapshot['bids'])}")
196
print(f"Total ask levels: {len(book_snapshot['asks'])}")
197
```
198
199
## Advanced Usage Patterns
200
201
### Bid-Ask Spread Monitor
202
203
Track and log spread changes in real-time with automatic detection of significant moves.
204
205
```python
206
class SpreadMonitor(cbpro.OrderBook):
207
def __init__(self, product_id, spread_threshold=0.01):
208
super().__init__(product_id=product_id)
209
self.spread_threshold = spread_threshold
210
self.last_spread = None
211
self.spread_history = []
212
213
def process_message(self, message):
214
super().process_message(message)
215
216
# Check spread after each update
217
bid = self.get_bid()
218
ask = self.get_ask()
219
220
if bid and ask:
221
current_spread = float(ask) - float(bid)
222
223
# Log significant spread changes
224
if self.last_spread:
225
spread_change = abs(current_spread - self.last_spread)
226
if spread_change > self.spread_threshold:
227
print(f"Spread change: ${self.last_spread:.2f} -> ${current_spread:.2f}")
228
229
self.last_spread = current_spread
230
self.spread_history.append({
231
'timestamp': time.time(),
232
'spread': current_spread,
233
'bid': float(bid),
234
'ask': float(ask)
235
})
236
237
# Keep only last 1000 spreads
238
if len(self.spread_history) > 1000:
239
self.spread_history = self.spread_history[-1000:]
240
241
# Use spread monitor
242
spread_monitor = SpreadMonitor('BTC-USD', spread_threshold=1.0)
243
```
244
245
### Market Depth Analysis
246
247
Analyze order book depth and liquidity at different price levels.
248
249
```python
250
class DepthAnalyzer(cbpro.OrderBook):
251
def __init__(self, product_id):
252
super().__init__(product_id=product_id)
253
self.depth_levels = [0.1, 0.25, 0.5, 1.0, 2.0] # Percentage levels
254
255
def get_market_depth(self):
256
"""Calculate market depth at various percentage levels from best bid/ask."""
257
bid = self.get_bid()
258
ask = self.get_ask()
259
260
if not bid or not ask:
261
return None
262
263
book = self.get_current_book()
264
depth_analysis = {
265
'timestamp': time.time(),
266
'best_bid': float(bid),
267
'best_ask': float(ask),
268
'bid_depth': {},
269
'ask_depth': {}
270
}
271
272
# Analyze bid depth
273
for level_pct in self.depth_levels:
274
price_threshold = float(bid) * (1 - level_pct / 100)
275
total_size = 0
276
total_value = 0
277
278
for bid_level in book['bids']:
279
price, size, _ = bid_level
280
if float(price) >= price_threshold:
281
total_size += float(size)
282
total_value += float(price) * float(size)
283
284
depth_analysis['bid_depth'][f'{level_pct}%'] = {
285
'price_threshold': price_threshold,
286
'total_size': total_size,
287
'total_value': total_value
288
}
289
290
# Analyze ask depth
291
for level_pct in self.depth_levels:
292
price_threshold = float(ask) * (1 + level_pct / 100)
293
total_size = 0
294
total_value = 0
295
296
for ask_level in book['asks']:
297
price, size, _ = ask_level
298
if float(price) <= price_threshold:
299
total_size += float(size)
300
total_value += float(price) * float(size)
301
302
depth_analysis['ask_depth'][f'{level_pct}%'] = {
303
'price_threshold': price_threshold,
304
'total_size': total_size,
305
'total_value': total_value
306
}
307
308
return depth_analysis
309
310
def process_message(self, message):
311
super().process_message(message)
312
313
# Analyze depth every 100 messages
314
if hasattr(self, 'message_count'):
315
self.message_count += 1
316
else:
317
self.message_count = 1
318
319
if self.message_count % 100 == 0:
320
depth = self.get_market_depth()
321
if depth:
322
print(f"Market depth analysis:")
323
print(f" 1% bid depth: {depth['bid_depth']['1.0%']['total_size']:.4f} BTC")
324
print(f" 1% ask depth: {depth['ask_depth']['1.0%']['total_size']:.4f} BTC")
325
326
depth_analyzer = DepthAnalyzer('BTC-USD')
327
```
328
329
### Order Book Console Display
330
331
Real-time console display of order book changes with bid-ask spread monitoring.
332
333
```python
334
class OrderBookConsole(cbpro.OrderBook):
335
def __init__(self, product_id):
336
super().__init__(product_id=product_id)
337
self._bid = None
338
self._ask = None
339
self._bid_depth = None
340
self._ask_depth = None
341
342
def process_message(self, message):
343
if message.get('product_id') == self.product_id:
344
super().process_message(message)
345
346
try:
347
# Calculate current bid-ask spread
348
bid = self.get_bid()
349
ask = self.get_ask()
350
351
if bid and ask:
352
bids = self.get_bids(bid)
353
asks = self.get_asks(ask)
354
355
bid_depth = sum(float(b['size']) for b in bids) if bids else 0
356
ask_depth = sum(float(a['size']) for a in asks) if asks else 0
357
358
# Only print if there are changes
359
if (self._bid != bid or self._ask != ask or
360
self._bid_depth != bid_depth or self._ask_depth != ask_depth):
361
362
self._bid = bid
363
self._ask = ask
364
self._bid_depth = bid_depth
365
self._ask_depth = ask_depth
366
367
spread = float(ask) - float(bid)
368
timestamp = datetime.now().strftime('%H:%M:%S')
369
370
print(f'{timestamp} {self.product_id} '
371
f'bid: {bid_depth:.3f} @ ${float(bid):,.2f} | '
372
f'ask: {ask_depth:.3f} @ ${float(ask):,.2f} | '
373
f'spread: ${spread:.2f}')
374
375
except Exception as e:
376
print(f"Display error: {e}")
377
378
# Usage with WebSocket
379
class ConsoleWebsocket(cbpro.WebsocketClient):
380
def __init__(self, products):
381
super().__init__(
382
products=products,
383
channels=['full'],
384
should_print=False
385
)
386
self.order_books = {}
387
for product in products:
388
self.order_books[product] = OrderBookConsole(product)
389
390
def on_message(self, msg):
391
product_id = msg.get('product_id')
392
if product_id in self.order_books:
393
self.order_books[product_id].process_message(msg)
394
395
# Monitor multiple products
396
console_ws = ConsoleWebsocket(['BTC-USD', 'ETH-USD'])
397
console_ws.start()
398
```
399
400
## Error Handling and Synchronization
401
402
The OrderBook class includes robust error handling:
403
404
- **Sequence Gap Detection**: Automatically detects missing messages and resyncs
405
- **Book Reset**: Reloads complete order book from REST API when needed
406
- **Message Validation**: Handles malformed or out-of-sequence messages
407
- **State Consistency**: Maintains consistent bid/ask ordering using SortedDict
408
409
### Sequence Gap Handling
410
411
```python
412
def on_sequence_gap(self, gap_start, gap_end):
413
"""
414
Called when a sequence gap is detected.
415
Override this method to customize gap handling behavior.
416
417
Parameters:
418
- gap_start: Last processed sequence number
419
- gap_end: Next received sequence number
420
"""
421
print(f"Sequence gap detected: {gap_start} -> {gap_end}")
422
print("Resetting order book...")
423
self.reset_book()
424
```
425
426
## Performance Considerations
427
428
- **Memory Usage**: Order book size grows with market activity; consider periodic resets for long-running applications
429
- **Processing Speed**: Keep message processing fast to avoid falling behind the feed
430
- **Data Structures**: Uses SortedDict for efficient price-level access and maintenance
431
- **Logging**: Optional pickle logging can impact performance; use only when needed
432
- **Channel Selection**: Use 'level2' channel for lighter-weight order book tracking if full depth isn't needed
433
434
The OrderBook class is designed for high-frequency updates and maintains real-time accuracy even during periods of intense trading activity.