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