nautilus_model/python/data/
depth.rs1use std::{
17 collections::{HashMap, hash_map::DefaultHasher},
18 hash::{Hash, Hasher},
19};
20
21use nautilus_core::{
22 python::{
23 IntoPyObjectNautilusExt,
24 serialization::{from_dict_pyo3, to_dict_pyo3},
25 to_pyvalue_err,
26 },
27 serialization::{
28 Serializable,
29 msgpack::{FromMsgPack, ToMsgPack},
30 },
31};
32use pyo3::{IntoPyObjectExt, prelude::*, pyclass::CompareOp, types::PyDict};
33
34use crate::{
35 data::{
36 depth::{DEPTH10_LEN, OrderBookDepth},
37 order::BookOrder,
38 },
39 enums::OrderSide,
40 identifiers::InstrumentId,
41 python::common::PY_MODULE_MODEL,
42 types::{Price, Quantity},
43};
44
45#[pymethods]
46#[pyo3_stub_gen::derive::gen_stub_pymethods]
47impl OrderBookDepth {
48 #[expect(clippy::too_many_arguments)]
56 #[new]
57 fn py_new(
58 instrument_id: InstrumentId,
59 bids: Vec<BookOrder>,
60 asks: Vec<BookOrder>,
61 bid_counts: Vec<u32>,
62 ask_counts: Vec<u32>,
63 flags: u8,
64 sequence: u64,
65 ts_event: u64,
66 ts_init: u64,
67 ) -> PyResult<Self> {
68 if bids.len() != bid_counts.len() {
69 return Err(to_pyvalue_err(format!(
70 "bid order and count lengths must match: {} orders and {} counts",
71 bids.len(),
72 bid_counts.len(),
73 )));
74 }
75
76 if asks.len() != ask_counts.len() {
77 return Err(to_pyvalue_err(format!(
78 "ask order and count lengths must match: {} orders and {} counts",
79 asks.len(),
80 ask_counts.len(),
81 )));
82 }
83
84 Self::new_checked(
85 instrument_id,
86 bids,
87 asks,
88 bid_counts,
89 ask_counts,
90 flags,
91 sequence,
92 ts_event.into(),
93 ts_init.into(),
94 )
95 .map_err(to_pyvalue_err)
96 }
97
98 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
99 match op {
100 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
101 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
102 _ => py.NotImplemented(),
103 }
104 }
105
106 fn __hash__(&self) -> isize {
107 let mut h = DefaultHasher::new();
108 self.hash(&mut h);
109 h.finish() as isize
110 }
111
112 fn __repr__(&self) -> String {
113 format!("{self:?}")
114 }
115
116 fn __str__(&self) -> String {
117 self.to_string()
118 }
119
120 #[getter]
121 #[pyo3(name = "instrument_id")]
122 fn py_instrument_id(&self) -> InstrumentId {
123 self.instrument_id
124 }
125
126 #[getter]
127 #[pyo3(name = "bids")]
128 fn py_bids(&self) -> Vec<BookOrder> {
129 self.bids.to_vec()
130 }
131
132 #[getter]
133 #[pyo3(name = "asks")]
134 fn py_asks(&self) -> Vec<BookOrder> {
135 self.asks.to_vec()
136 }
137
138 #[getter]
139 #[pyo3(name = "bid_counts")]
140 fn py_bid_counts(&self) -> Vec<u32> {
141 self.bid_counts.to_vec()
142 }
143
144 #[getter]
145 #[pyo3(name = "ask_counts")]
146 fn py_ask_counts(&self) -> Vec<u32> {
147 self.ask_counts.to_vec()
148 }
149
150 #[getter]
151 #[pyo3(name = "flags")]
152 fn py_flags(&self) -> u8 {
153 self.flags
154 }
155
156 #[getter]
157 #[pyo3(name = "sequence")]
158 fn py_sequence(&self) -> u64 {
159 self.sequence
160 }
161
162 #[getter]
163 #[pyo3(name = "ts_event")]
164 fn py_ts_event(&self) -> u64 {
165 self.ts_event.as_u64()
166 }
167
168 #[getter]
169 #[pyo3(name = "ts_init")]
170 fn py_ts_init(&self) -> u64 {
171 self.ts_init.as_u64()
172 }
173
174 #[staticmethod]
175 #[pyo3(name = "fully_qualified_name")]
176 fn py_fully_qualified_name() -> String {
177 format!("{}:{}", PY_MODULE_MODEL, stringify!(OrderBookDepth))
178 }
179
180 #[staticmethod]
182 #[pyo3(name = "get_metadata")]
183 fn py_get_metadata(
184 instrument_id: &InstrumentId,
185 price_precision: u8,
186 size_precision: u8,
187 ) -> HashMap<String, String> {
188 Self::get_metadata(instrument_id, price_precision, size_precision)
189 }
190
191 #[staticmethod]
193 #[pyo3(name = "get_fields")]
194 fn py_get_fields(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
195 let py_dict = PyDict::new(py);
196 for (k, v) in Self::get_fields() {
197 py_dict.set_item(k, v)?;
198 }
199
200 Ok(py_dict)
201 }
202
203 #[staticmethod]
205 #[pyo3(name = "get_stub")]
206 fn py_get_stub() -> Self {
207 let instrument_id = InstrumentId::from("AAPL.XNAS");
208 let flags = 0;
209 let sequence = 0;
210 let ts_event = 1;
211 let ts_init = 2;
212
213 let mut bids: [BookOrder; DEPTH10_LEN] = [BookOrder::default(); DEPTH10_LEN];
214 let mut asks: [BookOrder; DEPTH10_LEN] = [BookOrder::default(); DEPTH10_LEN];
215
216 let mut price = 99.00;
218 let mut quantity = 100.0;
219
220 for (i, order) in bids.iter_mut().take(DEPTH10_LEN).enumerate() {
221 *order = BookOrder::new(
222 OrderSide::Buy,
223 Price::new(price, 2),
224 Quantity::new(quantity, 0),
225 (i + 1) as u64,
226 );
227
228 price -= 1.0;
229 quantity += 100.0;
230 }
231
232 let mut price = 100.00;
234 let mut quantity = 100.0;
235
236 for (i, order) in asks.iter_mut().take(DEPTH10_LEN).enumerate() {
237 *order = BookOrder::new(
238 OrderSide::Sell,
239 Price::new(price, 2),
240 Quantity::new(quantity, 0),
241 (i + 11) as u64,
242 );
243
244 price += 1.0;
245 quantity += 100.0;
246 }
247
248 let bid_counts: [u32; 10] = [1; 10];
249 let ask_counts: [u32; 10] = [1; 10];
250
251 Self::new(
252 instrument_id,
253 bids,
254 asks,
255 bid_counts,
256 ask_counts,
257 flags,
258 sequence,
259 ts_event.into(),
260 ts_init.into(),
261 )
262 }
263
264 #[staticmethod]
266 #[pyo3(name = "from_dict")]
267 fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
268 from_dict_pyo3(py, values)
269 }
270
271 #[pyo3(name = "to_dict")]
273 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
274 to_dict_pyo3(py, self)
275 }
276
277 #[pyo3(name = "to_json_bytes")]
279 fn py_to_json_bytes(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
280 self.to_json_bytes()
281 .map_err(to_pyvalue_err)?
282 .into_py_any(py)
283 }
284
285 #[pyo3(name = "to_msgpack_bytes")]
287 fn py_to_msgpack_bytes(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
288 self.to_msgpack_bytes()
289 .map_err(to_pyvalue_err)?
290 .into_py_any(py)
291 }
292}
293
294#[pymethods]
295impl OrderBookDepth {
296 #[staticmethod]
297 #[pyo3(name = "from_json")]
298 fn py_from_json(data: &[u8]) -> PyResult<Self> {
299 Self::from_json_bytes(data).map_err(to_pyvalue_err)
300 }
301
302 #[staticmethod]
303 #[pyo3(name = "from_msgpack")]
304 fn py_from_msgpack(data: &[u8]) -> PyResult<Self> {
305 Self::from_msgpack_bytes(data).map_err(to_pyvalue_err)
306 }
307}