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}