1use parking_lot::RwLock;
7use rust_decimal::Decimal;
8use rust_decimal::prelude::ToPrimitive;
9use rusty_common::SmartString;
10use smallvec::SmallVec;
11use std::collections::BTreeMap;
12use std::sync::Arc;
13
14#[repr(align(64))]
18#[derive(Debug, Clone)]
19pub struct Level {
20 pub price: Decimal,
22 pub quantity: Decimal,
24 pub order_count: u32,
26 _padding: [u8; 28],
28}
29
30impl Default for Level {
31 fn default() -> Self {
32 Self {
33 price: Decimal::ZERO,
34 quantity: Decimal::ZERO,
35 order_count: 0,
36 _padding: [0; 28],
37 }
38 }
39}
40
41impl Level {
42 #[must_use]
44 pub const fn new(price: Decimal, quantity: Decimal, order_count: u32) -> Self {
45 Self {
46 price,
47 quantity,
48 order_count,
49 _padding: [0; 28],
50 }
51 }
52}
53
54pub struct OrderBook {
56 pub symbol: SmartString,
58 pub tick_size: Decimal,
60 pub lot_size: Decimal,
62 bids: Arc<RwLock<BTreeMap<i64, Level>>>,
64 asks: Arc<RwLock<BTreeMap<i64, Level>>>,
66 last_price: Arc<RwLock<Option<Decimal>>>,
68 last_update_ns: Arc<RwLock<u64>>,
70}
71
72impl OrderBook {
73 #[must_use]
75 pub fn new(symbol: SmartString, tick_size: Decimal, lot_size: Decimal) -> Self {
76 Self {
77 symbol,
78 tick_size,
79 lot_size,
80 bids: Arc::new(RwLock::new(BTreeMap::new())),
81 asks: Arc::new(RwLock::new(BTreeMap::new())),
82 last_price: Arc::new(RwLock::new(None)),
83 last_update_ns: Arc::new(RwLock::new(0)),
84 }
85 }
86
87 #[inline]
89 pub fn price_to_tick(&self, price: Decimal) -> i64 {
90 (price / self.tick_size).round().to_i64().unwrap_or(0)
91 }
92
93 #[inline]
95 pub fn tick_to_price(&self, tick: i64) -> Decimal {
96 Decimal::from(tick) * self.tick_size
97 }
98
99 #[inline]
101 pub fn round_quantity(&self, quantity: Decimal) -> Decimal {
102 (quantity / self.lot_size).round() * self.lot_size
103 }
104
105 pub fn update_bid(
107 &self,
108 price: Decimal,
109 quantity: Decimal,
110 order_count: u32,
111 timestamp_ns: u64,
112 ) {
113 let tick = self.price_to_tick(price);
114 let rounded_qty = self.round_quantity(quantity);
115
116 let mut bids = self.bids.write();
117 if rounded_qty > Decimal::ZERO && order_count > 0 {
118 bids.insert(tick, Level::new(price, rounded_qty, order_count));
119 } else {
120 bids.remove(&tick);
121 }
122
123 *self.last_update_ns.write() = timestamp_ns;
124 }
125
126 pub fn update_ask(
128 &self,
129 price: Decimal,
130 quantity: Decimal,
131 order_count: u32,
132 timestamp_ns: u64,
133 ) {
134 let tick = self.price_to_tick(price);
135 let rounded_qty = self.round_quantity(quantity);
136
137 let mut asks = self.asks.write();
138 if rounded_qty > Decimal::ZERO && order_count > 0 {
139 asks.insert(tick, Level::new(price, rounded_qty, order_count));
140 } else {
141 asks.remove(&tick);
142 }
143
144 *self.last_update_ns.write() = timestamp_ns;
145 }
146
147 pub fn best_bid(&self) -> Option<Level> {
149 self.bids.read().values().next_back().cloned()
150 }
151
152 pub fn best_ask(&self) -> Option<Level> {
154 self.asks.read().values().next().cloned()
155 }
156
157 pub fn bid_qty_at_tick(&self, tick: i64) -> Decimal {
159 self.bids
160 .read()
161 .get(&tick)
162 .map(|l| l.quantity)
163 .unwrap_or(Decimal::ZERO)
164 }
165
166 pub fn ask_qty_at_tick(&self, tick: i64) -> Decimal {
168 self.asks
169 .read()
170 .get(&tick)
171 .map(|l| l.quantity)
172 .unwrap_or(Decimal::ZERO)
173 }
174
175 pub fn top_bids(&self, n: usize) -> SmallVec<[Level; 10]> {
177 self.bids.read().values().rev().take(n).cloned().collect()
178 }
179
180 pub fn top_asks(&self, n: usize) -> SmallVec<[Level; 10]> {
182 self.asks.read().values().take(n).cloned().collect()
183 }
184
185 pub fn mid_price(&self) -> Option<Decimal> {
187 let bid = self.best_bid()?;
188 let ask = self.best_ask()?;
189 Some((bid.price + ask.price) / Decimal::from(2))
190 }
191
192 pub fn spread(&self) -> Option<Decimal> {
194 let bid = self.best_bid()?;
195 let ask = self.best_ask()?;
196 Some(ask.price - bid.price)
197 }
198
199 pub fn update_last_price(&self, price: Decimal, timestamp_ns: u64) {
201 *self.last_price.write() = Some(price);
202 *self.last_update_ns.write() = timestamp_ns;
203 }
204
205 pub fn last_price(&self) -> Option<Decimal> {
207 *self.last_price.read()
208 }
209
210 pub fn clear(&self) {
212 self.bids.write().clear();
213 self.asks.write().clear();
214 }
215
216 pub fn bid_liquidity_within(&self, ticks: i64) -> Decimal {
218 let bids = self.bids.read();
219 if let Some(best_level) = bids.values().next_back() {
220 let best_tick = self.price_to_tick(best_level.price);
221 let min_tick = best_tick - ticks;
222
223 bids.range(min_tick..=best_tick)
224 .map(|(_, level)| level.quantity)
225 .sum()
226 } else {
227 Decimal::ZERO
228 }
229 }
230
231 pub fn ask_liquidity_within(&self, ticks: i64) -> Decimal {
233 let asks = self.asks.read();
234 if let Some(best_level) = asks.values().next() {
235 let best_tick = self.price_to_tick(best_level.price);
236 let max_tick = best_tick + ticks;
237
238 asks.range(best_tick..=max_tick)
239 .map(|(_, level)| level.quantity)
240 .sum()
241 } else {
242 Decimal::ZERO
243 }
244 }
245
246 pub fn impact_bid(&self, quantity: Decimal) -> Option<Decimal> {
248 let asks = self.asks.read();
249 let mut remaining = quantity;
250 let mut total_cost = Decimal::ZERO;
251
252 for level in asks.values() {
253 let fill_qty = remaining.min(level.quantity);
254 total_cost += level.price * fill_qty;
255 remaining -= fill_qty;
256
257 if remaining <= Decimal::ZERO {
258 return Some(total_cost / quantity);
259 }
260 }
261
262 None }
264
265 pub fn impact_ask(&self, quantity: Decimal) -> Option<Decimal> {
267 let bids = self.bids.read();
268 let mut remaining = quantity;
269 let mut total_cost = Decimal::ZERO;
270
271 for level in bids.values().rev() {
272 let fill_qty = remaining.min(level.quantity);
273 total_cost += level.price * fill_qty;
274 remaining -= fill_qty;
275
276 if remaining <= Decimal::ZERO {
277 return Some(total_cost / quantity);
278 }
279 }
280
281 None }
283
284 pub fn imbalance(&self) -> f64 {
286 let bid_qty = self.bid_liquidity_within(5);
287 let ask_qty = self.ask_liquidity_within(5);
288 let total = bid_qty + ask_qty;
289
290 if total > Decimal::ZERO {
291 ((bid_qty - ask_qty) / total).to_f64().unwrap_or(f64::NAN)
292 } else {
293 0.0
294 }
295 }
296
297 pub fn total_volume(&self) -> Decimal {
299 let bids = self.bids.read();
300 let asks = self.asks.read();
301
302 let bid_volume: Decimal = bids.values().map(|level| level.quantity).sum();
303 let ask_volume: Decimal = asks.values().map(|level| level.quantity).sum();
304
305 bid_volume + ask_volume
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_l2_orderbook() {
315 use std::str::FromStr;
316 let book = OrderBook::new(
317 "BTC-USD".into(),
318 Decimal::from_str("0.01").unwrap(),
319 Decimal::from_str("0.001").unwrap(),
320 );
321
322 book.update_bid(
324 Decimal::from(50000),
325 Decimal::from_str("1.5").unwrap(),
326 3,
327 100,
328 );
329 book.update_bid(
330 Decimal::from(49999),
331 Decimal::from_str("2.0").unwrap(),
332 5,
333 101,
334 );
335 book.update_ask(
336 Decimal::from(50001),
337 Decimal::from_str("1.2").unwrap(),
338 2,
339 102,
340 );
341 book.update_ask(
342 Decimal::from(50002),
343 Decimal::from_str("2.5").unwrap(),
344 4,
345 103,
346 );
347
348 assert_eq!(book.best_bid().unwrap().price, Decimal::from(50000));
350 assert_eq!(book.best_ask().unwrap().price, Decimal::from(50001));
351
352 assert_eq!(book.spread().unwrap(), Decimal::from(1));
354
355 assert_eq!(
357 book.mid_price().unwrap(),
358 Decimal::from_str("50000.5").unwrap()
359 );
360
361 let imbalance = book.imbalance();
363 assert!(imbalance > 0.0); }
365}