Skip to main content

nautilus_common/cache/
api.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//! User-facing read API over the platform cache.
17
18use std::{
19    cell::{Ref, RefCell},
20    fmt::Display,
21};
22
23use ahash::AHashSet;
24use bytes::Bytes;
25#[cfg(feature = "defi")]
26use nautilus_model::defi::{Pool, PoolProfiler};
27use nautilus_model::{
28    accounts::AccountAny,
29    data::{
30        Bar, BarType, FundingRateUpdate, GreeksData, IndexPriceUpdate, InstrumentClose,
31        InstrumentStatus, MarkPriceUpdate, QuoteTick, TradeTick, option_chain::OptionGreeks,
32    },
33    enums::{AggregationSource, InstrumentClass, OmsType, OrderSide, PositionSide, PriceType},
34    identifiers::{
35        AccountId, ClientId, ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, PositionId,
36        StrategyId, Venue, VenueOrderId,
37    },
38    instruments::{InstrumentAny, SyntheticInstrument},
39    orderbook::{OrderBook, own::OwnOrderBook},
40    orders::{OrderAny, OrderList},
41    position::Position,
42    types::{Currency, Money, Price, Quantity},
43};
44use rust_decimal::Decimal;
45use ustr::Ustr;
46
47use super::{
48    Cache,
49    error::{
50        AccountLookupError, CurrencyLookupError, InstrumentLookupError, OrderBookLookupError,
51        OrderListLookupError, OrderLookupError, OwnOrderBookLookupError, PositionLookupError,
52        SyntheticInstrumentLookupError,
53    },
54};
55use crate::component::ComponentAccessError;
56
57/// User-facing cache API.
58///
59/// Point reads return owned snapshots where possible, so actor code does not retain a `Ref` into
60/// the live [`Cache`]. Plural collection reads return owned snapshots of all matching values and
61/// are intentionally named as bulk reads. Prefer the count, ID, or `has_*` methods in hot paths
62/// when a full snapshot is not needed.
63#[derive(Debug)]
64pub struct CacheApi<'a> {
65    cache: &'a RefCell<Cache>,
66}
67
68impl<'a> CacheApi<'a> {
69    pub(crate) fn new(cache: &'a RefCell<Cache>) -> Self {
70        Self { cache }
71    }
72
73    /// Returns the unrealized PnL for the `position` using cached market data.
74    ///
75    /// # Panics
76    ///
77    /// Panics if the cache is already mutably borrowed.
78    #[must_use]
79    pub fn calculate_unrealized_pnl(&self, position: &Position) -> Option<Money> {
80        self.cache().calculate_unrealized_pnl(position)
81    }
82
83    /// Returns the OMS type for the `position_id` (if known).
84    ///
85    /// # Panics
86    ///
87    /// Panics if the cache is already mutably borrowed.
88    #[must_use]
89    pub fn oms_type(&self, position_id: &PositionId) -> Option<OmsType> {
90        self.cache().oms_type(position_id)
91    }
92
93    /// Returns serialized position snapshot frames for the `position_id`.
94    ///
95    /// # Panics
96    ///
97    /// Panics if the cache is already mutably borrowed.
98    #[must_use]
99    pub fn position_snapshot_bytes(&self, position_id: &PositionId) -> Option<Vec<Vec<u8>>> {
100        self.cache().position_snapshot_bytes(position_id)
101    }
102
103    /// Returns the number of stored position snapshots for the `position_id`.
104    ///
105    /// # Panics
106    ///
107    /// Panics if the cache is already mutably borrowed.
108    #[must_use]
109    pub fn position_snapshot_count(&self, position_id: &PositionId) -> usize {
110        self.cache().position_snapshot_count(position_id)
111    }
112
113    /// Returns position snapshots matching the optional filters.
114    ///
115    /// # Panics
116    ///
117    /// Panics if the cache is already mutably borrowed.
118    #[must_use]
119    pub fn position_snapshots(
120        &self,
121        position_id: Option<&PositionId>,
122        account_id: Option<&AccountId>,
123    ) -> Vec<Position> {
124        self.cache().position_snapshots(position_id, account_id)
125    }
126
127    /// Returns position snapshots for `position_id` starting from `skip`.
128    ///
129    /// # Panics
130    ///
131    /// Panics if the cache is already mutably borrowed.
132    #[must_use]
133    pub fn position_snapshots_from(&self, position_id: &PositionId, skip: usize) -> Vec<Position> {
134        self.cache().position_snapshots_from(position_id, skip)
135    }
136
137    /// Returns position snapshot IDs for the `instrument_id`.
138    ///
139    /// # Panics
140    ///
141    /// Panics if the cache is already mutably borrowed.
142    #[must_use]
143    pub fn position_snapshot_ids(&self, instrument_id: &InstrumentId) -> AHashSet<PositionId> {
144        self.cache().position_snapshot_ids(instrument_id)
145    }
146
147    /// Returns the client order IDs of all orders matching the optional filter parameters.
148    ///
149    /// # Panics
150    ///
151    /// Panics if the cache is already mutably borrowed.
152    #[must_use]
153    pub fn client_order_ids(
154        &self,
155        venue: Option<&Venue>,
156        instrument_id: Option<&InstrumentId>,
157        strategy_id: Option<&StrategyId>,
158        account_id: Option<&AccountId>,
159    ) -> AHashSet<ClientOrderId> {
160        self.cache()
161            .client_order_ids(venue, instrument_id, strategy_id, account_id)
162    }
163
164    /// Returns the client order IDs of all open orders matching the optional filter parameters.
165    ///
166    /// # Panics
167    ///
168    /// Panics if the cache is already mutably borrowed.
169    #[must_use]
170    pub fn client_order_ids_open(
171        &self,
172        venue: Option<&Venue>,
173        instrument_id: Option<&InstrumentId>,
174        strategy_id: Option<&StrategyId>,
175        account_id: Option<&AccountId>,
176    ) -> AHashSet<ClientOrderId> {
177        self.cache()
178            .client_order_ids_open(venue, instrument_id, strategy_id, account_id)
179    }
180
181    /// Returns the client order IDs of all closed orders matching the optional filter parameters.
182    ///
183    /// # Panics
184    ///
185    /// Panics if the cache is already mutably borrowed.
186    #[must_use]
187    pub fn client_order_ids_closed(
188        &self,
189        venue: Option<&Venue>,
190        instrument_id: Option<&InstrumentId>,
191        strategy_id: Option<&StrategyId>,
192        account_id: Option<&AccountId>,
193    ) -> AHashSet<ClientOrderId> {
194        self.cache()
195            .client_order_ids_closed(venue, instrument_id, strategy_id, account_id)
196    }
197
198    /// Returns the client order IDs of all locally active orders matching the optional filter parameters.
199    ///
200    /// # Panics
201    ///
202    /// Panics if the cache is already mutably borrowed.
203    #[must_use]
204    pub fn client_order_ids_active_local(
205        &self,
206        venue: Option<&Venue>,
207        instrument_id: Option<&InstrumentId>,
208        strategy_id: Option<&StrategyId>,
209        account_id: Option<&AccountId>,
210    ) -> AHashSet<ClientOrderId> {
211        self.cache()
212            .client_order_ids_active_local(venue, instrument_id, strategy_id, account_id)
213    }
214
215    /// Returns the client order IDs of all emulated orders matching the optional filter parameters.
216    ///
217    /// # Panics
218    ///
219    /// Panics if the cache is already mutably borrowed.
220    #[must_use]
221    pub fn client_order_ids_emulated(
222        &self,
223        venue: Option<&Venue>,
224        instrument_id: Option<&InstrumentId>,
225        strategy_id: Option<&StrategyId>,
226        account_id: Option<&AccountId>,
227    ) -> AHashSet<ClientOrderId> {
228        self.cache()
229            .client_order_ids_emulated(venue, instrument_id, strategy_id, account_id)
230    }
231
232    /// Returns the client order IDs of all in-flight orders matching the optional filter parameters.
233    ///
234    /// # Panics
235    ///
236    /// Panics if the cache is already mutably borrowed.
237    #[must_use]
238    pub fn client_order_ids_inflight(
239        &self,
240        venue: Option<&Venue>,
241        instrument_id: Option<&InstrumentId>,
242        strategy_id: Option<&StrategyId>,
243        account_id: Option<&AccountId>,
244    ) -> AHashSet<ClientOrderId> {
245        self.cache()
246            .client_order_ids_inflight(venue, instrument_id, strategy_id, account_id)
247    }
248
249    /// Returns the position IDs of all positions matching the optional filter parameters.
250    ///
251    /// # Panics
252    ///
253    /// Panics if the cache is already mutably borrowed.
254    #[must_use]
255    pub fn position_ids(
256        &self,
257        venue: Option<&Venue>,
258        instrument_id: Option<&InstrumentId>,
259        strategy_id: Option<&StrategyId>,
260        account_id: Option<&AccountId>,
261    ) -> AHashSet<PositionId> {
262        self.cache()
263            .position_ids(venue, instrument_id, strategy_id, account_id)
264    }
265
266    /// Returns the position IDs of all open positions matching the optional filter parameters.
267    ///
268    /// # Panics
269    ///
270    /// Panics if the cache is already mutably borrowed.
271    #[must_use]
272    pub fn position_open_ids(
273        &self,
274        venue: Option<&Venue>,
275        instrument_id: Option<&InstrumentId>,
276        strategy_id: Option<&StrategyId>,
277        account_id: Option<&AccountId>,
278    ) -> AHashSet<PositionId> {
279        self.cache()
280            .position_open_ids(venue, instrument_id, strategy_id, account_id)
281    }
282
283    /// Returns the position IDs of all closed positions matching the optional filter parameters.
284    ///
285    /// # Panics
286    ///
287    /// Panics if the cache is already mutably borrowed.
288    #[must_use]
289    pub fn position_closed_ids(
290        &self,
291        venue: Option<&Venue>,
292        instrument_id: Option<&InstrumentId>,
293        strategy_id: Option<&StrategyId>,
294        account_id: Option<&AccountId>,
295    ) -> AHashSet<PositionId> {
296        self.cache()
297            .position_closed_ids(venue, instrument_id, strategy_id, account_id)
298    }
299
300    /// Returns the strategy IDs in the cache.
301    ///
302    /// # Panics
303    ///
304    /// Panics if the cache is already mutably borrowed.
305    #[must_use]
306    pub fn strategy_ids(&self) -> AHashSet<StrategyId> {
307        self.cache().strategy_ids()
308    }
309
310    /// Returns the execution algorithm IDs in the cache.
311    ///
312    /// # Panics
313    ///
314    /// Panics if the cache is already mutably borrowed.
315    #[must_use]
316    pub fn exec_algorithm_ids(&self) -> AHashSet<ExecAlgorithmId> {
317        self.cache().exec_algorithm_ids()
318    }
319
320    /// Returns an owned copy of the order for the `client_order_id` (if found).
321    ///
322    /// # Panics
323    ///
324    /// Panics if the cache is already mutably borrowed.
325    #[must_use]
326    pub fn order(&self, client_order_id: &ClientOrderId) -> Option<OrderAny> {
327        self.cache().order_owned(client_order_id)
328    }
329
330    /// Returns an owned copy of the order for the `client_order_id`.
331    ///
332    /// # Errors
333    ///
334    /// Returns an error if:
335    /// - The order is not present in the cache.
336    /// - The cache is already mutably borrowed.
337    pub fn try_order(&self, client_order_id: &ClientOrderId) -> Result<OrderAny, OrderLookupError> {
338        self.try_cache("try_order")?
339            .try_order_owned(client_order_id)
340    }
341
342    /// Returns owned copies of the orders for `client_order_ids`.
343    ///
344    /// # Panics
345    ///
346    /// Panics if the cache is already mutably borrowed.
347    #[must_use]
348    pub fn orders_for_ids(
349        &self,
350        client_order_ids: &[ClientOrderId],
351        context: &dyn Display,
352    ) -> Vec<OrderAny> {
353        self.cache().orders_for_ids(client_order_ids, context)
354    }
355
356    /// Returns the client order ID for the `venue_order_id` (if found).
357    ///
358    /// # Panics
359    ///
360    /// Panics if the cache is already mutably borrowed.
361    #[must_use]
362    pub fn client_order_id(&self, venue_order_id: &VenueOrderId) -> Option<ClientOrderId> {
363        self.cache().client_order_id(venue_order_id).copied()
364    }
365
366    /// Returns the venue order ID for the `client_order_id` (if found).
367    ///
368    /// # Panics
369    ///
370    /// Panics if the cache is already mutably borrowed.
371    #[must_use]
372    pub fn venue_order_id(&self, client_order_id: &ClientOrderId) -> Option<VenueOrderId> {
373        self.cache().venue_order_id(client_order_id).copied()
374    }
375
376    /// Returns the client ID indexed for the `client_order_id` (if found).
377    ///
378    /// # Panics
379    ///
380    /// Panics if the cache is already mutably borrowed.
381    #[must_use]
382    pub fn client_id(&self, client_order_id: &ClientOrderId) -> Option<ClientId> {
383        self.cache().client_id(client_order_id).copied()
384    }
385
386    /// Returns owned copies of all orders matching the optional filter parameters.
387    ///
388    /// # Panics
389    ///
390    /// Panics if the cache is already mutably borrowed.
391    #[must_use]
392    pub fn orders(
393        &self,
394        venue: Option<&Venue>,
395        instrument_id: Option<&InstrumentId>,
396        strategy_id: Option<&StrategyId>,
397        account_id: Option<&AccountId>,
398        side: Option<OrderSide>,
399    ) -> Vec<OrderAny> {
400        self.cache()
401            .orders_refs(venue, instrument_id, strategy_id, account_id, side)
402            .into_iter()
403            .map(|order| order.cloned())
404            .collect()
405    }
406
407    /// Returns owned copies of all open orders matching the optional filter parameters.
408    ///
409    /// # Panics
410    ///
411    /// Panics if the cache is already mutably borrowed.
412    #[must_use]
413    pub fn orders_open(
414        &self,
415        venue: Option<&Venue>,
416        instrument_id: Option<&InstrumentId>,
417        strategy_id: Option<&StrategyId>,
418        account_id: Option<&AccountId>,
419        side: Option<OrderSide>,
420    ) -> Vec<OrderAny> {
421        self.cache()
422            .orders_open_refs(venue, instrument_id, strategy_id, account_id, side)
423            .into_iter()
424            .map(|order| order.cloned())
425            .collect()
426    }
427
428    /// Returns owned copies of all closed orders matching the optional filter parameters.
429    ///
430    /// # Panics
431    ///
432    /// Panics if the cache is already mutably borrowed.
433    #[must_use]
434    pub fn orders_closed(
435        &self,
436        venue: Option<&Venue>,
437        instrument_id: Option<&InstrumentId>,
438        strategy_id: Option<&StrategyId>,
439        account_id: Option<&AccountId>,
440        side: Option<OrderSide>,
441    ) -> Vec<OrderAny> {
442        self.cache()
443            .orders_closed_refs(venue, instrument_id, strategy_id, account_id, side)
444            .into_iter()
445            .map(|order| order.cloned())
446            .collect()
447    }
448
449    /// Returns owned copies of all locally active orders matching the optional filter parameters.
450    ///
451    /// # Panics
452    ///
453    /// Panics if the cache is already mutably borrowed.
454    #[must_use]
455    pub fn orders_active_local(
456        &self,
457        venue: Option<&Venue>,
458        instrument_id: Option<&InstrumentId>,
459        strategy_id: Option<&StrategyId>,
460        account_id: Option<&AccountId>,
461        side: Option<OrderSide>,
462    ) -> Vec<OrderAny> {
463        self.cache()
464            .orders_active_local_refs(venue, instrument_id, strategy_id, account_id, side)
465            .into_iter()
466            .map(|order| order.cloned())
467            .collect()
468    }
469
470    /// Returns owned copies of all emulated orders matching the optional filter parameters.
471    ///
472    /// # Panics
473    ///
474    /// Panics if the cache is already mutably borrowed.
475    #[must_use]
476    pub fn orders_emulated(
477        &self,
478        venue: Option<&Venue>,
479        instrument_id: Option<&InstrumentId>,
480        strategy_id: Option<&StrategyId>,
481        account_id: Option<&AccountId>,
482        side: Option<OrderSide>,
483    ) -> Vec<OrderAny> {
484        self.cache()
485            .orders_emulated_refs(venue, instrument_id, strategy_id, account_id, side)
486            .into_iter()
487            .map(|order| order.cloned())
488            .collect()
489    }
490
491    /// Returns owned copies of all in-flight orders matching the optional filter parameters.
492    ///
493    /// # Panics
494    ///
495    /// Panics if the cache is already mutably borrowed.
496    #[must_use]
497    pub fn orders_inflight(
498        &self,
499        venue: Option<&Venue>,
500        instrument_id: Option<&InstrumentId>,
501        strategy_id: Option<&StrategyId>,
502        account_id: Option<&AccountId>,
503        side: Option<OrderSide>,
504    ) -> Vec<OrderAny> {
505        self.cache()
506            .orders_inflight_refs(venue, instrument_id, strategy_id, account_id, side)
507            .into_iter()
508            .map(|order| order.cloned())
509            .collect()
510    }
511
512    /// Returns owned copies of all orders for the `position_id`.
513    ///
514    /// # Panics
515    ///
516    /// Panics if the cache is already mutably borrowed.
517    #[must_use]
518    pub fn orders_for_position(&self, position_id: &PositionId) -> Vec<OrderAny> {
519        self.cache()
520            .orders_for_position(position_id)
521            .into_iter()
522            .map(|order| order.cloned())
523            .collect()
524    }
525
526    /// Returns whether an order with the `client_order_id` exists.
527    ///
528    /// # Panics
529    ///
530    /// Panics if the cache is already mutably borrowed.
531    #[must_use]
532    pub fn order_exists(&self, client_order_id: &ClientOrderId) -> bool {
533        self.cache().order_exists(client_order_id)
534    }
535
536    /// Returns whether an order with the `client_order_id` is open.
537    ///
538    /// # Panics
539    ///
540    /// Panics if the cache is already mutably borrowed.
541    #[must_use]
542    pub fn is_order_open(&self, client_order_id: &ClientOrderId) -> bool {
543        self.cache().is_order_open(client_order_id)
544    }
545
546    /// Returns whether an order with the `client_order_id` is closed.
547    ///
548    /// # Panics
549    ///
550    /// Panics if the cache is already mutably borrowed.
551    #[must_use]
552    pub fn is_order_closed(&self, client_order_id: &ClientOrderId) -> bool {
553        self.cache().is_order_closed(client_order_id)
554    }
555
556    /// Returns whether an order with the `client_order_id` is locally active.
557    ///
558    /// # Panics
559    ///
560    /// Panics if the cache is already mutably borrowed.
561    #[must_use]
562    pub fn is_order_active_local(&self, client_order_id: &ClientOrderId) -> bool {
563        self.cache().is_order_active_local(client_order_id)
564    }
565
566    /// Returns whether an order with the `client_order_id` is emulated.
567    ///
568    /// # Panics
569    ///
570    /// Panics if the cache is already mutably borrowed.
571    #[must_use]
572    pub fn is_order_emulated(&self, client_order_id: &ClientOrderId) -> bool {
573        self.cache().is_order_emulated(client_order_id)
574    }
575
576    /// Returns whether an order with the `client_order_id` is in-flight.
577    ///
578    /// # Panics
579    ///
580    /// Panics if the cache is already mutably borrowed.
581    #[must_use]
582    pub fn is_order_inflight(&self, client_order_id: &ClientOrderId) -> bool {
583        self.cache().is_order_inflight(client_order_id)
584    }
585
586    /// Returns whether an order with the `client_order_id` is `PENDING_CANCEL` locally.
587    ///
588    /// # Panics
589    ///
590    /// Panics if the cache is already mutably borrowed.
591    #[must_use]
592    pub fn is_order_pending_cancel_local(&self, client_order_id: &ClientOrderId) -> bool {
593        self.cache().is_order_pending_cancel_local(client_order_id)
594    }
595
596    /// Returns the count of all open orders matching the optional filter parameters.
597    ///
598    /// # Panics
599    ///
600    /// Panics if the cache is already mutably borrowed.
601    #[must_use]
602    pub fn orders_open_count(
603        &self,
604        venue: Option<&Venue>,
605        instrument_id: Option<&InstrumentId>,
606        strategy_id: Option<&StrategyId>,
607        account_id: Option<&AccountId>,
608        side: Option<OrderSide>,
609    ) -> usize {
610        self.cache()
611            .orders_open_count(venue, instrument_id, strategy_id, account_id, side)
612    }
613
614    /// Returns the count of all closed orders matching the optional filter parameters.
615    ///
616    /// # Panics
617    ///
618    /// Panics if the cache is already mutably borrowed.
619    #[must_use]
620    pub fn orders_closed_count(
621        &self,
622        venue: Option<&Venue>,
623        instrument_id: Option<&InstrumentId>,
624        strategy_id: Option<&StrategyId>,
625        account_id: Option<&AccountId>,
626        side: Option<OrderSide>,
627    ) -> usize {
628        self.cache()
629            .orders_closed_count(venue, instrument_id, strategy_id, account_id, side)
630    }
631
632    /// Returns the count of all locally active orders matching the optional filter parameters.
633    ///
634    /// # Panics
635    ///
636    /// Panics if the cache is already mutably borrowed.
637    #[must_use]
638    pub fn orders_active_local_count(
639        &self,
640        venue: Option<&Venue>,
641        instrument_id: Option<&InstrumentId>,
642        strategy_id: Option<&StrategyId>,
643        account_id: Option<&AccountId>,
644        side: Option<OrderSide>,
645    ) -> usize {
646        self.cache()
647            .orders_active_local_count(venue, instrument_id, strategy_id, account_id, side)
648    }
649
650    /// Returns the count of all emulated orders matching the optional filter parameters.
651    ///
652    /// # Panics
653    ///
654    /// Panics if the cache is already mutably borrowed.
655    #[must_use]
656    pub fn orders_emulated_count(
657        &self,
658        venue: Option<&Venue>,
659        instrument_id: Option<&InstrumentId>,
660        strategy_id: Option<&StrategyId>,
661        account_id: Option<&AccountId>,
662        side: Option<OrderSide>,
663    ) -> usize {
664        self.cache()
665            .orders_emulated_count(venue, instrument_id, strategy_id, account_id, side)
666    }
667
668    /// Returns the count of all in-flight orders matching the optional filter parameters.
669    ///
670    /// # Panics
671    ///
672    /// Panics if the cache is already mutably borrowed.
673    #[must_use]
674    pub fn orders_inflight_count(
675        &self,
676        venue: Option<&Venue>,
677        instrument_id: Option<&InstrumentId>,
678        strategy_id: Option<&StrategyId>,
679        account_id: Option<&AccountId>,
680        side: Option<OrderSide>,
681    ) -> usize {
682        self.cache()
683            .orders_inflight_count(venue, instrument_id, strategy_id, account_id, side)
684    }
685
686    /// Returns the count of all orders matching the optional filter parameters.
687    ///
688    /// # Panics
689    ///
690    /// Panics if the cache is already mutably borrowed.
691    #[must_use]
692    pub fn orders_total_count(
693        &self,
694        venue: Option<&Venue>,
695        instrument_id: Option<&InstrumentId>,
696        strategy_id: Option<&StrategyId>,
697        account_id: Option<&AccountId>,
698        side: Option<OrderSide>,
699    ) -> usize {
700        self.cache()
701            .orders_total_count(venue, instrument_id, strategy_id, account_id, side)
702    }
703
704    /// Returns whether any open order matches the optional filter parameters.
705    ///
706    /// # Panics
707    ///
708    /// Panics if the cache is already mutably borrowed.
709    #[must_use]
710    pub fn has_orders_open(
711        &self,
712        venue: Option<&Venue>,
713        instrument_id: Option<&InstrumentId>,
714        strategy_id: Option<&StrategyId>,
715        account_id: Option<&AccountId>,
716        side: Option<OrderSide>,
717    ) -> bool {
718        self.cache()
719            .has_orders_open(venue, instrument_id, strategy_id, account_id, side)
720    }
721
722    /// Returns whether any closed order matches the optional filter parameters.
723    ///
724    /// # Panics
725    ///
726    /// Panics if the cache is already mutably borrowed.
727    #[must_use]
728    pub fn has_orders_closed(
729        &self,
730        venue: Option<&Venue>,
731        instrument_id: Option<&InstrumentId>,
732        strategy_id: Option<&StrategyId>,
733        account_id: Option<&AccountId>,
734        side: Option<OrderSide>,
735    ) -> bool {
736        self.cache()
737            .has_orders_closed(venue, instrument_id, strategy_id, account_id, side)
738    }
739
740    /// Returns whether any locally active order matches the optional filter parameters.
741    ///
742    /// # Panics
743    ///
744    /// Panics if the cache is already mutably borrowed.
745    #[must_use]
746    pub fn has_orders_active_local(
747        &self,
748        venue: Option<&Venue>,
749        instrument_id: Option<&InstrumentId>,
750        strategy_id: Option<&StrategyId>,
751        account_id: Option<&AccountId>,
752        side: Option<OrderSide>,
753    ) -> bool {
754        self.cache()
755            .has_orders_active_local(venue, instrument_id, strategy_id, account_id, side)
756    }
757
758    /// Returns whether any emulated order matches the optional filter parameters.
759    ///
760    /// # Panics
761    ///
762    /// Panics if the cache is already mutably borrowed.
763    #[must_use]
764    pub fn has_orders_emulated(
765        &self,
766        venue: Option<&Venue>,
767        instrument_id: Option<&InstrumentId>,
768        strategy_id: Option<&StrategyId>,
769        account_id: Option<&AccountId>,
770        side: Option<OrderSide>,
771    ) -> bool {
772        self.cache()
773            .has_orders_emulated(venue, instrument_id, strategy_id, account_id, side)
774    }
775
776    /// Returns whether any in-flight order matches the optional filter parameters.
777    ///
778    /// # Panics
779    ///
780    /// Panics if the cache is already mutably borrowed.
781    #[must_use]
782    pub fn has_orders_inflight(
783        &self,
784        venue: Option<&Venue>,
785        instrument_id: Option<&InstrumentId>,
786        strategy_id: Option<&StrategyId>,
787        account_id: Option<&AccountId>,
788        side: Option<OrderSide>,
789    ) -> bool {
790        self.cache()
791            .has_orders_inflight(venue, instrument_id, strategy_id, account_id, side)
792    }
793
794    /// Returns whether any order matches the optional filter parameters.
795    ///
796    /// # Panics
797    ///
798    /// Panics if the cache is already mutably borrowed.
799    #[must_use]
800    pub fn has_orders(
801        &self,
802        venue: Option<&Venue>,
803        instrument_id: Option<&InstrumentId>,
804        strategy_id: Option<&StrategyId>,
805        account_id: Option<&AccountId>,
806        side: Option<OrderSide>,
807    ) -> bool {
808        self.cache()
809            .has_orders(venue, instrument_id, strategy_id, account_id, side)
810    }
811
812    /// Returns an owned copy of the order list for the `order_list_id` (if found).
813    ///
814    /// # Panics
815    ///
816    /// Panics if the cache is already mutably borrowed.
817    #[must_use]
818    pub fn order_list(&self, order_list_id: &OrderListId) -> Option<OrderList> {
819        self.cache().order_list(order_list_id).cloned()
820    }
821
822    /// Returns an owned copy of the order list for the `order_list_id`.
823    ///
824    /// # Errors
825    ///
826    /// Returns an error if:
827    /// - The order list is not present in the cache.
828    /// - The cache is already mutably borrowed.
829    pub fn try_order_list(
830        &self,
831        order_list_id: &OrderListId,
832    ) -> Result<OrderList, OrderListLookupError> {
833        self.try_cache("try_order_list")?
834            .try_order_list(order_list_id)
835            .cloned()
836    }
837
838    /// Returns owned copies of all order lists matching the optional filter parameters.
839    ///
840    /// # Panics
841    ///
842    /// Panics if the cache is already mutably borrowed.
843    #[must_use]
844    pub fn order_lists(
845        &self,
846        venue: Option<&Venue>,
847        instrument_id: Option<&InstrumentId>,
848        strategy_id: Option<&StrategyId>,
849        account_id: Option<&AccountId>,
850    ) -> Vec<OrderList> {
851        self.cache()
852            .order_lists(venue, instrument_id, strategy_id, account_id)
853            .into_iter()
854            .cloned()
855            .collect()
856    }
857
858    /// Returns whether an order list with the `order_list_id` exists.
859    ///
860    /// # Panics
861    ///
862    /// Panics if the cache is already mutably borrowed.
863    #[must_use]
864    pub fn order_list_exists(&self, order_list_id: &OrderListId) -> bool {
865        self.cache().order_list_exists(order_list_id)
866    }
867
868    /// Returns owned copies of all orders associated with the `exec_algorithm_id`.
869    ///
870    /// # Panics
871    ///
872    /// Panics if the cache is already mutably borrowed.
873    #[must_use]
874    pub fn orders_for_exec_algorithm(
875        &self,
876        exec_algorithm_id: &ExecAlgorithmId,
877        venue: Option<&Venue>,
878        instrument_id: Option<&InstrumentId>,
879        strategy_id: Option<&StrategyId>,
880        account_id: Option<&AccountId>,
881        side: Option<OrderSide>,
882    ) -> Vec<OrderAny> {
883        self.cache()
884            .orders_for_exec_algorithm(
885                exec_algorithm_id,
886                venue,
887                instrument_id,
888                strategy_id,
889                account_id,
890                side,
891            )
892            .into_iter()
893            .map(|order| order.cloned())
894            .collect()
895    }
896
897    /// Returns owned copies of all orders with the `exec_spawn_id`.
898    ///
899    /// # Panics
900    ///
901    /// Panics if the cache is already mutably borrowed.
902    #[must_use]
903    pub fn orders_for_exec_spawn(&self, exec_spawn_id: &ClientOrderId) -> Vec<OrderAny> {
904        self.cache()
905            .orders_for_exec_spawn(exec_spawn_id)
906            .into_iter()
907            .map(|order| order.cloned())
908            .collect()
909    }
910
911    /// Returns the total order quantity for the `exec_spawn_id`.
912    ///
913    /// # Panics
914    ///
915    /// Panics if the cache is already mutably borrowed.
916    #[must_use]
917    pub fn exec_spawn_total_quantity(
918        &self,
919        exec_spawn_id: &ClientOrderId,
920        active_only: bool,
921    ) -> Option<Quantity> {
922        self.cache()
923            .exec_spawn_total_quantity(exec_spawn_id, active_only)
924    }
925
926    /// Returns the total filled quantity for all orders with the `exec_spawn_id`.
927    ///
928    /// # Panics
929    ///
930    /// Panics if the cache is already mutably borrowed.
931    #[must_use]
932    pub fn exec_spawn_total_filled_qty(
933        &self,
934        exec_spawn_id: &ClientOrderId,
935        active_only: bool,
936    ) -> Option<Quantity> {
937        self.cache()
938            .exec_spawn_total_filled_qty(exec_spawn_id, active_only)
939    }
940
941    /// Returns the total leaves quantity for all orders with the `exec_spawn_id`.
942    ///
943    /// # Panics
944    ///
945    /// Panics if the cache is already mutably borrowed.
946    #[must_use]
947    pub fn exec_spawn_total_leaves_qty(
948        &self,
949        exec_spawn_id: &ClientOrderId,
950        active_only: bool,
951    ) -> Option<Quantity> {
952        self.cache()
953            .exec_spawn_total_leaves_qty(exec_spawn_id, active_only)
954    }
955
956    /// Returns an owned copy of the position for the `position_id` (if found).
957    ///
958    /// # Panics
959    ///
960    /// Panics if the cache is already mutably borrowed.
961    #[must_use]
962    pub fn position(&self, position_id: &PositionId) -> Option<Position> {
963        self.cache()
964            .position_ref(position_id)
965            .map(|position| position.cloned())
966    }
967
968    /// Returns an owned copy of the position for the `position_id`.
969    ///
970    /// # Errors
971    ///
972    /// Returns an error if:
973    /// - The position is not present in the cache.
974    /// - The cache is already mutably borrowed.
975    pub fn try_position(&self, position_id: &PositionId) -> Result<Position, PositionLookupError> {
976        self.try_cache("try_position")?
977            .try_position_ref(position_id)
978            .map(|position| position.cloned())
979    }
980
981    /// Returns an owned copy of the position for the `client_order_id` (if found).
982    ///
983    /// # Panics
984    ///
985    /// Panics if the cache is already mutably borrowed.
986    #[must_use]
987    pub fn position_for_order(&self, client_order_id: &ClientOrderId) -> Option<Position> {
988        self.cache()
989            .position_for_order_ref(client_order_id)
990            .map(|position| position.cloned())
991    }
992
993    /// Returns the position ID for the `client_order_id` (if found).
994    ///
995    /// # Panics
996    ///
997    /// Panics if the cache is already mutably borrowed.
998    #[must_use]
999    pub fn position_id(&self, client_order_id: &ClientOrderId) -> Option<PositionId> {
1000        self.cache().position_id(client_order_id).copied()
1001    }
1002
1003    /// Returns owned copies of all positions matching the optional filter parameters.
1004    ///
1005    /// # Panics
1006    ///
1007    /// Panics if the cache is already mutably borrowed.
1008    #[must_use]
1009    pub fn positions(
1010        &self,
1011        venue: Option<&Venue>,
1012        instrument_id: Option<&InstrumentId>,
1013        strategy_id: Option<&StrategyId>,
1014        account_id: Option<&AccountId>,
1015        side: Option<PositionSide>,
1016    ) -> Vec<Position> {
1017        self.cache()
1018            .positions_refs(venue, instrument_id, strategy_id, account_id, side)
1019            .into_iter()
1020            .map(|position| position.cloned())
1021            .collect()
1022    }
1023
1024    /// Returns owned copies of all open positions matching the optional filter parameters.
1025    ///
1026    /// # Panics
1027    ///
1028    /// Panics if the cache is already mutably borrowed.
1029    #[must_use]
1030    pub fn positions_open(
1031        &self,
1032        venue: Option<&Venue>,
1033        instrument_id: Option<&InstrumentId>,
1034        strategy_id: Option<&StrategyId>,
1035        account_id: Option<&AccountId>,
1036        side: Option<PositionSide>,
1037    ) -> Vec<Position> {
1038        self.cache()
1039            .positions_open_refs(venue, instrument_id, strategy_id, account_id, side)
1040            .into_iter()
1041            .map(|position| position.cloned())
1042            .collect()
1043    }
1044
1045    /// Returns owned copies of all closed positions matching the optional filter parameters.
1046    ///
1047    /// # Panics
1048    ///
1049    /// Panics if the cache is already mutably borrowed.
1050    #[must_use]
1051    pub fn positions_closed(
1052        &self,
1053        venue: Option<&Venue>,
1054        instrument_id: Option<&InstrumentId>,
1055        strategy_id: Option<&StrategyId>,
1056        account_id: Option<&AccountId>,
1057        side: Option<PositionSide>,
1058    ) -> Vec<Position> {
1059        self.cache()
1060            .positions_closed_refs(venue, instrument_id, strategy_id, account_id, side)
1061            .into_iter()
1062            .map(|position| position.cloned())
1063            .collect()
1064    }
1065
1066    /// Returns whether a position with the `position_id` exists.
1067    ///
1068    /// # Panics
1069    ///
1070    /// Panics if the cache is already mutably borrowed.
1071    #[must_use]
1072    pub fn position_exists(&self, position_id: &PositionId) -> bool {
1073        self.cache().position_exists(position_id)
1074    }
1075
1076    /// Returns whether a position with the `position_id` is open.
1077    ///
1078    /// # Panics
1079    ///
1080    /// Panics if the cache is already mutably borrowed.
1081    #[must_use]
1082    pub fn is_position_open(&self, position_id: &PositionId) -> bool {
1083        self.cache().is_position_open(position_id)
1084    }
1085
1086    /// Returns whether a position with the `position_id` is closed.
1087    ///
1088    /// # Panics
1089    ///
1090    /// Panics if the cache is already mutably borrowed.
1091    #[must_use]
1092    pub fn is_position_closed(&self, position_id: &PositionId) -> bool {
1093        self.cache().is_position_closed(position_id)
1094    }
1095
1096    /// Returns the count of all open positions matching the optional filter parameters.
1097    ///
1098    /// # Panics
1099    ///
1100    /// Panics if the cache is already mutably borrowed.
1101    #[must_use]
1102    pub fn positions_open_count(
1103        &self,
1104        venue: Option<&Venue>,
1105        instrument_id: Option<&InstrumentId>,
1106        strategy_id: Option<&StrategyId>,
1107        account_id: Option<&AccountId>,
1108        side: Option<PositionSide>,
1109    ) -> usize {
1110        self.cache()
1111            .positions_open_count(venue, instrument_id, strategy_id, account_id, side)
1112    }
1113
1114    /// Returns the count of all closed positions matching the optional filter parameters.
1115    ///
1116    /// # Panics
1117    ///
1118    /// Panics if the cache is already mutably borrowed.
1119    #[must_use]
1120    pub fn positions_closed_count(
1121        &self,
1122        venue: Option<&Venue>,
1123        instrument_id: Option<&InstrumentId>,
1124        strategy_id: Option<&StrategyId>,
1125        account_id: Option<&AccountId>,
1126        side: Option<PositionSide>,
1127    ) -> usize {
1128        self.cache()
1129            .positions_closed_count(venue, instrument_id, strategy_id, account_id, side)
1130    }
1131
1132    /// Returns the count of all positions matching the optional filter parameters.
1133    ///
1134    /// # Panics
1135    ///
1136    /// Panics if the cache is already mutably borrowed.
1137    #[must_use]
1138    pub fn positions_total_count(
1139        &self,
1140        venue: Option<&Venue>,
1141        instrument_id: Option<&InstrumentId>,
1142        strategy_id: Option<&StrategyId>,
1143        account_id: Option<&AccountId>,
1144        side: Option<PositionSide>,
1145    ) -> usize {
1146        self.cache()
1147            .positions_total_count(venue, instrument_id, strategy_id, account_id, side)
1148    }
1149
1150    /// Returns whether any open position matches the optional filter parameters.
1151    ///
1152    /// # Panics
1153    ///
1154    /// Panics if the cache is already mutably borrowed.
1155    #[must_use]
1156    pub fn has_positions_open(
1157        &self,
1158        venue: Option<&Venue>,
1159        instrument_id: Option<&InstrumentId>,
1160        strategy_id: Option<&StrategyId>,
1161        account_id: Option<&AccountId>,
1162        side: Option<PositionSide>,
1163    ) -> bool {
1164        self.cache()
1165            .has_positions_open(venue, instrument_id, strategy_id, account_id, side)
1166    }
1167
1168    /// Returns whether any closed position matches the optional filter parameters.
1169    ///
1170    /// # Panics
1171    ///
1172    /// Panics if the cache is already mutably borrowed.
1173    #[must_use]
1174    pub fn has_positions_closed(
1175        &self,
1176        venue: Option<&Venue>,
1177        instrument_id: Option<&InstrumentId>,
1178        strategy_id: Option<&StrategyId>,
1179        account_id: Option<&AccountId>,
1180        side: Option<PositionSide>,
1181    ) -> bool {
1182        self.cache()
1183            .has_positions_closed(venue, instrument_id, strategy_id, account_id, side)
1184    }
1185
1186    /// Returns whether any position matches the optional filter parameters.
1187    ///
1188    /// # Panics
1189    ///
1190    /// Panics if the cache is already mutably borrowed.
1191    #[must_use]
1192    pub fn has_positions(
1193        &self,
1194        venue: Option<&Venue>,
1195        instrument_id: Option<&InstrumentId>,
1196        strategy_id: Option<&StrategyId>,
1197        account_id: Option<&AccountId>,
1198        side: Option<PositionSide>,
1199    ) -> bool {
1200        self.cache()
1201            .has_positions(venue, instrument_id, strategy_id, account_id, side)
1202    }
1203
1204    /// Returns the strategy ID for the `client_order_id` (if found).
1205    ///
1206    /// # Panics
1207    ///
1208    /// Panics if the cache is already mutably borrowed.
1209    #[must_use]
1210    pub fn strategy_id_for_order(&self, client_order_id: &ClientOrderId) -> Option<StrategyId> {
1211        self.cache().strategy_id_for_order(client_order_id).copied()
1212    }
1213
1214    /// Returns the strategy ID for the `position_id` (if found).
1215    ///
1216    /// # Panics
1217    ///
1218    /// Panics if the cache is already mutably borrowed.
1219    #[must_use]
1220    pub fn strategy_id_for_position(&self, position_id: &PositionId) -> Option<StrategyId> {
1221        self.cache().strategy_id_for_position(position_id).copied()
1222    }
1223
1224    /// Returns the general cache value for the `key` (if found).
1225    ///
1226    /// # Errors
1227    ///
1228    /// Returns an error if:
1229    /// - The `key` is invalid.
1230    /// - The cache is already mutably borrowed.
1231    pub fn get(&self, key: &str) -> anyhow::Result<Option<Bytes>> {
1232        let cache = self.try_cache("get")?;
1233        let value = cache.get(key)?;
1234        Ok(value.cloned())
1235    }
1236
1237    /// Returns the price for the `instrument_id` and `price_type` (if found).
1238    ///
1239    /// For `Mid`, returns `None` if no quote is cached or either price is a sentinel.
1240    /// For quote precision `p`, the midpoint has precision `p + 1` when exactly representable,
1241    /// otherwise `p`, rounded half-even if necessary. The fallback applies when the precision
1242    /// limit or raw range rules out `p + 1`, and when `p` is the maximum float precision (16),
1243    /// so a midpoint of a float-convertible quote stays float-convertible.
1244    ///
1245    /// # Panics
1246    ///
1247    /// Panics if the cache is already mutably borrowed.
1248    #[must_use]
1249    pub fn price(&self, instrument_id: &InstrumentId, price_type: PriceType) -> Option<Price> {
1250        self.cache().price(instrument_id, price_type)
1251    }
1252
1253    /// Returns cached quotes for the `instrument_id`, most recently added first.
1254    ///
1255    /// See [`Cache::quotes`] for ordering and missing-data behavior.
1256    ///
1257    /// # Panics
1258    ///
1259    /// Panics if the cache is already mutably borrowed.
1260    #[must_use]
1261    pub fn quotes(&self, instrument_id: &InstrumentId) -> Option<Vec<QuoteTick>> {
1262        self.cache().quotes(instrument_id)
1263    }
1264
1265    /// Returns cached trades for the `instrument_id`, most recently added first.
1266    ///
1267    /// See [`Cache::trades`] for ordering and missing-data behavior.
1268    ///
1269    /// # Panics
1270    ///
1271    /// Panics if the cache is already mutably borrowed.
1272    #[must_use]
1273    pub fn trades(&self, instrument_id: &InstrumentId) -> Option<Vec<TradeTick>> {
1274        self.cache().trades(instrument_id)
1275    }
1276
1277    /// Returns cached mark price updates for the `instrument_id`, most recently added first.
1278    ///
1279    /// See [`Cache::mark_prices`] for ordering and missing-data behavior.
1280    ///
1281    /// # Panics
1282    ///
1283    /// Panics if the cache is already mutably borrowed.
1284    #[must_use]
1285    pub fn mark_prices(&self, instrument_id: &InstrumentId) -> Option<Vec<MarkPriceUpdate>> {
1286        self.cache().mark_prices(instrument_id)
1287    }
1288
1289    /// Returns cached index price updates for the `instrument_id`, most recently added first.
1290    ///
1291    /// See [`Cache::index_prices`] for ordering and missing-data behavior.
1292    ///
1293    /// # Panics
1294    ///
1295    /// Panics if the cache is already mutably borrowed.
1296    #[must_use]
1297    pub fn index_prices(&self, instrument_id: &InstrumentId) -> Option<Vec<IndexPriceUpdate>> {
1298        self.cache().index_prices(instrument_id)
1299    }
1300
1301    /// Returns cached funding rate updates for the `instrument_id`, most recently added first.
1302    ///
1303    /// See [`Cache::funding_rates`] for ordering and missing-data behavior.
1304    ///
1305    /// # Panics
1306    ///
1307    /// Panics if the cache is already mutably borrowed.
1308    #[must_use]
1309    pub fn funding_rates(&self, instrument_id: &InstrumentId) -> Option<Vec<FundingRateUpdate>> {
1310        self.cache().funding_rates(instrument_id)
1311    }
1312
1313    /// Returns cached instrument status updates for the `instrument_id`, most recently added first.
1314    ///
1315    /// See [`Cache::instrument_statuses`] for ordering and missing-data behavior.
1316    ///
1317    /// # Panics
1318    ///
1319    /// Panics if the cache is already mutably borrowed.
1320    #[must_use]
1321    pub fn instrument_statuses(
1322        &self,
1323        instrument_id: &InstrumentId,
1324    ) -> Option<Vec<InstrumentStatus>> {
1325        self.cache().instrument_statuses(instrument_id)
1326    }
1327
1328    /// Returns cached bars for the `bar_type`, newest `ts_event` first.
1329    ///
1330    /// See [`Cache::bars`] for ordering and missing-data behavior.
1331    ///
1332    /// # Panics
1333    ///
1334    /// Panics if the cache is already mutably borrowed.
1335    #[must_use]
1336    pub fn bars(&self, bar_type: &BarType) -> Option<Vec<Bar>> {
1337        self.cache().bars(bar_type)
1338    }
1339
1340    /// Returns an owned copy of the order book for the `instrument_id` (if found).
1341    ///
1342    /// See [`Cache::order_book`] for the current-state contract.
1343    ///
1344    /// # Panics
1345    ///
1346    /// Panics if the cache is already mutably borrowed.
1347    #[must_use]
1348    pub fn order_book(&self, instrument_id: &InstrumentId) -> Option<OrderBook> {
1349        self.cache().order_book(instrument_id).cloned()
1350    }
1351
1352    /// Returns an owned copy of the order book for the `instrument_id`.
1353    ///
1354    /// # Errors
1355    ///
1356    /// Returns an error if:
1357    /// - The order book is not present in the cache.
1358    /// - The cache is already mutably borrowed.
1359    pub fn try_order_book(
1360        &self,
1361        instrument_id: &InstrumentId,
1362    ) -> Result<OrderBook, OrderBookLookupError> {
1363        self.try_cache("try_order_book")?
1364            .try_order_book(instrument_id)
1365            .cloned()
1366    }
1367
1368    /// Returns an owned copy of the own order book for the `instrument_id` (if found).
1369    ///
1370    /// # Panics
1371    ///
1372    /// Panics if the cache is already mutably borrowed.
1373    #[must_use]
1374    pub fn own_order_book(&self, instrument_id: &InstrumentId) -> Option<OwnOrderBook> {
1375        self.cache().own_order_book(instrument_id).cloned()
1376    }
1377
1378    /// Returns an owned copy of the own order book for the `instrument_id`.
1379    ///
1380    /// # Errors
1381    ///
1382    /// Returns an error if:
1383    /// - The own order book is not present in the cache.
1384    /// - The cache is already mutably borrowed.
1385    pub fn try_own_order_book(
1386        &self,
1387        instrument_id: &InstrumentId,
1388    ) -> Result<OwnOrderBook, OwnOrderBookLookupError> {
1389        self.try_cache("try_own_order_book")?
1390            .try_own_order_book(instrument_id)
1391            .cloned()
1392    }
1393
1394    /// Returns the most recently added quote for the `instrument_id` (if found).
1395    ///
1396    /// # Panics
1397    ///
1398    /// Panics if the cache is already mutably borrowed.
1399    #[must_use]
1400    pub fn quote(&self, instrument_id: &InstrumentId) -> Option<QuoteTick> {
1401        self.cache().quote(instrument_id).copied()
1402    }
1403
1404    /// Returns the quote at `index` for the `instrument_id` (if found).
1405    ///
1406    /// Index 0 is the most recently added quote. See [`Cache::quote_at_index`] for ordering
1407    /// and out-of-range behavior.
1408    ///
1409    /// # Panics
1410    ///
1411    /// Panics if the cache is already mutably borrowed.
1412    #[must_use]
1413    pub fn quote_at_index(&self, instrument_id: &InstrumentId, index: usize) -> Option<QuoteTick> {
1414        self.cache().quote_at_index(instrument_id, index).copied()
1415    }
1416
1417    /// Returns the most recently added trade for the `instrument_id` (if found).
1418    ///
1419    /// # Panics
1420    ///
1421    /// Panics if the cache is already mutably borrowed.
1422    #[must_use]
1423    pub fn trade(&self, instrument_id: &InstrumentId) -> Option<TradeTick> {
1424        self.cache().trade(instrument_id).copied()
1425    }
1426
1427    /// Returns the trade at `index` for the `instrument_id` (if found).
1428    ///
1429    /// Index 0 is the most recently added trade. See [`Cache::trade_at_index`] for ordering
1430    /// and out-of-range behavior.
1431    ///
1432    /// # Panics
1433    ///
1434    /// Panics if the cache is already mutably borrowed.
1435    #[must_use]
1436    pub fn trade_at_index(&self, instrument_id: &InstrumentId, index: usize) -> Option<TradeTick> {
1437        self.cache().trade_at_index(instrument_id, index).copied()
1438    }
1439
1440    /// Returns the most recently added mark price update for the `instrument_id` (if found).
1441    ///
1442    /// # Panics
1443    ///
1444    /// Panics if the cache is already mutably borrowed.
1445    #[must_use]
1446    pub fn mark_price(&self, instrument_id: &InstrumentId) -> Option<MarkPriceUpdate> {
1447        self.cache().mark_price(instrument_id).copied()
1448    }
1449
1450    /// Returns the most recently added index price update for the `instrument_id` (if found).
1451    ///
1452    /// # Panics
1453    ///
1454    /// Panics if the cache is already mutably borrowed.
1455    #[must_use]
1456    pub fn index_price(&self, instrument_id: &InstrumentId) -> Option<IndexPriceUpdate> {
1457        self.cache().index_price(instrument_id).copied()
1458    }
1459
1460    /// Returns the most recently added funding rate update for the `instrument_id` (if found).
1461    ///
1462    /// # Panics
1463    ///
1464    /// Panics if the cache is already mutably borrowed.
1465    #[must_use]
1466    pub fn funding_rate(&self, instrument_id: &InstrumentId) -> Option<FundingRateUpdate> {
1467        self.cache().funding_rate(instrument_id).copied()
1468    }
1469
1470    /// Returns the most recently added instrument status update for the `instrument_id` (if found).
1471    ///
1472    /// # Panics
1473    ///
1474    /// Panics if the cache is already mutably borrowed.
1475    #[must_use]
1476    pub fn instrument_status(&self, instrument_id: &InstrumentId) -> Option<InstrumentStatus> {
1477        self.cache().instrument_status(instrument_id).copied()
1478    }
1479
1480    /// Returns the cached close for the `instrument_id` (if found).
1481    ///
1482    /// See [`Cache::instrument_close`] for replacement behavior.
1483    ///
1484    /// # Panics
1485    ///
1486    /// Panics if the cache is already mutably borrowed.
1487    #[must_use]
1488    pub fn instrument_close(&self, instrument_id: &InstrumentId) -> Option<InstrumentClose> {
1489        self.cache().instrument_close(instrument_id).copied()
1490    }
1491
1492    /// Returns the most recent bar by `ts_event` for the `bar_type` (if found).
1493    ///
1494    /// # Panics
1495    ///
1496    /// Panics if the cache is already mutably borrowed.
1497    #[must_use]
1498    pub fn bar(&self, bar_type: &BarType) -> Option<Bar> {
1499        self.cache().bar(bar_type).copied()
1500    }
1501
1502    /// Returns the bar at `index` for the `bar_type` (if found).
1503    ///
1504    /// Index 0 is the most recent bar by `ts_event`. See [`Cache::bar_at_index`] for ordering
1505    /// and out-of-range behavior.
1506    ///
1507    /// # Panics
1508    ///
1509    /// Panics if the cache is already mutably borrowed.
1510    #[must_use]
1511    pub fn bar_at_index(&self, bar_type: &BarType, index: usize) -> Option<Bar> {
1512        self.cache().bar_at_index(bar_type, index).copied()
1513    }
1514
1515    /// Returns the order book update count for the `instrument_id`.
1516    ///
1517    /// # Panics
1518    ///
1519    /// Panics if the cache is already mutably borrowed.
1520    #[must_use]
1521    pub fn book_update_count(&self, instrument_id: &InstrumentId) -> usize {
1522        self.cache().book_update_count(instrument_id)
1523    }
1524
1525    /// Returns the quote tick count for the `instrument_id`.
1526    ///
1527    /// # Panics
1528    ///
1529    /// Panics if the cache is already mutably borrowed.
1530    #[must_use]
1531    pub fn quote_count(&self, instrument_id: &InstrumentId) -> usize {
1532        self.cache().quote_count(instrument_id)
1533    }
1534
1535    /// Returns the trade tick count for the `instrument_id`.
1536    ///
1537    /// # Panics
1538    ///
1539    /// Panics if the cache is already mutably borrowed.
1540    #[must_use]
1541    pub fn trade_count(&self, instrument_id: &InstrumentId) -> usize {
1542        self.cache().trade_count(instrument_id)
1543    }
1544
1545    /// Returns the mark price update count for the `instrument_id`.
1546    ///
1547    /// # Panics
1548    ///
1549    /// Panics if the cache is already mutably borrowed.
1550    #[must_use]
1551    pub fn mark_price_count(&self, instrument_id: &InstrumentId) -> usize {
1552        self.cache().mark_price_count(instrument_id)
1553    }
1554
1555    /// Returns the index price update count for the `instrument_id`.
1556    ///
1557    /// # Panics
1558    ///
1559    /// Panics if the cache is already mutably borrowed.
1560    #[must_use]
1561    pub fn index_price_count(&self, instrument_id: &InstrumentId) -> usize {
1562        self.cache().index_price_count(instrument_id)
1563    }
1564
1565    /// Returns the funding rate update count for the `instrument_id`.
1566    ///
1567    /// # Panics
1568    ///
1569    /// Panics if the cache is already mutably borrowed.
1570    #[must_use]
1571    pub fn funding_rate_count(&self, instrument_id: &InstrumentId) -> usize {
1572        self.cache().funding_rate_count(instrument_id)
1573    }
1574
1575    /// Returns the instrument status update count for the `instrument_id`.
1576    ///
1577    /// # Panics
1578    ///
1579    /// Panics if the cache is already mutably borrowed.
1580    #[must_use]
1581    pub fn instrument_status_count(&self, instrument_id: &InstrumentId) -> usize {
1582        self.cache().instrument_status_count(instrument_id)
1583    }
1584
1585    /// Returns the bar count for the `bar_type`.
1586    ///
1587    /// # Panics
1588    ///
1589    /// Panics if the cache is already mutably borrowed.
1590    #[must_use]
1591    pub fn bar_count(&self, bar_type: &BarType) -> usize {
1592        self.cache().bar_count(bar_type)
1593    }
1594
1595    /// Returns whether the cache contains an order book for the `instrument_id`.
1596    ///
1597    /// # Panics
1598    ///
1599    /// Panics if the cache is already mutably borrowed.
1600    #[must_use]
1601    pub fn has_order_book(&self, instrument_id: &InstrumentId) -> bool {
1602        self.cache().has_order_book(instrument_id)
1603    }
1604
1605    /// Returns whether the cache contains quotes for the `instrument_id`.
1606    ///
1607    /// # Panics
1608    ///
1609    /// Panics if the cache is already mutably borrowed.
1610    #[must_use]
1611    pub fn has_quote_ticks(&self, instrument_id: &InstrumentId) -> bool {
1612        self.cache().has_quote_ticks(instrument_id)
1613    }
1614
1615    /// Returns whether the cache contains trades for the `instrument_id`.
1616    ///
1617    /// # Panics
1618    ///
1619    /// Panics if the cache is already mutably borrowed.
1620    #[must_use]
1621    pub fn has_trade_ticks(&self, instrument_id: &InstrumentId) -> bool {
1622        self.cache().has_trade_ticks(instrument_id)
1623    }
1624
1625    /// Returns whether the cache contains mark price updates for the `instrument_id`.
1626    ///
1627    /// # Panics
1628    ///
1629    /// Panics if the cache is already mutably borrowed.
1630    #[must_use]
1631    pub fn has_mark_prices(&self, instrument_id: &InstrumentId) -> bool {
1632        self.cache().has_mark_prices(instrument_id)
1633    }
1634
1635    /// Returns whether the cache contains index price updates for the `instrument_id`.
1636    ///
1637    /// # Panics
1638    ///
1639    /// Panics if the cache is already mutably borrowed.
1640    #[must_use]
1641    pub fn has_index_prices(&self, instrument_id: &InstrumentId) -> bool {
1642        self.cache().has_index_prices(instrument_id)
1643    }
1644
1645    /// Returns whether the cache contains funding rate updates for the `instrument_id`.
1646    ///
1647    /// # Panics
1648    ///
1649    /// Panics if the cache is already mutably borrowed.
1650    #[must_use]
1651    pub fn has_funding_rates(&self, instrument_id: &InstrumentId) -> bool {
1652        self.cache().has_funding_rates(instrument_id)
1653    }
1654
1655    /// Returns whether the cache contains instrument status updates for the `instrument_id`.
1656    ///
1657    /// # Panics
1658    ///
1659    /// Panics if the cache is already mutably borrowed.
1660    #[must_use]
1661    pub fn has_instrument_statuses(&self, instrument_id: &InstrumentId) -> bool {
1662        self.cache().has_instrument_statuses(instrument_id)
1663    }
1664
1665    /// Returns whether the cache contains a close for the `instrument_id`.
1666    ///
1667    /// # Panics
1668    ///
1669    /// Panics if the cache is already mutably borrowed.
1670    #[must_use]
1671    pub fn has_instrument_close(&self, instrument_id: &InstrumentId) -> bool {
1672        self.cache().has_instrument_close(instrument_id)
1673    }
1674
1675    /// Returns whether the cache contains bars for the `bar_type`.
1676    ///
1677    /// # Panics
1678    ///
1679    /// Panics if the cache is already mutably borrowed.
1680    #[must_use]
1681    pub fn has_bars(&self, bar_type: &BarType) -> bool {
1682        self.cache().has_bars(bar_type)
1683    }
1684
1685    /// Returns the exchange rate for the given currencies and price type (if available).
1686    ///
1687    /// # Panics
1688    ///
1689    /// Panics if the cache is already mutably borrowed.
1690    #[must_use]
1691    pub fn get_xrate(
1692        &self,
1693        venue: Venue,
1694        from_currency: Currency,
1695        to_currency: Currency,
1696        price_type: PriceType,
1697    ) -> Option<Decimal> {
1698        self.cache()
1699            .get_xrate(venue, from_currency, to_currency, price_type)
1700    }
1701
1702    /// Returns the mark exchange rate for the currency pair (if set).
1703    ///
1704    /// # Panics
1705    ///
1706    /// Panics if the cache is already mutably borrowed.
1707    #[must_use]
1708    pub fn get_mark_xrate(&self, from_currency: Currency, to_currency: Currency) -> Option<f64> {
1709        self.cache().get_mark_xrate(from_currency, to_currency)
1710    }
1711
1712    /// Returns the yield curve for the `key` (if found).
1713    ///
1714    /// # Panics
1715    ///
1716    /// Panics if the cache is already mutably borrowed.
1717    #[must_use]
1718    pub fn yield_curve(&self, key: &str) -> Option<Box<dyn Fn(f64) -> f64>> {
1719        self.cache().yield_curve(key)
1720    }
1721
1722    /// Returns an owned copy of the greeks data for the `instrument_id` (if found).
1723    ///
1724    /// # Panics
1725    ///
1726    /// Panics if the cache is already mutably borrowed.
1727    #[must_use]
1728    pub fn greeks(&self, instrument_id: &InstrumentId) -> Option<GreeksData> {
1729        self.cache().greeks(instrument_id)
1730    }
1731
1732    /// Returns exchange-provided option greeks for the `instrument_id` (if found).
1733    ///
1734    /// # Panics
1735    ///
1736    /// Panics if the cache is already mutably borrowed.
1737    #[must_use]
1738    pub fn option_greeks(&self, instrument_id: &InstrumentId) -> Option<OptionGreeks> {
1739        self.cache().option_greeks(instrument_id).copied()
1740    }
1741
1742    /// Returns the currency for the `code` (if found).
1743    ///
1744    /// # Panics
1745    ///
1746    /// Panics if the cache is already mutably borrowed.
1747    #[must_use]
1748    pub fn currency(&self, code: &Ustr) -> Option<Currency> {
1749        self.cache().currency(code).copied()
1750    }
1751
1752    /// Returns the currency for the `code`.
1753    ///
1754    /// # Errors
1755    ///
1756    /// Returns an error if:
1757    /// - The currency is not present in the cache.
1758    /// - The cache is already mutably borrowed.
1759    pub fn try_currency(&self, code: &Ustr) -> Result<Currency, CurrencyLookupError> {
1760        self.try_cache("try_currency")?.try_currency(code).copied()
1761    }
1762
1763    /// Returns an owned copy of the instrument for the `instrument_id` (if found).
1764    ///
1765    /// # Panics
1766    ///
1767    /// Panics if the cache is already mutably borrowed.
1768    #[must_use]
1769    pub fn instrument(&self, instrument_id: &InstrumentId) -> Option<InstrumentAny> {
1770        self.cache().instrument(instrument_id).cloned()
1771    }
1772
1773    /// Returns an owned copy of the instrument for the `instrument_id`.
1774    ///
1775    /// # Errors
1776    ///
1777    /// Returns an error if:
1778    /// - The instrument is not present in the cache.
1779    /// - The cache is already mutably borrowed.
1780    pub fn try_instrument(
1781        &self,
1782        instrument_id: &InstrumentId,
1783    ) -> Result<InstrumentAny, InstrumentLookupError> {
1784        self.try_cache("try_instrument")?
1785            .try_instrument(instrument_id)
1786            .cloned()
1787    }
1788
1789    /// Returns the instrument IDs in the cache, optionally filtered by `venue`.
1790    ///
1791    /// # Panics
1792    ///
1793    /// Panics if the cache is already mutably borrowed.
1794    #[must_use]
1795    pub fn instrument_ids(&self, venue: Option<&Venue>) -> Vec<InstrumentId> {
1796        self.cache()
1797            .instrument_ids(venue)
1798            .into_iter()
1799            .copied()
1800            .collect()
1801    }
1802
1803    /// Returns owned copies of all instruments for the `venue`.
1804    ///
1805    /// # Panics
1806    ///
1807    /// Panics if the cache is already mutably borrowed.
1808    #[must_use]
1809    pub fn instruments(&self, venue: &Venue, underlying: Option<&Ustr>) -> Vec<InstrumentAny> {
1810        self.cache()
1811            .instruments(venue, underlying)
1812            .into_iter()
1813            .cloned()
1814            .collect()
1815    }
1816
1817    /// Returns owned copies of all instruments for the `venue`, parent `root`, and instrument
1818    /// `class`.
1819    ///
1820    /// # Panics
1821    ///
1822    /// Panics if the cache is already mutably borrowed.
1823    #[must_use]
1824    pub fn instruments_by_parent(
1825        &self,
1826        venue: &Venue,
1827        root: &Ustr,
1828        class: InstrumentClass,
1829    ) -> Vec<InstrumentAny> {
1830        self.cache()
1831            .instruments_by_parent(venue, root, class)
1832            .into_iter()
1833            .cloned()
1834            .collect()
1835    }
1836
1837    /// Returns the bar types in the cache, optionally filtered by instrument and price type.
1838    ///
1839    /// # Panics
1840    ///
1841    /// Panics if the cache is already mutably borrowed.
1842    #[must_use]
1843    pub fn bar_types(
1844        &self,
1845        instrument_id: Option<&InstrumentId>,
1846        price_type: Option<&PriceType>,
1847        aggregation_source: AggregationSource,
1848    ) -> Vec<BarType> {
1849        self.cache()
1850            .bar_types(instrument_id, price_type, aggregation_source)
1851            .into_iter()
1852            .copied()
1853            .collect()
1854    }
1855
1856    /// Returns an owned copy of the synthetic instrument for the `instrument_id` (if found).
1857    ///
1858    /// # Panics
1859    ///
1860    /// Panics if the cache is already mutably borrowed.
1861    #[must_use]
1862    pub fn synthetic(&self, instrument_id: &InstrumentId) -> Option<SyntheticInstrument> {
1863        self.cache().synthetic(instrument_id).cloned()
1864    }
1865
1866    /// Returns an owned copy of the synthetic instrument for the `instrument_id`.
1867    ///
1868    /// # Errors
1869    ///
1870    /// Returns an error if:
1871    /// - The synthetic instrument is not present
1872    ///   in the cache.
1873    /// - The cache is already mutably borrowed.
1874    pub fn try_synthetic(
1875        &self,
1876        instrument_id: &InstrumentId,
1877    ) -> Result<SyntheticInstrument, SyntheticInstrumentLookupError> {
1878        self.try_cache("try_synthetic")?
1879            .try_synthetic(instrument_id)
1880            .cloned()
1881    }
1882
1883    /// Returns the synthetic instrument IDs in the cache.
1884    ///
1885    /// # Panics
1886    ///
1887    /// Panics if the cache is already mutably borrowed.
1888    #[must_use]
1889    pub fn synthetic_ids(&self) -> Vec<InstrumentId> {
1890        self.cache().synthetic_ids().into_iter().copied().collect()
1891    }
1892
1893    /// Returns owned copies of all synthetic instruments in the cache.
1894    ///
1895    /// # Panics
1896    ///
1897    /// Panics if the cache is already mutably borrowed.
1898    #[must_use]
1899    pub fn synthetics(&self) -> Vec<SyntheticInstrument> {
1900        self.cache().synthetics().into_iter().cloned().collect()
1901    }
1902
1903    /// Returns an owned copy of the pool for the `instrument_id` (if found).
1904    ///
1905    /// # Panics
1906    ///
1907    /// Panics if the cache is already mutably borrowed.
1908    #[cfg(feature = "defi")]
1909    #[must_use]
1910    pub fn pool(&self, instrument_id: &InstrumentId) -> Option<Pool> {
1911        self.cache().pool(instrument_id).cloned()
1912    }
1913
1914    /// Returns the pool instrument IDs in the cache, optionally filtered by `venue`.
1915    ///
1916    /// # Panics
1917    ///
1918    /// Panics if the cache is already mutably borrowed.
1919    #[cfg(feature = "defi")]
1920    #[must_use]
1921    pub fn pool_ids(&self, venue: Option<&Venue>) -> Vec<InstrumentId> {
1922        self.cache().pool_ids(venue)
1923    }
1924
1925    /// Returns owned copies of all pools in the cache, optionally filtered by `venue`.
1926    ///
1927    /// # Panics
1928    ///
1929    /// Panics if the cache is already mutably borrowed.
1930    #[cfg(feature = "defi")]
1931    #[must_use]
1932    pub fn pools(&self, venue: Option<&Venue>) -> Vec<Pool> {
1933        self.cache().pools(venue).into_iter().cloned().collect()
1934    }
1935
1936    /// Returns an owned copy of the pool profiler for the `instrument_id` (if found).
1937    ///
1938    /// # Panics
1939    ///
1940    /// Panics if the cache is already mutably borrowed.
1941    #[cfg(feature = "defi")]
1942    #[must_use]
1943    pub fn pool_profiler(&self, instrument_id: &InstrumentId) -> Option<PoolProfiler> {
1944        self.cache().pool_profiler(instrument_id).cloned()
1945    }
1946
1947    /// Returns the pool profiler instrument IDs in the cache, optionally filtered by `venue`.
1948    ///
1949    /// # Panics
1950    ///
1951    /// Panics if the cache is already mutably borrowed.
1952    #[cfg(feature = "defi")]
1953    #[must_use]
1954    pub fn pool_profiler_ids(&self, venue: Option<&Venue>) -> Vec<InstrumentId> {
1955        self.cache().pool_profiler_ids(venue)
1956    }
1957
1958    /// Returns owned copies of all pool profilers in the cache, optionally filtered by `venue`.
1959    ///
1960    /// # Panics
1961    ///
1962    /// Panics if the cache is already mutably borrowed.
1963    #[cfg(feature = "defi")]
1964    #[must_use]
1965    pub fn pool_profilers(&self, venue: Option<&Venue>) -> Vec<PoolProfiler> {
1966        self.cache()
1967            .pool_profilers(venue)
1968            .into_iter()
1969            .cloned()
1970            .collect()
1971    }
1972
1973    /// Returns an owned copy of the account for the `account_id` (if found).
1974    ///
1975    /// # Panics
1976    ///
1977    /// Panics if the cache is already mutably borrowed.
1978    #[must_use]
1979    pub fn account(&self, account_id: &AccountId) -> Option<AccountAny> {
1980        self.cache().account_owned(account_id)
1981    }
1982
1983    /// Returns an owned copy of the account for the `account_id`.
1984    ///
1985    /// # Errors
1986    ///
1987    /// Returns an error if:
1988    /// - The account is not present in the cache.
1989    /// - The cache is already mutably borrowed.
1990    pub fn try_account(&self, account_id: &AccountId) -> Result<AccountAny, AccountLookupError> {
1991        self.try_cache("try_account")?
1992            .try_account(account_id)
1993            .map(|account| account.cloned())
1994    }
1995
1996    /// Returns an owned copy of the account for the `venue` (if found).
1997    ///
1998    /// Returns `None` when more than one account is issued under the `venue`.
1999    ///
2000    /// # Panics
2001    ///
2002    /// Panics if the cache is already mutably borrowed.
2003    #[must_use]
2004    pub fn account_for_venue(&self, venue: &Venue) -> Option<AccountAny> {
2005        self.cache().account_for_venue_owned(venue)
2006    }
2007
2008    /// Returns the account ID for the `venue` (if found).
2009    ///
2010    /// Returns `None` when more than one account is issued under the `venue`.
2011    ///
2012    /// # Panics
2013    ///
2014    /// Panics if the cache is already mutably borrowed.
2015    #[must_use]
2016    pub fn account_id(&self, venue: &Venue) -> Option<AccountId> {
2017        self.cache().account_id(venue).copied()
2018    }
2019
2020    /// Returns owned copies of all accounts matching the `account_id`.
2021    ///
2022    /// # Panics
2023    ///
2024    /// Panics if the cache is already mutably borrowed.
2025    #[must_use]
2026    pub fn accounts(&self, account_id: &AccountId) -> Vec<AccountAny> {
2027        self.cache()
2028            .accounts(account_id)
2029            .into_iter()
2030            .map(|account| account.cloned())
2031            .collect()
2032    }
2033
2034    /// Returns owned copies of every account in the cache.
2035    ///
2036    /// # Panics
2037    ///
2038    /// Panics if the cache is already mutably borrowed.
2039    #[must_use]
2040    pub fn accounts_all(&self) -> Vec<AccountAny> {
2041        self.cache().accounts_all_owned()
2042    }
2043
2044    fn cache(&self) -> Ref<'_, Cache> {
2045        self.try_cache("cache read")
2046            .unwrap_or_else(|e| panic!("{e}"))
2047    }
2048
2049    fn try_cache(&self, operation: &'static str) -> Result<Ref<'_, Cache>, ComponentAccessError> {
2050        self.cache
2051            .try_borrow()
2052            .map_err(|_| ComponentAccessError::ReadConflict {
2053                resource: "cache",
2054                operation,
2055            })
2056    }
2057}