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::{BookLevel, OrderBook, analysis::book_check_integrity, own::OwnOrderBook},
27 types::{Price, Quantity},
28};
29
30#[pymethods]
31#[pyo3_stub_gen::derive::gen_stub_pymethods]
32impl OrderBook {
33 #[new]
41 fn py_new(instrument_id: InstrumentId, book_type: BookType) -> Self {
42 Self::new(instrument_id, book_type)
43 }
44
45 fn __repr__(&self) -> String {
46 format!("{self:?}")
47 }
48
49 fn __str__(&self) -> String {
50 self.to_string()
51 }
52
53 #[getter]
54 #[pyo3(name = "instrument_id")]
55 fn py_instrument_id(&self) -> InstrumentId {
56 self.instrument_id
57 }
58
59 #[getter]
60 #[pyo3(name = "book_type")]
61 fn py_book_type(&self) -> BookType {
62 self.book_type
63 }
64
65 #[getter]
66 #[pyo3(name = "sequence")]
67 fn py_sequence(&self) -> u64 {
68 self.sequence
69 }
70
71 #[getter]
72 #[pyo3(name = "ts_event")]
73 fn py_ts_event(&self) -> u64 {
74 self.ts_last.as_u64()
75 }
76
77 #[getter]
78 #[pyo3(name = "ts_init")]
79 fn py_ts_init(&self) -> u64 {
80 self.ts_last.as_u64()
81 }
82
83 #[getter]
84 #[pyo3(name = "ts_last")]
85 fn py_ts_last(&self) -> u64 {
86 self.ts_last.as_u64()
87 }
88
89 #[getter]
90 #[pyo3(name = "update_count")]
91 fn py_update_count(&self) -> u64 {
92 self.update_count
93 }
94
95 #[pyo3(name = "reset")]
97 fn py_reset(&mut self) {
98 self.reset();
99 }
100
101 #[pyo3(name = "add")]
103 #[pyo3(signature = (order, flags, sequence, ts_event))]
104 fn py_add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
105 self.add(order, flags, sequence, ts_event.into());
106 }
107
108 #[pyo3(name = "update")]
110 #[pyo3(signature = (order, flags, sequence, ts_event))]
111 fn py_update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
112 self.update(order, flags, sequence, ts_event.into());
113 }
114
115 #[pyo3(name = "delete")]
117 #[pyo3(signature = (order, flags, sequence, ts_event))]
118 fn py_delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
119 self.delete(order, flags, sequence, ts_event.into());
120 }
121
122 #[pyo3(name = "clear")]
124 #[pyo3(signature = (sequence, ts_event))]
125 fn py_clear(&mut self, sequence: u64, ts_event: u64) {
126 self.clear(sequence, ts_event.into());
127 }
128
129 #[pyo3(name = "clear_bids")]
131 #[pyo3(signature = (sequence, ts_event))]
132 fn py_clear_bids(&mut self, sequence: u64, ts_event: u64) {
133 self.clear_bids(sequence, ts_event.into());
134 }
135
136 #[pyo3(name = "clear_asks")]
138 #[pyo3(signature = (sequence, ts_event))]
139 fn py_clear_asks(&mut self, sequence: u64, ts_event: u64) {
140 self.clear_asks(sequence, ts_event.into());
141 }
142
143 #[pyo3(name = "clear_stale_levels")]
151 #[pyo3(signature = (side=None))]
152 fn py_clear_stale_levels(&mut self, side: Option<OrderSide>) -> Option<Vec<BookLevel>> {
153 self.clear_stale_levels(side)
154 }
155
156 #[pyo3(name = "apply_delta")]
165 fn py_apply_delta(&mut self, delta: &OrderBookDelta) -> PyResult<()> {
166 self.apply_delta_unchecked(delta).map_err(to_pyruntime_err)
167 }
168
169 #[pyo3(name = "apply_deltas")]
177 fn py_apply_deltas(&mut self, deltas: &OrderBookDeltas) -> PyResult<()> {
178 self.apply_deltas_unchecked(deltas)
179 .map_err(to_pyruntime_err)
180 }
181
182 #[pyo3(name = "apply_depth")]
188 fn py_apply_depth(&mut self, depth: &OrderBookDepth10) -> PyResult<()> {
189 self.apply_depth_unchecked(depth).map_err(to_pyruntime_err)
190 }
191
192 #[pyo3(name = "check_integrity")]
193 fn py_check_integrity(&mut self) -> PyResult<()> {
194 book_check_integrity(self).map_err(to_pyruntime_err)
195 }
196
197 #[pyo3(name = "bids")]
199 #[pyo3(signature = (depth=None))]
200 fn py_bids(&self, depth: Option<usize>) -> Vec<BookLevel> {
201 self.bids(depth)
202 .map(|level_ref| (*level_ref).clone())
203 .collect()
204 }
205
206 #[pyo3(name = "asks")]
208 #[pyo3(signature = (depth=None))]
209 fn py_asks(&self, depth: Option<usize>) -> Vec<BookLevel> {
210 self.asks(depth)
211 .map(|level_ref| (*level_ref).clone())
212 .collect()
213 }
214
215 #[pyo3(name = "bids_to_dict")]
216 #[pyo3(signature = (depth=None))]
217 fn py_bids_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
218 self.bids_as_map(depth)
219 }
220
221 #[pyo3(name = "asks_to_dict")]
222 #[pyo3(signature = (depth=None))]
223 fn py_asks_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
224 self.asks_as_map(depth)
225 }
226
227 #[pyo3(name = "group_bids")]
229 #[pyo3(signature = (group_size, depth=None))]
230 #[must_use]
231 pub fn py_group_bids(
232 &self,
233 group_size: Decimal,
234 depth: Option<usize>,
235 ) -> IndexMap<Decimal, Decimal> {
236 self.group_bids(group_size, depth)
237 }
238
239 #[pyo3(name = "group_asks")]
241 #[pyo3(signature = (group_size, depth=None))]
242 #[must_use]
243 pub fn py_group_asks(
244 &self,
245 group_size: Decimal,
246 depth: Option<usize>,
247 ) -> IndexMap<Decimal, Decimal> {
248 self.group_asks(group_size, depth)
249 }
250
251 #[pyo3(name = "bids_filtered_to_dict")]
252 #[pyo3(signature = (depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
253 fn py_bids_filtered_to_dict(
254 &self,
255 depth: Option<usize>,
256 own_book: Option<&OwnOrderBook>,
257 status: Option<std::collections::HashSet<OrderStatus>>,
258 accepted_buffer_ns: Option<u64>,
259 ts_now: Option<u64>,
260 ) -> IndexMap<Decimal, Decimal> {
261 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
262 self.bids_filtered_as_map(
263 depth,
264 own_book,
265 status_set.as_ref(),
266 accepted_buffer_ns,
267 ts_now,
268 )
269 }
270
271 #[pyo3(name = "asks_filtered_to_dict")]
272 #[pyo3(signature = (depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
273 fn py_asks_filtered_to_dict(
274 &self,
275 depth: Option<usize>,
276 own_book: Option<&OwnOrderBook>,
277 status: Option<std::collections::HashSet<OrderStatus>>,
278 accepted_buffer_ns: Option<u64>,
279 ts_now: Option<u64>,
280 ) -> IndexMap<Decimal, Decimal> {
281 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
282 self.asks_filtered_as_map(
283 depth,
284 own_book,
285 status_set.as_ref(),
286 accepted_buffer_ns,
287 ts_now,
288 )
289 }
290
291 #[pyo3(name = "group_bids_filtered")]
292 #[pyo3(signature = (group_size, depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
293 fn py_group_bids_filered(
294 &self,
295 group_size: Decimal,
296 depth: Option<usize>,
297 own_book: Option<&OwnOrderBook>,
298 status: Option<std::collections::HashSet<OrderStatus>>,
299 accepted_buffer_ns: Option<u64>,
300 ts_now: Option<u64>,
301 ) -> IndexMap<Decimal, Decimal> {
302 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
303 self.group_bids_filtered(
304 group_size,
305 depth,
306 own_book,
307 status_set.as_ref(),
308 accepted_buffer_ns,
309 ts_now,
310 )
311 }
312
313 #[pyo3(name = "group_asks_filtered")]
319 #[pyo3(signature = (group_size, depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
320 fn py_group_asks_filtered(
321 &self,
322 group_size: Decimal,
323 depth: Option<usize>,
324 own_book: Option<&OwnOrderBook>,
325 status: Option<std::collections::HashSet<OrderStatus>>,
326 accepted_buffer_ns: Option<u64>,
327 ts_now: Option<u64>,
328 ) -> IndexMap<Decimal, Decimal> {
329 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
330 self.group_asks_filtered(
331 group_size,
332 depth,
333 own_book,
334 status_set.as_ref(),
335 accepted_buffer_ns,
336 ts_now,
337 )
338 }
339
340 #[pyo3(name = "filtered_view")]
342 #[pyo3(signature = (own_book=None, depth=None, status=None, accepted_buffer_ns=None, ts_now=None))]
343 fn py_filtered_view(
344 &self,
345 own_book: Option<&OwnOrderBook>,
346 depth: Option<usize>,
347 status: Option<std::collections::HashSet<OrderStatus>>,
348 accepted_buffer_ns: Option<u64>,
349 ts_now: Option<u64>,
350 ) -> PyResult<Self> {
351 let status_set: Option<AHashSet<OrderStatus>> = status.map(|s| s.into_iter().collect());
352 self.filtered_view_checked(
353 own_book,
354 depth,
355 status_set.as_ref(),
356 accepted_buffer_ns,
357 ts_now,
358 )
359 .map_err(to_pyvalue_err)
360 }
361
362 #[pyo3(name = "best_bid_price")]
364 fn py_best_bid_price(&self) -> Option<Price> {
365 self.best_bid_price()
366 }
367
368 #[pyo3(name = "best_ask_price")]
370 fn py_best_ask_price(&self) -> Option<Price> {
371 self.best_ask_price()
372 }
373
374 #[pyo3(name = "best_bid_size")]
376 fn py_best_bid_size(&self) -> Option<Quantity> {
377 self.best_bid_size()
378 }
379
380 #[pyo3(name = "best_ask_size")]
382 fn py_best_ask_size(&self) -> Option<Quantity> {
383 self.best_ask_size()
384 }
385
386 #[pyo3(name = "spread")]
388 fn py_spread(&self) -> Option<f64> {
389 self.spread()
390 }
391
392 #[pyo3(name = "midpoint")]
394 fn py_midpoint(&self) -> Option<f64> {
395 self.midpoint()
396 }
397
398 #[pyo3(name = "get_avg_px_for_quantity")]
400 fn py_get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
401 self.get_avg_px_for_quantity(qty, order_side)
402 }
403
404 #[pyo3(name = "get_worst_px_for_quantity")]
406 fn py_get_worst_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> Option<Price> {
407 self.get_worst_px_for_quantity(qty, order_side)
408 }
409
410 #[pyo3(name = "get_avg_px_qty_for_exposure")]
412 fn py_get_avg_px_qty_for_exposure(
413 &self,
414 qty: Quantity,
415 order_side: OrderSide,
416 ) -> (f64, f64, f64) {
417 self.get_avg_px_qty_for_exposure(qty, order_side)
418 }
419
420 #[pyo3(name = "get_quantity_for_price")]
425 fn py_get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
426 self.get_quantity_for_price(price, order_side)
427 }
428
429 #[pyo3(name = "get_quantity_at_level")]
434 fn py_get_quantity_at_level(
435 &self,
436 price: Price,
437 order_side: OrderSide,
438 size_precision: u8,
439 ) -> Quantity {
440 self.get_quantity_at_level(price, order_side, size_precision)
441 }
442
443 #[pyo3(name = "simulate_fills")]
445 fn py_simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
446 self.simulate_fills(order)
447 }
448
449 #[pyo3(name = "pprint")]
451 #[pyo3(signature = (num_levels=3, group_size=None))]
452 fn py_pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
453 self.pprint(num_levels, group_size)
454 }
455}
456
457#[pyfunction()]
463#[pyo3(name = "update_book_with_quote_tick")]
464pub fn py_update_book_with_quote_tick(book: &mut OrderBook, quote: &QuoteTick) -> PyResult<()> {
465 book.update_quote_tick(quote).map_err(to_pyvalue_err)
466}
467
468#[pyfunction()]
474#[pyo3(name = "update_book_with_trade_tick")]
475pub fn py_update_book_with_trade_tick(book: &mut OrderBook, trade: &TradeTick) -> PyResult<()> {
476 book.update_trade_tick(trade).map_err(to_pyvalue_err)
477}