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)]
234mod tests {
235 use rstest::rstest;
236 use rust_decimal_macros::dec;
237
238 use crate::{
239 data::order::BookOrder,
240 enums::{OrderSide, OrderSideSpecified},
241 orderbook::{BookLevel, BookPrice},
242 types::{Price, Quantity, fixed::FIXED_SCALAR, quantity::QuantityRaw},
243 };
244
245 #[rstest]
246 fn test_empty_level() {
247 let level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
248 assert!(level.first().is_none());
249 assert_eq!(level.side(), OrderSideSpecified::Buy);
250 }
251
252 #[rstest]
253 fn test_level_from_order() {
254 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
255 let level = BookLevel::from_order(order);
256
257 assert_eq!(level.price.value, Price::from("1.00"));
258 assert_eq!(level.price.side, OrderSideSpecified::Buy);
259 assert_eq!(level.len(), 1);
260 assert_eq!(level.first().unwrap(), &order);
261 assert_eq!(level.size(), 10.0);
262 }
263
264 #[rstest]
265 #[should_panic(expected = "assertion `left == right` failed")]
266 fn test_add_order_incorrect_price_level() {
267 let mut level =
268 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
269 let incorrect_price_order =
270 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 1);
271 level.add(incorrect_price_order);
272 }
273
274 #[rstest]
275 #[should_panic(expected = "assertion `left == right` failed")]
276 fn test_add_bulk_orders_incorrect_price() {
277 let mut level =
278 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
279 let orders = vec![
280 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1),
281 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 2), ];
283 level.add_bulk(orders);
284 }
285
286 #[rstest]
287 fn test_add_bulk_empty() {
288 let mut level =
289 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
290 level.add_bulk(vec![]);
291 assert!(level.is_empty());
292 }
293
294 #[rstest]
295 fn test_comparisons_bid_side() {
296 let level0 = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
297 let level1 = BookLevel::new(BookPrice::new(Price::from("1.01"), OrderSideSpecified::Buy));
298 assert_eq!(level0, level0);
299 assert!(level0 > level1);
300 }
301
302 #[rstest]
303 fn test_comparisons_ask_side() {
304 let level0 = BookLevel::new(BookPrice::new(
305 Price::from("1.00"),
306 OrderSideSpecified::Sell,
307 ));
308 let level1 = BookLevel::new(BookPrice::new(
309 Price::from("1.01"),
310 OrderSideSpecified::Sell,
311 ));
312 assert_eq!(level0, level0);
313 assert!(level0 < level1);
314 }
315
316 #[rstest]
317 fn test_book_level_sorting() {
318 let mut levels = [
319 BookLevel::new(BookPrice::new(
320 Price::from("1.00"),
321 OrderSideSpecified::Sell,
322 )),
323 BookLevel::new(BookPrice::new(
324 Price::from("1.02"),
325 OrderSideSpecified::Sell,
326 )),
327 BookLevel::new(BookPrice::new(
328 Price::from("1.01"),
329 OrderSideSpecified::Sell,
330 )),
331 ];
332 levels.sort();
333 assert_eq!(levels[0].price.value, Price::from("1.00"));
334 assert_eq!(levels[1].price.value, Price::from("1.01"));
335 assert_eq!(levels[2].price.value, Price::from("1.02"));
336 }
337
338 #[rstest]
339 fn test_add_single_order() {
340 let mut level =
341 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
342 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
343
344 level.add(order);
345 assert!(!level.is_empty());
346 assert_eq!(level.len(), 1);
347 assert_eq!(level.size(), 10.0);
348 assert_eq!(level.first().unwrap(), &order);
349 }
350
351 #[rstest]
352 fn test_add_multiple_orders() {
353 let mut level =
354 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
355 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
356 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
357
358 level.add(order1);
359 level.add(order2);
360 assert_eq!(level.len(), 2);
361 assert_eq!(level.size(), 30.0);
362 assert_eq!(level.exposure(), 60.0);
363 assert_eq!(level.first().unwrap(), &order1);
364 }
365
366 #[rstest]
367 fn test_get_orders() {
368 let mut level =
369 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
370 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
371 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
372
373 level.add(order1);
374 level.add(order2);
375
376 let orders = level.get_orders();
377 assert_eq!(orders.len(), 2);
378 assert_eq!(orders[0], order1); assert_eq!(orders[1], order2);
380 }
381
382 #[rstest]
383 fn test_iter_returns_fifo() {
384 let mut level =
385 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
386 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
387 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
388 level.add(order1);
389 level.add(order2);
390
391 let orders: Vec<_> = level.iter().copied().collect();
392 assert_eq!(orders, vec![order1, order2]);
393 }
394
395 #[rstest]
396 fn test_update_order() {
397 let mut level =
398 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
399 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
400 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 0);
401
402 level.add(order1);
403 level.update(order2);
404 assert_eq!(level.len(), 1);
405 assert_eq!(level.size(), 20.0);
406 assert_eq!(level.exposure(), 20.0);
407 }
408
409 #[rstest]
410 fn test_update_inserts_if_missing() {
411 let mut level =
412 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
413 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
414 level.update(order);
415 assert_eq!(level.len(), 1);
416 assert_eq!(level.first().unwrap(), &order);
417 }
418
419 #[rstest]
420 fn test_update_zero_size_nonexistent() {
421 let mut level =
422 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
423 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 1);
424 level.update(order);
425 assert_eq!(level.len(), 0);
426 }
427
428 #[rstest]
429 fn test_fifo_order_after_updates() {
430 let mut level =
431 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
432
433 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
434 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
435
436 level.add(order1);
437 level.add(order2);
438
439 let updated_order1 =
441 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
442 level.update(updated_order1);
443
444 let orders = level.get_orders();
445 assert_eq!(orders.len(), 2);
446 assert_eq!(orders[0], updated_order1); assert_eq!(orders[1], order2); }
449
450 #[rstest]
451 fn test_insertion_order_after_mixed_operations() {
452 let mut level =
453 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
454 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
455 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
456 let order3 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(30), 3);
457
458 level.add(order1);
459 level.add(order2);
460 level.add(order3);
461
462 let updated_order2 =
464 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(25), 2);
465 level.update(updated_order2);
466
467 level.delete(&order1);
469
470 let orders = level.get_orders();
471 assert_eq!(orders, vec![updated_order2, order3]);
472 }
473
474 #[rstest]
475 #[should_panic(expected = "assertion `left == right` failed")]
476 fn test_update_order_incorrect_price() {
477 let mut level =
478 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
479
480 let initial_order =
482 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
483 level.add(initial_order);
484
485 let updated_order =
487 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
488 level.update(updated_order);
489 }
490
491 #[rstest]
492 fn test_update_order_with_zero_size() {
493 let mut level =
494 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
495 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
496 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 0);
497
498 level.add(order1);
499 level.update(order2);
500 assert_eq!(level.len(), 0);
501 assert_eq!(level.size(), 0.0);
502 assert_eq!(level.exposure(), 0.0);
503 }
504
505 #[rstest]
506 fn test_delete_nonexistent_order() {
507 let mut level =
508 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
509 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
510 level.delete(&order);
511 assert_eq!(level.len(), 0);
512 }
513
514 #[rstest]
515 fn test_delete_order() {
516 let mut level =
517 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
518 let order1_id = 0;
519 let order1 = BookOrder::new(
520 OrderSide::Buy,
521 Price::from("1.00"),
522 Quantity::from(10),
523 order1_id,
524 );
525 let order2_id = 1;
526 let order2 = BookOrder::new(
527 OrderSide::Buy,
528 Price::from("1.00"),
529 Quantity::from(20),
530 order2_id,
531 );
532
533 level.add(order1);
534 level.add(order2);
535 level.delete(&order1);
536 assert_eq!(level.len(), 1);
537 assert_eq!(level.size(), 20.0);
538 assert!(level.orders.contains_key(&order2_id));
539 assert_eq!(level.exposure(), 20.0);
540 }
541
542 #[rstest]
543 fn test_remove_order_by_id() {
544 let mut level =
545 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
546 let order1_id = 0;
547 let order1 = BookOrder::new(
548 OrderSide::Buy,
549 Price::from("1.00"),
550 Quantity::from(10),
551 order1_id,
552 );
553 let order2_id = 1;
554 let order2 = BookOrder::new(
555 OrderSide::Buy,
556 Price::from("1.00"),
557 Quantity::from(20),
558 order2_id,
559 );
560
561 level.add(order1);
562 level.add(order2);
563 level.remove_by_id(order2_id, 0, 0.into());
564 assert_eq!(level.len(), 1);
565 assert!(level.orders.contains_key(&order1_id));
566 assert_eq!(level.size(), 10.0);
567 assert_eq!(level.exposure(), 10.0);
568 }
569
570 #[rstest]
571 fn test_add_bulk_orders() {
572 let mut level =
573 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
574 let order1_id = 0;
575 let order1 = BookOrder::new(
576 OrderSide::Buy,
577 Price::from("2.00"),
578 Quantity::from(10),
579 order1_id,
580 );
581 let order2_id = 1;
582 let order2 = BookOrder::new(
583 OrderSide::Buy,
584 Price::from("2.00"),
585 Quantity::from(20),
586 order2_id,
587 );
588
589 let orders = vec![order1, order2];
590 level.add_bulk(orders);
591 assert_eq!(level.len(), 2);
592 assert_eq!(level.size(), 30.0);
593 assert_eq!(level.exposure(), 60.0);
594 }
595
596 #[rstest]
597 fn test_maximum_order_id() {
598 let mut level =
599 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
600
601 let order = BookOrder::new(
602 OrderSide::Buy,
603 Price::from("1.00"),
604 Quantity::from(10),
605 u64::MAX,
606 );
607 level.add(order);
608
609 assert_eq!(level.len(), 1);
610 assert_eq!(level.first().unwrap(), &order);
611 }
612
613 #[rstest]
614 #[should_panic(
615 expected = "Integrity error: order not found: order_id=1, sequence=2, ts_event=3"
616 )]
617 fn test_remove_nonexistent_order() {
618 let mut level =
619 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
620 level.remove_by_id(1, 2, 3.into());
621 }
622
623 #[rstest]
624 fn test_size() {
625 let mut level =
626 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
627 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
628 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
629
630 level.add(order1);
631 level.add(order2);
632 assert_eq!(level.size(), 25.0);
633 }
634
635 #[rstest]
636 fn test_size_raw() {
637 let mut level =
638 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
639 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
640 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
641
642 level.add(order1);
643 level.add(order2);
644 assert_eq!(
645 level.size_raw(),
646 (30.0 * FIXED_SCALAR).round() as QuantityRaw
647 );
648 }
649
650 #[rstest]
651 fn test_size_decimal() {
652 let mut level =
653 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
654 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
655 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
656
657 level.add(order1);
658 level.add(order2);
659 assert_eq!(level.size_decimal(), dec!(30.0));
660 }
661
662 #[rstest]
663 fn test_exposure() {
664 let mut level =
665 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
666 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
667 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
668
669 level.add(order1);
670 level.add(order2);
671 assert_eq!(level.exposure(), 60.0);
672 }
673
674 #[rstest]
675 fn test_exposure_raw() {
676 let mut level =
677 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
678 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
679 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
680
681 level.add(order1);
682 level.add(order2);
683 assert_eq!(
684 level.exposure_raw(),
685 (60.0 * FIXED_SCALAR).round() as QuantityRaw
686 );
687 }
688
689 #[rstest]
690 fn test_exposure_raw_saturates_on_overflow() {
691 #[cfg(feature = "high-precision")]
694 let (price_str, qty_str) = ("1000000000000.00", "1000000000000.00");
695 #[cfg(not(feature = "high-precision"))]
696 let (price_str, qty_str) = ("100000000.00", "1000000000.00");
697
698 let mut level = BookLevel::new(BookPrice::new(
699 Price::from(price_str),
700 OrderSideSpecified::Buy,
701 ));
702
703 let order = BookOrder::new(
705 OrderSide::Buy,
706 Price::from(price_str),
707 Quantity::from(qty_str),
708 0,
709 );
710
711 level.add(order);
712
713 let result = level.exposure_raw();
715 assert_eq!(result, QuantityRaw::MAX);
716 }
717
718 #[rstest]
719 fn test_exposure_raw_sum_saturates_on_overflow() {
720 #[cfg(feature = "high-precision")]
722 let (price_str, qty_str, count) = ("10000000000000.00", "10000000000000.00", 100);
723 #[cfg(not(feature = "high-precision"))]
724 let (price_str, qty_str, count) = ("1000000000.00", "1000000000.00", 100);
725
726 let mut level = BookLevel::new(BookPrice::new(
727 Price::from(price_str),
728 OrderSideSpecified::Buy,
729 ));
730
731 for i in 0..count {
733 let order = BookOrder::new(
734 OrderSide::Buy,
735 Price::from(price_str),
736 Quantity::from(qty_str),
737 i,
738 );
739 level.add(order);
740 }
741
742 let result = level.exposure_raw();
744 assert_eq!(result, QuantityRaw::MAX);
745 }
746}