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