rusty_model/data/
orderbook.rs

1//! Order book data structures
2
3use parking_lot::RwLock;
4use rust_decimal::Decimal;
5use smallvec::SmallVec;
6use smartstring::alias::String;
7use std::sync::Arc;
8
9/// Price level in the order book
10#[repr(align(16))] // Align to 16 bytes for better cache performance
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct PriceLevel {
13    /// The price of the level.
14    pub price: Decimal,
15    /// The quantity at the level.
16    pub quantity: Decimal,
17}
18
19impl PriceLevel {
20    /// Create a new price level
21    #[must_use]
22    pub const fn new(price: Decimal, quantity: Decimal) -> Self {
23        Self { price, quantity }
24    }
25}
26
27/// Order book data structure with const generic capacity
28#[repr(align(64))] // Align to cache line for HFT performance
29#[derive(Debug, Clone)]
30pub struct OrderBook<const N: usize = 64> {
31    /// The trading symbol.
32    pub symbol: String,
33    /// Nanosecond precision from exchange.
34    pub exchange_timestamp_ns: u64,
35    /// Nanosecond precision local time.
36    pub system_timestamp_ns: u64,
37
38    /// Must be initiated as sorted for binary search optimizations!
39    pub bids: SmallVec<[PriceLevel; N]>, // Optimize for typical depth
40    /// Must be initiated as sorted for binary search optimizations!
41    pub asks: SmallVec<[PriceLevel; N]>, // Optimize for typical depth
42}
43
44impl<const N: usize> OrderBook<N> {
45    /// Create a new order book
46    #[must_use]
47    pub fn new(
48        symbol: impl AsRef<str>,
49        exchange_timestamp_ns: u64,
50        system_timestamp_ns: u64,
51        bids: SmallVec<[PriceLevel; N]>,
52        asks: SmallVec<[PriceLevel; N]>,
53    ) -> Self {
54        Self {
55            symbol: String::from(symbol.as_ref()),
56            exchange_timestamp_ns,
57            system_timestamp_ns,
58            bids,
59            asks,
60        }
61    }
62
63    /// Create a new order book from iterators (for compatibility and zero-copy)
64    pub fn from_iters<B, A>(
65        symbol: impl AsRef<str>,
66        exchange_timestamp_ns: u64,
67        system_timestamp_ns: u64,
68        bids: B,
69        asks: A,
70    ) -> Self
71    where
72        B: IntoIterator<Item = PriceLevel>,
73        A: IntoIterator<Item = PriceLevel>,
74    {
75        Self {
76            symbol: String::from(symbol.as_ref()),
77            exchange_timestamp_ns,
78            system_timestamp_ns,
79            bids: bids.into_iter().collect(),
80            asks: asks.into_iter().collect(),
81        }
82    }
83
84    /// Create an empty order book from instrument ID
85    #[must_use]
86    pub fn new_empty(instrument_id: crate::instruments::InstrumentId) -> Self {
87        Self {
88            symbol: instrument_id.symbol,
89            exchange_timestamp_ns: 0,
90            system_timestamp_ns: 0,
91            bids: SmallVec::with_capacity(20),
92            asks: SmallVec::with_capacity(20),
93        }
94    }
95
96    /// Get the bid levels
97    #[must_use]
98    pub fn bids(&self) -> &[PriceLevel] {
99        &self.bids
100    }
101
102    /// Get the ask levels
103    #[must_use]
104    pub fn asks(&self) -> &[PriceLevel] {
105        &self.asks
106    }
107
108    /// Get the best bid price
109    #[must_use]
110    pub fn best_bid(&self) -> Option<&PriceLevel> {
111        self.bids.first()
112    }
113
114    /// Get the best ask price
115    #[must_use]
116    pub fn best_ask(&self) -> Option<&PriceLevel> {
117        self.asks.first()
118    }
119
120    /// Get the spread
121    #[must_use]
122    pub fn spread(&self) -> Option<Decimal> {
123        match (self.best_bid(), self.best_ask()) {
124            (Some(bid), Some(ask)) => Some(ask.price - bid.price),
125            _ => None,
126        }
127    }
128
129    /// Add a bid level
130    pub fn add_bid(&mut self, price: Decimal, quantity: Decimal) {
131        let level = PriceLevel::new(price, quantity);
132        match self
133            .bids
134            .binary_search_by(|probe| probe.price.cmp(&price).reverse())
135        {
136            Ok(i) => self.bids[i].quantity += quantity,
137            Err(i) => self.bids.insert(i, level),
138        }
139    }
140
141    /// Add an ask level
142    pub fn add_ask(&mut self, price: Decimal, quantity: Decimal) {
143        let level = PriceLevel::new(price, quantity);
144        match self.asks.binary_search_by(|probe| probe.price.cmp(&price)) {
145            Ok(i) => self.asks[i].quantity += quantity,
146            Err(i) => self.asks.insert(i, level),
147        }
148    }
149
150    /// Remove a bid level
151    pub fn remove_bid(&mut self, price: Decimal) {
152        self.bids.retain(|level| level.price != price);
153    }
154
155    /// Remove an ask level
156    pub fn remove_ask(&mut self, price: Decimal) {
157        self.asks.retain(|level| level.price != price);
158    }
159
160    /// Apply a snapshot to the order book
161    /// This replaces the current state with the snapshot data
162    pub fn apply_snapshot(&mut self, snapshot: crate::data::book_snapshot::OrderBookSnapshot<N>) {
163        self.exchange_timestamp_ns = snapshot.timestamp_event;
164        self.system_timestamp_ns = crate::common::current_time_ns();
165
166        // Clear and update bids
167        self.bids.clear();
168        self.bids.extend(
169            snapshot
170                .bids
171                .into_iter()
172                .map(|level| PriceLevel::new(level.price, level.quantity)),
173        );
174
175        // Clear and update asks
176        self.asks.clear();
177        self.asks.extend(
178            snapshot
179                .asks
180                .into_iter()
181                .map(|level| PriceLevel::new(level.price, level.quantity)),
182        );
183    }
184}
185
186/// Thread-safe shared order book with read/write methods
187#[derive(Debug, Clone)]
188pub struct SharedOrderBook<const N: usize = 64>(Arc<RwLock<OrderBook<N>>>);
189
190impl<const N: usize> SharedOrderBook<N> {
191    /// Create a new shared order book
192    #[must_use]
193    pub fn new(order_book: OrderBook<N>) -> Self {
194        Self(Arc::new(RwLock::new(order_book)))
195    }
196
197    /// Read the order book
198    pub fn read<R, F>(&self, f: F) -> R
199    where
200        F: FnOnce(&OrderBook<N>) -> R,
201    {
202        let guard = self.0.read();
203        f(&guard)
204    }
205
206    /// Write to the order book
207    pub fn write<R, F>(&self, f: F) -> R
208    where
209        F: FnOnce(&mut OrderBook<N>) -> R,
210    {
211        let mut guard = self.0.write();
212        f(&mut guard)
213    }
214}
215
216// Type aliases for backward compatibility
217/// An order book with a capacity of 64.
218pub type OrderBook64 = OrderBook<64>;
219/// An order book with a capacity of 32.
220pub type OrderBook32 = OrderBook<32>;
221/// An order book with a capacity of 128.
222pub type OrderBook128 = OrderBook<128>;
223
224/// A shared order book with a capacity of 64.
225pub type SharedOrderBook64 = SharedOrderBook<64>;
226/// A shared order book with a capacity of 32.
227pub type SharedOrderBook32 = SharedOrderBook<32>;
228/// A shared order book with a capacity of 128.
229pub type SharedOrderBook128 = SharedOrderBook<128>;
230
231// Default type aliases for seamless migration
232pub use OrderBook64 as DefaultOrderBook;
233pub use SharedOrderBook64 as DefaultSharedOrderBook;