1use std::{collections::HashMap, fmt::Display};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, serialization::Serializable};
22use serde::{Deserialize, Serialize};
23use smallvec::SmallVec;
24
25use super::{ARROW_TIMESTAMP_NANOSECOND, HasTsInit, order::BookOrder};
26use crate::{
27 enums::OrderSide,
28 identifiers::InstrumentId,
29 types::{PRICE_ERROR, PRICE_UNDEF},
30};
31
32pub const DEPTH10_LEN: usize = 10;
34pub const DEPTH_INLINE_LEN: usize = DEPTH10_LEN;
36
37const DEPTH_SIDE_LIST: &str = "List(Struct(price: Decimal128(38, 16), size: Decimal128(38, 16), count: UInt32, order_id: UInt64))";
38
39#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
47#[serde(try_from = "OrderBookDepthRaw")]
48#[cfg_attr(
49 feature = "python",
50 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
51)]
52#[cfg_attr(
53 feature = "python",
54 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
55)]
56pub struct OrderBookDepth {
57 pub instrument_id: InstrumentId,
59 pub bids: SmallVec<[BookOrder; DEPTH_INLINE_LEN]>,
61 pub asks: SmallVec<[BookOrder; DEPTH_INLINE_LEN]>,
63 pub bid_counts: SmallVec<[u32; DEPTH_INLINE_LEN]>,
65 pub ask_counts: SmallVec<[u32; DEPTH_INLINE_LEN]>,
67 pub flags: u8,
69 pub sequence: u64,
71 pub ts_event: UnixNanos,
73 pub ts_init: UnixNanos,
75}
76
77#[derive(Deserialize)]
78struct OrderBookDepthRaw {
79 instrument_id: InstrumentId,
80 bids: SmallVec<[BookOrder; DEPTH_INLINE_LEN]>,
81 asks: SmallVec<[BookOrder; DEPTH_INLINE_LEN]>,
82 bid_counts: SmallVec<[u32; DEPTH_INLINE_LEN]>,
83 ask_counts: SmallVec<[u32; DEPTH_INLINE_LEN]>,
84 flags: u8,
85 sequence: u64,
86 ts_event: UnixNanos,
87 ts_init: UnixNanos,
88}
89
90impl TryFrom<OrderBookDepthRaw> for OrderBookDepth {
91 type Error = anyhow::Error;
92
93 fn try_from(value: OrderBookDepthRaw) -> Result<Self, Self::Error> {
94 Self::new_checked(
95 value.instrument_id,
96 value.bids,
97 value.asks,
98 value.bid_counts,
99 value.ask_counts,
100 value.flags,
101 value.sequence,
102 value.ts_event,
103 value.ts_init,
104 )
105 }
106}
107
108impl OrderBookDepth {
109 #[expect(clippy::too_many_arguments)]
115 #[must_use]
116 pub fn new<B, A, BC, AC>(
117 instrument_id: InstrumentId,
118 bids: B,
119 asks: A,
120 bid_counts: BC,
121 ask_counts: AC,
122 flags: u8,
123 sequence: u64,
124 ts_event: UnixNanos,
125 ts_init: UnixNanos,
126 ) -> Self
127 where
128 B: IntoIterator<Item = BookOrder>,
129 A: IntoIterator<Item = BookOrder>,
130 BC: IntoIterator<Item = u32>,
131 AC: IntoIterator<Item = u32>,
132 {
133 Self::new_checked(
134 instrument_id,
135 bids,
136 asks,
137 bid_counts,
138 ask_counts,
139 flags,
140 sequence,
141 ts_event,
142 ts_init,
143 )
144 .unwrap_or_else(|e| panic!("{e}"))
145 }
146
147 #[expect(clippy::too_many_arguments)]
155 pub fn new_checked<B, A, BC, AC>(
156 instrument_id: InstrumentId,
157 bids: B,
158 asks: A,
159 bid_counts: BC,
160 ask_counts: AC,
161 flags: u8,
162 sequence: u64,
163 ts_event: UnixNanos,
164 ts_init: UnixNanos,
165 ) -> anyhow::Result<Self>
166 where
167 B: IntoIterator<Item = BookOrder>,
168 A: IntoIterator<Item = BookOrder>,
169 BC: IntoIterator<Item = u32>,
170 AC: IntoIterator<Item = u32>,
171 {
172 let bids = bids
173 .into_iter()
174 .collect::<SmallVec<[BookOrder; DEPTH_INLINE_LEN]>>();
175 let asks = asks
176 .into_iter()
177 .collect::<SmallVec<[BookOrder; DEPTH_INLINE_LEN]>>();
178 let bid_counts = bid_counts
179 .into_iter()
180 .collect::<SmallVec<[u32; DEPTH_INLINE_LEN]>>();
181 let ask_counts = ask_counts
182 .into_iter()
183 .collect::<SmallVec<[u32; DEPTH_INLINE_LEN]>>();
184 anyhow::ensure!(
185 bids.len() == bid_counts.len(),
186 "bid order and count lengths must match"
187 );
188 anyhow::ensure!(
189 asks.len() == ask_counts.len(),
190 "ask order and count lengths must match"
191 );
192 let (bids, bid_counts): (
193 SmallVec<[BookOrder; DEPTH_INLINE_LEN]>,
194 SmallVec<[u32; DEPTH_INLINE_LEN]>,
195 ) = bids
196 .into_iter()
197 .zip(bid_counts)
198 .filter(|(order, _)| is_depth_level_present(order))
199 .unzip();
200 let (asks, ask_counts): (
201 SmallVec<[BookOrder; DEPTH_INLINE_LEN]>,
202 SmallVec<[u32; DEPTH_INLINE_LEN]>,
203 ) = asks
204 .into_iter()
205 .zip(ask_counts)
206 .filter(|(order, _)| is_depth_level_present(order))
207 .unzip();
208 anyhow::ensure!(
209 bids.iter().all(|order| order.side == Some(OrderSide::Buy)),
210 "bid levels must have Buy side"
211 );
212 anyhow::ensure!(
213 asks.iter().all(|order| order.side == Some(OrderSide::Sell)),
214 "ask levels must have Sell side"
215 );
216
217 Ok(Self {
218 instrument_id,
219 bids,
220 asks,
221 bid_counts,
222 ask_counts,
223 flags,
224 sequence,
225 ts_event,
226 ts_init,
227 })
228 }
229
230 #[must_use]
232 pub fn get_metadata(
233 instrument_id: &InstrumentId,
234 price_precision: u8,
235 size_precision: u8,
236 ) -> HashMap<String, String> {
237 let mut metadata = HashMap::new();
238 metadata.insert("instrument_id".to_string(), instrument_id.to_string());
239 metadata.insert("price_precision".to_string(), price_precision.to_string());
240 metadata.insert("size_precision".to_string(), size_precision.to_string());
241 metadata
242 }
243
244 #[must_use]
246 pub fn get_fields() -> IndexMap<String, String> {
247 let mut metadata = IndexMap::new();
248 metadata.insert("bids".to_string(), DEPTH_SIDE_LIST.to_string());
249 metadata.insert("asks".to_string(), DEPTH_SIDE_LIST.to_string());
250 metadata.insert("flags".to_string(), "UInt8".to_string());
251 metadata.insert("sequence".to_string(), "UInt64".to_string());
252 metadata.insert(
253 "ts_event".to_string(),
254 ARROW_TIMESTAMP_NANOSECOND.to_string(),
255 );
256 metadata.insert(
257 "ts_init".to_string(),
258 ARROW_TIMESTAMP_NANOSECOND.to_string(),
259 );
260 metadata
261 }
262}
263
264#[must_use]
266pub fn is_depth_level_present(order: &BookOrder) -> bool {
267 order.side.is_some()
268 && order.price.raw != PRICE_UNDEF
269 && order.price.raw != PRICE_ERROR
270 && order.size.is_positive()
271}
272
273impl Display for OrderBookDepth {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 write!(
277 f,
278 "{},flags={},sequence={},ts_event={},ts_init={}",
279 self.instrument_id, self.flags, self.sequence, self.ts_event, self.ts_init
280 )
281 }
282}
283
284impl Serializable for OrderBookDepth {}
285
286impl HasTsInit for OrderBookDepth {
287 fn ts_init(&self) -> UnixNanos {
288 self.ts_init
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use std::{
295 collections::hash_map::DefaultHasher,
296 hash::{Hash, Hasher},
297 };
298
299 use rstest::rstest;
300 use serde_json;
301
302 use super::*;
303 use crate::{
304 data::{
305 order::{BookOrder, NULL_ORDER},
306 stubs::*,
307 },
308 enums::OrderSide,
309 types::{Price, QUANTITY_UNDEF, Quantity, price::PriceRaw},
310 };
311
312 fn create_test_book_order(
313 side: OrderSide,
314 price: &str,
315 size: &str,
316 order_id: u64,
317 ) -> BookOrder {
318 BookOrder::new(side, Price::from(price), Quantity::from(size), order_id)
319 }
320
321 fn create_test_depth() -> OrderBookDepth {
322 let instrument_id = InstrumentId::from("EURUSD.SIM");
323
324 let bids = [
326 create_test_book_order(OrderSide::Buy, "1.0500", "100000", 1),
327 create_test_book_order(OrderSide::Buy, "1.0499", "150000", 2),
328 create_test_book_order(OrderSide::Buy, "1.0498", "200000", 3),
329 create_test_book_order(OrderSide::Buy, "1.0497", "125000", 4),
330 create_test_book_order(OrderSide::Buy, "1.0496", "175000", 5),
331 create_test_book_order(OrderSide::Buy, "1.0495", "100000", 6),
332 create_test_book_order(OrderSide::Buy, "1.0494", "225000", 7),
333 create_test_book_order(OrderSide::Buy, "1.0493", "150000", 8),
334 create_test_book_order(OrderSide::Buy, "1.0492", "300000", 9),
335 create_test_book_order(OrderSide::Buy, "1.0491", "175000", 10),
336 ];
337
338 let asks = [
340 create_test_book_order(OrderSide::Sell, "1.0501", "100000", 11),
341 create_test_book_order(OrderSide::Sell, "1.0502", "125000", 12),
342 create_test_book_order(OrderSide::Sell, "1.0503", "150000", 13),
343 create_test_book_order(OrderSide::Sell, "1.0504", "175000", 14),
344 create_test_book_order(OrderSide::Sell, "1.0505", "200000", 15),
345 create_test_book_order(OrderSide::Sell, "1.0506", "100000", 16),
346 create_test_book_order(OrderSide::Sell, "1.0507", "250000", 17),
347 create_test_book_order(OrderSide::Sell, "1.0508", "125000", 18),
348 create_test_book_order(OrderSide::Sell, "1.0509", "300000", 19),
349 create_test_book_order(OrderSide::Sell, "1.0510", "175000", 20),
350 ];
351
352 let bid_counts = [1, 2, 1, 3, 1, 2, 1, 4, 1, 2];
353 let ask_counts = [2, 1, 3, 1, 2, 1, 4, 1, 2, 3];
354
355 OrderBookDepth::new(
356 instrument_id,
357 bids,
358 asks,
359 bid_counts,
360 ask_counts,
361 32, 12345, UnixNanos::from(1_000_000_000), UnixNanos::from(2_000_000_000), )
366 }
367
368 fn create_empty_depth() -> OrderBookDepth {
369 let instrument_id = InstrumentId::from("EMPTY.TEST");
370
371 let empty_bid = create_test_book_order(OrderSide::Buy, "0.0", "0", 0);
373 let empty_ask = create_test_book_order(OrderSide::Sell, "0.0", "0", 0);
374
375 OrderBookDepth::new(
376 instrument_id,
377 [empty_bid; DEPTH10_LEN],
378 [empty_ask; DEPTH10_LEN],
379 [0; DEPTH10_LEN],
380 [0; DEPTH10_LEN],
381 0,
382 0,
383 UnixNanos::from(0),
384 UnixNanos::from(0),
385 )
386 }
387
388 #[rstest]
389 fn test_order_book_depths_new() {
390 let depth = create_test_depth();
391
392 assert_eq!(depth.instrument_id, InstrumentId::from("EURUSD.SIM"));
393 assert_eq!(depth.bids.len(), DEPTH10_LEN);
394 assert_eq!(depth.asks.len(), DEPTH10_LEN);
395 assert_eq!(depth.bid_counts.len(), DEPTH10_LEN);
396 assert_eq!(depth.ask_counts.len(), DEPTH10_LEN);
397 assert_eq!(depth.flags, 32);
398 assert_eq!(depth.sequence, 12345);
399 assert_eq!(depth.ts_event, UnixNanos::from(1_000_000_000));
400 assert_eq!(depth.ts_init, UnixNanos::from(2_000_000_000));
401 }
402
403 #[rstest]
404 #[case::price(true)]
405 #[case::size(false)]
406 fn test_depths_drop_partial_undefined_level(#[case] price_undefined: bool) {
407 let instrument_id = InstrumentId::from("AUD/USD.SIM");
408 let bid = BookOrder::new(
409 OrderSide::Buy,
410 Price::from("1.23"),
411 Quantity::from("100.00"),
412 1,
413 );
414 let ask = BookOrder::new(
415 OrderSide::Sell,
416 Price::from("1.24"),
417 Quantity::from("100.00"),
418 2,
419 );
420 let mut asks = [ask; DEPTH10_LEN];
421 if price_undefined {
422 asks[1].price = Price::from_raw(PRICE_UNDEF, 0);
423 } else {
424 asks[1].size = Quantity::from_raw(QUANTITY_UNDEF, 0);
425 }
426 let depth = OrderBookDepth::new(
427 instrument_id,
428 [bid; DEPTH10_LEN],
429 asks,
430 [1; DEPTH10_LEN],
431 [1; DEPTH10_LEN],
432 0,
433 1,
434 1.into(),
435 1.into(),
436 );
437 let expected = OrderBookDepth::new(
438 instrument_id,
439 [bid; DEPTH10_LEN],
440 [ask; DEPTH10_LEN - 1],
441 [1; DEPTH10_LEN],
442 [1; DEPTH10_LEN - 1],
443 0,
444 1,
445 1.into(),
446 1.into(),
447 );
448
449 assert_eq!(depth, expected);
450 }
451
452 #[rstest]
453 fn test_order_book_depths_new_checked_rejects_mismatched_counts() {
454 let result = OrderBookDepth::new_checked(
455 InstrumentId::from("EURUSD.SIM"),
456 Vec::<BookOrder>::new(),
457 Vec::<BookOrder>::new(),
458 vec![1],
459 Vec::<u32>::new(),
460 0,
461 0,
462 UnixNanos::from(1),
463 UnixNanos::from(2),
464 );
465
466 assert_eq!(
467 result.unwrap_err().to_string(),
468 "bid order and count lengths must match",
469 );
470 }
471
472 #[rstest]
473 fn test_order_book_depths_new_checked_rejects_sell_bid() {
474 let result = OrderBookDepth::new_checked(
475 InstrumentId::from("EURUSD.SIM"),
476 [create_test_book_order(
477 OrderSide::Sell,
478 "1.0500",
479 "100000",
480 1,
481 )],
482 [],
483 [1],
484 [],
485 0,
486 0,
487 UnixNanos::from(1),
488 UnixNanos::from(2),
489 );
490
491 assert_eq!(
492 result.unwrap_err().to_string(),
493 "bid levels must have Buy side",
494 );
495 }
496
497 #[rstest]
498 fn test_order_book_depths_new_checked_rejects_buy_ask() {
499 let result = OrderBookDepth::new_checked(
500 InstrumentId::from("EURUSD.SIM"),
501 [],
502 [create_test_book_order(
503 OrderSide::Buy,
504 "1.0501",
505 "100000",
506 2,
507 )],
508 [],
509 [1],
510 0,
511 0,
512 UnixNanos::from(1),
513 UnixNanos::from(2),
514 );
515
516 assert_eq!(
517 result.unwrap_err().to_string(),
518 "ask levels must have Sell side",
519 );
520 }
521
522 #[rstest]
523 #[case(PRICE_UNDEF)]
524 #[case(PRICE_ERROR)]
525 fn test_depth_level_with_sentinel_price_is_absent(#[case] raw: PriceRaw) {
526 let order = BookOrder::new(
527 OrderSide::Buy,
528 Price::from_raw(raw, 0),
529 Quantity::from("1"),
530 1,
531 );
532
533 assert!(!is_depth_level_present(&order));
534 }
535
536 #[rstest]
537 #[case(PRICE_UNDEF)]
538 #[case(PRICE_ERROR)]
539 fn test_order_book_depths_new_checked_drops_sentinel_price(#[case] raw: PriceRaw) {
540 let order = BookOrder::new(
541 OrderSide::Buy,
542 Price::from_raw(raw, 0),
543 Quantity::from("1"),
544 1,
545 );
546
547 let depth = OrderBookDepth::new_checked(
548 InstrumentId::from("EURUSD.SIM"),
549 [order],
550 [],
551 [7],
552 [],
553 0,
554 0,
555 UnixNanos::from(1),
556 UnixNanos::from(2),
557 )
558 .unwrap();
559
560 assert!(depth.bids.is_empty());
561 assert!(depth.bid_counts.is_empty());
562 }
563
564 #[rstest]
565 fn test_order_book_depths_deserialize_rejects_mismatched_counts() {
566 let mut value = serde_json::to_value(create_test_depth()).unwrap();
567 value["bid_counts"].as_array_mut().unwrap().pop();
568
569 let error = serde_json::from_value::<OrderBookDepth>(value).unwrap_err();
570
571 assert!(
572 error
573 .to_string()
574 .contains("bid order and count lengths must match")
575 );
576 }
577
578 #[rstest]
579 fn test_order_book_depths_deserialize_drops_legacy_padding_and_zero_size_levels() {
580 let mut legacy = create_test_depth();
581 legacy.bids[1] = NULL_ORDER;
582 legacy.bids[2].size = Quantity::zero(legacy.bids[2].size.precision);
583 let payload = serde_json::to_string(&legacy).unwrap();
584
585 let decoded = serde_json::from_str::<OrderBookDepth>(&payload).unwrap();
586 let bid_order_ids = decoded
587 .bids
588 .iter()
589 .map(|order| order.order_id)
590 .collect::<Vec<_>>();
591
592 assert_eq!(bid_order_ids, vec![1, 4, 5, 6, 7, 8, 9, 10]);
593 assert_eq!(decoded.bid_counts.as_slice(), &[1, 3, 1, 2, 1, 4, 1, 2]);
594 assert_eq!(decoded.asks, legacy.asks);
595 assert_eq!(decoded.ask_counts, legacy.ask_counts);
596 assert_eq!(decoded.flags, legacy.flags);
597 assert_eq!(decoded.sequence, legacy.sequence);
598 assert_eq!(decoded.ts_event, legacy.ts_event);
599 assert_eq!(decoded.ts_init, legacy.ts_init);
600 }
601
602 #[rstest]
603 fn test_order_book_depths_msgpack_deserialize_drops_legacy_padding_and_zero_size_levels() {
604 let mut legacy = create_test_depth();
605 legacy.bids[1] = NULL_ORDER;
606 legacy.bids[2].size = Quantity::zero(legacy.bids[2].size.precision);
607 let payload = rmp_serde::to_vec_named(&legacy).unwrap();
608
609 let decoded = rmp_serde::from_slice::<OrderBookDepth>(&payload).unwrap();
610
611 assert_eq!(
612 decoded
613 .bids
614 .iter()
615 .map(|order| order.order_id)
616 .collect::<Vec<_>>(),
617 vec![1, 4, 5, 6, 7, 8, 9, 10],
618 );
619 assert_eq!(decoded.bid_counts.as_slice(), &[1, 3, 1, 2, 1, 4, 1, 2]);
620 assert_eq!(decoded.asks, legacy.asks);
621 assert_eq!(decoded.ask_counts, legacy.ask_counts);
622 assert_eq!(decoded.flags, legacy.flags);
623 assert_eq!(decoded.sequence, legacy.sequence);
624 assert_eq!(decoded.ts_event, legacy.ts_event);
625 assert_eq!(decoded.ts_init, legacy.ts_init);
626 }
627
628 #[rstest]
629 fn test_order_book_depths_new_with_all_parameters() {
630 let instrument_id = InstrumentId::from("GBPUSD.SIM");
631 let bid = create_test_book_order(OrderSide::Buy, "1.2500", "50000", 1);
632 let ask = create_test_book_order(OrderSide::Sell, "1.2501", "75000", 2);
633 let flags = 64u8;
634 let sequence = 999u64;
635 let ts_event = UnixNanos::from(5_000_000_000);
636 let ts_init = UnixNanos::from(6_000_000_000);
637
638 let depth = OrderBookDepth::new(
639 instrument_id,
640 [bid; DEPTH10_LEN],
641 [ask; DEPTH10_LEN],
642 [5; DEPTH10_LEN],
643 [3; DEPTH10_LEN],
644 flags,
645 sequence,
646 ts_event,
647 ts_init,
648 );
649
650 assert_eq!(depth.instrument_id, instrument_id);
651 assert_eq!(depth.bids[0], bid);
652 assert_eq!(depth.asks[0], ask);
653 assert_eq!(depth.bid_counts[0], 5);
654 assert_eq!(depth.ask_counts[0], 3);
655 assert_eq!(depth.flags, flags);
656 assert_eq!(depth.sequence, sequence);
657 assert_eq!(depth.ts_event, ts_event);
658 assert_eq!(depth.ts_init, ts_init);
659 }
660
661 #[rstest]
662 fn test_order_book_depths_lengths() {
663 let depth = create_test_depth();
664
665 assert_eq!(depth.bids.len(), 10);
667 assert_eq!(depth.asks.len(), 10);
668 assert_eq!(depth.bid_counts.len(), 10);
669 assert_eq!(depth.ask_counts.len(), 10);
670 }
671
672 #[rstest]
673 fn test_order_book_depths_indexing() {
674 let depth = create_test_depth();
675
676 assert_eq!(depth.bids[0].price, Price::from("1.0500"));
678 assert_eq!(depth.bids[9].price, Price::from("1.0491"));
679 assert_eq!(depth.asks[0].price, Price::from("1.0501"));
680 assert_eq!(depth.asks[9].price, Price::from("1.0510"));
681 assert_eq!(depth.bid_counts[0], 1);
682 assert_eq!(depth.bid_counts[9], 2);
683 assert_eq!(depth.ask_counts[0], 2);
684 assert_eq!(depth.ask_counts[9], 3);
685 }
686
687 #[rstest]
688 fn test_order_book_depths_bid_ask_ordering() {
689 let depth = create_test_depth();
690
691 for i in 0..9 {
693 assert!(
694 depth.bids[i].price >= depth.bids[i + 1].price,
695 "Bid prices should be in descending order: {} >= {}",
696 depth.bids[i].price,
697 depth.bids[i + 1].price
698 );
699 }
700
701 for i in 0..9 {
703 assert!(
704 depth.asks[i].price <= depth.asks[i + 1].price,
705 "Ask prices should be in ascending order: {} <= {}",
706 depth.asks[i].price,
707 depth.asks[i + 1].price
708 );
709 }
710
711 assert!(
713 depth.bids[0].price < depth.asks[0].price,
714 "Best bid {} should be less than best ask {}",
715 depth.bids[0].price,
716 depth.asks[0].price
717 );
718 }
719
720 #[rstest]
721 fn test_order_book_depths_clone() {
722 let depth1 = create_test_depth();
723 let depth2 = depth1.clone();
724
725 assert_eq!(depth1.instrument_id, depth2.instrument_id);
726 assert_eq!(depth1.bids, depth2.bids);
727 assert_eq!(depth1.asks, depth2.asks);
728 assert_eq!(depth1.bid_counts, depth2.bid_counts);
729 assert_eq!(depth1.ask_counts, depth2.ask_counts);
730 assert_eq!(depth1.flags, depth2.flags);
731 assert_eq!(depth1.sequence, depth2.sequence);
732 assert_eq!(depth1.ts_event, depth2.ts_event);
733 assert_eq!(depth1.ts_init, depth2.ts_init);
734 }
735
736 #[rstest]
737 fn test_order_book_depths_inline_and_spilled_storage() {
738 let inline = create_test_depth();
739 let bid = inline.bids[0];
740 let ask = inline.asks[0];
741 let spilled = OrderBookDepth::new(
742 inline.instrument_id,
743 vec![bid; 25],
744 vec![ask; 25],
745 vec![1; 25],
746 vec![1; 25],
747 inline.flags,
748 inline.sequence,
749 inline.ts_event,
750 inline.ts_init,
751 );
752
753 assert!(!inline.bids.spilled());
754 assert!(!inline.asks.spilled());
755 assert!(!inline.bid_counts.spilled());
756 assert!(!inline.ask_counts.spilled());
757 assert!(spilled.bids.spilled());
758 assert!(spilled.asks.spilled());
759 assert!(spilled.bid_counts.spilled());
760 assert!(spilled.ask_counts.spilled());
761 assert_eq!(spilled.bids.len(), 25);
762 assert_eq!(spilled.asks.len(), 25);
763 }
764
765 #[rstest]
766 fn test_order_book_depths_debug() {
767 let depth = create_test_depth();
768 let debug_str = format!("{depth:?}");
769
770 assert!(debug_str.contains("OrderBookDepth"));
771 assert!(debug_str.contains("EURUSD.SIM"));
772 assert!(debug_str.contains("flags: 32"));
773 assert!(debug_str.contains("sequence: 12345"));
774 }
775
776 #[rstest]
777 fn test_order_book_depths_partial_eq() {
778 let depth1 = create_test_depth();
779 let depth2 = create_test_depth();
780 let depth3 = create_empty_depth();
781
782 assert_eq!(depth1, depth2); assert_ne!(depth1, depth3); assert_ne!(depth2, depth3); }
786
787 #[rstest]
788 fn test_order_book_depths_eq_consistency() {
789 let depth1 = create_test_depth();
790 let depth2 = create_test_depth();
791
792 assert_eq!(depth1, depth2);
793 assert_eq!(depth2, depth1); assert_eq!(depth1, depth1); }
796
797 #[rstest]
798 fn test_order_book_depths_hash() {
799 let depth1 = create_test_depth();
800 let depth2 = create_test_depth();
801
802 let mut hasher1 = DefaultHasher::new();
803 let mut hasher2 = DefaultHasher::new();
804
805 depth1.hash(&mut hasher1);
806 depth2.hash(&mut hasher2);
807
808 assert_eq!(hasher1.finish(), hasher2.finish()); }
810
811 #[rstest]
812 fn test_order_book_depths_hash_different_objects() {
813 let depth1 = create_test_depth();
814 let depth2 = create_empty_depth();
815
816 let mut hasher1 = DefaultHasher::new();
817 let mut hasher2 = DefaultHasher::new();
818
819 depth1.hash(&mut hasher1);
820 depth2.hash(&mut hasher2);
821
822 assert_ne!(hasher1.finish(), hasher2.finish()); }
824
825 #[rstest]
826 fn test_order_book_depths_display() {
827 let depth = create_test_depth();
828 let display_str = format!("{depth}");
829
830 assert!(display_str.contains("EURUSD.SIM"));
831 assert!(display_str.contains("flags=32"));
832 assert!(display_str.contains("sequence=12345"));
833 assert!(display_str.contains("ts_event=1000000000"));
834 assert!(display_str.contains("ts_init=2000000000"));
835 }
836
837 #[rstest]
838 fn test_order_book_depths_display_format() {
839 let depth = create_test_depth();
840 let expected = "EURUSD.SIM,flags=32,sequence=12345,ts_event=1000000000,ts_init=2000000000";
841
842 assert_eq!(format!("{depth}"), expected);
843 }
844
845 #[rstest]
846 fn test_order_book_depths_serialization() {
847 let depth = create_test_depth();
848
849 let json = serde_json::to_string(&depth).unwrap();
851 let deserialized: OrderBookDepth = serde_json::from_str(&json).unwrap();
852
853 assert_eq!(depth, deserialized);
854 }
855
856 #[rstest]
857 fn test_order_book_depths_serializable_trait() {
858 fn assert_serializable<T: Serializable>(_: &T) {}
859
860 let depth = create_test_depth();
861
862 assert_serializable(&depth);
864 }
865
866 #[rstest]
867 fn test_order_book_depths_has_ts_init() {
868 let depth = create_test_depth();
869
870 assert_eq!(depth.ts_init(), UnixNanos::from(2_000_000_000));
871 }
872
873 #[rstest]
874 fn test_order_book_depths_get_metadata() {
875 let instrument_id = InstrumentId::from("EURUSD.SIM");
876 let price_precision = 5u8;
877 let size_precision = 0u8;
878
879 let metadata =
880 OrderBookDepth::get_metadata(&instrument_id, price_precision, size_precision);
881
882 assert_eq!(
883 metadata.get("instrument_id"),
884 Some(&"EURUSD.SIM".to_string())
885 );
886 assert_eq!(metadata.get("price_precision"), Some(&"5".to_string()));
887 assert_eq!(metadata.get("size_precision"), Some(&"0".to_string()));
888 assert_eq!(metadata.len(), 3);
889 }
890
891 #[rstest]
892 fn test_order_book_depths_get_fields() {
893 let fields = OrderBookDepth::get_fields();
894
895 assert_eq!(fields.get("bids"), Some(&DEPTH_SIDE_LIST.to_string()));
896 assert_eq!(fields.get("asks"), Some(&DEPTH_SIDE_LIST.to_string()));
897 assert_eq!(fields.get("flags"), Some(&"UInt8".to_string()));
898 assert_eq!(fields.get("sequence"), Some(&"UInt64".to_string()));
899 assert_eq!(
900 fields.get("ts_event"),
901 Some(&ARROW_TIMESTAMP_NANOSECOND.to_string())
902 );
903 assert_eq!(
904 fields.get("ts_init"),
905 Some(&ARROW_TIMESTAMP_NANOSECOND.to_string())
906 );
907 assert_eq!(fields.get("identifier"), None);
908 assert_eq!(fields.len(), 6);
909 }
910
911 #[rstest]
912 fn test_order_book_depths_get_fields_order() {
913 let fields = OrderBookDepth::get_fields();
914 let keys: Vec<&String> = fields.keys().collect();
915
916 assert_eq!(
917 keys,
918 vec!["bids", "asks", "flags", "sequence", "ts_event", "ts_init",]
919 );
920 }
921
922 #[rstest]
923 fn test_order_book_depths_empty_values() {
924 let depth = create_empty_depth();
925
926 assert_eq!(depth.instrument_id, InstrumentId::from("EMPTY.TEST"));
927 assert_eq!(depth.flags, 0);
928 assert_eq!(depth.sequence, 0);
929 assert_eq!(depth.ts_event, UnixNanos::from(0));
930 assert_eq!(depth.ts_init, UnixNanos::from(0));
931
932 assert!(depth.bids.is_empty());
933 assert!(depth.asks.is_empty());
934 assert!(depth.bid_counts.is_empty());
935 assert!(depth.ask_counts.is_empty());
936 }
937
938 #[rstest]
939 fn test_order_book_depths_max_values() {
940 let instrument_id = InstrumentId::from("MAX.TEST");
941 let max_bid = create_test_book_order(OrderSide::Buy, "999999.99", "999999999", u64::MAX);
942 let max_ask = create_test_book_order(OrderSide::Sell, "1000000.00", "999999999", u64::MAX);
943
944 let depth = OrderBookDepth::new(
945 instrument_id,
946 [max_bid; DEPTH10_LEN],
947 [max_ask; DEPTH10_LEN],
948 [u32::MAX; DEPTH10_LEN],
949 [u32::MAX; DEPTH10_LEN],
950 u8::MAX,
951 u64::MAX,
952 UnixNanos::from(u64::MAX),
953 UnixNanos::from(u64::MAX),
954 );
955
956 assert_eq!(depth.flags, u8::MAX);
957 assert_eq!(depth.sequence, u64::MAX);
958 assert_eq!(depth.ts_event, UnixNanos::from(u64::MAX));
959 assert_eq!(depth.ts_init, UnixNanos::from(u64::MAX));
960
961 for &count in &depth.bid_counts {
962 assert_eq!(count, u32::MAX);
963 }
964
965 for &count in &depth.ask_counts {
966 assert_eq!(count, u32::MAX);
967 }
968 }
969
970 #[rstest]
971 fn test_order_book_depths_different_instruments() {
972 let instruments = [
973 "EURUSD.SIM",
974 "GBPUSD.SIM",
975 "USDJPY.SIM",
976 "AUDUSD.SIM",
977 "USDCHF.SIM",
978 ];
979
980 for instrument_str in &instruments {
981 let instrument_id = InstrumentId::from(*instrument_str);
982 let bid = create_test_book_order(OrderSide::Buy, "1.0000", "100000", 1);
983 let ask = create_test_book_order(OrderSide::Sell, "1.0001", "100000", 2);
984
985 let depth = OrderBookDepth::new(
986 instrument_id,
987 [bid; DEPTH10_LEN],
988 [ask; DEPTH10_LEN],
989 [1; DEPTH10_LEN],
990 [1; DEPTH10_LEN],
991 0,
992 1,
993 UnixNanos::from(1_000_000_000),
994 UnixNanos::from(2_000_000_000),
995 );
996
997 assert_eq!(depth.instrument_id, instrument_id);
998 assert!(format!("{depth}").contains(instrument_str));
999 }
1000 }
1001
1002 #[rstest]
1003 fn test_order_book_depths_realistic_forex_spread() {
1004 let instrument_id = InstrumentId::from("EURUSD.SIM");
1005
1006 let best_bid = create_test_book_order(OrderSide::Buy, "1.08500", "1000000", 1);
1008 let best_ask = create_test_book_order(OrderSide::Sell, "1.08501", "1000000", 2);
1009
1010 let depth = OrderBookDepth::new(
1011 instrument_id,
1012 [best_bid; DEPTH10_LEN],
1013 [best_ask; DEPTH10_LEN],
1014 [5; DEPTH10_LEN], [3; DEPTH10_LEN],
1016 16, 123_456, UnixNanos::from(1_672_531_200_000_000_000), UnixNanos::from(1_672_531_200_000_100_000),
1020 );
1021
1022 assert_eq!(depth.bids[0].price, Price::from("1.08500"));
1023 assert_eq!(depth.asks[0].price, Price::from("1.08501"));
1024 assert!(depth.bids[0].price < depth.asks[0].price); assert_eq!(depth.bids[0].size, Quantity::from("1000000"));
1028 assert_eq!(depth.bid_counts[0], 5);
1029 assert_eq!(depth.ask_counts[0], 3);
1030 }
1031
1032 #[rstest]
1033 fn test_order_book_depth_with_stub(stub_depth10: OrderBookDepth) {
1034 let depth = stub_depth10;
1035
1036 assert_eq!(depth.instrument_id, InstrumentId::from("AAPL.XNAS"));
1037 assert_eq!(depth.bids.len(), 10);
1038 assert_eq!(depth.asks.len(), 10);
1039 assert_eq!(depth.asks[9].price, Price::from("109.0"));
1040 assert_eq!(depth.asks[0].price, Price::from("100.0"));
1041 assert_eq!(depth.bids[0].price, Price::from("99.0"));
1042 assert_eq!(depth.bids[9].price, Price::from("90.0"));
1043 assert_eq!(depth.bid_counts.len(), 10);
1044 assert_eq!(depth.ask_counts.len(), 10);
1045 assert_eq!(depth.bid_counts[0], 1);
1046 assert_eq!(depth.ask_counts[0], 1);
1047 assert_eq!(depth.flags, 0);
1048 assert_eq!(depth.sequence, 0);
1049 assert_eq!(depth.ts_event, UnixNanos::from(1));
1050 assert_eq!(depth.ts_init, UnixNanos::from(2));
1051 }
1052
1053 #[rstest]
1054 fn test_new(stub_depth10: OrderBookDepth) {
1055 let depth = stub_depth10;
1056 let instrument_id = InstrumentId::from("AAPL.XNAS");
1057 let flags = 0;
1058 let sequence = 0;
1059 let ts_event = 1;
1060 let ts_init = 2;
1061
1062 assert_eq!(depth.instrument_id, instrument_id);
1063 assert_eq!(depth.bids.len(), 10);
1064 assert_eq!(depth.asks.len(), 10);
1065 assert_eq!(depth.asks[9].price, Price::from("109.0"));
1066 assert_eq!(depth.asks[0].price, Price::from("100.0"));
1067 assert_eq!(depth.bids[0].price, Price::from("99.0"));
1068 assert_eq!(depth.bids[9].price, Price::from("90.0"));
1069 assert_eq!(depth.bid_counts.len(), 10);
1070 assert_eq!(depth.ask_counts.len(), 10);
1071 assert_eq!(depth.bid_counts[0], 1);
1072 assert_eq!(depth.ask_counts[0], 1);
1073 assert_eq!(depth.flags, flags);
1074 assert_eq!(depth.sequence, sequence);
1075 assert_eq!(depth.ts_event, ts_event);
1076 assert_eq!(depth.ts_init, ts_init);
1077 }
1078
1079 #[rstest]
1080 fn test_display(stub_depth10: OrderBookDepth) {
1081 let depth = stub_depth10;
1082 assert_eq!(
1083 format!("{depth}"),
1084 "AAPL.XNAS,flags=0,sequence=0,ts_event=1,ts_init=2".to_string()
1085 );
1086 }
1087}