1use std::cmp::Ordering;
19
20#[cfg(feature = "defi")]
21use alloy_primitives::U256;
22use indexmap::IndexMap;
23use nautilus_core::UnixNanos;
24use rust_decimal::Decimal;
25
26#[cfg(feature = "defi")]
27use crate::types::fixed::FIXED_PRECISION;
28use crate::{
29 data::order::{BookOrder, OrderId},
30 enums::OrderSide,
31 orderbook::{BookIntegrityError, BookPrice},
32 types::{fixed::checked_mul_div_fixed, price::PriceRaw, quantity::QuantityRaw},
33};
34
35#[derive(Clone, Debug, Eq)]
39#[cfg_attr(
40 feature = "python",
41 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
42)]
43#[cfg_attr(
44 feature = "python",
45 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
46)]
47pub struct BookLevel {
48 pub price: BookPrice,
49 pub(crate) orders: IndexMap<OrderId, BookOrder>,
50}
51
52impl BookLevel {
53 #[must_use]
55 pub fn new(price: BookPrice) -> Self {
56 Self {
57 price,
58 orders: IndexMap::new(),
59 }
60 }
61
62 #[must_use]
64 pub fn from_order(order: BookOrder) -> Self {
65 let mut level = Self {
66 price: order.to_book_price(),
67 orders: IndexMap::new(),
68 };
69 level.add(order);
70 level
71 }
72
73 #[must_use]
74 pub fn side(&self) -> OrderSide {
75 self.price.side
76 }
77
78 #[must_use]
80 pub fn len(&self) -> usize {
81 self.orders.len()
82 }
83
84 #[must_use]
86 pub fn is_empty(&self) -> bool {
87 self.orders.is_empty()
88 }
89
90 #[inline]
92 #[must_use]
93 pub fn first(&self) -> Option<&BookOrder> {
94 self.orders.get_index(0).map(|(_key, order)| order)
95 }
96
97 pub fn iter(&self) -> impl Iterator<Item = &BookOrder> {
99 self.orders.values()
100 }
101
102 #[must_use]
104 pub fn get_orders(&self) -> Vec<BookOrder> {
105 self.orders.values().copied().collect()
106 }
107
108 #[must_use]
110 pub fn size(&self) -> f64 {
111 self.orders.values().map(|o| o.size.as_f64()).sum()
112 }
113
114 #[must_use]
120 pub fn size_raw(&self) -> QuantityRaw {
121 self.orders
122 .values()
123 .try_fold(0, |total: QuantityRaw, order| {
124 total.checked_add(order.size.raw)
125 })
126 .expect("Overflow occurred when summing `BookLevel` raw size")
127 }
128
129 #[must_use]
131 pub fn size_decimal(&self) -> Decimal {
132 self.orders.values().map(|o| o.size.as_decimal()).sum()
133 }
134
135 #[must_use]
137 pub fn exposure(&self) -> f64 {
138 self.orders
139 .values()
140 .map(|o| o.price.as_f64() * o.size.as_f64())
141 .sum()
142 }
143
144 #[must_use]
152 pub fn exposure_raw(&self) -> QuantityRaw {
153 self.orders
154 .values()
155 .map(|order| {
156 calculate_exposure_raw(
157 order.price.raw,
158 order.size.raw,
159 order.price.precision,
160 order.size.precision,
161 )
162 })
163 .fold(0, QuantityRaw::saturating_add)
164 }
165
166 pub fn add_bulk(&mut self, orders: &[BookOrder]) {
168 for order in orders {
169 self.add(*order);
170 }
171 }
172
173 pub fn add(&mut self, order: BookOrder) {
175 debug_assert_eq!(order.price, self.price.value);
176
177 if !order.size.is_positive() {
178 log::warn!(
179 "Attempted to add order with non-positive size: order_id={order_id}, size={size}, ignoring",
180 order_id = order.order_id,
181 size = order.size
182 );
183 return;
184 }
185
186 self.orders.insert(order.order_id, order);
187 }
188
189 pub fn update(&mut self, order: BookOrder) {
192 debug_assert_eq!(order.price, self.price.value);
193
194 if order.size.raw == 0 {
195 self.orders.shift_remove(&order.order_id);
197 } else {
198 debug_assert!(
199 order.size.is_positive(),
200 "Order size must be positive: {}",
201 order.size
202 );
203 self.orders.insert(order.order_id, order);
204 }
205 }
206
207 pub fn delete(&mut self, order: &BookOrder) {
209 self.orders.shift_remove(&order.order_id);
210 }
211
212 pub fn remove_by_id(&mut self, order_id: OrderId, sequence: u64, ts_event: UnixNanos) {
218 assert!(
219 self.orders.shift_remove(&order_id).is_some(),
220 "{}",
221 &BookIntegrityError::OrderNotFound(order_id, sequence, ts_event)
222 );
223 }
224}
225
226fn calculate_exposure_raw(
227 price_raw: PriceRaw,
228 size_raw: QuantityRaw,
229 price_precision: u8,
230 size_precision: u8,
231) -> QuantityRaw {
232 let Ok(price_raw) = QuantityRaw::try_from(price_raw) else {
233 return 0;
234 };
235
236 #[cfg(feature = "defi")]
237 if price_precision > FIXED_PRECISION || size_precision > FIXED_PRECISION {
238 return calculate_exposure_raw_native(price_raw, size_raw, price_precision, size_precision);
239 }
240
241 #[cfg(not(feature = "defi"))]
242 let _ = (price_precision, size_precision);
243
244 checked_mul_div_fixed(price_raw, size_raw).unwrap_or(QuantityRaw::MAX)
245}
246
247#[cfg(feature = "defi")]
248fn calculate_exposure_raw_native(
249 price_raw: QuantityRaw,
250 size_raw: QuantityRaw,
251 price_precision: u8,
252 size_precision: u8,
253) -> QuantityRaw {
254 let scale_precision = price_precision.max(FIXED_PRECISION)
255 + size_precision.max(FIXED_PRECISION)
256 - FIXED_PRECISION;
257 let scalar = 10_u128.pow(u32::from(scale_precision));
258 let exposure = U256::from(price_raw)
259 .checked_mul(U256::from(size_raw))
260 .expect("a positive i128 times a u128 fits U256")
261 / U256::from(scalar);
262
263 QuantityRaw::try_from(exposure).unwrap_or(QuantityRaw::MAX)
264}
265
266impl PartialEq for BookLevel {
267 fn eq(&self, other: &Self) -> bool {
268 self.price == other.price
269 }
270}
271
272impl PartialOrd for BookLevel {
273 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
274 Some(self.cmp(other))
275 }
276}
277
278impl Ord for BookLevel {
279 fn cmp(&self, other: &Self) -> Ordering {
280 self.price.cmp(&other.price)
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use rstest::rstest;
287 use rust_decimal_macros::dec;
288
289 #[cfg(feature = "high-precision")]
290 use super::calculate_exposure_raw;
291 use crate::{
292 data::order::BookOrder,
293 enums::OrderSide,
294 orderbook::{BookLevel, BookPrice},
295 types::{
296 Price, Quantity,
297 fixed::{FIXED_PRECISION, FIXED_SCALAR},
298 price::PriceRaw,
299 quantity::QuantityRaw,
300 },
301 };
302
303 #[rstest]
304 fn test_empty_level() {
305 let level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
306 assert!(level.first().is_none());
307 assert_eq!(level.side(), OrderSide::Buy);
308 }
309
310 #[rstest]
311 fn test_level_from_order() {
312 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
313 let level = BookLevel::from_order(order);
314
315 assert_eq!(level.price.value, Price::from("1.00"));
316 assert_eq!(level.price.side, OrderSide::Buy);
317 assert_eq!(level.len(), 1);
318 assert_eq!(level.first().unwrap(), &order);
319 assert_eq!(level.size(), 10.0);
320 }
321
322 #[rstest]
323 #[should_panic(expected = "assertion `left == right` failed")]
324 fn test_add_order_incorrect_price_level() {
325 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
326 let incorrect_price_order =
327 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 1);
328 level.add(incorrect_price_order);
329 }
330
331 #[rstest]
332 #[should_panic(expected = "assertion `left == right` failed")]
333 fn test_add_bulk_orders_incorrect_price() {
334 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
335 let orders = [
336 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1),
337 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 2), ];
339 level.add_bulk(&orders);
340 }
341
342 #[rstest]
343 fn test_add_bulk_empty() {
344 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
345 level.add_bulk(&[]);
346 assert!(level.is_empty());
347 }
348
349 #[rstest]
350 #[case::bid(OrderSide::Buy, true)]
351 #[case::ask(OrderSide::Sell, false)]
352 fn test_comparisons(#[case] side: OrderSide, #[case] first_is_greater: bool) {
353 let level0 = BookLevel::new(BookPrice::new(Price::from("1.00"), side));
354 let same = BookLevel::new(BookPrice::new(Price::from("1.00"), side));
355 let level1 = BookLevel::new(BookPrice::new(Price::from("1.01"), side));
356
357 assert_eq!(level0, same);
358 assert_eq!(level0 > level1, first_is_greater);
359 assert_eq!(level0 < level1, !first_is_greater);
360 }
361
362 #[rstest]
363 fn test_book_level_sorting() {
364 let mut levels = [
365 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Sell)),
366 BookLevel::new(BookPrice::new(Price::from("1.02"), OrderSide::Sell)),
367 BookLevel::new(BookPrice::new(Price::from("1.01"), OrderSide::Sell)),
368 ];
369 levels.sort();
370 assert_eq!(levels[0].price.value, Price::from("1.00"));
371 assert_eq!(levels[1].price.value, Price::from("1.01"));
372 assert_eq!(levels[2].price.value, Price::from("1.02"));
373 }
374
375 #[rstest]
376 fn test_add_single_order() {
377 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
378 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
379
380 level.add(order);
381 assert!(!level.is_empty());
382 assert_eq!(level.len(), 1);
383 assert_eq!(level.size(), 10.0);
384 assert_eq!(level.first().unwrap(), &order);
385 }
386
387 #[rstest]
388 fn test_add_multiple_orders() {
389 let mut level = BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSide::Buy));
390 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
391 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
392
393 level.add(order1);
394 level.add(order2);
395 assert_eq!(level.len(), 2);
396 assert_eq!(level.size(), 30.0);
397 assert_eq!(level.exposure(), 60.0);
398 assert_eq!(level.first().unwrap(), &order1);
399 }
400
401 #[rstest]
402 fn test_get_orders() {
403 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
404 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
405 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
406
407 level.add(order1);
408 level.add(order2);
409
410 let orders = level.get_orders();
411 assert_eq!(orders.len(), 2);
412 assert_eq!(orders[0], order1); assert_eq!(orders[1], order2);
414 }
415
416 #[rstest]
417 fn test_iter_returns_fifo() {
418 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
419 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
420 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
421 level.add(order1);
422 level.add(order2);
423
424 let orders: Vec<_> = level.iter().copied().collect();
425 assert_eq!(orders, vec![order1, order2]);
426 }
427
428 #[rstest]
429 fn test_update_order() {
430 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
431 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
432 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 0);
433
434 level.add(order1);
435 level.update(order2);
436 assert_eq!(level.len(), 1);
437 assert_eq!(level.size(), 20.0);
438 assert_eq!(level.exposure(), 20.0);
439 }
440
441 #[rstest]
442 fn test_update_inserts_if_missing() {
443 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
444 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
445 level.update(order);
446 assert_eq!(level.len(), 1);
447 assert_eq!(level.first().unwrap(), &order);
448 }
449
450 #[rstest]
451 fn test_update_zero_size_nonexistent() {
452 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
453 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 1);
454 level.update(order);
455 assert_eq!(level.len(), 0);
456 }
457
458 #[rstest]
459 fn test_fifo_order_after_updates() {
460 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
461
462 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
463 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
464
465 level.add(order1);
466 level.add(order2);
467
468 let updated_order1 =
470 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
471 level.update(updated_order1);
472
473 let orders = level.get_orders();
474 assert_eq!(orders.len(), 2);
475 assert_eq!(orders[0], updated_order1); assert_eq!(orders[1], order2); }
478
479 #[rstest]
480 fn test_insertion_order_after_mixed_operations() {
481 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
482 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
483 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
484 let order3 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(30), 3);
485
486 level.add(order1);
487 level.add(order2);
488 level.add(order3);
489
490 let updated_order2 =
492 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(25), 2);
493 level.update(updated_order2);
494
495 level.delete(&order1);
497
498 let orders = level.get_orders();
499 assert_eq!(orders, vec![updated_order2, order3]);
500 }
501
502 #[rstest]
503 #[should_panic(expected = "assertion `left == right` failed")]
504 fn test_update_order_incorrect_price() {
505 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
506
507 let initial_order =
509 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
510 level.add(initial_order);
511
512 let updated_order =
514 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
515 level.update(updated_order);
516 }
517
518 #[rstest]
519 fn test_update_order_with_zero_size() {
520 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
521 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
522 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 0);
523
524 level.add(order1);
525 level.update(order2);
526 assert_eq!(level.len(), 0);
527 assert_eq!(level.size(), 0.0);
528 assert_eq!(level.exposure(), 0.0);
529 }
530
531 #[rstest]
532 fn test_delete_nonexistent_order() {
533 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
534 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
535 level.delete(&order);
536 assert_eq!(level.len(), 0);
537 }
538
539 #[rstest]
540 fn test_delete_order() {
541 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
542 let order1_id = 0;
543 let order1 = BookOrder::new(
544 OrderSide::Buy,
545 Price::from("1.00"),
546 Quantity::from(10),
547 order1_id,
548 );
549 let order2_id = 1;
550 let order2 = BookOrder::new(
551 OrderSide::Buy,
552 Price::from("1.00"),
553 Quantity::from(20),
554 order2_id,
555 );
556
557 level.add(order1);
558 level.add(order2);
559 level.delete(&order1);
560 assert_eq!(level.len(), 1);
561 assert_eq!(level.size(), 20.0);
562 assert!(level.orders.contains_key(&order2_id));
563 assert_eq!(level.exposure(), 20.0);
564 }
565
566 #[rstest]
567 fn test_remove_order_by_id() {
568 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
569 let order1_id = 0;
570 let order1 = BookOrder::new(
571 OrderSide::Buy,
572 Price::from("1.00"),
573 Quantity::from(10),
574 order1_id,
575 );
576 let order2_id = 1;
577 let order2 = BookOrder::new(
578 OrderSide::Buy,
579 Price::from("1.00"),
580 Quantity::from(20),
581 order2_id,
582 );
583
584 level.add(order1);
585 level.add(order2);
586 level.remove_by_id(order2_id, 0, 0.into());
587 assert_eq!(level.len(), 1);
588 assert!(level.orders.contains_key(&order1_id));
589 assert_eq!(level.size(), 10.0);
590 assert_eq!(level.exposure(), 10.0);
591 }
592
593 #[rstest]
594 fn test_add_bulk_orders() {
595 let mut level = BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSide::Buy));
596 let order1_id = 0;
597 let order1 = BookOrder::new(
598 OrderSide::Buy,
599 Price::from("2.00"),
600 Quantity::from(10),
601 order1_id,
602 );
603 let order2_id = 1;
604 let order2 = BookOrder::new(
605 OrderSide::Buy,
606 Price::from("2.00"),
607 Quantity::from(20),
608 order2_id,
609 );
610
611 let orders = [order1, order2];
612 level.add_bulk(&orders);
613 assert_eq!(level.len(), 2);
614 assert_eq!(level.size(), 30.0);
615 assert_eq!(level.exposure(), 60.0);
616 }
617
618 #[rstest]
619 fn test_maximum_order_id() {
620 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
621
622 let order = BookOrder::new(
623 OrderSide::Buy,
624 Price::from("1.00"),
625 Quantity::from(10),
626 u64::MAX,
627 );
628 level.add(order);
629
630 assert_eq!(level.len(), 1);
631 assert_eq!(level.first().unwrap(), &order);
632 }
633
634 #[rstest]
635 #[should_panic(
636 expected = "Integrity error: order not found: order_id=1, sequence=2, ts_event=3"
637 )]
638 fn test_remove_nonexistent_order() {
639 let mut level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSide::Buy));
640 level.remove_by_id(1, 2, 3.into());
641 }
642
643 #[rstest]
644 fn test_size_raw() {
645 let mut level = BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSide::Buy));
646 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
647 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
648
649 level.add(order1);
650 level.add(order2);
651 assert_eq!(
652 level.size_raw(),
653 (30.0 * FIXED_SCALAR).round() as QuantityRaw
654 );
655 }
656
657 #[rstest]
658 fn test_size_decimal() {
659 let mut level = BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSide::Buy));
660 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
661 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
662
663 level.add(order1);
664 level.add(order2);
665 assert_eq!(level.size_decimal(), dec!(30.0));
666 }
667
668 #[rstest]
669 #[case::negative("-2", "10", 0)]
670 #[case::zero("0", "1", 0)]
671 #[case::small("2", "10", 20)]
672 fn test_exposure_raw_exact_whole(
673 #[case] price: &str,
674 #[case] size: &str,
675 #[case] expected_units: QuantityRaw,
676 ) {
677 let price = Price::from(price);
678 let mut level = BookLevel::new(BookPrice::new(price, OrderSide::Buy));
679 level.add(BookOrder::new(
680 OrderSide::Buy,
681 price,
682 Quantity::from(size),
683 0,
684 ));
685
686 assert_eq!(
687 level.exposure_raw(),
688 expected_units * FIXED_SCALAR as QuantityRaw
689 );
690 }
691
692 #[rstest]
693 fn test_exposure_raw_truncates_sub_raw_unit() {
694 let scalar = FIXED_SCALAR as QuantityRaw;
695 let price = Price::from_raw((scalar + 1) as PriceRaw, FIXED_PRECISION);
696 let size = Quantity::from_raw(scalar + 1, FIXED_PRECISION);
697 let mut level = BookLevel::new(BookPrice::new(price, OrderSide::Buy));
698 level.add(BookOrder::new(OrderSide::Buy, price, size, 0));
699
700 assert_eq!(level.exposure_raw(), scalar + 2);
701 }
702
703 #[rstest]
704 fn test_exposure_raw_accumulates_exactly() {
705 let mut level = BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSide::Buy));
706 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
707 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
708
709 level.add(order1);
710 level.add(order2);
711 assert_eq!(level.exposure_raw(), 60 * FIXED_SCALAR as QuantityRaw);
712 }
713
714 #[cfg(not(feature = "high-precision"))]
715 #[rstest]
716 fn test_exposure_raw_preserves_non_saturating_raw_units() {
717 let price = Price::from("9007199253.999999999");
718 let size = Quantity::from("2.000000001");
719 let mut level = BookLevel::new(BookPrice::new(price, OrderSide::Buy));
720 level.add(BookOrder::new(OrderSide::Buy, price, size, 0));
721
722 assert_eq!(level.exposure_raw(), 18_014_398_517_007_199_251);
723 }
724
725 #[cfg(feature = "high-precision")]
726 #[rstest]
727 fn test_exposure_raw_avoids_phantom_overflow() {
728 let scalar = FIXED_SCALAR as QuantityRaw;
729 let price_raw = 100_000 * scalar;
730 let size_raw = 100 * scalar;
731
732 assert_eq!(price_raw.checked_mul(size_raw), None);
733 assert_eq!(
734 calculate_exposure_raw(
735 price_raw as PriceRaw,
736 size_raw,
737 FIXED_PRECISION,
738 FIXED_PRECISION,
739 ),
740 10_000_000 * scalar
741 );
742 }
743
744 #[rstest]
745 fn test_exposure_raw_saturates_single_order() {
746 #[cfg(feature = "high-precision")]
747 let (price_str, qty_str) = ("1000000000000.00", "1000000000000.00");
748 #[cfg(not(feature = "high-precision"))]
749 let (price_str, qty_str) = ("100000000.00", "1000000000.00");
750
751 let mut level = BookLevel::new(BookPrice::new(Price::from(price_str), OrderSide::Buy));
752 let order = BookOrder::new(
753 OrderSide::Buy,
754 Price::from(price_str),
755 Quantity::from(qty_str),
756 0,
757 );
758
759 level.add(order);
760
761 assert_eq!(level.exposure_raw(), QuantityRaw::MAX);
762 }
763
764 #[rstest]
765 fn test_exposure_raw_accumulation_saturates() {
766 #[cfg(feature = "high-precision")]
767 let (price_str, qty_str, expected_single) = (
768 "100000000000.0",
769 "200000000000.0",
770 200_000_000_000_000_000_000_000_000_000_000_000_000,
771 );
772 #[cfg(not(feature = "high-precision"))]
773 let (price_str, qty_str, expected_single) =
774 ("2.0", "5000000000.0", 10_000_000_000_000_000_000);
775
776 let mut level = BookLevel::new(BookPrice::new(Price::from(price_str), OrderSide::Buy));
777 level.add(BookOrder::new(
778 OrderSide::Buy,
779 Price::from(price_str),
780 Quantity::from(qty_str),
781 0,
782 ));
783 assert_eq!(level.exposure_raw(), expected_single);
784
785 level.add(BookOrder::new(
786 OrderSide::Buy,
787 Price::from(price_str),
788 Quantity::from(qty_str),
789 1,
790 ));
791 assert_eq!(level.exposure_raw(), QuantityRaw::MAX);
792 }
793
794 #[cfg(feature = "defi")]
795 #[rstest]
796 fn test_exposure_raw_preserves_native_defi_scales() {
797 let price_precision = FIXED_PRECISION + 1;
798 let size_precision = FIXED_PRECISION + 2;
799 let price = Price::from_raw(
800 125 * 10_i128.pow(u32::from(price_precision - 2)),
801 price_precision,
802 );
803 let size = Quantity::from_raw(
804 24 * 10_u128.pow(u32::from(size_precision - 1)),
805 size_precision,
806 );
807 let mut level = BookLevel::new(BookPrice::new(price, OrderSide::Buy));
808 level.add(BookOrder::new(OrderSide::Buy, price, size, 0));
809
810 assert_eq!(level.exposure_raw(), 3 * FIXED_SCALAR as QuantityRaw);
811 }
812
813 #[cfg(feature = "defi")]
814 #[rstest]
815 #[case::native_price(1_250_000_000_000_000_000, 24_000_000_000_000_000, 18, 8)]
816 #[case::native_size(12_500_000_000_000_000, 2_400_000_000_000_000_000, 8, 18)]
817 fn test_exposure_raw_preserves_mixed_defi_scales(
818 #[case] price_raw: PriceRaw,
819 #[case] size_raw: QuantityRaw,
820 #[case] price_precision: u8,
821 #[case] size_precision: u8,
822 ) {
823 assert_eq!(
824 calculate_exposure_raw(price_raw, size_raw, price_precision, size_precision),
825 3 * FIXED_SCALAR as QuantityRaw
826 );
827 }
828}