Skip to main content

nautilus_model/data/
depth.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 `OrderBookDepth10` aggregated top-of-book data type with a fixed depth of 10 levels per side.
17
18use std::{collections::HashMap, fmt::Display};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, serialization::Serializable};
22use serde::{Deserialize, Serialize};
23
24use super::{HasTsInit, order::BookOrder};
25use crate::{identifiers::InstrumentId, types::fixed::FIXED_SIZE_BINARY};
26
27pub const DEPTH10_LEN: usize = 10;
28
29/// Represents an aggregated order book update with a fixed depth of 10 levels per side.
30///
31/// This structure is specifically designed for scenarios where a snapshot of the top 10 bid and
32/// ask levels in an order book is needed. It differs from `OrderBookDelta` or `OrderBookDeltas`
33/// in its fixed-depth nature and is optimized for cases where a full depth representation is not
34/// required or practical.
35///
36/// Note: This type is not compatible with `OrderBookDelta` or `OrderBookDeltas` due to
37/// its specialized structure and limited depth use case.
38///
39/// Per-level [`BookOrder::order_id`] values are non-semantic for this aggregated MBP data.
40/// Parquet catalog decoding canonicalizes them to zero.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
42#[cfg_attr(
43    feature = "python",
44    pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
45)]
46#[cfg_attr(
47    feature = "python",
48    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
49)]
50pub struct OrderBookDepth10 {
51    /// The instrument ID for the book.
52    pub instrument_id: InstrumentId,
53    /// The bid orders for the depth update.
54    pub bids: [BookOrder; DEPTH10_LEN],
55    /// The ask orders for the depth update.
56    pub asks: [BookOrder; DEPTH10_LEN],
57    /// The count of bid orders per level for the depth update.
58    pub bid_counts: [u32; DEPTH10_LEN],
59    /// The count of ask orders per level for the depth update.
60    pub ask_counts: [u32; DEPTH10_LEN],
61    /// The record flags bit field, indicating event end and data information.
62    pub flags: u8,
63    /// The message sequence number assigned at the venue.
64    pub sequence: u64,
65    /// UNIX timestamp (nanoseconds) when the book event occurred.
66    pub ts_event: UnixNanos,
67    /// UNIX timestamp (nanoseconds) when the instance was created.
68    pub ts_init: UnixNanos,
69}
70
71impl OrderBookDepth10 {
72    /// Creates a new [`OrderBookDepth10`] instance.
73    #[expect(clippy::too_many_arguments)]
74    #[must_use]
75    pub fn new(
76        instrument_id: InstrumentId,
77        bids: [BookOrder; DEPTH10_LEN],
78        asks: [BookOrder; DEPTH10_LEN],
79        bid_counts: [u32; DEPTH10_LEN],
80        ask_counts: [u32; DEPTH10_LEN],
81        flags: u8,
82        sequence: u64,
83        ts_event: UnixNanos,
84        ts_init: UnixNanos,
85    ) -> Self {
86        Self {
87            instrument_id,
88            bids,
89            asks,
90            bid_counts,
91            ask_counts,
92            flags,
93            sequence,
94            ts_event,
95            ts_init,
96        }
97    }
98
99    /// Returns the metadata for the type, for use with serialization formats.
100    #[must_use]
101    pub fn get_metadata(
102        instrument_id: &InstrumentId,
103        price_precision: u8,
104        size_precision: u8,
105    ) -> HashMap<String, String> {
106        let mut metadata = HashMap::new();
107        metadata.insert("instrument_id".to_string(), instrument_id.to_string());
108        metadata.insert("price_precision".to_string(), price_precision.to_string());
109        metadata.insert("size_precision".to_string(), size_precision.to_string());
110        metadata
111    }
112
113    /// Returns the field map for the type, for use with Arrow schemas.
114    #[must_use]
115    pub fn get_fields() -> IndexMap<String, String> {
116        let mut metadata = IndexMap::new();
117
118        for name in ["bid_price", "ask_price", "bid_size", "ask_size"] {
119            for level in 0..DEPTH10_LEN {
120                metadata.insert(format!("{name}_{level}"), FIXED_SIZE_BINARY.to_string());
121            }
122        }
123
124        for name in ["bid_count", "ask_count"] {
125            for level in 0..DEPTH10_LEN {
126                metadata.insert(format!("{name}_{level}"), "UInt32".to_string());
127            }
128        }
129
130        metadata.insert("flags".to_string(), "UInt8".to_string());
131        metadata.insert("sequence".to_string(), "UInt64".to_string());
132        metadata.insert("ts_event".to_string(), "UInt64".to_string());
133        metadata.insert("ts_init".to_string(), "UInt64".to_string());
134        metadata
135    }
136}
137
138// TODO: Exact format for Debug and Display TBD
139impl Display for OrderBookDepth10 {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        write!(
142            f,
143            "{},flags={},sequence={},ts_event={},ts_init={}",
144            self.instrument_id, self.flags, self.sequence, self.ts_event, self.ts_init
145        )
146    }
147}
148
149impl Serializable for OrderBookDepth10 {}
150
151impl HasTsInit for OrderBookDepth10 {
152    fn ts_init(&self) -> UnixNanos {
153        self.ts_init
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use std::{
160        collections::hash_map::DefaultHasher,
161        hash::{Hash, Hasher},
162    };
163
164    use rstest::rstest;
165    use serde_json;
166
167    use super::*;
168    use crate::{
169        data::{order::BookOrder, stubs::*},
170        enums::OrderSide,
171        types::{Price, Quantity},
172    };
173
174    fn create_test_book_order(
175        side: OrderSide,
176        price: &str,
177        size: &str,
178        order_id: u64,
179    ) -> BookOrder {
180        BookOrder::new(side, Price::from(price), Quantity::from(size), order_id)
181    }
182
183    fn create_test_depth10() -> OrderBookDepth10 {
184        let instrument_id = InstrumentId::from("EURUSD.SIM");
185
186        // Create bid orders (descending prices)
187        let bids = [
188            create_test_book_order(OrderSide::Buy, "1.0500", "100000", 1),
189            create_test_book_order(OrderSide::Buy, "1.0499", "150000", 2),
190            create_test_book_order(OrderSide::Buy, "1.0498", "200000", 3),
191            create_test_book_order(OrderSide::Buy, "1.0497", "125000", 4),
192            create_test_book_order(OrderSide::Buy, "1.0496", "175000", 5),
193            create_test_book_order(OrderSide::Buy, "1.0495", "100000", 6),
194            create_test_book_order(OrderSide::Buy, "1.0494", "225000", 7),
195            create_test_book_order(OrderSide::Buy, "1.0493", "150000", 8),
196            create_test_book_order(OrderSide::Buy, "1.0492", "300000", 9),
197            create_test_book_order(OrderSide::Buy, "1.0491", "175000", 10),
198        ];
199
200        // Create ask orders (ascending prices)
201        let asks = [
202            create_test_book_order(OrderSide::Sell, "1.0501", "100000", 11),
203            create_test_book_order(OrderSide::Sell, "1.0502", "125000", 12),
204            create_test_book_order(OrderSide::Sell, "1.0503", "150000", 13),
205            create_test_book_order(OrderSide::Sell, "1.0504", "175000", 14),
206            create_test_book_order(OrderSide::Sell, "1.0505", "200000", 15),
207            create_test_book_order(OrderSide::Sell, "1.0506", "100000", 16),
208            create_test_book_order(OrderSide::Sell, "1.0507", "250000", 17),
209            create_test_book_order(OrderSide::Sell, "1.0508", "125000", 18),
210            create_test_book_order(OrderSide::Sell, "1.0509", "300000", 19),
211            create_test_book_order(OrderSide::Sell, "1.0510", "175000", 20),
212        ];
213
214        let bid_counts = [1, 2, 1, 3, 1, 2, 1, 4, 1, 2];
215        let ask_counts = [2, 1, 3, 1, 2, 1, 4, 1, 2, 3];
216
217        OrderBookDepth10::new(
218            instrument_id,
219            bids,
220            asks,
221            bid_counts,
222            ask_counts,
223            32,                             // flags
224            12345,                          // sequence
225            UnixNanos::from(1_000_000_000), // ts_event
226            UnixNanos::from(2_000_000_000), // ts_init
227        )
228    }
229
230    fn create_empty_depth10() -> OrderBookDepth10 {
231        let instrument_id = InstrumentId::from("EMPTY.TEST");
232
233        // Create empty orders with zero prices and quantities
234        let empty_bid = create_test_book_order(OrderSide::Buy, "0.0", "0", 0);
235        let empty_ask = create_test_book_order(OrderSide::Sell, "0.0", "0", 0);
236
237        OrderBookDepth10::new(
238            instrument_id,
239            [empty_bid; DEPTH10_LEN],
240            [empty_ask; DEPTH10_LEN],
241            [0; DEPTH10_LEN],
242            [0; DEPTH10_LEN],
243            0,
244            0,
245            UnixNanos::from(0),
246            UnixNanos::from(0),
247        )
248    }
249
250    #[rstest]
251    fn test_order_book_depth10_new() {
252        let depth = create_test_depth10();
253
254        assert_eq!(depth.instrument_id, InstrumentId::from("EURUSD.SIM"));
255        assert_eq!(depth.bids.len(), DEPTH10_LEN);
256        assert_eq!(depth.asks.len(), DEPTH10_LEN);
257        assert_eq!(depth.bid_counts.len(), DEPTH10_LEN);
258        assert_eq!(depth.ask_counts.len(), DEPTH10_LEN);
259        assert_eq!(depth.flags, 32);
260        assert_eq!(depth.sequence, 12345);
261        assert_eq!(depth.ts_event, UnixNanos::from(1_000_000_000));
262        assert_eq!(depth.ts_init, UnixNanos::from(2_000_000_000));
263    }
264
265    #[rstest]
266    fn test_order_book_depth10_new_with_all_parameters() {
267        let instrument_id = InstrumentId::from("GBPUSD.SIM");
268        let bid = create_test_book_order(OrderSide::Buy, "1.2500", "50000", 1);
269        let ask = create_test_book_order(OrderSide::Sell, "1.2501", "75000", 2);
270        let flags = 64u8;
271        let sequence = 999u64;
272        let ts_event = UnixNanos::from(5_000_000_000);
273        let ts_init = UnixNanos::from(6_000_000_000);
274
275        let depth = OrderBookDepth10::new(
276            instrument_id,
277            [bid; DEPTH10_LEN],
278            [ask; DEPTH10_LEN],
279            [5; DEPTH10_LEN],
280            [3; DEPTH10_LEN],
281            flags,
282            sequence,
283            ts_event,
284            ts_init,
285        );
286
287        assert_eq!(depth.instrument_id, instrument_id);
288        assert_eq!(depth.bids[0], bid);
289        assert_eq!(depth.asks[0], ask);
290        assert_eq!(depth.bid_counts[0], 5);
291        assert_eq!(depth.ask_counts[0], 3);
292        assert_eq!(depth.flags, flags);
293        assert_eq!(depth.sequence, sequence);
294        assert_eq!(depth.ts_event, ts_event);
295        assert_eq!(depth.ts_init, ts_init);
296    }
297
298    #[rstest]
299    fn test_order_book_depth10_fixed_array_sizes() {
300        let depth = create_test_depth10();
301
302        // Verify arrays are exactly DEPTH10_LEN (10)
303        assert_eq!(depth.bids.len(), 10);
304        assert_eq!(depth.asks.len(), 10);
305        assert_eq!(depth.bid_counts.len(), 10);
306        assert_eq!(depth.ask_counts.len(), 10);
307
308        // Verify DEPTH10_LEN constant
309        assert_eq!(DEPTH10_LEN, 10);
310    }
311
312    #[rstest]
313    fn test_order_book_depth10_array_indexing() {
314        let depth = create_test_depth10();
315
316        // Test first and last elements of each array
317        assert_eq!(depth.bids[0].price, Price::from("1.0500"));
318        assert_eq!(depth.bids[9].price, Price::from("1.0491"));
319        assert_eq!(depth.asks[0].price, Price::from("1.0501"));
320        assert_eq!(depth.asks[9].price, Price::from("1.0510"));
321        assert_eq!(depth.bid_counts[0], 1);
322        assert_eq!(depth.bid_counts[9], 2);
323        assert_eq!(depth.ask_counts[0], 2);
324        assert_eq!(depth.ask_counts[9], 3);
325    }
326
327    #[rstest]
328    fn test_order_book_depth10_bid_ask_ordering() {
329        let depth = create_test_depth10();
330
331        // Verify bid prices are in descending order (highest to lowest)
332        for i in 0..9 {
333            assert!(
334                depth.bids[i].price >= depth.bids[i + 1].price,
335                "Bid prices should be in descending order: {} >= {}",
336                depth.bids[i].price,
337                depth.bids[i + 1].price
338            );
339        }
340
341        // Verify ask prices are in ascending order (lowest to highest)
342        for i in 0..9 {
343            assert!(
344                depth.asks[i].price <= depth.asks[i + 1].price,
345                "Ask prices should be in ascending order: {} <= {}",
346                depth.asks[i].price,
347                depth.asks[i + 1].price
348            );
349        }
350
351        // Verify bid-ask spread (best bid < best ask)
352        assert!(
353            depth.bids[0].price < depth.asks[0].price,
354            "Best bid {} should be less than best ask {}",
355            depth.bids[0].price,
356            depth.asks[0].price
357        );
358    }
359
360    #[rstest]
361    fn test_order_book_depth10_clone() {
362        let depth1 = create_test_depth10();
363        let depth2 = depth1;
364
365        assert_eq!(depth1.instrument_id, depth2.instrument_id);
366        assert_eq!(depth1.bids, depth2.bids);
367        assert_eq!(depth1.asks, depth2.asks);
368        assert_eq!(depth1.bid_counts, depth2.bid_counts);
369        assert_eq!(depth1.ask_counts, depth2.ask_counts);
370        assert_eq!(depth1.flags, depth2.flags);
371        assert_eq!(depth1.sequence, depth2.sequence);
372        assert_eq!(depth1.ts_event, depth2.ts_event);
373        assert_eq!(depth1.ts_init, depth2.ts_init);
374    }
375
376    #[rstest]
377    fn test_order_book_depth10_copy() {
378        let depth1 = create_test_depth10();
379        let depth2 = depth1;
380
381        // Verify Copy trait by modifying one and ensuring the other is unchanged
382        // Since we're using Copy, this should work without explicit clone
383        assert_eq!(depth1, depth2);
384    }
385
386    #[rstest]
387    fn test_order_book_depth10_debug() {
388        let depth = create_test_depth10();
389        let debug_str = format!("{depth:?}");
390
391        assert!(debug_str.contains("OrderBookDepth10"));
392        assert!(debug_str.contains("EURUSD.SIM"));
393        assert!(debug_str.contains("flags: 32"));
394        assert!(debug_str.contains("sequence: 12345"));
395    }
396
397    #[rstest]
398    fn test_order_book_depth10_partial_eq() {
399        let depth1 = create_test_depth10();
400        let depth2 = create_test_depth10();
401        let depth3 = create_empty_depth10();
402
403        assert_eq!(depth1, depth2); // Same data
404        assert_ne!(depth1, depth3); // Different data
405        assert_ne!(depth2, depth3); // Different data
406    }
407
408    #[rstest]
409    fn test_order_book_depth10_eq_consistency() {
410        let depth1 = create_test_depth10();
411        let depth2 = create_test_depth10();
412
413        assert_eq!(depth1, depth2);
414        assert_eq!(depth2, depth1); // Symmetry
415        assert_eq!(depth1, depth1); // Reflexivity
416    }
417
418    #[rstest]
419    fn test_order_book_depth10_hash() {
420        let depth1 = create_test_depth10();
421        let depth2 = create_test_depth10();
422
423        let mut hasher1 = DefaultHasher::new();
424        let mut hasher2 = DefaultHasher::new();
425
426        depth1.hash(&mut hasher1);
427        depth2.hash(&mut hasher2);
428
429        assert_eq!(hasher1.finish(), hasher2.finish()); // Equal objects have equal hashes
430    }
431
432    #[rstest]
433    fn test_order_book_depth10_hash_different_objects() {
434        let depth1 = create_test_depth10();
435        let depth2 = create_empty_depth10();
436
437        let mut hasher1 = DefaultHasher::new();
438        let mut hasher2 = DefaultHasher::new();
439
440        depth1.hash(&mut hasher1);
441        depth2.hash(&mut hasher2);
442
443        assert_ne!(hasher1.finish(), hasher2.finish()); // Different objects should have different hashes
444    }
445
446    #[rstest]
447    fn test_order_book_depth10_display() {
448        let depth = create_test_depth10();
449        let display_str = format!("{depth}");
450
451        assert!(display_str.contains("EURUSD.SIM"));
452        assert!(display_str.contains("flags=32"));
453        assert!(display_str.contains("sequence=12345"));
454        assert!(display_str.contains("ts_event=1000000000"));
455        assert!(display_str.contains("ts_init=2000000000"));
456    }
457
458    #[rstest]
459    fn test_order_book_depth10_display_format() {
460        let depth = create_test_depth10();
461        let expected = "EURUSD.SIM,flags=32,sequence=12345,ts_event=1000000000,ts_init=2000000000";
462
463        assert_eq!(format!("{depth}"), expected);
464    }
465
466    #[rstest]
467    fn test_order_book_depth10_serialization() {
468        let depth = create_test_depth10();
469
470        // Test JSON serialization
471        let json = serde_json::to_string(&depth).unwrap();
472        let deserialized: OrderBookDepth10 = serde_json::from_str(&json).unwrap();
473
474        assert_eq!(depth, deserialized);
475    }
476
477    #[rstest]
478    fn test_order_book_depth10_serializable_trait() {
479        fn assert_serializable<T: Serializable>(_: &T) {}
480
481        let depth = create_test_depth10();
482
483        // Verify Serializable trait is implemented (compile-time check)
484        assert_serializable(&depth);
485    }
486
487    #[rstest]
488    fn test_order_book_depth10_has_ts_init() {
489        let depth = create_test_depth10();
490
491        assert_eq!(depth.ts_init(), UnixNanos::from(2_000_000_000));
492    }
493
494    #[rstest]
495    fn test_order_book_depth10_get_metadata() {
496        let instrument_id = InstrumentId::from("EURUSD.SIM");
497        let price_precision = 5u8;
498        let size_precision = 0u8;
499
500        let metadata =
501            OrderBookDepth10::get_metadata(&instrument_id, price_precision, size_precision);
502
503        assert_eq!(
504            metadata.get("instrument_id"),
505            Some(&"EURUSD.SIM".to_string())
506        );
507        assert_eq!(metadata.get("price_precision"), Some(&"5".to_string()));
508        assert_eq!(metadata.get("size_precision"), Some(&"0".to_string()));
509        assert_eq!(metadata.len(), 3);
510    }
511
512    #[rstest]
513    fn test_order_book_depth10_get_fields() {
514        let fields = OrderBookDepth10::get_fields();
515
516        // Verify all 10 bid and ask price fields
517        for i in 0..10 {
518            assert_eq!(
519                fields.get(&format!("bid_price_{i}")),
520                Some(&FIXED_SIZE_BINARY.to_string())
521            );
522            assert_eq!(
523                fields.get(&format!("ask_price_{i}")),
524                Some(&FIXED_SIZE_BINARY.to_string())
525            );
526        }
527
528        // Verify all 10 bid and ask size fields
529        for i in 0..10 {
530            assert_eq!(
531                fields.get(&format!("bid_size_{i}")),
532                Some(&FIXED_SIZE_BINARY.to_string())
533            );
534            assert_eq!(
535                fields.get(&format!("ask_size_{i}")),
536                Some(&FIXED_SIZE_BINARY.to_string())
537            );
538        }
539
540        // Verify all 10 bid and ask count fields
541        for i in 0..10 {
542            assert_eq!(
543                fields.get(&format!("bid_count_{i}")),
544                Some(&"UInt32".to_string())
545            );
546            assert_eq!(
547                fields.get(&format!("ask_count_{i}")),
548                Some(&"UInt32".to_string())
549            );
550        }
551
552        // Verify metadata fields
553        assert_eq!(fields.get("flags"), Some(&"UInt8".to_string()));
554        assert_eq!(fields.get("sequence"), Some(&"UInt64".to_string()));
555        assert_eq!(fields.get("ts_event"), Some(&"UInt64".to_string()));
556        assert_eq!(fields.get("ts_init"), Some(&"UInt64".to_string()));
557
558        // Verify total field count:
559        // 10 bid_price + 10 ask_price + 10 bid_size + 10 ask_size + 10 bid_count + 10 ask_count + 4 metadata = 64
560        assert_eq!(fields.len(), 64);
561    }
562
563    #[rstest]
564    fn test_order_book_depth10_get_fields_order() {
565        let fields = OrderBookDepth10::get_fields();
566        let keys: Vec<&String> = fields.keys().collect();
567
568        // Verify the ordering of fields matches expectations
569        assert_eq!(keys[0], "bid_price_0");
570        assert_eq!(keys[9], "bid_price_9");
571        assert_eq!(keys[10], "ask_price_0");
572        assert_eq!(keys[19], "ask_price_9");
573        assert_eq!(keys[20], "bid_size_0");
574        assert_eq!(keys[29], "bid_size_9");
575        assert_eq!(keys[30], "ask_size_0");
576        assert_eq!(keys[39], "ask_size_9");
577        assert_eq!(keys[40], "bid_count_0");
578        assert_eq!(keys[41], "bid_count_1");
579    }
580
581    #[rstest]
582    fn test_order_book_depth10_empty_values() {
583        let depth = create_empty_depth10();
584
585        assert_eq!(depth.instrument_id, InstrumentId::from("EMPTY.TEST"));
586        assert_eq!(depth.flags, 0);
587        assert_eq!(depth.sequence, 0);
588        assert_eq!(depth.ts_event, UnixNanos::from(0));
589        assert_eq!(depth.ts_init, UnixNanos::from(0));
590
591        // Verify all orders have zero prices and quantities
592        for bid in &depth.bids {
593            assert_eq!(bid.price, Price::from("0.0"));
594            assert_eq!(bid.size, Quantity::from("0"));
595            assert_eq!(bid.order_id, 0);
596        }
597
598        for ask in &depth.asks {
599            assert_eq!(ask.price, Price::from("0.0"));
600            assert_eq!(ask.size, Quantity::from("0"));
601            assert_eq!(ask.order_id, 0);
602        }
603
604        // Verify all counts are zero
605        for &count in &depth.bid_counts {
606            assert_eq!(count, 0);
607        }
608
609        for &count in &depth.ask_counts {
610            assert_eq!(count, 0);
611        }
612    }
613
614    #[rstest]
615    fn test_order_book_depth10_max_values() {
616        let instrument_id = InstrumentId::from("MAX.TEST");
617        let max_bid = create_test_book_order(OrderSide::Buy, "999999.99", "999999999", u64::MAX);
618        let max_ask = create_test_book_order(OrderSide::Sell, "1000000.00", "999999999", u64::MAX);
619
620        let depth = OrderBookDepth10::new(
621            instrument_id,
622            [max_bid; DEPTH10_LEN],
623            [max_ask; DEPTH10_LEN],
624            [u32::MAX; DEPTH10_LEN],
625            [u32::MAX; DEPTH10_LEN],
626            u8::MAX,
627            u64::MAX,
628            UnixNanos::from(u64::MAX),
629            UnixNanos::from(u64::MAX),
630        );
631
632        assert_eq!(depth.flags, u8::MAX);
633        assert_eq!(depth.sequence, u64::MAX);
634        assert_eq!(depth.ts_event, UnixNanos::from(u64::MAX));
635        assert_eq!(depth.ts_init, UnixNanos::from(u64::MAX));
636
637        for &count in &depth.bid_counts {
638            assert_eq!(count, u32::MAX);
639        }
640
641        for &count in &depth.ask_counts {
642            assert_eq!(count, u32::MAX);
643        }
644    }
645
646    #[rstest]
647    fn test_order_book_depth10_different_instruments() {
648        let instruments = [
649            "EURUSD.SIM",
650            "GBPUSD.SIM",
651            "USDJPY.SIM",
652            "AUDUSD.SIM",
653            "USDCHF.SIM",
654        ];
655
656        for instrument_str in &instruments {
657            let instrument_id = InstrumentId::from(*instrument_str);
658            let bid = create_test_book_order(OrderSide::Buy, "1.0000", "100000", 1);
659            let ask = create_test_book_order(OrderSide::Sell, "1.0001", "100000", 2);
660
661            let depth = OrderBookDepth10::new(
662                instrument_id,
663                [bid; DEPTH10_LEN],
664                [ask; DEPTH10_LEN],
665                [1; DEPTH10_LEN],
666                [1; DEPTH10_LEN],
667                0,
668                1,
669                UnixNanos::from(1_000_000_000),
670                UnixNanos::from(2_000_000_000),
671            );
672
673            assert_eq!(depth.instrument_id, instrument_id);
674            assert!(format!("{depth}").contains(instrument_str));
675        }
676    }
677
678    #[rstest]
679    fn test_order_book_depth10_realistic_forex_spread() {
680        let instrument_id = InstrumentId::from("EURUSD.SIM");
681
682        // Realistic EUR/USD spread with 0.1 pip spread
683        let best_bid = create_test_book_order(OrderSide::Buy, "1.08500", "1000000", 1);
684        let best_ask = create_test_book_order(OrderSide::Sell, "1.08501", "1000000", 2);
685
686        let depth = OrderBookDepth10::new(
687            instrument_id,
688            [best_bid; DEPTH10_LEN],
689            [best_ask; DEPTH10_LEN],
690            [5; DEPTH10_LEN], // Realistic order count
691            [3; DEPTH10_LEN],
692            16,                                         // Realistic flags
693            123_456,                                    // Realistic sequence
694            UnixNanos::from(1_672_531_200_000_000_000), // Jan 1, 2023 timestamp
695            UnixNanos::from(1_672_531_200_000_100_000),
696        );
697
698        assert_eq!(depth.bids[0].price, Price::from("1.08500"));
699        assert_eq!(depth.asks[0].price, Price::from("1.08501"));
700        assert!(depth.bids[0].price < depth.asks[0].price); // Positive spread
701
702        // Verify realistic quantities and counts
703        assert_eq!(depth.bids[0].size, Quantity::from("1000000"));
704        assert_eq!(depth.bid_counts[0], 5);
705        assert_eq!(depth.ask_counts[0], 3);
706    }
707
708    #[rstest]
709    fn test_order_book_depth10_with_stub(stub_depth10: OrderBookDepth10) {
710        let depth = stub_depth10;
711
712        assert_eq!(depth.instrument_id, InstrumentId::from("AAPL.XNAS"));
713        assert_eq!(depth.bids.len(), 10);
714        assert_eq!(depth.asks.len(), 10);
715        assert_eq!(depth.asks[9].price, Price::from("109.0"));
716        assert_eq!(depth.asks[0].price, Price::from("100.0"));
717        assert_eq!(depth.bids[0].price, Price::from("99.0"));
718        assert_eq!(depth.bids[9].price, Price::from("90.0"));
719        assert_eq!(depth.bid_counts.len(), 10);
720        assert_eq!(depth.ask_counts.len(), 10);
721        assert_eq!(depth.bid_counts[0], 1);
722        assert_eq!(depth.ask_counts[0], 1);
723        assert_eq!(depth.flags, 0);
724        assert_eq!(depth.sequence, 0);
725        assert_eq!(depth.ts_event, UnixNanos::from(1));
726        assert_eq!(depth.ts_init, UnixNanos::from(2));
727    }
728
729    #[rstest]
730    fn test_new(stub_depth10: OrderBookDepth10) {
731        let depth = stub_depth10;
732        let instrument_id = InstrumentId::from("AAPL.XNAS");
733        let flags = 0;
734        let sequence = 0;
735        let ts_event = 1;
736        let ts_init = 2;
737
738        assert_eq!(depth.instrument_id, instrument_id);
739        assert_eq!(depth.bids.len(), 10);
740        assert_eq!(depth.asks.len(), 10);
741        assert_eq!(depth.asks[9].price, Price::from("109.0"));
742        assert_eq!(depth.asks[0].price, Price::from("100.0"));
743        assert_eq!(depth.bids[0].price, Price::from("99.0"));
744        assert_eq!(depth.bids[9].price, Price::from("90.0"));
745        assert_eq!(depth.bid_counts.len(), 10);
746        assert_eq!(depth.ask_counts.len(), 10);
747        assert_eq!(depth.bid_counts[0], 1);
748        assert_eq!(depth.ask_counts[0], 1);
749        assert_eq!(depth.flags, flags);
750        assert_eq!(depth.sequence, sequence);
751        assert_eq!(depth.ts_event, ts_event);
752        assert_eq!(depth.ts_init, ts_init);
753    }
754
755    #[rstest]
756    fn test_display(stub_depth10: OrderBookDepth10) {
757        let depth = stub_depth10;
758        assert_eq!(
759            format!("{depth}"),
760            "AAPL.XNAS,flags=0,sequence=0,ts_event=1,ts_init=2".to_string()
761        );
762    }
763}