Skip to main content

nautilus_common/python/
cache.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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
16//! Python bindings for the [`Cache`] component.
17
18use std::{cell::RefCell, rc::Rc, sync::LazyLock};
19
20use bytes::Bytes;
21use nautilus_core::python::to_pyvalue_err;
22#[cfg(feature = "defi")]
23use nautilus_model::defi::{Pool, PoolProfiler};
24use nautilus_model::{
25    data::{
26        Bar, BarType, FundingRateUpdate, InstrumentClose, InstrumentStatus, QuoteTick, TradeTick,
27        YieldCurve,
28        prices::{IndexPriceUpdate, MarkPriceUpdate},
29    },
30    enums::{AggregationSource, OmsType, OrderSide, PositionSide, PriceType},
31    identifiers::{
32        AccountId, ClientId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
33        StrategyId, Venue, VenueOrderId,
34    },
35    instruments::SyntheticInstrument,
36    orderbook::{OrderBook, own::OwnOrderBook},
37    orders::OrderList,
38    position::Position,
39    python::{
40        account::account_any_to_pyobject,
41        instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
42        orders::{order_any_to_pyobject, pyobject_to_order_any},
43    },
44    types::{Currency, Money, Price, Quantity},
45};
46use pyo3::prelude::*;
47use rust_decimal::prelude::ToPrimitive;
48
49use crate::{
50    cache::{Cache, CacheConfig, database::CacheDatabaseFactory},
51    enums::SerializationEncoding,
52    python::{config_error_to_pyvalue_err, factory::FactoryRegistry},
53};
54
55/// Registry for Python cache database factory extractors.
56pub type CacheDatabaseFactoryRegistry = FactoryRegistry<dyn CacheDatabaseFactory>;
57
58static GLOBAL_CACHE_DATABASE_FACTORY_REGISTRY: LazyLock<CacheDatabaseFactoryRegistry> =
59    LazyLock::new(|| CacheDatabaseFactoryRegistry::new("cache database factory"));
60
61/// Returns the global Python cache database factory registry.
62#[must_use]
63pub fn get_global_cache_database_factory_registry() -> &'static CacheDatabaseFactoryRegistry {
64    &GLOBAL_CACHE_DATABASE_FACTORY_REGISTRY
65}
66
67/// Wrapper providing shared access to [`Cache`] from Python.
68///
69/// This wrapper holds an `Rc<RefCell<Cache>>` allowing actors to share
70/// the same cache instance. All methods delegate to the underlying cache.
71#[allow(non_camel_case_types)]
72#[pyo3::pyclass(
73    module = "nautilus_trader.common",
74    name = "Cache",
75    unsendable,
76    from_py_object
77)]
78#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")]
79#[derive(Debug, Clone)]
80pub struct PyCache(Rc<RefCell<Cache>>);
81
82impl PyCache {
83    /// Creates a `PyCache` from an `Rc<RefCell<Cache>>`.
84    #[must_use]
85    pub fn from_rc(rc: Rc<RefCell<Cache>>) -> Self {
86        Self(rc)
87    }
88
89    /// Gets the inner `Rc<RefCell<Cache>>` for use in Rust code.
90    #[must_use]
91    pub fn cache_rc(&self) -> Rc<RefCell<Cache>> {
92        self.0.clone()
93    }
94}
95
96#[pymethods]
97#[pyo3_stub_gen::derive::gen_stub_pymethods]
98impl PyCache {
99    #[new]
100    #[pyo3(signature = (config=None))]
101    fn py_new(config: Option<CacheConfig>) -> PyResult<Self> {
102        let cache = Cache::try_new(config, None).map_err(config_error_to_pyvalue_err)?;
103        Ok(Self(Rc::new(RefCell::new(cache))))
104    }
105
106    #[pyo3(name = "reset")]
107    fn py_reset(&mut self) {
108        self.0.borrow_mut().reset();
109    }
110
111    #[pyo3(name = "dispose")]
112    fn py_dispose(&mut self) {
113        self.0.borrow_mut().dispose();
114    }
115
116    #[pyo3(name = "purge_closed_orders", signature = (ts_now, buffer_secs=0))]
117    fn py_purge_closed_orders(&mut self, ts_now: u64, buffer_secs: u64) {
118        self.0
119            .borrow_mut()
120            .purge_closed_orders(ts_now.into(), buffer_secs);
121    }
122
123    #[pyo3(name = "purge_closed_positions", signature = (ts_now, buffer_secs=0))]
124    fn py_purge_closed_positions(&mut self, ts_now: u64, buffer_secs: u64) {
125        self.0
126            .borrow_mut()
127            .purge_closed_positions(ts_now.into(), buffer_secs);
128    }
129
130    #[pyo3(name = "purge_order")]
131    fn py_purge_order(&mut self, client_order_id: ClientOrderId) {
132        self.0.borrow_mut().purge_order(client_order_id);
133    }
134
135    #[pyo3(name = "purge_position")]
136    fn py_purge_position(&mut self, position_id: PositionId) {
137        self.0.borrow_mut().purge_position(position_id);
138    }
139
140    #[pyo3(name = "purge_instrument")]
141    fn py_purge_instrument(&mut self, instrument_id: InstrumentId) {
142        self.0.borrow_mut().purge_instrument(instrument_id);
143    }
144
145    #[pyo3(name = "purge_account_events", signature = (ts_now, lookback_secs=0))]
146    fn py_purge_account_events(&mut self, ts_now: u64, lookback_secs: u64) {
147        self.0
148            .borrow_mut()
149            .purge_account_events(ts_now.into(), lookback_secs);
150    }
151
152    #[pyo3(name = "get")]
153    fn py_get(&self, key: &str) -> PyResult<Option<Vec<u8>>> {
154        match self.0.borrow().get(key).map_err(to_pyvalue_err)? {
155            Some(bytes) => Ok(Some(bytes.to_vec())),
156            None => Ok(None),
157        }
158    }
159
160    #[pyo3(name = "add")]
161    fn py_add_general(&mut self, key: &str, value: Vec<u8>) -> PyResult<()> {
162        self.0
163            .borrow_mut()
164            .add(key, Bytes::from(value))
165            .map_err(to_pyvalue_err)
166    }
167
168    /// Adds an instrument close, replacing any close cached for the same instrument.
169    #[pyo3(name = "add_instrument_close")]
170    fn py_add_instrument_close(&mut self, close: InstrumentClose) -> PyResult<()> {
171        self.0
172            .borrow_mut()
173            .add_instrument_close(close)
174            .map_err(to_pyvalue_err)
175    }
176
177    #[pyo3(name = "add_yield_curve")]
178    fn py_add_yield_curve(&mut self, yield_curve: YieldCurve) -> PyResult<()> {
179        self.0
180            .borrow_mut()
181            .add_yield_curve(yield_curve)
182            .map_err(to_pyvalue_err)
183    }
184
185    /// Returns the quote at `index` for the `instrument_id`.
186    ///
187    /// Index 0 is the most recently added quote; index 1 is the previous insertion.
188    /// Entries are not sorted by timestamp. Returns `None` when no quote exists at the index.
189    #[pyo3(name = "quote", signature = (instrument_id, index=0))]
190    fn py_quote(&self, instrument_id: InstrumentId, index: usize) -> Option<QuoteTick> {
191        self.0
192            .borrow()
193            .quote_at_index(&instrument_id, index)
194            .copied()
195    }
196
197    /// Returns the trade at `index` for the `instrument_id`.
198    ///
199    /// Index 0 is the most recently added trade; index 1 is the previous insertion.
200    /// Entries are not sorted by timestamp. Returns `None` when no trade exists at the index.
201    #[pyo3(name = "trade", signature = (instrument_id, index=0))]
202    fn py_trade(&self, instrument_id: InstrumentId, index: usize) -> Option<TradeTick> {
203        self.0
204            .borrow()
205            .trade_at_index(&instrument_id, index)
206            .copied()
207    }
208
209    /// Returns the bar at `index` for the `bar_type`.
210    ///
211    /// Bars are ordered by `ts_event`, newest first. Index 0 is the most recent bar;
212    /// index 1 is the previous bar. Returns `None` when no bar exists at the index.
213    #[pyo3(name = "bar", signature = (bar_type, index=0))]
214    fn py_bar(&self, bar_type: BarType, index: usize) -> Option<Bar> {
215        self.0.borrow().bar_at_index(&bar_type, index).copied()
216    }
217
218    /// Returns cached quotes for the `instrument_id` in reverse insertion order.
219    ///
220    /// Index 0 is the most recently added quote. Entries are not sorted by timestamp.
221    /// Returns `None` when no quotes are cached for the instrument.
222    #[pyo3(name = "quotes")]
223    fn py_quotes(&self, instrument_id: InstrumentId) -> Option<Vec<QuoteTick>> {
224        self.0.borrow().quotes(&instrument_id)
225    }
226
227    /// Returns cached trades for the `instrument_id` in reverse insertion order.
228    ///
229    /// Index 0 is the most recently added trade. Entries are not sorted by timestamp.
230    /// Returns `None` when no trades are cached for the instrument.
231    #[pyo3(name = "trades")]
232    fn py_trades(&self, instrument_id: InstrumentId) -> Option<Vec<TradeTick>> {
233        self.0.borrow().trades(&instrument_id)
234    }
235
236    /// Returns cached bars for the `bar_type` in reverse chronological order by `ts_event`.
237    ///
238    /// Index 0 is the most recent bar. Returns `None` when no bars are cached for the bar type.
239    #[pyo3(name = "bars")]
240    fn py_bars(&self, bar_type: BarType) -> Option<Vec<Bar>> {
241        self.0.borrow().bars(&bar_type)
242    }
243
244    #[pyo3(name = "bar_types", signature = (aggregation_source, instrument_id=None, price_type=None))]
245    fn py_bar_types(
246        &self,
247        aggregation_source: AggregationSource,
248        instrument_id: Option<InstrumentId>,
249        price_type: Option<PriceType>,
250    ) -> Vec<BarType> {
251        self.0
252            .borrow()
253            .bar_types(
254                instrument_id.as_ref(),
255                price_type.as_ref(),
256                aggregation_source,
257            )
258            .into_iter()
259            .copied()
260            .collect()
261    }
262
263    /// Returns the most recently added mark price update for the `instrument_id`, or `None` if absent.
264    #[pyo3(name = "mark_price")]
265    fn py_mark_price(&self, instrument_id: InstrumentId) -> Option<MarkPriceUpdate> {
266        self.0.borrow().mark_price(&instrument_id).copied()
267    }
268
269    /// Returns cached mark price updates for the `instrument_id` in reverse insertion order.
270    ///
271    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
272    /// Returns `None` when no mark price updates are cached for the instrument.
273    #[pyo3(name = "mark_prices")]
274    fn py_mark_prices(&self, instrument_id: InstrumentId) -> Option<Vec<MarkPriceUpdate>> {
275        self.0.borrow().mark_prices(&instrument_id)
276    }
277
278    /// Returns the most recently added index price update for the `instrument_id`, or `None` if absent.
279    #[pyo3(name = "index_price")]
280    fn py_index_price(&self, instrument_id: InstrumentId) -> Option<IndexPriceUpdate> {
281        self.0.borrow().index_price(&instrument_id).copied()
282    }
283
284    /// Returns cached index price updates for the `instrument_id` in reverse insertion order.
285    ///
286    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
287    /// Returns `None` when no index price updates are cached for the instrument.
288    #[pyo3(name = "index_prices")]
289    fn py_index_prices(&self, instrument_id: InstrumentId) -> Option<Vec<IndexPriceUpdate>> {
290        self.0.borrow().index_prices(&instrument_id)
291    }
292
293    /// Returns the most recently added funding rate update for the `instrument_id`, or `None` if absent.
294    #[pyo3(name = "funding_rate")]
295    fn py_funding_rate(&self, instrument_id: InstrumentId) -> Option<FundingRateUpdate> {
296        self.0.borrow().funding_rate(&instrument_id).copied()
297    }
298
299    /// Returns cached funding rate updates for the `instrument_id` in reverse insertion order.
300    ///
301    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
302    /// Returns `None` when no funding rate updates are cached for the instrument.
303    #[pyo3(name = "funding_rates")]
304    fn py_funding_rates(&self, instrument_id: InstrumentId) -> Option<Vec<FundingRateUpdate>> {
305        self.0.borrow().funding_rates(&instrument_id)
306    }
307
308    /// Returns the most recently added instrument status for the `instrument_id`, or `None` if absent.
309    #[pyo3(name = "instrument_status")]
310    fn py_instrument_status(&self, instrument_id: InstrumentId) -> Option<InstrumentStatus> {
311        self.0.borrow().instrument_status(&instrument_id).copied()
312    }
313
314    /// Returns cached instrument status updates for the `instrument_id` in reverse insertion order.
315    ///
316    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
317    /// Returns `None` when no instrument status updates are cached for the instrument.
318    #[pyo3(name = "instrument_statuses")]
319    fn py_instrument_statuses(&self, instrument_id: InstrumentId) -> Option<Vec<InstrumentStatus>> {
320        self.0.borrow().instrument_statuses(&instrument_id)
321    }
322
323    /// Returns the close cached for `instrument_id`, if present.
324    ///
325    /// A later insertion replaces the cached close for the instrument; no history is kept.
326    #[pyo3(name = "instrument_close")]
327    fn py_instrument_close(&self, instrument_id: InstrumentId) -> Option<InstrumentClose> {
328        self.0.borrow().instrument_close(&instrument_id).copied()
329    }
330
331    #[pyo3(name = "price")]
332    fn py_price(&self, instrument_id: InstrumentId, price_type: PriceType) -> Option<Price> {
333        self.0.borrow().price(&instrument_id, price_type)
334    }
335
336    /// Returns the current order book for the `instrument_id`, or `None` if absent.
337    ///
338    /// The cache holds the current book state, not a history of book updates.
339    #[pyo3(name = "order_book")]
340    fn py_order_book(&self, instrument_id: InstrumentId) -> Option<OrderBook> {
341        self.0.borrow().order_book(&instrument_id).cloned()
342    }
343
344    /// Returns the best bid/ask price and size for the `instrument_id`, without cloning the
345    /// resident order book.
346    ///
347    /// Returns `(bid_price, bid_size, ask_price, ask_size)`, or `None` if the book is
348    /// missing, empty, or one-sided.
349    ///
350    /// For L3 books, each size is the first order's size at the best level, not the
351    /// aggregate level size, consistent with the order book's best-size getters.
352    ///
353    /// Prefer this over `order_book()` in hot paths that only need top-of-book values, since
354    /// `order_book()` clones the full book and its cost scales with depth.
355    #[pyo3(name = "top_of_book")]
356    fn py_top_of_book(
357        &self,
358        instrument_id: InstrumentId,
359    ) -> Option<(Price, Quantity, Price, Quantity)> {
360        let cache = self.0.borrow();
361        let book = cache.order_book(&instrument_id)?;
362        Some((
363            book.best_bid_price()?,
364            book.best_bid_size()?,
365            book.best_ask_price()?,
366            book.best_ask_size()?,
367        ))
368    }
369
370    #[pyo3(name = "has_order_book")]
371    fn py_has_order_book(&self, instrument_id: InstrumentId) -> bool {
372        self.0.borrow().has_order_book(&instrument_id)
373    }
374
375    #[pyo3(name = "book_update_count")]
376    fn py_book_update_count(&self, instrument_id: InstrumentId) -> usize {
377        self.0.borrow().book_update_count(&instrument_id)
378    }
379
380    #[pyo3(name = "has_quote_ticks")]
381    fn py_has_quote_ticks(&self, instrument_id: InstrumentId) -> bool {
382        self.0.borrow().has_quote_ticks(&instrument_id)
383    }
384
385    #[pyo3(name = "has_trade_ticks")]
386    fn py_has_trade_ticks(&self, instrument_id: InstrumentId) -> bool {
387        self.0.borrow().has_trade_ticks(&instrument_id)
388    }
389
390    #[pyo3(name = "has_mark_prices")]
391    fn py_has_mark_prices(&self, instrument_id: InstrumentId) -> bool {
392        self.0.borrow().has_mark_prices(&instrument_id)
393    }
394
395    #[pyo3(name = "has_index_prices")]
396    fn py_has_index_prices(&self, instrument_id: InstrumentId) -> bool {
397        self.0.borrow().has_index_prices(&instrument_id)
398    }
399
400    #[pyo3(name = "has_funding_rates")]
401    fn py_has_funding_rates(&self, instrument_id: InstrumentId) -> bool {
402        self.0.borrow().has_funding_rates(&instrument_id)
403    }
404
405    #[pyo3(name = "has_instrument_statuses")]
406    fn py_has_instrument_statuses(&self, instrument_id: InstrumentId) -> bool {
407        self.0.borrow().has_instrument_statuses(&instrument_id)
408    }
409
410    #[pyo3(name = "has_instrument_close")]
411    fn py_has_instrument_close(&self, instrument_id: InstrumentId) -> bool {
412        self.0.borrow().has_instrument_close(&instrument_id)
413    }
414
415    #[pyo3(name = "has_bars")]
416    fn py_has_bars(&self, bar_type: BarType) -> bool {
417        self.0.borrow().has_bars(&bar_type)
418    }
419
420    #[pyo3(name = "quote_count")]
421    fn py_quote_count(&self, instrument_id: InstrumentId) -> usize {
422        self.0.borrow().quote_count(&instrument_id)
423    }
424
425    #[pyo3(name = "trade_count")]
426    fn py_trade_count(&self, instrument_id: InstrumentId) -> usize {
427        self.0.borrow().trade_count(&instrument_id)
428    }
429
430    #[pyo3(name = "mark_price_count")]
431    fn py_mark_price_count(&self, instrument_id: InstrumentId) -> usize {
432        self.0.borrow().mark_price_count(&instrument_id)
433    }
434
435    #[pyo3(name = "index_price_count")]
436    fn py_index_price_count(&self, instrument_id: InstrumentId) -> usize {
437        self.0.borrow().index_price_count(&instrument_id)
438    }
439
440    #[pyo3(name = "funding_rate_count")]
441    fn py_funding_rate_count(&self, instrument_id: InstrumentId) -> usize {
442        self.0.borrow().funding_rate_count(&instrument_id)
443    }
444
445    #[pyo3(name = "instrument_status_count")]
446    fn py_instrument_status_count(&self, instrument_id: InstrumentId) -> usize {
447        self.0.borrow().instrument_status_count(&instrument_id)
448    }
449
450    #[pyo3(name = "bar_count")]
451    fn py_bar_count(&self, bar_type: BarType) -> usize {
452        self.0.borrow().bar_count(&bar_type)
453    }
454
455    #[pyo3(name = "get_xrate")]
456    fn py_get_xrate(
457        &self,
458        venue: Venue,
459        from_currency: Currency,
460        to_currency: Currency,
461        price_type: PriceType,
462    ) -> Option<f64> {
463        self.0
464            .borrow()
465            .get_xrate(venue, from_currency, to_currency, price_type)
466            .and_then(|rate| rate.to_f64())
467    }
468
469    #[pyo3(name = "get_mark_xrate")]
470    fn py_get_mark_xrate(&self, from_currency: Currency, to_currency: Currency) -> Option<f64> {
471        self.0.borrow().get_mark_xrate(from_currency, to_currency)
472    }
473
474    #[pyo3(name = "own_order_book")]
475    fn py_own_order_book(&self, instrument_id: InstrumentId) -> Option<OwnOrderBook> {
476        self.0.borrow().own_order_book(&instrument_id).cloned()
477    }
478
479    #[pyo3(name = "instrument")]
480    fn py_instrument(
481        &self,
482        py: Python,
483        instrument_id: InstrumentId,
484    ) -> PyResult<Option<Py<PyAny>>> {
485        let cache = self.0.borrow();
486        match cache.instrument(&instrument_id) {
487            Some(instrument) => Ok(Some(instrument_any_to_pyobject(py, instrument.clone())?)),
488            None => Ok(None),
489        }
490    }
491
492    #[pyo3(name = "instrument_ids", signature = (venue=None))]
493    fn py_instrument_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
494        self.0
495            .borrow()
496            .instrument_ids(venue.as_ref())
497            .into_iter()
498            .copied()
499            .collect()
500    }
501
502    #[pyo3(name = "instruments", signature = (venue=None))]
503    fn py_instruments(&self, py: Python, venue: Option<Venue>) -> PyResult<Vec<Py<PyAny>>> {
504        let cache = self.0.borrow();
505        let mut py_instruments = Vec::new();
506
507        match venue {
508            Some(venue) => {
509                for instrument in cache.instruments(&venue, None) {
510                    py_instruments.push(instrument_any_to_pyobject(py, (*instrument).clone())?);
511                }
512            }
513            None => {
514                for instrument_id in cache.instrument_ids(None) {
515                    if let Some(instrument) = cache.instrument(instrument_id) {
516                        py_instruments.push(instrument_any_to_pyobject(py, instrument.clone())?);
517                    }
518                }
519            }
520        }
521        Ok(py_instruments)
522    }
523
524    #[pyo3(name = "synthetic")]
525    fn py_synthetic(&self, instrument_id: InstrumentId) -> Option<SyntheticInstrument> {
526        self.0.borrow().synthetic(&instrument_id).cloned()
527    }
528
529    #[pyo3(name = "synthetic_ids")]
530    fn py_synthetic_ids(&self) -> Vec<InstrumentId> {
531        self.0
532            .borrow()
533            .synthetic_ids()
534            .into_iter()
535            .copied()
536            .collect()
537    }
538
539    #[pyo3(name = "account")]
540    fn py_account(&self, py: Python, account_id: AccountId) -> PyResult<Option<Py<PyAny>>> {
541        let cache = self.0.borrow();
542        match cache.account(&account_id) {
543            Some(account) => Ok(Some(account_any_to_pyobject(py, account.clone())?)),
544            None => Ok(None),
545        }
546    }
547
548    #[pyo3(name = "account_for_venue")]
549    fn py_account_for_venue(&self, py: Python, venue: Venue) -> PyResult<Option<Py<PyAny>>> {
550        let cache = self.0.borrow();
551        match cache.account_for_venue(&venue) {
552            Some(account) => Ok(Some(account_any_to_pyobject(py, account.clone())?)),
553            None => Ok(None),
554        }
555    }
556
557    #[pyo3(name = "account_id")]
558    fn py_account_id(&self, venue: Venue) -> Option<AccountId> {
559        self.0.borrow().account_id(&venue).copied()
560    }
561
562    #[pyo3(name = "client_order_ids", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
563    fn py_client_order_ids(
564        &self,
565        venue: Option<Venue>,
566        instrument_id: Option<InstrumentId>,
567        strategy_id: Option<StrategyId>,
568        account_id: Option<AccountId>,
569    ) -> Vec<ClientOrderId> {
570        self.0
571            .borrow()
572            .client_order_ids(
573                venue.as_ref(),
574                instrument_id.as_ref(),
575                strategy_id.as_ref(),
576                account_id.as_ref(),
577            )
578            .into_iter()
579            .collect()
580    }
581
582    #[pyo3(name = "client_order_ids_open", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
583    fn py_client_order_ids_open(
584        &self,
585        venue: Option<Venue>,
586        instrument_id: Option<InstrumentId>,
587        strategy_id: Option<StrategyId>,
588        account_id: Option<AccountId>,
589    ) -> Vec<ClientOrderId> {
590        self.0
591            .borrow()
592            .client_order_ids_open(
593                venue.as_ref(),
594                instrument_id.as_ref(),
595                strategy_id.as_ref(),
596                account_id.as_ref(),
597            )
598            .into_iter()
599            .collect()
600    }
601
602    #[pyo3(name = "client_order_ids_closed", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
603    fn py_client_order_ids_closed(
604        &self,
605        venue: Option<Venue>,
606        instrument_id: Option<InstrumentId>,
607        strategy_id: Option<StrategyId>,
608        account_id: Option<AccountId>,
609    ) -> Vec<ClientOrderId> {
610        self.0
611            .borrow()
612            .client_order_ids_closed(
613                venue.as_ref(),
614                instrument_id.as_ref(),
615                strategy_id.as_ref(),
616                account_id.as_ref(),
617            )
618            .into_iter()
619            .collect()
620    }
621
622    #[pyo3(name = "client_order_ids_emulated", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
623    fn py_client_order_ids_emulated(
624        &self,
625        venue: Option<Venue>,
626        instrument_id: Option<InstrumentId>,
627        strategy_id: Option<StrategyId>,
628        account_id: Option<AccountId>,
629    ) -> Vec<ClientOrderId> {
630        self.0
631            .borrow()
632            .client_order_ids_emulated(
633                venue.as_ref(),
634                instrument_id.as_ref(),
635                strategy_id.as_ref(),
636                account_id.as_ref(),
637            )
638            .into_iter()
639            .collect()
640    }
641
642    #[pyo3(name = "client_order_ids_inflight", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
643    fn py_client_order_ids_inflight(
644        &self,
645        venue: Option<Venue>,
646        instrument_id: Option<InstrumentId>,
647        strategy_id: Option<StrategyId>,
648        account_id: Option<AccountId>,
649    ) -> Vec<ClientOrderId> {
650        self.0
651            .borrow()
652            .client_order_ids_inflight(
653                venue.as_ref(),
654                instrument_id.as_ref(),
655                strategy_id.as_ref(),
656                account_id.as_ref(),
657            )
658            .into_iter()
659            .collect()
660    }
661
662    #[pyo3(name = "position_ids", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
663    fn py_position_ids(
664        &self,
665        venue: Option<Venue>,
666        instrument_id: Option<InstrumentId>,
667        strategy_id: Option<StrategyId>,
668        account_id: Option<AccountId>,
669    ) -> Vec<PositionId> {
670        self.0
671            .borrow()
672            .position_ids(
673                venue.as_ref(),
674                instrument_id.as_ref(),
675                strategy_id.as_ref(),
676                account_id.as_ref(),
677            )
678            .into_iter()
679            .collect()
680    }
681
682    #[pyo3(name = "position_open_ids", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
683    fn py_position_open_ids(
684        &self,
685        venue: Option<Venue>,
686        instrument_id: Option<InstrumentId>,
687        strategy_id: Option<StrategyId>,
688        account_id: Option<AccountId>,
689    ) -> Vec<PositionId> {
690        self.0
691            .borrow()
692            .position_open_ids(
693                venue.as_ref(),
694                instrument_id.as_ref(),
695                strategy_id.as_ref(),
696                account_id.as_ref(),
697            )
698            .into_iter()
699            .collect()
700    }
701
702    #[pyo3(name = "position_closed_ids", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
703    fn py_position_closed_ids(
704        &self,
705        venue: Option<Venue>,
706        instrument_id: Option<InstrumentId>,
707        strategy_id: Option<StrategyId>,
708        account_id: Option<AccountId>,
709    ) -> Vec<PositionId> {
710        self.0
711            .borrow()
712            .position_closed_ids(
713                venue.as_ref(),
714                instrument_id.as_ref(),
715                strategy_id.as_ref(),
716                account_id.as_ref(),
717            )
718            .into_iter()
719            .collect()
720    }
721
722    #[pyo3(name = "strategy_ids")]
723    fn py_strategy_ids(&self) -> Vec<StrategyId> {
724        self.0.borrow().strategy_ids().into_iter().collect()
725    }
726
727    #[pyo3(name = "exec_algorithm_ids")]
728    fn py_exec_algorithm_ids(&self) -> Vec<ExecAlgorithmId> {
729        self.0.borrow().exec_algorithm_ids().into_iter().collect()
730    }
731
732    #[pyo3(name = "order")]
733    fn py_order(&self, py: Python, client_order_id: ClientOrderId) -> PyResult<Option<Py<PyAny>>> {
734        let cache = self.0.borrow();
735        match cache.order(&client_order_id) {
736            Some(order) => Ok(Some(order_any_to_pyobject(py, order.clone())?)),
737            None => Ok(None),
738        }
739    }
740
741    #[pyo3(name = "client_order_id")]
742    fn py_client_order_id(&self, venue_order_id: VenueOrderId) -> Option<ClientOrderId> {
743        self.0.borrow().client_order_id(&venue_order_id).copied()
744    }
745
746    #[pyo3(name = "venue_order_id")]
747    fn py_venue_order_id(&self, client_order_id: ClientOrderId) -> Option<VenueOrderId> {
748        self.0.borrow().venue_order_id(&client_order_id).copied()
749    }
750
751    #[pyo3(name = "client_id")]
752    fn py_client_id(&self, client_order_id: ClientOrderId) -> Option<ClientId> {
753        self.0.borrow().client_id(&client_order_id).copied()
754    }
755
756    #[pyo3(name = "orders", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
757    fn py_orders(
758        &self,
759        py: Python,
760        venue: Option<Venue>,
761        instrument_id: Option<InstrumentId>,
762        strategy_id: Option<StrategyId>,
763        account_id: Option<AccountId>,
764        side: Option<OrderSide>,
765    ) -> PyResult<Vec<Py<PyAny>>> {
766        let cache = self.0.borrow();
767        cache
768            .orders(
769                venue.as_ref(),
770                instrument_id.as_ref(),
771                strategy_id.as_ref(),
772                account_id.as_ref(),
773                side,
774            )
775            .into_iter()
776            .map(|o| order_any_to_pyobject(py, o.clone()))
777            .collect()
778    }
779
780    #[pyo3(name = "orders_open", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
781    fn py_orders_open(
782        &self,
783        py: Python,
784        venue: Option<Venue>,
785        instrument_id: Option<InstrumentId>,
786        strategy_id: Option<StrategyId>,
787        account_id: Option<AccountId>,
788        side: Option<OrderSide>,
789    ) -> PyResult<Vec<Py<PyAny>>> {
790        let cache = self.0.borrow();
791        cache
792            .orders_open(
793                venue.as_ref(),
794                instrument_id.as_ref(),
795                strategy_id.as_ref(),
796                account_id.as_ref(),
797                side,
798            )
799            .into_iter()
800            .map(|o| order_any_to_pyobject(py, o.clone()))
801            .collect()
802    }
803
804    #[pyo3(name = "orders_closed", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
805    fn py_orders_closed(
806        &self,
807        py: Python,
808        venue: Option<Venue>,
809        instrument_id: Option<InstrumentId>,
810        strategy_id: Option<StrategyId>,
811        account_id: Option<AccountId>,
812        side: Option<OrderSide>,
813    ) -> PyResult<Vec<Py<PyAny>>> {
814        let cache = self.0.borrow();
815        cache
816            .orders_closed(
817                venue.as_ref(),
818                instrument_id.as_ref(),
819                strategy_id.as_ref(),
820                account_id.as_ref(),
821                side,
822            )
823            .into_iter()
824            .map(|o| order_any_to_pyobject(py, o.clone()))
825            .collect()
826    }
827
828    #[pyo3(name = "orders_emulated", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
829    fn py_orders_emulated(
830        &self,
831        py: Python,
832        venue: Option<Venue>,
833        instrument_id: Option<InstrumentId>,
834        strategy_id: Option<StrategyId>,
835        account_id: Option<AccountId>,
836        side: Option<OrderSide>,
837    ) -> PyResult<Vec<Py<PyAny>>> {
838        let cache = self.0.borrow();
839        cache
840            .orders_emulated(
841                venue.as_ref(),
842                instrument_id.as_ref(),
843                strategy_id.as_ref(),
844                account_id.as_ref(),
845                side,
846            )
847            .into_iter()
848            .map(|o| order_any_to_pyobject(py, o.clone()))
849            .collect()
850    }
851
852    #[pyo3(name = "orders_inflight", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
853    fn py_orders_inflight(
854        &self,
855        py: Python,
856        venue: Option<Venue>,
857        instrument_id: Option<InstrumentId>,
858        strategy_id: Option<StrategyId>,
859        account_id: Option<AccountId>,
860        side: Option<OrderSide>,
861    ) -> PyResult<Vec<Py<PyAny>>> {
862        let cache = self.0.borrow();
863        cache
864            .orders_inflight(
865                venue.as_ref(),
866                instrument_id.as_ref(),
867                strategy_id.as_ref(),
868                account_id.as_ref(),
869                side,
870            )
871            .into_iter()
872            .map(|o| order_any_to_pyobject(py, o.clone()))
873            .collect()
874    }
875
876    #[pyo3(name = "orders_for_position")]
877    fn py_orders_for_position(
878        &self,
879        py: Python,
880        position_id: PositionId,
881    ) -> PyResult<Vec<Py<PyAny>>> {
882        let cache = self.0.borrow();
883        cache
884            .orders_for_position(&position_id)
885            .into_iter()
886            .map(|o| order_any_to_pyobject(py, o.clone()))
887            .collect()
888    }
889
890    #[pyo3(name = "order_exists")]
891    fn py_order_exists(&self, client_order_id: ClientOrderId) -> bool {
892        self.0.borrow().order_exists(&client_order_id)
893    }
894
895    #[pyo3(name = "is_order_open")]
896    fn py_is_order_open(&self, client_order_id: ClientOrderId) -> bool {
897        self.0.borrow().is_order_open(&client_order_id)
898    }
899
900    #[pyo3(name = "is_order_closed")]
901    fn py_is_order_closed(&self, client_order_id: ClientOrderId) -> bool {
902        self.0.borrow().is_order_closed(&client_order_id)
903    }
904
905    #[pyo3(name = "is_order_emulated")]
906    fn py_is_order_emulated(&self, client_order_id: ClientOrderId) -> bool {
907        self.0.borrow().is_order_emulated(&client_order_id)
908    }
909
910    #[pyo3(name = "is_order_inflight")]
911    fn py_is_order_inflight(&self, client_order_id: ClientOrderId) -> bool {
912        self.0.borrow().is_order_inflight(&client_order_id)
913    }
914
915    #[pyo3(name = "is_order_pending_cancel_local")]
916    fn py_is_order_pending_cancel_local(&self, client_order_id: ClientOrderId) -> bool {
917        self.0
918            .borrow()
919            .is_order_pending_cancel_local(&client_order_id)
920    }
921
922    #[pyo3(name = "orders_open_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
923    fn py_orders_open_count(
924        &self,
925        venue: Option<Venue>,
926        instrument_id: Option<InstrumentId>,
927        strategy_id: Option<StrategyId>,
928        account_id: Option<AccountId>,
929        side: Option<OrderSide>,
930    ) -> usize {
931        self.0.borrow().orders_open_count(
932            venue.as_ref(),
933            instrument_id.as_ref(),
934            strategy_id.as_ref(),
935            account_id.as_ref(),
936            side,
937        )
938    }
939
940    #[pyo3(name = "orders_closed_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
941    fn py_orders_closed_count(
942        &self,
943        venue: Option<Venue>,
944        instrument_id: Option<InstrumentId>,
945        strategy_id: Option<StrategyId>,
946        account_id: Option<AccountId>,
947        side: Option<OrderSide>,
948    ) -> usize {
949        self.0.borrow().orders_closed_count(
950            venue.as_ref(),
951            instrument_id.as_ref(),
952            strategy_id.as_ref(),
953            account_id.as_ref(),
954            side,
955        )
956    }
957
958    #[pyo3(name = "orders_emulated_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
959    fn py_orders_emulated_count(
960        &self,
961        venue: Option<Venue>,
962        instrument_id: Option<InstrumentId>,
963        strategy_id: Option<StrategyId>,
964        account_id: Option<AccountId>,
965        side: Option<OrderSide>,
966    ) -> usize {
967        self.0.borrow().orders_emulated_count(
968            venue.as_ref(),
969            instrument_id.as_ref(),
970            strategy_id.as_ref(),
971            account_id.as_ref(),
972            side,
973        )
974    }
975
976    #[pyo3(name = "orders_inflight_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
977    fn py_orders_inflight_count(
978        &self,
979        venue: Option<Venue>,
980        instrument_id: Option<InstrumentId>,
981        strategy_id: Option<StrategyId>,
982        account_id: Option<AccountId>,
983        side: Option<OrderSide>,
984    ) -> usize {
985        self.0.borrow().orders_inflight_count(
986            venue.as_ref(),
987            instrument_id.as_ref(),
988            strategy_id.as_ref(),
989            account_id.as_ref(),
990            side,
991        )
992    }
993
994    #[pyo3(name = "orders_total_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
995    fn py_orders_total_count(
996        &self,
997        venue: Option<Venue>,
998        instrument_id: Option<InstrumentId>,
999        strategy_id: Option<StrategyId>,
1000        account_id: Option<AccountId>,
1001        side: Option<OrderSide>,
1002    ) -> usize {
1003        self.0.borrow().orders_total_count(
1004            venue.as_ref(),
1005            instrument_id.as_ref(),
1006            strategy_id.as_ref(),
1007            account_id.as_ref(),
1008            side,
1009        )
1010    }
1011
1012    #[pyo3(name = "order_list")]
1013    fn py_order_list(&self, order_list_id: OrderListId) -> Option<OrderList> {
1014        self.0.borrow().order_list(&order_list_id).cloned()
1015    }
1016
1017    #[pyo3(name = "order_lists", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None))]
1018    fn py_order_lists(
1019        &self,
1020        venue: Option<Venue>,
1021        instrument_id: Option<InstrumentId>,
1022        strategy_id: Option<StrategyId>,
1023        account_id: Option<AccountId>,
1024    ) -> Vec<OrderList> {
1025        let cache = self.0.borrow();
1026        cache
1027            .order_lists(
1028                venue.as_ref(),
1029                instrument_id.as_ref(),
1030                strategy_id.as_ref(),
1031                account_id.as_ref(),
1032            )
1033            .into_iter()
1034            .cloned()
1035            .collect()
1036    }
1037
1038    #[pyo3(name = "order_list_exists")]
1039    fn py_order_list_exists(&self, order_list_id: OrderListId) -> bool {
1040        self.0.borrow().order_list_exists(&order_list_id)
1041    }
1042
1043    #[pyo3(name = "orders_for_exec_algorithm", signature = (exec_algorithm_id, venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
1044    #[expect(clippy::too_many_arguments)]
1045    fn py_orders_for_exec_algorithm(
1046        &self,
1047        py: Python,
1048        exec_algorithm_id: ExecAlgorithmId,
1049        venue: Option<Venue>,
1050        instrument_id: Option<InstrumentId>,
1051        strategy_id: Option<StrategyId>,
1052        account_id: Option<AccountId>,
1053        side: Option<OrderSide>,
1054    ) -> PyResult<Vec<Py<PyAny>>> {
1055        let cache = self.0.borrow();
1056        cache
1057            .orders_for_exec_algorithm(
1058                &exec_algorithm_id,
1059                venue.as_ref(),
1060                instrument_id.as_ref(),
1061                strategy_id.as_ref(),
1062                account_id.as_ref(),
1063                side,
1064            )
1065            .into_iter()
1066            .map(|o| order_any_to_pyobject(py, o.clone()))
1067            .collect()
1068    }
1069
1070    #[pyo3(name = "orders_for_exec_spawn")]
1071    fn py_orders_for_exec_spawn(
1072        &self,
1073        py: Python,
1074        exec_spawn_id: ClientOrderId,
1075    ) -> PyResult<Vec<Py<PyAny>>> {
1076        let cache = self.0.borrow();
1077        cache
1078            .orders_for_exec_spawn(&exec_spawn_id)
1079            .into_iter()
1080            .map(|o| order_any_to_pyobject(py, o.clone()))
1081            .collect()
1082    }
1083
1084    #[pyo3(name = "exec_spawn_total_quantity")]
1085    fn py_exec_spawn_total_quantity(
1086        &self,
1087        exec_spawn_id: ClientOrderId,
1088        active_only: bool,
1089    ) -> Option<Quantity> {
1090        self.0
1091            .borrow()
1092            .exec_spawn_total_quantity(&exec_spawn_id, active_only)
1093    }
1094
1095    #[pyo3(name = "exec_spawn_total_filled_qty")]
1096    fn py_exec_spawn_total_filled_qty(
1097        &self,
1098        exec_spawn_id: ClientOrderId,
1099        active_only: bool,
1100    ) -> Option<Quantity> {
1101        self.0
1102            .borrow()
1103            .exec_spawn_total_filled_qty(&exec_spawn_id, active_only)
1104    }
1105
1106    #[pyo3(name = "exec_spawn_total_leaves_qty")]
1107    fn py_exec_spawn_total_leaves_qty(
1108        &self,
1109        exec_spawn_id: ClientOrderId,
1110        active_only: bool,
1111    ) -> Option<Quantity> {
1112        self.0
1113            .borrow()
1114            .exec_spawn_total_leaves_qty(&exec_spawn_id, active_only)
1115    }
1116
1117    #[pyo3(name = "position")]
1118    fn py_position(&self, py: Python, position_id: PositionId) -> PyResult<Option<Py<PyAny>>> {
1119        let cache = self.0.borrow();
1120        match cache.position(&position_id) {
1121            Some(position) => Ok(Some(position.clone().into_pyobject(py)?.into())),
1122            None => Ok(None),
1123        }
1124    }
1125
1126    #[pyo3(name = "position_for_order")]
1127    fn py_position_for_order(
1128        &self,
1129        py: Python,
1130        client_order_id: ClientOrderId,
1131    ) -> PyResult<Option<Py<PyAny>>> {
1132        let cache = self.0.borrow();
1133        match cache.position_for_order(&client_order_id) {
1134            Some(position) => Ok(Some(position.clone().into_pyobject(py)?.into())),
1135            None => Ok(None),
1136        }
1137    }
1138
1139    #[pyo3(name = "position_id")]
1140    fn py_position_id(&self, client_order_id: ClientOrderId) -> Option<PositionId> {
1141        self.0.borrow().position_id(&client_order_id).copied()
1142    }
1143
1144    #[pyo3(name = "positions", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
1145    fn py_positions(
1146        &self,
1147        py: Python,
1148        venue: Option<Venue>,
1149        instrument_id: Option<InstrumentId>,
1150        strategy_id: Option<StrategyId>,
1151        account_id: Option<AccountId>,
1152        side: Option<PositionSide>,
1153    ) -> PyResult<Vec<Py<PyAny>>> {
1154        let cache = self.0.borrow();
1155        cache
1156            .positions(
1157                venue.as_ref(),
1158                instrument_id.as_ref(),
1159                strategy_id.as_ref(),
1160                account_id.as_ref(),
1161                side,
1162            )
1163            .into_iter()
1164            .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
1165            .collect()
1166    }
1167
1168    #[pyo3(name = "positions_open", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
1169    fn py_positions_open(
1170        &self,
1171        py: Python,
1172        venue: Option<Venue>,
1173        instrument_id: Option<InstrumentId>,
1174        strategy_id: Option<StrategyId>,
1175        account_id: Option<AccountId>,
1176        side: Option<PositionSide>,
1177    ) -> PyResult<Vec<Py<PyAny>>> {
1178        let cache = self.0.borrow();
1179        cache
1180            .positions_open(
1181                venue.as_ref(),
1182                instrument_id.as_ref(),
1183                strategy_id.as_ref(),
1184                account_id.as_ref(),
1185                side,
1186            )
1187            .into_iter()
1188            .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
1189            .collect()
1190    }
1191
1192    #[pyo3(name = "positions_closed", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
1193    fn py_positions_closed(
1194        &self,
1195        py: Python,
1196        venue: Option<Venue>,
1197        instrument_id: Option<InstrumentId>,
1198        strategy_id: Option<StrategyId>,
1199        account_id: Option<AccountId>,
1200        side: Option<PositionSide>,
1201    ) -> PyResult<Vec<Py<PyAny>>> {
1202        let cache = self.0.borrow();
1203        cache
1204            .positions_closed(
1205                venue.as_ref(),
1206                instrument_id.as_ref(),
1207                strategy_id.as_ref(),
1208                account_id.as_ref(),
1209                side,
1210            )
1211            .into_iter()
1212            .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
1213            .collect()
1214    }
1215
1216    #[pyo3(name = "position_exists")]
1217    fn py_position_exists(&self, position_id: PositionId) -> bool {
1218        self.0.borrow().position_exists(&position_id)
1219    }
1220
1221    #[pyo3(name = "is_position_open")]
1222    fn py_is_position_open(&self, position_id: PositionId) -> bool {
1223        self.0.borrow().is_position_open(&position_id)
1224    }
1225
1226    #[pyo3(name = "is_position_closed")]
1227    fn py_is_position_closed(&self, position_id: PositionId) -> bool {
1228        self.0.borrow().is_position_closed(&position_id)
1229    }
1230
1231    #[pyo3(name = "positions_open_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
1232    fn py_positions_open_count(
1233        &self,
1234        venue: Option<Venue>,
1235        instrument_id: Option<InstrumentId>,
1236        strategy_id: Option<StrategyId>,
1237        account_id: Option<AccountId>,
1238        side: Option<PositionSide>,
1239    ) -> usize {
1240        self.0.borrow().positions_open_count(
1241            venue.as_ref(),
1242            instrument_id.as_ref(),
1243            strategy_id.as_ref(),
1244            account_id.as_ref(),
1245            side,
1246        )
1247    }
1248
1249    #[pyo3(name = "positions_closed_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
1250    fn py_positions_closed_count(
1251        &self,
1252        venue: Option<Venue>,
1253        instrument_id: Option<InstrumentId>,
1254        strategy_id: Option<StrategyId>,
1255        account_id: Option<AccountId>,
1256        side: Option<PositionSide>,
1257    ) -> usize {
1258        self.0.borrow().positions_closed_count(
1259            venue.as_ref(),
1260            instrument_id.as_ref(),
1261            strategy_id.as_ref(),
1262            account_id.as_ref(),
1263            side,
1264        )
1265    }
1266
1267    #[pyo3(name = "positions_total_count", signature = (venue=None, instrument_id=None, strategy_id=None, account_id=None, side=None))]
1268    fn py_positions_total_count(
1269        &self,
1270        venue: Option<Venue>,
1271        instrument_id: Option<InstrumentId>,
1272        strategy_id: Option<StrategyId>,
1273        account_id: Option<AccountId>,
1274        side: Option<PositionSide>,
1275    ) -> usize {
1276        self.0.borrow().positions_total_count(
1277            venue.as_ref(),
1278            instrument_id.as_ref(),
1279            strategy_id.as_ref(),
1280            account_id.as_ref(),
1281            side,
1282        )
1283    }
1284
1285    #[pyo3(name = "strategy_id_for_order")]
1286    fn py_strategy_id_for_order(&self, client_order_id: ClientOrderId) -> Option<StrategyId> {
1287        self.0
1288            .borrow()
1289            .strategy_id_for_order(&client_order_id)
1290            .copied()
1291    }
1292
1293    #[pyo3(name = "strategy_id_for_position")]
1294    fn py_strategy_id_for_position(&self, position_id: PositionId) -> Option<StrategyId> {
1295        self.0
1296            .borrow()
1297            .strategy_id_for_position(&position_id)
1298            .copied()
1299    }
1300
1301    #[pyo3(name = "position_snapshot_bytes")]
1302    fn py_position_snapshot_bytes(&self, position_id: PositionId) -> Option<Vec<Vec<u8>>> {
1303        self.0.borrow().position_snapshot_bytes(&position_id)
1304    }
1305
1306    #[pyo3(name = "snapshot_position")]
1307    #[expect(clippy::needless_pass_by_value)]
1308    fn py_snapshot_position(&self, py: Python, position: Py<PyAny>) -> PyResult<()> {
1309        let position_obj = position.extract::<Position>(py)?;
1310        self.0
1311            .borrow_mut()
1312            .snapshot_position(&position_obj)
1313            .map_err(to_pyvalue_err)
1314    }
1315
1316    #[pyo3(name = "position_snapshots", signature = (position_id=None, account_id=None))]
1317    fn py_position_snapshots(
1318        &self,
1319        py: Python,
1320        position_id: Option<PositionId>,
1321        account_id: Option<AccountId>,
1322    ) -> PyResult<Vec<Py<PyAny>>> {
1323        let cache = self.0.borrow();
1324        cache
1325            .position_snapshots(position_id.as_ref(), account_id.as_ref())
1326            .into_iter()
1327            .map(|p| Ok(p.into_pyobject(py)?.into()))
1328            .collect()
1329    }
1330}
1331
1332#[cfg(test)]
1333mod tests {
1334    use nautilus_core::UnixNanos;
1335    use nautilus_model::{
1336        data::{BookOrder, stubs::stub_instrument_close},
1337        enums::{BookType, InstrumentCloseType},
1338    };
1339    use pyo3::exceptions::PyValueError;
1340    use rstest::rstest;
1341
1342    use super::*;
1343
1344    fn book_order(side: OrderSide, price: &str, size: &str, id: u64) -> BookOrder {
1345        BookOrder::new(side, Price::from(price), Quantity::from(size), id)
1346    }
1347
1348    #[rstest]
1349    fn test_top_of_book_missing() {
1350        let cache = PyCache::from_rc(Rc::new(RefCell::new(Cache::default())));
1351
1352        assert_eq!(
1353            cache.py_top_of_book(InstrumentId::from("AUD/USD.SIM")),
1354            None
1355        );
1356    }
1357
1358    #[rstest]
1359    #[case::empty(None)]
1360    #[case::bid_only(Some(OrderSide::Buy))]
1361    #[case::ask_only(Some(OrderSide::Sell))]
1362    fn test_top_of_book_incomplete(#[case] side: Option<OrderSide>) {
1363        let instrument_id = InstrumentId::from("AUD/USD.SIM");
1364        let mut book = OrderBook::new(instrument_id, BookType::L2_MBP);
1365        if let Some(side) = side {
1366            book.add(book_order(side, "0.70000", "10", 1), 0, 1, 1.into());
1367        }
1368        let mut cache = Cache::default();
1369        cache.add_order_book(book).unwrap();
1370        let cache = PyCache::from_rc(Rc::new(RefCell::new(cache)));
1371
1372        assert_eq!(cache.py_top_of_book(instrument_id), None);
1373    }
1374
1375    #[rstest]
1376    fn test_top_of_book_python_values_and_resident_updates() {
1377        let instrument_id = InstrumentId::from("AUD/USD.SIM");
1378        let mut book = OrderBook::new(instrument_id, BookType::L2_MBP);
1379        book.add(
1380            book_order(OrderSide::Buy, "0.70000", "10", 1),
1381            0,
1382            1,
1383            1.into(),
1384        );
1385        book.add(
1386            book_order(OrderSide::Sell, "0.70010", "20", 2),
1387            0,
1388            2,
1389            2.into(),
1390        );
1391        let mut cache = Cache::default();
1392        cache.add_order_book(book).unwrap();
1393        let cache = Rc::new(RefCell::new(cache));
1394
1395        Python::initialize();
1396        Python::attach(|py| {
1397            let py_cache = Py::new(py, PyCache::from_rc(cache.clone())).unwrap();
1398            let original = py_cache
1399                .call_method1(py, "top_of_book", (instrument_id,))
1400                .unwrap();
1401            let expected = (
1402                Price::from("0.70000"),
1403                Quantity::from("10"),
1404                Price::from("0.70010"),
1405                Quantity::from("20"),
1406            );
1407            assert_eq!(
1408                original
1409                    .extract::<(Price, Quantity, Price, Quantity)>(py)
1410                    .unwrap(),
1411                expected
1412            );
1413
1414            {
1415                let mut cache = cache.borrow_mut();
1416                let book = cache.order_book_mut(&instrument_id).unwrap();
1417                book.update(
1418                    book_order(OrderSide::Buy, "0.70000", "15", 1),
1419                    0,
1420                    3,
1421                    3.into(),
1422                );
1423                book.add(
1424                    book_order(OrderSide::Sell, "0.70005", "25", 3),
1425                    0,
1426                    4,
1427                    4.into(),
1428                );
1429            }
1430            let updated = py_cache
1431                .call_method1(py, "top_of_book", (instrument_id,))
1432                .unwrap()
1433                .extract::<(Price, Quantity, Price, Quantity)>(py)
1434                .unwrap();
1435            assert_eq!(
1436                updated,
1437                (
1438                    Price::from("0.70000"),
1439                    Quantity::from("15"),
1440                    Price::from("0.70005"),
1441                    Quantity::from("25"),
1442                )
1443            );
1444            assert_eq!(
1445                original
1446                    .extract::<(Price, Quantity, Price, Quantity)>(py)
1447                    .unwrap(),
1448                expected
1449            );
1450
1451            cache
1452                .borrow_mut()
1453                .order_book_mut(&instrument_id)
1454                .unwrap()
1455                .clear_asks(5, 5.into());
1456            assert!(
1457                py_cache
1458                    .call_method1(py, "top_of_book", (instrument_id,))
1459                    .unwrap()
1460                    .is_none(py)
1461            );
1462        });
1463    }
1464
1465    #[rstest]
1466    fn test_top_of_book_l3_returns_first_order_sizes() {
1467        let instrument_id = InstrumentId::from("AUD/USD.SIM");
1468        let mut book = OrderBook::new(instrument_id, BookType::L3_MBO);
1469        let first_bid = book_order(OrderSide::Buy, "0.70000", "10", 1);
1470        let first_ask = book_order(OrderSide::Sell, "0.70010", "20", 3);
1471        book.add(first_bid, 0, 1, 1.into());
1472        book.add(
1473            book_order(OrderSide::Buy, "0.70000", "30", 2),
1474            0,
1475            2,
1476            2.into(),
1477        );
1478        book.add(first_ask, 0, 3, 3.into());
1479        book.add(
1480            book_order(OrderSide::Sell, "0.70010", "40", 4),
1481            0,
1482            4,
1483            4.into(),
1484        );
1485        let mut cache = Cache::default();
1486        cache.add_order_book(book).unwrap();
1487        let cache = PyCache::from_rc(Rc::new(RefCell::new(cache)));
1488
1489        assert_eq!(
1490            cache.py_top_of_book(instrument_id),
1491            Some((
1492                Price::from("0.70000"),
1493                Quantity::from("10"),
1494                Price::from("0.70010"),
1495                Quantity::from("20"),
1496            ))
1497        );
1498
1499        {
1500            let mut inner = cache.0.borrow_mut();
1501            let book = inner.order_book_mut(&instrument_id).unwrap();
1502            book.delete(first_bid, 0, 5, 5.into());
1503            book.delete(first_ask, 0, 6, 6.into());
1504        }
1505        assert_eq!(
1506            cache.py_top_of_book(instrument_id),
1507            Some((
1508                Price::from("0.70000"),
1509                Quantity::from("30"),
1510                Price::from("0.70010"),
1511                Quantity::from("40"),
1512            ))
1513        );
1514    }
1515
1516    fn create_order_list() -> OrderList {
1517        OrderList::new(
1518            OrderListId::from("OL-001"),
1519            InstrumentId::from("AUD/USD.SIM"),
1520            StrategyId::from("S-001"),
1521            vec![ClientOrderId::from("O-001")],
1522            UnixNanos::from(42_u64),
1523        )
1524    }
1525
1526    #[rstest]
1527    fn test_order_list_queries_preserve_concrete_type() {
1528        let order_list = create_order_list();
1529        let order_list_id = order_list.id;
1530        let cache = Rc::new(RefCell::new(Cache::default()));
1531        cache
1532            .borrow_mut()
1533            .add_order_list(order_list.clone())
1534            .unwrap();
1535        let py_cache = PyCache::from_rc(cache);
1536
1537        assert_eq!(
1538            py_cache.py_order_list(order_list_id),
1539            Some(order_list.clone()),
1540        );
1541        assert_eq!(
1542            py_cache.py_order_lists(None, None, None, None),
1543            vec![order_list],
1544        );
1545    }
1546
1547    #[rstest]
1548    fn test_add_instrument_close_replaces_existing() {
1549        let first = stub_instrument_close();
1550        let replacement = InstrumentClose::new(
1551            first.instrument_id,
1552            Price::from("0.00000"),
1553            InstrumentCloseType::EndOfSession,
1554            UnixNanos::from(3_u64),
1555            UnixNanos::from(4_u64),
1556        );
1557        let mut py_cache = PyCache::from_rc(Rc::new(RefCell::new(Cache::default())));
1558
1559        py_cache.py_add_instrument_close(first).unwrap();
1560        py_cache.py_add_instrument_close(replacement).unwrap();
1561
1562        assert_eq!(
1563            py_cache.py_instrument_close(first.instrument_id),
1564            Some(replacement)
1565        );
1566    }
1567
1568    #[rstest]
1569    fn test_py_cache_constructor_returns_value_error_for_invalid_config() {
1570        Python::initialize();
1571        let config = CacheConfig {
1572            tick_capacity: 0,
1573            ..Default::default()
1574        };
1575
1576        let err = PyCache::py_new(Some(config)).expect_err("invalid capacity must be rejected");
1577
1578        Python::attach(|py| assert!(err.is_instance_of::<PyValueError>(py)));
1579    }
1580
1581    #[rstest]
1582    fn test_native_cache_binding_returns_value_error_for_invalid_config() {
1583        Python::initialize();
1584        let config = CacheConfig {
1585            bar_capacity: 0,
1586            ..Default::default()
1587        };
1588
1589        let err = Cache::py_new(Some(config)).expect_err("invalid capacity must be rejected");
1590
1591        Python::attach(|py| assert!(err.is_instance_of::<PyValueError>(py)));
1592    }
1593}
1594
1595#[cfg(feature = "defi")]
1596#[pymethods]
1597#[pyo3_stub_gen::derive::gen_stub_pymethods]
1598impl PyCache {
1599    #[pyo3(name = "pool")]
1600    fn py_pool(&self, instrument_id: InstrumentId) -> Option<Pool> {
1601        self.0
1602            .try_borrow()
1603            .ok()
1604            .and_then(|cache| cache.pool(&instrument_id).cloned())
1605    }
1606
1607    #[pyo3(name = "pool_profiler")]
1608    fn py_pool_profiler(&self, instrument_id: InstrumentId) -> Option<PoolProfiler> {
1609        self.0
1610            .try_borrow()
1611            .ok()
1612            .and_then(|cache| cache.pool_profiler(&instrument_id).cloned())
1613    }
1614}
1615
1616#[pymethods]
1617#[pyo3_stub_gen::derive::gen_stub_pymethods]
1618impl CacheConfig {
1619    /// Configuration for `Cache` instances.
1620    #[new]
1621    #[expect(clippy::too_many_arguments)]
1622    #[pyo3(signature = (
1623        encoding=None,
1624        timestamps_as_iso8601=None,
1625        buffer_interval_ms=None,
1626        bulk_read_batch_size=None,
1627        use_trader_prefix=None,
1628        use_instance_id=None,
1629        flush_on_start=None,
1630        drop_instruments_on_reset=None,
1631        tick_capacity=None,
1632        bar_capacity=None,
1633        save_market_data=None,
1634        persist_account_events=None,
1635    ))]
1636    fn py_new(
1637        encoding: Option<SerializationEncoding>,
1638        timestamps_as_iso8601: Option<bool>,
1639        buffer_interval_ms: Option<usize>,
1640        bulk_read_batch_size: Option<usize>,
1641        use_trader_prefix: Option<bool>,
1642        use_instance_id: Option<bool>,
1643        flush_on_start: Option<bool>,
1644        drop_instruments_on_reset: Option<bool>,
1645        tick_capacity: Option<usize>,
1646        bar_capacity: Option<usize>,
1647        save_market_data: Option<bool>,
1648        persist_account_events: Option<bool>,
1649    ) -> PyResult<Self> {
1650        let config = Self {
1651            encoding: encoding.unwrap_or_default(),
1652            timestamps_as_iso8601: timestamps_as_iso8601.unwrap_or(false),
1653            buffer_interval_ms,
1654            bulk_read_batch_size,
1655            use_trader_prefix: use_trader_prefix.unwrap_or(true),
1656            use_instance_id: use_instance_id.unwrap_or(false),
1657            flush_on_start: flush_on_start.unwrap_or(false),
1658            drop_instruments_on_reset: drop_instruments_on_reset.unwrap_or(true),
1659            tick_capacity: tick_capacity.unwrap_or(10_000),
1660            bar_capacity: bar_capacity.unwrap_or(10_000),
1661            persist_account_events: persist_account_events.unwrap_or(true),
1662            save_market_data: save_market_data.unwrap_or(false),
1663        };
1664        config.validate().map_err(config_error_to_pyvalue_err)?;
1665        Ok(config)
1666    }
1667
1668    fn __str__(&self) -> String {
1669        format!("{self:?}")
1670    }
1671
1672    fn __repr__(&self) -> String {
1673        format!("{self:?}")
1674    }
1675
1676    #[getter]
1677    fn encoding(&self) -> SerializationEncoding {
1678        self.encoding
1679    }
1680
1681    #[getter]
1682    fn timestamps_as_iso8601(&self) -> bool {
1683        self.timestamps_as_iso8601
1684    }
1685
1686    #[getter]
1687    fn buffer_interval_ms(&self) -> Option<usize> {
1688        self.buffer_interval_ms
1689    }
1690
1691    #[getter]
1692    fn bulk_read_batch_size(&self) -> Option<usize> {
1693        self.bulk_read_batch_size
1694    }
1695
1696    #[getter]
1697    fn use_trader_prefix(&self) -> bool {
1698        self.use_trader_prefix
1699    }
1700
1701    #[getter]
1702    fn use_instance_id(&self) -> bool {
1703        self.use_instance_id
1704    }
1705
1706    #[getter]
1707    fn flush_on_start(&self) -> bool {
1708        self.flush_on_start
1709    }
1710
1711    #[getter]
1712    fn drop_instruments_on_reset(&self) -> bool {
1713        self.drop_instruments_on_reset
1714    }
1715
1716    #[getter]
1717    fn tick_capacity(&self) -> usize {
1718        self.tick_capacity
1719    }
1720
1721    #[getter]
1722    fn bar_capacity(&self) -> usize {
1723        self.bar_capacity
1724    }
1725
1726    #[getter]
1727    fn persist_account_events(&self) -> bool {
1728        self.persist_account_events
1729    }
1730
1731    #[getter]
1732    fn save_market_data(&self) -> bool {
1733        self.save_market_data
1734    }
1735}
1736
1737#[pymethods]
1738impl Cache {
1739    /// A common in-memory `Cache` for market and execution related data.
1740    #[new]
1741    fn py_new(config: Option<CacheConfig>) -> PyResult<Self> {
1742        Self::try_new(config, None).map_err(config_error_to_pyvalue_err)
1743    }
1744
1745    fn __repr__(&self) -> String {
1746        format!("{self:?}")
1747    }
1748
1749    /// Resets the cache.
1750    ///
1751    /// All stateful fields are reset to their initial value. Instruments,
1752    /// currencies, and synthetics are retained when `drop_instruments_on_reset`
1753    /// is `false` so that repeated backtest runs can reuse the same dataset. External order claims
1754    /// and execution client account, route, and external client registrations are retained so
1755    /// registered strategy and client routing remain configured across resets.
1756    #[pyo3(name = "reset")]
1757    fn py_reset(&mut self) {
1758        self.reset();
1759    }
1760
1761    /// Dispose of the cache which will close any underlying database adapter.
1762    ///
1763    /// If closing the database connection fails, an error is logged.
1764    #[pyo3(name = "dispose")]
1765    fn py_dispose(&mut self) {
1766        self.dispose();
1767    }
1768
1769    /// Purges all closed orders from the cache that are older than `buffer_secs`.
1770    ///
1771    ///
1772    /// Only orders that have been closed for at least this amount of time will be purged.
1773    /// A value of 0 means purge all closed orders regardless of when they were closed.
1774    #[pyo3(name = "purge_closed_orders", signature = (ts_now, buffer_secs=0))]
1775    fn py_purge_closed_orders(&mut self, ts_now: u64, buffer_secs: u64) {
1776        self.purge_closed_orders(ts_now.into(), buffer_secs);
1777    }
1778
1779    /// Purges all closed positions from the cache that are older than `buffer_secs`.
1780    #[pyo3(name = "purge_closed_positions", signature = (ts_now, buffer_secs=0))]
1781    fn py_purge_closed_positions(&mut self, ts_now: u64, buffer_secs: u64) {
1782        self.purge_closed_positions(ts_now.into(), buffer_secs);
1783    }
1784
1785    /// Purges the order with the `client_order_id` from the cache (if found).
1786    ///
1787    /// For safety, an order is prevented from being purged if it's open.
1788    #[pyo3(name = "purge_order")]
1789    fn py_purge_order(&mut self, client_order_id: ClientOrderId) {
1790        self.purge_order(client_order_id);
1791    }
1792
1793    /// Purges the position with the `position_id` from the cache (if found).
1794    ///
1795    /// For safety, a position is prevented from being purged if it's open.
1796    #[pyo3(name = "purge_position")]
1797    fn py_purge_position(&mut self, position_id: PositionId) {
1798        self.purge_position(position_id);
1799    }
1800
1801    /// Purges the instrument with the `instrument_id` from the cache.
1802    ///
1803    /// This refuses to purge when associated orders or positions remain in
1804    /// non-terminal state.
1805    #[pyo3(name = "purge_instrument")]
1806    fn py_purge_instrument(&mut self, instrument_id: InstrumentId) {
1807        self.purge_instrument(instrument_id);
1808    }
1809
1810    /// Purges all account state events which are outside the lookback window.
1811    ///
1812    /// Only events which are outside the lookback window will be purged.
1813    /// A value of 0 means purge all account state events.
1814    #[pyo3(name = "purge_account_events", signature = (ts_now, lookback_secs=0))]
1815    fn py_purge_account_events(&mut self, ts_now: u64, lookback_secs: u64) {
1816        self.purge_account_events(ts_now.into(), lookback_secs);
1817    }
1818
1819    /// Adds the `currency` to the cache.
1820    ///
1821    /// # Errors
1822    ///
1823    /// Returns an error if persisting the currency to the backing database fails.
1824    #[pyo3(name = "add_currency")]
1825    fn py_add_currency(&mut self, currency: Currency) -> PyResult<()> {
1826        self.add_currency(currency).map_err(to_pyvalue_err)
1827    }
1828
1829    /// Adds the `instrument` to the cache.
1830    ///
1831    /// # Errors
1832    ///
1833    /// Returns an error if persisting the instrument to the backing database fails.
1834    #[pyo3(name = "add_instrument")]
1835    fn py_add_instrument(&mut self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
1836        let instrument_any = pyobject_to_instrument_any(py, instrument)?;
1837        self.add_instrument(instrument_any).map_err(to_pyvalue_err)
1838    }
1839
1840    /// Returns a reference to the instrument for the `instrument_id` (if found).
1841    #[pyo3(name = "instrument")]
1842    fn py_instrument(
1843        &self,
1844        py: Python,
1845        instrument_id: InstrumentId,
1846    ) -> PyResult<Option<Py<PyAny>>> {
1847        match self.instrument(&instrument_id) {
1848            Some(instrument) => Ok(Some(instrument_any_to_pyobject(py, instrument.clone())?)),
1849            None => Ok(None),
1850        }
1851    }
1852
1853    /// Returns references to all instrument IDs for the `venue`.
1854    #[pyo3(name = "instrument_ids")]
1855    fn py_instrument_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
1856        self.instrument_ids(venue.as_ref())
1857            .into_iter()
1858            .copied()
1859            .collect()
1860    }
1861
1862    /// Returns references to all instruments for the `venue`.
1863    #[pyo3(name = "instruments")]
1864    fn py_instruments(&self, py: Python, venue: Option<Venue>) -> PyResult<Vec<Py<PyAny>>> {
1865        let mut py_instruments = Vec::new();
1866
1867        if let Some(venue) = venue {
1868            let instruments = self.instruments(&venue, None);
1869            for instrument in instruments {
1870                py_instruments.push(instrument_any_to_pyobject(py, (*instrument).clone())?);
1871            }
1872        } else {
1873            let instrument_ids = self.instrument_ids(None);
1874            for instrument_id in instrument_ids {
1875                if let Some(instrument) = self.instrument(instrument_id) {
1876                    py_instruments.push(instrument_any_to_pyobject(py, instrument.clone())?);
1877                }
1878            }
1879        }
1880
1881        Ok(py_instruments)
1882    }
1883
1884    /// Adds the `order` to the cache indexed with any given identifiers.
1885    ///
1886    /// # Parameters
1887    ///
1888    /// `override_existing`: If the added order should 'override' any existing order and replace
1889    /// it in the cache. This is currently used for emulated orders which are
1890    /// being released and transformed into another type.
1891    ///
1892    /// # Errors
1893    ///
1894    /// Returns an error if not `replace_existing` and the `order.client_order_id` is already contained in the cache,
1895    /// or if persisting the order to the backing database fails. The order and every index are
1896    /// committed to memory before persistence is attempted, so a persistence error leaves the
1897    /// cache internally consistent.
1898    #[pyo3(name = "add_order")]
1899    fn py_add_order(
1900        &mut self,
1901        py: Python,
1902        order: Py<PyAny>,
1903        position_id: Option<PositionId>,
1904        client_id: Option<ClientId>,
1905        replace_existing: Option<bool>,
1906    ) -> PyResult<()> {
1907        let order_any = pyobject_to_order_any(py, order)?;
1908        self.add_order(
1909            order_any,
1910            position_id,
1911            client_id,
1912            replace_existing.unwrap_or(false),
1913        )
1914        .map_err(to_pyvalue_err)
1915    }
1916
1917    /// Gets a borrow of the order with the `client_order_id` (if found).
1918    ///
1919    /// Prefer `Self.order_ref` in new native code.
1920    #[pyo3(name = "order")]
1921    fn py_order(&self, py: Python, client_order_id: ClientOrderId) -> PyResult<Option<Py<PyAny>>> {
1922        match self.order(&client_order_id) {
1923            Some(order) => Ok(Some(order_any_to_pyobject(py, order.clone())?)),
1924            None => Ok(None),
1925        }
1926    }
1927
1928    /// Returns whether an order with the `client_order_id` exists.
1929    #[pyo3(name = "order_exists")]
1930    fn py_order_exists(&self, client_order_id: ClientOrderId) -> bool {
1931        self.order_exists(&client_order_id)
1932    }
1933
1934    /// Returns whether an order with the `client_order_id` is open.
1935    #[pyo3(name = "is_order_open")]
1936    fn py_is_order_open(&self, client_order_id: ClientOrderId) -> bool {
1937        self.is_order_open(&client_order_id)
1938    }
1939
1940    /// Returns whether an order with the `client_order_id` is closed.
1941    #[pyo3(name = "is_order_closed")]
1942    fn py_is_order_closed(&self, client_order_id: ClientOrderId) -> bool {
1943        self.is_order_closed(&client_order_id)
1944    }
1945
1946    /// Returns whether an order with the `client_order_id` is locally active.
1947    ///
1948    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
1949    /// (a superset of emulated orders).
1950    #[pyo3(name = "is_order_active_local")]
1951    fn py_is_order_active_local(&self, client_order_id: ClientOrderId) -> bool {
1952        self.is_order_active_local(&client_order_id)
1953    }
1954
1955    /// Returns borrows of all locally active orders matching the optional filter parameters.
1956    ///
1957    /// Prefer `Self.orders_active_local_refs` in new native code.
1958    #[pyo3(name = "orders_active_local")]
1959    fn py_orders_active_local(
1960        &self,
1961        py: Python,
1962        venue: Option<Venue>,
1963        instrument_id: Option<InstrumentId>,
1964        strategy_id: Option<StrategyId>,
1965        account_id: Option<AccountId>,
1966        side: Option<OrderSide>,
1967    ) -> PyResult<Vec<Py<PyAny>>> {
1968        self.orders_active_local(
1969            venue.as_ref(),
1970            instrument_id.as_ref(),
1971            strategy_id.as_ref(),
1972            account_id.as_ref(),
1973            side,
1974        )
1975        .into_iter()
1976        .map(|order| order_any_to_pyobject(py, order.clone()))
1977        .collect()
1978    }
1979
1980    /// Returns the count of all locally active orders.
1981    ///
1982    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
1983    /// (a superset of emulated orders).
1984    #[pyo3(name = "orders_active_local_count")]
1985    fn py_orders_active_local_count(
1986        &self,
1987        venue: Option<Venue>,
1988        instrument_id: Option<InstrumentId>,
1989        strategy_id: Option<StrategyId>,
1990        account_id: Option<AccountId>,
1991        side: Option<OrderSide>,
1992    ) -> usize {
1993        self.orders_active_local_count(
1994            venue.as_ref(),
1995            instrument_id.as_ref(),
1996            strategy_id.as_ref(),
1997            account_id.as_ref(),
1998            side,
1999        )
2000    }
2001
2002    /// Returns the count of all open orders.
2003    #[pyo3(name = "orders_open_count")]
2004    fn py_orders_open_count(
2005        &self,
2006        venue: Option<Venue>,
2007        instrument_id: Option<InstrumentId>,
2008        strategy_id: Option<StrategyId>,
2009        account_id: Option<AccountId>,
2010        side: Option<OrderSide>,
2011    ) -> usize {
2012        self.orders_open_count(
2013            venue.as_ref(),
2014            instrument_id.as_ref(),
2015            strategy_id.as_ref(),
2016            account_id.as_ref(),
2017            side,
2018        )
2019    }
2020
2021    /// Returns the count of all closed orders.
2022    #[pyo3(name = "orders_closed_count")]
2023    fn py_orders_closed_count(
2024        &self,
2025        venue: Option<Venue>,
2026        instrument_id: Option<InstrumentId>,
2027        strategy_id: Option<StrategyId>,
2028        account_id: Option<AccountId>,
2029        side: Option<OrderSide>,
2030    ) -> usize {
2031        self.orders_closed_count(
2032            venue.as_ref(),
2033            instrument_id.as_ref(),
2034            strategy_id.as_ref(),
2035            account_id.as_ref(),
2036            side,
2037        )
2038    }
2039
2040    /// Returns the count of all orders.
2041    #[pyo3(name = "orders_total_count")]
2042    fn py_orders_total_count(
2043        &self,
2044        venue: Option<Venue>,
2045        instrument_id: Option<InstrumentId>,
2046        strategy_id: Option<StrategyId>,
2047        account_id: Option<AccountId>,
2048        side: Option<OrderSide>,
2049    ) -> usize {
2050        self.orders_total_count(
2051            venue.as_ref(),
2052            instrument_id.as_ref(),
2053            strategy_id.as_ref(),
2054            account_id.as_ref(),
2055            side,
2056        )
2057    }
2058
2059    /// Adds the `position` to the cache.
2060    ///
2061    /// For `OmsType.Unspecified`, IDs matching `{instrument_id}-{strategy_id}` resolve to
2062    /// `OmsType.Netting`. Other IDs retain the supplied OMS type.
2063    ///
2064    /// # Errors
2065    ///
2066    /// Returns an error if persisting the position to the backing database fails. After
2067    /// serialization succeeds, the complete operation is committed to memory before persistence
2068    /// is attempted, so a persistence error leaves the cache internally consistent.
2069    #[pyo3(name = "add_position")]
2070    #[expect(clippy::needless_pass_by_value)]
2071    fn py_add_position(
2072        &mut self,
2073        py: Python,
2074        position: Py<PyAny>,
2075        oms_type: OmsType,
2076    ) -> PyResult<()> {
2077        let position_obj = position.extract::<Position>(py)?;
2078        self.add_position(&position_obj, oms_type)
2079            .map_err(to_pyvalue_err)
2080    }
2081
2082    /// Creates a snapshot of the `position` by cloning it, assigning a new ID, and storing it
2083    /// in the position snapshots.
2084    ///
2085    /// The copy excludes `replay_events` and `fill_voids`, which no snapshot consumer reads,
2086    /// so snapshot size stays independent of the fills applied to the position ID. The copy
2087    /// encodes only when a consumer asks for the bytes, so this call stays off the encode path
2088    /// unless a backing database has to persist the frame.
2089    ///
2090    /// # Errors
2091    ///
2092    /// Returns an error if serializing or storing the position snapshot fails.
2093    #[pyo3(name = "snapshot_position")]
2094    #[expect(clippy::needless_pass_by_value)]
2095    fn py_snapshot_position(&mut self, py: Python, position: Py<PyAny>) -> PyResult<()> {
2096        let position_obj = position.extract::<Position>(py)?;
2097        self.snapshot_position(&position_obj)
2098            .map_err(to_pyvalue_err)
2099    }
2100
2101    /// Returns a borrow of the position with the `position_id` (if found).
2102    ///
2103    /// Prefer `Self.position_ref` in new native code.
2104    #[pyo3(name = "position")]
2105    fn py_position(&self, py: Python, position_id: PositionId) -> PyResult<Option<Py<PyAny>>> {
2106        match self.position(&position_id) {
2107            Some(position) => Ok(Some(position.clone().into_pyobject(py)?.into())),
2108            None => Ok(None),
2109        }
2110    }
2111
2112    /// Returns whether a position with the `position_id` exists.
2113    #[pyo3(name = "position_exists")]
2114    fn py_position_exists(&self, position_id: PositionId) -> bool {
2115        self.position_exists(&position_id)
2116    }
2117
2118    /// Returns whether a position with the `position_id` is open.
2119    #[pyo3(name = "is_position_open")]
2120    fn py_is_position_open(&self, position_id: PositionId) -> bool {
2121        self.is_position_open(&position_id)
2122    }
2123
2124    /// Returns whether a position with the `position_id` is closed.
2125    #[pyo3(name = "is_position_closed")]
2126    fn py_is_position_closed(&self, position_id: PositionId) -> bool {
2127        self.is_position_closed(&position_id)
2128    }
2129
2130    /// Returns the count of all open positions.
2131    #[pyo3(name = "positions_open_count")]
2132    fn py_positions_open_count(
2133        &self,
2134        venue: Option<Venue>,
2135        instrument_id: Option<InstrumentId>,
2136        strategy_id: Option<StrategyId>,
2137        account_id: Option<AccountId>,
2138        side: Option<PositionSide>,
2139    ) -> usize {
2140        self.positions_open_count(
2141            venue.as_ref(),
2142            instrument_id.as_ref(),
2143            strategy_id.as_ref(),
2144            account_id.as_ref(),
2145            side,
2146        )
2147    }
2148
2149    /// Returns the count of all closed positions.
2150    #[pyo3(name = "positions_closed_count")]
2151    fn py_positions_closed_count(
2152        &self,
2153        venue: Option<Venue>,
2154        instrument_id: Option<InstrumentId>,
2155        strategy_id: Option<StrategyId>,
2156        account_id: Option<AccountId>,
2157        side: Option<PositionSide>,
2158    ) -> usize {
2159        self.positions_closed_count(
2160            venue.as_ref(),
2161            instrument_id.as_ref(),
2162            strategy_id.as_ref(),
2163            account_id.as_ref(),
2164            side,
2165        )
2166    }
2167
2168    /// Returns the count of all positions.
2169    #[pyo3(name = "positions_total_count")]
2170    fn py_positions_total_count(
2171        &self,
2172        venue: Option<Venue>,
2173        instrument_id: Option<InstrumentId>,
2174        strategy_id: Option<StrategyId>,
2175        account_id: Option<AccountId>,
2176        side: Option<PositionSide>,
2177    ) -> usize {
2178        self.positions_total_count(
2179            venue.as_ref(),
2180            instrument_id.as_ref(),
2181            strategy_id.as_ref(),
2182            account_id.as_ref(),
2183            side,
2184        )
2185    }
2186
2187    /// Adds the `quote` tick to the cache.
2188    ///
2189    /// # Errors
2190    ///
2191    /// Returns an error if persisting the quote tick to the backing database fails.
2192    #[pyo3(name = "add_quote")]
2193    fn py_add_quote(&mut self, quote: QuoteTick) -> PyResult<()> {
2194        self.add_quote(quote).map_err(to_pyvalue_err)
2195    }
2196
2197    /// Adds the `trade` tick to the cache.
2198    ///
2199    /// # Errors
2200    ///
2201    /// Returns an error if persisting the trade tick to the backing database fails.
2202    #[pyo3(name = "add_trade")]
2203    fn py_add_trade(&mut self, trade: TradeTick) -> PyResult<()> {
2204        self.add_trade(trade).map_err(to_pyvalue_err)
2205    }
2206
2207    /// Adds the `bar` to the cache, keeping the per-`bar_type` series newest-first.
2208    ///
2209    /// A newer bar is pushed, an older `ts_event` is skipped, and an equal
2210    /// `ts_event` replaces the front bar for time bars.
2211    ///
2212    /// # Errors
2213    ///
2214    /// Returns an error if persisting the bar to the backing database fails.
2215    #[pyo3(name = "add_bar")]
2216    fn py_add_bar(&mut self, bar: Bar) -> PyResult<()> {
2217        self.add_bar(bar).map_err(to_pyvalue_err)
2218    }
2219
2220    /// Returns the most recently added quote for the `instrument_id`, or `None` if absent.
2221    #[pyo3(name = "quote")]
2222    fn py_quote(&self, instrument_id: InstrumentId) -> Option<QuoteTick> {
2223        self.quote(&instrument_id).copied()
2224    }
2225
2226    /// Returns the most recently added trade for the `instrument_id`, or `None` if absent.
2227    #[pyo3(name = "trade")]
2228    fn py_trade(&self, instrument_id: InstrumentId) -> Option<TradeTick> {
2229        self.trade(&instrument_id).copied()
2230    }
2231
2232    /// Returns the most recent bar by `ts_event` for the `bar_type`, or `None` if absent.
2233    #[pyo3(name = "bar")]
2234    fn py_bar(&self, bar_type: BarType) -> Option<Bar> {
2235        self.bar(&bar_type).copied()
2236    }
2237
2238    /// Returns cached quotes for the `instrument_id` in reverse insertion order.
2239    ///
2240    /// Index 0 is the most recently added quote. Entries are not sorted by timestamp.
2241    /// Returns `None` when no quotes are cached for the instrument.
2242    #[pyo3(name = "quotes")]
2243    fn py_quotes(&self, instrument_id: InstrumentId) -> Option<Vec<QuoteTick>> {
2244        self.quotes(&instrument_id)
2245    }
2246
2247    /// Returns cached trades for the `instrument_id` in reverse insertion order.
2248    ///
2249    /// Index 0 is the most recently added trade. Entries are not sorted by timestamp.
2250    /// Returns `None` when no trades are cached for the instrument.
2251    #[pyo3(name = "trades")]
2252    fn py_trades(&self, instrument_id: InstrumentId) -> Option<Vec<TradeTick>> {
2253        self.trades(&instrument_id)
2254    }
2255
2256    /// Returns cached bars for the `bar_type` in reverse chronological order by `ts_event`.
2257    ///
2258    /// Index 0 is the most recent bar. Returns `None` when no bars are cached for the bar type.
2259    #[pyo3(name = "bars")]
2260    fn py_bars(&self, bar_type: BarType) -> Option<Vec<Bar>> {
2261        self.bars(&bar_type)
2262    }
2263
2264    /// Returns whether the cache contains quotes for the `instrument_id`.
2265    #[pyo3(name = "has_quote_ticks")]
2266    fn py_has_quote_ticks(&self, instrument_id: InstrumentId) -> bool {
2267        self.has_quote_ticks(&instrument_id)
2268    }
2269
2270    /// Returns whether the cache contains trades for the `instrument_id`.
2271    #[pyo3(name = "has_trade_ticks")]
2272    fn py_has_trade_ticks(&self, instrument_id: InstrumentId) -> bool {
2273        self.has_trade_ticks(&instrument_id)
2274    }
2275
2276    /// Returns whether the cache contains mark price updates for the `instrument_id`.
2277    #[pyo3(name = "has_mark_prices")]
2278    fn py_has_mark_prices(&self, instrument_id: InstrumentId) -> bool {
2279        self.has_mark_prices(&instrument_id)
2280    }
2281
2282    /// Returns whether the cache contains index price updates for the `instrument_id`.
2283    #[pyo3(name = "has_index_prices")]
2284    fn py_has_index_prices(&self, instrument_id: InstrumentId) -> bool {
2285        self.has_index_prices(&instrument_id)
2286    }
2287
2288    /// Returns whether the cache contains funding rate updates for the `instrument_id`.
2289    #[pyo3(name = "has_funding_rates")]
2290    fn py_has_funding_rates(&self, instrument_id: InstrumentId) -> bool {
2291        self.has_funding_rates(&instrument_id)
2292    }
2293
2294    /// Returns whether the cache contains instrument status updates for the `instrument_id`.
2295    #[pyo3(name = "has_instrument_statuses")]
2296    fn py_has_instrument_statuses(&self, instrument_id: InstrumentId) -> bool {
2297        self.has_instrument_statuses(&instrument_id)
2298    }
2299
2300    /// Returns whether the cache contains a close for the `instrument_id`.
2301    #[pyo3(name = "has_instrument_close")]
2302    fn py_has_instrument_close(&self, instrument_id: InstrumentId) -> bool {
2303        self.has_instrument_close(&instrument_id)
2304    }
2305
2306    /// Returns whether the cache contains bars for the `bar_type`.
2307    #[pyo3(name = "has_bars")]
2308    fn py_has_bars(&self, bar_type: BarType) -> bool {
2309        self.has_bars(&bar_type)
2310    }
2311
2312    /// Gets the quote tick count for the `instrument_id`.
2313    #[pyo3(name = "quote_count")]
2314    fn py_quote_count(&self, instrument_id: InstrumentId) -> usize {
2315        self.quote_count(&instrument_id)
2316    }
2317
2318    /// Gets the trade tick count for the `instrument_id`.
2319    #[pyo3(name = "trade_count")]
2320    fn py_trade_count(&self, instrument_id: InstrumentId) -> usize {
2321        self.trade_count(&instrument_id)
2322    }
2323
2324    /// Gets the mark price update count for the `instrument_id`.
2325    #[pyo3(name = "mark_price_count")]
2326    fn py_mark_price_count(&self, instrument_id: InstrumentId) -> usize {
2327        self.mark_price_count(&instrument_id)
2328    }
2329
2330    /// Gets the index price update count for the `instrument_id`.
2331    #[pyo3(name = "index_price_count")]
2332    fn py_index_price_count(&self, instrument_id: InstrumentId) -> usize {
2333        self.index_price_count(&instrument_id)
2334    }
2335
2336    /// Gets the funding rate update count for the `instrument_id`.
2337    #[pyo3(name = "funding_rate_count")]
2338    fn py_funding_rate_count(&self, instrument_id: InstrumentId) -> usize {
2339        self.funding_rate_count(&instrument_id)
2340    }
2341
2342    /// Gets the instrument status update count for the `instrument_id`.
2343    #[pyo3(name = "instrument_status_count")]
2344    fn py_instrument_status_count(&self, instrument_id: InstrumentId) -> usize {
2345        self.instrument_status_count(&instrument_id)
2346    }
2347
2348    /// Gets the bar count for the `instrument_id`.
2349    #[pyo3(name = "bar_count")]
2350    fn py_bar_count(&self, bar_type: BarType) -> usize {
2351        self.bar_count(&bar_type)
2352    }
2353
2354    /// Returns the most recently added mark price update for the `instrument_id`, or `None` if absent.
2355    #[pyo3(name = "mark_price")]
2356    fn py_mark_price(&self, instrument_id: InstrumentId) -> Option<MarkPriceUpdate> {
2357        self.mark_price(&instrument_id).copied()
2358    }
2359
2360    /// Returns cached mark price updates for the `instrument_id` in reverse insertion order.
2361    ///
2362    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
2363    /// Returns `None` when no mark price updates are cached for the instrument.
2364    #[pyo3(name = "mark_prices")]
2365    fn py_mark_prices(&self, instrument_id: InstrumentId) -> Option<Vec<MarkPriceUpdate>> {
2366        self.mark_prices(&instrument_id)
2367    }
2368
2369    /// Returns the most recently added index price update for the `instrument_id`, or `None` if absent.
2370    #[pyo3(name = "index_price")]
2371    fn py_index_price(&self, instrument_id: InstrumentId) -> Option<IndexPriceUpdate> {
2372        self.index_price(&instrument_id).copied()
2373    }
2374
2375    /// Returns cached index price updates for the `instrument_id` in reverse insertion order.
2376    ///
2377    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
2378    /// Returns `None` when no index price updates are cached for the instrument.
2379    #[pyo3(name = "index_prices")]
2380    fn py_index_prices(&self, instrument_id: InstrumentId) -> Option<Vec<IndexPriceUpdate>> {
2381        self.index_prices(&instrument_id)
2382    }
2383
2384    /// Returns the most recently added funding rate update for the `instrument_id`, or `None` if absent.
2385    #[pyo3(name = "funding_rate")]
2386    fn py_funding_rate(&self, instrument_id: InstrumentId) -> Option<FundingRateUpdate> {
2387        self.funding_rate(&instrument_id).copied()
2388    }
2389
2390    /// Returns cached funding rate updates for the `instrument_id` in reverse insertion order.
2391    ///
2392    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
2393    /// Returns `None` when no funding rate updates are cached for the instrument.
2394    #[pyo3(name = "funding_rates")]
2395    fn py_funding_rates(&self, instrument_id: InstrumentId) -> Option<Vec<FundingRateUpdate>> {
2396        self.funding_rates(&instrument_id)
2397    }
2398
2399    /// Returns the most recently added instrument status for the `instrument_id`, or `None` if absent.
2400    #[pyo3(name = "instrument_status")]
2401    fn py_instrument_status(&self, instrument_id: InstrumentId) -> Option<InstrumentStatus> {
2402        self.instrument_status(&instrument_id).copied()
2403    }
2404
2405    /// Returns cached instrument status updates for the `instrument_id` in reverse insertion order.
2406    ///
2407    /// Index 0 is the most recently added update. Entries are not sorted by timestamp.
2408    /// Returns `None` when no instrument status updates are cached for the instrument.
2409    #[pyo3(name = "instrument_statuses")]
2410    fn py_instrument_statuses(&self, instrument_id: InstrumentId) -> Option<Vec<InstrumentStatus>> {
2411        self.instrument_statuses(&instrument_id)
2412    }
2413
2414    /// Returns the close cached for `instrument_id`, if present.
2415    ///
2416    /// A later insertion replaces the cached close for the instrument; no history is kept.
2417    #[pyo3(name = "instrument_close")]
2418    fn py_instrument_close(&self, instrument_id: InstrumentId) -> Option<InstrumentClose> {
2419        self.instrument_close(&instrument_id).copied()
2420    }
2421
2422    /// Returns the current order book for the `instrument_id`, or `None` if absent.
2423    ///
2424    /// The cache holds the current book state, not a history of book updates.
2425    #[pyo3(name = "order_book")]
2426    fn py_order_book(&self, instrument_id: InstrumentId) -> Option<OrderBook> {
2427        self.order_book(&instrument_id).cloned()
2428    }
2429
2430    /// Returns whether the cache contains an order book for the `instrument_id`.
2431    #[pyo3(name = "has_order_book")]
2432    fn py_has_order_book(&self, instrument_id: InstrumentId) -> bool {
2433        self.has_order_book(&instrument_id)
2434    }
2435
2436    /// Gets the order book update count for the `instrument_id`.
2437    #[pyo3(name = "book_update_count")]
2438    fn py_book_update_count(&self, instrument_id: InstrumentId) -> usize {
2439        self.book_update_count(&instrument_id)
2440    }
2441
2442    /// Returns a reference to the synthetic instrument for the `instrument_id` (if found).
2443    #[pyo3(name = "synthetic")]
2444    fn py_synthetic(&self, instrument_id: InstrumentId) -> Option<SyntheticInstrument> {
2445        self.synthetic(&instrument_id).cloned()
2446    }
2447
2448    /// Returns references to instrument IDs for all synthetic instruments contained in the cache.
2449    #[pyo3(name = "synthetic_ids")]
2450    fn py_synthetic_ids(&self) -> Vec<InstrumentId> {
2451        self.synthetic_ids().into_iter().copied().collect()
2452    }
2453
2454    /// Returns the `ClientOrderId`s of all orders.
2455    #[pyo3(name = "client_order_ids")]
2456    fn py_client_order_ids(
2457        &self,
2458        venue: Option<Venue>,
2459        instrument_id: Option<InstrumentId>,
2460        strategy_id: Option<StrategyId>,
2461        account_id: Option<AccountId>,
2462    ) -> Vec<ClientOrderId> {
2463        self.client_order_ids(
2464            venue.as_ref(),
2465            instrument_id.as_ref(),
2466            strategy_id.as_ref(),
2467            account_id.as_ref(),
2468        )
2469        .into_iter()
2470        .collect()
2471    }
2472
2473    /// Returns the `ClientOrderId`s of all open orders.
2474    #[pyo3(name = "client_order_ids_open")]
2475    fn py_client_order_ids_open(
2476        &self,
2477        venue: Option<Venue>,
2478        instrument_id: Option<InstrumentId>,
2479        strategy_id: Option<StrategyId>,
2480        account_id: Option<AccountId>,
2481    ) -> Vec<ClientOrderId> {
2482        self.client_order_ids_open(
2483            venue.as_ref(),
2484            instrument_id.as_ref(),
2485            strategy_id.as_ref(),
2486            account_id.as_ref(),
2487        )
2488        .into_iter()
2489        .collect()
2490    }
2491
2492    /// Returns the `ClientOrderId`s of all closed orders.
2493    #[pyo3(name = "client_order_ids_closed")]
2494    fn py_client_order_ids_closed(
2495        &self,
2496        venue: Option<Venue>,
2497        instrument_id: Option<InstrumentId>,
2498        strategy_id: Option<StrategyId>,
2499        account_id: Option<AccountId>,
2500    ) -> Vec<ClientOrderId> {
2501        self.client_order_ids_closed(
2502            venue.as_ref(),
2503            instrument_id.as_ref(),
2504            strategy_id.as_ref(),
2505            account_id.as_ref(),
2506        )
2507        .into_iter()
2508        .collect()
2509    }
2510
2511    /// Returns the `ClientOrderId`s of all emulated orders.
2512    #[pyo3(name = "client_order_ids_emulated")]
2513    fn py_client_order_ids_emulated(
2514        &self,
2515        venue: Option<Venue>,
2516        instrument_id: Option<InstrumentId>,
2517        strategy_id: Option<StrategyId>,
2518        account_id: Option<AccountId>,
2519    ) -> Vec<ClientOrderId> {
2520        self.client_order_ids_emulated(
2521            venue.as_ref(),
2522            instrument_id.as_ref(),
2523            strategy_id.as_ref(),
2524            account_id.as_ref(),
2525        )
2526        .into_iter()
2527        .collect()
2528    }
2529
2530    /// Returns the `ClientOrderId`s of all in-flight orders.
2531    #[pyo3(name = "client_order_ids_inflight")]
2532    fn py_client_order_ids_inflight(
2533        &self,
2534        venue: Option<Venue>,
2535        instrument_id: Option<InstrumentId>,
2536        strategy_id: Option<StrategyId>,
2537        account_id: Option<AccountId>,
2538    ) -> Vec<ClientOrderId> {
2539        self.client_order_ids_inflight(
2540            venue.as_ref(),
2541            instrument_id.as_ref(),
2542            strategy_id.as_ref(),
2543            account_id.as_ref(),
2544        )
2545        .into_iter()
2546        .collect()
2547    }
2548
2549    /// Returns `PositionId`s of all positions.
2550    #[pyo3(name = "position_ids")]
2551    fn py_position_ids(
2552        &self,
2553        venue: Option<Venue>,
2554        instrument_id: Option<InstrumentId>,
2555        strategy_id: Option<StrategyId>,
2556        account_id: Option<AccountId>,
2557    ) -> Vec<PositionId> {
2558        self.position_ids(
2559            venue.as_ref(),
2560            instrument_id.as_ref(),
2561            strategy_id.as_ref(),
2562            account_id.as_ref(),
2563        )
2564        .into_iter()
2565        .collect()
2566    }
2567
2568    /// Returns the `PositionId`s of all open positions.
2569    #[pyo3(name = "position_open_ids")]
2570    fn py_position_open_ids(
2571        &self,
2572        venue: Option<Venue>,
2573        instrument_id: Option<InstrumentId>,
2574        strategy_id: Option<StrategyId>,
2575        account_id: Option<AccountId>,
2576    ) -> Vec<PositionId> {
2577        self.position_open_ids(
2578            venue.as_ref(),
2579            instrument_id.as_ref(),
2580            strategy_id.as_ref(),
2581            account_id.as_ref(),
2582        )
2583        .into_iter()
2584        .collect()
2585    }
2586
2587    /// Returns the `PositionId`s of all closed positions.
2588    #[pyo3(name = "position_closed_ids")]
2589    fn py_position_closed_ids(
2590        &self,
2591        venue: Option<Venue>,
2592        instrument_id: Option<InstrumentId>,
2593        strategy_id: Option<StrategyId>,
2594        account_id: Option<AccountId>,
2595    ) -> Vec<PositionId> {
2596        self.position_closed_ids(
2597            venue.as_ref(),
2598            instrument_id.as_ref(),
2599            strategy_id.as_ref(),
2600            account_id.as_ref(),
2601        )
2602        .into_iter()
2603        .collect()
2604    }
2605
2606    /// Returns the `StrategyId`s of all strategies.
2607    #[pyo3(name = "strategy_ids")]
2608    fn py_strategy_ids(&self) -> Vec<StrategyId> {
2609        self.strategy_ids().into_iter().collect()
2610    }
2611
2612    /// Returns the `ExecAlgorithmId`s of all execution algorithms.
2613    #[pyo3(name = "exec_algorithm_ids")]
2614    fn py_exec_algorithm_ids(&self) -> Vec<ExecAlgorithmId> {
2615        self.exec_algorithm_ids().into_iter().collect()
2616    }
2617
2618    /// Gets a reference to the client order ID for the `venue_order_id` (if found).
2619    #[pyo3(name = "client_order_id")]
2620    fn py_client_order_id(&self, venue_order_id: VenueOrderId) -> Option<ClientOrderId> {
2621        self.client_order_id(&venue_order_id).copied()
2622    }
2623
2624    /// Gets a reference to the venue order ID for the `client_order_id` (if found).
2625    #[pyo3(name = "venue_order_id")]
2626    fn py_venue_order_id(&self, client_order_id: ClientOrderId) -> Option<VenueOrderId> {
2627        self.venue_order_id(&client_order_id).copied()
2628    }
2629
2630    /// Gets a reference to the client ID indexed for then `client_order_id` (if found).
2631    #[pyo3(name = "client_id")]
2632    fn py_client_id(&self, client_order_id: ClientOrderId) -> Option<ClientId> {
2633        self.client_id(&client_order_id).copied()
2634    }
2635
2636    /// Returns borrows of all orders matching the optional filter parameters.
2637    ///
2638    /// Prefer `Self.orders_refs` in new native code.
2639    #[pyo3(name = "orders")]
2640    fn py_orders(
2641        &self,
2642        py: Python,
2643        venue: Option<Venue>,
2644        instrument_id: Option<InstrumentId>,
2645        strategy_id: Option<StrategyId>,
2646        account_id: Option<AccountId>,
2647        side: Option<OrderSide>,
2648    ) -> PyResult<Vec<Py<PyAny>>> {
2649        self.orders(
2650            venue.as_ref(),
2651            instrument_id.as_ref(),
2652            strategy_id.as_ref(),
2653            account_id.as_ref(),
2654            side,
2655        )
2656        .into_iter()
2657        .map(|o| order_any_to_pyobject(py, o.clone()))
2658        .collect()
2659    }
2660
2661    /// Returns borrows of all open orders matching the optional filter parameters.
2662    ///
2663    /// Prefer `Self.orders_open_refs` in new native code.
2664    #[pyo3(name = "orders_open")]
2665    fn py_orders_open(
2666        &self,
2667        py: Python,
2668        venue: Option<Venue>,
2669        instrument_id: Option<InstrumentId>,
2670        strategy_id: Option<StrategyId>,
2671        account_id: Option<AccountId>,
2672        side: Option<OrderSide>,
2673    ) -> PyResult<Vec<Py<PyAny>>> {
2674        self.orders_open(
2675            venue.as_ref(),
2676            instrument_id.as_ref(),
2677            strategy_id.as_ref(),
2678            account_id.as_ref(),
2679            side,
2680        )
2681        .into_iter()
2682        .map(|o| order_any_to_pyobject(py, o.clone()))
2683        .collect()
2684    }
2685
2686    /// Returns borrows of all closed orders matching the optional filter parameters.
2687    ///
2688    /// Prefer `Self.orders_closed_refs` in new native code.
2689    #[pyo3(name = "orders_closed")]
2690    fn py_orders_closed(
2691        &self,
2692        py: Python,
2693        venue: Option<Venue>,
2694        instrument_id: Option<InstrumentId>,
2695        strategy_id: Option<StrategyId>,
2696        account_id: Option<AccountId>,
2697        side: Option<OrderSide>,
2698    ) -> PyResult<Vec<Py<PyAny>>> {
2699        self.orders_closed(
2700            venue.as_ref(),
2701            instrument_id.as_ref(),
2702            strategy_id.as_ref(),
2703            account_id.as_ref(),
2704            side,
2705        )
2706        .into_iter()
2707        .map(|o| order_any_to_pyobject(py, o.clone()))
2708        .collect()
2709    }
2710
2711    /// Returns borrows of all emulated orders matching the optional filter parameters.
2712    ///
2713    /// Prefer `Self.orders_emulated_refs` in new native code.
2714    #[pyo3(name = "orders_emulated")]
2715    fn py_orders_emulated(
2716        &self,
2717        py: Python,
2718        venue: Option<Venue>,
2719        instrument_id: Option<InstrumentId>,
2720        strategy_id: Option<StrategyId>,
2721        account_id: Option<AccountId>,
2722        side: Option<OrderSide>,
2723    ) -> PyResult<Vec<Py<PyAny>>> {
2724        self.orders_emulated(
2725            venue.as_ref(),
2726            instrument_id.as_ref(),
2727            strategy_id.as_ref(),
2728            account_id.as_ref(),
2729            side,
2730        )
2731        .into_iter()
2732        .map(|o| order_any_to_pyobject(py, o.clone()))
2733        .collect()
2734    }
2735
2736    /// Returns borrows of all in-flight orders matching the optional filter parameters.
2737    ///
2738    /// Prefer `Self.orders_inflight_refs` in new native code.
2739    #[pyo3(name = "orders_inflight")]
2740    fn py_orders_inflight(
2741        &self,
2742        py: Python,
2743        venue: Option<Venue>,
2744        instrument_id: Option<InstrumentId>,
2745        strategy_id: Option<StrategyId>,
2746        account_id: Option<AccountId>,
2747        side: Option<OrderSide>,
2748    ) -> PyResult<Vec<Py<PyAny>>> {
2749        self.orders_inflight(
2750            venue.as_ref(),
2751            instrument_id.as_ref(),
2752            strategy_id.as_ref(),
2753            account_id.as_ref(),
2754            side,
2755        )
2756        .into_iter()
2757        .map(|o| order_any_to_pyobject(py, o.clone()))
2758        .collect()
2759    }
2760
2761    /// Returns borrows of all orders for the `position_id`.
2762    #[pyo3(name = "orders_for_position")]
2763    fn py_orders_for_position(
2764        &self,
2765        py: Python,
2766        position_id: PositionId,
2767    ) -> PyResult<Vec<Py<PyAny>>> {
2768        self.orders_for_position(&position_id)
2769            .into_iter()
2770            .map(|o| order_any_to_pyobject(py, o.clone()))
2771            .collect()
2772    }
2773
2774    /// Returns whether an order with the `client_order_id` is emulated.
2775    #[pyo3(name = "is_order_emulated")]
2776    fn py_is_order_emulated(&self, client_order_id: ClientOrderId) -> bool {
2777        self.is_order_emulated(&client_order_id)
2778    }
2779
2780    /// Returns whether an order with the `client_order_id` is in-flight.
2781    #[pyo3(name = "is_order_inflight")]
2782    fn py_is_order_inflight(&self, client_order_id: ClientOrderId) -> bool {
2783        self.is_order_inflight(&client_order_id)
2784    }
2785
2786    /// Returns whether an order with the `client_order_id` is `PENDING_CANCEL` locally.
2787    #[pyo3(name = "is_order_pending_cancel_local")]
2788    fn py_is_order_pending_cancel_local(&self, client_order_id: ClientOrderId) -> bool {
2789        self.is_order_pending_cancel_local(&client_order_id)
2790    }
2791
2792    /// Returns the count of all emulated orders.
2793    #[pyo3(name = "orders_emulated_count")]
2794    fn py_orders_emulated_count(
2795        &self,
2796        venue: Option<Venue>,
2797        instrument_id: Option<InstrumentId>,
2798        strategy_id: Option<StrategyId>,
2799        account_id: Option<AccountId>,
2800        side: Option<OrderSide>,
2801    ) -> usize {
2802        self.orders_emulated_count(
2803            venue.as_ref(),
2804            instrument_id.as_ref(),
2805            strategy_id.as_ref(),
2806            account_id.as_ref(),
2807            side,
2808        )
2809    }
2810
2811    /// Returns the count of all in-flight orders.
2812    #[pyo3(name = "orders_inflight_count")]
2813    fn py_orders_inflight_count(
2814        &self,
2815        venue: Option<Venue>,
2816        instrument_id: Option<InstrumentId>,
2817        strategy_id: Option<StrategyId>,
2818        account_id: Option<AccountId>,
2819        side: Option<OrderSide>,
2820    ) -> usize {
2821        self.orders_inflight_count(
2822            venue.as_ref(),
2823            instrument_id.as_ref(),
2824            strategy_id.as_ref(),
2825            account_id.as_ref(),
2826            side,
2827        )
2828    }
2829
2830    /// Returns the order list for the `order_list_id`.
2831    #[pyo3(name = "order_list")]
2832    fn py_order_list(&self, order_list_id: OrderListId) -> Option<OrderList> {
2833        self.order_list(&order_list_id).cloned()
2834    }
2835
2836    /// Returns all order lists matching the optional filter parameters.
2837    #[pyo3(name = "order_lists")]
2838    fn py_order_lists(
2839        &self,
2840        venue: Option<Venue>,
2841        instrument_id: Option<InstrumentId>,
2842        strategy_id: Option<StrategyId>,
2843        account_id: Option<AccountId>,
2844    ) -> Vec<OrderList> {
2845        self.order_lists(
2846            venue.as_ref(),
2847            instrument_id.as_ref(),
2848            strategy_id.as_ref(),
2849            account_id.as_ref(),
2850        )
2851        .into_iter()
2852        .cloned()
2853        .collect()
2854    }
2855
2856    /// Returns whether an order list with the `order_list_id` exists.
2857    #[pyo3(name = "order_list_exists")]
2858    fn py_order_list_exists(&self, order_list_id: OrderListId) -> bool {
2859        self.order_list_exists(&order_list_id)
2860    }
2861
2862    /// Returns references to all orders associated with the `exec_algorithm_id` matching the
2863    /// optional filter parameters.
2864    #[pyo3(name = "orders_for_exec_algorithm")]
2865    #[expect(clippy::too_many_arguments)]
2866    fn py_orders_for_exec_algorithm(
2867        &self,
2868        py: Python,
2869        exec_algorithm_id: ExecAlgorithmId,
2870        venue: Option<Venue>,
2871        instrument_id: Option<InstrumentId>,
2872        strategy_id: Option<StrategyId>,
2873        account_id: Option<AccountId>,
2874        side: Option<OrderSide>,
2875    ) -> PyResult<Vec<Py<PyAny>>> {
2876        self.orders_for_exec_algorithm(
2877            &exec_algorithm_id,
2878            venue.as_ref(),
2879            instrument_id.as_ref(),
2880            strategy_id.as_ref(),
2881            account_id.as_ref(),
2882            side,
2883        )
2884        .into_iter()
2885        .map(|o| order_any_to_pyobject(py, o.clone()))
2886        .collect()
2887    }
2888
2889    /// Returns references to all orders with the `exec_spawn_id`.
2890    #[pyo3(name = "orders_for_exec_spawn")]
2891    fn py_orders_for_exec_spawn(
2892        &self,
2893        py: Python,
2894        exec_spawn_id: ClientOrderId,
2895    ) -> PyResult<Vec<Py<PyAny>>> {
2896        self.orders_for_exec_spawn(&exec_spawn_id)
2897            .into_iter()
2898            .map(|o| order_any_to_pyobject(py, o.clone()))
2899            .collect()
2900    }
2901
2902    /// Returns the total order quantity for the `exec_spawn_id`.
2903    #[pyo3(name = "exec_spawn_total_quantity")]
2904    fn py_exec_spawn_total_quantity(
2905        &self,
2906        exec_spawn_id: ClientOrderId,
2907        active_only: bool,
2908    ) -> Option<Quantity> {
2909        self.exec_spawn_total_quantity(&exec_spawn_id, active_only)
2910    }
2911
2912    /// Returns the total filled quantity for all orders with the `exec_spawn_id`.
2913    #[pyo3(name = "exec_spawn_total_filled_qty")]
2914    fn py_exec_spawn_total_filled_qty(
2915        &self,
2916        exec_spawn_id: ClientOrderId,
2917        active_only: bool,
2918    ) -> Option<Quantity> {
2919        self.exec_spawn_total_filled_qty(&exec_spawn_id, active_only)
2920    }
2921
2922    /// Returns the total leaves quantity for all orders with the `exec_spawn_id`.
2923    #[pyo3(name = "exec_spawn_total_leaves_qty")]
2924    fn py_exec_spawn_total_leaves_qty(
2925        &self,
2926        exec_spawn_id: ClientOrderId,
2927        active_only: bool,
2928    ) -> Option<Quantity> {
2929        self.exec_spawn_total_leaves_qty(&exec_spawn_id, active_only)
2930    }
2931
2932    /// Returns a borrow of the position for the `client_order_id` (if found).
2933    ///
2934    /// Prefer `Self.position_for_order_ref` in new native code.
2935    #[pyo3(name = "position_for_order")]
2936    fn py_position_for_order(
2937        &self,
2938        py: Python,
2939        client_order_id: ClientOrderId,
2940    ) -> PyResult<Option<Py<PyAny>>> {
2941        match self.position_for_order(&client_order_id) {
2942            Some(position) => Ok(Some(position.clone().into_pyobject(py)?.into())),
2943            None => Ok(None),
2944        }
2945    }
2946
2947    /// Returns a reference to the position ID for the `client_order_id` (if found).
2948    #[pyo3(name = "position_id")]
2949    fn py_position_id(&self, client_order_id: ClientOrderId) -> Option<PositionId> {
2950        self.position_id(&client_order_id).copied()
2951    }
2952
2953    /// Returns borrows of all positions matching the optional filter parameters.
2954    ///
2955    /// Prefer `Self.positions_refs` in new native code.
2956    #[pyo3(name = "positions")]
2957    fn py_positions(
2958        &self,
2959        py: Python,
2960        venue: Option<Venue>,
2961        instrument_id: Option<InstrumentId>,
2962        strategy_id: Option<StrategyId>,
2963        account_id: Option<AccountId>,
2964        side: Option<PositionSide>,
2965    ) -> PyResult<Vec<Py<PyAny>>> {
2966        self.positions(
2967            venue.as_ref(),
2968            instrument_id.as_ref(),
2969            strategy_id.as_ref(),
2970            account_id.as_ref(),
2971            side,
2972        )
2973        .into_iter()
2974        .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
2975        .collect()
2976    }
2977
2978    /// Returns borrows of all open positions matching the optional filter parameters.
2979    ///
2980    /// Prefer `Self.positions_open_refs` in new native code.
2981    #[pyo3(name = "positions_open")]
2982    fn py_positions_open(
2983        &self,
2984        py: Python,
2985        venue: Option<Venue>,
2986        instrument_id: Option<InstrumentId>,
2987        strategy_id: Option<StrategyId>,
2988        account_id: Option<AccountId>,
2989        side: Option<PositionSide>,
2990    ) -> PyResult<Vec<Py<PyAny>>> {
2991        self.positions_open(
2992            venue.as_ref(),
2993            instrument_id.as_ref(),
2994            strategy_id.as_ref(),
2995            account_id.as_ref(),
2996            side,
2997        )
2998        .into_iter()
2999        .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
3000        .collect()
3001    }
3002
3003    /// Returns borrows of all closed positions matching the optional filter parameters.
3004    ///
3005    /// Prefer `Self.positions_closed_refs` in new native code.
3006    #[pyo3(name = "positions_closed")]
3007    fn py_positions_closed(
3008        &self,
3009        py: Python,
3010        venue: Option<Venue>,
3011        instrument_id: Option<InstrumentId>,
3012        strategy_id: Option<StrategyId>,
3013        account_id: Option<AccountId>,
3014        side: Option<PositionSide>,
3015    ) -> PyResult<Vec<Py<PyAny>>> {
3016        self.positions_closed(
3017            venue.as_ref(),
3018            instrument_id.as_ref(),
3019            strategy_id.as_ref(),
3020            account_id.as_ref(),
3021            side,
3022        )
3023        .into_iter()
3024        .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
3025        .collect()
3026    }
3027
3028    /// Gets a reference to the strategy ID for the `client_order_id` (if found).
3029    #[pyo3(name = "strategy_id_for_order")]
3030    fn py_strategy_id_for_order(&self, client_order_id: ClientOrderId) -> Option<StrategyId> {
3031        self.strategy_id_for_order(&client_order_id).copied()
3032    }
3033
3034    /// Gets a reference to the strategy ID for the `position_id` (if found).
3035    #[pyo3(name = "strategy_id_for_position")]
3036    fn py_strategy_id_for_position(&self, position_id: PositionId) -> Option<StrategyId> {
3037        self.strategy_id_for_position(&position_id).copied()
3038    }
3039
3040    /// Gets the serialized position snapshot frames for the `position_id`.
3041    ///
3042    /// Each element in the returned vector is one JSON-encoded `Position` snapshot,
3043    /// in the order they were taken. Frames that fail to serialize are skipped with a warning.
3044    #[pyo3(name = "position_snapshot_bytes")]
3045    fn py_position_snapshot_bytes(&self, position_id: PositionId) -> Option<Vec<Vec<u8>>> {
3046        self.position_snapshot_bytes(&position_id)
3047    }
3048
3049    /// Returns all position snapshots with the given optional filters.
3050    ///
3051    /// When `position_id` is `Some`, only snapshots for that position are returned.
3052    /// When `account_id` is `Some`, snapshots are filtered to that account.
3053    #[pyo3(name = "position_snapshots", signature = (position_id=None, account_id=None))]
3054    fn py_position_snapshots(
3055        &self,
3056        py: Python,
3057        position_id: Option<PositionId>,
3058        account_id: Option<AccountId>,
3059    ) -> PyResult<Vec<Py<PyAny>>> {
3060        self.position_snapshots(position_id.as_ref(), account_id.as_ref())
3061            .into_iter()
3062            .map(|p| Ok(p.into_pyobject(py)?.into()))
3063            .collect()
3064    }
3065
3066    /// Returns a borrow of the account for the `account_id` (if found).
3067    ///
3068    /// Prefer `Self.account_ref` in new native code.
3069    #[pyo3(name = "account")]
3070    fn py_account(&self, py: Python, account_id: AccountId) -> PyResult<Option<Py<PyAny>>> {
3071        match self.account(&account_id) {
3072            Some(account) => Ok(Some(account_any_to_pyobject(py, account.clone())?)),
3073            None => Ok(None),
3074        }
3075    }
3076
3077    /// Returns a borrow of the account for the `venue` (if found).
3078    ///
3079    /// Returns `None` when more than one account is issued under the `venue`; look those
3080    /// accounts up by account ID instead.
3081    #[pyo3(name = "account_for_venue")]
3082    fn py_account_for_venue(&self, py: Python, venue: Venue) -> PyResult<Option<Py<PyAny>>> {
3083        match self.account_for_venue(&venue) {
3084            Some(account) => Ok(Some(account_any_to_pyobject(py, account.clone())?)),
3085            None => Ok(None),
3086        }
3087    }
3088
3089    /// Returns a reference to the account ID for the `venue` (if found).
3090    ///
3091    /// Returns `None` when more than one account is issued under the `venue`.
3092    #[pyo3(name = "account_id")]
3093    fn py_account_id(&self, venue: Venue) -> Option<AccountId> {
3094        self.account_id(&venue).copied()
3095    }
3096
3097    /// Gets a reference to the general value for the `key` (if found).
3098    ///
3099    /// # Errors
3100    ///
3101    /// Returns an error if the `key` is invalid.
3102    #[pyo3(name = "get")]
3103    fn py_get(&self, key: &str) -> PyResult<Option<Vec<u8>>> {
3104        match self.get(key).map_err(to_pyvalue_err)? {
3105            Some(bytes) => Ok(Some(bytes.to_vec())),
3106            None => Ok(None),
3107        }
3108    }
3109
3110    /// Adds a general `value` to the cache for the given `key`.
3111    #[pyo3(name = "add")]
3112    fn py_add_general(&mut self, key: &str, value: Vec<u8>) -> PyResult<()> {
3113        self.add(key, Bytes::from(value)).map_err(to_pyvalue_err)
3114    }
3115
3116    /// Returns the price for the `instrument_id` and `price_type` (if found).
3117    ///
3118    /// For `Mid`, returns `None` if no quote is cached or either price is a sentinel.
3119    /// For quote precision `p`, the midpoint has precision `p + 1` when exactly representable,
3120    /// otherwise `p`, rounded half-even if necessary. The fallback applies when the precision
3121    /// limit or raw range rules out `p + 1`, and when `p` is the maximum float precision (16),
3122    /// so a midpoint of a float-convertible quote stays float-convertible.
3123    #[pyo3(name = "price")]
3124    fn py_price(&self, instrument_id: InstrumentId, price_type: PriceType) -> Option<Price> {
3125        self.price(&instrument_id, price_type)
3126    }
3127
3128    /// Returns the exchange rate for the given parameters.
3129    #[pyo3(name = "get_xrate")]
3130    fn py_get_xrate(
3131        &self,
3132        venue: Venue,
3133        from_currency: Currency,
3134        to_currency: Currency,
3135        price_type: PriceType,
3136    ) -> Option<f64> {
3137        self.get_xrate(venue, from_currency, to_currency, price_type)
3138            .and_then(|rate| rate.to_f64())
3139    }
3140
3141    /// Returns the mark exchange rate for the given currency pair, or `None` if not set.
3142    #[pyo3(name = "get_mark_xrate")]
3143    fn py_get_mark_xrate(&self, from_currency: Currency, to_currency: Currency) -> Option<f64> {
3144        self.get_mark_xrate(from_currency, to_currency)
3145    }
3146
3147    /// Sets the mark exchange rate for the given currency pair and automatically sets the inverse rate.
3148    #[pyo3(name = "set_mark_xrate")]
3149    fn py_set_mark_xrate(&mut self, from_currency: Currency, to_currency: Currency, xrate: f64) {
3150        self.set_mark_xrate(from_currency, to_currency, xrate);
3151    }
3152
3153    /// Clears the mark exchange rate for the given currency pair direction.
3154    ///
3155    /// Removes only the `(from_currency, to_currency)` entry; the inverse rate written
3156    /// by `Self.set_mark_xrate` is retained until cleared separately or
3157    /// `Self.clear_mark_xrates` is called.
3158    #[pyo3(name = "clear_mark_xrate")]
3159    fn py_clear_mark_xrate(&mut self, from_currency: Currency, to_currency: Currency) {
3160        self.clear_mark_xrate(from_currency, to_currency);
3161    }
3162
3163    /// Clears all mark exchange rates.
3164    #[pyo3(name = "clear_mark_xrates")]
3165    fn py_clear_mark_xrates(&mut self) {
3166        self.clear_mark_xrates();
3167    }
3168
3169    /// Calculates the unrealized PnL for the given position.
3170    #[pyo3(name = "calculate_unrealized_pnl")]
3171    #[expect(clippy::needless_pass_by_value)]
3172    fn py_calculate_unrealized_pnl(
3173        &self,
3174        py: Python,
3175        position: Py<PyAny>,
3176    ) -> PyResult<Option<Money>> {
3177        let position = position.extract::<Position>(py)?;
3178        Ok(self.calculate_unrealized_pnl(&position))
3179    }
3180
3181    /// Gets a reference to the own order book for the `instrument_id`.
3182    #[pyo3(name = "own_order_book")]
3183    fn py_own_order_book(&self, instrument_id: InstrumentId) -> Option<OwnOrderBook> {
3184        self.own_order_book(&instrument_id).cloned()
3185    }
3186
3187    /// Updates the own order book with an order.
3188    ///
3189    /// This method adds, updates, or removes an order from the own order book
3190    /// based on the order's current state.
3191    ///
3192    /// Orders without prices (MARKET, etc.) are skipped as they cannot be
3193    /// represented in own books.
3194    #[pyo3(name = "update_own_order_book")]
3195    fn py_update_own_order_book(&mut self, py: Python, order: Py<PyAny>) -> PyResult<()> {
3196        let order_any = pyobject_to_order_any(py, order)?;
3197        self.update_own_order_book(&order_any);
3198        Ok(())
3199    }
3200
3201    /// Force removal of an order from own order books and clean up all indexes.
3202    ///
3203    /// This method is used when order event application fails and we need to ensure
3204    /// terminal orders are properly cleaned up from own books and all relevant indexes.
3205    /// Replicates the index cleanup that `update_order` performs for closed orders.
3206    #[pyo3(name = "force_remove_from_own_order_book")]
3207    fn py_force_remove_from_own_order_book(&mut self, client_order_id: ClientOrderId) {
3208        self.force_remove_from_own_order_book(&client_order_id);
3209    }
3210
3211    /// Audit all own order books against active order indexes.
3212    ///
3213    /// Ensures orders absent from the open, inflight, and active-local indexes are removed from
3214    /// own order books.
3215    #[pyo3(name = "audit_own_order_books")]
3216    fn py_audit_own_order_books(&mut self) {
3217        self.audit_own_order_books();
3218    }
3219}
3220
3221#[cfg(feature = "defi")]
3222#[pymethods]
3223impl Cache {
3224    /// Adds a `Pool` to the cache.
3225    ///
3226    /// This function does not return errors; the result type matches the other add methods.
3227    #[pyo3(name = "add_pool")]
3228    fn py_add_pool(&mut self, pool: Pool) -> PyResult<()> {
3229        self.add_pool(pool).map_err(to_pyvalue_err)
3230    }
3231
3232    /// Gets a reference to the pool for the `instrument_id`.
3233    #[pyo3(name = "pool")]
3234    fn py_pool(&self, instrument_id: InstrumentId) -> Option<Pool> {
3235        self.pool(&instrument_id).cloned()
3236    }
3237
3238    /// Returns the instrument IDs of all pools in the cache, optionally filtered by `venue`.
3239    #[pyo3(name = "pool_ids")]
3240    fn py_pool_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
3241        self.pool_ids(venue.as_ref())
3242    }
3243
3244    /// Returns references to all pools in the cache, optionally filtered by `venue`.
3245    #[pyo3(name = "pools")]
3246    fn py_pools(&self, venue: Option<Venue>) -> Vec<Pool> {
3247        self.pools(venue.as_ref()).into_iter().cloned().collect()
3248    }
3249
3250    /// Adds a `PoolProfiler` to the cache.
3251    ///
3252    /// This function does not return errors; the result type matches the other add methods.
3253    #[pyo3(name = "add_pool_profiler")]
3254    fn py_add_pool_profiler(&mut self, pool_profiler: PoolProfiler) -> PyResult<()> {
3255        self.add_pool_profiler(pool_profiler)
3256            .map_err(to_pyvalue_err)
3257    }
3258
3259    /// Gets a reference to the pool profiler for the `instrument_id`.
3260    #[pyo3(name = "pool_profiler")]
3261    fn py_pool_profiler(&self, instrument_id: InstrumentId) -> Option<PoolProfiler> {
3262        self.pool_profiler(&instrument_id).cloned()
3263    }
3264
3265    /// Returns the instrument IDs of all pool profilers in the cache, optionally filtered by `venue`.
3266    #[pyo3(name = "pool_profiler_ids")]
3267    fn py_pool_profiler_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
3268        self.pool_profiler_ids(venue.as_ref())
3269    }
3270
3271    /// Returns references to all pool profilers in the cache, optionally filtered by `venue`.
3272    #[pyo3(name = "pool_profilers")]
3273    fn py_pool_profilers(&self, venue: Option<Venue>) -> Vec<PoolProfiler> {
3274        self.pool_profilers(venue.as_ref())
3275            .into_iter()
3276            .cloned()
3277            .collect()
3278    }
3279}