1use std::cmp::Ordering;
19
20use indexmap::IndexMap;
21use nautilus_core::UnixNanos;
22use rust_decimal::Decimal;
23
24use crate::{
25 data::order::{BookOrder, OrderId},
26 enums::OrderSideSpecified,
27 orderbook::{BookIntegrityError, BookPrice},
28 types::{fixed::FIXED_SCALAR, quantity::QuantityRaw},
29};
30
31#[derive(Clone, Debug, Eq)]
35#[cfg_attr(
36 feature = "python",
37 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
38)]
39pub struct BookLevel {
40 pub price: BookPrice,
41 pub(crate) orders: IndexMap<OrderId, BookOrder>,
42}
43
44impl BookLevel {
45 #[must_use]
47 pub fn new(price: BookPrice) -> Self {
48 Self {
49 price,
50 orders: IndexMap::new(),
51 }
52 }
53
54 #[must_use]
56 pub fn from_order(order: BookOrder) -> Self {
57 let mut level = Self {
58 price: order.to_book_price(),
59 orders: IndexMap::new(),
60 };
61 level.add(order);
62 level
63 }
64
65 pub fn side(&self) -> OrderSideSpecified {
66 self.price.side
67 }
68
69 #[must_use]
71 pub fn len(&self) -> usize {
72 self.orders.len()
73 }
74
75 #[must_use]
77 pub fn is_empty(&self) -> bool {
78 self.orders.is_empty()
79 }
80
81 #[inline]
83 #[must_use]
84 pub fn first(&self) -> Option<&BookOrder> {
85 self.orders.get_index(0).map(|(_key, order)| order)
86 }
87
88 pub fn iter(&self) -> impl Iterator<Item = &BookOrder> {
90 self.orders.values()
91 }
92
93 #[must_use]
95 pub fn get_orders(&self) -> Vec<BookOrder> {
96 self.orders.values().copied().collect()
97 }
98
99 #[must_use]
101 pub fn size(&self) -> f64 {
102 self.orders.values().map(|o| o.size.as_f64()).sum()
103 }
104
105 #[must_use]
107 pub fn size_raw(&self) -> QuantityRaw {
108 self.orders.values().map(|o| o.size.raw).sum()
109 }
110
111 #[must_use]
113 pub fn size_decimal(&self) -> Decimal {
114 self.orders.values().map(|o| o.size.as_decimal()).sum()
115 }
116
117 #[must_use]
119 pub fn exposure(&self) -> f64 {
120 self.orders
121 .values()
122 .map(|o| o.price.as_f64() * o.size.as_f64())
123 .sum()
124 }
125
126 #[must_use]
130 pub fn exposure_raw(&self) -> QuantityRaw {
131 self.orders
132 .values()
133 .map(|o| {
134 let exposure_f64 = o.price.as_f64() * o.size.as_f64();
135 debug_assert!(
136 exposure_f64.is_finite(),
137 "Exposure calculation resulted in non-finite value for order {}: price={}, size={}",
138 o.order_id,
139 o.price,
140 o.size
141 );
142
143 let scaled = exposure_f64 * FIXED_SCALAR;
144 if scaled >= QuantityRaw::MAX as f64 {
145 QuantityRaw::MAX
146 } else if scaled < 0.0 {
147 0
148 } else {
149 scaled as QuantityRaw
150 }
151 })
152 .fold(0, |acc, val| acc.saturating_add(val))
153 }
154
155 pub fn add_bulk(&mut self, orders: Vec<BookOrder>) {
157 for order in orders {
158 self.add(order);
159 }
160 }
161
162 pub fn add(&mut self, order: BookOrder) {
164 debug_assert_eq!(order.price, self.price.value);
165
166 if !order.size.is_positive() {
167 log::warn!(
168 "Attempted to add order with non-positive size: order_id={order_id}, size={size}, ignoring",
169 order_id = order.order_id,
170 size = order.size
171 );
172 return;
173 }
174
175 self.orders.insert(order.order_id, order);
176 }
177
178 pub fn update(&mut self, order: BookOrder) {
181 debug_assert_eq!(order.price, self.price.value);
182
183 if order.size.raw == 0 {
184 self.orders.shift_remove(&order.order_id);
186 } else {
187 debug_assert!(
188 order.size.is_positive(),
189 "Order size must be positive: {}",
190 order.size
191 );
192 self.orders.insert(order.order_id, order);
193 }
194 }
195
196 pub fn delete(&mut self, order: &BookOrder) {
198 self.orders.shift_remove(&order.order_id);
199 }
200
201 pub fn remove_by_id(&mut self, order_id: OrderId, sequence: u64, ts_event: UnixNanos) {
207 assert!(
208 self.orders.shift_remove(&order_id).is_some(),
209 "{}",
210 &BookIntegrityError::OrderNotFound(order_id, sequence, ts_event)
211 );
212 }
213}
214
215impl PartialEq for BookLevel {
216 fn eq(&self, other: &Self) -> bool {
217 self.price == other.price
218 }
219}
220
221impl PartialOrd for BookLevel {
222 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
223 Some(self.cmp(other))
224 }
225}
226
227impl Ord for BookLevel {
228 fn cmp(&self, other: &Self) -> Ordering {
229 self.price.cmp(&other.price)
230 }
231}
232
233#[cfg(test)]
237mod tests {
238 use rstest::rstest;
239 use rust_decimal_macros::dec;
240
241 use crate::{
242 data::order::BookOrder,
243 enums::{OrderSide, OrderSideSpecified},
244 orderbook::{BookLevel, BookPrice},
245 types::{Price, Quantity, fixed::FIXED_SCALAR, quantity::QuantityRaw},
246 };
247
248 #[rstest]
249 fn test_empty_level() {
250 let level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
251 assert!(level.first().is_none());
252 assert_eq!(level.side(), OrderSideSpecified::Buy);
253 }
254
255 #[rstest]
256 fn test_level_from_order() {
257 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
258 let level = BookLevel::from_order(order);
259
260 assert_eq!(level.price.value, Price::from("1.00"));
261 assert_eq!(level.price.side, OrderSideSpecified::Buy);
262 assert_eq!(level.len(), 1);
263 assert_eq!(level.first().unwrap(), &order);
264 assert_eq!(level.size(), 10.0);
265 }
266
267 #[rstest]
268 #[should_panic(expected = "assertion `left == right` failed")]
269 fn test_add_order_incorrect_price_level() {
270 let mut level =
271 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
272 let incorrect_price_order =
273 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 1);
274 level.add(incorrect_price_order);
275 }
276
277 #[rstest]
278 #[should_panic(expected = "assertion `left == right` failed")]
279 fn test_add_bulk_orders_incorrect_price() {
280 let mut level =
281 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
282 let orders = vec![
283 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1),
284 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 2), ];
286 level.add_bulk(orders);
287 }
288
289 #[rstest]
290 fn test_add_bulk_empty() {
291 let mut level =
292 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
293 level.add_bulk(vec![]);
294 assert!(level.is_empty());
295 }
296
297 #[rstest]
298 fn test_comparisons_bid_side() {
299 let level0 = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
300 let level1 = BookLevel::new(BookPrice::new(Price::from("1.01"), OrderSideSpecified::Buy));
301 assert_eq!(level0, level0);
302 assert!(level0 > level1);
303 }
304
305 #[rstest]
306 fn test_comparisons_ask_side() {
307 let level0 = BookLevel::new(BookPrice::new(
308 Price::from("1.00"),
309 OrderSideSpecified::Sell,
310 ));
311 let level1 = BookLevel::new(BookPrice::new(
312 Price::from("1.01"),
313 OrderSideSpecified::Sell,
314 ));
315 assert_eq!(level0, level0);
316 assert!(level0 < level1);
317 }
318
319 #[rstest]
320 fn test_book_level_sorting() {
321 let mut levels = [
322 BookLevel::new(BookPrice::new(
323 Price::from("1.00"),
324 OrderSideSpecified::Sell,
325 )),
326 BookLevel::new(BookPrice::new(
327 Price::from("1.02"),
328 OrderSideSpecified::Sell,
329 )),
330 BookLevel::new(BookPrice::new(
331 Price::from("1.01"),
332 OrderSideSpecified::Sell,
333 )),
334 ];
335 levels.sort();
336 assert_eq!(levels[0].price.value, Price::from("1.00"));
337 assert_eq!(levels[1].price.value, Price::from("1.01"));
338 assert_eq!(levels[2].price.value, Price::from("1.02"));
339 }
340
341 #[rstest]
342 fn test_add_single_order() {
343 let mut level =
344 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
345 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
346
347 level.add(order);
348 assert!(!level.is_empty());
349 assert_eq!(level.len(), 1);
350 assert_eq!(level.size(), 10.0);
351 assert_eq!(level.first().unwrap(), &order);
352 }
353
354 #[rstest]
355 fn test_add_multiple_orders() {
356 let mut level =
357 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
358 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
359 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
360
361 level.add(order1);
362 level.add(order2);
363 assert_eq!(level.len(), 2);
364 assert_eq!(level.size(), 30.0);
365 assert_eq!(level.exposure(), 60.0);
366 assert_eq!(level.first().unwrap(), &order1);
367 }
368
369 #[rstest]
370 fn test_get_orders() {
371 let mut level =
372 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
373 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
374 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
375
376 level.add(order1);
377 level.add(order2);
378
379 let orders = level.get_orders();
380 assert_eq!(orders.len(), 2);
381 assert_eq!(orders[0], order1); assert_eq!(orders[1], order2);
383 }
384
385 #[rstest]
386 fn test_iter_returns_fifo() {
387 let mut level =
388 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
389 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
390 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
391 level.add(order1);
392 level.add(order2);
393
394 let orders: Vec<_> = level.iter().copied().collect();
395 assert_eq!(orders, vec![order1, order2]);
396 }
397
398 #[rstest]
399 fn test_update_order() {
400 let mut level =
401 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
402 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
403 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 0);
404
405 level.add(order1);
406 level.update(order2);
407 assert_eq!(level.len(), 1);
408 assert_eq!(level.size(), 20.0);
409 assert_eq!(level.exposure(), 20.0);
410 }
411
412 #[rstest]
413 fn test_update_inserts_if_missing() {
414 let mut level =
415 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
416 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
417 level.update(order);
418 assert_eq!(level.len(), 1);
419 assert_eq!(level.first().unwrap(), &order);
420 }
421
422 #[rstest]
423 fn test_update_zero_size_nonexistent() {
424 let mut level =
425 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
426 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 1);
427 level.update(order);
428 assert_eq!(level.len(), 0);
429 }
430
431 #[rstest]
432 fn test_fifo_order_after_updates() {
433 let mut level =
434 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
435
436 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
437 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
438
439 level.add(order1);
440 level.add(order2);
441
442 let updated_order1 =
444 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
445 level.update(updated_order1);
446
447 let orders = level.get_orders();
448 assert_eq!(orders.len(), 2);
449 assert_eq!(orders[0], updated_order1); assert_eq!(orders[1], order2); }
452
453 #[rstest]
454 fn test_insertion_order_after_mixed_operations() {
455 let mut level =
456 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
457 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
458 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
459 let order3 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(30), 3);
460
461 level.add(order1);
462 level.add(order2);
463 level.add(order3);
464
465 let updated_order2 =
467 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(25), 2);
468 level.update(updated_order2);
469
470 level.delete(&order1);
472
473 let orders = level.get_orders();
474 assert_eq!(orders, vec![updated_order2, order3]);
475 }
476
477 #[rstest]
478 #[should_panic(expected = "assertion `left == right` failed")]
479 fn test_update_order_incorrect_price() {
480 let mut level =
481 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
482
483 let initial_order =
485 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
486 level.add(initial_order);
487
488 let updated_order =
490 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
491 level.update(updated_order);
492 }
493
494 #[rstest]
495 fn test_update_order_with_zero_size() {
496 let mut level =
497 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
498 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
499 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 0);
500
501 level.add(order1);
502 level.update(order2);
503 assert_eq!(level.len(), 0);
504 assert_eq!(level.size(), 0.0);
505 assert_eq!(level.exposure(), 0.0);
506 }
507
508 #[rstest]
509 fn test_delete_nonexistent_order() {
510 let mut level =
511 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
512 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
513 level.delete(&order);
514 assert_eq!(level.len(), 0);
515 }
516
517 #[rstest]
518 fn test_delete_order() {
519 let mut level =
520 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
521 let order1_id = 0;
522 let order1 = BookOrder::new(
523 OrderSide::Buy,
524 Price::from("1.00"),
525 Quantity::from(10),
526 order1_id,
527 );
528 let order2_id = 1;
529 let order2 = BookOrder::new(
530 OrderSide::Buy,
531 Price::from("1.00"),
532 Quantity::from(20),
533 order2_id,
534 );
535
536 level.add(order1);
537 level.add(order2);
538 level.delete(&order1);
539 assert_eq!(level.len(), 1);
540 assert_eq!(level.size(), 20.0);
541 assert!(level.orders.contains_key(&order2_id));
542 assert_eq!(level.exposure(), 20.0);
543 }
544
545 #[rstest]
546 fn test_remove_order_by_id() {
547 let mut level =
548 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
549 let order1_id = 0;
550 let order1 = BookOrder::new(
551 OrderSide::Buy,
552 Price::from("1.00"),
553 Quantity::from(10),
554 order1_id,
555 );
556 let order2_id = 1;
557 let order2 = BookOrder::new(
558 OrderSide::Buy,
559 Price::from("1.00"),
560 Quantity::from(20),
561 order2_id,
562 );
563
564 level.add(order1);
565 level.add(order2);
566 level.remove_by_id(order2_id, 0, 0.into());
567 assert_eq!(level.len(), 1);
568 assert!(level.orders.contains_key(&order1_id));
569 assert_eq!(level.size(), 10.0);
570 assert_eq!(level.exposure(), 10.0);
571 }
572
573 #[rstest]
574 fn test_add_bulk_orders() {
575 let mut level =
576 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
577 let order1_id = 0;
578 let order1 = BookOrder::new(
579 OrderSide::Buy,
580 Price::from("2.00"),
581 Quantity::from(10),
582 order1_id,
583 );
584 let order2_id = 1;
585 let order2 = BookOrder::new(
586 OrderSide::Buy,
587 Price::from("2.00"),
588 Quantity::from(20),
589 order2_id,
590 );
591
592 let orders = vec![order1, order2];
593 level.add_bulk(orders);
594 assert_eq!(level.len(), 2);
595 assert_eq!(level.size(), 30.0);
596 assert_eq!(level.exposure(), 60.0);
597 }
598
599 #[rstest]
600 fn test_maximum_order_id() {
601 let mut level =
602 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
603
604 let order = BookOrder::new(
605 OrderSide::Buy,
606 Price::from("1.00"),
607 Quantity::from(10),
608 u64::MAX,
609 );
610 level.add(order);
611
612 assert_eq!(level.len(), 1);
613 assert_eq!(level.first().unwrap(), &order);
614 }
615
616 #[rstest]
617 #[should_panic(
618 expected = "Integrity error: order not found: order_id=1, sequence=2, ts_event=3"
619 )]
620 fn test_remove_nonexistent_order() {
621 let mut level =
622 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
623 level.remove_by_id(1, 2, 3.into());
624 }
625
626 #[rstest]
627 fn test_size() {
628 let mut level =
629 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
630 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
631 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
632
633 level.add(order1);
634 level.add(order2);
635 assert_eq!(level.size(), 25.0);
636 }
637
638 #[rstest]
639 fn test_size_raw() {
640 let mut level =
641 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
642 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
643 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
644
645 level.add(order1);
646 level.add(order2);
647 assert_eq!(
648 level.size_raw(),
649 (30.0 * FIXED_SCALAR).round() as QuantityRaw
650 );
651 }
652
653 #[rstest]
654 fn test_size_decimal() {
655 let mut level =
656 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
657 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
658 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
659
660 level.add(order1);
661 level.add(order2);
662 assert_eq!(level.size_decimal(), dec!(30.0));
663 }
664
665 #[rstest]
666 fn test_exposure() {
667 let mut level =
668 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
669 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
670 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
671
672 level.add(order1);
673 level.add(order2);
674 assert_eq!(level.exposure(), 60.0);
675 }
676
677 #[rstest]
678 fn test_exposure_raw() {
679 let mut level =
680 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
681 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
682 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
683
684 level.add(order1);
685 level.add(order2);
686 assert_eq!(
687 level.exposure_raw(),
688 (60.0 * FIXED_SCALAR).round() as QuantityRaw
689 );
690 }
691
692 #[rstest]
693 fn test_exposure_raw_saturates_on_overflow() {
694 #[cfg(feature = "high-precision")]
697 let (price_str, qty_str) = ("1000000000000.00", "1000000000000.00");
698 #[cfg(not(feature = "high-precision"))]
699 let (price_str, qty_str) = ("100000000.00", "1000000000.00");
700
701 let mut level = BookLevel::new(BookPrice::new(
702 Price::from(price_str),
703 OrderSideSpecified::Buy,
704 ));
705
706 let order = BookOrder::new(
708 OrderSide::Buy,
709 Price::from(price_str),
710 Quantity::from(qty_str),
711 0,
712 );
713
714 level.add(order);
715
716 let result = level.exposure_raw();
718 assert_eq!(result, QuantityRaw::MAX);
719 }
720
721 #[rstest]
722 fn test_exposure_raw_sum_saturates_on_overflow() {
723 #[cfg(feature = "high-precision")]
725 let (price_str, qty_str, count) = ("10000000000000.00", "10000000000000.00", 100);
726 #[cfg(not(feature = "high-precision"))]
727 let (price_str, qty_str, count) = ("1000000000.00", "1000000000.00", 100);
728
729 let mut level = BookLevel::new(BookPrice::new(
730 Price::from(price_str),
731 OrderSideSpecified::Buy,
732 ));
733
734 for i in 0..count {
736 let order = BookOrder::new(
737 OrderSide::Buy,
738 Price::from(price_str),
739 Quantity::from(qty_str),
740 i,
741 );
742 level.add(order);
743 }
744
745 let result = level.exposure_raw();
747 assert_eq!(result, QuantityRaw::MAX);
748 }
749}