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