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}