nautilus_model/data/
deltas.rs

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