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 side = order_side.as_specified();
700 let levels = match side {
701 OrderSideSpecified::Buy => &self.asks.levels,
702 OrderSideSpecified::Sell => &self.bids.levels,
703 };
704
705 analysis::get_quantity_for_price(price, side, levels)
706 }
707
708 #[must_use]
710 pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
711 match order.side.as_specified() {
712 OrderSideSpecified::Buy => self.asks.simulate_fills(order),
713 OrderSideSpecified::Sell => self.bids.simulate_fills(order),
714 }
715 }
716
717 #[must_use]
723 pub fn get_all_crossed_levels(
724 &self,
725 order_side: OrderSide,
726 price: Price,
727 size_precision: u8,
728 ) -> Vec<(Price, Quantity)> {
729 let side = order_side.as_specified();
730 let levels = match side {
731 OrderSideSpecified::Buy => &self.asks.levels,
732 OrderSideSpecified::Sell => &self.bids.levels,
733 };
734
735 analysis::get_levels_for_price(price, side, levels, size_precision)
736 }
737
738 #[must_use]
740 pub fn pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
741 pprint_book(self, num_levels, group_size)
742 }
743
744 fn increment(&mut self, sequence: u64, ts_event: UnixNanos) {
745 if sequence < self.sequence {
747 let msg = format!(
748 "Sequence number should not go backwards: old={}, new={}",
749 self.sequence, sequence
750 );
751 debug_assert!(sequence >= self.sequence, "{}", msg);
752 log::warn!("{msg}");
753 }
754
755 if ts_event < self.ts_last {
756 let msg = format!(
757 "Timestamp should not go backwards: old={}, new={}",
758 self.ts_last, ts_event
759 );
760 debug_assert!(ts_event >= self.ts_last, "{}", msg);
761 log::warn!("{msg}");
762 }
763
764 if self.update_count == u64::MAX {
765 debug_assert!(
767 self.update_count < u64::MAX,
768 "Update count at u64::MAX limit (about to overflow): {}",
769 self.update_count
770 );
771
772 log::warn!(
774 "Update count at u64::MAX: {} (instrument_id={})",
775 self.update_count,
776 self.instrument_id
777 );
778 }
779
780 self.sequence = sequence;
781 self.ts_last = ts_event;
782 self.update_count = self.update_count.saturating_add(1);
783 }
784
785 pub fn update_quote_tick(&mut self, quote: &QuoteTick) -> Result<(), InvalidBookOperation> {
791 if self.book_type != BookType::L1_MBP {
792 return Err(InvalidBookOperation::Update(self.book_type));
793 }
794
795 if cfg!(debug_assertions) && quote.bid_price > quote.ask_price {
797 log::warn!(
798 "Quote has crossed prices: bid={}, ask={} for {}",
799 quote.bid_price,
800 quote.ask_price,
801 self.instrument_id
802 );
803 }
804
805 let bid = BookOrder::new(
806 OrderSide::Buy,
807 quote.bid_price,
808 quote.bid_size,
809 OrderSide::Buy as u64,
810 );
811
812 let ask = BookOrder::new(
813 OrderSide::Sell,
814 quote.ask_price,
815 quote.ask_size,
816 OrderSide::Sell as u64,
817 );
818
819 self.update_book_bid(bid, quote.ts_event);
820 self.update_book_ask(ask, quote.ts_event);
821
822 self.increment(self.sequence.saturating_add(1), quote.ts_event);
823
824 Ok(())
825 }
826
827 pub fn update_trade_tick(&mut self, trade: &TradeTick) -> Result<(), InvalidBookOperation> {
833 if self.book_type != BookType::L1_MBP {
834 return Err(InvalidBookOperation::Update(self.book_type));
835 }
836
837 debug_assert!(
839 trade.price.raw != PRICE_UNDEF && trade.price.raw != PRICE_ERROR,
840 "Trade has invalid/uninitialized price: {}",
841 trade.price
842 );
843
844 debug_assert!(
846 trade.size.is_positive(),
847 "Trade has non-positive size: {}",
848 trade.size
849 );
850
851 let bid = BookOrder::new(
852 OrderSide::Buy,
853 trade.price,
854 trade.size,
855 OrderSide::Buy as u64,
856 );
857
858 let ask = BookOrder::new(
859 OrderSide::Sell,
860 trade.price,
861 trade.size,
862 OrderSide::Sell as u64,
863 );
864
865 self.update_book_bid(bid, trade.ts_event);
866 self.update_book_ask(ask, trade.ts_event);
867
868 self.increment(self.sequence.saturating_add(1), trade.ts_event);
869
870 Ok(())
871 }
872
873 fn update_book_bid(&mut self, order: BookOrder, ts_event: UnixNanos) {
874 if let Some(top_bids) = self.bids.top()
875 && let Some(top_bid) = top_bids.first()
876 {
877 self.bids.remove_order(top_bid.order_id, 0, ts_event);
878 }
879 self.bids.add(order, 0); }
881
882 fn update_book_ask(&mut self, order: BookOrder, ts_event: UnixNanos) {
883 if let Some(top_asks) = self.asks.top()
884 && let Some(top_ask) = top_asks.first()
885 {
886 self.asks.remove_order(top_ask.order_id, 0, ts_event);
887 }
888 self.asks.add(order, 0); }
890}
891
892fn filter_quantities(
893 public_map: &mut IndexMap<Decimal, Decimal>,
894 own_map: IndexMap<Decimal, Decimal>,
895) {
896 for (price, own_size) in own_map {
897 if let Some(public_size) = public_map.get_mut(&price) {
898 *public_size = (*public_size - own_size).max(Decimal::ZERO);
899
900 if *public_size == Decimal::ZERO {
901 public_map.shift_remove(&price);
902 }
903 }
904 }
905}
906
907fn group_levels<'a>(
908 levels_iter: impl Iterator<Item = &'a BookLevel>,
909 group_size: Decimal,
910 depth: Option<usize>,
911 is_bid: bool,
912) -> IndexMap<Decimal, Decimal> {
913 if group_size <= Decimal::ZERO {
914 log::error!("Invalid group_size: {group_size}, must be positive; returning empty map");
915 return IndexMap::new();
916 }
917
918 let mut levels = IndexMap::new();
919 let depth = depth.unwrap_or(usize::MAX);
920
921 for level in levels_iter {
922 let price = level.price.value.as_decimal();
923 let grouped_price = if is_bid {
924 (price / group_size).floor() * group_size
925 } else {
926 (price / group_size).ceil() * group_size
927 };
928 let size = level.size_decimal();
929
930 levels
931 .entry(grouped_price)
932 .and_modify(|total| *total += size)
933 .or_insert(size);
934
935 if levels.len() > depth {
936 levels.pop();
937 break;
938 }
939 }
940
941 levels
942}