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::{BookIntegrityError, InvalidBookOperation, ladder::BookLadder},
33 types::{
34 Price, Quantity,
35 price::{PRICE_ERROR, PRICE_UNDEF},
36 },
37};
38
39#[derive(Clone, Debug)]
47#[cfg_attr(
48 feature = "python",
49 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
50)]
51pub struct OrderBook {
52 pub instrument_id: InstrumentId,
54 pub book_type: BookType,
56 pub sequence: u64,
58 pub ts_last: UnixNanos,
60 pub update_count: u64,
62 pub(crate) bids: BookLadder,
63 pub(crate) asks: BookLadder,
64}
65
66impl PartialEq for OrderBook {
67 fn eq(&self, other: &Self) -> bool {
68 self.instrument_id == other.instrument_id && self.book_type == other.book_type
69 }
70}
71
72impl Eq for OrderBook {}
73
74impl Display for OrderBook {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(
77 f,
78 "{}(instrument_id={}, book_type={}, update_count={})",
79 stringify!(OrderBook),
80 self.instrument_id,
81 self.book_type,
82 self.update_count,
83 )
84 }
85}
86
87impl OrderBook {
88 #[must_use]
90 pub fn new(instrument_id: InstrumentId, book_type: BookType) -> Self {
91 Self {
92 instrument_id,
93 book_type,
94 sequence: 0,
95 ts_last: UnixNanos::default(),
96 update_count: 0,
97 bids: BookLadder::new(OrderSideSpecified::Buy, book_type),
98 asks: BookLadder::new(OrderSideSpecified::Sell, book_type),
99 }
100 }
101
102 pub fn reset(&mut self) {
104 self.bids.clear();
105 self.asks.clear();
106 self.sequence = 0;
107 self.ts_last = UnixNanos::default();
108 self.update_count = 0;
109 }
110
111 pub fn add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
113 let order = pre_process_order(self.book_type, order, flags);
114 match order.side.as_specified() {
115 OrderSideSpecified::Buy => self.bids.add(order),
116 OrderSideSpecified::Sell => self.asks.add(order),
117 }
118
119 self.increment(sequence, ts_event);
120 }
121
122 pub fn update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
124 let order = pre_process_order(self.book_type, order, flags);
125 match order.side.as_specified() {
126 OrderSideSpecified::Buy => self.bids.update(order),
127 OrderSideSpecified::Sell => self.asks.update(order),
128 }
129
130 self.increment(sequence, ts_event);
131 }
132
133 pub fn delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
135 let order = pre_process_order(self.book_type, order, flags);
136 match order.side.as_specified() {
137 OrderSideSpecified::Buy => self.bids.delete(order, sequence, ts_event),
138 OrderSideSpecified::Sell => self.asks.delete(order, sequence, ts_event),
139 }
140
141 self.increment(sequence, ts_event);
142 }
143
144 pub fn clear(&mut self, sequence: u64, ts_event: UnixNanos) {
146 self.bids.clear();
147 self.asks.clear();
148 self.increment(sequence, ts_event);
149 }
150
151 pub fn clear_bids(&mut self, sequence: u64, ts_event: UnixNanos) {
153 self.bids.clear();
154 self.increment(sequence, ts_event);
155 }
156
157 pub fn clear_asks(&mut self, sequence: u64, ts_event: UnixNanos) {
159 self.asks.clear();
160 self.increment(sequence, ts_event);
161 }
162
163 pub fn clear_stale_levels(&mut self, side: Option<OrderSide>) -> Option<Vec<BookLevel>> {
171 if self.book_type == BookType::L1_MBP {
172 return None;
174 }
175
176 let (Some(best_bid), Some(best_ask)) = (self.best_bid_price(), self.best_ask_price())
177 else {
178 return None;
179 };
180
181 if best_bid <= best_ask {
182 return None;
183 }
184
185 let mut removed_levels = Vec::new();
186 let mut clear_bids = false;
187 let mut clear_asks = false;
188
189 match side {
190 Some(OrderSide::Buy) => clear_bids = true,
191 Some(OrderSide::Sell) => clear_asks = true,
192 _ => {
193 clear_bids = true;
194 clear_asks = true;
195 }
196 }
197
198 let mut ask_prices_to_remove = Vec::new();
200 if clear_asks {
201 for (bp, _level) in self.asks.levels.iter() {
202 if bp.value <= best_bid {
203 ask_prices_to_remove.push(*bp);
204 } else {
205 break;
206 }
207 }
208 }
209
210 let mut bid_prices_to_remove = Vec::new();
212 if clear_bids {
213 for (bp, _level) in self.bids.levels.iter() {
214 if bp.value >= best_ask {
215 bid_prices_to_remove.push(*bp);
216 } else {
217 break;
218 }
219 }
220 }
221
222 if ask_prices_to_remove.is_empty() && bid_prices_to_remove.is_empty() {
223 return None;
224 }
225
226 let bid_count = bid_prices_to_remove.len();
227 let ask_count = ask_prices_to_remove.len();
228
229 for price in bid_prices_to_remove {
231 if let Some(level) = self.bids.remove_level(price) {
232 removed_levels.push(level);
233 }
234 }
235
236 for price in ask_prices_to_remove {
238 if let Some(level) = self.asks.remove_level(price) {
239 removed_levels.push(level);
240 }
241 }
242
243 self.increment(self.sequence, self.ts_last);
244
245 if removed_levels.is_empty() {
246 None
247 } else {
248 let total_orders: usize = removed_levels.iter().map(|level| level.orders.len()).sum();
249
250 log::warn!(
251 "Removed {} stale/crossed levels (instrument_id={}, bid_levels={}, ask_levels={}, total_orders={}), book was crossed with best_bid={} > best_ask={}",
252 removed_levels.len(),
253 self.instrument_id,
254 bid_count,
255 ask_count,
256 total_orders,
257 best_bid,
258 best_ask
259 );
260
261 Some(removed_levels)
262 }
263 }
264
265 pub fn apply_delta(&mut self, delta: &OrderBookDelta) -> Result<(), BookIntegrityError> {
273 let mut order = delta.order;
274
275 if order.side == OrderSide::NoOrderSide && order.order_id != 0 {
276 match self.resolve_no_side_order(order) {
277 Ok(resolved) => order = resolved,
278 Err(BookIntegrityError::OrderNotFoundForSideResolution(order_id)) => {
279 match delta.action {
280 BookAction::Add => return Err(BookIntegrityError::NoOrderSide),
281 BookAction::Update | BookAction::Delete => {
282 log::debug!(
284 "Skipping {:?} for unknown order_id={order_id}",
285 delta.action
286 );
287 return Ok(());
288 }
289 BookAction::Clear => {} }
291 }
292 Err(e) => return Err(e),
293 }
294 }
295
296 if order.side == OrderSide::NoOrderSide && delta.action != BookAction::Clear {
297 return Err(BookIntegrityError::NoOrderSide);
298 }
299
300 let flags = delta.flags;
301 let sequence = delta.sequence;
302 let ts_event = delta.ts_event;
303
304 match delta.action {
305 BookAction::Add => self.add(order, flags, sequence, ts_event),
306 BookAction::Update => self.update(order, flags, sequence, ts_event),
307 BookAction::Delete => self.delete(order, flags, sequence, ts_event),
308 BookAction::Clear => self.clear(sequence, ts_event),
309 }
310
311 Ok(())
312 }
313
314 pub fn apply_deltas(&mut self, deltas: &OrderBookDeltas) -> Result<(), BookIntegrityError> {
320 for delta in &deltas.deltas {
321 self.apply_delta(delta)?;
322 }
323 Ok(())
324 }
325
326 pub fn apply_depth(&mut self, depth: &OrderBookDepth10) {
328 self.bids.clear();
329 self.asks.clear();
330
331 for order in depth.bids {
332 if order.side == OrderSide::NoOrderSide || !order.size.is_positive() {
334 continue;
335 }
336
337 debug_assert_eq!(
338 order.side,
339 OrderSide::Buy,
340 "Bid order must have Buy side, was {:?}",
341 order.side
342 );
343
344 let order = pre_process_order(self.book_type, order, depth.flags);
345 self.bids.add(order);
346 }
347
348 for order in depth.asks {
349 if order.side == OrderSide::NoOrderSide || !order.size.is_positive() {
351 continue;
352 }
353
354 debug_assert_eq!(
355 order.side,
356 OrderSide::Sell,
357 "Ask order must have Sell side, was {:?}",
358 order.side
359 );
360
361 let order = pre_process_order(self.book_type, order, depth.flags);
362 self.asks.add(order);
363 }
364
365 self.increment(depth.sequence, depth.ts_event);
366 }
367
368 fn resolve_no_side_order(&self, mut order: BookOrder) -> Result<BookOrder, BookIntegrityError> {
369 let resolved_side = self
370 .bids
371 .cache
372 .get(&order.order_id)
373 .or_else(|| self.asks.cache.get(&order.order_id))
374 .map(|book_price| match book_price.side {
375 OrderSideSpecified::Buy => OrderSide::Buy,
376 OrderSideSpecified::Sell => OrderSide::Sell,
377 })
378 .ok_or(BookIntegrityError::OrderNotFoundForSideResolution(
379 order.order_id,
380 ))?;
381
382 order.side = resolved_side;
383
384 Ok(order)
385 }
386
387 pub fn bids(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
389 self.bids.levels.values().take(depth.unwrap_or(usize::MAX))
390 }
391
392 pub fn asks(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
394 self.asks.levels.values().take(depth.unwrap_or(usize::MAX))
395 }
396
397 pub fn bids_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
399 self.bids(depth)
400 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
401 .collect()
402 }
403
404 pub fn asks_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
406 self.asks(depth)
407 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
408 .collect()
409 }
410
411 pub fn group_bids(
413 &self,
414 group_size: Decimal,
415 depth: Option<usize>,
416 ) -> IndexMap<Decimal, Decimal> {
417 group_levels(self.bids(None), group_size, depth, true)
418 }
419
420 pub fn group_asks(
422 &self,
423 group_size: Decimal,
424 depth: Option<usize>,
425 ) -> IndexMap<Decimal, Decimal> {
426 group_levels(self.asks(None), group_size, depth, false)
427 }
428
429 pub fn bids_filtered_as_map(
435 &self,
436 depth: Option<usize>,
437 own_book: Option<&OwnOrderBook>,
438 status: Option<HashSet<OrderStatus>>,
439 accepted_buffer_ns: Option<u64>,
440 now: Option<u64>,
441 ) -> IndexMap<Decimal, Decimal> {
442 let mut public_map = self
443 .bids(depth)
444 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
445 .collect::<IndexMap<Decimal, Decimal>>();
446
447 if let Some(own_book) = own_book {
448 filter_quantities(
449 &mut public_map,
450 own_book.bid_quantity(status, None, None, accepted_buffer_ns, now),
451 );
452 }
453
454 public_map
455 }
456
457 pub fn asks_filtered_as_map(
463 &self,
464 depth: Option<usize>,
465 own_book: Option<&OwnOrderBook>,
466 status: Option<HashSet<OrderStatus>>,
467 accepted_buffer_ns: Option<u64>,
468 now: Option<u64>,
469 ) -> IndexMap<Decimal, Decimal> {
470 let mut public_map = self
471 .asks(depth)
472 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
473 .collect::<IndexMap<Decimal, Decimal>>();
474
475 if let Some(own_book) = own_book {
476 filter_quantities(
477 &mut public_map,
478 own_book.ask_quantity(status, None, None, accepted_buffer_ns, now),
479 );
480 }
481
482 public_map
483 }
484
485 pub fn group_bids_filtered(
491 &self,
492 group_size: Decimal,
493 depth: Option<usize>,
494 own_book: Option<&OwnOrderBook>,
495 status: Option<HashSet<OrderStatus>>,
496 accepted_buffer_ns: Option<u64>,
497 now: Option<u64>,
498 ) -> IndexMap<Decimal, Decimal> {
499 let mut public_map = group_levels(self.bids(None), group_size, depth, true);
500
501 if let Some(own_book) = own_book {
502 filter_quantities(
503 &mut public_map,
504 own_book.bid_quantity(status, depth, Some(group_size), accepted_buffer_ns, now),
505 );
506 }
507
508 public_map
509 }
510
511 pub fn group_asks_filtered(
517 &self,
518 group_size: Decimal,
519 depth: Option<usize>,
520 own_book: Option<&OwnOrderBook>,
521 status: Option<HashSet<OrderStatus>>,
522 accepted_buffer_ns: Option<u64>,
523 now: Option<u64>,
524 ) -> IndexMap<Decimal, Decimal> {
525 let mut public_map = group_levels(self.asks(None), group_size, depth, false);
526
527 if let Some(own_book) = own_book {
528 filter_quantities(
529 &mut public_map,
530 own_book.ask_quantity(status, depth, Some(group_size), accepted_buffer_ns, now),
531 );
532 }
533
534 public_map
535 }
536
537 #[must_use]
539 pub fn has_bid(&self) -> bool {
540 self.bids.top().is_some_and(|top| !top.orders.is_empty())
541 }
542
543 #[must_use]
545 pub fn has_ask(&self) -> bool {
546 self.asks.top().is_some_and(|top| !top.orders.is_empty())
547 }
548
549 #[must_use]
551 pub fn best_bid_price(&self) -> Option<Price> {
552 self.bids.top().map(|top| top.price.value)
553 }
554
555 #[must_use]
557 pub fn best_ask_price(&self) -> Option<Price> {
558 self.asks.top().map(|top| top.price.value)
559 }
560
561 #[must_use]
563 pub fn best_bid_size(&self) -> Option<Quantity> {
564 self.bids
565 .top()
566 .and_then(|top| top.first().map(|order| order.size))
567 }
568
569 #[must_use]
571 pub fn best_ask_size(&self) -> Option<Quantity> {
572 self.asks
573 .top()
574 .and_then(|top| top.first().map(|order| order.size))
575 }
576
577 #[must_use]
579 pub fn spread(&self) -> Option<f64> {
580 match (self.best_ask_price(), self.best_bid_price()) {
581 (Some(ask), Some(bid)) => Some(ask.as_f64() - bid.as_f64()),
582 _ => None,
583 }
584 }
585
586 #[must_use]
588 pub fn midpoint(&self) -> Option<f64> {
589 match (self.best_ask_price(), self.best_bid_price()) {
590 (Some(ask), Some(bid)) => Some((ask.as_f64() + bid.as_f64()) / 2.0),
591 _ => None,
592 }
593 }
594
595 #[must_use]
597 pub fn get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
598 let levels = match order_side.as_specified() {
599 OrderSideSpecified::Buy => &self.asks.levels,
600 OrderSideSpecified::Sell => &self.bids.levels,
601 };
602
603 analysis::get_avg_px_for_quantity(qty, levels)
604 }
605
606 #[must_use]
608 pub fn get_avg_px_qty_for_exposure(
609 &self,
610 target_exposure: Quantity,
611 order_side: OrderSide,
612 ) -> (f64, f64, f64) {
613 let levels = match order_side.as_specified() {
614 OrderSideSpecified::Buy => &self.asks.levels,
615 OrderSideSpecified::Sell => &self.bids.levels,
616 };
617
618 analysis::get_avg_px_qty_for_exposure(target_exposure, levels)
619 }
620
621 #[must_use]
623 pub fn get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
624 let levels = match order_side.as_specified() {
625 OrderSideSpecified::Buy => &self.asks.levels,
626 OrderSideSpecified::Sell => &self.bids.levels,
627 };
628
629 analysis::get_quantity_for_price(price, order_side, levels)
630 }
631
632 #[must_use]
634 pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
635 match order.side.as_specified() {
636 OrderSideSpecified::Buy => self.asks.simulate_fills(order),
637 OrderSideSpecified::Sell => self.bids.simulate_fills(order),
638 }
639 }
640
641 #[must_use]
643 pub fn pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
644 pprint_book(self, num_levels, group_size)
645 }
646
647 fn increment(&mut self, sequence: u64, ts_event: UnixNanos) {
648 if sequence < self.sequence {
650 let msg = format!(
651 "Sequence number should not go backwards: old={}, new={}",
652 self.sequence, sequence
653 );
654 debug_assert!(sequence >= self.sequence, "{}", msg);
655 log::warn!("{}", msg);
656 }
657
658 if ts_event < self.ts_last {
659 let msg = format!(
660 "Timestamp should not go backwards: old={}, new={}",
661 self.ts_last, ts_event
662 );
663 debug_assert!(ts_event >= self.ts_last, "{}", msg);
664 log::warn!("{}", msg);
665 }
666
667 if self.update_count == u64::MAX {
668 debug_assert!(
670 self.update_count < u64::MAX,
671 "Update count at u64::MAX limit (about to overflow): {}",
672 self.update_count
673 );
674
675 log::warn!(
677 "Update count at u64::MAX: {} (instrument_id={})",
678 self.update_count,
679 self.instrument_id
680 );
681 }
682
683 self.sequence = sequence;
684 self.ts_last = ts_event;
685 self.update_count = self.update_count.saturating_add(1);
686 }
687
688 pub fn update_quote_tick(&mut self, quote: &QuoteTick) -> Result<(), InvalidBookOperation> {
694 if self.book_type != BookType::L1_MBP {
695 return Err(InvalidBookOperation::Update(self.book_type));
696 }
697
698 if cfg!(debug_assertions) && quote.bid_price > quote.ask_price {
701 log::warn!(
702 "Quote has crossed prices: bid={}, ask={} for {}",
703 quote.bid_price,
704 quote.ask_price,
705 self.instrument_id
706 );
707 }
708 debug_assert!(
709 quote.bid_size.is_positive() && quote.ask_size.is_positive(),
710 "Quote has non-positive sizes: bid_size={}, ask_size={}",
711 quote.bid_size,
712 quote.ask_size
713 );
714
715 let bid = BookOrder::new(
716 OrderSide::Buy,
717 quote.bid_price,
718 quote.bid_size,
719 OrderSide::Buy as u64,
720 );
721
722 let ask = BookOrder::new(
723 OrderSide::Sell,
724 quote.ask_price,
725 quote.ask_size,
726 OrderSide::Sell as u64,
727 );
728
729 self.update_book_bid(bid, quote.ts_event);
730 self.update_book_ask(ask, quote.ts_event);
731
732 self.increment(self.sequence.saturating_add(1), quote.ts_event);
733
734 Ok(())
735 }
736
737 pub fn update_trade_tick(&mut self, trade: &TradeTick) -> Result<(), InvalidBookOperation> {
743 if self.book_type != BookType::L1_MBP {
744 return Err(InvalidBookOperation::Update(self.book_type));
745 }
746
747 debug_assert!(
749 trade.price.raw != PRICE_UNDEF && trade.price.raw != PRICE_ERROR,
750 "Trade has invalid/uninitialized price: {}",
751 trade.price
752 );
753 debug_assert!(
754 trade.size.is_positive(),
755 "Trade has non-positive size: {}",
756 trade.size
757 );
758
759 let bid = BookOrder::new(
760 OrderSide::Buy,
761 trade.price,
762 trade.size,
763 OrderSide::Buy as u64,
764 );
765
766 let ask = BookOrder::new(
767 OrderSide::Sell,
768 trade.price,
769 trade.size,
770 OrderSide::Sell as u64,
771 );
772
773 self.update_book_bid(bid, trade.ts_event);
774 self.update_book_ask(ask, trade.ts_event);
775
776 self.increment(self.sequence.saturating_add(1), trade.ts_event);
777
778 Ok(())
779 }
780
781 fn update_book_bid(&mut self, order: BookOrder, ts_event: UnixNanos) {
782 if let Some(top_bids) = self.bids.top()
783 && let Some(top_bid) = top_bids.first()
784 {
785 self.bids.remove_order(top_bid.order_id, 0, ts_event);
786 }
787 self.bids.add(order);
788 }
789
790 fn update_book_ask(&mut self, order: BookOrder, ts_event: UnixNanos) {
791 if let Some(top_asks) = self.asks.top()
792 && let Some(top_ask) = top_asks.first()
793 {
794 self.asks.remove_order(top_ask.order_id, 0, ts_event);
795 }
796 self.asks.add(order);
797 }
798}
799
800fn filter_quantities(
801 public_map: &mut IndexMap<Decimal, Decimal>,
802 own_map: IndexMap<Decimal, Decimal>,
803) {
804 for (price, own_size) in own_map {
805 if let Some(public_size) = public_map.get_mut(&price) {
806 *public_size = (*public_size - own_size).max(Decimal::ZERO);
807
808 if *public_size == Decimal::ZERO {
809 public_map.shift_remove(&price);
810 }
811 }
812 }
813}
814
815fn group_levels<'a>(
816 levels_iter: impl Iterator<Item = &'a BookLevel>,
817 group_size: Decimal,
818 depth: Option<usize>,
819 is_bid: bool,
820) -> IndexMap<Decimal, Decimal> {
821 if group_size <= Decimal::ZERO {
822 log::error!("Invalid group_size: {group_size}, must be positive; returning empty map");
823 return IndexMap::new();
824 }
825
826 let mut levels = IndexMap::new();
827 let depth = depth.unwrap_or(usize::MAX);
828
829 for level in levels_iter {
830 let price = level.price.value.as_decimal();
831 let grouped_price = if is_bid {
832 (price / group_size).floor() * group_size
833 } else {
834 (price / group_size).ceil() * group_size
835 };
836 let size = level.size_decimal();
837
838 levels
839 .entry(grouped_price)
840 .and_modify(|total| *total += size)
841 .or_insert(size);
842
843 if levels.len() > depth {
844 levels.pop();
845 break;
846 }
847 }
848
849 levels
850}