Skip to main content

nautilus_common/python/
cache.rs

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