1use std::{
19 cmp::Ordering,
20 collections::{BTreeMap, HashMap},
21 fmt::{Debug, Display, Formatter},
22};
23
24use nautilus_core::UnixNanos;
25
26use crate::{
27 data::order::{BookOrder, OrderId},
28 enums::{BookType, OrderSideSpecified},
29 orderbook::BookLevel,
30 types::{Price, Quantity},
31};
32
33#[derive(Clone, Copy, Debug, Eq)]
45#[cfg_attr(
46 feature = "python",
47 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
48)]
49pub struct BookPrice {
50 pub value: Price,
51 pub side: OrderSideSpecified,
52}
53
54impl BookPrice {
55 #[must_use]
57 pub fn new(value: Price, side: OrderSideSpecified) -> Self {
58 Self { value, side }
59 }
60}
61
62impl PartialOrd for BookPrice {
63 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64 Some(self.cmp(other))
65 }
66}
67
68impl PartialEq for BookPrice {
69 fn eq(&self, other: &Self) -> bool {
70 self.side == other.side && self.value == other.value
71 }
72}
73
74impl Ord for BookPrice {
75 fn cmp(&self, other: &Self) -> Ordering {
76 assert_eq!(
77 self.side, other.side,
78 "BookPrice compared across sides: {:?} vs {:?}",
79 self.side, other.side
80 );
81
82 match self.side.cmp(&other.side) {
83 Ordering::Equal => match self.side {
84 OrderSideSpecified::Buy => other.value.cmp(&self.value),
85 OrderSideSpecified::Sell => self.value.cmp(&other.value),
86 },
87 non_equal => non_equal,
88 }
89 }
90}
91
92impl Display for BookPrice {
93 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
94 write!(f, "{}", self.value)
95 }
96}
97
98#[derive(Clone, Debug)]
100pub(crate) struct BookLadder {
101 pub side: OrderSideSpecified,
102 pub book_type: BookType,
103 pub levels: BTreeMap<BookPrice, BookLevel>,
104 pub cache: HashMap<u64, BookPrice>,
105}
106
107impl BookLadder {
108 #[must_use]
110 pub fn new(side: OrderSideSpecified, book_type: BookType) -> Self {
111 Self {
112 side,
113 book_type,
114 levels: BTreeMap::new(),
115 cache: HashMap::new(),
116 }
117 }
118
119 #[must_use]
121 pub fn len(&self) -> usize {
122 self.levels.len()
123 }
124
125 #[must_use]
127 #[allow(dead_code, reason = "Used in tests")]
128 pub fn is_empty(&self) -> bool {
129 self.levels.is_empty()
130 }
131
132 #[allow(dead_code, reason = "Used in tests")]
133 pub fn add_bulk(&mut self, orders: Vec<BookOrder>) {
135 for order in orders {
136 self.add(order);
137 }
138 }
139
140 pub fn clear(&mut self) {
142 self.levels.clear();
143 self.cache.clear();
144 }
145
146 pub fn add(&mut self, order: BookOrder) {
148 if self.book_type == BookType::L1_MBP && !self.handle_l1_add(&order) {
149 return;
150 }
151
152 if self.book_type != BookType::L1_MBP && !order.size.is_positive() {
153 log::warn!(
154 "Attempted to add order with non-positive size: order_id={order_id}, size={size}, ignoring",
155 order_id = order.order_id,
156 size = order.size
157 );
158 return;
159 }
160
161 let book_price = order.to_book_price();
162 self.cache.insert(order.order_id, book_price);
163
164 match self.levels.get_mut(&book_price) {
165 Some(level) => {
166 level.add(order);
167 }
168 None => {
169 let level = BookLevel::from_order(order);
170 self.levels.insert(book_price, level);
171 }
172 }
173 }
174
175 fn handle_l1_add(&mut self, order: &BookOrder) -> bool {
183 if !order.size.is_positive() {
185 if let Some(&old_price) = self.cache.get(&order.order_id) {
186 if let Some(old_level) = self.levels.get_mut(&old_price) {
187 old_level.delete(order);
188 if old_level.is_empty() {
189 self.levels.remove(&old_price);
190 }
191 }
192 self.cache.remove(&order.order_id);
193 }
194 log::debug!(
195 "L1 zero-size add cleared top of book: order_id={order_id}, side={side:?}",
196 order_id = order.order_id,
197 side = self.side
198 );
199 return false;
200 }
201
202 if let Some(&old_price) = self.cache.get(&order.order_id) {
204 let book_price = order.to_book_price();
205 if old_price != book_price {
206 if let Some(old_level) = self.levels.get_mut(&old_price) {
208 old_level.delete(order);
209 if old_level.is_empty() {
210 self.levels.remove(&old_price);
211 }
212 }
213 }
214 }
215
216 true
217 }
218
219 pub fn update(&mut self, order: BookOrder) {
221 let price = self.cache.get(&order.order_id).copied();
222 if let Some(price) = price
223 && let Some(level) = self.levels.get_mut(&price)
224 {
225 if order.price == level.price.value {
226 let level_len_before = level.len();
227 level.update(order);
228
229 if order.size.raw == 0 {
231 self.cache.remove(&order.order_id);
232 debug_assert_eq!(
233 level.len(),
234 level_len_before - 1,
235 "Level should have one less order after zero-size update"
236 );
237 } else {
238 debug_assert!(
239 self.cache.contains_key(&order.order_id),
240 "Cache should still contain order {0} after update",
241 order.order_id
242 );
243 }
244
245 if level.is_empty() {
246 self.levels.remove(&price);
247 debug_assert!(
248 !self.cache.values().any(|p| *p == price),
249 "Cache should not contain removed price level {price:?}"
250 );
251 }
252
253 debug_assert_eq!(
254 self.cache.len(),
255 self.levels.values().map(|level| level.len()).sum::<usize>(),
256 "Cache size should equal total orders across all levels"
257 );
258 return;
259 }
260
261 self.cache.remove(&order.order_id);
263 level.delete(&order);
264
265 if level.is_empty() {
266 self.levels.remove(&price);
267 debug_assert!(
268 !self.cache.values().any(|p| *p == price),
269 "Cache should not contain removed price level {price:?}"
270 );
271 }
272 }
273
274 if order.size.is_positive() {
276 self.add(order);
277 }
278
279 debug_assert_eq!(
281 self.cache.len(),
282 self.levels.values().map(|level| level.len()).sum::<usize>(),
283 "Cache size should equal total orders across all levels"
284 );
285 }
286
287 pub fn delete(&mut self, order: BookOrder, sequence: u64, ts_event: UnixNanos) {
289 self.remove_order(order.order_id, sequence, ts_event);
290 }
291
292 pub fn remove_order(&mut self, order_id: OrderId, sequence: u64, ts_event: UnixNanos) {
294 if let Some(price) = self.cache.get(&order_id).copied()
295 && let Some(level) = self.levels.get_mut(&price)
296 {
297 if level.orders.contains_key(&order_id) {
299 let level_len_before = level.len();
300
301 self.cache.remove(&order_id);
303 level.remove_by_id(order_id, sequence, ts_event);
304
305 debug_assert_eq!(
306 level.len(),
307 level_len_before - 1,
308 "Level should have exactly one less order after removal"
309 );
310
311 if level.is_empty() {
312 self.levels.remove(&price);
313 debug_assert!(
314 !self.cache.values().any(|p| *p == price),
315 "Cache should not contain removed price level {price:?}"
316 );
317 }
318 }
319 }
320
321 debug_assert_eq!(
323 self.cache.len(),
324 self.levels.values().map(|level| level.len()).sum::<usize>(),
325 "Cache size should equal total orders across all levels"
326 );
327 }
328
329 pub fn remove_level(&mut self, price: BookPrice) -> Option<BookLevel> {
331 if let Some(level) = self.levels.remove(&price) {
332 for order_id in level.orders.keys() {
334 self.cache.remove(order_id);
335 }
336
337 debug_assert_eq!(
338 self.cache.len(),
339 self.levels.values().map(|level| level.len()).sum::<usize>(),
340 "Cache size should equal total orders across all levels"
341 );
342
343 Some(level)
344 } else {
345 None
346 }
347 }
348
349 #[must_use]
351 #[allow(dead_code, reason = "Used in tests")]
352 pub fn sizes(&self) -> f64 {
353 self.levels.values().map(BookLevel::size).sum()
354 }
355
356 #[must_use]
358 #[allow(dead_code, reason = "Used in tests")]
359 pub fn exposures(&self) -> f64 {
360 self.levels.values().map(BookLevel::exposure).sum()
361 }
362
363 #[must_use]
365 pub fn top(&self) -> Option<&BookLevel> {
366 match self.levels.iter().next() {
367 Some((_, l)) => Option::Some(l),
368 None => Option::None,
369 }
370 }
371
372 #[must_use]
375 pub fn simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
376 let is_reversed = self.side == OrderSideSpecified::Buy;
377 let mut fills = Vec::new();
378 let mut cumulative_denominator = Quantity::zero(order.size.precision);
379 let target = order.size;
380
381 for level in self.levels.values() {
382 if (is_reversed && level.price.value < order.price)
383 || (!is_reversed && level.price.value > order.price)
384 {
385 break;
386 }
387
388 for book_order in level.orders.values() {
389 let current = book_order.size;
390 if cumulative_denominator + current >= target {
391 let remainder = target - cumulative_denominator;
393 if remainder.is_positive() {
394 fills.push((book_order.price, remainder));
395 }
396 return fills;
397 }
398
399 fills.push((book_order.price, current));
401 cumulative_denominator += current;
402 }
403 }
404
405 fills
406 }
407}
408
409impl Display for BookLadder {
410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411 writeln!(f, "{}(side={})", stringify!(BookLadder), self.side)?;
412 for (price, level) in &self.levels {
413 writeln!(f, " {} -> {} orders", price, level.len())?;
414 }
415 Ok(())
416 }
417}
418
419#[cfg(test)]
423mod tests {
424 use rstest::rstest;
425
426 use crate::{
427 data::order::BookOrder,
428 enums::{BookType, OrderSide, OrderSideSpecified},
429 orderbook::ladder::{BookLadder, BookPrice},
430 types::{Price, Quantity},
431 };
432
433 #[rstest]
434 fn test_is_empty() {
435 let ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
436 assert!(ladder.is_empty(), "A new ladder should be empty");
437 }
438
439 #[rstest]
440 fn test_is_empty_after_add() {
441 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
442 assert!(ladder.is_empty(), "Ladder should start empty");
443 let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(100), 1);
444 ladder.add(order);
445 assert!(
446 !ladder.is_empty(),
447 "Ladder should not be empty after adding an order"
448 );
449 }
450
451 #[rstest]
452 fn test_add_bulk_empty() {
453 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
454 ladder.add_bulk(vec![]);
455 assert!(
456 ladder.is_empty(),
457 "Adding an empty vector should leave the ladder empty"
458 );
459 }
460
461 #[rstest]
462 fn test_add_bulk_orders() {
463 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
464 let orders = vec![
465 BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1),
466 BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2),
467 BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(50), 3),
468 ];
469 ladder.add_bulk(orders);
470 assert_eq!(ladder.len(), 1, "Ladder should have one price level");
472 let orders_in_level = ladder.top().unwrap().get_orders();
473 assert_eq!(
474 orders_in_level.len(),
475 3,
476 "Price level should contain all bulk orders"
477 );
478 }
479
480 #[rstest]
481 fn test_book_price_bid_sorting() {
482 let mut bid_prices = [
483 BookPrice::new(Price::from("2.0"), OrderSideSpecified::Buy),
484 BookPrice::new(Price::from("4.0"), OrderSideSpecified::Buy),
485 BookPrice::new(Price::from("1.0"), OrderSideSpecified::Buy),
486 BookPrice::new(Price::from("3.0"), OrderSideSpecified::Buy),
487 ];
488 bid_prices.sort();
489 assert_eq!(bid_prices[0].value, Price::from("4.0"));
490 }
491
492 #[rstest]
493 fn test_book_price_ask_sorting() {
494 let mut ask_prices = [
495 BookPrice::new(Price::from("2.0"), OrderSideSpecified::Sell),
496 BookPrice::new(Price::from("4.0"), OrderSideSpecified::Sell),
497 BookPrice::new(Price::from("1.0"), OrderSideSpecified::Sell),
498 BookPrice::new(Price::from("3.0"), OrderSideSpecified::Sell),
499 ];
500
501 ask_prices.sort();
502 assert_eq!(ask_prices[0].value, Price::from("1.0"));
503 }
504
505 #[rstest]
506 fn test_add_single_order() {
507 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
508 let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 0);
509
510 ladder.add(order);
511 assert_eq!(ladder.len(), 1);
512 assert_eq!(ladder.sizes(), 20.0);
513 assert_eq!(ladder.exposures(), 200.0);
514 assert_eq!(ladder.top().unwrap().price.value, Price::from("10.0"));
515 }
516
517 #[rstest]
518 fn test_add_multiple_buy_orders() {
519 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
520 let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 0);
521 let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(30), 1);
522 let order3 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(50), 2);
523 let order4 = BookOrder::new(OrderSide::Buy, Price::from("8.00"), Quantity::from(200), 3);
524
525 ladder.add_bulk(vec![order1, order2, order3, order4]);
526 assert_eq!(ladder.len(), 3);
527 assert_eq!(ladder.sizes(), 300.0);
528 assert_eq!(ladder.exposures(), 2520.0);
529 assert_eq!(ladder.top().unwrap().price.value, Price::from("10.0"));
530 }
531
532 #[rstest]
533 fn test_add_multiple_sell_orders() {
534 let mut ladder = BookLadder::new(OrderSideSpecified::Sell, BookType::L3_MBO);
535 let order1 = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 0);
536 let order2 = BookOrder::new(OrderSide::Sell, Price::from("12.00"), Quantity::from(30), 1);
537 let order3 = BookOrder::new(OrderSide::Sell, Price::from("12.00"), Quantity::from(50), 2);
538 let order4 = BookOrder::new(
539 OrderSide::Sell,
540 Price::from("13.00"),
541 Quantity::from(200),
542 0,
543 );
544
545 ladder.add_bulk(vec![order1, order2, order3, order4]);
546 assert_eq!(ladder.len(), 3);
547 assert_eq!(ladder.sizes(), 300.0);
548 assert_eq!(ladder.exposures(), 3780.0);
549 assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
550 }
551
552 #[rstest]
553 fn test_add_to_same_price_level() {
554 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
555 let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
556 let order2 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2);
557
558 ladder.add(order1);
559 ladder.add(order2);
560
561 assert_eq!(ladder.len(), 1);
562 assert_eq!(ladder.sizes(), 50.0);
563 assert_eq!(ladder.exposures(), 500.0);
564 }
565
566 #[rstest]
567 fn test_add_descending_buy_orders() {
568 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
569 let order1 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(20), 1);
570 let order2 = BookOrder::new(OrderSide::Buy, Price::from("8.00"), Quantity::from(30), 2);
571
572 ladder.add(order1);
573 ladder.add(order2);
574
575 assert_eq!(ladder.top().unwrap().price.value, Price::from("9.00"));
576 }
577
578 #[rstest]
579 fn test_add_ascending_sell_orders() {
580 let mut ladder = BookLadder::new(OrderSideSpecified::Sell, BookType::L3_MBO);
581 let order1 = BookOrder::new(OrderSide::Sell, Price::from("8.00"), Quantity::from(20), 1);
582 let order2 = BookOrder::new(OrderSide::Sell, Price::from("9.00"), Quantity::from(30), 2);
583
584 ladder.add(order1);
585 ladder.add(order2);
586
587 assert_eq!(ladder.top().unwrap().price.value, Price::from("8.00"));
588 }
589
590 #[rstest]
591 fn test_update_buy_order_price() {
592 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
593 let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
594
595 ladder.add(order);
596 let order = BookOrder::new(OrderSide::Buy, Price::from("11.10"), Quantity::from(20), 1);
597
598 ladder.update(order);
599 assert_eq!(ladder.len(), 1);
600 assert_eq!(ladder.sizes(), 20.0);
601 assert_eq!(ladder.exposures(), 222.0);
602 assert_eq!(ladder.top().unwrap().price.value, Price::from("11.1"));
603 }
604
605 #[rstest]
606 fn test_update_sell_order_price() {
607 let mut ladder = BookLadder::new(OrderSideSpecified::Sell, BookType::L3_MBO);
608 let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 1);
609
610 ladder.add(order);
611
612 let order = BookOrder::new(OrderSide::Sell, Price::from("11.10"), Quantity::from(20), 1);
613
614 ladder.update(order);
615 assert_eq!(ladder.len(), 1);
616 assert_eq!(ladder.sizes(), 20.0);
617 assert_eq!(ladder.exposures(), 222.0);
618 assert_eq!(ladder.top().unwrap().price.value, Price::from("11.1"));
619 }
620
621 #[rstest]
622 fn test_update_buy_order_size() {
623 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
624 let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
625
626 ladder.add(order);
627
628 let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(10), 1);
629
630 ladder.update(order);
631 assert_eq!(ladder.len(), 1);
632 assert_eq!(ladder.sizes(), 10.0);
633 assert_eq!(ladder.exposures(), 110.0);
634 assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
635 }
636
637 #[rstest]
638 fn test_update_sell_order_size() {
639 let mut ladder = BookLadder::new(OrderSideSpecified::Sell, BookType::L3_MBO);
640 let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(20), 1);
641
642 ladder.add(order);
643
644 let order = BookOrder::new(OrderSide::Sell, Price::from("11.00"), Quantity::from(10), 1);
645
646 ladder.update(order);
647 assert_eq!(ladder.len(), 1);
648 assert_eq!(ladder.sizes(), 10.0);
649 assert_eq!(ladder.exposures(), 110.0);
650 assert_eq!(ladder.top().unwrap().price.value, Price::from("11.0"));
651 }
652
653 #[rstest]
654 fn test_delete_non_existing_order() {
655 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
656 let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
657
658 ladder.delete(order, 0, 0.into());
659
660 assert_eq!(ladder.len(), 0);
661 }
662
663 #[rstest]
664 fn test_delete_buy_order() {
665 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
666 let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(20), 1);
667
668 ladder.add(order);
669
670 let order = BookOrder::new(OrderSide::Buy, Price::from("11.00"), Quantity::from(10), 1);
671
672 ladder.delete(order, 0, 0.into());
673 assert_eq!(ladder.len(), 0);
674 assert_eq!(ladder.sizes(), 0.0);
675 assert_eq!(ladder.exposures(), 0.0);
676 assert_eq!(ladder.top(), None);
677 }
678
679 #[rstest]
680 fn test_delete_sell_order() {
681 let mut ladder = BookLadder::new(OrderSideSpecified::Sell, BookType::L3_MBO);
682 let order = BookOrder::new(OrderSide::Sell, Price::from("10.00"), Quantity::from(10), 1);
683
684 ladder.add(order);
685
686 let order = BookOrder::new(OrderSide::Sell, Price::from("10.00"), Quantity::from(10), 1);
687
688 ladder.delete(order, 0, 0.into());
689 assert_eq!(ladder.len(), 0);
690 assert_eq!(ladder.sizes(), 0.0);
691 assert_eq!(ladder.exposures(), 0.0);
692 assert_eq!(ladder.top(), None);
693 }
694
695 #[rstest]
696 fn test_ladder_sizes_empty() {
697 let ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
698 assert_eq!(
699 ladder.sizes(),
700 0.0,
701 "An empty ladder should have total size 0.0"
702 );
703 }
704
705 #[rstest]
706 fn test_ladder_exposures_empty() {
707 let ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
708 assert_eq!(
709 ladder.exposures(),
710 0.0,
711 "An empty ladder should have total exposure 0.0"
712 );
713 }
714
715 #[rstest]
716 fn test_ladder_sizes() {
717 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
718 let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
719 let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.50"), Quantity::from(30), 2);
720 ladder.add(order1);
721 ladder.add(order2);
722
723 let expected_size = 20.0 + 30.0;
724 assert_eq!(
725 ladder.sizes(),
726 expected_size,
727 "Ladder total size should match the sum of order sizes"
728 );
729 }
730
731 #[rstest]
732 fn test_ladder_exposures() {
733 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
734 let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
735 let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.50"), Quantity::from(30), 2);
736 ladder.add(order1);
737 ladder.add(order2);
738
739 let expected_exposure = 10.00 * 20.0 + 9.50 * 30.0;
740 assert_eq!(
741 ladder.exposures(),
742 expected_exposure,
743 "Ladder total exposure should match the sum of individual exposures"
744 );
745 }
746
747 #[rstest]
748 fn test_iter_returns_fifo() {
749 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
750 let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
751 let order2 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(30), 2);
752 ladder.add(order1);
753 ladder.add(order2);
754 let orders: Vec<BookOrder> = ladder.top().unwrap().iter().copied().collect();
755 assert_eq!(
756 orders,
757 vec![order1, order2],
758 "Iterator should return orders in FIFO order"
759 );
760 }
761
762 #[rstest]
763 fn test_update_missing_order_inserts() {
764 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
765 let order = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
766 ladder.update(order);
768 assert_eq!(
769 ladder.len(),
770 1,
771 "Ladder should have one level after upsert update"
772 );
773 let orders = ladder.top().unwrap().get_orders();
774 assert_eq!(
775 orders.len(),
776 1,
777 "Price level should contain the inserted order"
778 );
779 assert_eq!(orders[0], order, "The inserted order should match");
780 }
781
782 #[rstest]
783 fn test_cache_consistency_after_operations() {
784 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
785 let order1 = BookOrder::new(OrderSide::Buy, Price::from("10.00"), Quantity::from(20), 1);
786 let order2 = BookOrder::new(OrderSide::Buy, Price::from("9.00"), Quantity::from(30), 2);
787 ladder.add(order1);
788 ladder.add(order2);
789
790 for (order_id, price) in &ladder.cache {
792 let level = ladder
793 .levels
794 .get(price)
795 .expect("Every price in the cache should have a corresponding level");
796 assert!(
797 level.orders.contains_key(order_id),
798 "Order id {order_id} should be present in the level for price {price}",
799 );
800 }
801 }
802
803 #[rstest]
804 fn test_simulate_fills_with_empty_book() {
805 let ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
806 let order = BookOrder::new(OrderSide::Buy, Price::max(2), Quantity::from(500), 1);
807
808 let fills = ladder.simulate_fills(&order);
809
810 assert!(fills.is_empty());
811 }
812
813 #[rstest]
814 #[case(OrderSide::Buy, Price::max(2), OrderSideSpecified::Sell)]
815 #[case(OrderSide::Sell, Price::min(2), OrderSideSpecified::Buy)]
816 fn test_simulate_order_fills_with_no_size(
817 #[case] side: OrderSide,
818 #[case] price: Price,
819 #[case] ladder_side: OrderSideSpecified,
820 ) {
821 let ladder = BookLadder::new(ladder_side, BookType::L3_MBO);
822 let order = BookOrder {
823 price, size: Quantity::from(500),
825 side,
826 order_id: 2,
827 };
828
829 let fills = ladder.simulate_fills(&order);
830
831 assert!(fills.is_empty());
832 }
833
834 #[rstest]
835 #[case(OrderSide::Buy, OrderSideSpecified::Sell, Price::from("60.0"))]
836 #[case(OrderSide::Sell, OrderSideSpecified::Buy, Price::from("40.0"))]
837 fn test_simulate_order_fills_buy_when_far_from_market(
838 #[case] order_side: OrderSide,
839 #[case] ladder_side: OrderSideSpecified,
840 #[case] ladder_price: Price,
841 ) {
842 let mut ladder = BookLadder::new(ladder_side, BookType::L3_MBO);
843
844 ladder.add(BookOrder {
845 price: ladder_price,
846 size: Quantity::from(100),
847 side: ladder_side.as_order_side(),
848 order_id: 1,
849 });
850
851 let order = BookOrder {
852 price: Price::from("50.00"),
853 size: Quantity::from(500),
854 side: order_side,
855 order_id: 2,
856 };
857
858 let fills = ladder.simulate_fills(&order);
859
860 assert!(fills.is_empty());
861 }
862
863 #[rstest]
864 fn test_simulate_order_fills_sell_when_far_from_market() {
865 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
866
867 ladder.add(BookOrder {
868 price: Price::from("100.00"),
869 size: Quantity::from(100),
870 side: OrderSide::Buy,
871 order_id: 1,
872 });
873
874 let order = BookOrder {
875 price: Price::from("150.00"), size: Quantity::from(500),
877 side: OrderSide::Buy,
878 order_id: 2,
879 };
880
881 let fills = ladder.simulate_fills(&order);
882
883 assert!(fills.is_empty());
884 }
885
886 #[rstest]
887 fn test_simulate_order_fills_buy() {
888 let mut ladder = BookLadder::new(OrderSideSpecified::Sell, BookType::L3_MBO);
889
890 ladder.add_bulk(vec![
891 BookOrder {
892 price: Price::from("100.00"),
893 size: Quantity::from(100),
894 side: OrderSide::Sell,
895 order_id: 1,
896 },
897 BookOrder {
898 price: Price::from("101.00"),
899 size: Quantity::from(200),
900 side: OrderSide::Sell,
901 order_id: 2,
902 },
903 BookOrder {
904 price: Price::from("102.00"),
905 size: Quantity::from(400),
906 side: OrderSide::Sell,
907 order_id: 3,
908 },
909 ]);
910
911 let order = BookOrder {
912 price: Price::max(2), size: Quantity::from(500),
914 side: OrderSide::Buy,
915 order_id: 4,
916 };
917
918 let fills = ladder.simulate_fills(&order);
919
920 assert_eq!(fills.len(), 3);
921
922 let (price1, size1) = fills[0];
923 assert_eq!(price1, Price::from("100.00"));
924 assert_eq!(size1, Quantity::from(100));
925
926 let (price2, size2) = fills[1];
927 assert_eq!(price2, Price::from("101.00"));
928 assert_eq!(size2, Quantity::from(200));
929
930 let (price3, size3) = fills[2];
931 assert_eq!(price3, Price::from("102.00"));
932 assert_eq!(size3, Quantity::from(200));
933 }
934
935 #[rstest]
936 fn test_simulate_order_fills_sell() {
937 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
938
939 ladder.add_bulk(vec![
940 BookOrder {
941 price: Price::from("102.00"),
942 size: Quantity::from(100),
943 side: OrderSide::Buy,
944 order_id: 1,
945 },
946 BookOrder {
947 price: Price::from("101.00"),
948 size: Quantity::from(200),
949 side: OrderSide::Buy,
950 order_id: 2,
951 },
952 BookOrder {
953 price: Price::from("100.00"),
954 size: Quantity::from(400),
955 side: OrderSide::Buy,
956 order_id: 3,
957 },
958 ]);
959
960 let order = BookOrder {
961 price: Price::min(2), size: Quantity::from(500),
963 side: OrderSide::Sell,
964 order_id: 4,
965 };
966
967 let fills = ladder.simulate_fills(&order);
968
969 assert_eq!(fills.len(), 3);
970
971 let (price1, size1) = fills[0];
972 assert_eq!(price1, Price::from("102.00"));
973 assert_eq!(size1, Quantity::from(100));
974
975 let (price2, size2) = fills[1];
976 assert_eq!(price2, Price::from("101.00"));
977 assert_eq!(size2, Quantity::from(200));
978
979 let (price3, size3) = fills[2];
980 assert_eq!(price3, Price::from("100.00"));
981 assert_eq!(size3, Quantity::from(200));
982 }
983
984 #[rstest]
985 fn test_simulate_order_fills_sell_with_size_at_limit_of_precision() {
986 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
987
988 ladder.add_bulk(vec![
989 BookOrder {
990 price: Price::from("102.00"),
991 size: Quantity::from("100.000000000"),
992 side: OrderSide::Buy,
993 order_id: 1,
994 },
995 BookOrder {
996 price: Price::from("101.00"),
997 size: Quantity::from("200.000000000"),
998 side: OrderSide::Buy,
999 order_id: 2,
1000 },
1001 BookOrder {
1002 price: Price::from("100.00"),
1003 size: Quantity::from("400.000000000"),
1004 side: OrderSide::Buy,
1005 order_id: 3,
1006 },
1007 ]);
1008
1009 let order = BookOrder {
1010 price: Price::min(2), size: Quantity::from("699.999999999"), side: OrderSide::Sell,
1013 order_id: 4,
1014 };
1015
1016 let fills = ladder.simulate_fills(&order);
1017
1018 assert_eq!(fills.len(), 3);
1019
1020 let (price1, size1) = fills[0];
1021 assert_eq!(price1, Price::from("102.00"));
1022 assert_eq!(size1, Quantity::from("100.000000000"));
1023
1024 let (price2, size2) = fills[1];
1025 assert_eq!(price2, Price::from("101.00"));
1026 assert_eq!(size2, Quantity::from("200.000000000"));
1027
1028 let (price3, size3) = fills[2];
1029 assert_eq!(price3, Price::from("100.00"));
1030 assert_eq!(size3, Quantity::from("399.999999999"));
1031 }
1032
1033 #[rstest]
1034 fn test_boundary_prices() {
1035 let max_price = Price::max(1);
1036 let min_price = Price::min(1);
1037
1038 let mut ladder_buy = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
1039 let mut ladder_sell = BookLadder::new(OrderSideSpecified::Sell, BookType::L3_MBO);
1040
1041 let order_buy = BookOrder::new(OrderSide::Buy, min_price, Quantity::from(1), 1);
1042 let order_sell = BookOrder::new(OrderSide::Sell, max_price, Quantity::from(1), 1);
1043
1044 ladder_buy.add(order_buy);
1045 ladder_sell.add(order_sell);
1046
1047 assert_eq!(ladder_buy.top().unwrap().price.value, min_price);
1048 assert_eq!(ladder_sell.top().unwrap().price.value, max_price);
1049 }
1050
1051 #[rstest]
1052 fn test_l1_ghost_levels_regression() {
1053 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L1_MBP);
1057 let side_constant = OrderSide::Buy as u64;
1058
1059 let order1 = BookOrder {
1061 side: OrderSide::Buy,
1062 price: Price::from("100.00"),
1063 size: Quantity::from(50),
1064 order_id: side_constant,
1065 };
1066 ladder.add(order1);
1067
1068 assert_eq!(ladder.len(), 1, "Should have one level after first add");
1069 assert_eq!(
1070 ladder.top().unwrap().price.value,
1071 Price::from("100.00"),
1072 "Top level should be at 100.00"
1073 );
1074
1075 let order2 = BookOrder {
1078 side: OrderSide::Buy,
1079 price: Price::from("101.00"),
1080 size: Quantity::from(60),
1081 order_id: side_constant, };
1083 ladder.add(order2);
1084
1085 assert_eq!(
1087 ladder.len(),
1088 1,
1089 "Should still have only one level after L1 update"
1090 );
1091 assert_eq!(
1092 ladder.top().unwrap().price.value,
1093 Price::from("101.00"),
1094 "Top level should be at new price 101.00"
1095 );
1096
1097 let prices: Vec<Price> = ladder.levels.keys().map(|bp| bp.value).collect();
1099 assert_eq!(
1100 prices,
1101 vec![Price::from("101.00")],
1102 "Should only have the new price level"
1103 );
1104
1105 let order3 = BookOrder {
1107 side: OrderSide::Buy,
1108 price: Price::from("100.50"),
1109 size: Quantity::from(70),
1110 order_id: side_constant,
1111 };
1112 ladder.add(order3);
1113
1114 assert_eq!(
1115 ladder.len(),
1116 1,
1117 "Should still have only one level after second update"
1118 );
1119 assert_eq!(
1120 ladder.top().unwrap().price.value,
1121 Price::from("100.50"),
1122 "Top level should be at new price 100.50"
1123 );
1124 }
1125
1126 #[rstest]
1127 fn test_l2_orders_not_affected_by_l1_fix() {
1128 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
1130
1131 let order1 = BookOrder {
1133 side: OrderSide::Buy,
1134 price: Price::from("100.00"),
1135 size: Quantity::from(50),
1136 order_id: Price::from("100.00").raw as u64,
1137 };
1138 ladder.add(order1);
1139
1140 let order2 = BookOrder {
1141 side: OrderSide::Buy,
1142 price: Price::from("99.00"),
1143 size: Quantity::from(60),
1144 order_id: Price::from("99.00").raw as u64,
1145 };
1146 ladder.add(order2);
1147
1148 assert_eq!(ladder.len(), 2, "L2 orders should create multiple levels");
1150 assert_eq!(
1151 ladder.top().unwrap().price.value,
1152 Price::from("100.00"),
1153 "Top level should be best bid"
1154 );
1155 }
1156
1157 #[rstest]
1158 fn test_zero_size_l1_order_clears_top() {
1159 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L1_MBP);
1162 let side_constant = OrderSide::Buy as u64;
1163
1164 let order1 = BookOrder {
1166 side: OrderSide::Buy,
1167 price: Price::from("100.00"),
1168 size: Quantity::from(50),
1169 order_id: side_constant,
1170 };
1171 ladder.add(order1);
1172
1173 assert_eq!(ladder.len(), 1);
1174 assert_eq!(ladder.top().unwrap().price.value, Price::from("100.00"));
1175 assert!(ladder.top().unwrap().first().is_some());
1176
1177 let order2 = BookOrder {
1179 side: OrderSide::Buy,
1180 price: Price::from("101.00"),
1181 size: Quantity::zero(9), order_id: side_constant,
1183 };
1184 ladder.add(order2);
1185
1186 assert_eq!(ladder.len(), 0, "Zero-size L1 add should clear the book");
1188 assert!(ladder.top().is_none(), "Book should be empty after clear");
1189
1190 assert!(
1192 ladder.cache.is_empty(),
1193 "Cache should be empty after L1 clear"
1194 );
1195 }
1196
1197 #[rstest]
1198 fn test_zero_size_order_to_empty_ladder() {
1199 let mut ladder = BookLadder::new(OrderSideSpecified::Sell, BookType::L1_MBP);
1201 let side_constant = OrderSide::Sell as u64;
1202
1203 let order = BookOrder {
1204 side: OrderSide::Sell,
1205 price: Price::from("100.00"),
1206 size: Quantity::zero(9),
1207 order_id: side_constant,
1208 };
1209 ladder.add(order);
1210
1211 assert_eq!(ladder.len(), 0, "Empty ladder should remain empty");
1212 assert!(ladder.top().is_none(), "Top should be None");
1213 assert!(
1214 ladder.cache.is_empty(),
1215 "Cache should remain empty for zero-size add"
1216 );
1217 }
1218
1219 #[rstest]
1220 fn test_l3_order_id_collision_no_ghost_levels() {
1221 let mut ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
1224
1225 let order1 = BookOrder {
1227 side: OrderSide::Buy,
1228 price: Price::from("100.00"),
1229 size: Quantity::from(50),
1230 order_id: 1, };
1232 ladder.add(order1);
1233
1234 assert_eq!(ladder.len(), 1);
1235
1236 let order2 = BookOrder {
1239 side: OrderSide::Buy,
1240 price: Price::from("99.00"),
1241 size: Quantity::from(60),
1242 order_id: 1, };
1244 ladder.add(order2);
1245
1246 assert_eq!(
1248 ladder.len(),
1249 2,
1250 "L3 should allow order ID 1 at multiple price levels"
1251 );
1252
1253 let prices: Vec<Price> = ladder.levels.keys().map(|bp| bp.value).collect();
1254 assert!(
1255 prices.contains(&Price::from("100.00")),
1256 "Level at 100.00 should still exist"
1257 );
1258 assert!(
1259 prices.contains(&Price::from("99.00")),
1260 "Level at 99.00 should exist"
1261 );
1262 }
1263
1264 #[rstest]
1265 fn test_l1_vs_l3_different_behavior_same_order_id() {
1266 let mut l1_ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L1_MBP);
1270 let side_constant = OrderSide::Buy as u64;
1271
1272 let order1 = BookOrder {
1273 side: OrderSide::Buy,
1274 price: Price::from("100.00"),
1275 size: Quantity::from(50),
1276 order_id: side_constant,
1277 };
1278 l1_ladder.add(order1);
1279
1280 let order2 = BookOrder {
1281 side: OrderSide::Buy,
1282 price: Price::from("101.00"),
1283 size: Quantity::from(60),
1284 order_id: side_constant, };
1286 l1_ladder.add(order2);
1287
1288 assert_eq!(l1_ladder.len(), 1, "L1 should have only 1 level");
1289 assert_eq!(
1290 l1_ladder.top().unwrap().price.value,
1291 Price::from("101.00"),
1292 "L1 should have replaced the old level"
1293 );
1294
1295 let mut l3_ladder = BookLadder::new(OrderSideSpecified::Buy, BookType::L3_MBO);
1297
1298 let order3 = BookOrder {
1299 side: OrderSide::Buy,
1300 price: Price::from("100.00"),
1301 size: Quantity::from(50),
1302 order_id: 1, };
1304 l3_ladder.add(order3);
1305
1306 let order4 = BookOrder {
1307 side: OrderSide::Buy,
1308 price: Price::from("101.00"),
1309 size: Quantity::from(60),
1310 order_id: 1, };
1312 l3_ladder.add(order4);
1313
1314 assert_eq!(l3_ladder.len(), 2, "L3 should have 2 levels");
1315 }
1316}