rusty_model/data/
book_snapshot.rs

1use std::fmt::{Display, Formatter};
2
3use parking_lot::RwLock;
4use rust_decimal::Decimal;
5use smallvec::SmallVec;
6use std::sync::Arc;
7
8use crate::data::orderbook::PriceLevel;
9use crate::instruments::InstrumentId;
10
11/// Full order book snapshot containing multiple price levels with const generic capacity
12///
13/// Optimized for HFT with cache-line alignment and `SmallVec` to avoid heap allocations.
14/// This represents a complete snapshot of an order book at a specific point in time,
15/// typically used for initial state or periodic full updates.
16#[derive(Debug, Clone)]
17#[repr(align(64))]
18pub struct OrderBookSnapshot<const N: usize = 64> {
19    /// Instrument identifier
20    pub instrument_id: InstrumentId,
21
22    /// Top bid levels (sorted by price descending)
23    /// Uses `SmallVec` to avoid heap allocations for normal sized order books
24    pub bids: SmallVec<[PriceLevel; N]>,
25
26    /// Top ask levels (sorted by price ascending)
27    /// Uses `SmallVec` to avoid heap allocations for normal sized order books
28    pub asks: SmallVec<[PriceLevel; N]>,
29
30    /// Order book sequence ID
31    pub sequence_id: u64,
32
33    /// Unix timestamp (nanoseconds) - Order book published time (server side)
34    pub timestamp_event: u64,
35
36    /// Unix timestamp (nanoseconds) - Order book initialized time (client side)
37    pub timestamp_init: u64,
38}
39
40impl<const N: usize> OrderBookSnapshot<N> {
41    /// Create a new optimized order book depth
42    #[inline]
43    #[must_use]
44    pub const fn new(
45        instrument_id: InstrumentId,
46        bids: SmallVec<[PriceLevel; N]>,
47        asks: SmallVec<[PriceLevel; N]>,
48        sequence_id: u64,
49        timestamp_event: u64,
50        timestamp_init: u64,
51    ) -> Self {
52        Self {
53            instrument_id,
54            bids,
55            asks,
56            sequence_id,
57            timestamp_event,
58            timestamp_init,
59        }
60    }
61
62    /// Create a new optimized order book depth with empty bids and asks
63    #[inline]
64    #[must_use]
65    pub fn new_empty(instrument_id: InstrumentId, timestamp_init: u64, sequence_id: u64) -> Self {
66        Self {
67            instrument_id,
68            bids: SmallVec::with_capacity(20), // Pre-allocate for typical L2 depth
69            asks: SmallVec::with_capacity(20), // Pre-allocate for typical L2 depth
70            sequence_id,
71            timestamp_event: 0,
72            timestamp_init,
73        }
74    }
75
76    /// Create an order book from arrays of price/size tuples
77    #[inline]
78    #[must_use]
79    pub fn from_arrays(
80        instrument_id: InstrumentId,
81        bids: &[(Decimal, Decimal)],
82        asks: &[(Decimal, Decimal)],
83        sequence_id: u64,
84        timestamp_event: u64,
85        timestamp_init: u64,
86    ) -> Self {
87        let mut bids_vec = SmallVec::with_capacity(bids.len());
88        let mut asks_vec = SmallVec::with_capacity(asks.len());
89
90        for &(price, size) in bids {
91            bids_vec.push(PriceLevel::new(price, size));
92        }
93
94        for &(price, size) in asks {
95            asks_vec.push(PriceLevel::new(price, size));
96        }
97
98        // Sort bids in descending order by price
99        bids_vec.sort_unstable_by(|a: &PriceLevel, b: &PriceLevel| b.price.cmp(&a.price));
100
101        // Sort asks in ascending order by price
102        asks_vec.sort_unstable_by(|a: &PriceLevel, b: &PriceLevel| a.price.cmp(&b.price));
103
104        Self::new(
105            instrument_id,
106            bids_vec,
107            asks_vec,
108            sequence_id,
109            timestamp_event,
110            timestamp_init,
111        )
112    }
113
114    /// Get the best bid (highest bid price)
115    #[inline]
116    #[must_use]
117    pub fn best_bid(&self) -> Option<PriceLevel> {
118        self.bids.first().copied()
119    }
120
121    /// Get the best ask (lowest ask price)
122    #[inline]
123    #[must_use]
124    pub fn best_ask(&self) -> Option<PriceLevel> {
125        self.asks.first().copied()
126    }
127
128    /// Get the mid price ((best bid + best ask) / 2)
129    #[inline]
130    #[must_use]
131    pub fn mid_price(&self) -> Option<Decimal> {
132        match (self.best_bid(), self.best_ask()) {
133            (Some(bid), Some(ask)) => Some((bid.price + ask.price) / Decimal::TWO),
134            _ => None,
135        }
136    }
137
138    /// Get the spread (best ask - best bid)
139    #[inline]
140    #[must_use]
141    pub fn spread(&self) -> Option<Decimal> {
142        match (self.best_bid(), self.best_ask()) {
143            (Some(bid), Some(ask)) => Some(ask.price - bid.price),
144            _ => None,
145        }
146    }
147
148    /// Calculate the latency between event time and init time (in nanoseconds)
149    #[inline]
150    #[must_use]
151    pub const fn latency(&self) -> u64 {
152        if self.timestamp_event == 0 {
153            return 0;
154        }
155        self.timestamp_init.saturating_sub(self.timestamp_event)
156    }
157
158    /// Add a bid price level
159    #[inline]
160    pub fn add_bid(&mut self, price: Decimal, size: Decimal) {
161        // Add the new price level
162        self.bids.push(PriceLevel::new(price, size));
163
164        // Re-sort bids in descending order by price
165        self.bids.sort_unstable_by(|a, b| b.price.cmp(&a.price));
166    }
167
168    /// Add an ask price level
169    #[inline]
170    pub fn add_ask(&mut self, price: Decimal, size: Decimal) {
171        // Add the new price level
172        self.asks.push(PriceLevel::new(price, size));
173
174        // Re-sort asks in ascending order by price
175        self.asks.sort_unstable_by(|a, b| a.price.cmp(&b.price));
176    }
177}
178
179/// Thread-safe wrapper around `OrderBookSnapshot` with low-contention read/write access
180#[derive(Clone)]
181pub struct SharedOrderBookSnapshot<const N: usize = 64> {
182    /// Inner order book protected by a read-write lock
183    inner: Arc<RwLock<OrderBookSnapshot<N>>>,
184}
185
186impl<const N: usize> SharedOrderBookSnapshot<N> {
187    /// Create a new shared order book depth
188    #[inline]
189    #[must_use]
190    pub fn new(book: OrderBookSnapshot<N>) -> Self {
191        Self {
192            inner: Arc::new(RwLock::new(book)),
193        }
194    }
195
196    /// Update the order book with a new snapshot
197    #[inline]
198    pub fn update(&self, book: OrderBookSnapshot<N>) {
199        let mut write_guard = self.inner.write();
200        *write_guard = book;
201    }
202
203    /// Execute a read operation on the order book
204    #[inline]
205    pub fn read<F, R>(&self, f: F) -> R
206    where
207        F: FnOnce(&OrderBookSnapshot<N>) -> R,
208    {
209        let read_guard = self.inner.read();
210        f(&read_guard)
211    }
212
213    /// Execute a write operation on the order book
214    #[inline]
215    pub fn write<F, R>(&self, f: F) -> R
216    where
217        F: FnOnce(&mut OrderBookSnapshot<N>) -> R,
218    {
219        let mut write_guard = self.inner.write();
220        f(&mut write_guard)
221    }
222}
223
224impl<const N: usize> Display for OrderBookSnapshot<N> {
225    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
226        write!(
227            f,
228            "OrderBookSnapshot(instrument_id: {}, bids: {} levels, asks: {} levels, timestamp_event: {}, timestamp_init: {})",
229            self.instrument_id,
230            self.bids.len(),
231            self.asks.len(),
232            self.timestamp_event,
233            self.timestamp_init,
234        )
235    }
236}
237
238// Type aliases for backward compatibility
239/// An order book snapshot with a capacity of 64.
240pub type OrderBookSnapshot64 = OrderBookSnapshot<64>;
241/// An order book snapshot with a capacity of 32.
242pub type OrderBookSnapshot32 = OrderBookSnapshot<32>;
243/// An order book snapshot with a capacity of 128.
244pub type OrderBookSnapshot128 = OrderBookSnapshot<128>;
245
246/// A shared order book snapshot with a capacity of 64.
247pub type SharedOrderBookSnapshot64 = SharedOrderBookSnapshot<64>;
248/// A shared order book snapshot with a capacity of 32.
249pub type SharedOrderBookSnapshot32 = SharedOrderBookSnapshot<32>;
250/// A shared order book snapshot with a capacity of 128.
251pub type SharedOrderBookSnapshot128 = SharedOrderBookSnapshot<128>;
252
253// Default type aliases for seamless migration
254pub use OrderBookSnapshot64 as DefaultOrderBookSnapshot;
255pub use SharedOrderBookSnapshot64 as DefaultSharedOrderBookSnapshot;