nautilus_model/data/
depth.rs

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