1use std::{
19 fmt::Display,
20 hash::{Hash, Hasher},
21};
22
23use nautilus_core::{
24 UnixNanos,
25 correctness::{FAILED, check_predicate_true},
26};
27use serde::{Deserialize, Serialize};
28
29use super::{HasTsInit, OrderBookDelta};
30use crate::identifiers::InstrumentId;
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
36#[cfg_attr(
37 feature = "python",
38 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
39)]
40#[cfg_attr(
41 feature = "python",
42 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
43)]
44pub struct OrderBookDeltas {
45 pub instrument_id: InstrumentId,
47 pub deltas: Vec<OrderBookDelta>,
49 pub flags: u8,
51 pub sequence: u64,
53 pub ts_event: UnixNanos,
55 pub ts_init: UnixNanos,
57}
58
59impl OrderBookDeltas {
60 #[must_use]
67 pub fn new(instrument_id: InstrumentId, deltas: Vec<OrderBookDelta>) -> Self {
68 Self::new_checked(instrument_id, deltas).expect(FAILED)
69 }
70
71 #[expect(
82 clippy::missing_panics_doc,
83 reason = "the unwrapped last element is guarded by the non-empty check"
84 )]
85 pub fn new_checked(
86 instrument_id: InstrumentId,
87 deltas: Vec<OrderBookDelta>,
88 ) -> anyhow::Result<Self> {
89 check_predicate_true(!deltas.is_empty(), "`deltas` cannot be empty")?;
90
91 let mismatch = deltas.iter().enumerate().find(|(_, delta)| {
92 instrument_id != delta.instrument_id
93 && (instrument_id.symbol.as_str() != delta.instrument_id.symbol.as_str()
94 || instrument_id.venue.as_str() != delta.instrument_id.venue.as_str())
95 });
96
97 if let Some((index, delta)) = mismatch {
98 check_predicate_true(
99 false,
100 &format!(
101 "`deltas` instrument IDs must match `instrument_id` {instrument_id}, but \
102 delta at index {index} of {} has {}",
103 deltas.len(),
104 delta.instrument_id,
105 ),
106 )?;
107 }
108 let last = deltas.last().expect("deltas not empty");
109 let flags = last.flags;
110 let sequence = last.sequence;
111 let ts_event = last.ts_event;
112 let ts_init = last.ts_init;
113 Ok(Self {
114 instrument_id,
115 deltas,
116 flags,
117 sequence,
118 ts_event,
119 ts_init,
120 })
121 }
122}
123
124impl PartialEq<Self> for OrderBookDeltas {
125 fn eq(&self, other: &Self) -> bool {
126 self.instrument_id == other.instrument_id && self.sequence == other.sequence
127 }
128}
129
130impl Eq for OrderBookDeltas {}
131
132impl Hash for OrderBookDeltas {
133 fn hash<H: Hasher>(&self, state: &mut H) {
134 self.instrument_id.hash(state);
135 self.sequence.hash(state);
136 }
137}
138
139impl Display for OrderBookDeltas {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 write!(
146 f,
147 "{},len={},flags={},sequence={},ts_event={},ts_init={}",
148 self.instrument_id,
149 self.deltas.len(),
150 self.flags,
151 self.sequence,
152 self.ts_event,
153 self.ts_init
154 )
155 }
156}
157
158impl HasTsInit for OrderBookDeltas {
159 fn ts_init(&self) -> UnixNanos {
160 self.ts_init
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use std::{
167 collections::hash_map::DefaultHasher,
168 hash::{Hash, Hasher},
169 };
170
171 use rstest::rstest;
172 use serde_json;
173
174 use super::*;
175 use crate::{
176 data::{order::BookOrder, stubs::stub_deltas},
177 enums::{BookAction, OrderSide},
178 types::{Price, Quantity},
179 };
180
181 fn create_test_delta() -> OrderBookDelta {
182 let instrument_id = InstrumentId::from("EURUSD.SIM");
183 OrderBookDelta::new(
184 instrument_id,
185 BookAction::Add,
186 BookOrder::new(
187 OrderSide::Buy,
188 Price::from("1.0500"),
189 Quantity::from("100000"),
190 1,
191 ),
192 0,
193 123,
194 UnixNanos::from(1_000_000_000),
195 UnixNanos::from(2_000_000_000),
196 )
197 }
198
199 fn create_test_deltas() -> OrderBookDeltas {
200 let instrument_id = InstrumentId::from("EURUSD.SIM");
201 let flags = 32;
202 let sequence = 123;
203 let ts_event = UnixNanos::from(1_000_000_000);
204 let ts_init = UnixNanos::from(2_000_000_000);
205
206 let delta1 = OrderBookDelta::new(
207 instrument_id,
208 BookAction::Add,
209 BookOrder::new(
210 OrderSide::Sell,
211 Price::from("1.0520"),
212 Quantity::from("50000"),
213 1,
214 ),
215 flags,
216 sequence,
217 ts_event,
218 ts_init,
219 );
220 let delta2 = OrderBookDelta::new(
221 instrument_id,
222 BookAction::Add,
223 BookOrder::new(
224 OrderSide::Buy,
225 Price::from("1.0500"),
226 Quantity::from("75000"),
227 2,
228 ),
229 flags,
230 sequence,
231 ts_event,
232 ts_init,
233 );
234
235 OrderBookDeltas::new(instrument_id, vec![delta1, delta2])
236 }
237
238 fn create_test_deltas_multiple() -> OrderBookDeltas {
239 let instrument_id = InstrumentId::from("GBPUSD.SIM");
240 let flags = 16;
241 let sequence = 456;
242 let ts_event = UnixNanos::from(3_000_000_000);
243 let ts_init = UnixNanos::from(4_000_000_000);
244
245 let deltas = vec![
246 OrderBookDelta::clear(instrument_id, sequence, ts_event, ts_init),
247 OrderBookDelta::new(
248 instrument_id,
249 BookAction::Add,
250 BookOrder::new(
251 OrderSide::Sell,
252 Price::from("1.2550"),
253 Quantity::from("100000"),
254 1,
255 ),
256 flags,
257 sequence,
258 ts_event,
259 ts_init,
260 ),
261 OrderBookDelta::new(
262 instrument_id,
263 BookAction::Update,
264 BookOrder::new(
265 OrderSide::Buy,
266 Price::from("1.2530"),
267 Quantity::from("200000"),
268 2,
269 ),
270 flags,
271 sequence,
272 ts_event,
273 ts_init,
274 ),
275 OrderBookDelta::new(
276 instrument_id,
277 BookAction::Delete,
278 BookOrder::new(
279 OrderSide::Sell,
280 Price::from("1.2560"),
281 Quantity::from("0"),
282 3,
283 ),
284 flags,
285 sequence,
286 ts_event,
287 ts_init,
288 ),
289 ];
290
291 OrderBookDeltas::new(instrument_id, deltas)
292 }
293
294 #[rstest]
295 fn test_order_book_deltas_new() {
296 let deltas = create_test_deltas();
297
298 assert_eq!(deltas.instrument_id, InstrumentId::from("EURUSD.SIM"));
299 assert_eq!(deltas.deltas.len(), 2);
300 assert_eq!(deltas.flags, 32);
301 assert_eq!(deltas.sequence, 123);
302 assert_eq!(deltas.ts_event, UnixNanos::from(1_000_000_000));
303 assert_eq!(deltas.ts_init, UnixNanos::from(2_000_000_000));
304 }
305
306 #[rstest]
307 fn test_order_book_deltas_new_checked_valid() {
308 let instrument_id = InstrumentId::from("EURUSD.SIM");
309 let delta = create_test_delta();
310
311 let result = OrderBookDeltas::new_checked(instrument_id, vec![delta]);
312
313 assert!(result.is_ok());
314 let deltas = result.unwrap();
315 assert_eq!(deltas.instrument_id, instrument_id);
316 assert_eq!(deltas.deltas.len(), 1);
317 }
318
319 #[rstest]
320 fn test_order_book_deltas_new_checked_accepts_homogeneous_deltas() {
321 let instrument_id = InstrumentId::from("EURUSD.SIM");
322 let delta1 = create_test_delta();
323 let mut delta2 = create_test_delta();
324 delta2.sequence = 124;
325
326 let result = OrderBookDeltas::new_checked(instrument_id, vec![delta1, delta2]);
327
328 assert!(result.is_ok());
329 assert_eq!(result.unwrap().deltas.len(), 2);
330 }
331
332 #[rstest]
333 #[case::first(0)]
334 #[case::later(1)]
335 fn test_order_book_deltas_new_checked_rejects_mismatched_instrument(
336 #[case] mismatch_index: usize,
337 ) {
338 let instrument_id = InstrumentId::from("EURUSD.SIM");
339 let mut deltas = vec![create_test_delta(), create_test_delta()];
340 deltas[mismatch_index].instrument_id = InstrumentId::from("GBPUSD.SIM");
341
342 let result = OrderBookDeltas::new_checked(instrument_id, deltas);
343
344 assert_eq!(
345 result.unwrap_err().to_string(),
346 format!(
347 "`deltas` instrument IDs must match `instrument_id` EURUSD.SIM, but delta at \
348 index {mismatch_index} of 2 has GBPUSD.SIM"
349 )
350 );
351 }
352
353 #[rstest]
354 fn test_order_book_deltas_new_checked_empty_deltas() {
355 let instrument_id = InstrumentId::from("EURUSD.SIM");
356
357 let result = OrderBookDeltas::new_checked(instrument_id, vec![]);
358
359 assert!(result.is_err());
360 assert!(
361 result
362 .unwrap_err()
363 .to_string()
364 .contains("`deltas` cannot be empty")
365 );
366 }
367
368 #[rstest]
369 #[should_panic(expected = "Condition failed")]
370 fn test_order_book_deltas_new_empty_deltas_panics() {
371 let instrument_id = InstrumentId::from("EURUSD.SIM");
372 let _ = OrderBookDeltas::new(instrument_id, vec![]);
373 }
374
375 #[rstest]
376 fn test_order_book_deltas_uses_last_delta_properties() {
377 let instrument_id = InstrumentId::from("EURUSD.SIM");
378
379 let delta1 = OrderBookDelta::new(
380 instrument_id,
381 BookAction::Add,
382 BookOrder::new(
383 OrderSide::Buy,
384 Price::from("1.0500"),
385 Quantity::from("100000"),
386 1,
387 ),
388 16, 100, UnixNanos::from(500_000_000), UnixNanos::from(1_000_000_000), );
393
394 let delta2 = OrderBookDelta::new(
395 instrument_id,
396 BookAction::Add,
397 BookOrder::new(
398 OrderSide::Sell,
399 Price::from("1.0520"),
400 Quantity::from("50000"),
401 2,
402 ),
403 32, 200, UnixNanos::from(1_500_000_000), UnixNanos::from(2_000_000_000), );
408
409 let deltas = OrderBookDeltas::new(instrument_id, vec![delta1, delta2]);
410
411 assert_eq!(deltas.flags, 32);
413 assert_eq!(deltas.sequence, 200);
414 assert_eq!(deltas.ts_event, UnixNanos::from(1_500_000_000));
415 assert_eq!(deltas.ts_init, UnixNanos::from(2_000_000_000));
416 }
417
418 #[rstest]
419 fn test_order_book_deltas_hash_different_objects() {
420 let deltas1 = create_test_deltas();
421 let deltas2 = create_test_deltas_multiple();
422
423 let mut hasher1 = DefaultHasher::new();
424 let mut hasher2 = DefaultHasher::new();
425
426 deltas1.hash(&mut hasher1);
427 deltas2.hash(&mut hasher2);
428
429 assert_ne!(hasher1.finish(), hasher2.finish()); }
431
432 #[rstest]
433 fn test_order_book_deltas_hash_uses_instrument_id_and_sequence() {
434 let instrument_id = InstrumentId::from("EURUSD.SIM");
435 let sequence = 123u64;
436
437 let mut expected_hasher = DefaultHasher::new();
439 instrument_id.hash(&mut expected_hasher);
440 sequence.hash(&mut expected_hasher);
441 let expected_hash = expected_hasher.finish();
442
443 let delta = OrderBookDelta::new(
444 instrument_id,
445 BookAction::Add,
446 BookOrder::new(
447 OrderSide::Buy,
448 Price::from("1.0500"),
449 Quantity::from("100000"),
450 1,
451 ),
452 0,
453 sequence,
454 UnixNanos::from(1_000_000_000),
455 UnixNanos::from(2_000_000_000),
456 );
457
458 let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
459
460 let mut deltas_hasher = DefaultHasher::new();
461 deltas.hash(&mut deltas_hasher);
462
463 assert_eq!(deltas_hasher.finish(), expected_hash);
464 }
465
466 #[rstest]
467 fn test_order_book_deltas_display() {
468 let deltas = create_test_deltas();
469 let display_str = format!("{deltas}");
470
471 assert!(display_str.contains("EURUSD.SIM"));
472 assert!(display_str.contains("len=2"));
473 assert!(display_str.contains("flags=32"));
474 assert!(display_str.contains("sequence=123"));
475 assert!(display_str.contains("ts_event=1000000000"));
476 assert!(display_str.contains("ts_init=2000000000"));
477 }
478
479 #[rstest]
480 fn test_order_book_deltas_display_format() {
481 let deltas = create_test_deltas();
482 let expected =
483 "EURUSD.SIM,len=2,flags=32,sequence=123,ts_event=1000000000,ts_init=2000000000";
484
485 assert_eq!(format!("{deltas}"), expected);
486 }
487
488 #[rstest]
489 fn test_order_book_deltas_has_ts_init() {
490 let deltas = create_test_deltas();
491
492 assert_eq!(deltas.ts_init(), UnixNanos::from(2_000_000_000));
493 }
494
495 #[rstest]
496 fn test_order_book_deltas_clone() {
497 let deltas1 = create_test_deltas();
498 let deltas2 = deltas1.clone();
499
500 assert_eq!(deltas1.instrument_id, deltas2.instrument_id);
501 assert_eq!(deltas1.deltas.len(), deltas2.deltas.len());
502 assert_eq!(deltas1.flags, deltas2.flags);
503 assert_eq!(deltas1.sequence, deltas2.sequence);
504 assert_eq!(deltas1.ts_event, deltas2.ts_event);
505 assert_eq!(deltas1.ts_init, deltas2.ts_init);
506 assert_eq!(deltas1, deltas2);
507 }
508
509 #[rstest]
510 fn test_order_book_deltas_debug() {
511 let deltas = create_test_deltas();
512 let debug_str = format!("{deltas:?}");
513
514 assert!(debug_str.contains("OrderBookDeltas"));
515 assert!(debug_str.contains("EURUSD.SIM"));
516 assert!(debug_str.contains("flags: 32"));
517 assert!(debug_str.contains("sequence: 123"));
518 }
519
520 #[rstest]
521 fn test_order_book_deltas_serialization() {
522 let deltas = create_test_deltas();
523
524 let json = serde_json::to_string(&deltas).unwrap();
526 let deserialized: OrderBookDeltas = serde_json::from_str(&json).unwrap();
527
528 assert_eq!(deltas.instrument_id, deserialized.instrument_id);
529 assert_eq!(deltas.deltas.len(), deserialized.deltas.len());
530 assert_eq!(deltas.flags, deserialized.flags);
531 assert_eq!(deltas.sequence, deserialized.sequence);
532 assert_eq!(deltas.ts_event, deserialized.ts_event);
533 assert_eq!(deltas.ts_init, deserialized.ts_init);
534 }
535
536 #[rstest]
537 fn test_order_book_deltas_single_delta() {
538 let delta = create_test_delta();
539 let instrument_id = delta.instrument_id;
540
541 let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
542
543 assert_eq!(deltas.instrument_id, instrument_id);
544 assert_eq!(deltas.deltas.len(), 1);
545 assert_eq!(deltas.flags, delta.flags);
546 assert_eq!(deltas.sequence, delta.sequence);
547 assert_eq!(deltas.ts_event, delta.ts_event);
548 assert_eq!(deltas.ts_init, delta.ts_init);
549 }
550
551 #[rstest]
552 fn test_order_book_deltas_large_number_of_deltas() {
553 let instrument_id = InstrumentId::from("ETHUSD.CRYPTO");
554 let mut delta_vec = Vec::new();
555
556 for i in 0..100 {
558 let delta = OrderBookDelta::new(
559 instrument_id,
560 BookAction::Add,
561 BookOrder::new(
562 OrderSide::Buy,
563 Price::from(&format!("1000.{i:02}")),
564 Quantity::from("1000"),
565 i as u64,
566 ),
567 0,
568 i as u64,
569 UnixNanos::from(1_000_000_000 + i as u64),
570 UnixNanos::from(2_000_000_000 + i as u64),
571 );
572 delta_vec.push(delta);
573 }
574
575 let deltas = OrderBookDeltas::new(instrument_id, delta_vec);
576
577 assert_eq!(deltas.deltas.len(), 100);
578 assert_eq!(deltas.sequence, 99); assert_eq!(deltas.ts_event, UnixNanos::from(1_000_000_000 + 99));
580 assert_eq!(deltas.ts_init, UnixNanos::from(2_000_000_000 + 99));
581 }
582
583 #[rstest]
584 fn test_order_book_deltas_different_action_types() {
585 let deltas = create_test_deltas_multiple();
586
587 assert_eq!(deltas.deltas.len(), 4);
588
589 assert_eq!(deltas.deltas[0].action, BookAction::Clear);
591 assert_eq!(deltas.deltas[1].action, BookAction::Add);
592 assert_eq!(deltas.deltas[2].action, BookAction::Update);
593 assert_eq!(deltas.deltas[3].action, BookAction::Delete);
594 }
595
596 #[rstest]
597 fn test_order_book_deltas_with_stub(stub_deltas: OrderBookDeltas) {
598 let deltas = stub_deltas;
599
600 assert_eq!(deltas.instrument_id, InstrumentId::from("AAPL.XNAS"));
601 assert_eq!(deltas.deltas.len(), 7);
602 assert_eq!(deltas.flags, 32);
603 assert_eq!(deltas.sequence, 0);
604 assert_eq!(deltas.ts_event, UnixNanos::from(1));
605 assert_eq!(deltas.ts_init, UnixNanos::from(2));
606 }
607
608 #[rstest]
609 fn test_display_with_stub(stub_deltas: OrderBookDeltas) {
610 let deltas = stub_deltas;
611 assert_eq!(
612 format!("{deltas}"),
613 "AAPL.XNAS,len=7,flags=32,sequence=0,ts_event=1,ts_init=2".to_string()
614 );
615 }
616
617 #[rstest]
618 fn test_order_book_deltas_zero_sequence() {
619 let instrument_id = InstrumentId::from("ZERO.TEST");
620 let delta = OrderBookDelta::new(
621 instrument_id,
622 BookAction::Add,
623 BookOrder::new(
624 OrderSide::Buy,
625 Price::from("100.0"),
626 Quantity::from("1000"),
627 1,
628 ),
629 0,
630 0, UnixNanos::from(0), UnixNanos::from(0),
633 );
634
635 let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
636
637 assert_eq!(deltas.sequence, 0);
638 assert_eq!(deltas.ts_event, UnixNanos::from(0));
639 assert_eq!(deltas.ts_init, UnixNanos::from(0));
640 }
641
642 #[rstest]
643 fn test_order_book_deltas_max_values() {
644 let instrument_id = InstrumentId::from("MAX.TEST");
645 let delta = OrderBookDelta::new(
646 instrument_id,
647 BookAction::Add,
648 BookOrder::new(
649 OrderSide::Buy,
650 Price::from("999999.99"),
651 Quantity::from("999999999"),
652 u64::MAX,
653 ),
654 u8::MAX,
655 u64::MAX,
656 UnixNanos::from(u64::MAX),
657 UnixNanos::from(u64::MAX),
658 );
659
660 let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
661
662 assert_eq!(deltas.flags, u8::MAX);
663 assert_eq!(deltas.sequence, u64::MAX);
664 assert_eq!(deltas.ts_event, UnixNanos::from(u64::MAX));
665 assert_eq!(deltas.ts_init, UnixNanos::from(u64::MAX));
666 }
667
668 #[rstest]
669 fn test_new() {
670 let instrument_id = InstrumentId::from("AAPL.XNAS");
671 let flags = 32; let sequence = 0;
673 let ts_event = 1;
674 let ts_init = 2;
675
676 let delta0 =
677 OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
678 let delta1 = OrderBookDelta::new(
679 instrument_id,
680 BookAction::Add,
681 BookOrder::new(
682 OrderSide::Sell,
683 Price::from("102.00"),
684 Quantity::from("300"),
685 1,
686 ),
687 flags,
688 sequence,
689 ts_event.into(),
690 ts_init.into(),
691 );
692 let delta2 = OrderBookDelta::new(
693 instrument_id,
694 BookAction::Add,
695 BookOrder::new(
696 OrderSide::Sell,
697 Price::from("101.00"),
698 Quantity::from("200"),
699 2,
700 ),
701 flags,
702 sequence,
703 ts_event.into(),
704 ts_init.into(),
705 );
706 let delta3 = OrderBookDelta::new(
707 instrument_id,
708 BookAction::Add,
709 BookOrder::new(
710 OrderSide::Sell,
711 Price::from("100.00"),
712 Quantity::from("100"),
713 3,
714 ),
715 flags,
716 sequence,
717 ts_event.into(),
718 ts_init.into(),
719 );
720 let delta4 = OrderBookDelta::new(
721 instrument_id,
722 BookAction::Add,
723 BookOrder::new(
724 OrderSide::Buy,
725 Price::from("99.00"),
726 Quantity::from("100"),
727 4,
728 ),
729 flags,
730 sequence,
731 ts_event.into(),
732 ts_init.into(),
733 );
734 let delta5 = OrderBookDelta::new(
735 instrument_id,
736 BookAction::Add,
737 BookOrder::new(
738 OrderSide::Buy,
739 Price::from("98.00"),
740 Quantity::from("200"),
741 5,
742 ),
743 flags,
744 sequence,
745 ts_event.into(),
746 ts_init.into(),
747 );
748 let delta6 = OrderBookDelta::new(
749 instrument_id,
750 BookAction::Add,
751 BookOrder::new(
752 OrderSide::Buy,
753 Price::from("97.00"),
754 Quantity::from("300"),
755 6,
756 ),
757 flags,
758 sequence,
759 ts_event.into(),
760 ts_init.into(),
761 );
762
763 let deltas = OrderBookDeltas::new(
764 instrument_id,
765 vec![delta0, delta1, delta2, delta3, delta4, delta5, delta6],
766 );
767
768 assert_eq!(deltas.instrument_id, instrument_id);
769 assert_eq!(deltas.deltas.len(), 7);
770 assert_eq!(deltas.flags, flags);
771 assert_eq!(deltas.sequence, sequence);
772 assert_eq!(deltas.ts_event, ts_event);
773 assert_eq!(deltas.ts_init, ts_init);
774 }
775}