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 indexmap::IndexMap;
17use nautilus_core::python::{to_pyruntime_err, to_pyvalue_err};
18use pyo3::prelude::*;
19use rust_decimal::Decimal;
20
21use crate::{
22    data::{BookOrder, OrderBookDelta, OrderBookDeltas, OrderBookDepth10, QuoteTick, TradeTick},
23    enums::{BookType, OrderSide},
24    identifiers::InstrumentId,
25    orderbook::{analysis::book_check_integrity, BookLevel, OrderBook},
26    types::{Price, Quantity},
27};
28
29#[pymethods]
30impl OrderBook {
31    #[new]
32    fn py_new(instrument_id: InstrumentId, book_type: BookType) -> Self {
33        Self::new(instrument_id, book_type)
34    }
35
36    fn __repr__(&self) -> String {
37        format!("{self:?}")
38    }
39
40    fn __str__(&self) -> String {
41        // TODO: Return debug string for now
42        format!("{self:?}")
43    }
44
45    #[getter]
46    #[pyo3(name = "instrument_id")]
47    fn py_instrument_id(&self) -> InstrumentId {
48        self.instrument_id
49    }
50
51    #[getter]
52    #[pyo3(name = "book_type")]
53    fn py_book_type(&self) -> BookType {
54        self.book_type
55    }
56
57    #[getter]
58    #[pyo3(name = "sequence")]
59    fn py_sequence(&self) -> u64 {
60        self.sequence
61    }
62
63    #[getter]
64    #[pyo3(name = "ts_event")]
65    fn py_ts_event(&self) -> u64 {
66        self.ts_last.as_u64()
67    }
68
69    #[getter]
70    #[pyo3(name = "ts_init")]
71    fn py_ts_init(&self) -> u64 {
72        self.ts_last.as_u64()
73    }
74
75    #[getter]
76    #[pyo3(name = "ts_last")]
77    fn py_ts_last(&self) -> u64 {
78        self.ts_last.as_u64()
79    }
80
81    #[getter]
82    #[pyo3(name = "count")]
83    fn py_count(&self) -> u64 {
84        self.count
85    }
86
87    #[pyo3(name = "reset")]
88    fn py_reset(&mut self) {
89        self.reset();
90    }
91
92    #[pyo3(signature = (order, flags, sequence, ts_event))]
93    #[pyo3(name = "add")]
94    fn py_add(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
95        self.add(order, flags, sequence, ts_event.into());
96    }
97
98    #[pyo3(signature = (order, flags, sequence, ts_event))]
99    #[pyo3(name = "update")]
100    fn py_update(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
101        self.update(order, flags, sequence, ts_event.into());
102    }
103
104    #[pyo3(signature = (order, flags, sequence, ts_event))]
105    #[pyo3(name = "delete")]
106    fn py_delete(&mut self, order: BookOrder, flags: u8, sequence: u64, ts_event: u64) {
107        self.delete(order, flags, sequence, ts_event.into());
108    }
109
110    #[pyo3(signature = (sequence, ts_event))]
111    #[pyo3(name = "clear")]
112    fn py_clear(&mut self, sequence: u64, ts_event: u64) {
113        self.clear(sequence, ts_event.into());
114    }
115
116    #[pyo3(signature = (sequence, ts_event))]
117    #[pyo3(name = "clear_bids")]
118    fn py_clear_bids(&mut self, sequence: u64, ts_event: u64) {
119        self.clear_bids(sequence, ts_event.into());
120    }
121
122    #[pyo3(signature = (sequence, ts_event))]
123    #[pyo3(name = "clear_asks")]
124    fn py_clear_asks(&mut self, sequence: u64, ts_event: u64) {
125        self.clear_asks(sequence, ts_event.into());
126    }
127
128    #[pyo3(name = "apply_delta")]
129    fn py_apply_delta(&mut self, delta: &OrderBookDelta) {
130        self.apply_delta(delta);
131    }
132
133    #[pyo3(name = "apply_deltas")]
134    fn py_apply_deltas(&mut self, deltas: &OrderBookDeltas) {
135        self.apply_deltas(deltas);
136    }
137
138    #[pyo3(name = "apply_depth")]
139    fn py_apply_depth(&mut self, depth: &OrderBookDepth10) {
140        self.apply_depth(depth);
141    }
142
143    #[pyo3(name = "check_integrity")]
144    fn py_check_integrity(&mut self) -> PyResult<()> {
145        book_check_integrity(self).map_err(to_pyruntime_err)
146    }
147
148    #[pyo3(signature = (depth=None))]
149    #[pyo3(name = "bids")]
150    fn py_bids(&self, depth: Option<usize>) -> Vec<BookLevel> {
151        self.bids(depth)
152            .map(|level_ref| (*level_ref).clone())
153            .collect()
154    }
155
156    #[pyo3(signature = (depth=None))]
157    #[pyo3(name = "asks")]
158    fn py_asks(&self, depth: Option<usize>) -> Vec<BookLevel> {
159        self.asks(depth)
160            .map(|level_ref| (*level_ref).clone())
161            .collect()
162    }
163
164    #[pyo3(signature = (depth=None))]
165    #[pyo3(name = "bids_to_dict")]
166    fn py_bids_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
167        self.bids_as_map(depth)
168    }
169
170    #[pyo3(signature = (depth=None))]
171    #[pyo3(name = "asks_to_dict")]
172    fn py_asks_to_dict(&self, depth: Option<usize>) -> IndexMap<Decimal, Decimal> {
173        self.asks_as_map(depth)
174    }
175
176    #[pyo3(signature = (group_size, depth=None))]
177    #[pyo3(name = "group_bids")]
178    pub fn py_group_bids(
179        &self,
180        group_size: Decimal,
181        depth: Option<usize>,
182    ) -> IndexMap<Decimal, Decimal> {
183        self.group_bids(group_size, depth)
184    }
185
186    #[pyo3(signature = (group_size, depth=None))]
187    #[pyo3(name = "group_asks")]
188    pub fn py_group_asks(
189        &self,
190        group_size: Decimal,
191        depth: Option<usize>,
192    ) -> IndexMap<Decimal, Decimal> {
193        self.group_asks(group_size, depth)
194    }
195
196    #[pyo3(name = "best_bid_price")]
197    fn py_best_bid_price(&self) -> Option<Price> {
198        self.best_bid_price()
199    }
200
201    #[pyo3(name = "best_ask_price")]
202    fn py_best_ask_price(&self) -> Option<Price> {
203        self.best_ask_price()
204    }
205
206    #[pyo3(name = "best_bid_size")]
207    fn py_best_bid_size(&self) -> Option<Quantity> {
208        self.best_bid_size()
209    }
210
211    #[pyo3(name = "best_ask_size")]
212    fn py_best_ask_size(&self) -> Option<Quantity> {
213        self.best_ask_size()
214    }
215
216    #[pyo3(name = "spread")]
217    fn py_spread(&self) -> Option<f64> {
218        self.spread()
219    }
220
221    #[pyo3(name = "midpoint")]
222    fn py_midpoint(&self) -> Option<f64> {
223        self.midpoint()
224    }
225
226    #[pyo3(name = "get_avg_px_for_quantity")]
227    fn py_get_avg_px_for_quantity(&self, qty: Quantity, order_side: OrderSide) -> f64 {
228        self.get_avg_px_for_quantity(qty, order_side)
229    }
230
231    #[pyo3(name = "get_avg_px_qty_for_exposure")]
232    fn py_get_avg_px_qty_for_exposure(
233        &self,
234        qty: Quantity,
235        order_side: OrderSide,
236    ) -> (f64, f64, f64) {
237        self.get_avg_px_qty_for_exposure(qty, order_side)
238    }
239
240    #[pyo3(name = "get_quantity_for_price")]
241    fn py_get_quantity_for_price(&self, price: Price, order_side: OrderSide) -> f64 {
242        self.get_quantity_for_price(price, order_side)
243    }
244
245    #[pyo3(name = "simulate_fills")]
246    fn py_simulate_fills(&self, order: &BookOrder) -> Vec<(Price, Quantity)> {
247        self.simulate_fills(order)
248    }
249
250    #[pyo3(name = "pprint")]
251    fn py_pprint(&self, num_levels: usize) -> String {
252        self.pprint(num_levels)
253    }
254}
255
256#[pyfunction()]
257#[pyo3(name = "update_book_with_quote_tick")]
258pub fn py_update_book_with_quote_tick(book: &mut OrderBook, quote: &QuoteTick) -> PyResult<()> {
259    book.update_quote_tick(quote).map_err(to_pyvalue_err)
260}
261
262#[pyfunction()]
263#[pyo3(name = "update_book_with_trade_tick")]
264pub fn py_update_book_with_trade_tick(book: &mut OrderBook, trade: &TradeTick) -> PyResult<()> {
265    book.update_trade_tick(trade).map_err(to_pyvalue_err)
266}