nautilus_model/python/orderbook/
own.rs1use std::{
17 collections::{HashSet, hash_map::DefaultHasher},
18 hash::{Hash, Hasher},
19};
20
21use ahash::AHashSet;
22use indexmap::IndexMap;
23use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyruntime_err, to_pyvalue_err};
24use pyo3::{Python, prelude::*, pyclass::CompareOp};
25use rust_decimal::Decimal;
26
27use crate::{
28 enums::{OrderSide, OrderStatus, OrderType, TimeInForce},
29 identifiers::{ClientOrderId, InstrumentId, TraderId, VenueOrderId},
30 orderbook::{
31 OwnBookOrder,
32 own::{OwnOrderBook, validate_accepted_buffer},
33 },
34 python::types::fixed::FloatArithmetic,
35 types::{Price, Quantity},
36};
37
38#[pymethods]
39#[pyo3_stub_gen::derive::gen_stub_pymethods]
40impl OwnBookOrder {
41 #[pyo3(signature = (trader_id, client_order_id, side, price, size, order_type, time_in_force, status, ts_last, ts_accepted, ts_submitted, ts_init, venue_order_id=None))]
46 #[new]
47 #[expect(clippy::too_many_arguments)]
48 fn py_new(
49 trader_id: TraderId,
50 client_order_id: ClientOrderId,
51 side: OrderSide,
52 price: Price,
53 size: Quantity,
54 order_type: OrderType,
55 time_in_force: TimeInForce,
56 status: OrderStatus,
57 ts_last: u64,
58 ts_accepted: u64,
59 ts_submitted: u64,
60 ts_init: u64,
61 venue_order_id: Option<VenueOrderId>,
62 ) -> Self {
63 Self::new(
64 trader_id,
65 client_order_id,
66 venue_order_id,
67 side,
68 price,
69 size,
70 order_type,
71 time_in_force,
72 status,
73 ts_last.into(),
74 ts_accepted.into(),
75 ts_submitted.into(),
76 ts_init.into(),
77 )
78 }
79
80 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
81 match op {
82 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
83 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
84 _ => py.NotImplemented(),
85 }
86 }
87
88 fn __hash__(&self) -> isize {
89 let mut hasher = DefaultHasher::new();
90 self.hash(&mut hasher);
91 hasher.finish() as isize
92 }
93
94 fn __repr__(&self) -> String {
95 format!("{self:?}")
96 }
97
98 fn __str__(&self) -> String {
99 self.to_string()
100 }
101
102 #[getter]
103 #[pyo3(name = "client_order_id")]
104 fn py_client_order_id(&self) -> ClientOrderId {
105 self.client_order_id
106 }
107
108 #[getter]
109 #[pyo3(name = "side")]
110 fn py_side(&self) -> OrderSide {
111 self.side
112 }
113
114 #[getter]
115 #[pyo3(name = "price")]
116 fn py_price(&self) -> Price {
117 self.price
118 }
119
120 #[getter]
121 #[pyo3(name = "size")]
122 fn py_size(&self) -> Quantity {
123 self.size
124 }
125
126 #[getter]
127 #[pyo3(name = "order_type")]
128 fn py_order_type(&self) -> OrderType {
129 self.order_type
130 }
131
132 #[getter]
133 #[pyo3(name = "time_in_force")]
134 fn py_time_in_force(&self) -> TimeInForce {
135 self.time_in_force
136 }
137
138 #[getter]
139 #[pyo3(name = "status")]
140 fn py_status(&self) -> OrderStatus {
141 self.status
142 }
143
144 #[getter]
145 #[pyo3(name = "ts_last")]
146 fn py_ts_last(&self) -> u64 {
147 self.ts_last.into()
148 }
149
150 #[getter]
151 #[pyo3(name = "ts_init")]
152 fn py_ts_init(&self) -> u64 {
153 self.ts_init.into()
154 }
155
156 #[pyo3(name = "exposure")]
158 fn py_exposure(&self) -> PyResult<f64> {
159 self.price.check_float_precision()?;
160 self.size.check_float_precision()?;
161 Ok(self.exposure())
162 }
163
164 #[pyo3(name = "signed_size")]
166 fn py_signed_size(&self) -> PyResult<f64> {
167 self.size.check_float_precision()?;
168 Ok(self.signed_size())
169 }
170}
171
172#[pymethods]
173#[pyo3_stub_gen::derive::gen_stub_pymethods]
174impl OwnOrderBook {
175 #[new]
177 fn py_new(instrument_id: InstrumentId) -> Self {
178 Self::new(instrument_id)
179 }
180
181 fn __repr__(&self) -> String {
182 format!("{self:?}")
183 }
184
185 fn __str__(&self) -> String {
186 self.to_string()
187 }
188
189 #[getter]
190 #[pyo3(name = "instrument_id")]
191 fn py_instrument_id(&self) -> InstrumentId {
192 self.instrument_id
193 }
194
195 #[getter]
196 #[pyo3(name = "ts_last")]
197 fn py_ts_last(&self) -> u64 {
198 self.ts_last.as_u64()
199 }
200
201 #[getter]
202 #[pyo3(name = "update_count")]
203 fn py_update_count(&self) -> u64 {
204 self.update_count
205 }
206
207 #[pyo3(name = "reset")]
209 fn py_reset(&mut self) {
210 self.reset();
211 }
212
213 #[pyo3(name = "add")]
216 fn py_add(&mut self, order: OwnBookOrder) {
217 self.add(order);
218 }
219
220 #[pyo3(name = "update")]
226 fn py_update(&mut self, order: OwnBookOrder) -> PyResult<()> {
227 self.update(order).map_err(to_pyruntime_err)
228 }
229
230 #[pyo3(name = "delete")]
236 fn py_delete(&mut self, order: OwnBookOrder) -> PyResult<()> {
237 self.delete(order).map_err(to_pyruntime_err)
238 }
239
240 #[pyo3(name = "clear")]
242 fn py_clear(&mut self) {
243 self.clear();
244 }
245
246 #[pyo3(name = "bid_client_order_ids")]
248 #[must_use]
249 pub fn py_bid_client_order_ids(&self) -> Vec<ClientOrderId> {
250 self.bid_client_order_ids()
251 }
252
253 #[pyo3(name = "ask_client_order_ids")]
255 #[must_use]
256 pub fn py_ask_client_order_ids(&self) -> Vec<ClientOrderId> {
257 self.ask_client_order_ids()
258 }
259
260 #[pyo3(name = "is_order_in_book")]
262 #[must_use]
263 pub fn py_is_order_in_book(&self, client_order_id: &ClientOrderId) -> bool {
264 self.is_order_in_book(client_order_id)
265 }
266
267 #[pyo3(name = "orders_to_list")]
268 fn py_orders_to_list(&self) -> Vec<OwnBookOrder> {
269 let total_orders = self.bids.cache.len() + self.asks.cache.len();
270 let mut all_orders = Vec::with_capacity(total_orders);
271
272 all_orders.extend(
273 self.bids()
274 .flat_map(|level| level.orders.values().copied())
275 .chain(self.asks().flat_map(|level| level.orders.values().copied())),
276 );
277
278 all_orders
279 }
280
281 #[pyo3(name = "bids_to_list")]
282 fn py_bids_to_list(&self) -> Vec<OwnBookOrder> {
283 self.bids()
284 .flat_map(|level| level.orders.values().copied())
285 .collect()
286 }
287
288 #[pyo3(name = "asks_to_list")]
289 fn py_asks_to_list(&self) -> Vec<OwnBookOrder> {
290 self.asks()
291 .flat_map(|level| level.orders.values().copied())
292 .collect()
293 }
294
295 #[pyo3(name = "bids_to_dict")]
296 #[pyo3(signature = (status=None, accepted_buffer_ns=None, ts_now=None))]
297 fn py_bids_to_dict(
298 &self,
299 status: Option<HashSet<OrderStatus>>,
300 accepted_buffer_ns: Option<u64>,
301 ts_now: Option<u64>,
302 ) -> PyResult<IndexMap<Decimal, Vec<OwnBookOrder>>> {
303 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
304 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
305 Ok(self.bids_as_map(status_set.as_ref(), accepted_buffer_ns, ts_now))
306 }
307
308 #[pyo3(name = "asks_to_dict")]
309 #[pyo3(signature = (status=None, accepted_buffer_ns=None, ts_now=None))]
310 fn py_asks_to_dict(
311 &self,
312 status: Option<HashSet<OrderStatus>>,
313 accepted_buffer_ns: Option<u64>,
314 ts_now: Option<u64>,
315 ) -> PyResult<IndexMap<Decimal, Vec<OwnBookOrder>>> {
316 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
317 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
318 Ok(self.asks_as_map(status_set.as_ref(), accepted_buffer_ns, ts_now))
319 }
320
321 #[pyo3(name = "bid_quantity")]
330 #[pyo3(signature = (status=None, depth=None, group_size=None, accepted_buffer_ns=None, ts_now=None))]
331 fn py_bid_quantity(
332 &self,
333 status: Option<HashSet<OrderStatus>>,
334 depth: Option<usize>,
335 group_size: Option<Decimal>,
336 accepted_buffer_ns: Option<u64>,
337 ts_now: Option<u64>,
338 ) -> PyResult<IndexMap<Decimal, Decimal>> {
339 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
340 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
341 Ok(self.bid_quantity(
342 status_set.as_ref(),
343 depth,
344 group_size,
345 accepted_buffer_ns,
346 ts_now,
347 ))
348 }
349
350 #[pyo3(name = "ask_quantity")]
359 #[pyo3(signature = (status=None, depth=None, group_size=None, accepted_buffer_ns=None, ts_now=None))]
360 fn py_ask_quantity(
361 &self,
362 status: Option<HashSet<OrderStatus>>,
363 depth: Option<usize>,
364 group_size: Option<Decimal>,
365 accepted_buffer_ns: Option<u64>,
366 ts_now: Option<u64>,
367 ) -> PyResult<IndexMap<Decimal, Decimal>> {
368 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
369 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
370 Ok(self.ask_quantity(
371 status_set.as_ref(),
372 depth,
373 group_size,
374 accepted_buffer_ns,
375 ts_now,
376 ))
377 }
378
379 #[pyo3(name = "combined_with_opposite")]
388 fn py_combined_with_opposite(&self, opposite: &Self) -> PyResult<Self> {
389 self.combined_with_opposite(opposite)
390 .map_err(to_pyvalue_err)
391 }
392
393 #[pyo3(name = "audit_open_orders")]
394 fn py_audit_open_orders(&mut self, open_order_ids: HashSet<ClientOrderId>) {
395 self.audit_open_orders(&open_order_ids.into_iter().collect());
396 }
397
398 #[pyo3(name = "pprint")]
400 #[pyo3(signature = (num_levels=3, group_size=None))]
401 fn py_pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
402 self.pprint(num_levels, group_size)
403 }
404}