Skip to main content

nautilus_model/data/
delta.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 `OrderBookDelta` data type intended to carry book state information.
17
18use std::{collections::HashMap, fmt::Display, hash::Hash};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, correctness::FAILED, serialization::Serializable};
22use serde::{Deserialize, Serialize};
23
24use super::{
25    HasTsInit,
26    order::{BookOrder, NULL_ORDER},
27};
28use crate::{
29    enums::{BookAction, RecordFlag},
30    identifiers::InstrumentId,
31    types::{fixed::FIXED_SIZE_BINARY, quantity::check_positive_quantity},
32};
33
34/// Represents a single change/delta in an order book.
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
36#[serde(tag = "type")]
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 OrderBookDelta {
46    /// The instrument ID for the book.
47    pub instrument_id: InstrumentId,
48    /// The order book delta action.
49    pub action: BookAction,
50    /// The order to apply.
51    pub order: BookOrder,
52    /// The record flags bit field indicating event end and data information.
53    pub flags: u8,
54    /// The message sequence number assigned at the venue.
55    pub sequence: u64,
56    /// UNIX timestamp (nanoseconds) when the book event occurred.
57    pub ts_event: UnixNanos,
58    /// UNIX timestamp (nanoseconds) when the instance was created.
59    pub ts_init: UnixNanos,
60}
61
62impl OrderBookDelta {
63    /// Creates a new [`OrderBookDelta`] instance with correctness checking.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if `action` is [`BookAction::Add`] or [`BookAction::Update`] and `size` is not positive (> 0).
68    ///
69    /// # Notes
70    ///
71    /// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
72    pub fn new_checked(
73        instrument_id: InstrumentId,
74        action: BookAction,
75        order: BookOrder,
76        flags: u8,
77        sequence: u64,
78        ts_event: UnixNanos,
79        ts_init: UnixNanos,
80    ) -> anyhow::Result<Self> {
81        if matches!(action, BookAction::Add | BookAction::Update) {
82            check_positive_quantity(order.size, stringify!(order.size))?;
83        }
84
85        Ok(Self {
86            instrument_id,
87            action,
88            order,
89            flags,
90            sequence,
91            ts_event,
92            ts_init,
93        })
94    }
95
96    /// Creates a new [`OrderBookDelta`] instance.
97    ///
98    /// # Panics
99    ///
100    /// Panics if `action` is [`BookAction::Add`] or [`BookAction::Update`] and `size` is not positive (> 0).
101    #[must_use]
102    pub fn new(
103        instrument_id: InstrumentId,
104        action: BookAction,
105        order: BookOrder,
106        flags: u8,
107        sequence: u64,
108        ts_event: UnixNanos,
109        ts_init: UnixNanos,
110    ) -> Self {
111        Self::new_checked(
112            instrument_id,
113            action,
114            order,
115            flags,
116            sequence,
117            ts_event,
118            ts_init,
119        )
120        .expect(FAILED)
121    }
122
123    /// Creates a new [`OrderBookDelta`] instance with a `Clear` action and NULL order.
124    #[must_use]
125    pub fn clear(
126        instrument_id: InstrumentId,
127        sequence: u64,
128        ts_event: UnixNanos,
129        ts_init: UnixNanos,
130    ) -> Self {
131        Self {
132            instrument_id,
133            action: BookAction::Clear,
134            order: NULL_ORDER,
135            flags: RecordFlag::F_SNAPSHOT as u8,
136            sequence,
137            ts_event,
138            ts_init,
139        }
140    }
141
142    /// Returns whether the delta adds an order.
143    #[cfg_attr(not(feature = "python"), allow(dead_code))]
144    #[must_use]
145    pub(crate) const fn is_add(&self) -> bool {
146        matches!(self.action, BookAction::Add)
147    }
148
149    /// Returns whether the delta updates an order.
150    #[cfg_attr(not(feature = "python"), allow(dead_code))]
151    #[must_use]
152    pub(crate) const fn is_update(&self) -> bool {
153        matches!(self.action, BookAction::Update)
154    }
155
156    /// Returns whether the delta deletes an order.
157    #[cfg_attr(not(feature = "python"), allow(dead_code))]
158    #[must_use]
159    pub(crate) const fn is_delete(&self) -> bool {
160        matches!(self.action, BookAction::Delete)
161    }
162
163    /// Returns whether the delta clears the order book.
164    #[cfg_attr(not(feature = "python"), allow(dead_code))]
165    #[must_use]
166    pub(crate) const fn is_clear(&self) -> bool {
167        matches!(self.action, BookAction::Clear)
168    }
169
170    /// Returns the metadata for the type, for use with serialization formats.
171    #[must_use]
172    pub fn get_metadata(
173        instrument_id: &InstrumentId,
174        price_precision: u8,
175        size_precision: u8,
176    ) -> HashMap<String, String> {
177        let mut metadata = HashMap::new();
178        metadata.insert("instrument_id".to_string(), instrument_id.to_string());
179        metadata.insert("price_precision".to_string(), price_precision.to_string());
180        metadata.insert("size_precision".to_string(), size_precision.to_string());
181        metadata
182    }
183
184    /// Returns the field map for the type, for use with Arrow schemas.
185    #[must_use]
186    pub fn get_fields() -> IndexMap<String, String> {
187        let mut metadata = IndexMap::new();
188        metadata.insert("action".to_string(), "UInt8".to_string());
189        metadata.insert("side".to_string(), "UInt8".to_string());
190        metadata.insert("price".to_string(), FIXED_SIZE_BINARY.to_string());
191        metadata.insert("size".to_string(), FIXED_SIZE_BINARY.to_string());
192        metadata.insert("order_id".to_string(), "UInt64".to_string());
193        metadata.insert("flags".to_string(), "UInt8".to_string());
194        metadata.insert("sequence".to_string(), "UInt64".to_string());
195        metadata.insert("ts_event".to_string(), "UInt64".to_string());
196        metadata.insert("ts_init".to_string(), "UInt64".to_string());
197        metadata
198    }
199}
200
201impl Display for OrderBookDelta {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        write!(
204            f,
205            "{},{},{},{},{},{},{}",
206            self.instrument_id,
207            self.action,
208            self.order,
209            self.flags,
210            self.sequence,
211            self.ts_event,
212            self.ts_init
213        )
214    }
215}
216
217impl Serializable for OrderBookDelta {}
218
219impl HasTsInit for OrderBookDelta {
220    fn ts_init(&self) -> UnixNanos {
221        self.ts_init
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use std::{
228        collections::hash_map::DefaultHasher,
229        hash::{Hash, Hasher},
230    };
231
232    use nautilus_core::{
233        UnixNanos,
234        serialization::{
235            Serializable,
236            msgpack::{FromMsgPack, ToMsgPack},
237        },
238    };
239    use rstest::rstest;
240
241    use crate::{
242        data::{BookOrder, HasTsInit, OrderBookDelta, stubs::*},
243        enums::{BookAction, OrderSide, RecordFlag},
244        identifiers::InstrumentId,
245        types::{Price, Quantity},
246    };
247
248    fn create_test_delta() -> OrderBookDelta {
249        let order = BookOrder::new(
250            OrderSide::Buy,
251            Price::from("1.0500"),
252            Quantity::from("100000"),
253            12345,
254        );
255        OrderBookDelta::new(
256            InstrumentId::from("EURUSD.SIM"),
257            BookAction::Add,
258            order,
259            0,
260            123,
261            UnixNanos::from(1_000_000_000),
262            UnixNanos::from(2_000_000_000),
263        )
264    }
265
266    #[rstest]
267    fn test_order_book_delta_new() {
268        let delta = create_test_delta();
269
270        assert_eq!(delta.instrument_id, InstrumentId::from("EURUSD.SIM"));
271        assert_eq!(delta.action, BookAction::Add);
272        assert_eq!(delta.order.side, OrderSide::Buy.into());
273        assert_eq!(delta.order.price, Price::from("1.0500"));
274        assert_eq!(delta.order.size, Quantity::from("100000"));
275        assert_eq!(delta.order.order_id, 12345);
276        assert_eq!(delta.flags, 0);
277        assert_eq!(delta.sequence, 123);
278        assert_eq!(delta.ts_event, UnixNanos::from(1_000_000_000));
279        assert_eq!(delta.ts_init, UnixNanos::from(2_000_000_000));
280    }
281
282    #[rstest]
283    fn test_order_book_delta_new_checked_valid() {
284        let order = BookOrder::new(
285            OrderSide::Sell,
286            Price::from("1.0505"),
287            Quantity::from("50000"),
288            67890,
289        );
290        let result = OrderBookDelta::new_checked(
291            InstrumentId::from("GBPUSD.SIM"),
292            BookAction::Update,
293            order,
294            16,
295            456,
296            UnixNanos::from(500_000_000),
297            UnixNanos::from(1_500_000_000),
298        );
299
300        assert!(result.is_ok());
301        let delta = result.unwrap();
302        assert_eq!(delta.instrument_id, InstrumentId::from("GBPUSD.SIM"));
303        assert_eq!(delta.action, BookAction::Update);
304        assert_eq!(delta.order.side, OrderSide::Sell.into());
305        assert_eq!(delta.flags, 16);
306    }
307
308    #[rstest]
309    #[should_panic(expected = "invalid `Quantity` for 'order.size' not positive, was 0")]
310    fn test_order_book_delta_new_with_zero_size_panics() {
311        let instrument_id = InstrumentId::from("AAPL.XNAS");
312        let action = BookAction::Add;
313        let price = Price::from("100.00");
314        let zero_size = Quantity::from(0);
315        let side = OrderSide::Buy;
316        let order_id = 123_456;
317        let flags = 0;
318        let sequence = 1;
319        let ts_event = UnixNanos::from(0);
320        let ts_init = UnixNanos::from(1);
321
322        let order = BookOrder::new(side, price, zero_size, order_id);
323
324        let _ = OrderBookDelta::new(
325            instrument_id,
326            action,
327            order,
328            flags,
329            sequence,
330            ts_event,
331            ts_init,
332        );
333    }
334
335    #[rstest]
336    fn test_order_book_delta_new_checked_with_zero_size_error() {
337        let instrument_id = InstrumentId::from("AAPL.XNAS");
338        let action = BookAction::Add;
339        let price = Price::from("100.00");
340        let zero_size = Quantity::from(0);
341        let side = OrderSide::Buy;
342        let order_id = 123_456;
343        let flags = 0;
344        let sequence = 1;
345        let ts_event = UnixNanos::from(0);
346        let ts_init = UnixNanos::from(1);
347
348        let order = BookOrder::new(side, price, zero_size, order_id);
349
350        let result = OrderBookDelta::new_checked(
351            instrument_id,
352            action,
353            order,
354            flags,
355            sequence,
356            ts_event,
357            ts_init,
358        );
359
360        assert!(result.is_err());
361        assert!(
362            result
363                .unwrap_err()
364                .to_string()
365                .contains("invalid `Quantity` for 'order.size' not positive")
366        );
367    }
368
369    #[rstest]
370    fn test_order_book_delta_new_checked_delete_with_zero_size_ok() {
371        let order = BookOrder::new(
372            OrderSide::Buy,
373            Price::from("100.00"),
374            Quantity::from(0),
375            123_456,
376        );
377        let result = OrderBookDelta::new_checked(
378            InstrumentId::from("TEST.SIM"),
379            BookAction::Delete,
380            order,
381            0,
382            1,
383            UnixNanos::from(0),
384            UnixNanos::from(1),
385        );
386
387        assert!(result.is_ok());
388    }
389
390    #[rstest]
391    fn test_order_book_delta_clear() {
392        let instrument_id = InstrumentId::from("BTCUSD.CRYPTO");
393        let sequence = 999;
394        let ts_event = UnixNanos::from(3_000_000_000);
395        let ts_init = UnixNanos::from(4_000_000_000);
396
397        let delta = OrderBookDelta::clear(instrument_id, sequence, ts_event, ts_init);
398
399        assert_eq!(delta.instrument_id, instrument_id);
400        assert_eq!(delta.action, BookAction::Clear);
401        assert!(delta.order.price.is_zero());
402        assert!(delta.order.size.is_zero());
403        assert_eq!(delta.order.side, None);
404        assert_eq!(delta.order.order_id, 0);
405        assert_eq!(delta.flags, RecordFlag::F_SNAPSHOT as u8);
406        assert_eq!(delta.sequence, sequence);
407        assert_eq!(delta.ts_event, ts_event);
408        assert_eq!(delta.ts_init, ts_init);
409    }
410
411    #[rstest]
412    fn test_get_metadata() {
413        let instrument_id = InstrumentId::from("EURUSD.SIM");
414        let metadata = OrderBookDelta::get_metadata(&instrument_id, 5, 8);
415
416        assert_eq!(metadata.len(), 3);
417        assert_eq!(
418            metadata.get("instrument_id"),
419            Some(&"EURUSD.SIM".to_string())
420        );
421        assert_eq!(metadata.get("price_precision"), Some(&"5".to_string()));
422        assert_eq!(metadata.get("size_precision"), Some(&"8".to_string()));
423    }
424
425    #[rstest]
426    fn test_get_fields() {
427        let fields = OrderBookDelta::get_fields();
428
429        assert_eq!(fields.len(), 9);
430        assert_eq!(fields.get("action"), Some(&"UInt8".to_string()));
431        assert_eq!(fields.get("side"), Some(&"UInt8".to_string()));
432
433        #[cfg(feature = "high-precision")]
434        {
435            assert_eq!(
436                fields.get("price"),
437                Some(&"FixedSizeBinary(16)".to_string())
438            );
439            assert_eq!(fields.get("size"), Some(&"FixedSizeBinary(16)".to_string()));
440        }
441        #[cfg(not(feature = "high-precision"))]
442        {
443            assert_eq!(fields.get("price"), Some(&"FixedSizeBinary(8)".to_string()));
444            assert_eq!(fields.get("size"), Some(&"FixedSizeBinary(8)".to_string()));
445        }
446
447        assert_eq!(fields.get("order_id"), Some(&"UInt64".to_string()));
448        assert_eq!(fields.get("flags"), Some(&"UInt8".to_string()));
449        assert_eq!(fields.get("sequence"), Some(&"UInt64".to_string()));
450        assert_eq!(fields.get("ts_event"), Some(&"UInt64".to_string()));
451        assert_eq!(fields.get("ts_init"), Some(&"UInt64".to_string()));
452    }
453
454    #[rstest]
455    #[case::add(BookAction::Add, (true, false, false, false))]
456    #[case::update(BookAction::Update, (false, true, false, false))]
457    #[case::delete(BookAction::Delete, (false, false, true, false))]
458    #[case::clear(BookAction::Clear, (false, false, false, true))]
459    fn test_order_book_delta_action_predicates(
460        #[case] action: BookAction,
461        #[case] expected: (bool, bool, bool, bool),
462    ) {
463        let order = BookOrder::new(
464            OrderSide::Buy,
465            Price::from("100.00"),
466            if matches!(action, BookAction::Delete | BookAction::Clear) {
467                Quantity::from(0)
468            } else {
469                Quantity::from("1000")
470            },
471            123_456,
472        );
473
474        let result = if matches!(action, BookAction::Clear) {
475            Ok(OrderBookDelta::clear(
476                InstrumentId::from("TEST.SIM"),
477                1,
478                UnixNanos::from(1_000_000_000),
479                UnixNanos::from(2_000_000_000),
480            ))
481        } else {
482            OrderBookDelta::new_checked(
483                InstrumentId::from("TEST.SIM"),
484                action,
485                order,
486                0,
487                1,
488                UnixNanos::from(1_000_000_000),
489                UnixNanos::from(2_000_000_000),
490            )
491        };
492
493        assert!(result.is_ok());
494        let delta = result.unwrap();
495        assert_eq!(delta.action, action);
496        assert_eq!(
497            (
498                delta.is_add(),
499                delta.is_update(),
500                delta.is_delete(),
501                delta.is_clear(),
502            ),
503            expected
504        );
505    }
506
507    #[rstest]
508    #[case(OrderSide::Buy)]
509    #[case(OrderSide::Sell)]
510    fn test_order_book_delta_with_different_sides(#[case] side: OrderSide) {
511        let order = BookOrder::new(side, Price::from("100.00"), Quantity::from("1000"), 123_456);
512
513        let delta = OrderBookDelta::new(
514            InstrumentId::from("TEST.SIM"),
515            BookAction::Add,
516            order,
517            0,
518            1,
519            UnixNanos::from(1_000_000_000),
520            UnixNanos::from(2_000_000_000),
521        );
522
523        assert_eq!(delta.order.side, side.into());
524    }
525
526    #[rstest]
527    fn test_order_book_delta_has_ts_init() {
528        let delta = create_test_delta();
529        assert_eq!(delta.ts_init(), UnixNanos::from(2_000_000_000));
530    }
531
532    #[rstest]
533    fn test_order_book_delta_display() {
534        let delta = create_test_delta();
535        let display_str = format!("{delta}");
536
537        assert!(display_str.contains("EURUSD.SIM"));
538        assert!(display_str.contains("ADD"));
539        assert!(display_str.contains("BUY"));
540        assert!(display_str.contains("1.0500"));
541        assert!(display_str.contains("100000"));
542        assert!(display_str.contains("12345"));
543        assert!(display_str.contains("123"));
544    }
545
546    #[rstest]
547    fn test_order_book_delta_with_zero_timestamps() {
548        let order = BookOrder::new(
549            OrderSide::Buy,
550            Price::from("100.00"),
551            Quantity::from("1000"),
552            123_456,
553        );
554        let delta = OrderBookDelta::new(
555            InstrumentId::from("TEST.SIM"),
556            BookAction::Add,
557            order,
558            0,
559            0,
560            UnixNanos::from(0),
561            UnixNanos::from(0),
562        );
563
564        assert_eq!(delta.sequence, 0);
565        assert_eq!(delta.ts_event, UnixNanos::from(0));
566        assert_eq!(delta.ts_init, UnixNanos::from(0));
567    }
568
569    #[rstest]
570    fn test_order_book_delta_with_max_values() {
571        let order = BookOrder::new(
572            OrderSide::Sell,
573            Price::from("999999.9999"),
574            Quantity::from("999999999.9999"),
575            u64::MAX,
576        );
577        let delta = OrderBookDelta::new(
578            InstrumentId::from("TEST.SIM"),
579            BookAction::Update,
580            order,
581            u8::MAX,
582            u64::MAX,
583            UnixNanos::from(u64::MAX),
584            UnixNanos::from(u64::MAX),
585        );
586
587        assert_eq!(delta.flags, u8::MAX);
588        assert_eq!(delta.sequence, u64::MAX);
589        assert_eq!(delta.order.order_id, u64::MAX);
590        assert_eq!(delta.ts_event, UnixNanos::from(u64::MAX));
591        assert_eq!(delta.ts_init, UnixNanos::from(u64::MAX));
592    }
593
594    #[rstest]
595    fn test_new() {
596        let instrument_id = InstrumentId::from("AAPL.XNAS");
597        let action = BookAction::Add;
598        let price = Price::from("100.00");
599        let size = Quantity::from("10");
600        let side = OrderSide::Buy;
601        let order_id = 123_456;
602        let flags = 0;
603        let sequence = 1;
604        let ts_event = 1;
605        let ts_init = 2;
606
607        let order = BookOrder::new(side, price, size, order_id);
608
609        let delta = OrderBookDelta::new(
610            instrument_id,
611            action,
612            order,
613            flags,
614            sequence,
615            ts_event.into(),
616            ts_init.into(),
617        );
618
619        assert_eq!(delta.instrument_id, instrument_id);
620        assert_eq!(delta.action, action);
621        assert_eq!(delta.order.price, price);
622        assert_eq!(delta.order.size, size);
623        assert_eq!(delta.order.side, side.into());
624        assert_eq!(delta.order.order_id, order_id);
625        assert_eq!(delta.flags, flags);
626        assert_eq!(delta.sequence, sequence);
627        assert_eq!(delta.ts_event, ts_event);
628        assert_eq!(delta.ts_init, ts_init);
629    }
630
631    #[rstest]
632    fn test_clear() {
633        let instrument_id = InstrumentId::from("AAPL.XNAS");
634        let sequence = 1;
635        let ts_event = 2;
636        let ts_init = 3;
637
638        let delta = OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
639
640        assert_eq!(delta.instrument_id, instrument_id);
641        assert_eq!(delta.action, BookAction::Clear);
642        assert!(delta.order.price.is_zero());
643        assert!(delta.order.size.is_zero());
644        assert_eq!(delta.order.side, None);
645        assert_eq!(delta.order.order_id, 0);
646        assert_eq!(delta.flags, 32);
647        assert_eq!(delta.sequence, sequence);
648        assert_eq!(delta.ts_event, ts_event);
649        assert_eq!(delta.ts_init, ts_init);
650    }
651
652    #[rstest]
653    fn test_order_book_delta_hash() {
654        let delta1 = create_test_delta();
655        let delta2 = create_test_delta();
656
657        let mut hasher1 = DefaultHasher::new();
658        let mut hasher2 = DefaultHasher::new();
659
660        delta1.hash(&mut hasher1);
661        delta2.hash(&mut hasher2);
662
663        assert_eq!(hasher1.finish(), hasher2.finish());
664    }
665
666    #[rstest]
667    fn test_order_book_delta_hash_different_deltas() {
668        let delta1 = create_test_delta();
669        let order2 = BookOrder::new(
670            OrderSide::Sell,
671            Price::from("1.0505"),
672            Quantity::from("50000"),
673            67890,
674        );
675        let delta2 = OrderBookDelta::new(
676            InstrumentId::from("EURUSD.SIM"),
677            BookAction::Add,
678            order2,
679            0,
680            123,
681            UnixNanos::from(1_000_000_000),
682            UnixNanos::from(2_000_000_000),
683        );
684
685        let mut hasher1 = DefaultHasher::new();
686        let mut hasher2 = DefaultHasher::new();
687
688        delta1.hash(&mut hasher1);
689        delta2.hash(&mut hasher2);
690
691        assert_ne!(hasher1.finish(), hasher2.finish());
692    }
693
694    #[rstest]
695    fn test_order_book_delta_partial_eq() {
696        let delta1 = create_test_delta();
697        let delta2 = create_test_delta();
698
699        // Test equality
700        assert_eq!(delta1, delta2);
701
702        // Test inequality with different instrument
703        let order3 = BookOrder::new(
704            OrderSide::Buy,
705            Price::from("1.0500"),
706            Quantity::from("100000"),
707            12345,
708        );
709        let delta3 = OrderBookDelta::new(
710            InstrumentId::from("GBPUSD.SIM"),
711            BookAction::Add,
712            order3,
713            0,
714            123,
715            UnixNanos::from(1_000_000_000),
716            UnixNanos::from(2_000_000_000),
717        );
718
719        assert_ne!(delta1, delta3);
720    }
721
722    #[rstest]
723    fn test_order_book_delta_clone() {
724        let delta1 = create_test_delta();
725        let delta2 = delta1;
726
727        assert_eq!(delta1, delta2);
728        assert_eq!(delta1.instrument_id, delta2.instrument_id);
729        assert_eq!(delta1.action, delta2.action);
730        assert_eq!(delta1.order, delta2.order);
731        assert_eq!(delta1.flags, delta2.flags);
732        assert_eq!(delta1.sequence, delta2.sequence);
733        assert_eq!(delta1.ts_event, delta2.ts_event);
734        assert_eq!(delta1.ts_init, delta2.ts_init);
735    }
736
737    #[rstest]
738    fn test_order_book_delta_debug() {
739        let delta = create_test_delta();
740        let debug_str = format!("{delta:?}");
741
742        assert!(debug_str.contains("OrderBookDelta"));
743        assert!(debug_str.contains("EURUSD.SIM"));
744        assert!(debug_str.contains("Add"));
745        assert!(debug_str.contains("BUY"));
746        assert!(debug_str.contains("1.0500"));
747    }
748
749    #[rstest]
750    fn test_order_book_delta_serialization() {
751        let delta = create_test_delta();
752
753        let json = serde_json::to_string(&delta).unwrap();
754        let deserialized: OrderBookDelta = serde_json::from_str(&json).unwrap();
755
756        assert_order_book_delta_fields(&delta, &deserialized);
757    }
758
759    #[rstest]
760    fn test_json_serialization(stub_delta: OrderBookDelta) {
761        let delta = stub_delta;
762        let serialized = delta.to_json_bytes().unwrap();
763        let deserialized = OrderBookDelta::from_json_bytes(serialized.as_ref()).unwrap();
764        assert_order_book_delta_fields(&delta, &deserialized);
765    }
766
767    #[rstest]
768    fn test_msgpack_serialization(stub_delta: OrderBookDelta) {
769        let delta = stub_delta;
770        let serialized = delta.to_msgpack_bytes().unwrap();
771        let deserialized = OrderBookDelta::from_msgpack_bytes(serialized.as_ref()).unwrap();
772        assert_order_book_delta_fields(&delta, &deserialized);
773    }
774
775    fn assert_order_book_delta_fields(expected: &OrderBookDelta, actual: &OrderBookDelta) {
776        assert_eq!(expected.instrument_id, actual.instrument_id);
777        assert_eq!(expected.action, actual.action);
778        assert_eq!(expected.order.side, actual.order.side);
779        assert_eq!(expected.order.price, actual.order.price);
780        assert_eq!(expected.order.size, actual.order.size);
781        assert_eq!(expected.order.order_id, actual.order.order_id);
782        assert_eq!(expected.flags, actual.flags);
783        assert_eq!(expected.sequence, actual.sequence);
784        assert_eq!(expected.ts_event, actual.ts_event);
785        assert_eq!(expected.ts_init, actual.ts_init);
786    }
787}