Skip to main content

nanobook/
book.rs

1//! OrderBook: The complete order book with both sides and order storage.
2//!
3//! This is the core data structure that combines:
4//! - Bids (buy orders) sorted high → low
5//! - Asks (sell orders) sorted low → high
6//! - Central order storage for O(1) lookup by OrderId
7
8use rustc_hash::FxHashMap;
9
10use crate::{Order, OrderId, Price, PriceLevels, Quantity, Side, TimeInForce, Timestamp, TradeId};
11
12// Re-import for tests only
13#[cfg(test)]
14use crate::OrderStatus;
15
16/// The complete order book.
17///
18/// Maintains both sides of the book plus a central index of all orders
19/// (active and historical) for O(1) lookup.
20#[derive(Clone, Debug)]
21pub struct OrderBook {
22    /// Buy orders, sorted by price descending (best = highest)
23    bids: PriceLevels,
24    /// Sell orders, sorted by price ascending (best = lowest)
25    asks: PriceLevels,
26    /// All orders indexed by ID (includes filled/cancelled for history)
27    pub(crate) orders: FxHashMap<OrderId, Order>,
28    /// Next order ID to assign
29    next_order_id: u64,
30    /// Next trade ID to assign
31    next_trade_id: u64,
32    /// Next timestamp to assign (monotonic counter)
33    next_timestamp: u64,
34}
35
36impl OrderBook {
37    /// Create a new empty order book.
38    pub fn new() -> Self {
39        Self {
40            bids: PriceLevels::new(Side::Buy),
41            asks: PriceLevels::new(Side::Sell),
42            orders: FxHashMap::default(),
43            next_order_id: 1,
44            next_trade_id: 1,
45            next_timestamp: 1,
46        }
47    }
48
49    // === ID and timestamp generation ===
50
51    /// Generate the next order ID (monotonically increasing).
52    pub fn next_order_id(&mut self) -> OrderId {
53        let id = OrderId(self.next_order_id);
54        self.next_order_id += 1;
55        id
56    }
57
58    /// Generate the next trade ID (monotonically increasing).
59    pub fn next_trade_id(&mut self) -> TradeId {
60        let id = TradeId(self.next_trade_id);
61        self.next_trade_id += 1;
62        id
63    }
64
65    /// Generate the next timestamp (monotonically increasing).
66    pub fn next_timestamp(&mut self) -> Timestamp {
67        let ts = self.next_timestamp;
68        self.next_timestamp += 1;
69        ts
70    }
71
72    /// Peek at what the next order ID would be (without consuming it).
73    pub fn peek_next_order_id(&self) -> OrderId {
74        OrderId(self.next_order_id)
75    }
76
77    // === Order access ===
78
79    /// Get an order by ID (includes historical filled/cancelled orders).
80    pub fn get_order(&self, order_id: OrderId) -> Option<&Order> {
81        self.orders.get(&order_id)
82    }
83
84    /// Get a mutable reference to an order by ID.
85    pub fn get_order_mut(&mut self, order_id: OrderId) -> Option<&mut Order> {
86        self.orders.get_mut(&order_id)
87    }
88
89    /// Check if an order exists.
90    pub fn contains_order(&self, order_id: OrderId) -> bool {
91        self.orders.contains_key(&order_id)
92    }
93
94    /// Returns the total number of orders (including historical).
95    pub fn order_count(&self) -> usize {
96        self.orders.len()
97    }
98
99    /// Returns the number of active orders (on the book).
100    pub fn active_order_count(&self) -> usize {
101        self.orders.values().filter(|o| o.is_active()).count()
102    }
103
104    // === Book access ===
105
106    /// Get the bids side (buy orders).
107    pub fn bids(&self) -> &PriceLevels {
108        &self.bids
109    }
110
111    /// Get the asks side (sell orders).
112    pub fn asks(&self) -> &PriceLevels {
113        &self.asks
114    }
115
116    /// Get mutable access to bids.
117    pub fn bids_mut(&mut self) -> &mut PriceLevels {
118        &mut self.bids
119    }
120
121    /// Get mutable access to asks.
122    pub fn asks_mut(&mut self) -> &mut PriceLevels {
123        &mut self.asks
124    }
125
126    /// Get the appropriate side for an order.
127    pub fn side(&self, side: Side) -> &PriceLevels {
128        match side {
129            Side::Buy => &self.bids,
130            Side::Sell => &self.asks,
131        }
132    }
133
134    /// Get mutable access to the appropriate side.
135    pub fn side_mut(&mut self, side: Side) -> &mut PriceLevels {
136        match side {
137            Side::Buy => &mut self.bids,
138            Side::Sell => &mut self.asks,
139        }
140    }
141
142    /// Get the opposite side (for matching).
143    pub fn opposite_side(&self, side: Side) -> &PriceLevels {
144        self.side(side.opposite())
145    }
146
147    /// Get mutable access to the opposite side.
148    pub fn opposite_side_mut(&mut self, side: Side) -> &mut PriceLevels {
149        self.side_mut(side.opposite())
150    }
151
152    // === Best prices ===
153
154    /// Get the best bid price (highest buy price).
155    pub fn best_bid(&self) -> Option<Price> {
156        self.bids.best_price()
157    }
158
159    /// Get the best ask price (lowest sell price).
160    pub fn best_ask(&self) -> Option<Price> {
161        self.asks.best_price()
162    }
163
164    /// Get both best bid and best ask.
165    pub fn best_bid_ask(&self) -> (Option<Price>, Option<Price>) {
166        (self.best_bid(), self.best_ask())
167    }
168
169    /// Get the spread (best ask - best bid), if both exist.
170    pub fn spread(&self) -> Option<i64> {
171        match (self.best_bid(), self.best_ask()) {
172            (Some(bid), Some(ask)) => Some(ask.0 - bid.0),
173            _ => None,
174        }
175    }
176
177    /// Check if the book is crossed (best bid >= best ask).
178    /// This should never happen after matching is complete.
179    pub fn is_crossed(&self) -> bool {
180        match (self.best_bid(), self.best_ask()) {
181            (Some(bid), Some(ask)) => bid >= ask,
182            _ => false,
183        }
184    }
185
186    // === Order management ===
187
188    /// Add a new order to the book.
189    ///
190    /// The order must have a unique ID (typically from `next_order_id()`).
191    /// The order is stored in the central index and added to the appropriate
192    /// price level based on its side and price.
193    ///
194    /// # Panics
195    ///
196    /// Panics if an order with the same ID already exists.
197    pub fn add_order(&mut self, mut order: Order) {
198        assert!(
199            !self.orders.contains_key(&order.id),
200            "order {} already exists",
201            order.id
202        );
203
204        let side = order.side;
205        let price = order.price;
206        let quantity = order.remaining_quantity;
207        let order_id = order.id;
208
209        // Add to appropriate price level and get its index
210        let index = self.side_mut(side).insert_order(price, order_id, quantity);
211        order.position_in_level = index;
212
213        // Store in central index
214        self.orders.insert(order_id, order);
215    }
216
217    /// Remove an order from the book (for cancellation).
218    ///
219    /// Updates the order's status to Cancelled and marks it as a tombstone
220    /// in the price level queue for O(1) performance.
221    pub fn cancel_order(&mut self, order_id: OrderId) -> Option<Quantity> {
222        let order = self.orders.get_mut(&order_id)?;
223
224        if !order.is_active() {
225            return None;
226        }
227
228        let side = order.side;
229        let price = order.price;
230        let remaining = order.remaining_quantity;
231        let index = order.position_in_level;
232
233        // Cancel the order (updates status)
234        order.cancel();
235
236        // Mark as tombstone in price level (O(1))
237        self.side_mut(side).mark_tombstone(price, index, remaining);
238
239        Some(remaining)
240    }
241
242    /// Create a new order with auto-generated ID and timestamp.
243    ///
244    /// This is a convenience method that:
245    /// 1. Generates the next order ID
246    /// 2. Generates the next timestamp
247    /// 3. Creates the Order struct
248    ///
249    /// The order is NOT added to the book — use `add_order()` for that.
250    pub fn create_order(
251        &mut self,
252        side: Side,
253        price: Price,
254        quantity: Quantity,
255        time_in_force: TimeInForce,
256    ) -> Order {
257        let id = self.next_order_id();
258        let timestamp = self.next_timestamp();
259        Order::new(id, side, price, quantity, timestamp, time_in_force)
260    }
261
262    /// Remove filled and cancelled orders from history to free memory.
263    ///
264    /// Active orders (on the book) are preserved. Returns the number of
265    /// orders removed.
266    ///
267    /// Use this periodically for long-running instances to prevent
268    /// unbounded memory growth.
269    pub fn clear_history(&mut self) -> usize {
270        let before = self.orders.len();
271        self.orders.retain(|_, order| order.is_active());
272        before - self.orders.len()
273    }
274
275    /// Remove all tombstones from the book.
276    pub fn compact(&mut self) {
277        self.bids.compact();
278        self.asks.compact();
279    }
280}
281
282impl Default for OrderBook {
283    fn default() -> Self {
284        Self::new()
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn new_book_is_empty() {
294        let book = OrderBook::new();
295
296        assert_eq!(book.order_count(), 0);
297        assert_eq!(book.active_order_count(), 0);
298        assert_eq!(book.best_bid(), None);
299        assert_eq!(book.best_ask(), None);
300        assert_eq!(book.spread(), None);
301        assert!(!book.is_crossed());
302    }
303
304    #[test]
305    fn id_generation_is_monotonic() {
306        let mut book = OrderBook::new();
307
308        assert_eq!(book.next_order_id(), OrderId(1));
309        assert_eq!(book.next_order_id(), OrderId(2));
310        assert_eq!(book.next_order_id(), OrderId(3));
311
312        assert_eq!(book.next_trade_id(), TradeId(1));
313        assert_eq!(book.next_trade_id(), TradeId(2));
314
315        assert_eq!(book.next_timestamp(), 1);
316        assert_eq!(book.next_timestamp(), 2);
317    }
318
319    #[test]
320    fn peek_order_id_does_not_consume() {
321        let mut book = OrderBook::new();
322
323        assert_eq!(book.peek_next_order_id(), OrderId(1));
324        assert_eq!(book.peek_next_order_id(), OrderId(1));
325        assert_eq!(book.next_order_id(), OrderId(1));
326        assert_eq!(book.peek_next_order_id(), OrderId(2));
327    }
328
329    #[test]
330    fn add_and_get_order() {
331        let mut book = OrderBook::new();
332
333        let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
334        let order_id = order.id;
335        book.add_order(order);
336
337        assert!(book.contains_order(order_id));
338        assert_eq!(book.order_count(), 1);
339        assert_eq!(book.active_order_count(), 1);
340
341        let retrieved = book.get_order(order_id).unwrap();
342        assert_eq!(retrieved.id, order_id);
343        assert_eq!(retrieved.price, Price(100_00));
344        assert_eq!(retrieved.remaining_quantity, 100);
345    }
346
347    #[test]
348    fn add_order_updates_best_prices() {
349        let mut book = OrderBook::new();
350
351        // Add a bid
352        let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
353        book.add_order(bid);
354        assert_eq!(book.best_bid(), Some(Price(100_00)));
355        assert_eq!(book.best_ask(), None);
356
357        // Add an ask
358        let ask = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
359        book.add_order(ask);
360        assert_eq!(book.best_bid(), Some(Price(100_00)));
361        assert_eq!(book.best_ask(), Some(Price(101_00)));
362    }
363
364    #[test]
365    fn spread_calculation() {
366        let mut book = OrderBook::new();
367
368        // No spread without both sides
369        assert_eq!(book.spread(), None);
370
371        let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
372        book.add_order(bid);
373        assert_eq!(book.spread(), None);
374
375        let ask = book.create_order(Side::Sell, Price(101_50), 100, TimeInForce::GTC);
376        book.add_order(ask);
377        assert_eq!(book.spread(), Some(150)); // 101.50 - 100.00 = 1.50 = 150 cents
378    }
379
380    #[test]
381    fn cancel_order() {
382        let mut book = OrderBook::new();
383
384        let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
385        let order_id = order.id;
386        book.add_order(order);
387
388        assert_eq!(book.active_order_count(), 1);
389        assert_eq!(book.best_bid(), Some(Price(100_00)));
390
391        // Cancel it
392        let cancelled = book.cancel_order(order_id);
393        assert_eq!(cancelled, Some(100));
394
395        // Order still exists but is not active
396        assert_eq!(book.order_count(), 1);
397        assert_eq!(book.active_order_count(), 0);
398        assert_eq!(
399            book.get_order(order_id).unwrap().status,
400            OrderStatus::Cancelled
401        );
402
403        // Best bid is now gone
404        assert_eq!(book.best_bid(), None);
405    }
406
407    #[test]
408    fn cancel_nonexistent_order() {
409        let mut book = OrderBook::new();
410        assert_eq!(book.cancel_order(OrderId(999)), None);
411    }
412
413    #[test]
414    fn cancel_already_cancelled() {
415        let mut book = OrderBook::new();
416
417        let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
418        let order_id = order.id;
419        book.add_order(order);
420
421        book.cancel_order(order_id);
422        assert_eq!(book.cancel_order(order_id), None); // Already cancelled
423    }
424
425    #[test]
426    fn multiple_orders_same_price() {
427        let mut book = OrderBook::new();
428
429        let o1 = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
430        let o2 = book.create_order(Side::Buy, Price(100_00), 200, TimeInForce::GTC);
431        let o3 = book.create_order(Side::Buy, Price(100_00), 150, TimeInForce::GTC);
432
433        book.add_order(o1);
434        book.add_order(o2);
435        book.add_order(o3);
436
437        assert_eq!(book.active_order_count(), 3);
438        assert_eq!(book.bids().level_count(), 1);
439        assert_eq!(book.bids().total_quantity(), 450);
440    }
441
442    #[test]
443    fn multiple_price_levels() {
444        let mut book = OrderBook::new();
445
446        let o1 = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
447        let o2 = book.create_order(Side::Buy, Price(99_00), 200, TimeInForce::GTC);
448        let o3 = book.create_order(Side::Sell, Price(101_00), 150, TimeInForce::GTC);
449        let o4 = book.create_order(Side::Sell, Price(102_00), 175, TimeInForce::GTC);
450
451        book.add_order(o1);
452        book.add_order(o2);
453        book.add_order(o3);
454        book.add_order(o4);
455
456        assert_eq!(book.bids().level_count(), 2);
457        assert_eq!(book.asks().level_count(), 2);
458        assert_eq!(book.best_bid(), Some(Price(100_00)));
459        assert_eq!(book.best_ask(), Some(Price(101_00)));
460    }
461
462    #[test]
463    fn is_crossed() {
464        let mut book = OrderBook::new();
465
466        // Not crossed when empty
467        assert!(!book.is_crossed());
468
469        // Not crossed with normal spread
470        let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
471        let ask = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
472        book.add_order(bid);
473        book.add_order(ask);
474        assert!(!book.is_crossed());
475
476        // Would be crossed if we add a higher bid (in practice, matching prevents this)
477        let high_bid = book.create_order(Side::Buy, Price(102_00), 100, TimeInForce::GTC);
478        book.add_order(high_bid);
479        assert!(book.is_crossed());
480    }
481
482    #[test]
483    fn opposite_side() {
484        let mut book = OrderBook::new();
485
486        let bid = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
487        let ask = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
488        book.add_order(bid);
489        book.add_order(ask);
490
491        // Opposite of buy is sell (asks)
492        assert_eq!(
493            book.opposite_side(Side::Buy).best_price(),
494            Some(Price(101_00))
495        );
496        // Opposite of sell is buy (bids)
497        assert_eq!(
498            book.opposite_side(Side::Sell).best_price(),
499            Some(Price(100_00))
500        );
501    }
502
503    #[test]
504    #[should_panic(expected = "already exists")]
505    fn add_duplicate_order_panics() {
506        let mut book = OrderBook::new();
507
508        let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
509        let order_clone = order.clone();
510
511        book.add_order(order);
512        book.add_order(order_clone); // Panic: duplicate ID
513    }
514
515    #[test]
516    fn get_order_mut() {
517        let mut book = OrderBook::new();
518
519        let order = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
520        let order_id = order.id;
521        book.add_order(order);
522
523        // Modify via mutable reference
524        {
525            let order = book.get_order_mut(order_id).unwrap();
526            order.fill(30);
527        }
528
529        // Verify change persisted
530        let order = book.get_order(order_id).unwrap();
531        assert_eq!(order.remaining_quantity, 70);
532        assert_eq!(order.filled_quantity, 30);
533    }
534
535    #[test]
536    fn clear_history_removes_inactive_orders() {
537        let mut book = OrderBook::new();
538
539        // Add some orders
540        let o1 = book.create_order(Side::Buy, Price(100_00), 100, TimeInForce::GTC);
541        let o2 = book.create_order(Side::Buy, Price(99_00), 100, TimeInForce::GTC);
542        let o3 = book.create_order(Side::Sell, Price(101_00), 100, TimeInForce::GTC);
543        let o1_id = o1.id;
544        let o2_id = o2.id;
545        let o3_id = o3.id;
546
547        book.add_order(o1);
548        book.add_order(o2);
549        book.add_order(o3);
550
551        assert_eq!(book.order_count(), 3);
552
553        // Cancel one order (becomes inactive but stays in history)
554        book.cancel_order(o1_id);
555        assert_eq!(book.order_count(), 3);
556        assert_eq!(book.active_order_count(), 2);
557
558        // Clear history - should remove the cancelled order
559        let removed = book.clear_history();
560        assert_eq!(removed, 1);
561        assert_eq!(book.order_count(), 2);
562        assert_eq!(book.active_order_count(), 2);
563
564        // Cancelled order should no longer be accessible
565        assert!(book.get_order(o1_id).is_none());
566        // Active orders should still be there
567        assert!(book.get_order(o2_id).is_some());
568        assert!(book.get_order(o3_id).is_some());
569    }
570}