order_book/market_depth_cache.rs
1use crate::order_book::OrderBook;
2use crate::types::{AggregatedDepthMap, OrderEvent, Side};
3use parking_lot::RwLock;
4use std::collections::BTreeMap;
5
6/// An external cache service that maintains aggregated market depth.
7///
8/// This structure is completely decoupled from the core `OrderBook` and operates
9/// on events published by the order book. It maintains its own state and locks,
10/// allowing for maximum concurrency:
11///
12/// - Readers can query market depth without blocking order insertion
13/// - Order insertion doesn't need to wait for depth aggregation
14/// - The cache can be updated asynchronously after the core book is modified
15///
16/// ## Architecture
17///
18/// This follows the Observer Pattern:
19///
20/// - The `OrderBook` is the publisher (subject)
21/// - The `MarketDepthCache` is the subscriber (observer)
22/// - `OrderEvent` is the message passed between them
23///
24/// ## Thread Safety
25///
26/// The bid and ask depth maps are protected by separate `RwLock`s, allowing
27/// concurrent reads and serialized writes. This structure can be safely shared
28/// across threads using `Arc<MarketDepthCache>`.
29#[derive(Debug)]
30pub struct MarketDepthCache {
31 /// Aggregated bid depth: maps aggregated price levels to total quantities
32 aggregated_bid_depth: RwLock<AggregatedDepthMap>,
33 /// Aggregated ask depth: maps aggregated price levels to total quantities
34 aggregated_ask_depth: RwLock<AggregatedDepthMap>,
35}
36
37impl MarketDepthCache {
38 /// Creates a new empty market depth cache.
39 ///
40 /// ## Examples
41 ///
42 /// ```
43 /// use order_book::MarketDepthCache;
44 ///
45 /// let cache = MarketDepthCache::new();
46 /// ```
47 pub fn new() -> Self {
48 MarketDepthCache {
49 aggregated_bid_depth: RwLock::new(BTreeMap::new()),
50 aggregated_ask_depth: RwLock::new(BTreeMap::new()),
51 }
52 }
53
54 /// Processes an order event and updates the aggregated market depth.
55 ///
56 /// This method is called after an order is inserted into the order book.
57 /// It aggregates the order price to its level and updates the cached quantity.
58 ///
59 /// The operation is $O(\log{N})$ where $N$ is the number of aggregated price levels.
60 /// The lock is held only for the duration of the `BTreeMap` update.
61 ///
62 /// ## Arguments
63 ///
64 /// * `event`: The order event to process
65 ///
66 /// ## Examples
67 ///
68 /// ```
69 /// use order_book::{OrderBook, MarketDepthCache, Order, Side};
70 /// use rust_decimal::Decimal;
71 ///
72 /// let mut order_book = OrderBook::new();
73 /// let cache = MarketDepthCache::new();
74 ///
75 /// let order = Order::new(100.50, 100, Side::Bid);
76 ///
77 /// let event = order_book.insert_order(order);
78 /// cache.process_order_event(event);
79 /// ```
80 pub fn process_order_event(&self, event: OrderEvent) {
81 // Aggregate the price to its level using the core book's logic
82 let aggregated_price_level = OrderBook::aggregate_price_to_level(event.price);
83
84 // Select the appropriate depth map based on side
85 let mut depth_write_lock = match event.side {
86 Side::Bid => self.aggregated_bid_depth.write(),
87 Side::Ask => self.aggregated_ask_depth.write(),
88 };
89
90 // Update the aggregated quantity at this level
91 *depth_write_lock.entry(aggregated_price_level).or_insert(0) += event.quantity_delta;
92
93 // Lock is automatically released here
94 }
95
96 /// Retrieves a snapshot of the current aggregated market depth.
97 ///
98 /// This method clones the current depth maps to provide a consistent snapshot.
99 /// Multiple readers can call this method concurrently without blocking each other
100 /// or blocking order insertion.
101 ///
102 /// The operation is $O(N)$ where $N$ is the number of aggregated price levels,
103 /// due to the `BTreeMap` clone.
104 ///
105 /// ## Returns
106 ///
107 /// A tuple of `(bid_depth, ask_depth)` where each is an `AggregatedDepthMap`
108 /// mapping aggregated price levels to total quantities.
109 ///
110 /// ## Examples
111 ///
112 /// ```
113 /// use order_book::{OrderBook, MarketDepthCache, Order, Side};
114 /// use rust_decimal::Decimal;
115 ///
116 /// let mut order_book = OrderBook::new();
117 /// let cache = MarketDepthCache::new();
118 ///
119 /// let order = Order::new(100.50, 100, Side::Bid);
120 ///
121 /// let event = order_book.insert_order(order);
122 /// cache.process_order_event(event);
123 ///
124 /// let (bid_depth, ask_depth) = cache.get_aggregated_market_depth();
125 /// assert_eq!(bid_depth.get(&Decimal::new(100, 0)), Some(&100));
126 /// ```
127 pub fn get_aggregated_market_depth(&self) -> (AggregatedDepthMap, AggregatedDepthMap) {
128 // Acquire read locks and clone the maps
129 let bid_depth_snapshot = self.aggregated_bid_depth.read().clone();
130 let ask_depth_snapshot = self.aggregated_ask_depth.read().clone();
131
132 // Read locks are automatically released here
133 (bid_depth_snapshot, ask_depth_snapshot)
134 }
135
136 /// Returns the total quantity at a specific aggregated price level.
137 ///
138 /// ## Arguments
139 ///
140 /// * `aggregated_level`: The aggregated price level to query
141 /// * `side`: The side (bid or ask) to query
142 ///
143 /// ## Returns
144 ///
145 /// The total quantity at that level, or 0 if no orders exist
146 ///
147 /// ## Examples
148 ///
149 /// ```
150 /// use order_book::{OrderBook, MarketDepthCache, Order, Side};
151 /// use rust_decimal::Decimal;
152 ///
153 /// let mut order_book = OrderBook::new();
154 /// let cache = MarketDepthCache::new();
155 ///
156 /// let order = Order::new(100.50, 100, Side::Bid);
157 ///
158 /// let event = order_book.insert_order(order);
159 /// cache.process_order_event(event);
160 ///
161 /// let quantity = cache.get_quantity_at_level(Decimal::new(100, 0), Side::Bid);
162 /// assert_eq!(quantity, 100);
163 /// ```
164 pub fn get_quantity_at_level(
165 &self,
166 aggregated_level: rust_decimal::Decimal,
167 side: Side,
168 ) -> u64 {
169 let depth_read_lock = match side {
170 Side::Bid => self.aggregated_bid_depth.read(),
171 Side::Ask => self.aggregated_ask_depth.read(),
172 };
173
174 depth_read_lock.get(&aggregated_level).copied().unwrap_or(0)
175 }
176
177 /// Returns the number of aggregated price levels on the bid side.
178 pub fn bid_levels_count(&self) -> usize {
179 self.aggregated_bid_depth.read().len()
180 }
181
182 /// Returns the number of aggregated price levels on the ask side.
183 pub fn ask_levels_count(&self) -> usize {
184 self.aggregated_ask_depth.read().len()
185 }
186
187 /// Clears all cached market depth data.
188 ///
189 /// This is useful for testing or resetting the cache state.
190 pub fn clear(&self) {
191 self.aggregated_bid_depth.write().clear();
192 self.aggregated_ask_depth.write().clear();
193 }
194}
195
196impl Default for MarketDepthCache {
197 fn default() -> Self {
198 Self::new()
199 }
200}