nautilus_model/python/orderbook/
book.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::collections::HashSet;
17
18use indexmap::IndexMap;
19use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
20use pyo3::prelude::*;
21use rust_decimal::Decimal;
22
23use crate::{
24    data::{BookOrder, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick},
25    enums::{BookType, OrderSide, OrderStatus},
26    identifiers::InstrumentId,
27    orderbook::{BookLevel, OrderBook, analysis::book_check_integrity, own::OwnOrderBook},
28    types::{Price, Quantity},
29};
30
31#[pymethods]
32impl OrderBook {
33    #[new]
34    fn py_new(instrument_id: InstrumentId, book_type: BookType) -> Self {
35        Self::new(instrument_id, book_type)
36    }
37
38    fn __repr__(&self) -> String {
39        format!("{self:?}")
40    }
41
42    fn __str__(&self) -> String {
43        self.to_string()
44    }
45
46    #[getter]
47    #[pyo3(name = "instrument_id")]
48    fn py_instrument_id(&self) -> InstrumentId {
49        self.instrument_id
50    }
51
52    #[getter]
53    #[pyo3(name = "book_type")]
54    fn py_book_type(&self) -> BookType {
55        self.book_type
56    }
57
58    #[getter]
59    #[pyo3(name = "sequence")]
60    fn py_sequence(&self) -> u64 {
61        self.sequence
62    }
63
64    #[getter]
65    #[pyo3(name = "ts_event")]
66    fn py_ts_event(&self) -> u64 {
67        self.ts_last.as_u64()
68    }
69
70    #[getter]
71    #[pyo3(name = "ts_init")]
72    fn py_ts_init(&self) -> u64 {
73        self.ts_last.as_u64()
74    }
75
76    #[getter]
77    #[pyo3(name = "ts_last")]
78    fn py_ts_last(&self) -> u64 {
79        self.ts_last.as_u64()
80    }
81
82    #[getter]
83    #[pyo3(name = "update_count")]
84    fn py_update_count(&self) -> u64 {
85        self.update_count
86    }
87
88    #[pyo3(name = "reset")]
89    fn py_reset(&mut self) {
90        self.reset();
91    }
92
93    #[pyo3(name = "add")]
94    #[pyo3(signature = (order, flags, sequence, ts_event))]
95    fn py_add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
96        self.add(order, flags, sequence, ts_event.into());
97    }
98
99    #[pyo3(name = "update")]
100    #[pyo3(signature = (order, flags, sequence, ts_event))]
101    fn py_update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
102        self.update(order, flags, sequence, ts_event.into());
103    }
104
105    #[pyo3(name = "delete")]
106    #[pyo3(signature = (order, flags, sequence, ts_event))]
107    fn py_delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
108        self.delete(order, flags, sequence, ts_event.into());
109    }
110
111    #[pyo3(name = "clear")]
112    #[pyo3(signature = (sequence, ts_event))]
113    fn py_clear(&mut self, sequence: u64, ts_event: u64) {
114        self.clear(sequence, ts_event.into());
115    }
116
117    #[pyo3(name = "clear_bids")]
118    #[pyo3(signature = (sequence, ts_event))]
119    fn py_clear_bids(&mut self, sequence: u64, ts_event: u64) {
120        self.clear_bids(sequence, ts_event.into());
121    }
122
123    #[pyo3(name = "clear_asks")]
124    #[pyo3(signature = (sequence, ts_event))]
125    fn py_clear_asks(&mut self, sequence: u64, ts_event: u64) {
126        self.clear_asks(sequence, ts_event.into());
127    }
128
129    #[pyo3(name = "clear_stale_levels")]
130    #[pyo3(signature = (side=None))]
131    fn py_clear_stale_levels(&mut self, side: Option<OrderSide>) -> Option<Vec<BookLevel>> {
132        self.clear_stale_levels(side)
133    }
134
135    #[pyo3(name = "apply_delta")]
136    fn py_apply_delta(&mut self, delta: &OrderBookDelta) -> PyResult<()> {
137        self.apply_delta(delta).map_err(to_pyruntime_err)
138    }
139
140    #[pyo3(name = "apply_deltas")]
141    fn py_apply_deltas(&mut self, deltas: &OrderBookDeltas) -> PyResult<()> {
142        self.apply_deltas(deltas).map_err(to_pyruntime_err)
143    }
144
145    #[pyo3(name = "apply_depth")]
146    fn py_apply_depth(&mut self, depth: &OrderBookDepth10) {
147        self.apply_depth(depth);
148    }
149
150    #[pyo3(name = "check_integrity")]
151    fn py_check_integrity(&mut self) -> PyResult<()> {
152        book_check_integrity(self).map_err(to_pyruntime_err)
153    }
154
155    #[pyo3(name = "bids")]
156    #[pyo3(signature = (depth=None))]
157    fn py_bids(&self, depth: Option<usize>) -> Vec<BookLevel> {
158        self.bids(depth)
159            .map(|level_ref| (*level_ref).clone())
160            .collect()
161    }
162
163    #[pyo3(name = "asks")]
164    #[pyo3(signature = (depth=None))]
165    fn py_asks(&self, depth: Option<usize>) -> Vec<BookLevel> {
166        self.asks(depth)
167            .map(|level_ref| (*level_ref).clone())
168            .collect()
169    }
170
171    #[pyo3(name = "bids_to_dict")]
172    #[pyo3(signature = (depth=None))]
173    fn py_bids_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
174        self.bids_as_map(depth)
175    }
176
177    #[pyo3(name = "asks_to_dict")]
178    #[pyo3(signature = (depth=None))]
179    fn py_asks_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
180        self.asks_as_map(depth)
181    }
182
183    #[pyo3(name = "group_bids")]
184    #[pyo3(signature = (group_size, depth=None))]
185    pub fn py_group_bids(
186        &self,
187        group_size: Decimal,
188        depth: Option<usize>,
189    ) -> IndexMap<Decimal, Decimal> {
190        self.group_bids(group_size, depth)
191    }
192
193    #[pyo3(name = "group_asks")]
194    #[pyo3(signature = (group_size, depth=None))]
195    pub fn py_group_asks(
196        &self,
197        group_size: Decimal,
198        depth: Option<usize>,
199    ) -> IndexMap<Decimal, Decimal> {
200        self.group_asks(group_size, depth)
201    }
202
203    #[pyo3(name = "bids_filtered_to_dict")]
204    #[pyo3(signature = (depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
205    fn py_bids_filtered_to_dict(
206        &self,
207        depth: Option<usize>,
208        own_book: Option<&OwnOrderBook>,
209        status: Option<HashSet<OrderStatus>>,
210        accepted_buffer_ns: Option<u64>,
211        ts_now: Option<u64>,
212    ) -> IndexMap<Decimal, Decimal> {
213        self.bids_filtered_as_map(depth, own_book, status, accepted_buffer_ns, ts_now)
214    }
215
216    #[pyo3(name = "asks_filtered_to_dict")]
217    #[pyo3(signature = (depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
218    fn py_asks_filtered_to_dict(
219        &self,
220        depth: Option<usize>,
221        own_book: Option<&OwnOrderBook>,
222        status: Option<HashSet<OrderStatus>>,
223        accepted_buffer_ns: Option<u64>,
224        ts_now: Option<u64>,
225    ) -> IndexMap<Decimal, Decimal> {
226        self.asks_filtered_as_map(depth, own_book, status, accepted_buffer_ns, ts_now)
227    }
228
229    #[pyo3(name = "group_bids_filtered")]
230    #[pyo3(signature = (group_size, depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
231    fn py_group_bids_filered(
232        &self,
233        group_size: Decimal,
234        depth: Option<usize>,
235        own_book: Option<&OwnOrderBook>,
236        status: Option<HashSet<OrderStatus>>,
237        accepted_buffer_ns: Option<u64>,
238        ts_now: Option<u64>,
239    ) -> IndexMap<Decimal, Decimal> {
240        self.group_bids_filtered(
241            group_size,
242            depth,
243            own_book,
244            status,
245            accepted_buffer_ns,
246            ts_now,
247        )
248    }
249
250    #[pyo3(name = "group_asks_filtered")]
251    #[pyo3(signature = (group_size, depth=None, own_book=None, status=None, accepted_buffer_ns=None, ts_now=None))]
252    fn py_group_asks_filtered(
253        &self,
254        group_size: Decimal,
255        depth: Option<usize>,
256        own_book: Option<&OwnOrderBook>,
257        status: Option<HashSet<OrderStatus>>,
258        accepted_buffer_ns: Option<u64>,
259        ts_now: Option<u64>,
260    ) -> IndexMap<Decimal, Decimal> {
261        self.group_asks_filtered(
262            group_size,
263            depth,
264            own_book,
265            status,
266            accepted_buffer_ns,
267            ts_now,
268        )
269    }
270
271    #[pyo3(name = "best_bid_price")]
272    fn py_best_bid_price(&self) -> Option<Price> {
273        self.best_bid_price()
274    }
275
276    #[pyo3(name = "best_ask_price")]
277    fn py_best_ask_price(&self) -> Option<Price> {
278        self.best_ask_price()
279    }
280
281    #[pyo3(name = "best_bid_size")]
282    fn py_best_bid_size(&self) -> Option<Quantity> {
283        self.best_bid_size()
284    }
285
286    #[pyo3(name = "best_ask_size")]
287    fn py_best_ask_size(&self) -> Option<Quantity> {
288        self.best_ask_size()
289    }
290
291    #[pyo3(name = "spread")]
292    fn py_spread(&self) -> Option<f64> {
293        self.spread()
294    }
295
296    #[pyo3(name = "midpoint")]
297    fn py_midpoint(&self) -> Option<f64> {
298        self.midpoint()
299    }
300
301    #[pyo3(name = "get_avg_px_for_quantity")]
302    fn py_get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
303        self.get_avg_px_for_quantity(qty, order_side)
304    }
305
306    #[pyo3(name = "get_avg_px_qty_for_exposure")]
307    fn py_get_avg_px_qty_for_exposure(
308        &self,
309        qty: Quantity,
310        order_side: OrderSide,
311    ) -> (f64, f64, f64) {
312        self.get_avg_px_qty_for_exposure(qty, order_side)
313    }
314
315    #[pyo3(name = "get_quantity_for_price")]
316    fn py_get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
317        self.get_quantity_for_price(price, order_side)
318    }
319
320    #[pyo3(name = "simulate_fills")]
321    fn py_simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
322        self.simulate_fills(order)
323    }
324
325    #[pyo3(name = "pprint")]
326    #[pyo3(signature = (num_levels=3, group_size=None))]
327    fn py_pprint(&self, num_levels: usize, group_size: Option<Decimal>) -> String {
328        self.pprint(num_levels, group_size)
329    }
330}
331
332/// Updates the `OrderBook` with a [`QuoteTick`].
333///
334/// # Errors
335///
336/// Returns a `PyErr` if the update operation fails.
337#[pyfunction()]
338#[pyo3(name = "update_book_with_quote_tick")]
339pub fn py_update_book_with_quote_tick(book: &mut OrderBook, quote: &QuoteTick) -> PyResult<()> {
340    book.update_quote_tick(quote).map_err(to_pyvalue_err)
341}
342
343/// Updates the `OrderBook` with a [`TradeTick`].
344///
345/// # Errors
346///
347/// Returns a `PyErr` if the update operation fails.
348#[pyfunction()]
349#[pyo3(name = "update_book_with_trade_tick")]
350pub fn py_update_book_with_trade_tick(book: &mut OrderBook, trade: &TradeTick) -> PyResult<()> {
351    book.update_trade_tick(trade).map_err(to_pyvalue_err)
352}