1use std::fmt::Display;
19
20use ahash::AHashSet;
21use indexmap::IndexMap;
22use nautilus_core::UnixNanos;
23use rust_decimal::Decimal;
24
25use super::{
26 aggregation::pre_process_order, analysis, display::pprint_book, level::BookLevel,
27 own::OwnOrderBook,
28};
29use crate::{
30 data::{BookOrder, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick},
31 enums::{BookAction, BookType, OrderSide, OrderSideSpecified, OrderStatus},
32 identifiers::InstrumentId,
33 orderbook::{BookIntegrityError, InvalidBookOperation, ladder::BookLadder},
34 types::{
35 Price, Quantity,
36 price::{PRICE_ERROR, PRICE_UNDEF},
37 },
38};
39
40#[derive(Clone, Debug)]
48#[cfg_attr(
49 feature = "python",
50 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
51)]
52pub struct OrderBook {
53 pub instrument_id: InstrumentId,
55 pub book_type: BookType,
57 pub sequence: u64,
59 pub ts_last: UnixNanos,
61 pub update_count: u64,
63 pub(crate) bids: BookLadder,
64 pub(crate) asks: BookLadder,
65}
66
67impl PartialEq for OrderBook {
68 fn eq(&self, other: &Self) -> bool {
69 self.instrument_id == other.instrument_id && self.book_type == other.book_type
70 }
71}
72
73impl Eq for OrderBook {}
74
75impl Display for OrderBook {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(
78 f,
79 "{}(instrument_id={}, book_type={}, update_count={})",
80 stringify!(OrderBook),
81 self.instrument_id,
82 self.book_type,
83 self.update_count,
84 )
85 }
86}
87
88impl OrderBook {
89 #[must_use]
91 pub fn new(instrument_id: InstrumentId, book_type: BookType) -> Self {
92 Self {
93 instrument_id,
94 book_type,
95 sequence: 0,
96 ts_last: UnixNanos::default(),
97 update_count: 0,
98 bids: BookLadder::new(OrderSideSpecified::Buy, book_type),
99 asks: BookLadder::new(OrderSideSpecified::Sell, book_type),
100 }
101 }
102
103 pub fn reset(&mut self) {
105 self.bids.clear();
106 self.asks.clear();
107 self.sequence = 0;
108 self.ts_last = UnixNanos::default();
109 self.update_count = 0;
110 }
111
112 pub fn add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
114 let order = pre_process_order(self.book_type, order, flags);
115 match order.side.as_specified() {
116 OrderSideSpecified::Buy => self.bids.add(order, flags),
117 OrderSideSpecified::Sell => self.asks.add(order, flags),
118 }
119
120 self.increment(sequence, ts_event);
121 }
122
123 pub fn update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
125 let order = pre_process_order(self.book_type, order, flags);
126 match order.side.as_specified() {
127 OrderSideSpecified::Buy => self.bids.update(order, flags),
128 OrderSideSpecified::Sell => self.asks.update(order, flags),
129 }
130
131 self.increment(sequence, ts_event);
132 }
133
134 pub fn delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
136 let order = pre_process_order(self.book_type, order, flags);
137 match order.side.as_specified() {
138 OrderSideSpecified::Buy => self.bids.delete(order, sequence, ts_event),
139 OrderSideSpecified::Sell => self.asks.delete(order, sequence, ts_event),
140 }
141
142 self.increment(sequence, ts_event);
143 }
144
145 pub fn clear(&mut self, sequence: u64, ts_event: UnixNanos) {
147 self.bids.clear();
148 self.asks.clear();
149 self.increment(sequence, ts_event);
150 }
151
152 pub fn clear_bids(&mut self, sequence: u64, ts_event: UnixNanos) {
154 self.bids.clear();
155 self.increment(sequence, ts_event);
156 }
157
158 pub fn clear_asks(&mut self, sequence: u64, ts_event: UnixNanos) {
160 self.asks.clear();
161 self.increment(sequence, ts_event);
162 }
163
164 pub fn clear_stale_levels(&mut self, side: Option<OrderSide>) -> Option<Vec<BookLevel>> {
172 if self.book_type == BookType::L1_MBP {
173 return None;
175 }
176
177 let (Some(best_bid), Some(best_ask)) = (self.best_bid_price(), self.best_ask_price())
178 else {
179 return None;
180 };
181
182 if best_bid <= best_ask {
183 return None;
184 }
185
186 let mut removed_levels = Vec::new();
187 let mut clear_bids = false;
188 let mut clear_asks = false;
189
190 match side {
191 Some(OrderSide::Buy) => clear_bids = true,
192 Some(OrderSide::Sell) => clear_asks = true,
193 _ => {
194 clear_bids = true;
195 clear_asks = true;
196 }
197 }
198
199 let mut ask_prices_to_remove = Vec::new();
201 if clear_asks {
202 for bp in self.asks.levels.keys() {
203 if bp.value <= best_bid {
204 ask_prices_to_remove.push(*bp);
205 } else {
206 break;
207 }
208 }
209 }
210
211 let mut bid_prices_to_remove = Vec::new();
213 if clear_bids {
214 for bp in self.bids.levels.keys() {
215 if bp.value >= best_ask {
216 bid_prices_to_remove.push(*bp);
217 } else {
218 break;
219 }
220 }
221 }
222
223 if ask_prices_to_remove.is_empty() && bid_prices_to_remove.is_empty() {
224 return None;
225 }
226
227 let bid_count = bid_prices_to_remove.len();
228 let ask_count = ask_prices_to_remove.len();
229
230 for price in bid_prices_to_remove {
232 if let Some(level) = self.bids.remove_level(price) {
233 removed_levels.push(level);
234 }
235 }
236
237 for price in ask_prices_to_remove {
239 if let Some(level) = self.asks.remove_level(price) {
240 removed_levels.push(level);
241 }
242 }
243
244 self.increment(self.sequence, self.ts_last);
245
246 if removed_levels.is_empty() {
247 None
248 } else {
249 let total_orders: usize = removed_levels.iter().map(|level| level.orders.len()).sum();
250
251 log::warn!(
252 "Removed {} stale/crossed levels (instrument_id={}, bid_levels={}, ask_levels={}, total_orders={}), book was crossed with best_bid={} > best_ask={}",
253 removed_levels.len(),
254 self.instrument_id,
255 bid_count,
256 ask_count,
257 total_orders,
258 best_bid,
259 best_ask
260 );
261
262 Some(removed_levels)
263 }
264 }
265
266 pub fn apply_delta(&mut self, delta: &OrderBookDelta) -> Result<(), BookIntegrityError> {
275 if delta.instrument_id != self.instrument_id {
276 return Err(BookIntegrityError::InstrumentMismatch(
277 self.instrument_id,
278 delta.instrument_id,
279 ));
280 }
281 self.apply_delta_unchecked(delta)
282 }
283
284 pub fn apply_delta_unchecked(
297 &mut self,
298 delta: &OrderBookDelta,
299 ) -> Result<(), BookIntegrityError> {
300 let mut order = delta.order;
301
302 if order.side == OrderSide::NoOrderSide && order.order_id != 0 {
303 match self.resolve_no_side_order(order) {
304 Ok(resolved) => order = resolved,
305 Err(BookIntegrityError::OrderNotFoundForSideResolution(order_id)) => {
306 match delta.action {
307 BookAction::Add => return Err(BookIntegrityError::NoOrderSide),
308 BookAction::Update | BookAction::Delete => {
309 log::debug!(
311 "Skipping {:?} for unknown order_id={order_id}",
312 delta.action
313 );
314 return Ok(());
315 }
316 BookAction::Clear => {} }
318 }
319 Err(e) => return Err(e),
320 }
321 }
322
323 if order.side == OrderSide::NoOrderSide && delta.action != BookAction::Clear {
324 return Err(BookIntegrityError::NoOrderSide);
325 }
326
327 let flags = delta.flags;
328 let sequence = delta.sequence;
329 let ts_event = delta.ts_event;
330
331 match delta.action {
332 BookAction::Add => self.add(order, flags, sequence, ts_event),
333 BookAction::Update => self.update(order, flags, sequence, ts_event),
334 BookAction::Delete => self.delete(order, flags, sequence, ts_event),
335 BookAction::Clear => self.clear(sequence, ts_event),
336 }
337
338 Ok(())
339 }
340
341 pub fn apply_deltas(&mut self, deltas: &OrderBookDeltas) -> Result<(), BookIntegrityError> {
349 if deltas.instrument_id != self.instrument_id {
350 return Err(BookIntegrityError::InstrumentMismatch(
351 self.instrument_id,
352 deltas.instrument_id,
353 ));
354 }
355 self.apply_deltas_unchecked(deltas)
356 }
357
358 pub fn apply_deltas_unchecked(
366 &mut self,
367 deltas: &OrderBookDeltas,
368 ) -> Result<(), BookIntegrityError> {
369 for delta in &deltas.deltas {
370 self.apply_delta_unchecked(delta)?;
371 }
372 Ok(())
373 }
374
375 pub fn apply_depth(&mut self, depth: &OrderBookDepth10) -> Result<(), BookIntegrityError> {
381 if depth.instrument_id != self.instrument_id {
382 return Err(BookIntegrityError::InstrumentMismatch(
383 self.instrument_id,
384 depth.instrument_id,
385 ));
386 }
387 self.apply_depth_unchecked(depth)
388 }
389
390 pub fn apply_depth_unchecked(
398 &mut self,
399 depth: &OrderBookDepth10,
400 ) -> Result<(), BookIntegrityError> {
401 self.bids.clear();
402 self.asks.clear();
403
404 for order in depth.bids {
405 if order.side == OrderSide::NoOrderSide || !order.size.is_positive() {
407 continue;
408 }
409
410 debug_assert_eq!(
411 order.side,
412 OrderSide::Buy,
413 "Bid order must have Buy side, was {:?}",
414 order.side
415 );
416
417 let order = pre_process_order(self.book_type, order, depth.flags);
418 self.bids.add(order, depth.flags);
419 }
420
421 for order in depth.asks {
422 if order.side == OrderSide::NoOrderSide || !order.size.is_positive() {
424 continue;
425 }
426
427 debug_assert_eq!(
428 order.side,
429 OrderSide::Sell,
430 "Ask order must have Sell side, was {:?}",
431 order.side
432 );
433
434 let order = pre_process_order(self.book_type, order, depth.flags);
435 self.asks.add(order, depth.flags);
436 }
437
438 self.increment(depth.sequence, depth.ts_event);
439
440 Ok(())
441 }
442
443 fn resolve_no_side_order(&self, mut order: BookOrder) -> Result<BookOrder, BookIntegrityError> {
444 let resolved_side = self
445 .bids
446 .cache
447 .get(&order.order_id)
448 .or_else(|| self.asks.cache.get(&order.order_id))
449 .map(|book_price| match book_price.side {
450 OrderSideSpecified::Buy => OrderSide::Buy,
451 OrderSideSpecified::Sell => OrderSide::Sell,
452 })
453 .ok_or(BookIntegrityError::OrderNotFoundForSideResolution(
454 order.order_id,
455 ))?;
456
457 order.side = resolved_side;
458
459 Ok(order)
460 }
461
462 pub fn bids(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
464 self.bids.levels.values().take(depth.unwrap_or(usize::MAX))
465 }
466
467 pub fn asks(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
469 self.asks.levels.values().take(depth.unwrap_or(usize::MAX))
470 }
471
472 pub fn bids_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
474 self.bids(depth)
475 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
476 .collect()
477 }
478
479 pub fn asks_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
481 self.asks(depth)
482 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
483 .collect()
484 }
485
486 pub fn group_bids(
488 &self,
489 group_size: Decimal,
490 depth: Option<usize>,
491 ) -> IndexMap<Decimal, Decimal> {
492 group_levels(self.bids(None), group_size, depth, true)
493 }
494
495 pub fn group_asks(
497 &self,
498 group_size: Decimal,
499 depth: Option<usize>,
500 ) -> IndexMap<Decimal, Decimal> {
501 group_levels(self.asks(None), group_size, depth, false)
502 }
503
504 pub fn bids_filtered_as_map(
510 &self,
511 depth: Option<usize>,
512 own_book: Option<&OwnOrderBook>,
513 status: Option<AHashSet<OrderStatus>>,
514 accepted_buffer_ns: Option<u64>,
515 now: Option<u64>,
516 ) -> IndexMap<Decimal, Decimal> {
517 let mut public_map = self
518 .bids(depth)
519 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
520 .collect::<IndexMap<Decimal, Decimal>>();
521
522 if let Some(own_book) = own_book {
523 filter_quantities(
524 &mut public_map,
525 own_book.bid_quantity(status, None, None, accepted_buffer_ns, now),
526 );
527 }
528
529 public_map
530 }
531
532 pub fn asks_filtered_as_map(
538 &self,
539 depth: Option<usize>,
540 own_book: Option<&OwnOrderBook>,
541 status: Option<AHashSet<OrderStatus>>,
542 accepted_buffer_ns: Option<u64>,
543 now: Option<u64>,
544 ) -> IndexMap<Decimal, Decimal> {
545 let mut public_map = self
546 .asks(depth)
547 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
548 .collect::<IndexMap<Decimal, Decimal>>();
549
550 if let Some(own_book) = own_book {
551 filter_quantities(
552 &mut public_map,
553 own_book.ask_quantity(status, None, None, accepted_buffer_ns, now),
554 );
555 }
556
557 public_map
558 }
559
560 pub fn group_bids_filtered(
566 &self,
567 group_size: Decimal,
568 depth: Option<usize>,
569 own_book: Option<&OwnOrderBook>,
570 status: Option<AHashSet<OrderStatus>>,
571 accepted_buffer_ns: Option<u64>,
572 now: Option<u64>,
573 ) -> IndexMap<Decimal, Decimal> {
574 let mut public_map = group_levels(self.bids(None), group_size, depth, true);
575
576 if let Some(own_book) = own_book {
577 filter_quantities(
578 &mut public_map,
579 own_book.bid_quantity(status, depth, Some(group_size), accepted_buffer_ns, now),
580 );
581 }
582
583 public_map
584 }
585
586 pub fn group_asks_filtered(
592 &self,
593 group_size: Decimal,
594 depth: Option<usize>,
595 own_book: Option<&OwnOrderBook>,
596 status: Option<AHashSet<OrderStatus>>,
597 accepted_buffer_ns: Option<u64>,
598 now: Option<u64>,
599 ) -> IndexMap<Decimal, Decimal> {
600 let mut public_map = group_levels(self.asks(None), group_size, depth, false);
601
602 if let Some(own_book) = own_book {
603 filter_quantities(
604 &mut public_map,
605 own_book.ask_quantity(status, depth, Some(group_size), accepted_buffer_ns, now),
606 );
607 }
608
609 public_map
610 }
611
612 #[must_use]
614 pub fn has_bid(&self) -> bool {
615 self.bids.top().is_some_and(|top| !top.orders.is_empty())
616 }
617
618 #[must_use]
620 pub fn has_ask(&self) -> bool {
621 self.asks.top().is_some_and(|top| !top.orders.is_empty())
622 }
623
624 #[must_use]
626 pub fn best_bid_price(&self) -> Option<Price> {
627 self.bids.top().map(|top| top.price.value)
628 }
629
630 #[must_use]
632 pub fn best_ask_price(&self) -> Option<Price> {
633 self.asks.top().map(|top| top.price.value)
634 }
635
636 #[must_use]
638 pub fn best_bid_size(&self) -> Option<Quantity> {
639 self.bids
640 .top()
641 .and_then(|top| top.first().map(|order| order.size))
642 }
643
644 #[must_use]
646 pub fn best_ask_size(&self) -> Option<Quantity> {
647 self.asks
648 .top()
649 .and_then(|top| top.first().map(|order| order.size))
650 }
651
652 #[must_use]
654 pub fn spread(&self) -> Option<f64> {
655 match (self.best_ask_price(), self.best_bid_price()) {
656 (Some(ask), Some(bid)) => Some(ask.as_f64() - bid.as_f64()),
657 _ => None,
658 }
659 }
660
661 #[must_use]
663 pub fn midpoint(&self) -> Option<f64> {
664 match (self.best_ask_price(), self.best_bid_price()) {
665 (Some(ask), Some(bid)) => Some((ask.as_f64() + bid.as_f64()) / 2.0),
666 _ => None,
667 }
668 }
669
670 #[must_use]
672 pub fn get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
673 let levels = match order_side.as_specified() {
674 OrderSideSpecified::Buy => &self.asks.levels,
675 OrderSideSpecified::Sell => &self.bids.levels,
676 };
677
678 analysis::get_avg_px_for_quantity(qty, levels)
679 }
680
681 #[must_use]
683 pub fn get_avg_px_qty_for_exposure(
684 &self,
685 target_exposure: Quantity,
686 order_side: OrderSide,
687 ) -> (f64, f64, f64) {
688 let levels = match order_side.as_specified() {
689 OrderSideSpecified::Buy => &self.asks.levels,
690 OrderSideSpecified::Sell => &self.bids.levels,
691 };
692
693 analysis::get_avg_px_qty_for_exposure(target_exposure, levels)
694 }
695
696 #[must_use]
698 pub fn get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
699 let levels = match order_side.as_specified() {
700 OrderSideSpecified::Buy => &self.asks.levels,
701 OrderSideSpecified::Sell => &self.bids.levels,
702 };
703
704 analysis::get_quantity_for_price(price, order_side, levels)
705 }
706
707 #[must_use]
709 pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
710 match order.side.as_specified() {
711 OrderSideSpecified::Buy => self.asks.simulate_fills(order),
712 OrderSideSpecified::Sell => self.bids.simulate_fills(order),
713 }
714 }
715
716 #[must_use]
718 pub fn pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
719 pprint_book(self, num_levels, group_size)
720 }
721
722 fn increment(&mut self, sequence: u64, ts_event: UnixNanos) {
723 if sequence < self.sequence {
725 let msg = format!(
726 "Sequence number should not go backwards: old={}, new={}",
727 self.sequence, sequence
728 );
729 debug_assert!(sequence >= self.sequence, "{}", msg);
730 log::warn!("{msg}");
731 }
732
733 if ts_event < self.ts_last {
734 let msg = format!(
735 "Timestamp should not go backwards: old={}, new={}",
736 self.ts_last, ts_event
737 );
738 debug_assert!(ts_event >= self.ts_last, "{}", msg);
739 log::warn!("{msg}");
740 }
741
742 if self.update_count == u64::MAX {
743 debug_assert!(
745 self.update_count < u64::MAX,
746 "Update count at u64::MAX limit (about to overflow): {}",
747 self.update_count
748 );
749
750 log::warn!(
752 "Update count at u64::MAX: {} (instrument_id={})",
753 self.update_count,
754 self.instrument_id
755 );
756 }
757
758 self.sequence = sequence;
759 self.ts_last = ts_event;
760 self.update_count = self.update_count.saturating_add(1);
761 }
762
763 pub fn update_quote_tick(&mut self, quote: &QuoteTick) -> Result<(), InvalidBookOperation> {
769 if self.book_type != BookType::L1_MBP {
770 return Err(InvalidBookOperation::Update(self.book_type));
771 }
772
773 if cfg!(debug_assertions) && quote.bid_price > quote.ask_price {
775 log::warn!(
776 "Quote has crossed prices: bid={}, ask={} for {}",
777 quote.bid_price,
778 quote.ask_price,
779 self.instrument_id
780 );
781 }
782
783 let bid = BookOrder::new(
784 OrderSide::Buy,
785 quote.bid_price,
786 quote.bid_size,
787 OrderSide::Buy as u64,
788 );
789
790 let ask = BookOrder::new(
791 OrderSide::Sell,
792 quote.ask_price,
793 quote.ask_size,
794 OrderSide::Sell as u64,
795 );
796
797 self.update_book_bid(bid, quote.ts_event);
798 self.update_book_ask(ask, quote.ts_event);
799
800 self.increment(self.sequence.saturating_add(1), quote.ts_event);
801
802 Ok(())
803 }
804
805 pub fn update_trade_tick(&mut self, trade: &TradeTick) -> Result<(), InvalidBookOperation> {
811 if self.book_type != BookType::L1_MBP {
812 return Err(InvalidBookOperation::Update(self.book_type));
813 }
814
815 debug_assert!(
817 trade.price.raw != PRICE_UNDEF && trade.price.raw != PRICE_ERROR,
818 "Trade has invalid/uninitialized price: {}",
819 trade.price
820 );
821
822 debug_assert!(
824 trade.size.is_positive(),
825 "Trade has non-positive size: {}",
826 trade.size
827 );
828
829 let bid = BookOrder::new(
830 OrderSide::Buy,
831 trade.price,
832 trade.size,
833 OrderSide::Buy as u64,
834 );
835
836 let ask = BookOrder::new(
837 OrderSide::Sell,
838 trade.price,
839 trade.size,
840 OrderSide::Sell as u64,
841 );
842
843 self.update_book_bid(bid, trade.ts_event);
844 self.update_book_ask(ask, trade.ts_event);
845
846 self.increment(self.sequence.saturating_add(1), trade.ts_event);
847
848 Ok(())
849 }
850
851 fn update_book_bid(&mut self, order: BookOrder, ts_event: UnixNanos) {
852 if let Some(top_bids) = self.bids.top()
853 && let Some(top_bid) = top_bids.first()
854 {
855 self.bids.remove_order(top_bid.order_id, 0, ts_event);
856 }
857 self.bids.add(order, 0); }
859
860 fn update_book_ask(&mut self, order: BookOrder, ts_event: UnixNanos) {
861 if let Some(top_asks) = self.asks.top()
862 && let Some(top_ask) = top_asks.first()
863 {
864 self.asks.remove_order(top_ask.order_id, 0, ts_event);
865 }
866 self.asks.add(order, 0); }
868}
869
870fn filter_quantities(
871 public_map: &mut IndexMap<Decimal, Decimal>,
872 own_map: IndexMap<Decimal, Decimal>,
873) {
874 for (price, own_size) in own_map {
875 if let Some(public_size) = public_map.get_mut(&price) {
876 *public_size = (*public_size - own_size).max(Decimal::ZERO);
877
878 if *public_size == Decimal::ZERO {
879 public_map.shift_remove(&price);
880 }
881 }
882 }
883}
884
885fn group_levels<'a>(
886 levels_iter: impl Iterator<Item = &'a BookLevel>,
887 group_size: Decimal,
888 depth: Option<usize>,
889 is_bid: bool,
890) -> IndexMap<Decimal, Decimal> {
891 if group_size <= Decimal::ZERO {
892 log::error!("Invalid group_size: {group_size}, must be positive; returning empty map");
893 return IndexMap::new();
894 }
895
896 let mut levels = IndexMap::new();
897 let depth = depth.unwrap_or(usize::MAX);
898
899 for level in levels_iter {
900 let price = level.price.value.as_decimal();
901 let grouped_price = if is_bid {
902 (price / group_size).floor() * group_size
903 } else {
904 (price / group_size).ceil() * group_size
905 };
906 let size = level.size_decimal();
907
908 levels
909 .entry(grouped_price)
910 .and_modify(|total| *total += size)
911 .or_insert(size);
912
913 if levels.len() > depth {
914 levels.pop();
915 break;
916 }
917 }
918
919 levels
920}