nautilus_model/python/data/
delta.rs1use std::{
17 collections::{HashMap, hash_map::DefaultHasher},
18 hash::{Hash, Hasher},
19 str::FromStr,
20};
21
22use nautilus_core::{
23 python::{
24 IntoPyObjectNautilusExt,
25 serialization::{from_dict_pyo3, to_dict_pyo3},
26 to_pyvalue_err,
27 },
28 serialization::{
29 Serializable,
30 msgpack::{FromMsgPack, ToMsgPack},
31 },
32};
33use pyo3::{IntoPyObjectExt, basic::CompareOp, prelude::*, types::PyDict};
34
35use super::data_to_pycapsule;
36use crate::{
37 data::{BookOrder, Data, NULL_ORDER, OrderBookDelta, order::OrderId},
38 enums::{BookAction, FromU8, OrderSide},
39 identifiers::InstrumentId,
40 python::common::PY_MODULE_MODEL,
41 types::{
42 price::{Price, PriceRaw},
43 quantity::{Quantity, QuantityRaw},
44 },
45};
46
47impl OrderBookDelta {
48 pub fn from_pyobject(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
58 if let Ok(delta) = obj.cast::<Self>() {
60 return Ok(*delta.borrow());
61 }
62
63 let instrument_id_obj: Bound<'_, PyAny> = obj.getattr("instrument_id")?.extract()?;
64 let instrument_id_str: String = instrument_id_obj.getattr("value")?.extract()?;
65 let instrument_id = InstrumentId::from_str(instrument_id_str.as_str())
66 .map_err(to_pyvalue_err)
67 .unwrap();
68
69 let action_obj: Bound<'_, PyAny> = obj.getattr("action")?.extract()?;
70 let action_u8 = action_obj.getattr("value")?.extract()?;
71 let action = BookAction::from_u8(action_u8).unwrap();
72
73 let flags: u8 = obj.getattr("flags")?.extract()?;
74 let sequence: u64 = obj.getattr("sequence")?.extract()?;
75 let ts_event: u64 = obj.getattr("ts_event")?.extract()?;
76 let ts_init: u64 = obj.getattr("ts_init")?.extract()?;
77
78 let order_pyobject = obj.getattr("order")?;
79 let order: BookOrder = if order_pyobject.is_none() {
80 NULL_ORDER
81 } else {
82 let side_obj: Bound<'_, PyAny> = order_pyobject.getattr("side")?.extract()?;
83 let side_u8 = side_obj.getattr("value")?.extract()?;
84 let side = OrderSide::from_u8(side_u8).unwrap();
85
86 let price_py: Bound<'_, PyAny> = order_pyobject.getattr("price")?;
87 let price_raw: PriceRaw = price_py.getattr("raw")?.extract()?;
88 let price_prec: u8 = price_py.getattr("precision")?.extract()?;
89 let price = Price::from_raw(price_raw, price_prec);
90
91 let size_py: Bound<'_, PyAny> = order_pyobject.getattr("size")?;
92 let size_raw: QuantityRaw = size_py.getattr("raw")?.extract()?;
93 let size_prec: u8 = size_py.getattr("precision")?.extract()?;
94 let size = Quantity::from_raw(size_raw, size_prec);
95
96 let order_id: OrderId = order_pyobject.getattr("order_id")?.extract()?;
97 BookOrder {
98 side,
99 price,
100 size,
101 order_id,
102 }
103 };
104
105 Ok(Self::new(
106 instrument_id,
107 action,
108 order,
109 flags,
110 sequence,
111 ts_event.into(),
112 ts_init.into(),
113 ))
114 }
115}
116
117#[pymethods]
118#[pyo3_stub_gen::derive::gen_stub_pymethods]
119impl OrderBookDelta {
120 #[new]
122 fn py_new(
123 instrument_id: InstrumentId,
124 action: BookAction,
125 order: BookOrder,
126 flags: u8,
127 sequence: u64,
128 ts_event: u64,
129 ts_init: u64,
130 ) -> PyResult<Self> {
131 Self::new_checked(
132 instrument_id,
133 action,
134 order,
135 flags,
136 sequence,
137 ts_event.into(),
138 ts_init.into(),
139 )
140 .map_err(to_pyvalue_err)
141 }
142
143 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
144 match op {
145 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
146 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
147 _ => py.NotImplemented(),
148 }
149 }
150
151 fn __hash__(&self) -> isize {
152 let mut h = DefaultHasher::new();
153 self.hash(&mut h);
154 h.finish() as isize
155 }
156
157 fn __repr__(&self) -> String {
158 format!("{self:?}")
159 }
160
161 fn __str__(&self) -> String {
162 self.to_string()
163 }
164
165 #[getter]
166 #[pyo3(name = "instrument_id")]
167 fn py_instrument_id(&self) -> InstrumentId {
168 self.instrument_id
169 }
170
171 #[getter]
172 #[pyo3(name = "action")]
173 fn py_action(&self) -> BookAction {
174 self.action
175 }
176
177 #[getter]
178 #[pyo3(name = "order")]
179 fn py_order(&self) -> BookOrder {
180 self.order
181 }
182
183 #[getter]
184 #[pyo3(name = "flags")]
185 fn py_flags(&self) -> u8 {
186 self.flags
187 }
188
189 #[getter]
190 #[pyo3(name = "sequence")]
191 fn py_sequence(&self) -> u64 {
192 self.sequence
193 }
194
195 #[getter]
196 #[pyo3(name = "ts_event")]
197 fn py_ts_event(&self) -> u64 {
198 self.ts_event.as_u64()
199 }
200
201 #[getter]
202 #[pyo3(name = "ts_init")]
203 fn py_ts_init(&self) -> u64 {
204 self.ts_init.as_u64()
205 }
206
207 #[staticmethod]
208 #[pyo3(name = "fully_qualified_name")]
209 fn py_fully_qualified_name() -> String {
210 format!("{}:{}", PY_MODULE_MODEL, stringify!(OrderBookDelta))
211 }
212
213 #[staticmethod]
215 #[pyo3(name = "get_metadata")]
216 fn py_get_metadata(
217 instrument_id: &InstrumentId,
218 price_precision: u8,
219 size_precision: u8,
220 ) -> HashMap<String, String> {
221 Self::get_metadata(instrument_id, price_precision, size_precision)
222 }
223
224 #[staticmethod]
226 #[pyo3(name = "get_fields")]
227 fn py_get_fields(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
228 let py_dict = PyDict::new(py);
229 for (k, v) in Self::get_fields() {
230 py_dict.set_item(k, v)?;
231 }
232
233 Ok(py_dict)
234 }
235
236 #[staticmethod]
238 #[pyo3(name = "from_dict")]
239 fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
240 from_dict_pyo3(py, values)
241 }
242
243 #[pyo3(name = "as_pycapsule")]
259 fn py_as_pycapsule(&self, py: Python<'_>) -> Py<PyAny> {
260 data_to_pycapsule(py, Data::Delta(*self))
261 }
262
263 #[pyo3(name = "to_dict")]
265 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
266 to_dict_pyo3(py, self)
267 }
268
269 #[pyo3(name = "to_json_bytes")]
271 fn py_to_json_bytes(&self, py: Python<'_>) -> Py<PyAny> {
272 self.to_json_bytes().unwrap().into_py_any_unwrap(py)
273 }
274
275 #[pyo3(name = "to_msgpack_bytes")]
277 fn py_to_msgpack_bytes(&self, py: Python<'_>) -> Py<PyAny> {
278 self.to_msgpack_bytes().unwrap().into_py_any_unwrap(py)
279 }
280
281 fn __reduce__(&self, py: Python) -> PyResult<Py<PyAny>> {
282 let from_dict = py.get_type::<Self>().getattr("from_dict")?;
283 let dict = self.py_to_dict(py)?;
284 (from_dict, (dict,)).into_py_any(py)
285 }
286}
287
288#[pymethods]
289impl OrderBookDelta {
290 #[staticmethod]
291 #[pyo3(name = "from_json")]
292 fn py_from_json(data: &[u8]) -> PyResult<Self> {
293 Self::from_json_bytes(data).map_err(to_pyvalue_err)
294 }
295
296 #[staticmethod]
297 #[pyo3(name = "from_msgpack")]
298 fn py_from_msgpack(data: &[u8]) -> PyResult<Self> {
299 Self::from_msgpack_bytes(data).map_err(to_pyvalue_err)
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use rstest::rstest;
306
307 use super::*;
308 use crate::data::stubs::*;
309
310 #[rstest]
311 fn test_order_book_delta_py_new_with_zero_size_returns_error() {
312 Python::initialize();
313 Python::attach(|_py| {
314 let instrument_id = InstrumentId::from("AAPL.XNAS");
315 let action = BookAction::Add;
316 let zero_size = Quantity::from(0);
317 let price = Price::from("100.00");
318 let side = OrderSide::Buy;
319 let order_id = 123_456;
320 let flags = 0;
321 let sequence = 1;
322 let ts_event = 1;
323 let ts_init = 2;
324
325 let order = BookOrder::new(side, price, zero_size, order_id);
326
327 let result = OrderBookDelta::py_new(
328 instrument_id,
329 action,
330 order,
331 flags,
332 sequence,
333 ts_event,
334 ts_init,
335 );
336 assert!(result.is_err());
337 });
338 }
339
340 #[rstest]
341 fn test_to_dict(stub_delta: OrderBookDelta) {
342 let delta = stub_delta;
343
344 Python::initialize();
345 Python::attach(|py| {
346 let dict_string = delta.py_to_dict(py).unwrap().to_string();
347 let expected_string = "{'type': 'OrderBookDelta', 'instrument_id': 'AAPL.XNAS', 'action': 'ADD', 'order': {'side': 'BUY', 'price': '100.00', 'size': '10', 'order_id': 123456}, 'flags': 0, 'sequence': 1, 'ts_event': 1, 'ts_init': 2}";
348 assert_eq!(dict_string, expected_string);
349 });
350 }
351
352 #[rstest]
353 fn test_from_dict(stub_delta: OrderBookDelta) {
354 let delta = stub_delta;
355
356 Python::initialize();
357 Python::attach(|py| {
358 let dict = delta.py_to_dict(py).unwrap();
359 let parsed = OrderBookDelta::py_from_dict(py, dict).unwrap();
360 assert_eq!(parsed, delta);
361 });
362 }
363
364 #[rstest]
365 fn test_from_pyobject(stub_delta: OrderBookDelta) {
366 let delta = stub_delta;
367
368 Python::initialize();
369 Python::attach(|py| {
370 let delta_pyobject = delta.into_py_any_unwrap(py);
371 let parsed_delta = OrderBookDelta::from_pyobject(delta_pyobject.bind(py)).unwrap();
372 assert_eq!(parsed_delta, delta);
373 });
374 }
375}