Skip to main content

nautilus_common/python/
cache.rs

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