1use std::{collections::HashSet, fmt::Display};
19
20use indexmap::IndexMap;
21use nautilus_core::UnixNanos;
22use rust_decimal::Decimal;
23
24use super::{
25 aggregation::pre_process_order, analysis, display::pprint_book, level::BookLevel,
26 own::OwnOrderBook,
27};
28use crate::{
29 data::{BookOrder, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick},
30 enums::{BookAction, BookType, OrderSide, OrderSideSpecified, OrderStatus},
31 identifiers::InstrumentId,
32 orderbook::{InvalidBookOperation, ladder::BookLadder},
33 types::{Price, Quantity},
34};
35
36#[derive(Clone, Debug)]
44#[cfg_attr(
45 feature = "python",
46 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
47)]
48pub struct OrderBook {
49 pub instrument_id: InstrumentId,
51 pub book_type: BookType,
53 pub sequence: u64,
55 pub ts_last: UnixNanos,
57 pub update_count: u64,
59 pub(crate) bids: BookLadder,
60 pub(crate) asks: BookLadder,
61}
62
63impl PartialEq for OrderBook {
64 fn eq(&self, other: &Self) -> bool {
65 self.instrument_id == other.instrument_id && self.book_type == other.book_type
66 }
67}
68
69impl Eq for OrderBook {}
70
71impl Display for OrderBook {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 write!(
74 f,
75 "{}(instrument_id={}, book_type={}, update_count={})",
76 stringify!(OrderBook),
77 self.instrument_id,
78 self.book_type,
79 self.update_count,
80 )
81 }
82}
83
84impl OrderBook {
85 #[must_use]
87 pub fn new(instrument_id: InstrumentId, book_type: BookType) -> Self {
88 Self {
89 instrument_id,
90 book_type,
91 sequence: 0,
92 ts_last: UnixNanos::default(),
93 update_count: 0,
94 bids: BookLadder::new(OrderSideSpecified::Buy),
95 asks: BookLadder::new(OrderSideSpecified::Sell),
96 }
97 }
98
99 pub fn reset(&mut self) {
101 self.bids.clear();
102 self.asks.clear();
103 self.sequence = 0;
104 self.ts_last = UnixNanos::default();
105 self.update_count = 0;
106 }
107
108 pub fn add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
110 let order = pre_process_order(self.book_type, order, flags);
111 match order.side.as_specified() {
112 OrderSideSpecified::Buy => self.bids.add(order),
113 OrderSideSpecified::Sell => self.asks.add(order),
114 }
115
116 self.increment(sequence, ts_event);
117 }
118
119 pub fn update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
121 let order = pre_process_order(self.book_type, order, flags);
122 match order.side.as_specified() {
123 OrderSideSpecified::Buy => self.bids.update(order),
124 OrderSideSpecified::Sell => self.asks.update(order),
125 }
126
127 self.increment(sequence, ts_event);
128 }
129
130 pub fn delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
132 let order = pre_process_order(self.book_type, order, flags);
133 match order.side.as_specified() {
134 OrderSideSpecified::Buy => self.bids.delete(order, sequence, ts_event),
135 OrderSideSpecified::Sell => self.asks.delete(order, sequence, ts_event),
136 }
137
138 self.increment(sequence, ts_event);
139 }
140
141 pub fn clear(&mut self, sequence: u64, ts_event: UnixNanos) {
143 self.bids.clear();
144 self.asks.clear();
145 self.increment(sequence, ts_event);
146 }
147
148 pub fn clear_bids(&mut self, sequence: u64, ts_event: UnixNanos) {
150 self.bids.clear();
151 self.increment(sequence, ts_event);
152 }
153
154 pub fn clear_asks(&mut self, sequence: u64, ts_event: UnixNanos) {
156 self.asks.clear();
157 self.increment(sequence, ts_event);
158 }
159
160 pub fn apply_delta(&mut self, delta: &OrderBookDelta) {
162 let order = delta.order;
163 let flags = delta.flags;
164 let sequence = delta.sequence;
165 let ts_event = delta.ts_event;
166 match delta.action {
167 BookAction::Add => self.add(order, flags, sequence, ts_event),
168 BookAction::Update => self.update(order, flags, sequence, ts_event),
169 BookAction::Delete => self.delete(order, flags, sequence, ts_event),
170 BookAction::Clear => self.clear(sequence, ts_event),
171 }
172 }
173
174 pub fn apply_deltas(&mut self, deltas: &OrderBookDeltas) {
176 for delta in &deltas.deltas {
177 self.apply_delta(delta);
178 }
179 }
180
181 pub fn apply_depth(&mut self, depth: &OrderBookDepth10) {
183 self.bids.clear();
184 self.asks.clear();
185
186 for order in depth.bids {
187 self.add(order, depth.flags, depth.sequence, depth.ts_event);
188 }
189
190 for order in depth.asks {
191 self.add(order, depth.flags, depth.sequence, depth.ts_event);
192 }
193 }
194
195 pub fn bids(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
197 self.bids.levels.values().take(depth.unwrap_or(usize::MAX))
198 }
199
200 pub fn asks(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
202 self.asks.levels.values().take(depth.unwrap_or(usize::MAX))
203 }
204
205 pub fn bids_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
207 self.bids(depth)
208 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
209 .collect()
210 }
211
212 pub fn asks_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
214 self.asks(depth)
215 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
216 .collect()
217 }
218
219 pub fn group_bids(
221 &self,
222 group_size: Decimal,
223 depth: Option<usize>,
224 ) -> IndexMap<Decimal, Decimal> {
225 group_levels(self.bids(None), group_size, depth, true)
226 }
227
228 pub fn group_asks(
230 &self,
231 group_size: Decimal,
232 depth: Option<usize>,
233 ) -> IndexMap<Decimal, Decimal> {
234 group_levels(self.asks(None), group_size, depth, false)
235 }
236
237 pub fn bids_filtered_as_map(
243 &self,
244 depth: Option<usize>,
245 own_book: Option<&OwnOrderBook>,
246 status: Option<HashSet<OrderStatus>>,
247 accepted_buffer_ns: Option<u64>,
248 now: Option<u64>,
249 ) -> IndexMap<Decimal, Decimal> {
250 let mut public_map = self
251 .bids(depth)
252 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
253 .collect::<IndexMap<Decimal, Decimal>>();
254
255 if let Some(own_book) = own_book {
256 filter_quantities(
257 &mut public_map,
258 own_book.bid_quantity(status, accepted_buffer_ns, now),
259 );
260 }
261
262 public_map
263 }
264
265 pub fn asks_filtered_as_map(
271 &self,
272 depth: Option<usize>,
273 own_book: Option<&OwnOrderBook>,
274 status: Option<HashSet<OrderStatus>>,
275 accepted_buffer_ns: Option<u64>,
276 now: Option<u64>,
277 ) -> IndexMap<Decimal, Decimal> {
278 let mut public_map = self
279 .asks(depth)
280 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
281 .collect::<IndexMap<Decimal, Decimal>>();
282
283 if let Some(own_book) = own_book {
284 filter_quantities(
285 &mut public_map,
286 own_book.ask_quantity(status, accepted_buffer_ns, now),
287 );
288 }
289
290 public_map
291 }
292
293 pub fn group_bids_filtered(
299 &self,
300 group_size: Decimal,
301 depth: Option<usize>,
302 own_book: Option<&OwnOrderBook>,
303 status: Option<HashSet<OrderStatus>>,
304 accepted_buffer_ns: Option<u64>,
305 now: Option<u64>,
306 ) -> IndexMap<Decimal, Decimal> {
307 let mut public_map = group_levels(self.bids(None), group_size, depth, true);
308
309 if let Some(own_book) = own_book {
310 filter_quantities(
311 &mut public_map,
312 own_book.group_bids(group_size, depth, status, accepted_buffer_ns, now),
313 );
314 }
315
316 public_map
317 }
318
319 pub fn group_asks_filtered(
325 &self,
326 group_size: Decimal,
327 depth: Option<usize>,
328 own_book: Option<&OwnOrderBook>,
329 status: Option<HashSet<OrderStatus>>,
330 accepted_buffer_ns: Option<u64>,
331 now: Option<u64>,
332 ) -> IndexMap<Decimal, Decimal> {
333 let mut public_map = group_levels(self.asks(None), group_size, depth, false);
334
335 if let Some(own_book) = own_book {
336 filter_quantities(
337 &mut public_map,
338 own_book.group_asks(group_size, depth, status, accepted_buffer_ns, now),
339 );
340 }
341
342 public_map
343 }
344
345 #[must_use]
347 pub fn has_bid(&self) -> bool {
348 self.bids.top().is_some_and(|top| !top.orders.is_empty())
349 }
350
351 #[must_use]
353 pub fn has_ask(&self) -> bool {
354 self.asks.top().is_some_and(|top| !top.orders.is_empty())
355 }
356
357 #[must_use]
359 pub fn best_bid_price(&self) -> Option<Price> {
360 self.bids.top().map(|top| top.price.value)
361 }
362
363 #[must_use]
365 pub fn best_ask_price(&self) -> Option<Price> {
366 self.asks.top().map(|top| top.price.value)
367 }
368
369 #[must_use]
371 pub fn best_bid_size(&self) -> Option<Quantity> {
372 self.bids
373 .top()
374 .and_then(|top| top.first().map(|order| order.size))
375 }
376
377 #[must_use]
379 pub fn best_ask_size(&self) -> Option<Quantity> {
380 self.asks
381 .top()
382 .and_then(|top| top.first().map(|order| order.size))
383 }
384
385 #[must_use]
387 pub fn spread(&self) -> Option<f64> {
388 match (self.best_ask_price(), self.best_bid_price()) {
389 (Some(ask), Some(bid)) => Some(ask.as_f64() - bid.as_f64()),
390 _ => None,
391 }
392 }
393
394 #[must_use]
396 pub fn midpoint(&self) -> Option<f64> {
397 match (self.best_ask_price(), self.best_bid_price()) {
398 (Some(ask), Some(bid)) => Some((ask.as_f64() + bid.as_f64()) / 2.0),
399 _ => None,
400 }
401 }
402
403 #[must_use]
405 pub fn get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
406 let levels = match order_side.as_specified() {
407 OrderSideSpecified::Buy => &self.asks.levels,
408 OrderSideSpecified::Sell => &self.bids.levels,
409 };
410
411 analysis::get_avg_px_for_quantity(qty, levels)
412 }
413
414 #[must_use]
416 pub fn get_avg_px_qty_for_exposure(
417 &self,
418 target_exposure: Quantity,
419 order_side: OrderSide,
420 ) -> (f64, f64, f64) {
421 let levels = match order_side.as_specified() {
422 OrderSideSpecified::Buy => &self.asks.levels,
423 OrderSideSpecified::Sell => &self.bids.levels,
424 };
425
426 analysis::get_avg_px_qty_for_exposure(target_exposure, levels)
427 }
428
429 #[must_use]
431 pub fn get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
432 let levels = match order_side.as_specified() {
433 OrderSideSpecified::Buy => &self.asks.levels,
434 OrderSideSpecified::Sell => &self.bids.levels,
435 };
436
437 analysis::get_quantity_for_price(price, order_side, levels)
438 }
439
440 #[must_use]
442 pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
443 match order.side.as_specified() {
444 OrderSideSpecified::Buy => self.asks.simulate_fills(order),
445 OrderSideSpecified::Sell => self.bids.simulate_fills(order),
446 }
447 }
448
449 #[must_use]
451 pub fn pprint(&self, num_levels: usize) -> String {
452 pprint_book(&self.bids, &self.asks, num_levels)
453 }
454
455 fn increment(&mut self, sequence: u64, ts_event: UnixNanos) {
456 self.sequence = sequence;
457 self.ts_last = ts_event;
458 self.update_count += 1;
459 }
460
461 pub fn update_quote_tick(&mut self, quote: &QuoteTick) -> Result<(), InvalidBookOperation> {
463 if self.book_type != BookType::L1_MBP {
464 return Err(InvalidBookOperation::Update(self.book_type));
465 };
466
467 let bid = BookOrder::new(
468 OrderSide::Buy,
469 quote.bid_price,
470 quote.bid_size,
471 OrderSide::Buy as u64,
472 );
473
474 let ask = BookOrder::new(
475 OrderSide::Sell,
476 quote.ask_price,
477 quote.ask_size,
478 OrderSide::Sell as u64,
479 );
480
481 self.update_book_bid(bid, quote.ts_event);
482 self.update_book_ask(ask, quote.ts_event);
483
484 Ok(())
485 }
486
487 pub fn update_trade_tick(&mut self, trade: &TradeTick) -> Result<(), InvalidBookOperation> {
489 if self.book_type != BookType::L1_MBP {
490 return Err(InvalidBookOperation::Update(self.book_type));
491 };
492
493 let bid = BookOrder::new(
494 OrderSide::Buy,
495 trade.price,
496 trade.size,
497 OrderSide::Buy as u64,
498 );
499
500 let ask = BookOrder::new(
501 OrderSide::Sell,
502 trade.price,
503 trade.size,
504 OrderSide::Sell as u64,
505 );
506
507 self.update_book_bid(bid, trade.ts_event);
508 self.update_book_ask(ask, trade.ts_event);
509
510 Ok(())
511 }
512
513 fn update_book_bid(&mut self, order: BookOrder, ts_event: UnixNanos) {
514 if let Some(top_bids) = self.bids.top() {
515 if let Some(top_bid) = top_bids.first() {
516 self.bids.remove(top_bid.order_id, 0, ts_event);
517 }
518 }
519 self.bids.add(order);
520 }
521
522 fn update_book_ask(&mut self, order: BookOrder, ts_event: UnixNanos) {
523 if let Some(top_asks) = self.asks.top() {
524 if let Some(top_ask) = top_asks.first() {
525 self.asks.remove(top_ask.order_id, 0, ts_event);
526 }
527 }
528 self.asks.add(order);
529 }
530}
531
532fn filter_quantities(
533 public_map: &mut IndexMap<Decimal, Decimal>,
534 own_map: IndexMap<Decimal, Decimal>,
535) {
536 for (price, own_size) in own_map {
537 if let Some(public_size) = public_map.get_mut(&price) {
538 *public_size = (*public_size - own_size).max(Decimal::ZERO);
539
540 if *public_size == Decimal::ZERO {
541 public_map.shift_remove(&price);
542 }
543 }
544 }
545}
546
547fn group_levels<'a>(
548 levels_iter: impl Iterator<Item = &'a BookLevel>,
549 group_size: Decimal,
550 depth: Option<usize>,
551 is_bid: bool,
552) -> IndexMap<Decimal, Decimal> {
553 let mut levels = IndexMap::new();
554 let depth = depth.unwrap_or(usize::MAX);
555
556 for level in levels_iter {
557 let price = level.price.value.as_decimal();
558 let grouped_price = if is_bid {
559 (price / group_size).floor() * group_size
560 } else {
561 (price / group_size).ceil() * group_size
562 };
563 let size = level.size_decimal();
564
565 levels
566 .entry(grouped_price)
567 .and_modify(|total| *total += size)
568 .or_insert(size);
569
570 if levels.len() > depth {
571 levels.pop();
572 break;
573 }
574 }
575
576 levels
577}