nautilus_model/data/
deltas.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 `OrderBookDeltas` container type to carry a bulk of `OrderBookDelta` records.
17
18use std::{
19    fmt::{Display, Formatter},
20    hash::{Hash, Hasher},
21    ops::{Deref, DerefMut},
22};
23
24use nautilus_core::{
25    correctness::{check_predicate_true, FAILED},
26    UnixNanos,
27};
28use serde::{Deserialize, Serialize};
29
30use super::{GetTsInit, OrderBookDelta};
31use crate::identifiers::InstrumentId;
32
33/// Represents a grouped batch of `OrderBookDelta` updates for an `OrderBook`.
34///
35/// This type cannot be `repr(C)` due to the `deltas` vec.
36#[derive(Clone, Debug, Serialize, Deserialize)]
37#[cfg_attr(
38    feature = "python",
39    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
40)]
41pub struct OrderBookDeltas {
42    /// The instrument ID for the book.
43    pub instrument_id: InstrumentId,
44    /// The order book deltas.
45    pub deltas: Vec<OrderBookDelta>,
46    /// The record flags bit field, indicating event end and data information.
47    pub flags: u8,
48    /// The message sequence number assigned at the venue.
49    pub sequence: u64,
50    /// UNIX timestamp (nanoseconds) when the book event occurred.
51    pub ts_event: UnixNanos,
52    /// UNIX timestamp (nanoseconds) when the struct was initialized.
53    pub ts_init: UnixNanos,
54}
55
56impl OrderBookDeltas {
57    /// Creates a new [`OrderBookDeltas`] instance.
58    #[allow(clippy::too_many_arguments)]
59    #[must_use]
60    pub fn new(instrument_id: InstrumentId, deltas: Vec<OrderBookDelta>) -> Self {
61        Self::new_checked(instrument_id, deltas).expect(FAILED)
62    }
63
64    /// Creates a new [`OrderBookDeltas`] instance with correctness checking.
65    ///
66    /// # Notes
67    ///
68    /// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
69    #[allow(clippy::too_many_arguments)]
70    pub fn new_checked(
71        instrument_id: InstrumentId,
72        deltas: Vec<OrderBookDelta>,
73    ) -> anyhow::Result<Self> {
74        check_predicate_true(!deltas.is_empty(), "`deltas` cannot be empty")?;
75        // SAFETY: We asserted `deltas` is not empty
76        let last = deltas.last().unwrap();
77        let flags = last.flags;
78        let sequence = last.sequence;
79        let ts_event = last.ts_event;
80        let ts_init = last.ts_init;
81        Ok(Self {
82            instrument_id,
83            deltas,
84            flags,
85            sequence,
86            ts_event,
87            ts_init,
88        })
89    }
90}
91
92impl PartialEq<Self> for OrderBookDeltas {
93    fn eq(&self, other: &Self) -> bool {
94        self.instrument_id == other.instrument_id && self.sequence == other.sequence
95    }
96}
97
98impl Eq for OrderBookDeltas {}
99
100impl Hash for OrderBookDeltas {
101    fn hash<H: Hasher>(&self, state: &mut H) {
102        self.instrument_id.hash(state);
103        self.sequence.hash(state);
104    }
105}
106
107// TODO: Implement
108// impl Serializable for OrderBookDeltas {}
109
110// TODO: Exact format for Debug and Display TBD
111impl Display for OrderBookDeltas {
112    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113        write!(
114            f,
115            "{},len={},flags={},sequence={},ts_event={},ts_init={}",
116            self.instrument_id,
117            self.deltas.len(),
118            self.flags,
119            self.sequence,
120            self.ts_event,
121            self.ts_init
122        )
123    }
124}
125
126impl GetTsInit for OrderBookDeltas {
127    fn ts_init(&self) -> UnixNanos {
128        self.ts_init
129    }
130}
131
132/// C compatible Foreign Function Interface (FFI) for an underlying [`OrderBookDeltas`].
133///
134/// This struct wraps `OrderBookDeltas` in a way that makes it compatible with C function
135/// calls, enabling interaction with `OrderBookDeltas` in a C environment.
136///
137/// It implements the `Deref` trait, allowing instances of `OrderBookDeltas_API` to be
138/// dereferenced to `OrderBookDeltas`, providing access to `OrderBookDeltas`'s methods without
139/// having to manually access the underlying `OrderBookDeltas` instance.
140#[repr(C)]
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142#[allow(non_camel_case_types)]
143pub struct OrderBookDeltas_API(Box<OrderBookDeltas>);
144
145// TODO: This wrapper will go along with Cython
146impl OrderBookDeltas_API {
147    #[must_use]
148    pub fn new(deltas: OrderBookDeltas) -> Self {
149        Self(Box::new(deltas))
150    }
151
152    /// Consumes the wrapper and returns the inner `OrderBookDeltas`.
153    #[must_use]
154    pub fn into_inner(self) -> OrderBookDeltas {
155        *self.0
156    }
157}
158
159impl Deref for OrderBookDeltas_API {
160    type Target = OrderBookDeltas;
161
162    fn deref(&self) -> &Self::Target {
163        &self.0
164    }
165}
166
167impl DerefMut for OrderBookDeltas_API {
168    fn deref_mut(&mut self) -> &mut Self::Target {
169        &mut self.0
170    }
171}
172
173////////////////////////////////////////////////////////////////////////////////
174// Tests
175////////////////////////////////////////////////////////////////////////////////
176#[cfg(test)]
177mod tests {
178    use rstest::rstest;
179
180    use super::*;
181    use crate::{
182        data::{order::BookOrder, stubs::stub_deltas},
183        enums::{BookAction, OrderSide},
184        types::{Price, Quantity},
185    };
186
187    #[rstest]
188    fn test_new() {
189        let instrument_id = InstrumentId::from("AAPL.XNAS");
190        let flags = 32; // Snapshot flag
191        let sequence = 0;
192        let ts_event = 1;
193        let ts_init = 2;
194
195        let delta0 =
196            OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
197        let delta1 = OrderBookDelta::new(
198            instrument_id,
199            BookAction::Add,
200            BookOrder::new(
201                OrderSide::Sell,
202                Price::from("102.00"),
203                Quantity::from("300"),
204                1,
205            ),
206            flags,
207            sequence,
208            ts_event.into(),
209            ts_init.into(),
210        );
211        let delta2 = OrderBookDelta::new(
212            instrument_id,
213            BookAction::Add,
214            BookOrder::new(
215                OrderSide::Sell,
216                Price::from("101.00"),
217                Quantity::from("200"),
218                2,
219            ),
220            flags,
221            sequence,
222            ts_event.into(),
223            ts_init.into(),
224        );
225        let delta3 = OrderBookDelta::new(
226            instrument_id,
227            BookAction::Add,
228            BookOrder::new(
229                OrderSide::Sell,
230                Price::from("100.00"),
231                Quantity::from("100"),
232                3,
233            ),
234            flags,
235            sequence,
236            ts_event.into(),
237            ts_init.into(),
238        );
239        let delta4 = OrderBookDelta::new(
240            instrument_id,
241            BookAction::Add,
242            BookOrder::new(
243                OrderSide::Buy,
244                Price::from("99.00"),
245                Quantity::from("100"),
246                4,
247            ),
248            flags,
249            sequence,
250            ts_event.into(),
251            ts_init.into(),
252        );
253        let delta5 = OrderBookDelta::new(
254            instrument_id,
255            BookAction::Add,
256            BookOrder::new(
257                OrderSide::Buy,
258                Price::from("98.00"),
259                Quantity::from("200"),
260                5,
261            ),
262            flags,
263            sequence,
264            ts_event.into(),
265            ts_init.into(),
266        );
267        let delta6 = OrderBookDelta::new(
268            instrument_id,
269            BookAction::Add,
270            BookOrder::new(
271                OrderSide::Buy,
272                Price::from("97.00"),
273                Quantity::from("300"),
274                6,
275            ),
276            flags,
277            sequence,
278            ts_event.into(),
279            ts_init.into(),
280        );
281
282        let deltas = OrderBookDeltas::new(
283            instrument_id,
284            vec![delta0, delta1, delta2, delta3, delta4, delta5, delta6],
285        );
286
287        assert_eq!(deltas.instrument_id, instrument_id);
288        assert_eq!(deltas.deltas.len(), 7);
289        assert_eq!(deltas.flags, flags);
290        assert_eq!(deltas.sequence, sequence);
291        assert_eq!(deltas.ts_event, ts_event);
292        assert_eq!(deltas.ts_init, ts_init);
293    }
294
295    // TODO: Exact format for Debug and Display TBD
296    #[rstest]
297    fn test_display(stub_deltas: OrderBookDeltas) {
298        let deltas = stub_deltas;
299        assert_eq!(
300            format!("{deltas}"),
301            "AAPL.XNAS,len=7,flags=32,sequence=0,ts_event=1,ts_init=2".to_string()
302        );
303    }
304}