1use ahash::AHashSet;
17use indexmap::IndexMap;
18use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
19use pyo3::prelude::*;
20use rust_decimal::Decimal;
21
22use crate::{
23 data::{BookOrder, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick},
24 enums::{BookType, OrderSide, OrderStatus},
25 identifiers::InstrumentId,
26 orderbook::{
27 BookLevel, OrderBook,
28 analysis::book_check_integrity,
29 own::{OwnOrderBook, validate_accepted_buffer},
30 },
31 types::{Price, Quantity},
32};
33
34#[pymethods]
35#[pyo3_stub_gen::derive::gen_stub_pymethods]
36impl OrderBook {
37 #[new]
45 fn py_new(instrument_id: InstrumentId, book_type: BookType) -> Self {
46 Self::new(instrument_id, book_type)
47 }
48
49 fn __repr__(&self) -> String {
50 format!("{self:?}")
51 }
52
53 fn __str__(&self) -> String {
54 self.to_string()
55 }
56
57 #[getter]
58 #[pyo3(name = "instrument_id")]
59 fn py_instrument_id(&self) -> InstrumentId {
60 self.instrument_id
61 }
62
63 #[getter]
64 #[pyo3(name = "book_type")]
65 fn py_book_type(&self) -> BookType {
66 self.book_type
67 }
68
69 #[getter]
70 #[pyo3(name = "sequence")]
71 fn py_sequence(&self) -> u64 {
72 self.sequence
73 }
74
75 #[getter]
76 #[pyo3(name = "ts_event")]
77 fn py_ts_event(&self) -> u64 {
78 self.ts_last.as_u64()
79 }
80
81 #[getter]
82 #[pyo3(name = "ts_init")]
83 fn py_ts_init(&self) -> u64 {
84 self.ts_last.as_u64()
85 }
86
87 #[getter]
88 #[pyo3(name = "ts_last")]
89 fn py_ts_last(&self) -> u64 {
90 self.ts_last.as_u64()
91 }
92
93 #[getter]
94 #[pyo3(name = "update_count")]
95 fn py_update_count(&self) -> u64 {
96 self.update_count
97 }
98
99 #[pyo3(name = "reset")]
101 fn py_reset(&mut self) {
102 self.reset();
103 }
104
105 #[pyo3(name = "add")]
107 #[pyo3(signature = (order, flags, sequence, ts_event))]
108 fn py_add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
109 self.add(order, flags, sequence, ts_event.into());
110 }
111
112 #[pyo3(name = "update")]
114 #[pyo3(signature = (order, flags, sequence, ts_event))]
115 fn py_update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
116 self.update(order, flags, sequence, ts_event.into());
117 }
118
119 #[pyo3(name = "delete")]
121 #[pyo3(signature = (order, flags, sequence, ts_event))]
122 fn py_delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
123 self.delete(order, flags, sequence, ts_event.into());
124 }
125
126 #[pyo3(name = "clear")]
128 #[pyo3(signature = (sequence, ts_event))]
129 fn py_clear(&mut self, sequence: u64, ts_event: u64) {
130 self.clear(sequence, ts_event.into());
131 }
132
133 #[pyo3(name = "clear_bids")]
135 #[pyo3(signature = (sequence, ts_event))]
136 fn py_clear_bids(&mut self, sequence: u64, ts_event: u64) {
137 self.clear_bids(sequence, ts_event.into());
138 }
139
140 #[pyo3(name = "clear_asks")]
142 #[pyo3(signature = (sequence, ts_event))]
143 fn py_clear_asks(&mut self, sequence: u64, ts_event: u64) {
144 self.clear_asks(sequence, ts_event.into());
145 }
146
147 #[pyo3(name = "clear_stale_levels")]
155 #[pyo3(signature = (side=None))]
156 fn py_clear_stale_levels(&mut self, side: Option<OrderSide>) -> Option<Vec<BookLevel>> {
157 self.clear_stale_levels(side)
158 }
159
160 #[pyo3(name = "apply_delta")]
174 fn py_apply_delta(&mut self, delta: &OrderBookDelta) -> PyResult<()> {
175 self.apply_delta_unchecked(delta).map_err(to_pyruntime_err)
176 }
177
178 #[pyo3(name = "apply_deltas")]
186 fn py_apply_deltas(&mut self, deltas: &OrderBookDeltas) -> PyResult<()> {
187 self.apply_deltas_unchecked(deltas)
188 .map_err(to_pyruntime_err)
189 }
190
191 #[pyo3(name = "apply_depth")]
197 fn py_apply_depth(&mut self, depth: &OrderBookDepth10) -> PyResult<()> {
198 self.apply_depth_unchecked(depth).map_err(to_pyruntime_err)
199 }
200
201 #[pyo3(name = "check_integrity")]
202 fn py_check_integrity(&mut self) -> PyResult<()> {
203 book_check_integrity(self).map_err(to_pyruntime_err)
204 }
205
206 #[pyo3(name = "bids")]
208 #[pyo3(signature = (depth=None))]
209 fn py_bids(&self, depth: Option<usize>) -> Vec<BookLevel> {
210 self.bids(depth)
211 .map(|level_ref| (*level_ref).clone())
212 .collect()
213 }
214
215 #[pyo3(name = "asks")]
217 #[pyo3(signature = (depth=None))]
218 fn py_asks(&self, depth: Option<usize>) -> Vec<BookLevel> {
219 self.asks(depth)
220 .map(|level_ref| (*level_ref).clone())
221 .collect()
222 }
223
224 #[pyo3(name = "bids_to_dict")]
225 #[pyo3(signature = (depth=None))]
226 fn py_bids_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
227 self.bids_as_map(depth)
228 }
229
230 #[pyo3(name = "asks_to_dict")]
231 #[pyo3(signature = (depth=None))]
232 fn py_asks_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
233 self.asks_as_map(depth)
234 }
235
236 #[pyo3(name = "group_bids")]
238 #[pyo3(signature = (group_size, depth=None))]
239 #[must_use]
240 pub fn py_group_bids(
241 &self,
242 group_size: Decimal,
243 depth: Option<usize>,
244 ) -> IndexMap<Decimal, Decimal> {
245 self.group_bids(group_size, depth)
246 }
247
248 #[pyo3(name = "group_asks")]
250 #[pyo3(signature = (group_size, depth=None))]
251 #[must_use]
252 pub fn py_group_asks(
253 &self,
254 group_size: Decimal,
255 depth: Option<usize>,
256 ) -> IndexMap<Decimal, Decimal> {
257 self.group_asks(group_size, depth)
258 }
259
260 #[pyo3(name = "bids_filtered_to_dict")]
261 #[pyo3(signature = (depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
262 fn py_bids_filtered_to_dict(
263 &self,
264 depth: Option<usize>,
265 own_book: Option<&OwnOrderBook>,
266 status: Option<std::collections::HashSet<OrderStatus>>,
267 accepted_buffer_ns: Option<u64>,
268 ts_now: Option<u64>,
269 ) -> PyResult<IndexMap<Decimal, Decimal>> {
270 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
271 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
272 Ok(self.bids_filtered_as_map(
273 depth,
274 own_book,
275 status_set.as_ref(),
276 accepted_buffer_ns,
277 ts_now,
278 ))
279 }
280
281 #[pyo3(name = "asks_filtered_to_dict")]
282 #[pyo3(signature = (depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
283 fn py_asks_filtered_to_dict(
284 &self,
285 depth: Option<usize>,
286 own_book: Option<&OwnOrderBook>,
287 status: Option<std::collections::HashSet<OrderStatus>>,
288 accepted_buffer_ns: Option<u64>,
289 ts_now: Option<u64>,
290 ) -> PyResult<IndexMap<Decimal, Decimal>> {
291 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
292 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
293 Ok(self.asks_filtered_as_map(
294 depth,
295 own_book,
296 status_set.as_ref(),
297 accepted_buffer_ns,
298 ts_now,
299 ))
300 }
301
302 #[pyo3(name = "group_bids_filtered")]
303 #[pyo3(signature = (group_size, depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
304 fn py_group_bids_filered(
305 &self,
306 group_size: Decimal,
307 depth: Option<usize>,
308 own_book: Option<&OwnOrderBook>,
309 status: Option<std::collections::HashSet<OrderStatus>>,
310 accepted_buffer_ns: Option<u64>,
311 ts_now: Option<u64>,
312 ) -> PyResult<IndexMap<Decimal, Decimal>> {
313 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
314 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
315 Ok(self.group_bids_filtered(
316 group_size,
317 depth,
318 own_book,
319 status_set.as_ref(),
320 accepted_buffer_ns,
321 ts_now,
322 ))
323 }
324
325 #[pyo3(name = "group_asks_filtered")]
332 #[pyo3(signature = (group_size, depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
333 fn py_group_asks_filtered(
334 &self,
335 group_size: Decimal,
336 depth: Option<usize>,
337 own_book: Option<&OwnOrderBook>,
338 status: Option<std::collections::HashSet<OrderStatus>>,
339 accepted_buffer_ns: Option<u64>,
340 ts_now: Option<u64>,
341 ) -> PyResult<IndexMap<Decimal, Decimal>> {
342 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
343 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
344 Ok(self.group_asks_filtered(
345 group_size,
346 depth,
347 own_book,
348 status_set.as_ref(),
349 accepted_buffer_ns,
350 ts_now,
351 ))
352 }
353
354 #[pyo3(name = "filtered_view")]
356 #[pyo3(signature = (own_book=None, depth=None, status=None, accepted_buffer_ns=None, ts_now=None))]
357 fn py_filtered_view(
358 &self,
359 own_book: Option<&OwnOrderBook>,
360 depth: Option<usize>,
361 status: Option<std::collections::HashSet<OrderStatus>>,
362 accepted_buffer_ns: Option<u64>,
363 ts_now: Option<u64>,
364 ) -> PyResult<Self> {
365 validate_accepted_buffer(accepted_buffer_ns, ts_now).map_err(to_pyvalue_err)?;
366 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
367 self.filtered_view_checked(
368 own_book,
369 depth,
370 status_set.as_ref(),
371 accepted_buffer_ns,
372 ts_now,
373 )
374 .map_err(to_pyvalue_err)
375 }
376
377 #[pyo3(name = "best_bid_price")]
379 fn py_best_bid_price(&self) -> Option<Price> {
380 self.best_bid_price()
381 }
382
383 #[pyo3(name = "best_ask_price")]
385 fn py_best_ask_price(&self) -> Option<Price> {
386 self.best_ask_price()
387 }
388
389 #[pyo3(name = "best_bid_size")]
391 fn py_best_bid_size(&self) -> Option<Quantity> {
392 self.best_bid_size()
393 }
394
395 #[pyo3(name = "best_ask_size")]
397 fn py_best_ask_size(&self) -> Option<Quantity> {
398 self.best_ask_size()
399 }
400
401 #[pyo3(name = "spread")]
403 fn py_spread(&self) -> Option<f64> {
404 self.spread()
405 }
406
407 #[pyo3(name = "midpoint")]
409 fn py_midpoint(&self) -> Option<f64> {
410 self.midpoint()
411 }
412
413 #[pyo3(name = "get_avg_px_for_quantity")]
415 fn py_get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
416 self.get_avg_px_for_quantity(qty, order_side)
417 }
418
419 #[pyo3(name = "get_worst_px_for_quantity")]
421 fn py_get_worst_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> Option<Price> {
422 self.get_worst_px_for_quantity(qty, order_side)
423 }
424
425 #[pyo3(name = "get_avg_px_qty_for_exposure")]
427 fn py_get_avg_px_qty_for_exposure(
428 &self,
429 qty: Quantity,
430 order_side: OrderSide,
431 ) -> (f64, f64, f64) {
432 self.get_avg_px_qty_for_exposure(qty, order_side)
433 }
434
435 #[pyo3(name = "get_quantity_for_price")]
440 fn py_get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
441 self.get_quantity_for_price(price, order_side)
442 }
443
444 #[pyo3(name = "get_quantity_at_level")]
449 fn py_get_quantity_at_level(
450 &self,
451 price: Price,
452 order_side: OrderSide,
453 size_precision: u8,
454 ) -> Quantity {
455 self.get_quantity_at_level(price, order_side, size_precision)
456 }
457
458 #[pyo3(name = "simulate_fills")]
460 fn py_simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
461 self.simulate_fills(order)
462 }
463
464 #[pyo3(name = "pprint")]
466 #[pyo3(signature = (num_levels=3, group_size=None))]
467 fn py_pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
468 self.pprint(num_levels, group_size)
469 }
470}
471
472#[pyfunction()]
478#[pyo3(name = "update_book_with_quote_tick")]
479pub fn py_update_book_with_quote_tick(book: &mut OrderBook, quote: &QuoteTick) -> PyResult<()> {
480 book.update_quote_tick(quote).map_err(to_pyvalue_err)
481}
482
483#[pyfunction()]
489#[pyo3(name = "update_book_with_trade_tick")]
490pub fn py_update_book_with_trade_tick(book: &mut OrderBook, trade: &TradeTick) -> PyResult<()> {
491 book.update_trade_tick(trade).map_err(to_pyvalue_err)
492}