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