order_book/
order_book.rs

1use crate::types::{Order, OrderEvent, ExactPriceLevelMap, Side};
2use rust_decimal::Decimal;
3use std::collections::BTreeMap;
4
5/// The core order book structure that maintains price-time priority.
6///
7/// This structure is responsible only for:
8///
9/// - Storing orders at each price level
10/// - Maintaining price priority (best bid/ask)
11/// - Publishing events when orders are inserted
12///
13/// It does not maintain aggregated market depth, as that is handled by the external
14/// `MarketDepthCache` service to minimize lock contention.
15///
16/// ## Thread Safety
17///
18/// This structure is designed to be wrapped in a `RwLock` for concurrent access.
19/// The write lock should be held only briefly during order insertion.
20#[derive(Debug)]
21pub struct OrderBook {
22    /// Ask side (sell orders): sorted by ascending price (lowest ask first)
23    asks: ExactPriceLevelMap,
24    /// Bid side (buy orders): sorted by descending price (highest bid first)
25    bids: ExactPriceLevelMap,
26}
27
28impl OrderBook {
29    /// Creates a new empty order book.
30    ///
31    /// ## Examples
32    ///
33    /// ```
34    /// use order_book::OrderBook;
35    ///
36    /// let order_book = OrderBook::new();
37    /// ```
38    pub fn new() -> Self {
39        OrderBook {
40            asks: BTreeMap::new(),
41            bids: BTreeMap::new(),
42        }
43    }
44
45    /// Aggregates a precise price to its integer price level.
46    ///
47    /// This function truncates the decimal portion of the price, effectively
48    /// grouping all orders with prices like 100.01, 100.25, 100.99 into the
49    /// same aggregated level of 100.
50    ///
51    /// ## Arguments
52    ///
53    /// * `price`: The exact order price to aggregate
54    ///
55    /// ## Returns
56    ///
57    /// The truncated price level as a Decimal
58    ///
59    /// ## Examples
60    ///
61    /// ```
62    /// use order_book::OrderBook;
63    /// use rust_decimal::Decimal;
64    ///
65    /// let price = Decimal::new(10025, 2); // 100.25
66    /// let aggregated = OrderBook::aggregate_price_to_level(price);
67    /// assert_eq!(aggregated, Decimal::new(100, 0)); // 100
68    /// ```
69    pub fn aggregate_price_to_level(price: Decimal) -> Decimal {
70        price.trunc()
71    }
72
73    /// Inserts a new order into the order book and returns an event.
74    ///
75    /// This method:
76    /// 1. Adds the order to the appropriate price level (maintaining time priority)
77    /// 2. Returns an `OrderEvent` that downstream services can use to update their state
78    ///
79    /// The write lock should be held only during this operation, which is $O(\log{N})$
80    /// where $N$ is the number of distinct price levels.
81    ///
82    /// ## Arguments
83    ///
84    /// * `order`: The order to insert
85    ///
86    /// ## Returns
87    ///
88    /// An `OrderEvent` describing the change that occurred
89    ///
90    /// ## Examples
91    ///
92    /// ```
93    /// use order_book::{OrderBook, Order, Side};
94    /// use rust_decimal::Decimal;
95    ///
96    /// let mut order_book = OrderBook::new();
97    /// let order = Order::new(100.50, 100, Side::Bid);
98    ///
99    /// let event = order_book.insert_order(order);
100    /// assert_eq!(event.quantity_delta, 100);
101    /// ```
102    pub fn insert_order(&mut self, order: Order) -> OrderEvent {
103        // Select the appropriate price level map based on side
104        let price_level_map = match order.side {
105            Side::Bid => &mut self.bids,
106            Side::Ask => &mut self.asks,
107        };
108
109        // Insert the order at its price level, maintaining time priority
110        price_level_map
111            .entry(order.price)
112            .or_insert_with(Vec::new)
113            .push(order.clone());
114
115        // Publish the event for downstream consumers
116        OrderEvent {
117            price: order.price,
118            quantity_delta: order.quantity,
119            side: order.side,
120        }
121    }
122
123    /// Computes the current best bid and best ask prices.
124    ///
125    /// This operation acquires a read lock and is O(1) due to the BTreeMap structure:
126    ///
127    /// - Best bid is the highest price in the bid map (last key)
128    /// - Best ask is the lowest price in the ask map (first key)
129    ///
130    /// ## Returns
131    ///
132    /// A tuple of `(best_bid, best_ask)` where each is `Option<Decimal>`.
133    /// Returns `None` if there are no orders on that side.
134    ///
135    /// ## Examples
136    ///
137    /// ```
138    /// use order_book::{OrderBook, Order, Side};
139    /// use rust_decimal::Decimal;
140    ///
141    /// let mut order_book = OrderBook::new();
142    /// order_book.insert_order(Order::new(100.50, 100, Side::Bid));
143    ///
144    /// let (best_bid, best_ask, spread) = order_book.compute_spread();
145    /// assert_eq!(best_bid, Some(Decimal::new(10050, 2)));
146    /// assert_eq!(best_ask, None);
147    /// ```
148    pub fn compute_spread(&self) -> (Option<Decimal>, Option<Decimal>, Option<Decimal>) {
149        // BTreeMap maintains sorted order:
150        // - For bids: higher prices come last (use `next_back` to get highest)
151        // - For asks: lower prices come first (use `next` to get lowest)
152        let best_bid = self.bids.keys().next_back().copied();
153        let best_ask = self.asks.keys().next().copied();
154        let spread = best_bid.and_then(|b| best_ask.map(|a| a - b));
155
156        (best_bid, best_ask, spread)
157    }
158
159    /// Returns the number of distinct price levels on the bid side.
160    ///
161    /// ## Returns
162    ///
163    /// The count of unique bid price levels
164    pub fn bid_levels_count(&self) -> usize {
165        self.bids.len()
166    }
167
168    /// Returns the number of distinct price levels on the ask side.
169    ///
170    /// ## Returns
171    ///
172    /// The count of unique ask price levels
173    pub fn ask_levels_count(&self) -> usize {
174        self.asks.len()
175    }
176
177    /// Returns the total number of orders at a specific price level.
178    ///
179    /// ## Arguments
180    ///
181    /// * `price`: The price level to query
182    /// * `side`: The side (bid or ask) to query
183    ///
184    /// ## Returns
185    ///
186    /// The number of orders at that price level, or 0 if no orders exist
187    pub fn orders_at_exact_price_level(&self, price: Decimal, side: Side) -> usize {
188        let price_level_map = match side {
189            Side::Bid => &self.bids,
190            Side::Ask => &self.asks,
191        };
192
193        price_level_map
194            .get(&price)
195            .map(|orders| orders.len())
196            .unwrap_or(0)
197    }
198}
199
200impl Default for OrderBook {
201    fn default() -> Self {
202        Self::new()
203    }
204}