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 = "posei_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]
128 pub fn exposure_raw(&self) -> QuantityRaw {
129 self.orders
130 .values()
131 .map(|o| {
132 let exposure_f64 = o.price.as_f64() * o.size.as_f64();
133 debug_assert!(
134 exposure_f64.is_finite(),
135 "Exposure calculation resulted in non-finite value for order {}: price={}, size={}",
136 o.order_id,
137 o.price,
138 o.size
139 );
140 (exposure_f64 * FIXED_SCALAR) as QuantityRaw
141 })
142 .sum()
143 }
144
145 pub fn add_bulk(&mut self, orders: Vec<BookOrder>) {
147 for order in orders {
148 self.add(order);
149 }
150 }
151
152 pub fn add(&mut self, order: BookOrder) {
154 debug_assert_eq!(order.price, self.price.value);
155 debug_assert!(
156 order.size.is_positive(),
157 "Order size must be positive: {}",
158 order.size
159 );
160
161 self.orders.insert(order.order_id, order);
162 }
163
164 pub fn update(&mut self, order: BookOrder) {
167 debug_assert_eq!(order.price, self.price.value);
168
169 if order.size.raw == 0 {
170 self.orders.shift_remove(&order.order_id);
172 } else {
173 debug_assert!(
174 order.size.is_positive(),
175 "Order size must be positive: {}",
176 order.size
177 );
178 self.orders.insert(order.order_id, order);
179 }
180 }
181
182 pub fn delete(&mut self, order: &BookOrder) {
184 self.orders.shift_remove(&order.order_id);
185 }
186
187 pub fn remove_by_id(&mut self, order_id: OrderId, sequence: u64, ts_event: UnixNanos) {
193 assert!(
194 self.orders.shift_remove(&order_id).is_some(),
195 "{}",
196 &BookIntegrityError::OrderNotFound(order_id, sequence, ts_event)
197 );
198 }
199}
200
201impl PartialEq for BookLevel {
202 fn eq(&self, other: &Self) -> bool {
203 self.price == other.price
204 }
205}
206
207impl PartialOrd for BookLevel {
208 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
209 Some(self.cmp(other))
210 }
211}
212
213impl Ord for BookLevel {
214 fn cmp(&self, other: &Self) -> Ordering {
215 self.price.cmp(&other.price)
216 }
217}
218
219#[cfg(test)]
223mod tests {
224 use rstest::rstest;
225 use rust_decimal_macros::dec;
226
227 use crate::{
228 data::order::BookOrder,
229 enums::{OrderSide, OrderSideSpecified},
230 orderbook::{BookLevel, BookPrice},
231 types::{Price, Quantity, fixed::FIXED_SCALAR, quantity::QuantityRaw},
232 };
233
234 #[rstest]
235 fn test_empty_level() {
236 let level = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
237 assert!(level.first().is_none());
238 assert_eq!(level.side(), OrderSideSpecified::Buy);
239 }
240
241 #[rstest]
242 fn test_level_from_order() {
243 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
244 let level = BookLevel::from_order(order);
245
246 assert_eq!(level.price.value, Price::from("1.00"));
247 assert_eq!(level.price.side, OrderSideSpecified::Buy);
248 assert_eq!(level.len(), 1);
249 assert_eq!(level.first().unwrap(), &order);
250 assert_eq!(level.size(), 10.0);
251 }
252
253 #[rstest]
254 #[should_panic(expected = "assertion `left == right` failed")]
255 fn test_add_order_incorrect_price_level() {
256 let mut level =
257 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
258 let incorrect_price_order =
259 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 1);
260 level.add(incorrect_price_order);
261 }
262
263 #[rstest]
264 #[should_panic(expected = "assertion `left == right` failed")]
265 fn test_add_bulk_orders_incorrect_price() {
266 let mut level =
267 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
268 let orders = vec![
269 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1),
270 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 2), ];
272 level.add_bulk(orders);
273 }
274
275 #[rstest]
276 fn test_add_bulk_empty() {
277 let mut level =
278 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
279 level.add_bulk(vec![]);
280 assert!(level.is_empty());
281 }
282
283 #[rstest]
284 fn test_comparisons_bid_side() {
285 let level0 = BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
286 let level1 = BookLevel::new(BookPrice::new(Price::from("1.01"), OrderSideSpecified::Buy));
287 assert_eq!(level0, level0);
288 assert!(level0 > level1);
289 }
290
291 #[rstest]
292 fn test_comparisons_ask_side() {
293 let level0 = BookLevel::new(BookPrice::new(
294 Price::from("1.00"),
295 OrderSideSpecified::Sell,
296 ));
297 let level1 = BookLevel::new(BookPrice::new(
298 Price::from("1.01"),
299 OrderSideSpecified::Sell,
300 ));
301 assert_eq!(level0, level0);
302 assert!(level0 < level1);
303 }
304
305 #[rstest]
306 fn test_book_level_sorting() {
307 let mut levels = vec![
308 BookLevel::new(BookPrice::new(
309 Price::from("1.00"),
310 OrderSideSpecified::Sell,
311 )),
312 BookLevel::new(BookPrice::new(
313 Price::from("1.02"),
314 OrderSideSpecified::Sell,
315 )),
316 BookLevel::new(BookPrice::new(
317 Price::from("1.01"),
318 OrderSideSpecified::Sell,
319 )),
320 ];
321 levels.sort();
322 assert_eq!(levels[0].price.value, Price::from("1.00"));
323 assert_eq!(levels[1].price.value, Price::from("1.01"));
324 assert_eq!(levels[2].price.value, Price::from("1.02"));
325 }
326
327 #[rstest]
328 fn test_add_single_order() {
329 let mut level =
330 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
331 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
332
333 level.add(order);
334 assert!(!level.is_empty());
335 assert_eq!(level.len(), 1);
336 assert_eq!(level.size(), 10.0);
337 assert_eq!(level.first().unwrap(), &order);
338 }
339
340 #[rstest]
341 fn test_add_multiple_orders() {
342 let mut level =
343 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
344 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
345 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
346
347 level.add(order1);
348 level.add(order2);
349 assert_eq!(level.len(), 2);
350 assert_eq!(level.size(), 30.0);
351 assert_eq!(level.exposure(), 60.0);
352 assert_eq!(level.first().unwrap(), &order1);
353 }
354
355 #[rstest]
356 fn test_get_orders() {
357 let mut level =
358 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
359 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
360 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
361
362 level.add(order1);
363 level.add(order2);
364
365 let orders = level.get_orders();
366 assert_eq!(orders.len(), 2);
367 assert_eq!(orders[0], order1); assert_eq!(orders[1], order2);
369 }
370
371 #[rstest]
372 fn test_iter_returns_fifo() {
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 level.add(order1);
378 level.add(order2);
379
380 let orders: Vec<_> = level.iter().copied().collect();
381 assert_eq!(orders, vec![order1, order2]);
382 }
383
384 #[rstest]
385 fn test_update_order() {
386 let mut level =
387 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
388 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
389 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 0);
390
391 level.add(order1);
392 level.update(order2);
393 assert_eq!(level.len(), 1);
394 assert_eq!(level.size(), 20.0);
395 assert_eq!(level.exposure(), 20.0);
396 }
397
398 #[rstest]
399 fn test_update_inserts_if_missing() {
400 let mut level =
401 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
402 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
403 level.update(order);
404 assert_eq!(level.len(), 1);
405 assert_eq!(level.first().unwrap(), &order);
406 }
407
408 #[rstest]
409 fn test_update_zero_size_nonexistent() {
410 let mut level =
411 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
412 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 1);
413 level.update(order);
414 assert_eq!(level.len(), 0);
415 }
416
417 #[rstest]
418 fn test_fifo_order_after_updates() {
419 let mut level =
420 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
421
422 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
423 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
424
425 level.add(order1);
426 level.add(order2);
427
428 let updated_order1 =
430 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
431 level.update(updated_order1);
432
433 let orders = level.get_orders();
434 assert_eq!(orders.len(), 2);
435 assert_eq!(orders[0], updated_order1); assert_eq!(orders[1], order2); }
438
439 #[rstest]
440 fn test_insertion_order_after_mixed_operations() {
441 let mut level =
442 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
443 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
444 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(20), 2);
445 let order3 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(30), 3);
446
447 level.add(order1);
448 level.add(order2);
449 level.add(order3);
450
451 let updated_order2 =
453 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(25), 2);
454 level.update(updated_order2);
455
456 level.delete(&order1);
458
459 let orders = level.get_orders();
460 assert_eq!(orders, vec![updated_order2, order3]);
461 }
462
463 #[rstest]
464 #[should_panic(expected = "assertion `left == right` failed")]
465 fn test_update_order_incorrect_price() {
466 let mut level =
467 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
468
469 let initial_order =
471 BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
472 level.add(initial_order);
473
474 let updated_order =
476 BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
477 level.update(updated_order);
478 }
479
480 #[rstest]
481 fn test_update_order_with_zero_size() {
482 let mut level =
483 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
484 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
485 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::zero(0), 0);
486
487 level.add(order1);
488 level.update(order2);
489 assert_eq!(level.len(), 0);
490 assert_eq!(level.size(), 0.0);
491 assert_eq!(level.exposure(), 0.0);
492 }
493
494 #[rstest]
495 fn test_delete_nonexistent_order() {
496 let mut level =
497 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
498 let order = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 1);
499 level.delete(&order);
500 assert_eq!(level.len(), 0);
501 }
502
503 #[rstest]
504 fn test_delete_order() {
505 let mut level =
506 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
507 let order1_id = 0;
508 let order1 = BookOrder::new(
509 OrderSide::Buy,
510 Price::from("1.00"),
511 Quantity::from(10),
512 order1_id,
513 );
514 let order2_id = 1;
515 let order2 = BookOrder::new(
516 OrderSide::Buy,
517 Price::from("1.00"),
518 Quantity::from(20),
519 order2_id,
520 );
521
522 level.add(order1);
523 level.add(order2);
524 level.delete(&order1);
525 assert_eq!(level.len(), 1);
526 assert_eq!(level.size(), 20.0);
527 assert!(level.orders.contains_key(&order2_id));
528 assert_eq!(level.exposure(), 20.0);
529 }
530
531 #[rstest]
532 fn test_remove_order_by_id() {
533 let mut level =
534 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
535 let order1_id = 0;
536 let order1 = BookOrder::new(
537 OrderSide::Buy,
538 Price::from("1.00"),
539 Quantity::from(10),
540 order1_id,
541 );
542 let order2_id = 1;
543 let order2 = BookOrder::new(
544 OrderSide::Buy,
545 Price::from("1.00"),
546 Quantity::from(20),
547 order2_id,
548 );
549
550 level.add(order1);
551 level.add(order2);
552 level.remove_by_id(order2_id, 0, 0.into());
553 assert_eq!(level.len(), 1);
554 assert!(level.orders.contains_key(&order1_id));
555 assert_eq!(level.size(), 10.0);
556 assert_eq!(level.exposure(), 10.0);
557 }
558
559 #[rstest]
560 fn test_add_bulk_orders() {
561 let mut level =
562 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
563 let order1_id = 0;
564 let order1 = BookOrder::new(
565 OrderSide::Buy,
566 Price::from("2.00"),
567 Quantity::from(10),
568 order1_id,
569 );
570 let order2_id = 1;
571 let order2 = BookOrder::new(
572 OrderSide::Buy,
573 Price::from("2.00"),
574 Quantity::from(20),
575 order2_id,
576 );
577
578 let orders = vec![order1, order2];
579 level.add_bulk(orders);
580 assert_eq!(level.len(), 2);
581 assert_eq!(level.size(), 30.0);
582 assert_eq!(level.exposure(), 60.0);
583 }
584
585 #[rstest]
586 fn test_maximum_order_id() {
587 let mut level =
588 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
589
590 let order = BookOrder::new(
591 OrderSide::Buy,
592 Price::from("1.00"),
593 Quantity::from(10),
594 u64::MAX,
595 );
596 level.add(order);
597
598 assert_eq!(level.len(), 1);
599 assert_eq!(level.first().unwrap(), &order);
600 }
601
602 #[rstest]
603 #[should_panic(
604 expected = "Integrity error: order not found: order_id=1, sequence=2, ts_event=3"
605 )]
606 fn test_remove_nonexistent_order() {
607 let mut level =
608 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
609 level.remove_by_id(1, 2, 3.into());
610 }
611
612 #[rstest]
613 fn test_size() {
614 let mut level =
615 BookLevel::new(BookPrice::new(Price::from("1.00"), OrderSideSpecified::Buy));
616 let order1 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(10), 0);
617 let order2 = BookOrder::new(OrderSide::Buy, Price::from("1.00"), Quantity::from(15), 1);
618
619 level.add(order1);
620 level.add(order2);
621 assert_eq!(level.size(), 25.0);
622 }
623
624 #[rstest]
625 fn test_size_raw() {
626 let mut level =
627 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
628 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
629 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
630
631 level.add(order1);
632 level.add(order2);
633 assert_eq!(
634 level.size_raw(),
635 (30.0 * FIXED_SCALAR).round() as QuantityRaw
636 );
637 }
638
639 #[rstest]
640 fn test_size_decimal() {
641 let mut level =
642 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
643 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
644 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
645
646 level.add(order1);
647 level.add(order2);
648 assert_eq!(level.size_decimal(), dec!(30.0));
649 }
650
651 #[rstest]
652 fn test_exposure() {
653 let mut level =
654 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
655 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
656 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
657
658 level.add(order1);
659 level.add(order2);
660 assert_eq!(level.exposure(), 60.0);
661 }
662
663 #[rstest]
664 fn test_exposure_raw() {
665 let mut level =
666 BookLevel::new(BookPrice::new(Price::from("2.00"), OrderSideSpecified::Buy));
667 let order1 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(10), 0);
668 let order2 = BookOrder::new(OrderSide::Buy, Price::from("2.00"), Quantity::from(20), 1);
669
670 level.add(order1);
671 level.add(order2);
672 assert_eq!(
673 level.exposure_raw(),
674 (60.0 * FIXED_SCALAR).round() as QuantityRaw
675 );
676 }
677}