1use std::fmt::Display;
19
20use ahash::AHashSet;
21use indexmap::IndexMap;
22use nautilus_core::{UnixNanos, correctness::FAILED};
23use rust_decimal::Decimal;
24
25use super::{
26 BookViewError, aggregation::pre_process_order, analysis, display::pprint_book,
27 level::BookLevel, own::OwnOrderBook,
28};
29use crate::{
30 data::{BookOrder, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick},
31 enums::{BookAction, BookType, OrderSide, OrderSideSpecified, OrderStatus, RecordFlag},
32 identifiers::InstrumentId,
33 orderbook::{
34 BookIntegrityError, InvalidBookOperation,
35 ladder::{BookLadder, BookPrice},
36 },
37 types::{
38 Price, Quantity,
39 price::{PRICE_ERROR, PRICE_UNDEF},
40 },
41};
42
43#[derive(Clone, Debug)]
51#[cfg_attr(
52 feature = "python",
53 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", from_py_object)
54)]
55#[cfg_attr(
56 feature = "python",
57 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
58)]
59pub struct OrderBook {
60 pub instrument_id: InstrumentId,
62 pub book_type: BookType,
64 pub sequence: u64,
66 pub ts_last: UnixNanos,
68 pub update_count: u64,
70 pub(crate) bids: BookLadder,
71 pub(crate) asks: BookLadder,
72}
73
74impl PartialEq for OrderBook {
75 fn eq(&self, other: &Self) -> bool {
76 self.instrument_id == other.instrument_id && self.book_type == other.book_type
77 }
78}
79
80impl Eq for OrderBook {}
81
82impl Display for OrderBook {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(
85 f,
86 "{}(instrument_id={}, book_type={}, update_count={})",
87 stringify!(OrderBook),
88 self.instrument_id,
89 self.book_type,
90 self.update_count,
91 )
92 }
93}
94
95impl OrderBook {
96 #[must_use]
98 pub fn new(instrument_id: InstrumentId, book_type: BookType) -> Self {
99 Self {
100 instrument_id,
101 book_type,
102 sequence: 0,
103 ts_last: UnixNanos::default(),
104 update_count: 0,
105 bids: BookLadder::new(OrderSideSpecified::Buy, book_type),
106 asks: BookLadder::new(OrderSideSpecified::Sell, book_type),
107 }
108 }
109
110 pub fn reset(&mut self) {
112 self.bids.clear();
113 self.asks.clear();
114 self.sequence = 0;
115 self.ts_last = UnixNanos::default();
116 self.update_count = 0;
117 }
118
119 pub fn add(&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.add(order, flags),
124 OrderSideSpecified::Sell => self.asks.add(order, flags),
125 }
126
127 self.increment(sequence, ts_event);
128 }
129
130 pub fn update(&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.update(order, flags),
135 OrderSideSpecified::Sell => self.asks.update(order, flags),
136 }
137
138 self.increment(sequence, ts_event);
139 }
140
141 pub fn delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: UnixNanos) {
143 let order = pre_process_order(self.book_type, order, flags);
144 match order.side.as_specified() {
145 OrderSideSpecified::Buy => self.bids.delete(order, sequence, ts_event),
146 OrderSideSpecified::Sell => self.asks.delete(order, sequence, ts_event),
147 }
148
149 self.increment(sequence, ts_event);
150 }
151
152 pub fn clear(&mut self, sequence: u64, ts_event: UnixNanos) {
154 self.bids.clear();
155 self.asks.clear();
156 self.increment(sequence, ts_event);
157 }
158
159 pub fn clear_bids(&mut self, sequence: u64, ts_event: UnixNanos) {
161 self.bids.clear();
162 self.increment(sequence, ts_event);
163 }
164
165 pub fn clear_asks(&mut self, sequence: u64, ts_event: UnixNanos) {
167 self.asks.clear();
168 self.increment(sequence, ts_event);
169 }
170
171 pub fn clear_stale_levels(&mut self, side: Option<OrderSide>) -> Option<Vec<BookLevel>> {
179 if self.book_type == BookType::L1_MBP {
180 return None;
182 }
183
184 let (Some(best_bid), Some(best_ask)) = (self.best_bid_price(), self.best_ask_price())
185 else {
186 return None;
187 };
188
189 if best_bid <= best_ask {
190 return None;
191 }
192
193 let mut removed_levels = Vec::new();
194 let mut clear_bids = false;
195 let mut clear_asks = false;
196
197 match side {
198 Some(OrderSide::Buy) => clear_bids = true,
199 Some(OrderSide::Sell) => clear_asks = true,
200 _ => {
201 clear_bids = true;
202 clear_asks = true;
203 }
204 }
205
206 let mut ask_prices_to_remove = Vec::new();
208
209 if clear_asks {
210 for bp in self.asks.levels.keys() {
211 if bp.value <= best_bid {
212 ask_prices_to_remove.push(*bp);
213 } else {
214 break;
215 }
216 }
217 }
218
219 let mut bid_prices_to_remove = Vec::new();
221
222 if clear_bids {
223 for bp in self.bids.levels.keys() {
224 if bp.value >= best_ask {
225 bid_prices_to_remove.push(*bp);
226 } else {
227 break;
228 }
229 }
230 }
231
232 if ask_prices_to_remove.is_empty() && bid_prices_to_remove.is_empty() {
233 return None;
234 }
235
236 let bid_count = bid_prices_to_remove.len();
237 let ask_count = ask_prices_to_remove.len();
238
239 for price in bid_prices_to_remove {
241 if let Some(level) = self.bids.remove_level(price) {
242 removed_levels.push(level);
243 }
244 }
245
246 for price in ask_prices_to_remove {
248 if let Some(level) = self.asks.remove_level(price) {
249 removed_levels.push(level);
250 }
251 }
252
253 self.increment(self.sequence, self.ts_last);
254
255 if removed_levels.is_empty() {
256 None
257 } else {
258 let total_orders: usize = removed_levels.iter().map(|level| level.orders.len()).sum();
259
260 log::warn!(
261 "Removed {} stale/crossed levels (instrument_id={}, bid_levels={}, ask_levels={}, total_orders={}), book was crossed with best_bid={} > best_ask={}",
262 removed_levels.len(),
263 self.instrument_id,
264 bid_count,
265 ask_count,
266 total_orders,
267 best_bid,
268 best_ask
269 );
270
271 Some(removed_levels)
272 }
273 }
274
275 pub fn apply_delta(&mut self, delta: &OrderBookDelta) -> Result<(), BookIntegrityError> {
284 if delta.instrument_id != self.instrument_id {
285 return Err(BookIntegrityError::InstrumentMismatch(
286 self.instrument_id,
287 delta.instrument_id,
288 ));
289 }
290 self.apply_delta_unchecked(delta)
291 }
292
293 pub fn apply_delta_unchecked(
306 &mut self,
307 delta: &OrderBookDelta,
308 ) -> Result<(), BookIntegrityError> {
309 let mut order = delta.order;
310
311 if order.side == OrderSide::NoOrderSide && order.order_id != 0 {
312 match self.resolve_no_side_order(order) {
313 Ok(resolved) => order = resolved,
314 Err(BookIntegrityError::OrderNotFoundForSideResolution(order_id)) => {
315 match delta.action {
316 BookAction::Add => return Err(BookIntegrityError::NoOrderSide),
317 BookAction::Update | BookAction::Delete => {
318 log::debug!(
320 "Skipping {:?} for unknown order_id={order_id}",
321 delta.action
322 );
323 return Ok(());
324 }
325 BookAction::Clear => {} }
327 }
328 Err(e) => return Err(e),
329 }
330 }
331
332 if order.side == OrderSide::NoOrderSide && delta.action != BookAction::Clear {
333 return Err(BookIntegrityError::NoOrderSide);
334 }
335
336 let flags = delta.flags;
337 let sequence = delta.sequence;
338 let ts_event = delta.ts_event;
339
340 match delta.action {
341 BookAction::Add => self.add(order, flags, sequence, ts_event),
342 BookAction::Update => self.update(order, flags, sequence, ts_event),
343 BookAction::Delete => self.delete(order, flags, sequence, ts_event),
344 BookAction::Clear => self.clear(sequence, ts_event),
345 }
346
347 Ok(())
348 }
349
350 pub fn apply_deltas(&mut self, deltas: &OrderBookDeltas) -> Result<(), BookIntegrityError> {
358 if deltas.instrument_id != self.instrument_id {
359 return Err(BookIntegrityError::InstrumentMismatch(
360 self.instrument_id,
361 deltas.instrument_id,
362 ));
363 }
364 self.apply_deltas_unchecked(deltas)
365 }
366
367 pub fn apply_deltas_unchecked(
375 &mut self,
376 deltas: &OrderBookDeltas,
377 ) -> Result<(), BookIntegrityError> {
378 for delta in &deltas.deltas {
379 self.apply_delta_unchecked(delta)?;
380 }
381 Ok(())
382 }
383
384 #[must_use]
398 pub fn to_deltas(&self, ts_event: UnixNanos, ts_init: UnixNanos) -> OrderBookDeltas {
399 let mut deltas = Vec::new();
400
401 let total_orders = self.bids(None).map(|level| level.len()).sum::<usize>()
402 + self.asks(None).map(|level| level.len()).sum::<usize>();
403
404 let mut clear = OrderBookDelta::clear(self.instrument_id, self.sequence, ts_event, ts_init);
406
407 if total_orders == 0 {
408 clear.flags |= RecordFlag::F_LAST as u8;
409 }
410 deltas.push(clear);
411
412 let mut order_count = 0;
413
414 for level in self.bids(None) {
416 for order in level.iter() {
417 order_count += 1;
418 let flags = if order_count == total_orders {
419 RecordFlag::F_SNAPSHOT as u8 | RecordFlag::F_LAST as u8
420 } else {
421 RecordFlag::F_SNAPSHOT as u8
422 };
423
424 deltas.push(OrderBookDelta::new(
425 self.instrument_id,
426 BookAction::Add,
427 *order,
428 flags,
429 self.sequence,
430 ts_event,
431 ts_init,
432 ));
433 }
434 }
435
436 for level in self.asks(None) {
438 for order in level.iter() {
439 order_count += 1;
440 let flags = if order_count == total_orders {
441 RecordFlag::F_SNAPSHOT as u8 | RecordFlag::F_LAST as u8
442 } else {
443 RecordFlag::F_SNAPSHOT as u8
444 };
445
446 deltas.push(OrderBookDelta::new(
447 self.instrument_id,
448 BookAction::Add,
449 *order,
450 flags,
451 self.sequence,
452 ts_event,
453 ts_init,
454 ));
455 }
456 }
457
458 OrderBookDeltas::new(self.instrument_id, deltas)
459 }
460
461 pub fn apply_depth(&mut self, depth: &OrderBookDepth10) -> Result<(), BookIntegrityError> {
467 if depth.instrument_id != self.instrument_id {
468 return Err(BookIntegrityError::InstrumentMismatch(
469 self.instrument_id,
470 depth.instrument_id,
471 ));
472 }
473 self.apply_depth_unchecked(depth)
474 }
475
476 pub fn apply_depth_unchecked(
484 &mut self,
485 depth: &OrderBookDepth10,
486 ) -> Result<(), BookIntegrityError> {
487 self.bids.clear();
488 self.asks.clear();
489
490 for order in depth.bids {
491 if order.side == OrderSide::NoOrderSide || !order.size.is_positive() {
493 continue;
494 }
495
496 if order.side != OrderSide::Buy {
497 debug_assert_eq!(
498 order.side,
499 OrderSide::Buy,
500 "Bid order must have Buy side, was {:?}",
501 order.side
502 );
503 log::warn!(
504 "Skipping bid order with wrong side {:?} (instrument_id={})",
505 order.side,
506 self.instrument_id
507 );
508 continue;
509 }
510
511 let order = pre_process_order(self.book_type, order, depth.flags);
512 self.bids.add(order, depth.flags);
513 }
514
515 for order in depth.asks {
516 if order.side == OrderSide::NoOrderSide || !order.size.is_positive() {
518 continue;
519 }
520
521 if order.side != OrderSide::Sell {
522 debug_assert_eq!(
523 order.side,
524 OrderSide::Sell,
525 "Ask order must have Sell side, was {:?}",
526 order.side
527 );
528 log::warn!(
529 "Skipping ask order with wrong side {:?} (instrument_id={})",
530 order.side,
531 self.instrument_id
532 );
533 continue;
534 }
535
536 let order = pre_process_order(self.book_type, order, depth.flags);
537 self.asks.add(order, depth.flags);
538 }
539
540 self.increment(depth.sequence, depth.ts_event);
541
542 Ok(())
543 }
544
545 fn resolve_no_side_order(&self, mut order: BookOrder) -> Result<BookOrder, BookIntegrityError> {
546 let resolved_side = self
547 .bids
548 .cache
549 .get(&order.order_id)
550 .or_else(|| self.asks.cache.get(&order.order_id))
551 .map(|book_price| match book_price.side {
552 OrderSideSpecified::Buy => OrderSide::Buy,
553 OrderSideSpecified::Sell => OrderSide::Sell,
554 })
555 .ok_or(BookIntegrityError::OrderNotFoundForSideResolution(
556 order.order_id,
557 ))?;
558
559 order.side = resolved_side;
560
561 Ok(order)
562 }
563
564 pub fn bids(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
566 self.bids.levels.values().take(depth.unwrap_or(usize::MAX))
567 }
568
569 pub fn asks(&self, depth: Option<usize>) -> impl Iterator<Item = &BookLevel> {
571 self.asks.levels.values().take(depth.unwrap_or(usize::MAX))
572 }
573
574 #[must_use]
576 pub fn bids_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
577 self.bids(depth)
578 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
579 .collect()
580 }
581
582 #[must_use]
584 pub fn asks_as_map(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
585 self.asks(depth)
586 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
587 .collect()
588 }
589
590 #[must_use]
592 pub fn group_bids(
593 &self,
594 group_size: Decimal,
595 depth: Option<usize>,
596 ) -> IndexMap<Decimal, Decimal> {
597 group_levels(self.bids(None), group_size, depth, true)
598 }
599
600 #[must_use]
602 pub fn group_asks(
603 &self,
604 group_size: Decimal,
605 depth: Option<usize>,
606 ) -> IndexMap<Decimal, Decimal> {
607 group_levels(self.asks(None), group_size, depth, false)
608 }
609
610 #[must_use]
616 pub fn bids_filtered_as_map(
617 &self,
618 depth: Option<usize>,
619 own_book: Option<&OwnOrderBook>,
620 status: Option<&AHashSet<OrderStatus>>,
621 accepted_buffer_ns: Option<u64>,
622 now: Option<u64>,
623 ) -> IndexMap<Decimal, Decimal> {
624 let mut public_map = self
625 .bids(depth)
626 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
627 .collect::<IndexMap<Decimal, Decimal>>();
628
629 if let Some(own_book) = own_book {
630 filter_quantities(
631 &mut public_map,
632 own_book.bid_quantity(status, None, None, accepted_buffer_ns, now),
633 );
634 }
635
636 public_map
637 }
638
639 #[must_use]
645 pub fn asks_filtered_as_map(
646 &self,
647 depth: Option<usize>,
648 own_book: Option<&OwnOrderBook>,
649 status: Option<&AHashSet<OrderStatus>>,
650 accepted_buffer_ns: Option<u64>,
651 now: Option<u64>,
652 ) -> IndexMap<Decimal, Decimal> {
653 let mut public_map = self
654 .asks(depth)
655 .map(|level| (level.price.value.as_decimal(), level.size_decimal()))
656 .collect::<IndexMap<Decimal, Decimal>>();
657
658 if let Some(own_book) = own_book {
659 filter_quantities(
660 &mut public_map,
661 own_book.ask_quantity(status, None, None, accepted_buffer_ns, now),
662 );
663 }
664
665 public_map
666 }
667
668 #[must_use]
676 pub fn filtered_view(
677 &self,
678 own_book: Option<&OwnOrderBook>,
679 depth: Option<usize>,
680 status: Option<&AHashSet<OrderStatus>>,
681 accepted_buffer_ns: Option<u64>,
682 now: Option<u64>,
683 ) -> Self {
684 self.filtered_view_checked(own_book, depth, status, accepted_buffer_ns, now)
685 .expect(FAILED)
686 }
687
688 pub fn filtered_view_checked(
700 &self,
701 own_book: Option<&OwnOrderBook>,
702 depth: Option<usize>,
703 status: Option<&AHashSet<OrderStatus>>,
704 accepted_buffer_ns: Option<u64>,
705 now: Option<u64>,
706 ) -> Result<Self, BookViewError> {
707 if let Some(own_book) = own_book
708 && self.instrument_id != own_book.instrument_id
709 {
710 return Err(BookViewError::InstrumentMismatch(
711 self.instrument_id,
712 own_book.instrument_id,
713 ));
714 }
715
716 let bids_map = self.bids_filtered_as_map(depth, own_book, status, accepted_buffer_ns, now);
717 let asks_map = self.asks_filtered_as_map(depth, own_book, status, accepted_buffer_ns, now);
718
719 let mut filtered_book = Self::new(self.instrument_id, self.book_type);
720 filtered_book.sequence = self.sequence;
721 filtered_book.ts_last = self.ts_last;
722
723 let sequence = self.sequence;
724 let ts_event = self.ts_last;
725
726 let mut order_id = 1_u64;
727
728 for (price, quantity) in bids_map {
729 if quantity <= Decimal::ZERO {
730 continue;
731 }
732
733 let order = BookOrder::new(
734 OrderSide::Buy,
735 Price::from_decimal(price).expect("Invalid bid price for OrderBook::filtered_view"),
736 Quantity::from_decimal(quantity)
737 .expect("Invalid bid quantity for OrderBook::filtered_view"),
738 order_id,
739 );
740 order_id += 1;
741 filtered_book.add(order, 0, sequence, ts_event);
742 }
743
744 for (price, quantity) in asks_map {
745 if quantity <= Decimal::ZERO {
746 continue;
747 }
748
749 let order = BookOrder::new(
750 OrderSide::Sell,
751 Price::from_decimal(price).expect("Invalid ask price for OrderBook::filtered_view"),
752 Quantity::from_decimal(quantity)
753 .expect("Invalid ask quantity for OrderBook::filtered_view"),
754 order_id,
755 );
756 order_id += 1;
757 filtered_book.add(order, 0, sequence, ts_event);
758 }
759
760 Ok(filtered_book)
761 }
762
763 #[must_use]
769 pub fn group_bids_filtered(
770 &self,
771 group_size: Decimal,
772 depth: Option<usize>,
773 own_book: Option<&OwnOrderBook>,
774 status: Option<&AHashSet<OrderStatus>>,
775 accepted_buffer_ns: Option<u64>,
776 now: Option<u64>,
777 ) -> IndexMap<Decimal, Decimal> {
778 let mut public_map = group_levels(self.bids(None), group_size, depth, true);
779
780 if let Some(own_book) = own_book {
781 filter_quantities(
782 &mut public_map,
783 own_book.bid_quantity(status, depth, Some(group_size), accepted_buffer_ns, now),
784 );
785 }
786
787 public_map
788 }
789
790 #[must_use]
796 pub fn group_asks_filtered(
797 &self,
798 group_size: Decimal,
799 depth: Option<usize>,
800 own_book: Option<&OwnOrderBook>,
801 status: Option<&AHashSet<OrderStatus>>,
802 accepted_buffer_ns: Option<u64>,
803 now: Option<u64>,
804 ) -> IndexMap<Decimal, Decimal> {
805 let mut public_map = group_levels(self.asks(None), group_size, depth, false);
806
807 if let Some(own_book) = own_book {
808 filter_quantities(
809 &mut public_map,
810 own_book.ask_quantity(status, depth, Some(group_size), accepted_buffer_ns, now),
811 );
812 }
813
814 public_map
815 }
816
817 #[must_use]
819 pub fn has_bid(&self) -> bool {
820 self.bids.top().is_some_and(|top| !top.orders.is_empty())
821 }
822
823 #[must_use]
825 pub fn has_ask(&self) -> bool {
826 self.asks.top().is_some_and(|top| !top.orders.is_empty())
827 }
828
829 #[must_use]
831 pub fn best_bid_price(&self) -> Option<Price> {
832 self.bids.top().map(|top| top.price.value)
833 }
834
835 #[must_use]
837 pub fn best_ask_price(&self) -> Option<Price> {
838 self.asks.top().map(|top| top.price.value)
839 }
840
841 #[must_use]
843 pub fn best_bid_size(&self) -> Option<Quantity> {
844 self.bids
845 .top()
846 .and_then(|top| top.first().map(|order| order.size))
847 }
848
849 #[must_use]
851 pub fn best_ask_size(&self) -> Option<Quantity> {
852 self.asks
853 .top()
854 .and_then(|top| top.first().map(|order| order.size))
855 }
856
857 #[must_use]
859 pub fn spread(&self) -> Option<f64> {
860 match (self.best_ask_price(), self.best_bid_price()) {
861 (Some(ask), Some(bid)) => Some(ask.as_f64() - bid.as_f64()),
862 _ => None,
863 }
864 }
865
866 #[must_use]
868 pub fn midpoint(&self) -> Option<f64> {
869 match (self.best_ask_price(), self.best_bid_price()) {
870 (Some(ask), Some(bid)) => Some(f64::midpoint(ask.as_f64(), bid.as_f64())),
871 _ => None,
872 }
873 }
874
875 #[must_use]
877 pub fn get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
878 let levels = match order_side.as_specified() {
879 OrderSideSpecified::Buy => &self.asks.levels,
880 OrderSideSpecified::Sell => &self.bids.levels,
881 };
882
883 analysis::get_avg_px_for_quantity(qty, levels)
884 }
885
886 #[must_use]
888 pub fn get_worst_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> Option<Price> {
889 let levels = match order_side.as_specified() {
890 OrderSideSpecified::Buy => &self.asks.levels,
891 OrderSideSpecified::Sell => &self.bids.levels,
892 };
893
894 analysis::get_worst_px_for_quantity(qty, levels)
895 }
896
897 #[must_use]
899 pub fn get_avg_px_qty_for_exposure(
900 &self,
901 target_exposure: Quantity,
902 order_side: OrderSide,
903 ) -> (f64, f64, f64) {
904 let levels = match order_side.as_specified() {
905 OrderSideSpecified::Buy => &self.asks.levels,
906 OrderSideSpecified::Sell => &self.bids.levels,
907 };
908
909 analysis::get_avg_px_qty_for_exposure(target_exposure, levels)
910 }
911
912 #[must_use]
917 pub fn get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
918 let side = order_side.as_specified();
919 let levels = match side {
920 OrderSideSpecified::Buy => &self.asks.levels,
921 OrderSideSpecified::Sell => &self.bids.levels,
922 };
923
924 analysis::get_quantity_for_price(price, side, levels)
925 }
926
927 #[must_use]
932 pub fn get_quantity_at_level(
933 &self,
934 price: Price,
935 order_side: OrderSide,
936 size_precision: u8,
937 ) -> Quantity {
938 let side = order_side.as_specified();
939
940 let (levels, book_side) = match side {
943 OrderSideSpecified::Buy => (&self.asks.levels, OrderSideSpecified::Sell),
944 OrderSideSpecified::Sell => (&self.bids.levels, OrderSideSpecified::Buy),
945 };
946
947 let book_price = BookPrice::new(price, book_side);
948
949 levels
950 .get(&book_price)
951 .map_or(Quantity::zero(size_precision), |level| {
952 Quantity::from_raw(level.size_raw(), size_precision)
953 })
954 }
955
956 #[must_use]
958 pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
959 match order.side.as_specified() {
960 OrderSideSpecified::Buy => self.asks.simulate_fills(order),
961 OrderSideSpecified::Sell => self.bids.simulate_fills(order),
962 }
963 }
964
965 #[must_use]
971 pub fn get_all_crossed_levels(
972 &self,
973 order_side: OrderSide,
974 price: Price,
975 size_precision: u8,
976 ) -> Vec<(Price, Quantity)> {
977 let side = order_side.as_specified();
978 let levels = match side {
979 OrderSideSpecified::Buy => &self.asks.levels,
980 OrderSideSpecified::Sell => &self.bids.levels,
981 };
982
983 analysis::get_levels_for_price(price, side, levels, size_precision)
984 }
985
986 #[must_use]
988 pub fn pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
989 pprint_book(self, num_levels, group_size)
990 }
991
992 fn increment(&mut self, sequence: u64, ts_event: UnixNanos) {
993 if sequence > 0 && sequence < self.sequence {
994 log::warn!(
995 "Out-of-order update: sequence {} < {} (instrument_id={})",
996 sequence,
997 self.sequence,
998 self.instrument_id
999 );
1000 }
1001
1002 if ts_event < self.ts_last {
1003 log::warn!(
1004 "Out-of-order update: ts_event {} < {} (instrument_id={})",
1005 ts_event,
1006 self.ts_last,
1007 self.instrument_id
1008 );
1009 }
1010
1011 if self.update_count == u64::MAX {
1012 debug_assert!(
1013 self.update_count < u64::MAX,
1014 "Update count at u64::MAX limit (about to overflow): {}",
1015 self.update_count
1016 );
1017 log::warn!(
1018 "Update count at u64::MAX: {} (instrument_id={})",
1019 self.update_count,
1020 self.instrument_id
1021 );
1022 }
1023
1024 self.sequence = sequence.max(self.sequence);
1026 self.ts_last = ts_event.max(self.ts_last);
1027 self.update_count = self.update_count.saturating_add(1);
1028 }
1029
1030 pub fn update_quote_tick(&mut self, quote: &QuoteTick) -> Result<(), InvalidBookOperation> {
1036 if self.book_type != BookType::L1_MBP {
1037 return Err(InvalidBookOperation::Update(self.book_type));
1038 }
1039
1040 if quote.ts_event < self.ts_last {
1041 log::warn!(
1042 "Skipping stale quote: ts_event {} < ts_last {} (instrument_id={})",
1043 quote.ts_event,
1044 self.ts_last,
1045 self.instrument_id
1046 );
1047 return Ok(());
1048 }
1049
1050 if cfg!(debug_assertions) && quote.bid_price > quote.ask_price {
1052 log::warn!(
1053 "Quote has crossed prices: bid={}, ask={} for {}",
1054 quote.bid_price,
1055 quote.ask_price,
1056 self.instrument_id
1057 );
1058 }
1059
1060 let bid = BookOrder::new(
1061 OrderSide::Buy,
1062 quote.bid_price,
1063 quote.bid_size,
1064 OrderSide::Buy as u64,
1065 );
1066
1067 let ask = BookOrder::new(
1068 OrderSide::Sell,
1069 quote.ask_price,
1070 quote.ask_size,
1071 OrderSide::Sell as u64,
1072 );
1073
1074 self.update_book_bid(bid, quote.ts_event);
1075 self.update_book_ask(ask, quote.ts_event);
1076
1077 self.increment(self.sequence.saturating_add(1), quote.ts_event);
1078
1079 Ok(())
1080 }
1081
1082 pub fn update_trade_tick(&mut self, trade: &TradeTick) -> Result<(), InvalidBookOperation> {
1088 if self.book_type != BookType::L1_MBP {
1089 return Err(InvalidBookOperation::Update(self.book_type));
1090 }
1091
1092 if trade.ts_event < self.ts_last {
1093 log::warn!(
1094 "Skipping stale trade: ts_event {} < ts_last {} (instrument_id={})",
1095 trade.ts_event,
1096 self.ts_last,
1097 self.instrument_id
1098 );
1099 return Ok(());
1100 }
1101
1102 debug_assert!(
1104 trade.price.raw != PRICE_UNDEF && trade.price.raw != PRICE_ERROR,
1105 "Trade has invalid/uninitialized price: {}",
1106 trade.price
1107 );
1108
1109 debug_assert!(
1111 trade.size.is_positive(),
1112 "Trade has non-positive size: {}",
1113 trade.size
1114 );
1115
1116 let bid = BookOrder::new(
1117 OrderSide::Buy,
1118 trade.price,
1119 trade.size,
1120 OrderSide::Buy as u64,
1121 );
1122
1123 let ask = BookOrder::new(
1124 OrderSide::Sell,
1125 trade.price,
1126 trade.size,
1127 OrderSide::Sell as u64,
1128 );
1129
1130 self.update_book_bid(bid, trade.ts_event);
1131 self.update_book_ask(ask, trade.ts_event);
1132
1133 self.increment(self.sequence.saturating_add(1), trade.ts_event);
1134
1135 Ok(())
1136 }
1137
1138 fn update_book_bid(&mut self, order: BookOrder, ts_event: UnixNanos) {
1139 if let Some(top_bids) = self.bids.top()
1140 && let Some(top_bid) = top_bids.first()
1141 {
1142 self.bids.remove_order(top_bid.order_id, 0, ts_event);
1143 }
1144 self.bids.add(order, 0); }
1146
1147 fn update_book_ask(&mut self, order: BookOrder, ts_event: UnixNanos) {
1148 if let Some(top_asks) = self.asks.top()
1149 && let Some(top_ask) = top_asks.first()
1150 {
1151 self.asks.remove_order(top_ask.order_id, 0, ts_event);
1152 }
1153 self.asks.add(order, 0); }
1155
1156 #[must_use]
1163 pub fn deltas_to_quotes(book_type: BookType, deltas: &[OrderBookDelta]) -> Vec<QuoteTick> {
1164 assert!(!deltas.is_empty(), "`deltas` must not be empty");
1165
1166 let instrument_id = deltas[0].instrument_id;
1167 let mut book = Self::new(instrument_id, book_type);
1168 let mut quotes = Vec::new();
1169 let mut last_bid: Option<Price> = None;
1170 let mut last_ask: Option<Price> = None;
1171
1172 for delta in deltas {
1173 book.apply_delta(delta).unwrap();
1174 let bid = book.best_bid_price();
1175 let ask = book.best_ask_price();
1176
1177 if bid.is_none() || ask.is_none() {
1180 last_bid = None;
1181 last_ask = None;
1182 }
1183
1184 if let (Some(bid_px), Some(ask_px)) = (bid, ask)
1185 && (bid != last_bid || ask != last_ask)
1186 {
1187 last_bid = bid;
1188 last_ask = ask;
1189 let bid_level = book.bids.top().unwrap();
1190 let ask_level = book.asks.top().unwrap();
1191 let precision = bid_level.first().unwrap().size.precision;
1192 let bid_sz = Quantity::from_raw(bid_level.size_raw(), precision);
1193 let ask_sz = Quantity::from_raw(ask_level.size_raw(), precision);
1194 let quote = QuoteTick::new(
1195 instrument_id,
1196 bid_px,
1197 ask_px,
1198 bid_sz,
1199 ask_sz,
1200 delta.ts_event,
1201 delta.ts_init,
1202 );
1203
1204 quotes.push(quote);
1205 }
1206 }
1207
1208 quotes
1209 }
1210}
1211
1212fn filter_quantities(
1213 public_map: &mut IndexMap<Decimal, Decimal>,
1214 own_map: IndexMap<Decimal, Decimal>,
1215) {
1216 for (price, own_size) in own_map {
1217 if let Some(public_size) = public_map.get_mut(&price) {
1218 *public_size = (*public_size - own_size).max(Decimal::ZERO);
1219
1220 if *public_size == Decimal::ZERO {
1221 public_map.shift_remove(&price);
1222 }
1223 }
1224 }
1225}
1226
1227fn group_levels<'a>(
1228 levels_iter: impl Iterator<Item = &'a BookLevel>,
1229 group_size: Decimal,
1230 depth: Option<usize>,
1231 is_bid: bool,
1232) -> IndexMap<Decimal, Decimal> {
1233 if group_size <= Decimal::ZERO {
1234 log::error!("Invalid group_size: {group_size}, must be positive; returning empty map");
1235 return IndexMap::new();
1236 }
1237
1238 let mut levels = IndexMap::new();
1239 let depth = depth.unwrap_or(usize::MAX);
1240
1241 for level in levels_iter {
1242 let price = level.price.value.as_decimal();
1243 let grouped_price = if is_bid {
1244 (price / group_size).floor() * group_size
1245 } else {
1246 (price / group_size).ceil() * group_size
1247 };
1248 let size = level.size_decimal();
1249
1250 levels
1251 .entry(grouped_price)
1252 .and_modify(|total| *total += size)
1253 .or_insert(size);
1254
1255 if levels.len() > depth {
1256 levels.pop();
1257 break;
1258 }
1259 }
1260
1261 levels
1262}