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