Skip to main content

nautilus_common/python/
cache.rs

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