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    ARROW_ENUM_DICTIONARY, ARROW_TIMESTAMP_NANOSECOND, HasTsInit,
26    order::{BookOrder, NULL_ORDER},
27};
28use crate::{
29    enums::{BookAction, RecordFlag},
30    identifiers::InstrumentId,
31    types::{fixed::FIXED_DECIMAL, 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(), ARROW_ENUM_DICTIONARY.to_string());
189        metadata.insert("side".to_string(), ARROW_ENUM_DICTIONARY.to_string());
190        metadata.insert("price".to_string(), FIXED_DECIMAL.to_string());
191        metadata.insert("size".to_string(), FIXED_DECIMAL.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(
196            "ts_event".to_string(),
197            ARROW_TIMESTAMP_NANOSECOND.to_string(),
198        );
199        metadata.insert(
200            "ts_init".to_string(),
201            ARROW_TIMESTAMP_NANOSECOND.to_string(),
202        );
203        metadata
204    }
205}
206
207impl Display for OrderBookDelta {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        write!(
210            f,
211            "{},{},{},{},{},{},{}",
212            self.instrument_id,
213            self.action,
214            self.order,
215            self.flags,
216            self.sequence,
217            self.ts_event,
218            self.ts_init
219        )
220    }
221}
222
223impl Serializable for OrderBookDelta {}
224
225impl HasTsInit for OrderBookDelta {
226    fn ts_init(&self) -> UnixNanos {
227        self.ts_init
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use std::{
234        collections::hash_map::DefaultHasher,
235        hash::{Hash, Hasher},
236    };
237
238    use nautilus_core::{
239        UnixNanos,
240        serialization::{
241            Serializable,
242            msgpack::{FromMsgPack, ToMsgPack},
243        },
244    };
245    use rstest::rstest;
246
247    use crate::{
248        data::{
249            ARROW_ENUM_DICTIONARY, ARROW_TIMESTAMP_NANOSECOND, BookOrder, HasTsInit,
250            OrderBookDelta, stubs::*,
251        },
252        enums::{BookAction, OrderSide, RecordFlag},
253        identifiers::InstrumentId,
254        types::{Price, Quantity, fixed::FIXED_DECIMAL},
255    };
256
257    fn create_test_delta() -> OrderBookDelta {
258        let order = BookOrder::new(
259            OrderSide::Buy,
260            Price::from("1.0500"),
261            Quantity::from("100000"),
262            12345,
263        );
264        OrderBookDelta::new(
265            InstrumentId::from("EURUSD.SIM"),
266            BookAction::Add,
267            order,
268            0,
269            123,
270            UnixNanos::from(1_000_000_000),
271            UnixNanos::from(2_000_000_000),
272        )
273    }
274
275    #[rstest]
276    fn test_order_book_delta_new() {
277        let delta = create_test_delta();
278
279        assert_eq!(delta.instrument_id, InstrumentId::from("EURUSD.SIM"));
280        assert_eq!(delta.action, BookAction::Add);
281        assert_eq!(delta.order.side, OrderSide::Buy.into());
282        assert_eq!(delta.order.price, Price::from("1.0500"));
283        assert_eq!(delta.order.size, Quantity::from("100000"));
284        assert_eq!(delta.order.order_id, 12345);
285        assert_eq!(delta.flags, 0);
286        assert_eq!(delta.sequence, 123);
287        assert_eq!(delta.ts_event, UnixNanos::from(1_000_000_000));
288        assert_eq!(delta.ts_init, UnixNanos::from(2_000_000_000));
289    }
290
291    #[rstest]
292    fn test_order_book_delta_new_checked_valid() {
293        let order = BookOrder::new(
294            OrderSide::Sell,
295            Price::from("1.0505"),
296            Quantity::from("50000"),
297            67890,
298        );
299        let result = OrderBookDelta::new_checked(
300            InstrumentId::from("GBPUSD.SIM"),
301            BookAction::Update,
302            order,
303            16,
304            456,
305            UnixNanos::from(500_000_000),
306            UnixNanos::from(1_500_000_000),
307        );
308
309        assert!(result.is_ok());
310        let delta = result.unwrap();
311        assert_eq!(delta.instrument_id, InstrumentId::from("GBPUSD.SIM"));
312        assert_eq!(delta.action, BookAction::Update);
313        assert_eq!(delta.order.side, OrderSide::Sell.into());
314        assert_eq!(delta.flags, 16);
315    }
316
317    #[rstest]
318    #[should_panic(expected = "invalid `Quantity` for 'order.size' not positive, was 0")]
319    fn test_order_book_delta_new_with_zero_size_panics() {
320        let instrument_id = InstrumentId::from("AAPL.XNAS");
321        let action = BookAction::Add;
322        let price = Price::from("100.00");
323        let zero_size = Quantity::from(0);
324        let side = OrderSide::Buy;
325        let order_id = 123_456;
326        let flags = 0;
327        let sequence = 1;
328        let ts_event = UnixNanos::from(0);
329        let ts_init = UnixNanos::from(1);
330
331        let order = BookOrder::new(side, price, zero_size, order_id);
332
333        let _ = OrderBookDelta::new(
334            instrument_id,
335            action,
336            order,
337            flags,
338            sequence,
339            ts_event,
340            ts_init,
341        );
342    }
343
344    #[rstest]
345    fn test_order_book_delta_new_checked_with_zero_size_error() {
346        let instrument_id = InstrumentId::from("AAPL.XNAS");
347        let action = BookAction::Add;
348        let price = Price::from("100.00");
349        let zero_size = Quantity::from(0);
350        let side = OrderSide::Buy;
351        let order_id = 123_456;
352        let flags = 0;
353        let sequence = 1;
354        let ts_event = UnixNanos::from(0);
355        let ts_init = UnixNanos::from(1);
356
357        let order = BookOrder::new(side, price, zero_size, order_id);
358
359        let result = OrderBookDelta::new_checked(
360            instrument_id,
361            action,
362            order,
363            flags,
364            sequence,
365            ts_event,
366            ts_init,
367        );
368
369        assert!(result.is_err());
370        assert!(
371            result
372                .unwrap_err()
373                .to_string()
374                .contains("invalid `Quantity` for 'order.size' not positive")
375        );
376    }
377
378    #[rstest]
379    fn test_order_book_delta_new_checked_delete_with_zero_size_ok() {
380        let order = BookOrder::new(
381            OrderSide::Buy,
382            Price::from("100.00"),
383            Quantity::from(0),
384            123_456,
385        );
386        let result = OrderBookDelta::new_checked(
387            InstrumentId::from("TEST.SIM"),
388            BookAction::Delete,
389            order,
390            0,
391            1,
392            UnixNanos::from(0),
393            UnixNanos::from(1),
394        );
395
396        assert!(result.is_ok());
397    }
398
399    #[rstest]
400    fn test_order_book_delta_clear() {
401        let instrument_id = InstrumentId::from("BTCUSD.CRYPTO");
402        let sequence = 999;
403        let ts_event = UnixNanos::from(3_000_000_000);
404        let ts_init = UnixNanos::from(4_000_000_000);
405
406        let delta = OrderBookDelta::clear(instrument_id, sequence, ts_event, ts_init);
407
408        assert_eq!(delta.instrument_id, instrument_id);
409        assert_eq!(delta.action, BookAction::Clear);
410        assert!(delta.order.price.is_zero());
411        assert!(delta.order.size.is_zero());
412        assert_eq!(delta.order.side, None);
413        assert_eq!(delta.order.order_id, 0);
414        assert_eq!(delta.flags, RecordFlag::F_SNAPSHOT as u8);
415        assert_eq!(delta.sequence, sequence);
416        assert_eq!(delta.ts_event, ts_event);
417        assert_eq!(delta.ts_init, ts_init);
418    }
419
420    #[rstest]
421    fn test_get_metadata() {
422        let instrument_id = InstrumentId::from("EURUSD.SIM");
423        let metadata = OrderBookDelta::get_metadata(&instrument_id, 5, 8);
424
425        assert_eq!(metadata.len(), 3);
426        assert_eq!(
427            metadata.get("instrument_id"),
428            Some(&"EURUSD.SIM".to_string())
429        );
430        assert_eq!(metadata.get("price_precision"), Some(&"5".to_string()));
431        assert_eq!(metadata.get("size_precision"), Some(&"8".to_string()));
432    }
433
434    #[rstest]
435    fn test_get_fields() {
436        let fields = OrderBookDelta::get_fields();
437
438        assert_eq!(fields.len(), 9);
439        assert_eq!(
440            fields.get("action"),
441            Some(&ARROW_ENUM_DICTIONARY.to_string())
442        );
443        assert_eq!(fields.get("side"), Some(&ARROW_ENUM_DICTIONARY.to_string()));
444
445        assert_eq!(fields.get("price"), Some(&FIXED_DECIMAL.to_string()));
446        assert_eq!(fields.get("size"), Some(&FIXED_DECIMAL.to_string()));
447
448        assert_eq!(fields.get("order_id"), Some(&"UInt64".to_string()));
449        assert_eq!(fields.get("flags"), Some(&"UInt8".to_string()));
450        assert_eq!(fields.get("sequence"), Some(&"UInt64".to_string()));
451        assert_eq!(
452            fields.get("ts_event"),
453            Some(&ARROW_TIMESTAMP_NANOSECOND.to_string())
454        );
455        assert_eq!(
456            fields.get("ts_init"),
457            Some(&ARROW_TIMESTAMP_NANOSECOND.to_string())
458        );
459        assert_eq!(fields.get("identifier"), None);
460    }
461
462    #[rstest]
463    #[case::add(BookAction::Add, (true, false, false, false))]
464    #[case::update(BookAction::Update, (false, true, false, false))]
465    #[case::delete(BookAction::Delete, (false, false, true, false))]
466    #[case::clear(BookAction::Clear, (false, false, false, true))]
467    fn test_order_book_delta_action_predicates(
468        #[case] action: BookAction,
469        #[case] expected: (bool, bool, bool, bool),
470    ) {
471        let order = BookOrder::new(
472            OrderSide::Buy,
473            Price::from("100.00"),
474            if matches!(action, BookAction::Delete | BookAction::Clear) {
475                Quantity::from(0)
476            } else {
477                Quantity::from("1000")
478            },
479            123_456,
480        );
481
482        let result = if matches!(action, BookAction::Clear) {
483            Ok(OrderBookDelta::clear(
484                InstrumentId::from("TEST.SIM"),
485                1,
486                UnixNanos::from(1_000_000_000),
487                UnixNanos::from(2_000_000_000),
488            ))
489        } else {
490            OrderBookDelta::new_checked(
491                InstrumentId::from("TEST.SIM"),
492                action,
493                order,
494                0,
495                1,
496                UnixNanos::from(1_000_000_000),
497                UnixNanos::from(2_000_000_000),
498            )
499        };
500
501        assert!(result.is_ok());
502        let delta = result.unwrap();
503        assert_eq!(delta.action, action);
504        assert_eq!(
505            (
506                delta.is_add(),
507                delta.is_update(),
508                delta.is_delete(),
509                delta.is_clear(),
510            ),
511            expected
512        );
513    }
514
515    #[rstest]
516    #[case(OrderSide::Buy)]
517    #[case(OrderSide::Sell)]
518    fn test_order_book_delta_with_different_sides(#[case] side: OrderSide) {
519        let order = BookOrder::new(side, Price::from("100.00"), Quantity::from("1000"), 123_456);
520
521        let delta = OrderBookDelta::new(
522            InstrumentId::from("TEST.SIM"),
523            BookAction::Add,
524            order,
525            0,
526            1,
527            UnixNanos::from(1_000_000_000),
528            UnixNanos::from(2_000_000_000),
529        );
530
531        assert_eq!(delta.order.side, side.into());
532    }
533
534    #[rstest]
535    fn test_order_book_delta_has_ts_init() {
536        let delta = create_test_delta();
537        assert_eq!(delta.ts_init(), UnixNanos::from(2_000_000_000));
538    }
539
540    #[rstest]
541    fn test_order_book_delta_display() {
542        let delta = create_test_delta();
543        let display_str = format!("{delta}");
544
545        assert!(display_str.contains("EURUSD.SIM"));
546        assert!(display_str.contains("ADD"));
547        assert!(display_str.contains("BUY"));
548        assert!(display_str.contains("1.0500"));
549        assert!(display_str.contains("100000"));
550        assert!(display_str.contains("12345"));
551        assert!(display_str.contains("123"));
552    }
553
554    #[rstest]
555    fn test_order_book_delta_with_zero_timestamps() {
556        let order = BookOrder::new(
557            OrderSide::Buy,
558            Price::from("100.00"),
559            Quantity::from("1000"),
560            123_456,
561        );
562        let delta = OrderBookDelta::new(
563            InstrumentId::from("TEST.SIM"),
564            BookAction::Add,
565            order,
566            0,
567            0,
568            UnixNanos::from(0),
569            UnixNanos::from(0),
570        );
571
572        assert_eq!(delta.sequence, 0);
573        assert_eq!(delta.ts_event, UnixNanos::from(0));
574        assert_eq!(delta.ts_init, UnixNanos::from(0));
575    }
576
577    #[rstest]
578    fn test_order_book_delta_with_max_values() {
579        let order = BookOrder::new(
580            OrderSide::Sell,
581            Price::from("999999.9999"),
582            Quantity::from("999999999.9999"),
583            u64::MAX,
584        );
585        let delta = OrderBookDelta::new(
586            InstrumentId::from("TEST.SIM"),
587            BookAction::Update,
588            order,
589            u8::MAX,
590            u64::MAX,
591            UnixNanos::from(u64::MAX),
592            UnixNanos::from(u64::MAX),
593        );
594
595        assert_eq!(delta.flags, u8::MAX);
596        assert_eq!(delta.sequence, u64::MAX);
597        assert_eq!(delta.order.order_id, u64::MAX);
598        assert_eq!(delta.ts_event, UnixNanos::from(u64::MAX));
599        assert_eq!(delta.ts_init, UnixNanos::from(u64::MAX));
600    }
601
602    #[rstest]
603    fn test_new() {
604        let instrument_id = InstrumentId::from("AAPL.XNAS");
605        let action = BookAction::Add;
606        let price = Price::from("100.00");
607        let size = Quantity::from("10");
608        let side = OrderSide::Buy;
609        let order_id = 123_456;
610        let flags = 0;
611        let sequence = 1;
612        let ts_event = 1;
613        let ts_init = 2;
614
615        let order = BookOrder::new(side, price, size, order_id);
616
617        let delta = OrderBookDelta::new(
618            instrument_id,
619            action,
620            order,
621            flags,
622            sequence,
623            ts_event.into(),
624            ts_init.into(),
625        );
626
627        assert_eq!(delta.instrument_id, instrument_id);
628        assert_eq!(delta.action, action);
629        assert_eq!(delta.order.price, price);
630        assert_eq!(delta.order.size, size);
631        assert_eq!(delta.order.side, side.into());
632        assert_eq!(delta.order.order_id, order_id);
633        assert_eq!(delta.flags, flags);
634        assert_eq!(delta.sequence, sequence);
635        assert_eq!(delta.ts_event, ts_event);
636        assert_eq!(delta.ts_init, ts_init);
637    }
638
639    #[rstest]
640    fn test_clear() {
641        let instrument_id = InstrumentId::from("AAPL.XNAS");
642        let sequence = 1;
643        let ts_event = 2;
644        let ts_init = 3;
645
646        let delta = OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
647
648        assert_eq!(delta.instrument_id, instrument_id);
649        assert_eq!(delta.action, BookAction::Clear);
650        assert!(delta.order.price.is_zero());
651        assert!(delta.order.size.is_zero());
652        assert_eq!(delta.order.side, None);
653        assert_eq!(delta.order.order_id, 0);
654        assert_eq!(delta.flags, 32);
655        assert_eq!(delta.sequence, sequence);
656        assert_eq!(delta.ts_event, ts_event);
657        assert_eq!(delta.ts_init, ts_init);
658    }
659
660    #[rstest]
661    fn test_order_book_delta_hash() {
662        let delta1 = create_test_delta();
663        let delta2 = create_test_delta();
664
665        let mut hasher1 = DefaultHasher::new();
666        let mut hasher2 = DefaultHasher::new();
667
668        delta1.hash(&mut hasher1);
669        delta2.hash(&mut hasher2);
670
671        assert_eq!(hasher1.finish(), hasher2.finish());
672    }
673
674    #[rstest]
675    fn test_order_book_delta_hash_different_deltas() {
676        let delta1 = create_test_delta();
677        let order2 = BookOrder::new(
678            OrderSide::Sell,
679            Price::from("1.0505"),
680            Quantity::from("50000"),
681            67890,
682        );
683        let delta2 = OrderBookDelta::new(
684            InstrumentId::from("EURUSD.SIM"),
685            BookAction::Add,
686            order2,
687            0,
688            123,
689            UnixNanos::from(1_000_000_000),
690            UnixNanos::from(2_000_000_000),
691        );
692
693        let mut hasher1 = DefaultHasher::new();
694        let mut hasher2 = DefaultHasher::new();
695
696        delta1.hash(&mut hasher1);
697        delta2.hash(&mut hasher2);
698
699        assert_ne!(hasher1.finish(), hasher2.finish());
700    }
701
702    #[rstest]
703    fn test_order_book_delta_partial_eq() {
704        let delta1 = create_test_delta();
705        let delta2 = create_test_delta();
706
707        // Test equality
708        assert_eq!(delta1, delta2);
709
710        // Test inequality with different instrument
711        let order3 = BookOrder::new(
712            OrderSide::Buy,
713            Price::from("1.0500"),
714            Quantity::from("100000"),
715            12345,
716        );
717        let delta3 = OrderBookDelta::new(
718            InstrumentId::from("GBPUSD.SIM"),
719            BookAction::Add,
720            order3,
721            0,
722            123,
723            UnixNanos::from(1_000_000_000),
724            UnixNanos::from(2_000_000_000),
725        );
726
727        assert_ne!(delta1, delta3);
728    }
729
730    #[rstest]
731    fn test_order_book_delta_clone() {
732        let delta1 = create_test_delta();
733        let delta2 = delta1;
734
735        assert_eq!(delta1, delta2);
736        assert_eq!(delta1.instrument_id, delta2.instrument_id);
737        assert_eq!(delta1.action, delta2.action);
738        assert_eq!(delta1.order, delta2.order);
739        assert_eq!(delta1.flags, delta2.flags);
740        assert_eq!(delta1.sequence, delta2.sequence);
741        assert_eq!(delta1.ts_event, delta2.ts_event);
742        assert_eq!(delta1.ts_init, delta2.ts_init);
743    }
744
745    #[rstest]
746    fn test_order_book_delta_debug() {
747        let delta = create_test_delta();
748        let debug_str = format!("{delta:?}");
749
750        assert!(debug_str.contains("OrderBookDelta"));
751        assert!(debug_str.contains("EURUSD.SIM"));
752        assert!(debug_str.contains("Add"));
753        assert!(debug_str.contains("BUY"));
754        assert!(debug_str.contains("1.0500"));
755    }
756
757    #[rstest]
758    fn test_order_book_delta_serialization() {
759        let delta = create_test_delta();
760
761        let json = serde_json::to_string(&delta).unwrap();
762        let deserialized: OrderBookDelta = serde_json::from_str(&json).unwrap();
763
764        assert_order_book_delta_fields(&delta, &deserialized);
765    }
766
767    #[rstest]
768    fn test_json_serialization(stub_delta: OrderBookDelta) {
769        let delta = stub_delta;
770        let serialized = delta.to_json_bytes().unwrap();
771        let deserialized = OrderBookDelta::from_json_bytes(serialized.as_ref()).unwrap();
772        assert_order_book_delta_fields(&delta, &deserialized);
773    }
774
775    #[rstest]
776    fn test_msgpack_serialization(stub_delta: OrderBookDelta) {
777        let delta = stub_delta;
778        let serialized = delta.to_msgpack_bytes().unwrap();
779        let deserialized = OrderBookDelta::from_msgpack_bytes(serialized.as_ref()).unwrap();
780        assert_order_book_delta_fields(&delta, &deserialized);
781    }
782
783    fn assert_order_book_delta_fields(expected: &OrderBookDelta, actual: &OrderBookDelta) {
784        assert_eq!(expected.instrument_id, actual.instrument_id);
785        assert_eq!(expected.action, actual.action);
786        assert_eq!(expected.order.side, actual.order.side);
787        assert_eq!(expected.order.price, actual.order.price);
788        assert_eq!(expected.order.size, actual.order.size);
789        assert_eq!(expected.order.order_id, actual.order.order_id);
790        assert_eq!(expected.flags, actual.flags);
791        assert_eq!(expected.sequence, actual.sequence);
792        assert_eq!(expected.ts_event, actual.ts_event);
793        assert_eq!(expected.ts_init, actual.ts_init);
794    }
795}