Skip to main content

nautilus_model/data/
deltas.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! An `OrderBookDeltas` container type to carry a bulk of `OrderBookDelta` records.
17
18use std::{
19    fmt::Display,
20    hash::{Hash, Hasher},
21};
22
23use nautilus_core::{
24    UnixNanos,
25    correctness::{FAILED, check_predicate_true},
26    serialization::Serializable,
27};
28use serde::{Deserialize, Serialize};
29
30use super::{HasTsInit, OrderBookDelta};
31use crate::{enums::RecordFlag, identifiers::InstrumentId};
32
33/// Represents a grouped batch of `OrderBookDelta` updates for an `OrderBook`.
34///
35/// This type cannot be `repr(C)` due to the `deltas` vec.
36#[derive(Clone, Debug, Serialize, Deserialize)]
37#[cfg_attr(
38    feature = "python",
39    pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
40)]
41#[cfg_attr(
42    feature = "python",
43    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
44)]
45pub struct OrderBookDeltas {
46    /// The instrument ID for the book.
47    pub instrument_id: InstrumentId,
48    /// The order book deltas.
49    pub deltas: Vec<OrderBookDelta>,
50    /// The record flags bit field, indicating event end and data information.
51    pub flags: u8,
52    /// The message sequence number assigned at the venue.
53    pub sequence: u64,
54    /// UNIX timestamp (nanoseconds) when the book event occurred.
55    pub ts_event: UnixNanos,
56    /// UNIX timestamp (nanoseconds) when the instance was created.
57    pub ts_init: UnixNanos,
58}
59
60impl OrderBookDeltas {
61    /// Creates a new [`OrderBookDeltas`] instance.
62    ///
63    /// # Panics
64    ///
65    /// Panics if `deltas` is empty or contains an instrument ID that does not match
66    /// `instrument_id`.
67    #[must_use]
68    pub fn new(instrument_id: InstrumentId, deltas: Vec<OrderBookDelta>) -> Self {
69        Self::new_checked(instrument_id, deltas).expect(FAILED)
70    }
71
72    /// Creates a new [`OrderBookDeltas`] instance with correctness checking.
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if `deltas` is empty or contains an instrument ID that does not match
77    /// `instrument_id`.
78    ///
79    /// # Notes
80    ///
81    /// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
82    #[expect(
83        clippy::missing_panics_doc,
84        reason = "the unwrapped last element is guarded by the non-empty check"
85    )]
86    pub fn new_checked(
87        instrument_id: InstrumentId,
88        deltas: Vec<OrderBookDelta>,
89    ) -> anyhow::Result<Self> {
90        check_predicate_true(!deltas.is_empty(), "`deltas` cannot be empty")?;
91
92        let mismatch = deltas.iter().enumerate().find(|(_, delta)| {
93            instrument_id != delta.instrument_id
94                && (instrument_id.symbol.as_str() != delta.instrument_id.symbol.as_str()
95                    || instrument_id.venue.as_str() != delta.instrument_id.venue.as_str())
96        });
97
98        if let Some((index, delta)) = mismatch {
99            check_predicate_true(
100                false,
101                &format!(
102                    "`deltas` instrument IDs must match `instrument_id` {instrument_id}, but \
103                     delta at index {index} of {} has {}",
104                    deltas.len(),
105                    delta.instrument_id,
106                ),
107            )?;
108        }
109        let last = deltas.last().expect("deltas not empty");
110        let flags = last.flags;
111        let sequence = last.sequence;
112        let ts_event = last.ts_event;
113        let ts_init = last.ts_init;
114        Ok(Self {
115            instrument_id,
116            deltas,
117            flags,
118            sequence,
119            ts_event,
120            ts_init,
121        })
122    }
123
124    /// Returns whether the batch is a snapshot.
125    #[cfg_attr(not(any(feature = "ffi", feature = "python")), allow(dead_code))]
126    #[must_use]
127    pub(crate) fn is_snapshot(&self) -> bool {
128        RecordFlag::F_SNAPSHOT.matches(self.flags)
129    }
130}
131
132impl PartialEq<Self> for OrderBookDeltas {
133    fn eq(&self, other: &Self) -> bool {
134        self.instrument_id == other.instrument_id && self.sequence == other.sequence
135    }
136}
137
138impl Eq for OrderBookDeltas {}
139
140impl Hash for OrderBookDeltas {
141    fn hash<H: Hasher>(&self, state: &mut H) {
142        self.instrument_id.hash(state);
143        self.sequence.hash(state);
144    }
145}
146
147impl Display for OrderBookDeltas {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(
150            f,
151            "{},len={},flags={},sequence={},ts_event={},ts_init={}",
152            self.instrument_id,
153            self.deltas.len(),
154            self.flags,
155            self.sequence,
156            self.ts_event,
157            self.ts_init
158        )
159    }
160}
161
162impl Serializable for OrderBookDeltas {}
163
164impl HasTsInit for OrderBookDeltas {
165    fn ts_init(&self) -> UnixNanos {
166        self.ts_init
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use std::{
173        collections::hash_map::DefaultHasher,
174        hash::{Hash, Hasher},
175    };
176
177    use nautilus_core::serialization::{
178        Serializable,
179        msgpack::{FromMsgPack, ToMsgPack},
180    };
181    use rstest::rstest;
182    use serde_json;
183
184    use super::*;
185    use crate::{
186        data::{order::BookOrder, stubs::stub_deltas},
187        enums::{BookAction, OrderSide, RecordFlag},
188        types::{Price, Quantity},
189    };
190
191    fn create_test_delta() -> OrderBookDelta {
192        let instrument_id = InstrumentId::from("EURUSD.SIM");
193        OrderBookDelta::new(
194            instrument_id,
195            BookAction::Add,
196            BookOrder::new(
197                OrderSide::Buy,
198                Price::from("1.0500"),
199                Quantity::from("100000"),
200                1,
201            ),
202            0,
203            123,
204            UnixNanos::from(1_000_000_000),
205            UnixNanos::from(2_000_000_000),
206        )
207    }
208
209    fn create_test_deltas() -> OrderBookDeltas {
210        let instrument_id = InstrumentId::from("EURUSD.SIM");
211        let flags = 32;
212        let sequence = 123;
213        let ts_event = UnixNanos::from(1_000_000_000);
214        let ts_init = UnixNanos::from(2_000_000_000);
215
216        let delta1 = OrderBookDelta::new(
217            instrument_id,
218            BookAction::Add,
219            BookOrder::new(
220                OrderSide::Sell,
221                Price::from("1.0520"),
222                Quantity::from("50000"),
223                1,
224            ),
225            flags,
226            sequence,
227            ts_event,
228            ts_init,
229        );
230        let delta2 = OrderBookDelta::new(
231            instrument_id,
232            BookAction::Add,
233            BookOrder::new(
234                OrderSide::Buy,
235                Price::from("1.0500"),
236                Quantity::from("75000"),
237                2,
238            ),
239            flags,
240            sequence,
241            ts_event,
242            ts_init,
243        );
244
245        OrderBookDeltas::new(instrument_id, vec![delta1, delta2])
246    }
247
248    fn create_test_deltas_multiple() -> OrderBookDeltas {
249        let instrument_id = InstrumentId::from("GBPUSD.SIM");
250        let flags = 16;
251        let sequence = 456;
252        let ts_event = UnixNanos::from(3_000_000_000);
253        let ts_init = UnixNanos::from(4_000_000_000);
254
255        let deltas = vec![
256            OrderBookDelta::clear(instrument_id, sequence, ts_event, ts_init),
257            OrderBookDelta::new(
258                instrument_id,
259                BookAction::Add,
260                BookOrder::new(
261                    OrderSide::Sell,
262                    Price::from("1.2550"),
263                    Quantity::from("100000"),
264                    1,
265                ),
266                flags,
267                sequence,
268                ts_event,
269                ts_init,
270            ),
271            OrderBookDelta::new(
272                instrument_id,
273                BookAction::Update,
274                BookOrder::new(
275                    OrderSide::Buy,
276                    Price::from("1.2530"),
277                    Quantity::from("200000"),
278                    2,
279                ),
280                flags,
281                sequence,
282                ts_event,
283                ts_init,
284            ),
285            OrderBookDelta::new(
286                instrument_id,
287                BookAction::Delete,
288                BookOrder::new(
289                    OrderSide::Sell,
290                    Price::from("1.2560"),
291                    Quantity::from("0"),
292                    3,
293                ),
294                flags,
295                sequence,
296                ts_event,
297                ts_init,
298            ),
299        ];
300
301        OrderBookDeltas::new(instrument_id, deltas)
302    }
303
304    // Compared field by field, as `PartialEq` covers only `instrument_id` and `sequence` here,
305    // and only `order_id` on the nested `BookOrder`.
306    fn assert_book_order_fields(expected: &BookOrder, actual: &BookOrder) {
307        assert_eq!(expected.side, actual.side);
308        assert_eq!(expected.price, actual.price);
309        assert_eq!(expected.size, actual.size);
310        assert_eq!(expected.order_id, actual.order_id);
311    }
312
313    fn assert_order_book_delta_fields(expected: &OrderBookDelta, actual: &OrderBookDelta) {
314        assert_eq!(expected.instrument_id, actual.instrument_id);
315        assert_eq!(expected.action, actual.action);
316        assert_book_order_fields(&expected.order, &actual.order);
317        assert_eq!(expected.flags, actual.flags);
318        assert_eq!(expected.sequence, actual.sequence);
319        assert_eq!(expected.ts_event, actual.ts_event);
320        assert_eq!(expected.ts_init, actual.ts_init);
321    }
322
323    fn assert_order_book_deltas_fields(expected: &OrderBookDeltas, actual: &OrderBookDeltas) {
324        assert_eq!(expected.instrument_id, actual.instrument_id);
325        assert_eq!(expected.flags, actual.flags);
326        assert_eq!(expected.sequence, actual.sequence);
327        assert_eq!(expected.ts_event, actual.ts_event);
328        assert_eq!(expected.ts_init, actual.ts_init);
329        assert_eq!(expected.deltas.len(), actual.deltas.len());
330
331        for (expected_delta, actual_delta) in expected.deltas.iter().zip(&actual.deltas) {
332            assert_order_book_delta_fields(expected_delta, actual_delta);
333        }
334    }
335
336    #[rstest]
337    fn test_order_book_deltas_new() {
338        let deltas = create_test_deltas();
339
340        assert_eq!(deltas.instrument_id, InstrumentId::from("EURUSD.SIM"));
341        assert_eq!(deltas.deltas.len(), 2);
342        assert_eq!(deltas.flags, 32);
343        assert_eq!(deltas.sequence, 123);
344        assert_eq!(deltas.ts_event, UnixNanos::from(1_000_000_000));
345        assert_eq!(deltas.ts_init, UnixNanos::from(2_000_000_000));
346    }
347
348    #[rstest]
349    fn test_order_book_deltas_new_checked_valid() {
350        let instrument_id = InstrumentId::from("EURUSD.SIM");
351        let delta = create_test_delta();
352
353        let result = OrderBookDeltas::new_checked(instrument_id, vec![delta]);
354
355        assert!(result.is_ok());
356        let deltas = result.unwrap();
357        assert_eq!(deltas.instrument_id, instrument_id);
358        assert_eq!(deltas.deltas.len(), 1);
359    }
360
361    #[rstest]
362    fn test_order_book_deltas_new_checked_accepts_homogeneous_deltas() {
363        let instrument_id = InstrumentId::from("EURUSD.SIM");
364        let delta1 = create_test_delta();
365        let mut delta2 = create_test_delta();
366        delta2.sequence = 124;
367
368        let result = OrderBookDeltas::new_checked(instrument_id, vec![delta1, delta2]);
369
370        assert!(result.is_ok());
371        assert_eq!(result.unwrap().deltas.len(), 2);
372    }
373
374    #[rstest]
375    #[case::first(0)]
376    #[case::later(1)]
377    fn test_order_book_deltas_new_checked_rejects_mismatched_instrument(
378        #[case] mismatch_index: usize,
379    ) {
380        let instrument_id = InstrumentId::from("EURUSD.SIM");
381        let mut deltas = vec![create_test_delta(), create_test_delta()];
382        deltas[mismatch_index].instrument_id = InstrumentId::from("GBPUSD.SIM");
383
384        let result = OrderBookDeltas::new_checked(instrument_id, deltas);
385
386        assert_eq!(
387            result.unwrap_err().to_string(),
388            format!(
389                "`deltas` instrument IDs must match `instrument_id` EURUSD.SIM, but delta at \
390                 index {mismatch_index} of 2 has GBPUSD.SIM"
391            )
392        );
393    }
394
395    #[rstest]
396    fn test_order_book_deltas_new_checked_empty_deltas() {
397        let instrument_id = InstrumentId::from("EURUSD.SIM");
398
399        let result = OrderBookDeltas::new_checked(instrument_id, vec![]);
400
401        assert!(result.is_err());
402        assert!(
403            result
404                .unwrap_err()
405                .to_string()
406                .contains("`deltas` cannot be empty")
407        );
408    }
409
410    #[rstest]
411    #[should_panic(expected = "Condition failed")]
412    fn test_order_book_deltas_new_empty_deltas_panics() {
413        let instrument_id = InstrumentId::from("EURUSD.SIM");
414        let _ = OrderBookDeltas::new(instrument_id, vec![]);
415    }
416
417    #[rstest]
418    fn test_order_book_deltas_uses_last_delta_properties() {
419        let instrument_id = InstrumentId::from("EURUSD.SIM");
420
421        let delta1 = OrderBookDelta::new(
422            instrument_id,
423            BookAction::Add,
424            BookOrder::new(
425                OrderSide::Buy,
426                Price::from("1.0500"),
427                Quantity::from("100000"),
428                1,
429            ),
430            16,                             // Different flags
431            100,                            // Different sequence
432            UnixNanos::from(500_000_000),   // Different ts_event
433            UnixNanos::from(1_000_000_000), // Different ts_init
434        );
435
436        let delta2 = OrderBookDelta::new(
437            instrument_id,
438            BookAction::Add,
439            BookOrder::new(
440                OrderSide::Sell,
441                Price::from("1.0520"),
442                Quantity::from("50000"),
443                2,
444            ),
445            32,                             // Final flags
446            200,                            // Final sequence
447            UnixNanos::from(1_500_000_000), // Final ts_event
448            UnixNanos::from(2_000_000_000), // Final ts_init
449        );
450
451        let deltas = OrderBookDeltas::new(instrument_id, vec![delta1, delta2]);
452
453        // Should use properties from the last delta
454        assert_eq!(deltas.flags, 32);
455        assert_eq!(deltas.sequence, 200);
456        assert_eq!(deltas.ts_event, UnixNanos::from(1_500_000_000));
457        assert_eq!(deltas.ts_init, UnixNanos::from(2_000_000_000));
458    }
459
460    #[rstest]
461    #[case::snapshot(
462        vec![(BookAction::Add, RecordFlag::F_SNAPSHOT as u8)],
463        true
464    )]
465    #[case::combined_flags(
466        vec![(
467            BookAction::Add,
468            RecordFlag::F_SNAPSHOT as u8 | RecordFlag::F_LAST as u8,
469        )],
470        true
471    )]
472    #[case::not_snapshot(vec![(BookAction::Add, RecordFlag::F_MBP as u8)], false)]
473    #[case::clear_without_snapshot(
474        vec![(BookAction::Clear, 0), (BookAction::Add, 0)],
475        false
476    )]
477    fn test_order_book_deltas_is_snapshot(
478        #[case] actions_and_flags: Vec<(BookAction, u8)>,
479        #[case] expected: bool,
480    ) {
481        let instrument_id = InstrumentId::from("EURUSD.SIM");
482        let deltas = actions_and_flags
483            .into_iter()
484            .map(|(action, flags)| {
485                let mut delta = create_test_delta();
486                delta.action = action;
487                delta.flags = flags;
488                delta
489            })
490            .collect();
491        let deltas = OrderBookDeltas::new(instrument_id, deltas);
492
493        assert_eq!(deltas.is_snapshot(), expected);
494    }
495
496    #[rstest]
497    fn test_order_book_deltas_hash_different_objects() {
498        let deltas1 = create_test_deltas();
499        let deltas2 = create_test_deltas_multiple();
500
501        let mut hasher1 = DefaultHasher::new();
502        let mut hasher2 = DefaultHasher::new();
503
504        deltas1.hash(&mut hasher1);
505        deltas2.hash(&mut hasher2);
506
507        assert_ne!(hasher1.finish(), hasher2.finish()); // Different objects should have different hashes
508    }
509
510    #[rstest]
511    fn test_order_book_deltas_hash_uses_instrument_id_and_sequence() {
512        let instrument_id = InstrumentId::from("EURUSD.SIM");
513        let sequence = 123u64;
514
515        // Create separate hasher to verify what's being hashed
516        let mut expected_hasher = DefaultHasher::new();
517        instrument_id.hash(&mut expected_hasher);
518        sequence.hash(&mut expected_hasher);
519        let expected_hash = expected_hasher.finish();
520
521        let delta = OrderBookDelta::new(
522            instrument_id,
523            BookAction::Add,
524            BookOrder::new(
525                OrderSide::Buy,
526                Price::from("1.0500"),
527                Quantity::from("100000"),
528                1,
529            ),
530            0,
531            sequence,
532            UnixNanos::from(1_000_000_000),
533            UnixNanos::from(2_000_000_000),
534        );
535
536        let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
537
538        let mut deltas_hasher = DefaultHasher::new();
539        deltas.hash(&mut deltas_hasher);
540
541        assert_eq!(deltas_hasher.finish(), expected_hash);
542    }
543
544    #[rstest]
545    fn test_order_book_deltas_display() {
546        let deltas = create_test_deltas();
547        let display_str = format!("{deltas}");
548
549        assert!(display_str.contains("EURUSD.SIM"));
550        assert!(display_str.contains("len=2"));
551        assert!(display_str.contains("flags=32"));
552        assert!(display_str.contains("sequence=123"));
553        assert!(display_str.contains("ts_event=1000000000"));
554        assert!(display_str.contains("ts_init=2000000000"));
555    }
556
557    #[rstest]
558    fn test_order_book_deltas_display_format() {
559        let deltas = create_test_deltas();
560        let expected =
561            "EURUSD.SIM,len=2,flags=32,sequence=123,ts_event=1000000000,ts_init=2000000000";
562
563        assert_eq!(format!("{deltas}"), expected);
564    }
565
566    #[rstest]
567    fn test_order_book_deltas_has_ts_init() {
568        let deltas = create_test_deltas();
569
570        assert_eq!(deltas.ts_init(), UnixNanos::from(2_000_000_000));
571    }
572
573    #[rstest]
574    fn test_order_book_deltas_clone() {
575        let deltas1 = create_test_deltas();
576        let deltas2 = deltas1.clone();
577
578        assert_eq!(deltas1.instrument_id, deltas2.instrument_id);
579        assert_eq!(deltas1.deltas.len(), deltas2.deltas.len());
580        assert_eq!(deltas1.flags, deltas2.flags);
581        assert_eq!(deltas1.sequence, deltas2.sequence);
582        assert_eq!(deltas1.ts_event, deltas2.ts_event);
583        assert_eq!(deltas1.ts_init, deltas2.ts_init);
584        assert_eq!(deltas1, deltas2);
585    }
586
587    #[rstest]
588    fn test_order_book_deltas_debug() {
589        let deltas = create_test_deltas();
590        let debug_str = format!("{deltas:?}");
591
592        assert!(debug_str.contains("OrderBookDeltas"));
593        assert!(debug_str.contains("EURUSD.SIM"));
594        assert!(debug_str.contains("flags: 32"));
595        assert!(debug_str.contains("sequence: 123"));
596    }
597
598    #[rstest]
599    fn test_order_book_deltas_serialization() {
600        let deltas = create_test_deltas();
601
602        // Test JSON serialization
603        let json = serde_json::to_string(&deltas).unwrap();
604        let deserialized: OrderBookDeltas = serde_json::from_str(&json).unwrap();
605
606        assert_eq!(deltas.instrument_id, deserialized.instrument_id);
607        assert_eq!(deltas.deltas.len(), deserialized.deltas.len());
608        assert_eq!(deltas.flags, deserialized.flags);
609        assert_eq!(deltas.sequence, deserialized.sequence);
610        assert_eq!(deltas.ts_event, deserialized.ts_event);
611        assert_eq!(deltas.ts_init, deserialized.ts_init);
612    }
613
614    #[rstest]
615    fn test_json_serialization(stub_deltas: OrderBookDeltas) {
616        let deltas = stub_deltas;
617        let serialized = deltas.to_json_bytes().unwrap();
618        let deserialized = OrderBookDeltas::from_json_bytes(serialized.as_ref()).unwrap();
619
620        assert_order_book_deltas_fields(&deltas, &deserialized);
621    }
622
623    #[rstest]
624    fn test_msgpack_serialization() {
625        let deltas = create_test_deltas_multiple();
626        let serialized = deltas.to_msgpack_bytes().unwrap();
627        let deserialized = OrderBookDeltas::from_msgpack_bytes(serialized.as_ref()).unwrap();
628
629        assert_order_book_deltas_fields(&deltas, &deserialized);
630    }
631
632    #[rstest]
633    fn test_order_book_deltas_single_delta() {
634        let delta = create_test_delta();
635        let instrument_id = delta.instrument_id;
636
637        let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
638
639        assert_eq!(deltas.instrument_id, instrument_id);
640        assert_eq!(deltas.deltas.len(), 1);
641        assert_eq!(deltas.flags, delta.flags);
642        assert_eq!(deltas.sequence, delta.sequence);
643        assert_eq!(deltas.ts_event, delta.ts_event);
644        assert_eq!(deltas.ts_init, delta.ts_init);
645    }
646
647    #[rstest]
648    fn test_order_book_deltas_large_number_of_deltas() {
649        let instrument_id = InstrumentId::from("ETHUSD.CRYPTO");
650        let mut delta_vec = Vec::new();
651
652        // Create 100 deltas
653        for i in 0..100 {
654            let delta = OrderBookDelta::new(
655                instrument_id,
656                BookAction::Add,
657                BookOrder::new(
658                    OrderSide::Buy,
659                    Price::from(&format!("1000.{i:02}")),
660                    Quantity::from("1000"),
661                    i as u64,
662                ),
663                0,
664                i as u64,
665                UnixNanos::from(1_000_000_000 + i as u64),
666                UnixNanos::from(2_000_000_000 + i as u64),
667            );
668            delta_vec.push(delta);
669        }
670
671        let deltas = OrderBookDeltas::new(instrument_id, delta_vec);
672
673        assert_eq!(deltas.deltas.len(), 100);
674        assert_eq!(deltas.sequence, 99); // Last delta's sequence
675        assert_eq!(deltas.ts_event, UnixNanos::from(1_000_000_000 + 99));
676        assert_eq!(deltas.ts_init, UnixNanos::from(2_000_000_000 + 99));
677    }
678
679    #[rstest]
680    fn test_order_book_deltas_different_action_types() {
681        let deltas = create_test_deltas_multiple();
682
683        assert_eq!(deltas.deltas.len(), 4);
684
685        // Verify different action types are preserved
686        assert_eq!(deltas.deltas[0].action, BookAction::Clear);
687        assert_eq!(deltas.deltas[1].action, BookAction::Add);
688        assert_eq!(deltas.deltas[2].action, BookAction::Update);
689        assert_eq!(deltas.deltas[3].action, BookAction::Delete);
690    }
691
692    #[rstest]
693    fn test_order_book_deltas_with_stub(stub_deltas: OrderBookDeltas) {
694        let deltas = stub_deltas;
695
696        assert_eq!(deltas.instrument_id, InstrumentId::from("AAPL.XNAS"));
697        assert_eq!(deltas.deltas.len(), 7);
698        assert_eq!(deltas.flags, 32);
699        assert_eq!(deltas.sequence, 0);
700        assert_eq!(deltas.ts_event, UnixNanos::from(1));
701        assert_eq!(deltas.ts_init, UnixNanos::from(2));
702    }
703
704    #[rstest]
705    fn test_display_with_stub(stub_deltas: OrderBookDeltas) {
706        let deltas = stub_deltas;
707        assert_eq!(
708            format!("{deltas}"),
709            "AAPL.XNAS,len=7,flags=32,sequence=0,ts_event=1,ts_init=2".to_string()
710        );
711    }
712
713    #[rstest]
714    fn test_order_book_deltas_zero_sequence() {
715        let instrument_id = InstrumentId::from("ZERO.TEST");
716        let delta = OrderBookDelta::new(
717            instrument_id,
718            BookAction::Add,
719            BookOrder::new(
720                OrderSide::Buy,
721                Price::from("100.0"),
722                Quantity::from("1000"),
723                1,
724            ),
725            0,
726            0,                  // Zero sequence
727            UnixNanos::from(0), // Zero timestamp
728            UnixNanos::from(0),
729        );
730
731        let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
732
733        assert_eq!(deltas.sequence, 0);
734        assert_eq!(deltas.ts_event, UnixNanos::from(0));
735        assert_eq!(deltas.ts_init, UnixNanos::from(0));
736    }
737
738    #[rstest]
739    fn test_order_book_deltas_max_values() {
740        let instrument_id = InstrumentId::from("MAX.TEST");
741        let delta = OrderBookDelta::new(
742            instrument_id,
743            BookAction::Add,
744            BookOrder::new(
745                OrderSide::Buy,
746                Price::from("999999.99"),
747                Quantity::from("999999999"),
748                u64::MAX,
749            ),
750            u8::MAX,
751            u64::MAX,
752            UnixNanos::from(u64::MAX),
753            UnixNanos::from(u64::MAX),
754        );
755
756        let deltas = OrderBookDeltas::new(instrument_id, vec![delta]);
757
758        assert_eq!(deltas.flags, u8::MAX);
759        assert_eq!(deltas.sequence, u64::MAX);
760        assert_eq!(deltas.ts_event, UnixNanos::from(u64::MAX));
761        assert_eq!(deltas.ts_init, UnixNanos::from(u64::MAX));
762    }
763
764    #[rstest]
765    fn test_new() {
766        let instrument_id = InstrumentId::from("AAPL.XNAS");
767        let flags = 32; // Snapshot flag
768        let sequence = 0;
769        let ts_event = 1;
770        let ts_init = 2;
771
772        let delta0 =
773            OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
774        let delta1 = OrderBookDelta::new(
775            instrument_id,
776            BookAction::Add,
777            BookOrder::new(
778                OrderSide::Sell,
779                Price::from("102.00"),
780                Quantity::from("300"),
781                1,
782            ),
783            flags,
784            sequence,
785            ts_event.into(),
786            ts_init.into(),
787        );
788        let delta2 = OrderBookDelta::new(
789            instrument_id,
790            BookAction::Add,
791            BookOrder::new(
792                OrderSide::Sell,
793                Price::from("101.00"),
794                Quantity::from("200"),
795                2,
796            ),
797            flags,
798            sequence,
799            ts_event.into(),
800            ts_init.into(),
801        );
802        let delta3 = OrderBookDelta::new(
803            instrument_id,
804            BookAction::Add,
805            BookOrder::new(
806                OrderSide::Sell,
807                Price::from("100.00"),
808                Quantity::from("100"),
809                3,
810            ),
811            flags,
812            sequence,
813            ts_event.into(),
814            ts_init.into(),
815        );
816        let delta4 = OrderBookDelta::new(
817            instrument_id,
818            BookAction::Add,
819            BookOrder::new(
820                OrderSide::Buy,
821                Price::from("99.00"),
822                Quantity::from("100"),
823                4,
824            ),
825            flags,
826            sequence,
827            ts_event.into(),
828            ts_init.into(),
829        );
830        let delta5 = OrderBookDelta::new(
831            instrument_id,
832            BookAction::Add,
833            BookOrder::new(
834                OrderSide::Buy,
835                Price::from("98.00"),
836                Quantity::from("200"),
837                5,
838            ),
839            flags,
840            sequence,
841            ts_event.into(),
842            ts_init.into(),
843        );
844        let delta6 = OrderBookDelta::new(
845            instrument_id,
846            BookAction::Add,
847            BookOrder::new(
848                OrderSide::Buy,
849                Price::from("97.00"),
850                Quantity::from("300"),
851                6,
852            ),
853            flags,
854            sequence,
855            ts_event.into(),
856            ts_init.into(),
857        );
858
859        let deltas = OrderBookDeltas::new(
860            instrument_id,
861            vec![delta0, delta1, delta2, delta3, delta4, delta5, delta6],
862        );
863
864        assert_eq!(deltas.instrument_id, instrument_id);
865        assert_eq!(deltas.deltas.len(), 7);
866        assert_eq!(deltas.flags, flags);
867        assert_eq!(deltas.sequence, sequence);
868        assert_eq!(deltas.ts_event, ts_event);
869        assert_eq!(deltas.ts_init, ts_init);
870    }
871}