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::{
19    collections::HashMap,
20    fmt::{Display, Formatter},
21};
22
23use indexmap::IndexMap;
24use nautilus_core::{serialization::Serializable, UnixNanos};
25use serde::{Deserialize, Serialize};
26
27use super::{order::BookOrder, GetTsInit};
28use crate::identifiers::InstrumentId;
29
30pub const DEPTH10_LEN: usize = 10;
31
32/// Represents a aggregated order book update with a fixed depth of 10 levels per side.
33///
34/// This structure is specifically designed for scenarios where a snapshot of the top 10 bid and
35/// ask levels in an order book is needed. It differs from `OrderBookDelta` or `OrderBookDeltas`
36/// in its fixed-depth nature and is optimized for cases where a full depth representation is not
37/// required or practical.
38///
39/// Note: This type is not compatible with `OrderBookDelta` or `OrderBookDeltas` due to
40/// its specialized structure and limited depth use case.
41#[repr(C)]
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
43#[cfg_attr(
44    feature = "python",
45    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
46)]
47pub struct OrderBookDepth10 {
48    /// The instrument ID for the book.
49    pub instrument_id: InstrumentId,
50    /// The bid orders for the depth update.
51    pub bids: [BookOrder; DEPTH10_LEN],
52    /// The ask orders for the depth update.
53    pub asks: [BookOrder; DEPTH10_LEN],
54    /// The count of bid orders per level for the depth update.
55    pub bid_counts: [u32; DEPTH10_LEN],
56    /// The count of ask orders per level for the depth update.
57    pub ask_counts: [u32; DEPTH10_LEN],
58    /// The record flags bit field, indicating event end and data information.
59    pub flags: u8,
60    /// The message sequence number assigned at the venue.
61    pub sequence: u64,
62    /// UNIX timestamp (nanoseconds) when the book event occurred.
63    pub ts_event: UnixNanos,
64    /// UNIX timestamp (nanoseconds) when the struct was initialized.
65    pub ts_init: UnixNanos,
66}
67
68impl OrderBookDepth10 {
69    /// Creates a new [`OrderBookDepth10`] instance.
70    #[allow(clippy::too_many_arguments)]
71    #[must_use]
72    pub fn new(
73        instrument_id: InstrumentId,
74        bids: [BookOrder; DEPTH10_LEN],
75        asks: [BookOrder; DEPTH10_LEN],
76        bid_counts: [u32; DEPTH10_LEN],
77        ask_counts: [u32; DEPTH10_LEN],
78        flags: u8,
79        sequence: u64,
80        ts_event: UnixNanos,
81        ts_init: UnixNanos,
82    ) -> Self {
83        Self {
84            instrument_id,
85            bids,
86            asks,
87            bid_counts,
88            ask_counts,
89            flags,
90            sequence,
91            ts_event,
92            ts_init,
93        }
94    }
95
96    /// Returns the metadata for the type, for use with serialization formats.
97    #[must_use]
98    pub fn get_metadata(
99        instrument_id: &InstrumentId,
100        price_precision: u8,
101        size_precision: u8,
102    ) -> HashMap<String, String> {
103        let mut metadata = HashMap::new();
104        metadata.insert("instrument_id".to_string(), instrument_id.to_string());
105        metadata.insert("price_precision".to_string(), price_precision.to_string());
106        metadata.insert("size_precision".to_string(), size_precision.to_string());
107        metadata
108    }
109
110    /// Returns the field map for the type, for use with Arrow schemas.
111    #[must_use]
112    pub fn get_fields() -> IndexMap<String, String> {
113        let mut metadata = IndexMap::new();
114        metadata.insert("bid_price_0".to_string(), "Int64".to_string());
115        metadata.insert("bid_price_1".to_string(), "Int64".to_string());
116        metadata.insert("bid_price_2".to_string(), "Int64".to_string());
117        metadata.insert("bid_price_3".to_string(), "Int64".to_string());
118        metadata.insert("bid_price_4".to_string(), "Int64".to_string());
119        metadata.insert("bid_price_5".to_string(), "Int64".to_string());
120        metadata.insert("bid_price_6".to_string(), "Int64".to_string());
121        metadata.insert("bid_price_7".to_string(), "Int64".to_string());
122        metadata.insert("bid_price_8".to_string(), "Int64".to_string());
123        metadata.insert("bid_price_9".to_string(), "Int64".to_string());
124        metadata.insert("ask_price_0".to_string(), "Int64".to_string());
125        metadata.insert("ask_price_1".to_string(), "Int64".to_string());
126        metadata.insert("ask_price_2".to_string(), "Int64".to_string());
127        metadata.insert("ask_price_3".to_string(), "Int64".to_string());
128        metadata.insert("ask_price_4".to_string(), "Int64".to_string());
129        metadata.insert("ask_price_5".to_string(), "Int64".to_string());
130        metadata.insert("ask_price_6".to_string(), "Int64".to_string());
131        metadata.insert("ask_price_7".to_string(), "Int64".to_string());
132        metadata.insert("ask_price_8".to_string(), "Int64".to_string());
133        metadata.insert("ask_price_9".to_string(), "Int64".to_string());
134        metadata.insert("bid_size_0".to_string(), "UInt64".to_string());
135        metadata.insert("bid_size_1".to_string(), "UInt64".to_string());
136        metadata.insert("bid_size_2".to_string(), "UInt64".to_string());
137        metadata.insert("bid_size_3".to_string(), "UInt64".to_string());
138        metadata.insert("bid_size_4".to_string(), "UInt64".to_string());
139        metadata.insert("bid_size_5".to_string(), "UInt64".to_string());
140        metadata.insert("bid_size_6".to_string(), "UInt64".to_string());
141        metadata.insert("bid_size_7".to_string(), "UInt64".to_string());
142        metadata.insert("bid_size_8".to_string(), "UInt64".to_string());
143        metadata.insert("bid_size_9".to_string(), "UInt64".to_string());
144        metadata.insert("ask_size_0".to_string(), "UInt64".to_string());
145        metadata.insert("ask_size_1".to_string(), "UInt64".to_string());
146        metadata.insert("ask_size_2".to_string(), "UInt64".to_string());
147        metadata.insert("ask_size_3".to_string(), "UInt64".to_string());
148        metadata.insert("ask_size_4".to_string(), "UInt64".to_string());
149        metadata.insert("ask_size_5".to_string(), "UInt64".to_string());
150        metadata.insert("ask_size_6".to_string(), "UInt64".to_string());
151        metadata.insert("ask_size_7".to_string(), "UInt64".to_string());
152        metadata.insert("ask_size_8".to_string(), "UInt64".to_string());
153        metadata.insert("ask_size_9".to_string(), "UInt64".to_string());
154        metadata.insert("bid_count_0".to_string(), "UInt32".to_string());
155        metadata.insert("bid_count_1".to_string(), "UInt32".to_string());
156        metadata.insert("bid_count_2".to_string(), "UInt32".to_string());
157        metadata.insert("bid_count_3".to_string(), "UInt32".to_string());
158        metadata.insert("bid_count_4".to_string(), "UInt32".to_string());
159        metadata.insert("bid_count_5".to_string(), "UInt32".to_string());
160        metadata.insert("bid_count_6".to_string(), "UInt32".to_string());
161        metadata.insert("bid_count_7".to_string(), "UInt32".to_string());
162        metadata.insert("bid_count_8".to_string(), "UInt32".to_string());
163        metadata.insert("bid_count_9".to_string(), "UInt32".to_string());
164        metadata.insert("ask_count_0".to_string(), "UInt32".to_string());
165        metadata.insert("ask_count_1".to_string(), "UInt32".to_string());
166        metadata.insert("ask_count_2".to_string(), "UInt32".to_string());
167        metadata.insert("ask_count_3".to_string(), "UInt32".to_string());
168        metadata.insert("ask_count_4".to_string(), "UInt32".to_string());
169        metadata.insert("ask_count_5".to_string(), "UInt32".to_string());
170        metadata.insert("ask_count_6".to_string(), "UInt32".to_string());
171        metadata.insert("ask_count_7".to_string(), "UInt32".to_string());
172        metadata.insert("ask_count_8".to_string(), "UInt32".to_string());
173        metadata.insert("ask_count_9".to_string(), "UInt32".to_string());
174        metadata.insert("flags".to_string(), "UInt8".to_string());
175        metadata.insert("sequence".to_string(), "UInt64".to_string());
176        metadata.insert("ts_event".to_string(), "UInt64".to_string());
177        metadata.insert("ts_init".to_string(), "UInt64".to_string());
178        metadata
179    }
180}
181
182// TODO: Exact format for Debug and Display TBD
183impl Display for OrderBookDepth10 {
184    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
185        write!(
186            f,
187            "{},flags={},sequence={},ts_event={},ts_init={}",
188            self.instrument_id, self.flags, self.sequence, self.ts_event, self.ts_init
189        )
190    }
191}
192
193impl Serializable for OrderBookDepth10 {}
194
195impl GetTsInit for OrderBookDepth10 {
196    fn ts_init(&self) -> UnixNanos {
197        self.ts_init
198    }
199}
200
201////////////////////////////////////////////////////////////////////////////////
202// Tests
203////////////////////////////////////////////////////////////////////////////////
204#[cfg(test)]
205mod tests {
206    use rstest::rstest;
207
208    use super::*;
209    use crate::data::stubs::*;
210
211    #[rstest]
212    fn test_new(stub_depth10: OrderBookDepth10) {
213        let depth = stub_depth10;
214        let instrument_id = InstrumentId::from("AAPL.XNAS");
215        let flags = 0;
216        let sequence = 0;
217        let ts_event = 1;
218        let ts_init = 2;
219
220        assert_eq!(depth.instrument_id, instrument_id);
221        assert_eq!(depth.bids.len(), 10);
222        assert_eq!(depth.asks.len(), 10);
223        assert_eq!(depth.asks[9].price.as_f64(), 109.0);
224        assert_eq!(depth.asks[0].price.as_f64(), 100.0);
225        assert_eq!(depth.bids[0].price.as_f64(), 99.0);
226        assert_eq!(depth.bids[9].price.as_f64(), 90.0);
227        assert_eq!(depth.bid_counts.len(), 10);
228        assert_eq!(depth.ask_counts.len(), 10);
229        assert_eq!(depth.bid_counts[0], 1);
230        assert_eq!(depth.ask_counts[0], 1);
231        assert_eq!(depth.flags, flags);
232        assert_eq!(depth.sequence, sequence);
233        assert_eq!(depth.ts_event, ts_event);
234        assert_eq!(depth.ts_init, ts_init);
235    }
236
237    // TODO: Exact format for Debug and Display TBD
238    #[rstest]
239    fn test_display(stub_depth10: OrderBookDepth10) {
240        let depth = stub_depth10;
241        assert_eq!(
242            format!("{depth}"),
243            "AAPL.XNAS,flags=0,sequence=0,ts_event=1,ts_init=2".to_string()
244        );
245    }
246}