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