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(|_| ())
1124            .map_err(to_pyvalue_err)
1125    }
1126
1127    #[pyo3(name = "position_snapshots", signature = (position_id=None, account_id=None))]
1128    fn py_position_snapshots(
1129        &self,
1130        py: Python,
1131        position_id: Option<PositionId>,
1132        account_id: Option<AccountId>,
1133    ) -> PyResult<Vec<Py<PyAny>>> {
1134        let cache = self.0.borrow();
1135        cache
1136            .position_snapshots(position_id.as_ref(), account_id.as_ref())
1137            .into_iter()
1138            .map(|p| Ok(p.into_pyobject(py)?.into()))
1139            .collect()
1140    }
1141}
1142
1143#[cfg(feature = "defi")]
1144#[pymethods]
1145#[pyo3_stub_gen::derive::gen_stub_pymethods]
1146impl PyCache {
1147    #[pyo3(name = "pool")]
1148    fn py_pool(&self, instrument_id: InstrumentId) -> Option<Pool> {
1149        self.0
1150            .try_borrow()
1151            .ok()
1152            .and_then(|cache| cache.pool(&instrument_id).cloned())
1153    }
1154
1155    #[pyo3(name = "pool_profiler")]
1156    fn py_pool_profiler(&self, instrument_id: InstrumentId) -> Option<PoolProfiler> {
1157        self.0
1158            .try_borrow()
1159            .ok()
1160            .and_then(|cache| cache.pool_profiler(&instrument_id).cloned())
1161    }
1162}
1163
1164#[pymethods]
1165#[pyo3_stub_gen::derive::gen_stub_pymethods]
1166impl CacheConfig {
1167    /// Configuration for `Cache` instances.
1168    #[new]
1169    #[expect(clippy::too_many_arguments)]
1170    #[pyo3(signature = (
1171        encoding=None,
1172        timestamps_as_iso8601=None,
1173        buffer_interval_ms=None,
1174        bulk_read_batch_size=None,
1175        use_trader_prefix=None,
1176        use_instance_id=None,
1177        flush_on_start=None,
1178        drop_instruments_on_reset=None,
1179        tick_capacity=None,
1180        bar_capacity=None,
1181        save_market_data=None,
1182        persist_account_events=None,
1183    ))]
1184    fn py_new(
1185        encoding: Option<SerializationEncoding>,
1186        timestamps_as_iso8601: Option<bool>,
1187        buffer_interval_ms: Option<usize>,
1188        bulk_read_batch_size: Option<usize>,
1189        use_trader_prefix: Option<bool>,
1190        use_instance_id: Option<bool>,
1191        flush_on_start: Option<bool>,
1192        drop_instruments_on_reset: Option<bool>,
1193        tick_capacity: Option<usize>,
1194        bar_capacity: Option<usize>,
1195        save_market_data: Option<bool>,
1196        persist_account_events: Option<bool>,
1197    ) -> Self {
1198        Self::new(
1199            None, // database is None since we can't expose it to Python yet
1200            encoding.unwrap_or(SerializationEncoding::MsgPack),
1201            timestamps_as_iso8601.unwrap_or(false),
1202            buffer_interval_ms,
1203            bulk_read_batch_size,
1204            use_trader_prefix.unwrap_or(true),
1205            use_instance_id.unwrap_or(false),
1206            flush_on_start.unwrap_or(false),
1207            drop_instruments_on_reset.unwrap_or(true),
1208            tick_capacity.unwrap_or(10_000),
1209            bar_capacity.unwrap_or(10_000),
1210            persist_account_events.unwrap_or(true),
1211            save_market_data.unwrap_or(false),
1212        )
1213    }
1214
1215    fn __str__(&self) -> String {
1216        format!("{self:?}")
1217    }
1218
1219    fn __repr__(&self) -> String {
1220        format!("{self:?}")
1221    }
1222
1223    #[getter]
1224    fn encoding(&self) -> SerializationEncoding {
1225        self.encoding
1226    }
1227
1228    #[getter]
1229    fn timestamps_as_iso8601(&self) -> bool {
1230        self.timestamps_as_iso8601
1231    }
1232
1233    #[getter]
1234    fn buffer_interval_ms(&self) -> Option<usize> {
1235        self.buffer_interval_ms
1236    }
1237
1238    #[getter]
1239    fn bulk_read_batch_size(&self) -> Option<usize> {
1240        self.bulk_read_batch_size
1241    }
1242
1243    #[getter]
1244    fn use_trader_prefix(&self) -> bool {
1245        self.use_trader_prefix
1246    }
1247
1248    #[getter]
1249    fn use_instance_id(&self) -> bool {
1250        self.use_instance_id
1251    }
1252
1253    #[getter]
1254    fn flush_on_start(&self) -> bool {
1255        self.flush_on_start
1256    }
1257
1258    #[getter]
1259    fn drop_instruments_on_reset(&self) -> bool {
1260        self.drop_instruments_on_reset
1261    }
1262
1263    #[getter]
1264    fn tick_capacity(&self) -> usize {
1265        self.tick_capacity
1266    }
1267
1268    #[getter]
1269    fn bar_capacity(&self) -> usize {
1270        self.bar_capacity
1271    }
1272
1273    #[getter]
1274    fn persist_account_events(&self) -> bool {
1275        self.persist_account_events
1276    }
1277
1278    #[getter]
1279    fn save_market_data(&self) -> bool {
1280        self.save_market_data
1281    }
1282}
1283
1284#[pymethods]
1285impl Cache {
1286    /// A common in-memory `Cache` for market and execution related data.
1287    #[new]
1288    fn py_new(config: Option<CacheConfig>) -> Self {
1289        Self::new(config, None)
1290    }
1291
1292    fn __repr__(&self) -> String {
1293        format!("{self:?}")
1294    }
1295
1296    /// Resets the cache.
1297    ///
1298    /// All stateful fields are reset to their initial value. Instruments,
1299    /// currencies and synthetics are retained when `drop_instruments_on_reset`
1300    /// is `false` so that repeated backtest runs can reuse the same dataset.
1301    #[pyo3(name = "reset")]
1302    fn py_reset(&mut self) {
1303        self.reset();
1304    }
1305
1306    /// Dispose of the cache which will close any underlying database adapter.
1307    ///
1308    /// If closing the database connection fails, an error is logged.
1309    #[pyo3(name = "dispose")]
1310    fn py_dispose(&mut self) {
1311        self.dispose();
1312    }
1313
1314    /// Adds the `currency` to the cache.
1315    ///
1316    /// # Errors
1317    ///
1318    /// Returns an error if persisting the currency to the backing database fails.
1319    #[pyo3(name = "add_currency")]
1320    fn py_add_currency(&mut self, currency: Currency) -> PyResult<()> {
1321        self.add_currency(currency).map_err(to_pyvalue_err)
1322    }
1323
1324    /// Adds the `instrument` to the cache.
1325    ///
1326    /// # Errors
1327    ///
1328    /// Returns an error if persisting the instrument to the backing database fails.
1329    #[pyo3(name = "add_instrument")]
1330    fn py_add_instrument(&mut self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
1331        let instrument_any = pyobject_to_instrument_any(py, instrument)?;
1332        self.add_instrument(instrument_any).map_err(to_pyvalue_err)
1333    }
1334
1335    /// Returns a reference to the instrument for the `instrument_id` (if found).
1336    #[pyo3(name = "instrument")]
1337    fn py_instrument(
1338        &self,
1339        py: Python,
1340        instrument_id: InstrumentId,
1341    ) -> PyResult<Option<Py<PyAny>>> {
1342        match self.instrument(&instrument_id) {
1343            Some(instrument) => Ok(Some(instrument_any_to_pyobject(py, instrument.clone())?)),
1344            None => Ok(None),
1345        }
1346    }
1347
1348    /// Returns references to all instrument IDs for the `venue`.
1349    #[pyo3(name = "instrument_ids")]
1350    fn py_instrument_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
1351        self.instrument_ids(venue.as_ref())
1352            .into_iter()
1353            .copied()
1354            .collect()
1355    }
1356
1357    /// Returns references to all instruments for the `venue`.
1358    #[pyo3(name = "instruments")]
1359    fn py_instruments(&self, py: Python, venue: Option<Venue>) -> PyResult<Vec<Py<PyAny>>> {
1360        let mut py_instruments = Vec::new();
1361
1362        if let Some(venue) = 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        } else {
1368            let instrument_ids = self.instrument_ids(None);
1369            for instrument_id in instrument_ids {
1370                if let Some(instrument) = self.instrument(instrument_id) {
1371                    py_instruments.push(instrument_any_to_pyobject(py, instrument.clone())?);
1372                }
1373            }
1374        }
1375
1376        Ok(py_instruments)
1377    }
1378
1379    /// Adds the `order` to the cache indexed with any given identifiers.
1380    ///
1381    /// # Parameters
1382    ///
1383    /// `override_existing`: If the added order should 'override' any existing order and replace
1384    /// it in the cache. This is currently used for emulated orders which are
1385    /// being released and transformed into another type.
1386    #[pyo3(name = "add_order")]
1387    fn py_add_order(
1388        &mut self,
1389        py: Python,
1390        order: Py<PyAny>,
1391        position_id: Option<PositionId>,
1392        client_id: Option<ClientId>,
1393        replace_existing: Option<bool>,
1394    ) -> PyResult<()> {
1395        let order_any = pyobject_to_order_any(py, order)?;
1396        self.add_order(
1397            order_any,
1398            position_id,
1399            client_id,
1400            replace_existing.unwrap_or(false),
1401        )
1402        .map_err(to_pyvalue_err)
1403    }
1404
1405    /// Gets a borrow of the order with the `client_order_id` (if found).
1406    ///
1407    /// The returned `OrderRef` is tied to the cache borrow's scope and panics at runtime if
1408    /// held across a mutation of the same order. Drop the borrow before dispatching events; if
1409    /// post-event state is required, perform a fresh lookup. Use `Self.order_owned` when an
1410    /// owned snapshot is needed for a boundary handover.
1411    #[pyo3(name = "order")]
1412    fn py_order(&self, py: Python, client_order_id: ClientOrderId) -> PyResult<Option<Py<PyAny>>> {
1413        match self.order(&client_order_id) {
1414            Some(order) => Ok(Some(order_any_to_pyobject(py, order.clone())?)),
1415            None => Ok(None),
1416        }
1417    }
1418
1419    /// Returns whether an order with the `client_order_id` exists.
1420    #[pyo3(name = "order_exists")]
1421    fn py_order_exists(&self, client_order_id: ClientOrderId) -> bool {
1422        self.order_exists(&client_order_id)
1423    }
1424
1425    /// Returns whether an order with the `client_order_id` is open.
1426    #[pyo3(name = "is_order_open")]
1427    fn py_is_order_open(&self, client_order_id: ClientOrderId) -> bool {
1428        self.is_order_open(&client_order_id)
1429    }
1430
1431    /// Returns whether an order with the `client_order_id` is closed.
1432    #[pyo3(name = "is_order_closed")]
1433    fn py_is_order_closed(&self, client_order_id: ClientOrderId) -> bool {
1434        self.is_order_closed(&client_order_id)
1435    }
1436
1437    /// Returns whether an order with the `client_order_id` is locally active.
1438    ///
1439    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
1440    /// (a superset of emulated orders).
1441    #[pyo3(name = "is_order_active_local")]
1442    fn py_is_order_active_local(&self, client_order_id: ClientOrderId) -> bool {
1443        self.is_order_active_local(&client_order_id)
1444    }
1445
1446    /// Returns borrows of all locally active orders matching the optional filter parameters.
1447    ///
1448    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
1449    /// (a superset of emulated orders).
1450    #[pyo3(name = "orders_active_local")]
1451    fn py_orders_active_local(
1452        &self,
1453        py: Python,
1454        venue: Option<Venue>,
1455        instrument_id: Option<InstrumentId>,
1456        strategy_id: Option<StrategyId>,
1457        account_id: Option<AccountId>,
1458        side: Option<OrderSide>,
1459    ) -> PyResult<Vec<Py<PyAny>>> {
1460        self.orders_active_local(
1461            venue.as_ref(),
1462            instrument_id.as_ref(),
1463            strategy_id.as_ref(),
1464            account_id.as_ref(),
1465            side,
1466        )
1467        .into_iter()
1468        .map(|order| order_any_to_pyobject(py, order.clone()))
1469        .collect()
1470    }
1471
1472    /// Returns the count of all locally active orders.
1473    ///
1474    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
1475    /// (a superset of emulated orders).
1476    #[pyo3(name = "orders_active_local_count")]
1477    fn py_orders_active_local_count(
1478        &self,
1479        venue: Option<Venue>,
1480        instrument_id: Option<InstrumentId>,
1481        strategy_id: Option<StrategyId>,
1482        account_id: Option<AccountId>,
1483        side: Option<OrderSide>,
1484    ) -> usize {
1485        self.orders_active_local_count(
1486            venue.as_ref(),
1487            instrument_id.as_ref(),
1488            strategy_id.as_ref(),
1489            account_id.as_ref(),
1490            side,
1491        )
1492    }
1493
1494    /// Returns the count of all open orders.
1495    #[pyo3(name = "orders_open_count")]
1496    fn py_orders_open_count(
1497        &self,
1498        venue: Option<Venue>,
1499        instrument_id: Option<InstrumentId>,
1500        strategy_id: Option<StrategyId>,
1501        account_id: Option<AccountId>,
1502        side: Option<OrderSide>,
1503    ) -> usize {
1504        self.orders_open_count(
1505            venue.as_ref(),
1506            instrument_id.as_ref(),
1507            strategy_id.as_ref(),
1508            account_id.as_ref(),
1509            side,
1510        )
1511    }
1512
1513    /// Returns the count of all closed orders.
1514    #[pyo3(name = "orders_closed_count")]
1515    fn py_orders_closed_count(
1516        &self,
1517        venue: Option<Venue>,
1518        instrument_id: Option<InstrumentId>,
1519        strategy_id: Option<StrategyId>,
1520        account_id: Option<AccountId>,
1521        side: Option<OrderSide>,
1522    ) -> usize {
1523        self.orders_closed_count(
1524            venue.as_ref(),
1525            instrument_id.as_ref(),
1526            strategy_id.as_ref(),
1527            account_id.as_ref(),
1528            side,
1529        )
1530    }
1531
1532    /// Returns the count of all orders.
1533    #[pyo3(name = "orders_total_count")]
1534    fn py_orders_total_count(
1535        &self,
1536        venue: Option<Venue>,
1537        instrument_id: Option<InstrumentId>,
1538        strategy_id: Option<StrategyId>,
1539        account_id: Option<AccountId>,
1540        side: Option<OrderSide>,
1541    ) -> usize {
1542        self.orders_total_count(
1543            venue.as_ref(),
1544            instrument_id.as_ref(),
1545            strategy_id.as_ref(),
1546            account_id.as_ref(),
1547            side,
1548        )
1549    }
1550
1551    /// Adds the `position` to the cache.
1552    #[pyo3(name = "add_position")]
1553    #[expect(clippy::needless_pass_by_value)]
1554    fn py_add_position(
1555        &mut self,
1556        py: Python,
1557        position: Py<PyAny>,
1558        oms_type: OmsType,
1559    ) -> PyResult<()> {
1560        let position_obj = position.extract::<Position>(py)?;
1561        self.add_position(&position_obj, oms_type)
1562            .map_err(to_pyvalue_err)
1563    }
1564
1565    /// Creates a snapshot of the `position` by cloning it, assigning a new ID,
1566    /// serializing it, and storing it in the position snapshots.
1567    ///
1568    /// # Errors
1569    ///
1570    /// Returns an error if serializing or storing the position snapshot fails.
1571    #[pyo3(name = "snapshot_position")]
1572    #[expect(clippy::needless_pass_by_value)]
1573    fn py_snapshot_position(&mut self, py: Python, position: Py<PyAny>) -> PyResult<()> {
1574        let position_obj = position.extract::<Position>(py)?;
1575        self.snapshot_position(&position_obj)
1576            .map(|_| ())
1577            .map_err(to_pyvalue_err)
1578    }
1579
1580    /// Returns a borrow of the position with the `position_id` (if found).
1581    #[pyo3(name = "position")]
1582    fn py_position(&self, py: Python, position_id: PositionId) -> PyResult<Option<Py<PyAny>>> {
1583        match self.position(&position_id) {
1584            Some(position) => Ok(Some(position.clone().into_pyobject(py)?.into())),
1585            None => Ok(None),
1586        }
1587    }
1588
1589    /// Returns whether a position with the `position_id` exists.
1590    #[pyo3(name = "position_exists")]
1591    fn py_position_exists(&self, position_id: PositionId) -> bool {
1592        self.position_exists(&position_id)
1593    }
1594
1595    /// Returns whether a position with the `position_id` is open.
1596    #[pyo3(name = "is_position_open")]
1597    fn py_is_position_open(&self, position_id: PositionId) -> bool {
1598        self.is_position_open(&position_id)
1599    }
1600
1601    /// Returns whether a position with the `position_id` is closed.
1602    #[pyo3(name = "is_position_closed")]
1603    fn py_is_position_closed(&self, position_id: PositionId) -> bool {
1604        self.is_position_closed(&position_id)
1605    }
1606
1607    /// Returns the count of all open positions.
1608    #[pyo3(name = "positions_open_count")]
1609    fn py_positions_open_count(
1610        &self,
1611        venue: Option<Venue>,
1612        instrument_id: Option<InstrumentId>,
1613        strategy_id: Option<StrategyId>,
1614        account_id: Option<AccountId>,
1615        side: Option<PositionSide>,
1616    ) -> usize {
1617        self.positions_open_count(
1618            venue.as_ref(),
1619            instrument_id.as_ref(),
1620            strategy_id.as_ref(),
1621            account_id.as_ref(),
1622            side,
1623        )
1624    }
1625
1626    /// Returns the count of all closed positions.
1627    #[pyo3(name = "positions_closed_count")]
1628    fn py_positions_closed_count(
1629        &self,
1630        venue: Option<Venue>,
1631        instrument_id: Option<InstrumentId>,
1632        strategy_id: Option<StrategyId>,
1633        account_id: Option<AccountId>,
1634        side: Option<PositionSide>,
1635    ) -> usize {
1636        self.positions_closed_count(
1637            venue.as_ref(),
1638            instrument_id.as_ref(),
1639            strategy_id.as_ref(),
1640            account_id.as_ref(),
1641            side,
1642        )
1643    }
1644
1645    /// Returns the count of all positions.
1646    #[pyo3(name = "positions_total_count")]
1647    fn py_positions_total_count(
1648        &self,
1649        venue: Option<Venue>,
1650        instrument_id: Option<InstrumentId>,
1651        strategy_id: Option<StrategyId>,
1652        account_id: Option<AccountId>,
1653        side: Option<PositionSide>,
1654    ) -> usize {
1655        self.positions_total_count(
1656            venue.as_ref(),
1657            instrument_id.as_ref(),
1658            strategy_id.as_ref(),
1659            account_id.as_ref(),
1660            side,
1661        )
1662    }
1663
1664    /// Adds the `quote` tick to the cache.
1665    ///
1666    /// # Errors
1667    ///
1668    /// Returns an error if persisting the quote tick to the backing database fails.
1669    #[pyo3(name = "add_quote")]
1670    fn py_add_quote(&mut self, quote: QuoteTick) -> PyResult<()> {
1671        self.add_quote(quote).map_err(to_pyvalue_err)
1672    }
1673
1674    /// Adds the `trade` tick to the cache.
1675    ///
1676    /// # Errors
1677    ///
1678    /// Returns an error if persisting the trade tick to the backing database fails.
1679    #[pyo3(name = "add_trade")]
1680    fn py_add_trade(&mut self, trade: TradeTick) -> PyResult<()> {
1681        self.add_trade(trade).map_err(to_pyvalue_err)
1682    }
1683
1684    /// Adds the `bar` to the cache.
1685    ///
1686    /// # Errors
1687    ///
1688    /// Returns an error if persisting the bar to the backing database fails.
1689    #[pyo3(name = "add_bar")]
1690    fn py_add_bar(&mut self, bar: Bar) -> PyResult<()> {
1691        self.add_bar(bar).map_err(to_pyvalue_err)
1692    }
1693
1694    /// Gets a reference to the latest quote for the `instrument_id`.
1695    #[pyo3(name = "quote")]
1696    fn py_quote(&self, instrument_id: InstrumentId) -> Option<QuoteTick> {
1697        self.quote(&instrument_id).copied()
1698    }
1699
1700    /// Gets a reference to the latest trade for the `instrument_id`.
1701    #[pyo3(name = "trade")]
1702    fn py_trade(&self, instrument_id: InstrumentId) -> Option<TradeTick> {
1703        self.trade(&instrument_id).copied()
1704    }
1705
1706    /// Gets a reference to the latest bar for the `bar_type`.
1707    #[pyo3(name = "bar")]
1708    fn py_bar(&self, bar_type: BarType) -> Option<Bar> {
1709        self.bar(&bar_type).copied()
1710    }
1711
1712    /// Gets all quotes for the `instrument_id`.
1713    #[pyo3(name = "quotes")]
1714    fn py_quotes(&self, instrument_id: InstrumentId) -> Option<Vec<QuoteTick>> {
1715        self.quotes(&instrument_id)
1716    }
1717
1718    /// Gets all trades for the `instrument_id`.
1719    #[pyo3(name = "trades")]
1720    fn py_trades(&self, instrument_id: InstrumentId) -> Option<Vec<TradeTick>> {
1721        self.trades(&instrument_id)
1722    }
1723
1724    /// Gets all bars for the `bar_type`.
1725    #[pyo3(name = "bars")]
1726    fn py_bars(&self, bar_type: BarType) -> Option<Vec<Bar>> {
1727        self.bars(&bar_type)
1728    }
1729
1730    /// Returns whether the cache contains quotes for the `instrument_id`.
1731    #[pyo3(name = "has_quote_ticks")]
1732    fn py_has_quote_ticks(&self, instrument_id: InstrumentId) -> bool {
1733        self.has_quote_ticks(&instrument_id)
1734    }
1735
1736    /// Returns whether the cache contains trades for the `instrument_id`.
1737    #[pyo3(name = "has_trade_ticks")]
1738    fn py_has_trade_ticks(&self, instrument_id: InstrumentId) -> bool {
1739        self.has_trade_ticks(&instrument_id)
1740    }
1741
1742    /// Returns whether the cache contains bars for the `bar_type`.
1743    #[pyo3(name = "has_bars")]
1744    fn py_has_bars(&self, bar_type: BarType) -> bool {
1745        self.has_bars(&bar_type)
1746    }
1747
1748    /// Gets the quote tick count for the `instrument_id`.
1749    #[pyo3(name = "quote_count")]
1750    fn py_quote_count(&self, instrument_id: InstrumentId) -> usize {
1751        self.quote_count(&instrument_id)
1752    }
1753
1754    /// Gets the trade tick count for the `instrument_id`.
1755    #[pyo3(name = "trade_count")]
1756    fn py_trade_count(&self, instrument_id: InstrumentId) -> usize {
1757        self.trade_count(&instrument_id)
1758    }
1759
1760    /// Gets the bar count for the `instrument_id`.
1761    #[pyo3(name = "bar_count")]
1762    fn py_bar_count(&self, bar_type: BarType) -> usize {
1763        self.bar_count(&bar_type)
1764    }
1765
1766    /// Gets a reference to the latest mark price update for the `instrument_id`.
1767    #[pyo3(name = "mark_price")]
1768    fn py_mark_price(&self, instrument_id: InstrumentId) -> Option<MarkPriceUpdate> {
1769        self.mark_price(&instrument_id).copied()
1770    }
1771
1772    /// Gets all mark price updates for the `instrument_id`.
1773    #[pyo3(name = "mark_prices")]
1774    fn py_mark_prices(&self, instrument_id: InstrumentId) -> Option<Vec<MarkPriceUpdate>> {
1775        self.mark_prices(&instrument_id)
1776    }
1777
1778    /// Gets a reference to the latest index price update for the `instrument_id`.
1779    #[pyo3(name = "index_price")]
1780    fn py_index_price(&self, instrument_id: InstrumentId) -> Option<IndexPriceUpdate> {
1781        self.index_price(&instrument_id).copied()
1782    }
1783
1784    /// Gets all index price updates for the `instrument_id`.
1785    #[pyo3(name = "index_prices")]
1786    fn py_index_prices(&self, instrument_id: InstrumentId) -> Option<Vec<IndexPriceUpdate>> {
1787        self.index_prices(&instrument_id)
1788    }
1789
1790    /// Gets a reference to the latest funding rate update for the `instrument_id`.
1791    #[pyo3(name = "funding_rate")]
1792    fn py_funding_rate(&self, instrument_id: InstrumentId) -> Option<FundingRateUpdate> {
1793        self.funding_rate(&instrument_id).copied()
1794    }
1795
1796    /// Gets a reference to the latest instrument status update for the `instrument_id`.
1797    #[pyo3(name = "instrument_status")]
1798    fn py_instrument_status(&self, instrument_id: InstrumentId) -> Option<InstrumentStatus> {
1799        self.instrument_status(&instrument_id).copied()
1800    }
1801
1802    /// Gets all instrument status updates for the `instrument_id`.
1803    #[pyo3(name = "instrument_statuses")]
1804    fn py_instrument_statuses(&self, instrument_id: InstrumentId) -> Option<Vec<InstrumentStatus>> {
1805        self.instrument_statuses(&instrument_id)
1806    }
1807
1808    /// Gets a reference to the order book for the `instrument_id`.
1809    #[pyo3(name = "order_book")]
1810    fn py_order_book(&self, instrument_id: InstrumentId) -> Option<OrderBook> {
1811        self.order_book(&instrument_id).cloned()
1812    }
1813
1814    /// Returns whether the cache contains an order book for the `instrument_id`.
1815    #[pyo3(name = "has_order_book")]
1816    fn py_has_order_book(&self, instrument_id: InstrumentId) -> bool {
1817        self.has_order_book(&instrument_id)
1818    }
1819
1820    /// Gets the order book update count for the `instrument_id`.
1821    #[pyo3(name = "book_update_count")]
1822    fn py_book_update_count(&self, instrument_id: InstrumentId) -> usize {
1823        self.book_update_count(&instrument_id)
1824    }
1825
1826    /// Returns a reference to the synthetic instrument for the `instrument_id` (if found).
1827    #[pyo3(name = "synthetic")]
1828    fn py_synthetic(&self, instrument_id: InstrumentId) -> Option<SyntheticInstrument> {
1829        self.synthetic(&instrument_id).cloned()
1830    }
1831
1832    /// Returns references to instrument IDs for all synthetic instruments contained in the cache.
1833    #[pyo3(name = "synthetic_ids")]
1834    fn py_synthetic_ids(&self) -> Vec<InstrumentId> {
1835        self.synthetic_ids().into_iter().copied().collect()
1836    }
1837
1838    /// Returns the `ClientOrderId`s of all orders.
1839    #[pyo3(name = "client_order_ids")]
1840    fn py_client_order_ids(
1841        &self,
1842        venue: Option<Venue>,
1843        instrument_id: Option<InstrumentId>,
1844        strategy_id: Option<StrategyId>,
1845        account_id: Option<AccountId>,
1846    ) -> Vec<ClientOrderId> {
1847        self.client_order_ids(
1848            venue.as_ref(),
1849            instrument_id.as_ref(),
1850            strategy_id.as_ref(),
1851            account_id.as_ref(),
1852        )
1853        .into_iter()
1854        .collect()
1855    }
1856
1857    /// Returns the `ClientOrderId`s of all open orders.
1858    #[pyo3(name = "client_order_ids_open")]
1859    fn py_client_order_ids_open(
1860        &self,
1861        venue: Option<Venue>,
1862        instrument_id: Option<InstrumentId>,
1863        strategy_id: Option<StrategyId>,
1864        account_id: Option<AccountId>,
1865    ) -> Vec<ClientOrderId> {
1866        self.client_order_ids_open(
1867            venue.as_ref(),
1868            instrument_id.as_ref(),
1869            strategy_id.as_ref(),
1870            account_id.as_ref(),
1871        )
1872        .into_iter()
1873        .collect()
1874    }
1875
1876    /// Returns the `ClientOrderId`s of all closed orders.
1877    #[pyo3(name = "client_order_ids_closed")]
1878    fn py_client_order_ids_closed(
1879        &self,
1880        venue: Option<Venue>,
1881        instrument_id: Option<InstrumentId>,
1882        strategy_id: Option<StrategyId>,
1883        account_id: Option<AccountId>,
1884    ) -> Vec<ClientOrderId> {
1885        self.client_order_ids_closed(
1886            venue.as_ref(),
1887            instrument_id.as_ref(),
1888            strategy_id.as_ref(),
1889            account_id.as_ref(),
1890        )
1891        .into_iter()
1892        .collect()
1893    }
1894
1895    /// Returns the `ClientOrderId`s of all emulated orders.
1896    #[pyo3(name = "client_order_ids_emulated")]
1897    fn py_client_order_ids_emulated(
1898        &self,
1899        venue: Option<Venue>,
1900        instrument_id: Option<InstrumentId>,
1901        strategy_id: Option<StrategyId>,
1902        account_id: Option<AccountId>,
1903    ) -> Vec<ClientOrderId> {
1904        self.client_order_ids_emulated(
1905            venue.as_ref(),
1906            instrument_id.as_ref(),
1907            strategy_id.as_ref(),
1908            account_id.as_ref(),
1909        )
1910        .into_iter()
1911        .collect()
1912    }
1913
1914    /// Returns the `ClientOrderId`s of all in-flight orders.
1915    #[pyo3(name = "client_order_ids_inflight")]
1916    fn py_client_order_ids_inflight(
1917        &self,
1918        venue: Option<Venue>,
1919        instrument_id: Option<InstrumentId>,
1920        strategy_id: Option<StrategyId>,
1921        account_id: Option<AccountId>,
1922    ) -> Vec<ClientOrderId> {
1923        self.client_order_ids_inflight(
1924            venue.as_ref(),
1925            instrument_id.as_ref(),
1926            strategy_id.as_ref(),
1927            account_id.as_ref(),
1928        )
1929        .into_iter()
1930        .collect()
1931    }
1932
1933    /// Returns `PositionId`s of all positions.
1934    #[pyo3(name = "position_ids")]
1935    fn py_position_ids(
1936        &self,
1937        venue: Option<Venue>,
1938        instrument_id: Option<InstrumentId>,
1939        strategy_id: Option<StrategyId>,
1940        account_id: Option<AccountId>,
1941    ) -> Vec<PositionId> {
1942        self.position_ids(
1943            venue.as_ref(),
1944            instrument_id.as_ref(),
1945            strategy_id.as_ref(),
1946            account_id.as_ref(),
1947        )
1948        .into_iter()
1949        .collect()
1950    }
1951
1952    /// Returns the `PositionId`s of all open positions.
1953    #[pyo3(name = "position_open_ids")]
1954    fn py_position_open_ids(
1955        &self,
1956        venue: Option<Venue>,
1957        instrument_id: Option<InstrumentId>,
1958        strategy_id: Option<StrategyId>,
1959        account_id: Option<AccountId>,
1960    ) -> Vec<PositionId> {
1961        self.position_open_ids(
1962            venue.as_ref(),
1963            instrument_id.as_ref(),
1964            strategy_id.as_ref(),
1965            account_id.as_ref(),
1966        )
1967        .into_iter()
1968        .collect()
1969    }
1970
1971    /// Returns the `PositionId`s of all closed positions.
1972    #[pyo3(name = "position_closed_ids")]
1973    fn py_position_closed_ids(
1974        &self,
1975        venue: Option<Venue>,
1976        instrument_id: Option<InstrumentId>,
1977        strategy_id: Option<StrategyId>,
1978        account_id: Option<AccountId>,
1979    ) -> Vec<PositionId> {
1980        self.position_closed_ids(
1981            venue.as_ref(),
1982            instrument_id.as_ref(),
1983            strategy_id.as_ref(),
1984            account_id.as_ref(),
1985        )
1986        .into_iter()
1987        .collect()
1988    }
1989
1990    /// Returns the `ComponentId`s of all actors.
1991    #[pyo3(name = "actor_ids")]
1992    fn py_actor_ids(&self) -> Vec<ComponentId> {
1993        self.actor_ids().into_iter().collect()
1994    }
1995
1996    /// Returns the `StrategyId`s of all strategies.
1997    #[pyo3(name = "strategy_ids")]
1998    fn py_strategy_ids(&self) -> Vec<StrategyId> {
1999        self.strategy_ids().into_iter().collect()
2000    }
2001
2002    /// Returns the `ExecAlgorithmId`s of all execution algorithms.
2003    #[pyo3(name = "exec_algorithm_ids")]
2004    fn py_exec_algorithm_ids(&self) -> Vec<ExecAlgorithmId> {
2005        self.exec_algorithm_ids().into_iter().collect()
2006    }
2007
2008    /// Gets a reference to the client order ID for the `venue_order_id` (if found).
2009    #[pyo3(name = "client_order_id")]
2010    fn py_client_order_id(&self, venue_order_id: VenueOrderId) -> Option<ClientOrderId> {
2011        self.client_order_id(&venue_order_id).copied()
2012    }
2013
2014    /// Gets a reference to the venue order ID for the `client_order_id` (if found).
2015    #[pyo3(name = "venue_order_id")]
2016    fn py_venue_order_id(&self, client_order_id: ClientOrderId) -> Option<VenueOrderId> {
2017        self.venue_order_id(&client_order_id).copied()
2018    }
2019
2020    /// Gets a reference to the client ID indexed for then `client_order_id` (if found).
2021    #[pyo3(name = "client_id")]
2022    fn py_client_id(&self, client_order_id: ClientOrderId) -> Option<ClientId> {
2023        self.client_id(&client_order_id).copied()
2024    }
2025
2026    /// Returns borrows of all orders matching the optional filter parameters.
2027    ///
2028    /// Each `Ref` in the returned vector borrows its underlying cell; mutating any of
2029    /// those orders while the vector is alive will panic at runtime. Drop the vector
2030    /// before issuing writes.
2031    #[pyo3(name = "orders")]
2032    fn py_orders(
2033        &self,
2034        py: Python,
2035        venue: Option<Venue>,
2036        instrument_id: Option<InstrumentId>,
2037        strategy_id: Option<StrategyId>,
2038        account_id: Option<AccountId>,
2039        side: Option<OrderSide>,
2040    ) -> PyResult<Vec<Py<PyAny>>> {
2041        self.orders(
2042            venue.as_ref(),
2043            instrument_id.as_ref(),
2044            strategy_id.as_ref(),
2045            account_id.as_ref(),
2046            side,
2047        )
2048        .into_iter()
2049        .map(|o| order_any_to_pyobject(py, o.clone()))
2050        .collect()
2051    }
2052
2053    /// Returns borrows of all open orders matching the optional filter parameters.
2054    #[pyo3(name = "orders_open")]
2055    fn py_orders_open(
2056        &self,
2057        py: Python,
2058        venue: Option<Venue>,
2059        instrument_id: Option<InstrumentId>,
2060        strategy_id: Option<StrategyId>,
2061        account_id: Option<AccountId>,
2062        side: Option<OrderSide>,
2063    ) -> PyResult<Vec<Py<PyAny>>> {
2064        self.orders_open(
2065            venue.as_ref(),
2066            instrument_id.as_ref(),
2067            strategy_id.as_ref(),
2068            account_id.as_ref(),
2069            side,
2070        )
2071        .into_iter()
2072        .map(|o| order_any_to_pyobject(py, o.clone()))
2073        .collect()
2074    }
2075
2076    /// Returns borrows of all closed orders matching the optional filter parameters.
2077    #[pyo3(name = "orders_closed")]
2078    fn py_orders_closed(
2079        &self,
2080        py: Python,
2081        venue: Option<Venue>,
2082        instrument_id: Option<InstrumentId>,
2083        strategy_id: Option<StrategyId>,
2084        account_id: Option<AccountId>,
2085        side: Option<OrderSide>,
2086    ) -> PyResult<Vec<Py<PyAny>>> {
2087        self.orders_closed(
2088            venue.as_ref(),
2089            instrument_id.as_ref(),
2090            strategy_id.as_ref(),
2091            account_id.as_ref(),
2092            side,
2093        )
2094        .into_iter()
2095        .map(|o| order_any_to_pyobject(py, o.clone()))
2096        .collect()
2097    }
2098
2099    /// Returns borrows of all emulated orders matching the optional filter parameters.
2100    #[pyo3(name = "orders_emulated")]
2101    fn py_orders_emulated(
2102        &self,
2103        py: Python,
2104        venue: Option<Venue>,
2105        instrument_id: Option<InstrumentId>,
2106        strategy_id: Option<StrategyId>,
2107        account_id: Option<AccountId>,
2108        side: Option<OrderSide>,
2109    ) -> PyResult<Vec<Py<PyAny>>> {
2110        self.orders_emulated(
2111            venue.as_ref(),
2112            instrument_id.as_ref(),
2113            strategy_id.as_ref(),
2114            account_id.as_ref(),
2115            side,
2116        )
2117        .into_iter()
2118        .map(|o| order_any_to_pyobject(py, o.clone()))
2119        .collect()
2120    }
2121
2122    /// Returns borrows of all in-flight orders matching the optional filter parameters.
2123    #[pyo3(name = "orders_inflight")]
2124    fn py_orders_inflight(
2125        &self,
2126        py: Python,
2127        venue: Option<Venue>,
2128        instrument_id: Option<InstrumentId>,
2129        strategy_id: Option<StrategyId>,
2130        account_id: Option<AccountId>,
2131        side: Option<OrderSide>,
2132    ) -> PyResult<Vec<Py<PyAny>>> {
2133        self.orders_inflight(
2134            venue.as_ref(),
2135            instrument_id.as_ref(),
2136            strategy_id.as_ref(),
2137            account_id.as_ref(),
2138            side,
2139        )
2140        .into_iter()
2141        .map(|o| order_any_to_pyobject(py, o.clone()))
2142        .collect()
2143    }
2144
2145    /// Returns borrows of all orders for the `position_id`.
2146    #[pyo3(name = "orders_for_position")]
2147    fn py_orders_for_position(
2148        &self,
2149        py: Python,
2150        position_id: PositionId,
2151    ) -> PyResult<Vec<Py<PyAny>>> {
2152        self.orders_for_position(&position_id)
2153            .into_iter()
2154            .map(|o| order_any_to_pyobject(py, o.clone()))
2155            .collect()
2156    }
2157
2158    /// Returns whether an order with the `client_order_id` is emulated.
2159    #[pyo3(name = "is_order_emulated")]
2160    fn py_is_order_emulated(&self, client_order_id: ClientOrderId) -> bool {
2161        self.is_order_emulated(&client_order_id)
2162    }
2163
2164    /// Returns whether an order with the `client_order_id` is in-flight.
2165    #[pyo3(name = "is_order_inflight")]
2166    fn py_is_order_inflight(&self, client_order_id: ClientOrderId) -> bool {
2167        self.is_order_inflight(&client_order_id)
2168    }
2169
2170    /// Returns whether an order with the `client_order_id` is `PENDING_CANCEL` locally.
2171    #[pyo3(name = "is_order_pending_cancel_local")]
2172    fn py_is_order_pending_cancel_local(&self, client_order_id: ClientOrderId) -> bool {
2173        self.is_order_pending_cancel_local(&client_order_id)
2174    }
2175
2176    /// Returns the count of all emulated orders.
2177    #[pyo3(name = "orders_emulated_count")]
2178    fn py_orders_emulated_count(
2179        &self,
2180        venue: Option<Venue>,
2181        instrument_id: Option<InstrumentId>,
2182        strategy_id: Option<StrategyId>,
2183        account_id: Option<AccountId>,
2184        side: Option<OrderSide>,
2185    ) -> usize {
2186        self.orders_emulated_count(
2187            venue.as_ref(),
2188            instrument_id.as_ref(),
2189            strategy_id.as_ref(),
2190            account_id.as_ref(),
2191            side,
2192        )
2193    }
2194
2195    /// Returns the count of all in-flight orders.
2196    #[pyo3(name = "orders_inflight_count")]
2197    fn py_orders_inflight_count(
2198        &self,
2199        venue: Option<Venue>,
2200        instrument_id: Option<InstrumentId>,
2201        strategy_id: Option<StrategyId>,
2202        account_id: Option<AccountId>,
2203        side: Option<OrderSide>,
2204    ) -> usize {
2205        self.orders_inflight_count(
2206            venue.as_ref(),
2207            instrument_id.as_ref(),
2208            strategy_id.as_ref(),
2209            account_id.as_ref(),
2210            side,
2211        )
2212    }
2213
2214    /// Returns the order list for the `order_list_id`.
2215    #[pyo3(name = "order_list")]
2216    fn py_order_list(&self, order_list_id: OrderListId) -> Option<OrderList> {
2217        self.order_list(&order_list_id).cloned()
2218    }
2219
2220    /// Returns all order lists matching the optional filter parameters.
2221    #[pyo3(name = "order_lists")]
2222    fn py_order_lists(
2223        &self,
2224        venue: Option<Venue>,
2225        instrument_id: Option<InstrumentId>,
2226        strategy_id: Option<StrategyId>,
2227        account_id: Option<AccountId>,
2228    ) -> Vec<OrderList> {
2229        self.order_lists(
2230            venue.as_ref(),
2231            instrument_id.as_ref(),
2232            strategy_id.as_ref(),
2233            account_id.as_ref(),
2234        )
2235        .into_iter()
2236        .cloned()
2237        .collect()
2238    }
2239
2240    /// Returns whether an order list with the `order_list_id` exists.
2241    #[pyo3(name = "order_list_exists")]
2242    fn py_order_list_exists(&self, order_list_id: OrderListId) -> bool {
2243        self.order_list_exists(&order_list_id)
2244    }
2245
2246    /// Returns references to all orders associated with the `exec_algorithm_id` matching the
2247    /// optional filter parameters.
2248    #[pyo3(name = "orders_for_exec_algorithm")]
2249    #[expect(clippy::too_many_arguments)]
2250    fn py_orders_for_exec_algorithm(
2251        &self,
2252        py: Python,
2253        exec_algorithm_id: ExecAlgorithmId,
2254        venue: Option<Venue>,
2255        instrument_id: Option<InstrumentId>,
2256        strategy_id: Option<StrategyId>,
2257        account_id: Option<AccountId>,
2258        side: Option<OrderSide>,
2259    ) -> PyResult<Vec<Py<PyAny>>> {
2260        self.orders_for_exec_algorithm(
2261            &exec_algorithm_id,
2262            venue.as_ref(),
2263            instrument_id.as_ref(),
2264            strategy_id.as_ref(),
2265            account_id.as_ref(),
2266            side,
2267        )
2268        .into_iter()
2269        .map(|o| order_any_to_pyobject(py, o.clone()))
2270        .collect()
2271    }
2272
2273    /// Returns references to all orders with the `exec_spawn_id`.
2274    #[pyo3(name = "orders_for_exec_spawn")]
2275    fn py_orders_for_exec_spawn(
2276        &self,
2277        py: Python,
2278        exec_spawn_id: ClientOrderId,
2279    ) -> PyResult<Vec<Py<PyAny>>> {
2280        self.orders_for_exec_spawn(&exec_spawn_id)
2281            .into_iter()
2282            .map(|o| order_any_to_pyobject(py, o.clone()))
2283            .collect()
2284    }
2285
2286    /// Returns the total order quantity for the `exec_spawn_id`.
2287    #[pyo3(name = "exec_spawn_total_quantity")]
2288    fn py_exec_spawn_total_quantity(
2289        &self,
2290        exec_spawn_id: ClientOrderId,
2291        active_only: bool,
2292    ) -> Option<Quantity> {
2293        self.exec_spawn_total_quantity(&exec_spawn_id, active_only)
2294    }
2295
2296    /// Returns the total filled quantity for all orders with the `exec_spawn_id`.
2297    #[pyo3(name = "exec_spawn_total_filled_qty")]
2298    fn py_exec_spawn_total_filled_qty(
2299        &self,
2300        exec_spawn_id: ClientOrderId,
2301        active_only: bool,
2302    ) -> Option<Quantity> {
2303        self.exec_spawn_total_filled_qty(&exec_spawn_id, active_only)
2304    }
2305
2306    /// Returns the total leaves quantity for all orders with the `exec_spawn_id`.
2307    #[pyo3(name = "exec_spawn_total_leaves_qty")]
2308    fn py_exec_spawn_total_leaves_qty(
2309        &self,
2310        exec_spawn_id: ClientOrderId,
2311        active_only: bool,
2312    ) -> Option<Quantity> {
2313        self.exec_spawn_total_leaves_qty(&exec_spawn_id, active_only)
2314    }
2315
2316    /// Returns a borrow of the position for the `client_order_id` (if found).
2317    #[pyo3(name = "position_for_order")]
2318    fn py_position_for_order(
2319        &self,
2320        py: Python,
2321        client_order_id: ClientOrderId,
2322    ) -> PyResult<Option<Py<PyAny>>> {
2323        match self.position_for_order(&client_order_id) {
2324            Some(position) => Ok(Some(position.clone().into_pyobject(py)?.into())),
2325            None => Ok(None),
2326        }
2327    }
2328
2329    /// Returns a reference to the position ID for the `client_order_id` (if found).
2330    #[pyo3(name = "position_id")]
2331    fn py_position_id(&self, client_order_id: ClientOrderId) -> Option<PositionId> {
2332        self.position_id(&client_order_id).copied()
2333    }
2334
2335    /// Returns borrows of all positions matching the optional filter parameters.
2336    ///
2337    /// Each `PositionRef` in the returned vector borrows its underlying cell; mutating any of
2338    /// those positions while the vector is alive will panic at runtime. Drop the vector before
2339    /// issuing writes.
2340    #[pyo3(name = "positions")]
2341    fn py_positions(
2342        &self,
2343        py: Python,
2344        venue: Option<Venue>,
2345        instrument_id: Option<InstrumentId>,
2346        strategy_id: Option<StrategyId>,
2347        account_id: Option<AccountId>,
2348        side: Option<PositionSide>,
2349    ) -> PyResult<Vec<Py<PyAny>>> {
2350        self.positions(
2351            venue.as_ref(),
2352            instrument_id.as_ref(),
2353            strategy_id.as_ref(),
2354            account_id.as_ref(),
2355            side,
2356        )
2357        .into_iter()
2358        .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
2359        .collect()
2360    }
2361
2362    /// Returns borrows of all open positions matching the optional filter parameters.
2363    #[pyo3(name = "positions_open")]
2364    fn py_positions_open(
2365        &self,
2366        py: Python,
2367        venue: Option<Venue>,
2368        instrument_id: Option<InstrumentId>,
2369        strategy_id: Option<StrategyId>,
2370        account_id: Option<AccountId>,
2371        side: Option<PositionSide>,
2372    ) -> PyResult<Vec<Py<PyAny>>> {
2373        self.positions_open(
2374            venue.as_ref(),
2375            instrument_id.as_ref(),
2376            strategy_id.as_ref(),
2377            account_id.as_ref(),
2378            side,
2379        )
2380        .into_iter()
2381        .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
2382        .collect()
2383    }
2384
2385    /// Returns borrows of all closed positions matching the optional filter parameters.
2386    #[pyo3(name = "positions_closed")]
2387    fn py_positions_closed(
2388        &self,
2389        py: Python,
2390        venue: Option<Venue>,
2391        instrument_id: Option<InstrumentId>,
2392        strategy_id: Option<StrategyId>,
2393        account_id: Option<AccountId>,
2394        side: Option<PositionSide>,
2395    ) -> PyResult<Vec<Py<PyAny>>> {
2396        self.positions_closed(
2397            venue.as_ref(),
2398            instrument_id.as_ref(),
2399            strategy_id.as_ref(),
2400            account_id.as_ref(),
2401            side,
2402        )
2403        .into_iter()
2404        .map(|p| Ok(p.clone().into_pyobject(py)?.into()))
2405        .collect()
2406    }
2407
2408    /// Gets a reference to the strategy ID for the `client_order_id` (if found).
2409    #[pyo3(name = "strategy_id_for_order")]
2410    fn py_strategy_id_for_order(&self, client_order_id: ClientOrderId) -> Option<StrategyId> {
2411        self.strategy_id_for_order(&client_order_id).copied()
2412    }
2413
2414    /// Gets a reference to the strategy ID for the `position_id` (if found).
2415    #[pyo3(name = "strategy_id_for_position")]
2416    fn py_strategy_id_for_position(&self, position_id: PositionId) -> Option<StrategyId> {
2417        self.strategy_id_for_position(&position_id).copied()
2418    }
2419
2420    /// Gets the serialized position snapshot frames for the `position_id`.
2421    ///
2422    /// Each element in the returned vector is one JSON-encoded `Position` snapshot,
2423    /// in the order they were taken.
2424    #[pyo3(name = "position_snapshot_bytes")]
2425    fn py_position_snapshot_bytes(&self, position_id: PositionId) -> Option<Vec<Vec<u8>>> {
2426        self.position_snapshot_bytes(&position_id)
2427    }
2428
2429    /// Returns all position snapshots with the given optional filters.
2430    ///
2431    /// When `position_id` is `Some`, only snapshots for that position are returned.
2432    /// When `account_id` is `Some`, snapshots are filtered to that account.
2433    /// Frames that fail to deserialize are skipped with a warning.
2434    #[pyo3(name = "position_snapshots", signature = (position_id=None, account_id=None))]
2435    fn py_position_snapshots(
2436        &self,
2437        py: Python,
2438        position_id: Option<PositionId>,
2439        account_id: Option<AccountId>,
2440    ) -> PyResult<Vec<Py<PyAny>>> {
2441        self.position_snapshots(position_id.as_ref(), account_id.as_ref())
2442            .into_iter()
2443            .map(|p| Ok(p.into_pyobject(py)?.into()))
2444            .collect()
2445    }
2446
2447    /// Returns a borrow of the account for the `account_id` (if found).
2448    #[pyo3(name = "account")]
2449    fn py_account(&self, py: Python, account_id: AccountId) -> PyResult<Option<Py<PyAny>>> {
2450        match self.account(&account_id) {
2451            Some(account) => Ok(Some(account_any_to_pyobject(py, account.clone())?)),
2452            None => Ok(None),
2453        }
2454    }
2455
2456    /// Returns a borrow of the account for the `venue` (if found).
2457    #[pyo3(name = "account_for_venue")]
2458    fn py_account_for_venue(&self, py: Python, venue: Venue) -> PyResult<Option<Py<PyAny>>> {
2459        match self.account_for_venue(&venue) {
2460            Some(account) => Ok(Some(account_any_to_pyobject(py, account.clone())?)),
2461            None => Ok(None),
2462        }
2463    }
2464
2465    /// Returns a reference to the account ID for the `venue` (if found).
2466    #[pyo3(name = "account_id")]
2467    fn py_account_id(&self, venue: Venue) -> Option<AccountId> {
2468        self.account_id(&venue).copied()
2469    }
2470
2471    /// Gets a reference to the general value for the `key` (if found).
2472    ///
2473    /// # Errors
2474    ///
2475    /// Returns an error if the `key` is invalid.
2476    #[pyo3(name = "get")]
2477    fn py_get(&self, key: &str) -> PyResult<Option<Vec<u8>>> {
2478        match self.get(key).map_err(to_pyvalue_err)? {
2479            Some(bytes) => Ok(Some(bytes.to_vec())),
2480            None => Ok(None),
2481        }
2482    }
2483
2484    /// Adds a general `value` to the cache for the given `key`.
2485    #[pyo3(name = "add")]
2486    fn py_add_general(&mut self, key: &str, value: Vec<u8>) -> PyResult<()> {
2487        self.add(key, Bytes::from(value)).map_err(to_pyvalue_err)
2488    }
2489
2490    /// Returns the price for the `instrument_id` and `price_type` (if found).
2491    #[pyo3(name = "price")]
2492    fn py_price(&self, instrument_id: InstrumentId, price_type: PriceType) -> Option<Price> {
2493        self.price(&instrument_id, price_type)
2494    }
2495
2496    /// Returns the exchange rate for the given parameters.
2497    #[pyo3(name = "get_xrate")]
2498    fn py_get_xrate(
2499        &self,
2500        venue: Venue,
2501        from_currency: Currency,
2502        to_currency: Currency,
2503        price_type: PriceType,
2504    ) -> Option<f64> {
2505        self.get_xrate(venue, from_currency, to_currency, price_type)
2506    }
2507
2508    /// Returns the mark exchange rate for the given currency pair, or `None` if not set.
2509    #[pyo3(name = "get_mark_xrate")]
2510    fn py_get_mark_xrate(&self, from_currency: Currency, to_currency: Currency) -> Option<f64> {
2511        self.get_mark_xrate(from_currency, to_currency)
2512    }
2513
2514    /// Sets the mark exchange rate for the given currency pair and automatically sets the inverse rate.
2515    #[pyo3(name = "set_mark_xrate")]
2516    fn py_set_mark_xrate(&mut self, from_currency: Currency, to_currency: Currency, xrate: f64) {
2517        self.set_mark_xrate(from_currency, to_currency, xrate);
2518    }
2519
2520    /// Clears the mark exchange rate for the given currency pair.
2521    #[pyo3(name = "clear_mark_xrate")]
2522    fn py_clear_mark_xrate(&mut self, from_currency: Currency, to_currency: Currency) {
2523        self.clear_mark_xrate(from_currency, to_currency);
2524    }
2525
2526    /// Clears all mark exchange rates.
2527    #[pyo3(name = "clear_mark_xrates")]
2528    fn py_clear_mark_xrates(&mut self) {
2529        self.clear_mark_xrates();
2530    }
2531
2532    /// Calculates the unrealized PnL for the given position.
2533    #[pyo3(name = "calculate_unrealized_pnl")]
2534    #[expect(clippy::needless_pass_by_value)]
2535    fn py_calculate_unrealized_pnl(
2536        &self,
2537        py: Python,
2538        position: Py<PyAny>,
2539    ) -> PyResult<Option<Money>> {
2540        let position = position.extract::<Position>(py)?;
2541        Ok(self.calculate_unrealized_pnl(&position))
2542    }
2543
2544    /// Gets a reference to the own order book for the `instrument_id`.
2545    #[pyo3(name = "own_order_book")]
2546    fn py_own_order_book(&self, instrument_id: InstrumentId) -> Option<OwnOrderBook> {
2547        self.own_order_book(&instrument_id).cloned()
2548    }
2549
2550    /// Updates the own order book with an order.
2551    ///
2552    /// This method adds, updates, or removes an order from the own order book
2553    /// based on the order's current state.
2554    ///
2555    /// Orders without prices (MARKET, etc.) are skipped as they cannot be
2556    /// represented in own books.
2557    #[pyo3(name = "update_own_order_book")]
2558    fn py_update_own_order_book(&mut self, py: Python, order: Py<PyAny>) -> PyResult<()> {
2559        let order_any = pyobject_to_order_any(py, order)?;
2560        self.update_own_order_book(&order_any);
2561        Ok(())
2562    }
2563
2564    /// Force removal of an order from own order books and clean up all indexes.
2565    ///
2566    /// This method is used when order event application fails and we need to ensure
2567    /// terminal orders are properly cleaned up from own books and all relevant indexes.
2568    /// Replicates the index cleanup that `update_order` performs for closed orders.
2569    #[pyo3(name = "force_remove_from_own_order_book")]
2570    fn py_force_remove_from_own_order_book(&mut self, client_order_id: ClientOrderId) {
2571        self.force_remove_from_own_order_book(&client_order_id);
2572    }
2573
2574    /// Audit all own order books against open and inflight order indexes.
2575    ///
2576    /// Ensures closed orders are removed from own order books. This includes both
2577    /// orders tracked in `orders_open` (`ACCEPTED`, `TRIGGERED`, `PENDING_*`, `PARTIALLY_FILLED`)
2578    /// and `orders_inflight` (`INITIALIZED`, `SUBMITTED`) to prevent false positives
2579    /// during venue latency windows.
2580    #[pyo3(name = "audit_own_order_books")]
2581    fn py_audit_own_order_books(&mut self) {
2582        self.audit_own_order_books();
2583    }
2584}
2585
2586#[cfg(feature = "defi")]
2587#[pymethods]
2588impl Cache {
2589    /// Adds a `Pool` to the cache.
2590    ///
2591    /// # Errors
2592    ///
2593    /// This function currently does not return errors but follows the same pattern as other add methods for consistency.
2594    #[pyo3(name = "add_pool")]
2595    fn py_add_pool(&mut self, pool: Pool) -> PyResult<()> {
2596        self.add_pool(pool).map_err(to_pyvalue_err)
2597    }
2598
2599    /// Gets a reference to the pool for the `instrument_id`.
2600    #[pyo3(name = "pool")]
2601    fn py_pool(&self, instrument_id: InstrumentId) -> Option<Pool> {
2602        self.pool(&instrument_id).cloned()
2603    }
2604
2605    /// Returns the instrument IDs of all pools in the cache, optionally filtered by `venue`.
2606    #[pyo3(name = "pool_ids")]
2607    fn py_pool_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
2608        self.pool_ids(venue.as_ref())
2609    }
2610
2611    /// Returns references to all pools in the cache, optionally filtered by `venue`.
2612    #[pyo3(name = "pools")]
2613    fn py_pools(&self, venue: Option<Venue>) -> Vec<Pool> {
2614        self.pools(venue.as_ref()).into_iter().cloned().collect()
2615    }
2616
2617    /// Adds a `PoolProfiler` to the cache.
2618    ///
2619    /// # Errors
2620    ///
2621    /// This function currently does not return errors but follows the same pattern as other add methods for consistency.
2622    #[pyo3(name = "add_pool_profiler")]
2623    fn py_add_pool_profiler(&mut self, pool_profiler: PoolProfiler) -> PyResult<()> {
2624        self.add_pool_profiler(pool_profiler)
2625            .map_err(to_pyvalue_err)
2626    }
2627
2628    /// Gets a reference to the pool profiler for the `instrument_id`.
2629    #[pyo3(name = "pool_profiler")]
2630    fn py_pool_profiler(&self, instrument_id: InstrumentId) -> Option<PoolProfiler> {
2631        self.pool_profiler(&instrument_id).cloned()
2632    }
2633
2634    /// Returns the instrument IDs of all pool profilers in the cache, optionally filtered by `venue`.
2635    #[pyo3(name = "pool_profiler_ids")]
2636    fn py_pool_profiler_ids(&self, venue: Option<Venue>) -> Vec<InstrumentId> {
2637        self.pool_profiler_ids(venue.as_ref())
2638    }
2639
2640    /// Returns references to all pool profilers in the cache, optionally filtered by `venue`.
2641    #[pyo3(name = "pool_profilers")]
2642    fn py_pool_profilers(&self, venue: Option<Venue>) -> Vec<PoolProfiler> {
2643        self.pool_profilers(venue.as_ref())
2644            .into_iter()
2645            .cloned()
2646            .collect()
2647    }
2648}