1use std::{collections::HashMap, fmt::Display, hash::Hash};
19
20use indexmap::IndexMap;
21use nautilus_core::{UnixNanos, correctness::FAILED, serialization::Serializable};
22use serde::{Deserialize, Serialize};
23
24use super::{
25 GetTsInit,
26 order::{BookOrder, NULL_ORDER},
27};
28use crate::{
29 enums::{BookAction, RecordFlag},
30 identifiers::InstrumentId,
31 types::{fixed::FIXED_SIZE_BINARY, quantity::check_positive_quantity},
32};
33
34#[repr(C)]
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
37#[serde(tag = "type")]
38#[cfg_attr(
39 feature = "python",
40 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
41)]
42pub struct OrderBookDelta {
43 pub instrument_id: InstrumentId,
45 pub action: BookAction,
47 pub order: BookOrder,
49 pub flags: u8,
51 pub sequence: u64,
53 pub ts_event: UnixNanos,
55 pub ts_init: UnixNanos,
57}
58
59impl OrderBookDelta {
60 pub fn new_checked(
71 instrument_id: InstrumentId,
72 action: BookAction,
73 order: BookOrder,
74 flags: u8,
75 sequence: u64,
76 ts_event: UnixNanos,
77 ts_init: UnixNanos,
78 ) -> anyhow::Result<Self> {
79 if matches!(action, BookAction::Add | BookAction::Update) {
80 check_positive_quantity(order.size, stringify!(order.size))?;
81 }
82
83 Ok(Self {
84 instrument_id,
85 action,
86 order,
87 flags,
88 sequence,
89 ts_event,
90 ts_init,
91 })
92 }
93
94 #[must_use]
101 pub fn new(
102 instrument_id: InstrumentId,
103 action: BookAction,
104 order: BookOrder,
105 flags: u8,
106 sequence: u64,
107 ts_event: UnixNanos,
108 ts_init: UnixNanos,
109 ) -> Self {
110 Self::new_checked(
111 instrument_id,
112 action,
113 order,
114 flags,
115 sequence,
116 ts_event,
117 ts_init,
118 )
119 .expect(FAILED)
120 }
121
122 #[must_use]
124 pub fn clear(
125 instrument_id: InstrumentId,
126 sequence: u64,
127 ts_event: UnixNanos,
128 ts_init: UnixNanos,
129 ) -> Self {
130 Self {
131 instrument_id,
132 action: BookAction::Clear,
133 order: NULL_ORDER,
134 flags: RecordFlag::F_SNAPSHOT as u8,
135 sequence,
136 ts_event,
137 ts_init,
138 }
139 }
140
141 #[must_use]
143 pub fn get_metadata(
144 instrument_id: &InstrumentId,
145 price_precision: u8,
146 size_precision: u8,
147 ) -> HashMap<String, String> {
148 let mut metadata = HashMap::new();
149 metadata.insert("instrument_id".to_string(), instrument_id.to_string());
150 metadata.insert("price_precision".to_string(), price_precision.to_string());
151 metadata.insert("size_precision".to_string(), size_precision.to_string());
152 metadata
153 }
154
155 #[must_use]
157 pub fn get_fields() -> IndexMap<String, String> {
158 let mut metadata = IndexMap::new();
159 metadata.insert("action".to_string(), "UInt8".to_string());
160 metadata.insert("side".to_string(), "UInt8".to_string());
161 metadata.insert("price".to_string(), FIXED_SIZE_BINARY.to_string());
162 metadata.insert("size".to_string(), FIXED_SIZE_BINARY.to_string());
163 metadata.insert("order_id".to_string(), "UInt64".to_string());
164 metadata.insert("flags".to_string(), "UInt8".to_string());
165 metadata.insert("sequence".to_string(), "UInt64".to_string());
166 metadata.insert("ts_event".to_string(), "UInt64".to_string());
167 metadata.insert("ts_init".to_string(), "UInt64".to_string());
168 metadata
169 }
170}
171
172impl Display for OrderBookDelta {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 write!(
175 f,
176 "{},{},{},{},{},{},{}",
177 self.instrument_id,
178 self.action,
179 self.order,
180 self.flags,
181 self.sequence,
182 self.ts_event,
183 self.ts_init
184 )
185 }
186}
187
188impl Serializable for OrderBookDelta {}
189
190impl GetTsInit for OrderBookDelta {
191 fn ts_init(&self) -> UnixNanos {
192 self.ts_init
193 }
194}
195
196#[cfg(test)]
200mod tests {
201 use nautilus_core::{UnixNanos, serialization::Serializable};
202 use rstest::rstest;
203
204 use crate::{
205 data::{BookOrder, OrderBookDelta, stubs::*},
206 enums::{BookAction, OrderSide},
207 identifiers::InstrumentId,
208 types::{Price, Quantity},
209 };
210
211 #[rstest]
212 fn test_order_book_delta_new_with_zero_size_panics() {
213 let instrument_id = InstrumentId::from("AAPL.XNAS");
214 let action = BookAction::Add;
215 let price = Price::from("100.00");
216 let zero_size = Quantity::from(0);
217 let side = OrderSide::Buy;
218 let order_id = 123_456;
219 let flags = 0;
220 let sequence = 1;
221 let ts_event = UnixNanos::from(0);
222 let ts_init = UnixNanos::from(1);
223
224 let order = BookOrder::new(side, price, zero_size, order_id);
225
226 let result = std::panic::catch_unwind(|| {
227 let _ = OrderBookDelta::new(
228 instrument_id,
229 action,
230 order,
231 flags,
232 sequence,
233 ts_event,
234 ts_init,
235 );
236 });
237 assert!(result.is_err());
238 }
239
240 #[rstest]
241 fn test_order_book_delta_new_checked_with_zero_size_error() {
242 let instrument_id = InstrumentId::from("AAPL.XNAS");
243 let action = BookAction::Add;
244 let price = Price::from("100.00");
245 let zero_size = Quantity::from(0);
246 let side = OrderSide::Buy;
247 let order_id = 123_456;
248 let flags = 0;
249 let sequence = 1;
250 let ts_event = UnixNanos::from(0);
251 let ts_init = UnixNanos::from(1);
252
253 let order = BookOrder::new(side, price, zero_size, order_id);
254
255 let result = OrderBookDelta::new_checked(
256 instrument_id,
257 action,
258 order,
259 flags,
260 sequence,
261 ts_event,
262 ts_init,
263 );
264
265 assert!(result.is_err());
266 }
267
268 #[rstest]
269 fn test_new() {
270 let instrument_id = InstrumentId::from("AAPL.XNAS");
271 let action = BookAction::Add;
272 let price = Price::from("100.00");
273 let size = Quantity::from("10");
274 let side = OrderSide::Buy;
275 let order_id = 123_456;
276 let flags = 0;
277 let sequence = 1;
278 let ts_event = 1;
279 let ts_init = 2;
280
281 let order = BookOrder::new(side, price, size, order_id);
282
283 let delta = OrderBookDelta::new(
284 instrument_id,
285 action,
286 order,
287 flags,
288 sequence,
289 ts_event.into(),
290 ts_init.into(),
291 );
292
293 assert_eq!(delta.instrument_id, instrument_id);
294 assert_eq!(delta.action, action);
295 assert_eq!(delta.order.price, price);
296 assert_eq!(delta.order.size, size);
297 assert_eq!(delta.order.side, side);
298 assert_eq!(delta.order.order_id, order_id);
299 assert_eq!(delta.flags, flags);
300 assert_eq!(delta.sequence, sequence);
301 assert_eq!(delta.ts_event, ts_event);
302 assert_eq!(delta.ts_init, ts_init);
303 }
304
305 #[rstest]
306 fn test_clear() {
307 let instrument_id = InstrumentId::from("AAPL.XNAS");
308 let sequence = 1;
309 let ts_event = 2;
310 let ts_init = 3;
311
312 let delta = OrderBookDelta::clear(instrument_id, sequence, ts_event.into(), ts_init.into());
313
314 assert_eq!(delta.instrument_id, instrument_id);
315 assert_eq!(delta.action, BookAction::Clear);
316 assert!(delta.order.price.is_zero());
317 assert!(delta.order.size.is_zero());
318 assert_eq!(delta.order.side, OrderSide::NoOrderSide);
319 assert_eq!(delta.order.order_id, 0);
320 assert_eq!(delta.flags, 32);
321 assert_eq!(delta.sequence, sequence);
322 assert_eq!(delta.ts_event, ts_event);
323 assert_eq!(delta.ts_init, ts_init);
324 }
325
326 #[rstest]
327 fn test_display(stub_delta: OrderBookDelta) {
328 let delta = stub_delta;
329 assert_eq!(
330 format!("{delta}"),
331 "AAPL.XNAS,ADD,BUY,100.00,10,123456,0,1,1,2".to_string()
332 );
333 }
334
335 #[rstest]
336 fn test_json_serialization(stub_delta: OrderBookDelta) {
337 let delta = stub_delta;
338 let serialized = delta.as_json_bytes().unwrap();
339 let deserialized = OrderBookDelta::from_json_bytes(serialized.as_ref()).unwrap();
340 assert_eq!(deserialized, delta);
341 }
342
343 #[rstest]
344 fn test_msgpack_serialization(stub_delta: OrderBookDelta) {
345 let delta = stub_delta;
346 let serialized = delta.as_msgpack_bytes().unwrap();
347 let deserialized = OrderBookDelta::from_msgpack_bytes(serialized.as_ref()).unwrap();
348 assert_eq!(deserialized, delta);
349 }
350}