Skip to main content

nautilus_common/cache/
mod.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//! In-memory cache for market and execution data, with optional persistent backing.
17//!
18//! Provides methods to load, query, and update cached data such as instruments, orders, and prices.
19
20pub mod config;
21pub mod database;
22pub mod fifo;
23pub mod quote;
24pub mod refs;
25
26mod bounded;
27mod error;
28mod index;
29mod position;
30
31#[cfg(test)]
32mod tests;
33
34use std::{
35    borrow::Cow,
36    cell::{Ref, RefCell},
37    fmt::{Debug, Display},
38    rc::Rc,
39    time::{SystemTime, UNIX_EPOCH},
40};
41
42use ahash::{AHashMap, AHashSet};
43use bounded::BoundedVecDeque;
44use bytes::Bytes;
45pub use config::CacheConfig; // Re-export
46use database::{CacheDatabaseAdapter, CacheMap};
47pub use error::{
48    ACCOUNT_NOT_FOUND, AccountLookupError, CURRENCY_NOT_FOUND, CurrencyLookupError,
49    INSTRUMENT_NOT_FOUND, InstrumentLookupError, ORDER_BOOK_NOT_FOUND, ORDER_LIST_NOT_FOUND,
50    ORDER_NOT_FOUND, OWN_ORDER_BOOK_NOT_FOUND, OrderBookLookupError, OrderListLookupError,
51    OrderLookupError, OwnOrderBookLookupError, POSITION_NOT_FOUND, PositionLookupError,
52    SYNTHETIC_INSTRUMENT_NOT_FOUND, SyntheticInstrumentLookupError, VenueOrderIdOwnershipError,
53};
54use index::CacheIndex;
55use indexmap::IndexMap;
56use nautilus_core::{
57    SharedCell, UnixNanos,
58    correctness::{
59        check_key_not_in_map, check_predicate_false, check_slice_not_empty,
60        check_valid_string_ascii,
61    },
62    datetime::secs_to_nanos_unchecked,
63};
64#[cfg(feature = "defi")]
65use nautilus_model::defi::{Pool, PoolProfiler};
66use nautilus_model::{
67    accounts::{Account, AccountAny},
68    data::{
69        Bar, BarType, FundingRateUpdate, GreeksData, IndexPriceUpdate, InstrumentStatus,
70        MarkPriceUpdate, QuoteTick, TradeTick, YieldCurveData, option_chain::OptionGreeks,
71    },
72    enums::{
73        AggregationSource, ContingencyType, InstrumentClass, OmsType, OrderSide, PositionSide,
74        PriceType, TriggerType,
75    },
76    events::{AccountState, OrderEventAny},
77    identifiers::{
78        AccountId, ClientId, ClientOrderId, ComponentId, ExecAlgorithmId, InstrumentId,
79        OrderListId, PositionId, StrategyId, Venue, VenueOrderId,
80    },
81    instruments::{Instrument, InstrumentAny, SyntheticInstrument},
82    orderbook::{
83        OrderBook,
84        own::{OwnOrderBook, should_handle_own_book_order},
85    },
86    orders::{Order, OrderAny, OrderError, OrderList},
87    position::Position,
88    types::{Currency, Money, Price, Quantity},
89};
90pub use position::CacheSnapshotRef;
91use position::PositionSnapshotFrame;
92pub use refs::{AccountRef, AccountRefMut, OrderRef, OrderRefMut, PositionRef, PositionRefMut};
93use rust_decimal::Decimal;
94use ustr::Ustr;
95
96use crate::xrate::get_exchange_rate;
97
98// TODO: Reassess whether CacheView should consolidate with CacheApi once adapter and client
99// construction no longer need a cache-handle facade.
100/// Read-only view over the platform cache.
101///
102/// Adapter-facing code receives this type instead of the mutable cache handle so cache writes stay
103/// owned by the data and execution engines.
104#[derive(Clone, Debug)]
105pub struct CacheView {
106    inner: Rc<RefCell<Cache>>,
107}
108
109impl CacheView {
110    /// Creates a new [`CacheView`] from a cache handle.
111    #[must_use]
112    pub fn new(inner: Rc<RefCell<Cache>>) -> Self {
113        Self { inner }
114    }
115
116    /// Borrows the cache immutably.
117    ///
118    /// # Panics
119    ///
120    /// Panics if the cache is already mutably borrowed.
121    pub fn borrow(&self) -> Ref<'_, Cache> {
122        self.inner.borrow()
123    }
124}
125
126impl From<Rc<RefCell<Cache>>> for CacheView {
127    fn from(inner: Rc<RefCell<Cache>>) -> Self {
128        Self::new(inner)
129    }
130}
131
132/// User-facing cache API.
133///
134/// Point reads return owned snapshots where possible, so actor code does not retain a `Ref` into
135/// the live [`Cache`]. Plural collection reads return owned snapshots of all matching values and
136/// are intentionally named as bulk reads. Prefer the count, ID, or `has_*` methods in hot paths
137/// when a full snapshot is not needed.
138#[derive(Debug)]
139pub struct CacheApi<'a> {
140    cache: &'a RefCell<Cache>,
141}
142
143impl<'a> CacheApi<'a> {
144    pub(crate) fn new(cache: &'a RefCell<Cache>) -> Self {
145        Self { cache }
146    }
147
148    /// Returns the unrealized PnL for the `position` using cached market data.
149    ///
150    /// # Panics
151    ///
152    /// Panics if the cache is already mutably borrowed.
153    #[must_use]
154    pub fn calculate_unrealized_pnl(&self, position: &Position) -> Option<Money> {
155        self.cache().calculate_unrealized_pnl(position)
156    }
157
158    /// Returns the OMS type for the `position_id` (if known).
159    ///
160    /// # Panics
161    ///
162    /// Panics if the cache is already mutably borrowed.
163    #[must_use]
164    pub fn oms_type(&self, position_id: &PositionId) -> Option<OmsType> {
165        self.cache().oms_type(position_id)
166    }
167
168    /// Returns serialized position snapshot frames for the `position_id`.
169    ///
170    /// # Panics
171    ///
172    /// Panics if the cache is already mutably borrowed.
173    #[must_use]
174    pub fn position_snapshot_bytes(&self, position_id: &PositionId) -> Option<Vec<Vec<u8>>> {
175        self.cache().position_snapshot_bytes(position_id)
176    }
177
178    /// Returns the number of stored position snapshots for the `position_id`.
179    ///
180    /// # Panics
181    ///
182    /// Panics if the cache is already mutably borrowed.
183    #[must_use]
184    pub fn position_snapshot_count(&self, position_id: &PositionId) -> usize {
185        self.cache().position_snapshot_count(position_id)
186    }
187
188    /// Returns position snapshots matching the optional filters.
189    ///
190    /// # Panics
191    ///
192    /// Panics if the cache is already mutably borrowed.
193    #[must_use]
194    pub fn position_snapshots(
195        &self,
196        position_id: Option<&PositionId>,
197        account_id: Option<&AccountId>,
198    ) -> Vec<Position> {
199        self.cache().position_snapshots(position_id, account_id)
200    }
201
202    /// Returns position snapshots for `position_id` starting from `skip`.
203    ///
204    /// # Panics
205    ///
206    /// Panics if the cache is already mutably borrowed.
207    #[must_use]
208    pub fn position_snapshots_from(&self, position_id: &PositionId, skip: usize) -> Vec<Position> {
209        self.cache().position_snapshots_from(position_id, skip)
210    }
211
212    /// Returns position snapshot IDs for the `instrument_id`.
213    ///
214    /// # Panics
215    ///
216    /// Panics if the cache is already mutably borrowed.
217    #[must_use]
218    pub fn position_snapshot_ids(&self, instrument_id: &InstrumentId) -> AHashSet<PositionId> {
219        self.cache().position_snapshot_ids(instrument_id)
220    }
221
222    /// Returns the client order IDs of all orders matching the optional filter parameters.
223    ///
224    /// # Panics
225    ///
226    /// Panics if the cache is already mutably borrowed.
227    #[must_use]
228    pub fn client_order_ids(
229        &self,
230        venue: Option<&Venue>,
231        instrument_id: Option<&InstrumentId>,
232        strategy_id: Option<&StrategyId>,
233        account_id: Option<&AccountId>,
234    ) -> AHashSet<ClientOrderId> {
235        self.cache()
236            .client_order_ids(venue, instrument_id, strategy_id, account_id)
237    }
238
239    /// Returns the client order IDs of all open orders matching the optional filter parameters.
240    ///
241    /// # Panics
242    ///
243    /// Panics if the cache is already mutably borrowed.
244    #[must_use]
245    pub fn client_order_ids_open(
246        &self,
247        venue: Option<&Venue>,
248        instrument_id: Option<&InstrumentId>,
249        strategy_id: Option<&StrategyId>,
250        account_id: Option<&AccountId>,
251    ) -> AHashSet<ClientOrderId> {
252        self.cache()
253            .client_order_ids_open(venue, instrument_id, strategy_id, account_id)
254    }
255
256    /// Returns the client order IDs of all closed orders matching the optional filter parameters.
257    ///
258    /// # Panics
259    ///
260    /// Panics if the cache is already mutably borrowed.
261    #[must_use]
262    pub fn client_order_ids_closed(
263        &self,
264        venue: Option<&Venue>,
265        instrument_id: Option<&InstrumentId>,
266        strategy_id: Option<&StrategyId>,
267        account_id: Option<&AccountId>,
268    ) -> AHashSet<ClientOrderId> {
269        self.cache()
270            .client_order_ids_closed(venue, instrument_id, strategy_id, account_id)
271    }
272
273    /// Returns the client order IDs of all locally active orders matching the optional filter parameters.
274    ///
275    /// # Panics
276    ///
277    /// Panics if the cache is already mutably borrowed.
278    #[must_use]
279    pub fn client_order_ids_active_local(
280        &self,
281        venue: Option<&Venue>,
282        instrument_id: Option<&InstrumentId>,
283        strategy_id: Option<&StrategyId>,
284        account_id: Option<&AccountId>,
285    ) -> AHashSet<ClientOrderId> {
286        self.cache()
287            .client_order_ids_active_local(venue, instrument_id, strategy_id, account_id)
288    }
289
290    /// Returns the client order IDs of all emulated orders matching the optional filter parameters.
291    ///
292    /// # Panics
293    ///
294    /// Panics if the cache is already mutably borrowed.
295    #[must_use]
296    pub fn client_order_ids_emulated(
297        &self,
298        venue: Option<&Venue>,
299        instrument_id: Option<&InstrumentId>,
300        strategy_id: Option<&StrategyId>,
301        account_id: Option<&AccountId>,
302    ) -> AHashSet<ClientOrderId> {
303        self.cache()
304            .client_order_ids_emulated(venue, instrument_id, strategy_id, account_id)
305    }
306
307    /// Returns the client order IDs of all in-flight orders matching the optional filter parameters.
308    ///
309    /// # Panics
310    ///
311    /// Panics if the cache is already mutably borrowed.
312    #[must_use]
313    pub fn client_order_ids_inflight(
314        &self,
315        venue: Option<&Venue>,
316        instrument_id: Option<&InstrumentId>,
317        strategy_id: Option<&StrategyId>,
318        account_id: Option<&AccountId>,
319    ) -> AHashSet<ClientOrderId> {
320        self.cache()
321            .client_order_ids_inflight(venue, instrument_id, strategy_id, account_id)
322    }
323
324    /// Returns the position IDs of all positions matching the optional filter parameters.
325    ///
326    /// # Panics
327    ///
328    /// Panics if the cache is already mutably borrowed.
329    #[must_use]
330    pub fn position_ids(
331        &self,
332        venue: Option<&Venue>,
333        instrument_id: Option<&InstrumentId>,
334        strategy_id: Option<&StrategyId>,
335        account_id: Option<&AccountId>,
336    ) -> AHashSet<PositionId> {
337        self.cache()
338            .position_ids(venue, instrument_id, strategy_id, account_id)
339    }
340
341    /// Returns the position IDs of all open positions matching the optional filter parameters.
342    ///
343    /// # Panics
344    ///
345    /// Panics if the cache is already mutably borrowed.
346    #[must_use]
347    pub fn position_open_ids(
348        &self,
349        venue: Option<&Venue>,
350        instrument_id: Option<&InstrumentId>,
351        strategy_id: Option<&StrategyId>,
352        account_id: Option<&AccountId>,
353    ) -> AHashSet<PositionId> {
354        self.cache()
355            .position_open_ids(venue, instrument_id, strategy_id, account_id)
356    }
357
358    /// Returns the position IDs of all closed positions matching the optional filter parameters.
359    ///
360    /// # Panics
361    ///
362    /// Panics if the cache is already mutably borrowed.
363    #[must_use]
364    pub fn position_closed_ids(
365        &self,
366        venue: Option<&Venue>,
367        instrument_id: Option<&InstrumentId>,
368        strategy_id: Option<&StrategyId>,
369        account_id: Option<&AccountId>,
370    ) -> AHashSet<PositionId> {
371        self.cache()
372            .position_closed_ids(venue, instrument_id, strategy_id, account_id)
373    }
374
375    /// Returns the actor IDs in the cache.
376    ///
377    /// # Panics
378    ///
379    /// Panics if the cache is already mutably borrowed.
380    #[must_use]
381    pub fn actor_ids(&self) -> AHashSet<ComponentId> {
382        self.cache().actor_ids()
383    }
384
385    /// Returns the strategy IDs in the cache.
386    ///
387    /// # Panics
388    ///
389    /// Panics if the cache is already mutably borrowed.
390    #[must_use]
391    pub fn strategy_ids(&self) -> AHashSet<StrategyId> {
392        self.cache().strategy_ids()
393    }
394
395    /// Returns the execution algorithm IDs in the cache.
396    ///
397    /// # Panics
398    ///
399    /// Panics if the cache is already mutably borrowed.
400    #[must_use]
401    pub fn exec_algorithm_ids(&self) -> AHashSet<ExecAlgorithmId> {
402        self.cache().exec_algorithm_ids()
403    }
404
405    /// Returns an owned copy of the order for the `client_order_id` (if found).
406    ///
407    /// # Panics
408    ///
409    /// Panics if the cache is already mutably borrowed.
410    #[must_use]
411    pub fn order(&self, client_order_id: &ClientOrderId) -> Option<OrderAny> {
412        self.cache().order_owned(client_order_id)
413    }
414
415    // panics-doc-ok
416    /// Returns an owned copy of the order for the `client_order_id`.
417    ///
418    /// # Errors
419    ///
420    /// Returns [`OrderLookupError::NotFound`] when the order is not present in the cache.
421    ///
422    /// # Panics
423    ///
424    /// Panics if the cache is already mutably borrowed.
425    pub fn try_order(&self, client_order_id: &ClientOrderId) -> Result<OrderAny, OrderLookupError> {
426        self.cache().try_order_owned(client_order_id)
427    }
428
429    /// Returns owned copies of the orders for `client_order_ids`.
430    ///
431    /// # Panics
432    ///
433    /// Panics if the cache is already mutably borrowed.
434    #[must_use]
435    pub fn orders_for_ids(
436        &self,
437        client_order_ids: &[ClientOrderId],
438        context: &dyn Display,
439    ) -> Vec<OrderAny> {
440        self.cache().orders_for_ids(client_order_ids, context)
441    }
442
443    /// Returns the client order ID for the `venue_order_id` (if found).
444    ///
445    /// # Panics
446    ///
447    /// Panics if the cache is already mutably borrowed.
448    #[must_use]
449    pub fn client_order_id(&self, venue_order_id: &VenueOrderId) -> Option<ClientOrderId> {
450        self.cache().client_order_id(venue_order_id).copied()
451    }
452
453    /// Returns the venue order ID for the `client_order_id` (if found).
454    ///
455    /// # Panics
456    ///
457    /// Panics if the cache is already mutably borrowed.
458    #[must_use]
459    pub fn venue_order_id(&self, client_order_id: &ClientOrderId) -> Option<VenueOrderId> {
460        self.cache().venue_order_id(client_order_id).copied()
461    }
462
463    /// Returns the client ID indexed for the `client_order_id` (if found).
464    ///
465    /// # Panics
466    ///
467    /// Panics if the cache is already mutably borrowed.
468    #[must_use]
469    pub fn client_id(&self, client_order_id: &ClientOrderId) -> Option<ClientId> {
470        self.cache().client_id(client_order_id).copied()
471    }
472
473    /// Returns owned copies of all orders matching the optional filter parameters.
474    ///
475    /// # Panics
476    ///
477    /// Panics if the cache is already mutably borrowed.
478    #[must_use]
479    pub fn orders(
480        &self,
481        venue: Option<&Venue>,
482        instrument_id: Option<&InstrumentId>,
483        strategy_id: Option<&StrategyId>,
484        account_id: Option<&AccountId>,
485        side: Option<OrderSide>,
486    ) -> Vec<OrderAny> {
487        self.cache()
488            .orders_refs(venue, instrument_id, strategy_id, account_id, side)
489            .into_iter()
490            .map(|order| order.cloned())
491            .collect()
492    }
493
494    /// Returns owned copies of all open orders matching the optional filter parameters.
495    ///
496    /// # Panics
497    ///
498    /// Panics if the cache is already mutably borrowed.
499    #[must_use]
500    pub fn orders_open(
501        &self,
502        venue: Option<&Venue>,
503        instrument_id: Option<&InstrumentId>,
504        strategy_id: Option<&StrategyId>,
505        account_id: Option<&AccountId>,
506        side: Option<OrderSide>,
507    ) -> Vec<OrderAny> {
508        self.cache()
509            .orders_open_refs(venue, instrument_id, strategy_id, account_id, side)
510            .into_iter()
511            .map(|order| order.cloned())
512            .collect()
513    }
514
515    /// Returns owned copies of all closed orders matching the optional filter parameters.
516    ///
517    /// # Panics
518    ///
519    /// Panics if the cache is already mutably borrowed.
520    #[must_use]
521    pub fn orders_closed(
522        &self,
523        venue: Option<&Venue>,
524        instrument_id: Option<&InstrumentId>,
525        strategy_id: Option<&StrategyId>,
526        account_id: Option<&AccountId>,
527        side: Option<OrderSide>,
528    ) -> Vec<OrderAny> {
529        self.cache()
530            .orders_closed_refs(venue, instrument_id, strategy_id, account_id, side)
531            .into_iter()
532            .map(|order| order.cloned())
533            .collect()
534    }
535
536    /// Returns owned copies of all locally active orders matching the optional filter parameters.
537    ///
538    /// # Panics
539    ///
540    /// Panics if the cache is already mutably borrowed.
541    #[must_use]
542    pub fn orders_active_local(
543        &self,
544        venue: Option<&Venue>,
545        instrument_id: Option<&InstrumentId>,
546        strategy_id: Option<&StrategyId>,
547        account_id: Option<&AccountId>,
548        side: Option<OrderSide>,
549    ) -> Vec<OrderAny> {
550        self.cache()
551            .orders_active_local_refs(venue, instrument_id, strategy_id, account_id, side)
552            .into_iter()
553            .map(|order| order.cloned())
554            .collect()
555    }
556
557    /// Returns owned copies of all emulated orders matching the optional filter parameters.
558    ///
559    /// # Panics
560    ///
561    /// Panics if the cache is already mutably borrowed.
562    #[must_use]
563    pub fn orders_emulated(
564        &self,
565        venue: Option<&Venue>,
566        instrument_id: Option<&InstrumentId>,
567        strategy_id: Option<&StrategyId>,
568        account_id: Option<&AccountId>,
569        side: Option<OrderSide>,
570    ) -> Vec<OrderAny> {
571        self.cache()
572            .orders_emulated_refs(venue, instrument_id, strategy_id, account_id, side)
573            .into_iter()
574            .map(|order| order.cloned())
575            .collect()
576    }
577
578    /// Returns owned copies of all in-flight orders matching the optional filter parameters.
579    ///
580    /// # Panics
581    ///
582    /// Panics if the cache is already mutably borrowed.
583    #[must_use]
584    pub fn orders_inflight(
585        &self,
586        venue: Option<&Venue>,
587        instrument_id: Option<&InstrumentId>,
588        strategy_id: Option<&StrategyId>,
589        account_id: Option<&AccountId>,
590        side: Option<OrderSide>,
591    ) -> Vec<OrderAny> {
592        self.cache()
593            .orders_inflight_refs(venue, instrument_id, strategy_id, account_id, side)
594            .into_iter()
595            .map(|order| order.cloned())
596            .collect()
597    }
598
599    /// Returns owned copies of all orders for the `position_id`.
600    ///
601    /// # Panics
602    ///
603    /// Panics if the cache is already mutably borrowed.
604    #[must_use]
605    pub fn orders_for_position(&self, position_id: &PositionId) -> Vec<OrderAny> {
606        self.cache()
607            .orders_for_position(position_id)
608            .into_iter()
609            .map(|order| order.cloned())
610            .collect()
611    }
612
613    /// Returns whether an order with the `client_order_id` exists.
614    ///
615    /// # Panics
616    ///
617    /// Panics if the cache is already mutably borrowed.
618    #[must_use]
619    pub fn order_exists(&self, client_order_id: &ClientOrderId) -> bool {
620        self.cache().order_exists(client_order_id)
621    }
622
623    /// Returns whether an order with the `client_order_id` is open.
624    ///
625    /// # Panics
626    ///
627    /// Panics if the cache is already mutably borrowed.
628    #[must_use]
629    pub fn is_order_open(&self, client_order_id: &ClientOrderId) -> bool {
630        self.cache().is_order_open(client_order_id)
631    }
632
633    /// Returns whether an order with the `client_order_id` is closed.
634    ///
635    /// # Panics
636    ///
637    /// Panics if the cache is already mutably borrowed.
638    #[must_use]
639    pub fn is_order_closed(&self, client_order_id: &ClientOrderId) -> bool {
640        self.cache().is_order_closed(client_order_id)
641    }
642
643    /// Returns whether an order with the `client_order_id` is locally active.
644    ///
645    /// # Panics
646    ///
647    /// Panics if the cache is already mutably borrowed.
648    #[must_use]
649    pub fn is_order_active_local(&self, client_order_id: &ClientOrderId) -> bool {
650        self.cache().is_order_active_local(client_order_id)
651    }
652
653    /// Returns whether an order with the `client_order_id` is emulated.
654    ///
655    /// # Panics
656    ///
657    /// Panics if the cache is already mutably borrowed.
658    #[must_use]
659    pub fn is_order_emulated(&self, client_order_id: &ClientOrderId) -> bool {
660        self.cache().is_order_emulated(client_order_id)
661    }
662
663    /// Returns whether an order with the `client_order_id` is in-flight.
664    ///
665    /// # Panics
666    ///
667    /// Panics if the cache is already mutably borrowed.
668    #[must_use]
669    pub fn is_order_inflight(&self, client_order_id: &ClientOrderId) -> bool {
670        self.cache().is_order_inflight(client_order_id)
671    }
672
673    /// Returns whether an order with the `client_order_id` is `PENDING_CANCEL` locally.
674    ///
675    /// # Panics
676    ///
677    /// Panics if the cache is already mutably borrowed.
678    #[must_use]
679    pub fn is_order_pending_cancel_local(&self, client_order_id: &ClientOrderId) -> bool {
680        self.cache().is_order_pending_cancel_local(client_order_id)
681    }
682
683    /// Returns the count of all open orders matching the optional filter parameters.
684    ///
685    /// # Panics
686    ///
687    /// Panics if the cache is already mutably borrowed.
688    #[must_use]
689    pub fn orders_open_count(
690        &self,
691        venue: Option<&Venue>,
692        instrument_id: Option<&InstrumentId>,
693        strategy_id: Option<&StrategyId>,
694        account_id: Option<&AccountId>,
695        side: Option<OrderSide>,
696    ) -> usize {
697        self.cache()
698            .orders_open_count(venue, instrument_id, strategy_id, account_id, side)
699    }
700
701    /// Returns the count of all closed orders matching the optional filter parameters.
702    ///
703    /// # Panics
704    ///
705    /// Panics if the cache is already mutably borrowed.
706    #[must_use]
707    pub fn orders_closed_count(
708        &self,
709        venue: Option<&Venue>,
710        instrument_id: Option<&InstrumentId>,
711        strategy_id: Option<&StrategyId>,
712        account_id: Option<&AccountId>,
713        side: Option<OrderSide>,
714    ) -> usize {
715        self.cache()
716            .orders_closed_count(venue, instrument_id, strategy_id, account_id, side)
717    }
718
719    /// Returns the count of all locally active orders matching the optional filter parameters.
720    ///
721    /// # Panics
722    ///
723    /// Panics if the cache is already mutably borrowed.
724    #[must_use]
725    pub fn orders_active_local_count(
726        &self,
727        venue: Option<&Venue>,
728        instrument_id: Option<&InstrumentId>,
729        strategy_id: Option<&StrategyId>,
730        account_id: Option<&AccountId>,
731        side: Option<OrderSide>,
732    ) -> usize {
733        self.cache()
734            .orders_active_local_count(venue, instrument_id, strategy_id, account_id, side)
735    }
736
737    /// Returns the count of all emulated orders matching the optional filter parameters.
738    ///
739    /// # Panics
740    ///
741    /// Panics if the cache is already mutably borrowed.
742    #[must_use]
743    pub fn orders_emulated_count(
744        &self,
745        venue: Option<&Venue>,
746        instrument_id: Option<&InstrumentId>,
747        strategy_id: Option<&StrategyId>,
748        account_id: Option<&AccountId>,
749        side: Option<OrderSide>,
750    ) -> usize {
751        self.cache()
752            .orders_emulated_count(venue, instrument_id, strategy_id, account_id, side)
753    }
754
755    /// Returns the count of all in-flight orders matching the optional filter parameters.
756    ///
757    /// # Panics
758    ///
759    /// Panics if the cache is already mutably borrowed.
760    #[must_use]
761    pub fn orders_inflight_count(
762        &self,
763        venue: Option<&Venue>,
764        instrument_id: Option<&InstrumentId>,
765        strategy_id: Option<&StrategyId>,
766        account_id: Option<&AccountId>,
767        side: Option<OrderSide>,
768    ) -> usize {
769        self.cache()
770            .orders_inflight_count(venue, instrument_id, strategy_id, account_id, side)
771    }
772
773    /// Returns the count of all orders matching the optional filter parameters.
774    ///
775    /// # Panics
776    ///
777    /// Panics if the cache is already mutably borrowed.
778    #[must_use]
779    pub fn orders_total_count(
780        &self,
781        venue: Option<&Venue>,
782        instrument_id: Option<&InstrumentId>,
783        strategy_id: Option<&StrategyId>,
784        account_id: Option<&AccountId>,
785        side: Option<OrderSide>,
786    ) -> usize {
787        self.cache()
788            .orders_total_count(venue, instrument_id, strategy_id, account_id, side)
789    }
790
791    /// Returns whether any open order matches the optional filter parameters.
792    ///
793    /// # Panics
794    ///
795    /// Panics if the cache is already mutably borrowed.
796    #[must_use]
797    pub fn has_orders_open(
798        &self,
799        venue: Option<&Venue>,
800        instrument_id: Option<&InstrumentId>,
801        strategy_id: Option<&StrategyId>,
802        account_id: Option<&AccountId>,
803        side: Option<OrderSide>,
804    ) -> bool {
805        self.cache()
806            .has_orders_open(venue, instrument_id, strategy_id, account_id, side)
807    }
808
809    /// Returns whether any closed order matches the optional filter parameters.
810    ///
811    /// # Panics
812    ///
813    /// Panics if the cache is already mutably borrowed.
814    #[must_use]
815    pub fn has_orders_closed(
816        &self,
817        venue: Option<&Venue>,
818        instrument_id: Option<&InstrumentId>,
819        strategy_id: Option<&StrategyId>,
820        account_id: Option<&AccountId>,
821        side: Option<OrderSide>,
822    ) -> bool {
823        self.cache()
824            .has_orders_closed(venue, instrument_id, strategy_id, account_id, side)
825    }
826
827    /// Returns whether any locally active order matches the optional filter parameters.
828    ///
829    /// # Panics
830    ///
831    /// Panics if the cache is already mutably borrowed.
832    #[must_use]
833    pub fn has_orders_active_local(
834        &self,
835        venue: Option<&Venue>,
836        instrument_id: Option<&InstrumentId>,
837        strategy_id: Option<&StrategyId>,
838        account_id: Option<&AccountId>,
839        side: Option<OrderSide>,
840    ) -> bool {
841        self.cache()
842            .has_orders_active_local(venue, instrument_id, strategy_id, account_id, side)
843    }
844
845    /// Returns whether any emulated order matches the optional filter parameters.
846    ///
847    /// # Panics
848    ///
849    /// Panics if the cache is already mutably borrowed.
850    #[must_use]
851    pub fn has_orders_emulated(
852        &self,
853        venue: Option<&Venue>,
854        instrument_id: Option<&InstrumentId>,
855        strategy_id: Option<&StrategyId>,
856        account_id: Option<&AccountId>,
857        side: Option<OrderSide>,
858    ) -> bool {
859        self.cache()
860            .has_orders_emulated(venue, instrument_id, strategy_id, account_id, side)
861    }
862
863    /// Returns whether any in-flight order matches the optional filter parameters.
864    ///
865    /// # Panics
866    ///
867    /// Panics if the cache is already mutably borrowed.
868    #[must_use]
869    pub fn has_orders_inflight(
870        &self,
871        venue: Option<&Venue>,
872        instrument_id: Option<&InstrumentId>,
873        strategy_id: Option<&StrategyId>,
874        account_id: Option<&AccountId>,
875        side: Option<OrderSide>,
876    ) -> bool {
877        self.cache()
878            .has_orders_inflight(venue, instrument_id, strategy_id, account_id, side)
879    }
880
881    /// Returns whether any order matches the optional filter parameters.
882    ///
883    /// # Panics
884    ///
885    /// Panics if the cache is already mutably borrowed.
886    #[must_use]
887    pub fn has_orders(
888        &self,
889        venue: Option<&Venue>,
890        instrument_id: Option<&InstrumentId>,
891        strategy_id: Option<&StrategyId>,
892        account_id: Option<&AccountId>,
893        side: Option<OrderSide>,
894    ) -> bool {
895        self.cache()
896            .has_orders(venue, instrument_id, strategy_id, account_id, side)
897    }
898
899    /// Returns an owned copy of the order list for the `order_list_id` (if found).
900    ///
901    /// # Panics
902    ///
903    /// Panics if the cache is already mutably borrowed.
904    #[must_use]
905    pub fn order_list(&self, order_list_id: &OrderListId) -> Option<OrderList> {
906        self.cache().order_list(order_list_id).cloned()
907    }
908
909    // panics-doc-ok
910    /// Returns an owned copy of the order list for the `order_list_id`.
911    ///
912    /// # Errors
913    ///
914    /// Returns [`OrderListLookupError::NotFound`] when the order list is not present in the cache.
915    ///
916    /// # Panics
917    ///
918    /// Panics if the cache is already mutably borrowed.
919    pub fn try_order_list(
920        &self,
921        order_list_id: &OrderListId,
922    ) -> Result<OrderList, OrderListLookupError> {
923        self.cache().try_order_list(order_list_id).cloned()
924    }
925
926    /// Returns owned copies of all order lists matching the optional filter parameters.
927    ///
928    /// # Panics
929    ///
930    /// Panics if the cache is already mutably borrowed.
931    #[must_use]
932    pub fn order_lists(
933        &self,
934        venue: Option<&Venue>,
935        instrument_id: Option<&InstrumentId>,
936        strategy_id: Option<&StrategyId>,
937        account_id: Option<&AccountId>,
938    ) -> Vec<OrderList> {
939        self.cache()
940            .order_lists(venue, instrument_id, strategy_id, account_id)
941            .into_iter()
942            .cloned()
943            .collect()
944    }
945
946    /// Returns whether an order list with the `order_list_id` exists.
947    ///
948    /// # Panics
949    ///
950    /// Panics if the cache is already mutably borrowed.
951    #[must_use]
952    pub fn order_list_exists(&self, order_list_id: &OrderListId) -> bool {
953        self.cache().order_list_exists(order_list_id)
954    }
955
956    /// Returns owned copies of all orders associated with the `exec_algorithm_id`.
957    ///
958    /// # Panics
959    ///
960    /// Panics if the cache is already mutably borrowed.
961    #[must_use]
962    pub fn orders_for_exec_algorithm(
963        &self,
964        exec_algorithm_id: &ExecAlgorithmId,
965        venue: Option<&Venue>,
966        instrument_id: Option<&InstrumentId>,
967        strategy_id: Option<&StrategyId>,
968        account_id: Option<&AccountId>,
969        side: Option<OrderSide>,
970    ) -> Vec<OrderAny> {
971        self.cache()
972            .orders_for_exec_algorithm(
973                exec_algorithm_id,
974                venue,
975                instrument_id,
976                strategy_id,
977                account_id,
978                side,
979            )
980            .into_iter()
981            .map(|order| order.cloned())
982            .collect()
983    }
984
985    /// Returns owned copies of all orders with the `exec_spawn_id`.
986    ///
987    /// # Panics
988    ///
989    /// Panics if the cache is already mutably borrowed.
990    #[must_use]
991    pub fn orders_for_exec_spawn(&self, exec_spawn_id: &ClientOrderId) -> Vec<OrderAny> {
992        self.cache()
993            .orders_for_exec_spawn(exec_spawn_id)
994            .into_iter()
995            .map(|order| order.cloned())
996            .collect()
997    }
998
999    /// Returns the total order quantity for the `exec_spawn_id`.
1000    ///
1001    /// # Panics
1002    ///
1003    /// Panics if the cache is already mutably borrowed.
1004    #[must_use]
1005    pub fn exec_spawn_total_quantity(
1006        &self,
1007        exec_spawn_id: &ClientOrderId,
1008        active_only: bool,
1009    ) -> Option<Quantity> {
1010        self.cache()
1011            .exec_spawn_total_quantity(exec_spawn_id, active_only)
1012    }
1013
1014    /// Returns the total filled quantity for all orders with the `exec_spawn_id`.
1015    ///
1016    /// # Panics
1017    ///
1018    /// Panics if the cache is already mutably borrowed.
1019    #[must_use]
1020    pub fn exec_spawn_total_filled_qty(
1021        &self,
1022        exec_spawn_id: &ClientOrderId,
1023        active_only: bool,
1024    ) -> Option<Quantity> {
1025        self.cache()
1026            .exec_spawn_total_filled_qty(exec_spawn_id, active_only)
1027    }
1028
1029    /// Returns the total leaves quantity for all orders with the `exec_spawn_id`.
1030    ///
1031    /// # Panics
1032    ///
1033    /// Panics if the cache is already mutably borrowed.
1034    #[must_use]
1035    pub fn exec_spawn_total_leaves_qty(
1036        &self,
1037        exec_spawn_id: &ClientOrderId,
1038        active_only: bool,
1039    ) -> Option<Quantity> {
1040        self.cache()
1041            .exec_spawn_total_leaves_qty(exec_spawn_id, active_only)
1042    }
1043
1044    /// Returns an owned copy of the position for the `position_id` (if found).
1045    ///
1046    /// # Panics
1047    ///
1048    /// Panics if the cache is already mutably borrowed.
1049    #[must_use]
1050    pub fn position(&self, position_id: &PositionId) -> Option<Position> {
1051        self.cache()
1052            .position_ref(position_id)
1053            .map(|position| position.cloned())
1054    }
1055
1056    // panics-doc-ok
1057    /// Returns an owned copy of the position for the `position_id`.
1058    ///
1059    /// # Errors
1060    ///
1061    /// Returns [`PositionLookupError::NotFound`] when the position is not present in the cache.
1062    ///
1063    /// # Panics
1064    ///
1065    /// Panics if the cache is already mutably borrowed.
1066    pub fn try_position(&self, position_id: &PositionId) -> Result<Position, PositionLookupError> {
1067        self.cache()
1068            .try_position_ref(position_id)
1069            .map(|position| position.cloned())
1070    }
1071
1072    /// Returns an owned copy of the position for the `client_order_id` (if found).
1073    ///
1074    /// # Panics
1075    ///
1076    /// Panics if the cache is already mutably borrowed.
1077    #[must_use]
1078    pub fn position_for_order(&self, client_order_id: &ClientOrderId) -> Option<Position> {
1079        self.cache()
1080            .position_for_order_ref(client_order_id)
1081            .map(|position| position.cloned())
1082    }
1083
1084    /// Returns the position ID for the `client_order_id` (if found).
1085    ///
1086    /// # Panics
1087    ///
1088    /// Panics if the cache is already mutably borrowed.
1089    #[must_use]
1090    pub fn position_id(&self, client_order_id: &ClientOrderId) -> Option<PositionId> {
1091        self.cache().position_id(client_order_id).copied()
1092    }
1093
1094    /// Returns owned copies of all positions matching the optional filter parameters.
1095    ///
1096    /// # Panics
1097    ///
1098    /// Panics if the cache is already mutably borrowed.
1099    #[must_use]
1100    pub fn positions(
1101        &self,
1102        venue: Option<&Venue>,
1103        instrument_id: Option<&InstrumentId>,
1104        strategy_id: Option<&StrategyId>,
1105        account_id: Option<&AccountId>,
1106        side: Option<PositionSide>,
1107    ) -> Vec<Position> {
1108        self.cache()
1109            .positions_refs(venue, instrument_id, strategy_id, account_id, side)
1110            .into_iter()
1111            .map(|position| position.cloned())
1112            .collect()
1113    }
1114
1115    /// Returns owned copies of all open positions matching the optional filter parameters.
1116    ///
1117    /// # Panics
1118    ///
1119    /// Panics if the cache is already mutably borrowed.
1120    #[must_use]
1121    pub fn positions_open(
1122        &self,
1123        venue: Option<&Venue>,
1124        instrument_id: Option<&InstrumentId>,
1125        strategy_id: Option<&StrategyId>,
1126        account_id: Option<&AccountId>,
1127        side: Option<PositionSide>,
1128    ) -> Vec<Position> {
1129        self.cache()
1130            .positions_open_refs(venue, instrument_id, strategy_id, account_id, side)
1131            .into_iter()
1132            .map(|position| position.cloned())
1133            .collect()
1134    }
1135
1136    /// Returns owned copies of all closed positions matching the optional filter parameters.
1137    ///
1138    /// # Panics
1139    ///
1140    /// Panics if the cache is already mutably borrowed.
1141    #[must_use]
1142    pub fn positions_closed(
1143        &self,
1144        venue: Option<&Venue>,
1145        instrument_id: Option<&InstrumentId>,
1146        strategy_id: Option<&StrategyId>,
1147        account_id: Option<&AccountId>,
1148        side: Option<PositionSide>,
1149    ) -> Vec<Position> {
1150        self.cache()
1151            .positions_closed_refs(venue, instrument_id, strategy_id, account_id, side)
1152            .into_iter()
1153            .map(|position| position.cloned())
1154            .collect()
1155    }
1156
1157    /// Returns whether a position with the `position_id` exists.
1158    ///
1159    /// # Panics
1160    ///
1161    /// Panics if the cache is already mutably borrowed.
1162    #[must_use]
1163    pub fn position_exists(&self, position_id: &PositionId) -> bool {
1164        self.cache().position_exists(position_id)
1165    }
1166
1167    /// Returns whether a position with the `position_id` is open.
1168    ///
1169    /// # Panics
1170    ///
1171    /// Panics if the cache is already mutably borrowed.
1172    #[must_use]
1173    pub fn is_position_open(&self, position_id: &PositionId) -> bool {
1174        self.cache().is_position_open(position_id)
1175    }
1176
1177    /// Returns whether a position with the `position_id` is closed.
1178    ///
1179    /// # Panics
1180    ///
1181    /// Panics if the cache is already mutably borrowed.
1182    #[must_use]
1183    pub fn is_position_closed(&self, position_id: &PositionId) -> bool {
1184        self.cache().is_position_closed(position_id)
1185    }
1186
1187    /// Returns the count of all open positions matching the optional filter parameters.
1188    ///
1189    /// # Panics
1190    ///
1191    /// Panics if the cache is already mutably borrowed.
1192    #[must_use]
1193    pub fn positions_open_count(
1194        &self,
1195        venue: Option<&Venue>,
1196        instrument_id: Option<&InstrumentId>,
1197        strategy_id: Option<&StrategyId>,
1198        account_id: Option<&AccountId>,
1199        side: Option<PositionSide>,
1200    ) -> usize {
1201        self.cache()
1202            .positions_open_count(venue, instrument_id, strategy_id, account_id, side)
1203    }
1204
1205    /// Returns the count of all closed positions matching the optional filter parameters.
1206    ///
1207    /// # Panics
1208    ///
1209    /// Panics if the cache is already mutably borrowed.
1210    #[must_use]
1211    pub fn positions_closed_count(
1212        &self,
1213        venue: Option<&Venue>,
1214        instrument_id: Option<&InstrumentId>,
1215        strategy_id: Option<&StrategyId>,
1216        account_id: Option<&AccountId>,
1217        side: Option<PositionSide>,
1218    ) -> usize {
1219        self.cache()
1220            .positions_closed_count(venue, instrument_id, strategy_id, account_id, side)
1221    }
1222
1223    /// Returns the count of all positions matching the optional filter parameters.
1224    ///
1225    /// # Panics
1226    ///
1227    /// Panics if the cache is already mutably borrowed.
1228    #[must_use]
1229    pub fn positions_total_count(
1230        &self,
1231        venue: Option<&Venue>,
1232        instrument_id: Option<&InstrumentId>,
1233        strategy_id: Option<&StrategyId>,
1234        account_id: Option<&AccountId>,
1235        side: Option<PositionSide>,
1236    ) -> usize {
1237        self.cache()
1238            .positions_total_count(venue, instrument_id, strategy_id, account_id, side)
1239    }
1240
1241    /// Returns whether any open position matches the optional filter parameters.
1242    ///
1243    /// # Panics
1244    ///
1245    /// Panics if the cache is already mutably borrowed.
1246    #[must_use]
1247    pub fn has_positions_open(
1248        &self,
1249        venue: Option<&Venue>,
1250        instrument_id: Option<&InstrumentId>,
1251        strategy_id: Option<&StrategyId>,
1252        account_id: Option<&AccountId>,
1253        side: Option<PositionSide>,
1254    ) -> bool {
1255        self.cache()
1256            .has_positions_open(venue, instrument_id, strategy_id, account_id, side)
1257    }
1258
1259    /// Returns whether any closed position matches the optional filter parameters.
1260    ///
1261    /// # Panics
1262    ///
1263    /// Panics if the cache is already mutably borrowed.
1264    #[must_use]
1265    pub fn has_positions_closed(
1266        &self,
1267        venue: Option<&Venue>,
1268        instrument_id: Option<&InstrumentId>,
1269        strategy_id: Option<&StrategyId>,
1270        account_id: Option<&AccountId>,
1271        side: Option<PositionSide>,
1272    ) -> bool {
1273        self.cache()
1274            .has_positions_closed(venue, instrument_id, strategy_id, account_id, side)
1275    }
1276
1277    /// Returns whether any position matches the optional filter parameters.
1278    ///
1279    /// # Panics
1280    ///
1281    /// Panics if the cache is already mutably borrowed.
1282    #[must_use]
1283    pub fn has_positions(
1284        &self,
1285        venue: Option<&Venue>,
1286        instrument_id: Option<&InstrumentId>,
1287        strategy_id: Option<&StrategyId>,
1288        account_id: Option<&AccountId>,
1289        side: Option<PositionSide>,
1290    ) -> bool {
1291        self.cache()
1292            .has_positions(venue, instrument_id, strategy_id, account_id, side)
1293    }
1294
1295    /// Returns the strategy ID for the `client_order_id` (if found).
1296    ///
1297    /// # Panics
1298    ///
1299    /// Panics if the cache is already mutably borrowed.
1300    #[must_use]
1301    pub fn strategy_id_for_order(&self, client_order_id: &ClientOrderId) -> Option<StrategyId> {
1302        self.cache().strategy_id_for_order(client_order_id).copied()
1303    }
1304
1305    /// Returns the strategy ID for the `position_id` (if found).
1306    ///
1307    /// # Panics
1308    ///
1309    /// Panics if the cache is already mutably borrowed.
1310    #[must_use]
1311    pub fn strategy_id_for_position(&self, position_id: &PositionId) -> Option<StrategyId> {
1312        self.cache().strategy_id_for_position(position_id).copied()
1313    }
1314
1315    // panics-doc-ok
1316    /// Returns the general cache value for the `key` (if found).
1317    ///
1318    /// # Errors
1319    ///
1320    /// Returns an error if the `key` is invalid.
1321    ///
1322    /// # Panics
1323    ///
1324    /// Panics if the cache is already mutably borrowed.
1325    pub fn get(&self, key: &str) -> anyhow::Result<Option<Bytes>> {
1326        let cache = self.cache();
1327        let value = cache.get(key)?;
1328        Ok(value.cloned())
1329    }
1330
1331    /// Returns the price for the `instrument_id` and `price_type` (if found).
1332    ///
1333    /// # Panics
1334    ///
1335    /// Panics if the cache is already mutably borrowed, or if `price_type` is [`PriceType::Mid`]
1336    /// and the quote price precision is already at the maximum fixed precision.
1337    #[must_use]
1338    pub fn price(&self, instrument_id: &InstrumentId, price_type: PriceType) -> Option<Price> {
1339        self.cache().price(instrument_id, price_type)
1340    }
1341
1342    /// Returns all quotes for the `instrument_id` (if found).
1343    ///
1344    /// # Panics
1345    ///
1346    /// Panics if the cache is already mutably borrowed.
1347    #[must_use]
1348    pub fn quotes(&self, instrument_id: &InstrumentId) -> Option<Vec<QuoteTick>> {
1349        self.cache().quotes(instrument_id)
1350    }
1351
1352    /// Returns all trades for the `instrument_id` (if found).
1353    ///
1354    /// # Panics
1355    ///
1356    /// Panics if the cache is already mutably borrowed.
1357    #[must_use]
1358    pub fn trades(&self, instrument_id: &InstrumentId) -> Option<Vec<TradeTick>> {
1359        self.cache().trades(instrument_id)
1360    }
1361
1362    /// Returns all mark price updates for the `instrument_id` (if found).
1363    ///
1364    /// # Panics
1365    ///
1366    /// Panics if the cache is already mutably borrowed.
1367    #[must_use]
1368    pub fn mark_prices(&self, instrument_id: &InstrumentId) -> Option<Vec<MarkPriceUpdate>> {
1369        self.cache().mark_prices(instrument_id)
1370    }
1371
1372    /// Returns all index price updates for the `instrument_id` (if found).
1373    ///
1374    /// # Panics
1375    ///
1376    /// Panics if the cache is already mutably borrowed.
1377    #[must_use]
1378    pub fn index_prices(&self, instrument_id: &InstrumentId) -> Option<Vec<IndexPriceUpdate>> {
1379        self.cache().index_prices(instrument_id)
1380    }
1381
1382    /// Returns all funding rate updates for the `instrument_id` (if found).
1383    ///
1384    /// # Panics
1385    ///
1386    /// Panics if the cache is already mutably borrowed.
1387    #[must_use]
1388    pub fn funding_rates(&self, instrument_id: &InstrumentId) -> Option<Vec<FundingRateUpdate>> {
1389        self.cache().funding_rates(instrument_id)
1390    }
1391
1392    /// Returns all instrument status updates for the `instrument_id` (if found).
1393    ///
1394    /// # Panics
1395    ///
1396    /// Panics if the cache is already mutably borrowed.
1397    #[must_use]
1398    pub fn instrument_statuses(
1399        &self,
1400        instrument_id: &InstrumentId,
1401    ) -> Option<Vec<InstrumentStatus>> {
1402        self.cache().instrument_statuses(instrument_id)
1403    }
1404
1405    /// Returns all bars for the `bar_type` (if found).
1406    ///
1407    /// # Panics
1408    ///
1409    /// Panics if the cache is already mutably borrowed.
1410    #[must_use]
1411    pub fn bars(&self, bar_type: &BarType) -> Option<Vec<Bar>> {
1412        self.cache().bars(bar_type)
1413    }
1414
1415    /// Returns an owned copy of the order book for the `instrument_id` (if found).
1416    ///
1417    /// # Panics
1418    ///
1419    /// Panics if the cache is already mutably borrowed.
1420    #[must_use]
1421    pub fn order_book(&self, instrument_id: &InstrumentId) -> Option<OrderBook> {
1422        self.cache().order_book(instrument_id).cloned()
1423    }
1424
1425    // panics-doc-ok
1426    /// Returns an owned copy of the order book for the `instrument_id`.
1427    ///
1428    /// # Errors
1429    ///
1430    /// Returns [`OrderBookLookupError::NotFound`] when the order book is not present in the cache.
1431    ///
1432    /// # Panics
1433    ///
1434    /// Panics if the cache is already mutably borrowed.
1435    pub fn try_order_book(
1436        &self,
1437        instrument_id: &InstrumentId,
1438    ) -> Result<OrderBook, OrderBookLookupError> {
1439        self.cache().try_order_book(instrument_id).cloned()
1440    }
1441
1442    /// Returns an owned copy of the own order book for the `instrument_id` (if found).
1443    ///
1444    /// # Panics
1445    ///
1446    /// Panics if the cache is already mutably borrowed.
1447    #[must_use]
1448    pub fn own_order_book(&self, instrument_id: &InstrumentId) -> Option<OwnOrderBook> {
1449        self.cache().own_order_book(instrument_id).cloned()
1450    }
1451
1452    // panics-doc-ok
1453    /// Returns an owned copy of the own order book for the `instrument_id`.
1454    ///
1455    /// # Errors
1456    ///
1457    /// Returns [`OwnOrderBookLookupError::NotFound`] when the own order book is not present in the
1458    /// cache.
1459    ///
1460    /// # Panics
1461    ///
1462    /// Panics if the cache is already mutably borrowed.
1463    pub fn try_own_order_book(
1464        &self,
1465        instrument_id: &InstrumentId,
1466    ) -> Result<OwnOrderBook, OwnOrderBookLookupError> {
1467        self.cache().try_own_order_book(instrument_id).cloned()
1468    }
1469
1470    /// Returns the latest quote 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 quote(&self, instrument_id: &InstrumentId) -> Option<QuoteTick> {
1477        self.cache().quote(instrument_id).copied()
1478    }
1479
1480    /// Returns the quote at `index` for the `instrument_id` (if found).
1481    ///
1482    /// Index 0 is the most recent.
1483    ///
1484    /// # Panics
1485    ///
1486    /// Panics if the cache is already mutably borrowed.
1487    #[must_use]
1488    pub fn quote_at_index(&self, instrument_id: &InstrumentId, index: usize) -> Option<QuoteTick> {
1489        self.cache().quote_at_index(instrument_id, index).copied()
1490    }
1491
1492    /// Returns the latest trade for the `instrument_id` (if found).
1493    ///
1494    /// # Panics
1495    ///
1496    /// Panics if the cache is already mutably borrowed.
1497    #[must_use]
1498    pub fn trade(&self, instrument_id: &InstrumentId) -> Option<TradeTick> {
1499        self.cache().trade(instrument_id).copied()
1500    }
1501
1502    /// Returns the trade at `index` for the `instrument_id` (if found).
1503    ///
1504    /// Index 0 is the most recent.
1505    ///
1506    /// # Panics
1507    ///
1508    /// Panics if the cache is already mutably borrowed.
1509    #[must_use]
1510    pub fn trade_at_index(&self, instrument_id: &InstrumentId, index: usize) -> Option<TradeTick> {
1511        self.cache().trade_at_index(instrument_id, index).copied()
1512    }
1513
1514    /// Returns the latest mark price update for the `instrument_id` (if found).
1515    ///
1516    /// # Panics
1517    ///
1518    /// Panics if the cache is already mutably borrowed.
1519    #[must_use]
1520    pub fn mark_price(&self, instrument_id: &InstrumentId) -> Option<MarkPriceUpdate> {
1521        self.cache().mark_price(instrument_id).copied()
1522    }
1523
1524    /// Returns the latest index price update for the `instrument_id` (if found).
1525    ///
1526    /// # Panics
1527    ///
1528    /// Panics if the cache is already mutably borrowed.
1529    #[must_use]
1530    pub fn index_price(&self, instrument_id: &InstrumentId) -> Option<IndexPriceUpdate> {
1531        self.cache().index_price(instrument_id).copied()
1532    }
1533
1534    /// Returns the latest funding rate update for the `instrument_id` (if found).
1535    ///
1536    /// # Panics
1537    ///
1538    /// Panics if the cache is already mutably borrowed.
1539    #[must_use]
1540    pub fn funding_rate(&self, instrument_id: &InstrumentId) -> Option<FundingRateUpdate> {
1541        self.cache().funding_rate(instrument_id).copied()
1542    }
1543
1544    /// Returns the latest instrument status update for the `instrument_id` (if found).
1545    ///
1546    /// # Panics
1547    ///
1548    /// Panics if the cache is already mutably borrowed.
1549    #[must_use]
1550    pub fn instrument_status(&self, instrument_id: &InstrumentId) -> Option<InstrumentStatus> {
1551        self.cache().instrument_status(instrument_id).copied()
1552    }
1553
1554    /// Returns the latest bar for the `bar_type` (if found).
1555    ///
1556    /// # Panics
1557    ///
1558    /// Panics if the cache is already mutably borrowed.
1559    #[must_use]
1560    pub fn bar(&self, bar_type: &BarType) -> Option<Bar> {
1561        self.cache().bar(bar_type).copied()
1562    }
1563
1564    /// Returns the bar at `index` for the `bar_type` (if found).
1565    ///
1566    /// Index 0 is the most recent.
1567    ///
1568    /// # Panics
1569    ///
1570    /// Panics if the cache is already mutably borrowed.
1571    #[must_use]
1572    pub fn bar_at_index(&self, bar_type: &BarType, index: usize) -> Option<Bar> {
1573        self.cache().bar_at_index(bar_type, index).copied()
1574    }
1575
1576    /// Returns the order book update count for the `instrument_id`.
1577    ///
1578    /// # Panics
1579    ///
1580    /// Panics if the cache is already mutably borrowed.
1581    #[must_use]
1582    pub fn book_update_count(&self, instrument_id: &InstrumentId) -> usize {
1583        self.cache().book_update_count(instrument_id)
1584    }
1585
1586    /// Returns the quote tick count for the `instrument_id`.
1587    ///
1588    /// # Panics
1589    ///
1590    /// Panics if the cache is already mutably borrowed.
1591    #[must_use]
1592    pub fn quote_count(&self, instrument_id: &InstrumentId) -> usize {
1593        self.cache().quote_count(instrument_id)
1594    }
1595
1596    /// Returns the trade tick count for the `instrument_id`.
1597    ///
1598    /// # Panics
1599    ///
1600    /// Panics if the cache is already mutably borrowed.
1601    #[must_use]
1602    pub fn trade_count(&self, instrument_id: &InstrumentId) -> usize {
1603        self.cache().trade_count(instrument_id)
1604    }
1605
1606    /// Returns the mark price update count for the `instrument_id`.
1607    ///
1608    /// # Panics
1609    ///
1610    /// Panics if the cache is already mutably borrowed.
1611    #[must_use]
1612    pub fn mark_price_count(&self, instrument_id: &InstrumentId) -> usize {
1613        self.cache().mark_price_count(instrument_id)
1614    }
1615
1616    /// Returns the index price update count for the `instrument_id`.
1617    ///
1618    /// # Panics
1619    ///
1620    /// Panics if the cache is already mutably borrowed.
1621    #[must_use]
1622    pub fn index_price_count(&self, instrument_id: &InstrumentId) -> usize {
1623        self.cache().index_price_count(instrument_id)
1624    }
1625
1626    /// Returns the funding rate update count for the `instrument_id`.
1627    ///
1628    /// # Panics
1629    ///
1630    /// Panics if the cache is already mutably borrowed.
1631    #[must_use]
1632    pub fn funding_rate_count(&self, instrument_id: &InstrumentId) -> usize {
1633        self.cache().funding_rate_count(instrument_id)
1634    }
1635
1636    /// Returns the instrument status update count for the `instrument_id`.
1637    ///
1638    /// # Panics
1639    ///
1640    /// Panics if the cache is already mutably borrowed.
1641    #[must_use]
1642    pub fn instrument_status_count(&self, instrument_id: &InstrumentId) -> usize {
1643        self.cache().instrument_status_count(instrument_id)
1644    }
1645
1646    /// Returns the bar count for the `bar_type`.
1647    ///
1648    /// # Panics
1649    ///
1650    /// Panics if the cache is already mutably borrowed.
1651    #[must_use]
1652    pub fn bar_count(&self, bar_type: &BarType) -> usize {
1653        self.cache().bar_count(bar_type)
1654    }
1655
1656    /// Returns whether the cache contains an order book for the `instrument_id`.
1657    ///
1658    /// # Panics
1659    ///
1660    /// Panics if the cache is already mutably borrowed.
1661    #[must_use]
1662    pub fn has_order_book(&self, instrument_id: &InstrumentId) -> bool {
1663        self.cache().has_order_book(instrument_id)
1664    }
1665
1666    /// Returns whether the cache contains quotes for the `instrument_id`.
1667    ///
1668    /// # Panics
1669    ///
1670    /// Panics if the cache is already mutably borrowed.
1671    #[must_use]
1672    pub fn has_quote_ticks(&self, instrument_id: &InstrumentId) -> bool {
1673        self.cache().has_quote_ticks(instrument_id)
1674    }
1675
1676    /// Returns whether the cache contains trades for the `instrument_id`.
1677    ///
1678    /// # Panics
1679    ///
1680    /// Panics if the cache is already mutably borrowed.
1681    #[must_use]
1682    pub fn has_trade_ticks(&self, instrument_id: &InstrumentId) -> bool {
1683        self.cache().has_trade_ticks(instrument_id)
1684    }
1685
1686    /// Returns whether the cache contains mark price updates for the `instrument_id`.
1687    ///
1688    /// # Panics
1689    ///
1690    /// Panics if the cache is already mutably borrowed.
1691    #[must_use]
1692    pub fn has_mark_prices(&self, instrument_id: &InstrumentId) -> bool {
1693        self.cache().has_mark_prices(instrument_id)
1694    }
1695
1696    /// Returns whether the cache contains index price updates for the `instrument_id`.
1697    ///
1698    /// # Panics
1699    ///
1700    /// Panics if the cache is already mutably borrowed.
1701    #[must_use]
1702    pub fn has_index_prices(&self, instrument_id: &InstrumentId) -> bool {
1703        self.cache().has_index_prices(instrument_id)
1704    }
1705
1706    /// Returns whether the cache contains funding rate updates for the `instrument_id`.
1707    ///
1708    /// # Panics
1709    ///
1710    /// Panics if the cache is already mutably borrowed.
1711    #[must_use]
1712    pub fn has_funding_rates(&self, instrument_id: &InstrumentId) -> bool {
1713        self.cache().has_funding_rates(instrument_id)
1714    }
1715
1716    /// Returns whether the cache contains instrument status updates for the `instrument_id`.
1717    ///
1718    /// # Panics
1719    ///
1720    /// Panics if the cache is already mutably borrowed.
1721    #[must_use]
1722    pub fn has_instrument_statuses(&self, instrument_id: &InstrumentId) -> bool {
1723        self.cache().has_instrument_statuses(instrument_id)
1724    }
1725
1726    /// Returns whether the cache contains bars for the `bar_type`.
1727    ///
1728    /// # Panics
1729    ///
1730    /// Panics if the cache is already mutably borrowed.
1731    #[must_use]
1732    pub fn has_bars(&self, bar_type: &BarType) -> bool {
1733        self.cache().has_bars(bar_type)
1734    }
1735
1736    /// Returns the exchange rate for the given currencies and price type (if available).
1737    ///
1738    /// # Panics
1739    ///
1740    /// Panics if the cache is already mutably borrowed.
1741    #[must_use]
1742    pub fn get_xrate(
1743        &self,
1744        venue: Venue,
1745        from_currency: Currency,
1746        to_currency: Currency,
1747        price_type: PriceType,
1748    ) -> Option<Decimal> {
1749        self.cache()
1750            .get_xrate(venue, from_currency, to_currency, price_type)
1751    }
1752
1753    /// Returns the mark exchange rate for the currency pair (if set).
1754    ///
1755    /// # Panics
1756    ///
1757    /// Panics if the cache is already mutably borrowed.
1758    #[must_use]
1759    pub fn get_mark_xrate(&self, from_currency: Currency, to_currency: Currency) -> Option<f64> {
1760        self.cache().get_mark_xrate(from_currency, to_currency)
1761    }
1762
1763    /// Returns the yield curve for the `key` (if found).
1764    ///
1765    /// # Panics
1766    ///
1767    /// Panics if the cache is already mutably borrowed.
1768    #[must_use]
1769    pub fn yield_curve(&self, key: &str) -> Option<Box<dyn Fn(f64) -> f64>> {
1770        self.cache().yield_curve(key)
1771    }
1772
1773    /// Returns an owned copy of the greeks data for the `instrument_id` (if found).
1774    ///
1775    /// # Panics
1776    ///
1777    /// Panics if the cache is already mutably borrowed.
1778    #[must_use]
1779    pub fn greeks(&self, instrument_id: &InstrumentId) -> Option<GreeksData> {
1780        self.cache().greeks(instrument_id)
1781    }
1782
1783    /// Returns exchange-provided option greeks for the `instrument_id` (if found).
1784    ///
1785    /// # Panics
1786    ///
1787    /// Panics if the cache is already mutably borrowed.
1788    #[must_use]
1789    pub fn option_greeks(&self, instrument_id: &InstrumentId) -> Option<OptionGreeks> {
1790        self.cache().option_greeks(instrument_id).copied()
1791    }
1792
1793    /// Returns the currency for the `code` (if found).
1794    ///
1795    /// # Panics
1796    ///
1797    /// Panics if the cache is already mutably borrowed.
1798    #[must_use]
1799    pub fn currency(&self, code: &Ustr) -> Option<Currency> {
1800        self.cache().currency(code).copied()
1801    }
1802
1803    // panics-doc-ok
1804    /// Returns the currency for the `code`.
1805    ///
1806    /// # Errors
1807    ///
1808    /// Returns [`CurrencyLookupError::NotFound`] when the currency is not present in the cache.
1809    ///
1810    /// # Panics
1811    ///
1812    /// Panics if the cache is already mutably borrowed.
1813    pub fn try_currency(&self, code: &Ustr) -> Result<Currency, CurrencyLookupError> {
1814        self.cache().try_currency(code).copied()
1815    }
1816
1817    /// Returns an owned copy of the instrument for the `instrument_id` (if found).
1818    ///
1819    /// # Panics
1820    ///
1821    /// Panics if the cache is already mutably borrowed.
1822    #[must_use]
1823    pub fn instrument(&self, instrument_id: &InstrumentId) -> Option<InstrumentAny> {
1824        self.cache().instrument(instrument_id).cloned()
1825    }
1826
1827    // panics-doc-ok
1828    /// Returns an owned copy of the instrument for the `instrument_id`.
1829    ///
1830    /// # Errors
1831    ///
1832    /// Returns [`InstrumentLookupError::NotFound`] when the instrument is not present in the cache.
1833    ///
1834    /// # Panics
1835    ///
1836    /// Panics if the cache is already mutably borrowed.
1837    pub fn try_instrument(
1838        &self,
1839        instrument_id: &InstrumentId,
1840    ) -> Result<InstrumentAny, InstrumentLookupError> {
1841        self.cache().try_instrument(instrument_id).cloned()
1842    }
1843
1844    /// Returns the instrument IDs in the cache, optionally filtered by `venue`.
1845    ///
1846    /// # Panics
1847    ///
1848    /// Panics if the cache is already mutably borrowed.
1849    #[must_use]
1850    pub fn instrument_ids(&self, venue: Option<&Venue>) -> Vec<InstrumentId> {
1851        self.cache()
1852            .instrument_ids(venue)
1853            .into_iter()
1854            .copied()
1855            .collect()
1856    }
1857
1858    /// Returns owned copies of all instruments for the `venue`.
1859    ///
1860    /// # Panics
1861    ///
1862    /// Panics if the cache is already mutably borrowed.
1863    #[must_use]
1864    pub fn instruments(&self, venue: &Venue, underlying: Option<&Ustr>) -> Vec<InstrumentAny> {
1865        self.cache()
1866            .instruments(venue, underlying)
1867            .into_iter()
1868            .cloned()
1869            .collect()
1870    }
1871
1872    /// Returns owned copies of all instruments for the `venue`, parent `root`, and instrument
1873    /// `class`.
1874    ///
1875    /// # Panics
1876    ///
1877    /// Panics if the cache is already mutably borrowed.
1878    #[must_use]
1879    pub fn instruments_by_parent(
1880        &self,
1881        venue: &Venue,
1882        root: &Ustr,
1883        class: InstrumentClass,
1884    ) -> Vec<InstrumentAny> {
1885        self.cache()
1886            .instruments_by_parent(venue, root, class)
1887            .into_iter()
1888            .cloned()
1889            .collect()
1890    }
1891
1892    /// Returns the bar types in the cache, optionally filtered by instrument and price type.
1893    ///
1894    /// # Panics
1895    ///
1896    /// Panics if the cache is already mutably borrowed.
1897    #[must_use]
1898    pub fn bar_types(
1899        &self,
1900        instrument_id: Option<&InstrumentId>,
1901        price_type: Option<&PriceType>,
1902        aggregation_source: AggregationSource,
1903    ) -> Vec<BarType> {
1904        self.cache()
1905            .bar_types(instrument_id, price_type, aggregation_source)
1906            .into_iter()
1907            .copied()
1908            .collect()
1909    }
1910
1911    /// Returns an owned copy of the synthetic instrument for the `instrument_id` (if found).
1912    ///
1913    /// # Panics
1914    ///
1915    /// Panics if the cache is already mutably borrowed.
1916    #[must_use]
1917    pub fn synthetic(&self, instrument_id: &InstrumentId) -> Option<SyntheticInstrument> {
1918        self.cache().synthetic(instrument_id).cloned()
1919    }
1920
1921    // panics-doc-ok
1922    /// Returns an owned copy of the synthetic instrument for the `instrument_id`.
1923    ///
1924    /// # Errors
1925    ///
1926    /// Returns [`SyntheticInstrumentLookupError::NotFound`] when the synthetic instrument is not
1927    /// present in the cache.
1928    ///
1929    /// # Panics
1930    ///
1931    /// Panics if the cache is already mutably borrowed.
1932    pub fn try_synthetic(
1933        &self,
1934        instrument_id: &InstrumentId,
1935    ) -> Result<SyntheticInstrument, SyntheticInstrumentLookupError> {
1936        self.cache().try_synthetic(instrument_id).cloned()
1937    }
1938
1939    /// Returns the synthetic instrument IDs in the cache.
1940    ///
1941    /// # Panics
1942    ///
1943    /// Panics if the cache is already mutably borrowed.
1944    #[must_use]
1945    pub fn synthetic_ids(&self) -> Vec<InstrumentId> {
1946        self.cache().synthetic_ids().into_iter().copied().collect()
1947    }
1948
1949    /// Returns owned copies of all synthetic instruments in the cache.
1950    ///
1951    /// # Panics
1952    ///
1953    /// Panics if the cache is already mutably borrowed.
1954    #[must_use]
1955    pub fn synthetics(&self) -> Vec<SyntheticInstrument> {
1956        self.cache().synthetics().into_iter().cloned().collect()
1957    }
1958
1959    /// Returns an owned copy of the pool for the `instrument_id` (if found).
1960    ///
1961    /// # Panics
1962    ///
1963    /// Panics if the cache is already mutably borrowed.
1964    #[cfg(feature = "defi")]
1965    #[must_use]
1966    pub fn pool(&self, instrument_id: &InstrumentId) -> Option<Pool> {
1967        self.cache().pool(instrument_id).cloned()
1968    }
1969
1970    /// Returns the pool instrument IDs in the cache, optionally filtered by `venue`.
1971    ///
1972    /// # Panics
1973    ///
1974    /// Panics if the cache is already mutably borrowed.
1975    #[cfg(feature = "defi")]
1976    #[must_use]
1977    pub fn pool_ids(&self, venue: Option<&Venue>) -> Vec<InstrumentId> {
1978        self.cache().pool_ids(venue)
1979    }
1980
1981    /// Returns owned copies of all pools in the cache, optionally filtered by `venue`.
1982    ///
1983    /// # Panics
1984    ///
1985    /// Panics if the cache is already mutably borrowed.
1986    #[cfg(feature = "defi")]
1987    #[must_use]
1988    pub fn pools(&self, venue: Option<&Venue>) -> Vec<Pool> {
1989        self.cache().pools(venue).into_iter().cloned().collect()
1990    }
1991
1992    /// Returns an owned copy of the pool profiler for the `instrument_id` (if found).
1993    ///
1994    /// # Panics
1995    ///
1996    /// Panics if the cache is already mutably borrowed.
1997    #[cfg(feature = "defi")]
1998    #[must_use]
1999    pub fn pool_profiler(&self, instrument_id: &InstrumentId) -> Option<PoolProfiler> {
2000        self.cache().pool_profiler(instrument_id).cloned()
2001    }
2002
2003    /// Returns the pool profiler instrument IDs in the cache, optionally filtered by `venue`.
2004    ///
2005    /// # Panics
2006    ///
2007    /// Panics if the cache is already mutably borrowed.
2008    #[cfg(feature = "defi")]
2009    #[must_use]
2010    pub fn pool_profiler_ids(&self, venue: Option<&Venue>) -> Vec<InstrumentId> {
2011        self.cache().pool_profiler_ids(venue)
2012    }
2013
2014    /// Returns owned copies of all pool profilers in the cache, optionally filtered by `venue`.
2015    ///
2016    /// # Panics
2017    ///
2018    /// Panics if the cache is already mutably borrowed.
2019    #[cfg(feature = "defi")]
2020    #[must_use]
2021    pub fn pool_profilers(&self, venue: Option<&Venue>) -> Vec<PoolProfiler> {
2022        self.cache()
2023            .pool_profilers(venue)
2024            .into_iter()
2025            .cloned()
2026            .collect()
2027    }
2028
2029    /// Returns an owned copy of the account for the `account_id` (if found).
2030    ///
2031    /// # Panics
2032    ///
2033    /// Panics if the cache is already mutably borrowed.
2034    #[must_use]
2035    pub fn account(&self, account_id: &AccountId) -> Option<AccountAny> {
2036        self.cache().account_owned(account_id)
2037    }
2038
2039    // panics-doc-ok
2040    /// Returns an owned copy of the account for the `account_id`.
2041    ///
2042    /// # Errors
2043    ///
2044    /// Returns [`AccountLookupError::NotFound`] when the account is not present in the cache.
2045    ///
2046    /// # Panics
2047    ///
2048    /// Panics if the cache is already mutably borrowed.
2049    pub fn try_account(&self, account_id: &AccountId) -> Result<AccountAny, AccountLookupError> {
2050        self.cache()
2051            .try_account(account_id)
2052            .map(|account| account.cloned())
2053    }
2054
2055    /// Returns an owned copy of the account for the `venue` (if found).
2056    ///
2057    /// # Panics
2058    ///
2059    /// Panics if the cache is already mutably borrowed.
2060    #[must_use]
2061    pub fn account_for_venue(&self, venue: &Venue) -> Option<AccountAny> {
2062        self.cache().account_for_venue_owned(venue)
2063    }
2064
2065    /// Returns the account ID for the `venue` (if found).
2066    ///
2067    /// # Panics
2068    ///
2069    /// Panics if the cache is already mutably borrowed.
2070    #[must_use]
2071    pub fn account_id(&self, venue: &Venue) -> Option<AccountId> {
2072        self.cache().account_id(venue).copied()
2073    }
2074
2075    /// Returns owned copies of all accounts matching the `account_id`.
2076    ///
2077    /// # Panics
2078    ///
2079    /// Panics if the cache is already mutably borrowed.
2080    #[must_use]
2081    pub fn accounts(&self, account_id: &AccountId) -> Vec<AccountAny> {
2082        self.cache()
2083            .accounts(account_id)
2084            .into_iter()
2085            .map(|account| account.cloned())
2086            .collect()
2087    }
2088
2089    /// Returns owned copies of every account in the cache.
2090    ///
2091    /// # Panics
2092    ///
2093    /// Panics if the cache is already mutably borrowed.
2094    #[must_use]
2095    pub fn accounts_all(&self) -> Vec<AccountAny> {
2096        self.cache().accounts_all_owned()
2097    }
2098
2099    fn cache(&self) -> Ref<'_, Cache> {
2100        self.cache.borrow()
2101    }
2102}
2103
2104// Filter sources resolved from an order or position query.
2105//
2106// Captures the three states of a multi-key index intersection without committing to an owned
2107// result set: no filters at all (the caller iterates the bucket directly), one or more filter
2108// sources resolved successfully (intersect them lazily), or one filter resolved to no entries
2109// at all (the result is unconditionally empty).
2110enum FilterSources<'a, K> {
2111    Unfiltered,
2112    Empty,
2113    Sets(Vec<&'a AHashSet<K>>),
2114}
2115
2116// Intersects a non-empty collection of filter sources by sorting them ascending by length and
2117// driving the loop from the smallest set, collecting one `AHashSet` of matching keys.
2118//
2119// Single-source inputs short-circuit to a direct `AHashSet::clone` (memcopy of the bucket
2120// table) rather than rehashing each entry through `iter().copied().collect()`.
2121fn intersect_filter_sources<K>(mut sources: Vec<&AHashSet<K>>) -> AHashSet<K>
2122where
2123    K: Copy + Eq + std::hash::Hash,
2124{
2125    debug_assert!(!sources.is_empty());
2126    sources.sort_unstable_by_key(|s| s.len());
2127    let driver = sources[0];
2128    let rest = &sources[1..];
2129
2130    if rest.is_empty() {
2131        return driver.clone();
2132    }
2133
2134    driver
2135        .iter()
2136        .filter(|id| rest.iter().all(|s| s.contains(id)))
2137        .copied()
2138        .collect()
2139}
2140
2141// Intersects `bucket` with one or more filter sources.
2142//
2143// For exactly one filter source, iterates the larger of (bucket, filter) and looks up in the
2144// smaller. The larger set scans linearly (HW-prefetcher friendly) and the smaller stays hot in
2145// cache, which empirically beats the size-ordered approach when the smaller filter is too
2146// large to fit in L1 (e.g., a 20k-entry venue filter against a 100k-entry bucket). For two or
2147// more filters the size-ordered driver is reinstated and the bucket joins the source list.
2148fn intersect_pair_or_many<'a, K>(
2149    bucket: &'a AHashSet<K>,
2150    mut sources: Vec<&'a AHashSet<K>>,
2151) -> AHashSet<K>
2152where
2153    K: Copy + Eq + std::hash::Hash,
2154{
2155    debug_assert!(!sources.is_empty());
2156    if sources.len() == 1 {
2157        let filter = sources[0];
2158        let (larger, smaller) = if bucket.len() >= filter.len() {
2159            (bucket, filter)
2160        } else {
2161            (filter, bucket)
2162        };
2163        return larger.intersection(smaller).copied().collect();
2164    }
2165
2166    sources.push(bucket);
2167    intersect_filter_sources(sources)
2168}
2169
2170/// A common in-memory `Cache` for market and execution related data.
2171#[cfg_attr(
2172    feature = "python",
2173    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.common", unsendable)
2174)]
2175pub struct Cache {
2176    config: CacheConfig,
2177    index: CacheIndex,
2178    database: Option<Box<dyn CacheDatabaseAdapter>>,
2179    general: AHashMap<String, Bytes>,
2180    currencies: AHashMap<Ustr, Currency>,
2181    instruments: AHashMap<InstrumentId, InstrumentAny>,
2182    synthetics: AHashMap<InstrumentId, SyntheticInstrument>,
2183    books: AHashMap<InstrumentId, OrderBook>,
2184    own_books: AHashMap<InstrumentId, OwnOrderBook>,
2185    quotes: AHashMap<InstrumentId, BoundedVecDeque<QuoteTick>>,
2186    trades: AHashMap<InstrumentId, BoundedVecDeque<TradeTick>>,
2187    mark_xrates: AHashMap<(Currency, Currency), f64>,
2188    mark_prices: AHashMap<InstrumentId, BoundedVecDeque<MarkPriceUpdate>>,
2189    index_prices: AHashMap<InstrumentId, BoundedVecDeque<IndexPriceUpdate>>,
2190    funding_rates: AHashMap<InstrumentId, BoundedVecDeque<FundingRateUpdate>>,
2191    instrument_statuses: AHashMap<InstrumentId, BoundedVecDeque<InstrumentStatus>>,
2192    bars: AHashMap<BarType, BoundedVecDeque<Bar>>,
2193    greeks: AHashMap<InstrumentId, GreeksData>,
2194    option_greeks: AHashMap<InstrumentId, OptionGreeks>,
2195    yield_curves: AHashMap<String, YieldCurveData>,
2196    accounts: AHashMap<AccountId, SharedCell<AccountAny>>,
2197    orders: AHashMap<ClientOrderId, SharedCell<OrderAny>>,
2198    order_lists: AHashMap<OrderListId, OrderList>,
2199    positions: AHashMap<PositionId, SharedCell<Position>>,
2200    position_snapshots: AHashMap<PositionId, Vec<PositionSnapshotFrame>>,
2201    position_snapshot_revisions: AHashMap<PositionId, u64>,
2202    #[cfg(feature = "defi")]
2203    pub(crate) defi: crate::defi::cache::DefiCache,
2204}
2205
2206impl Debug for Cache {
2207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2208        f.debug_struct(stringify!(Cache))
2209            .field("config", &self.config)
2210            .field("index", &self.index)
2211            .field("general", &self.general)
2212            .field("currencies", &self.currencies)
2213            .field("instruments", &self.instruments)
2214            .field("synthetics", &self.synthetics)
2215            .field("books", &self.books)
2216            .field("own_books", &self.own_books)
2217            .field("quotes", &self.quotes)
2218            .field("trades", &self.trades)
2219            .field("mark_xrates", &self.mark_xrates)
2220            .field("mark_prices", &self.mark_prices)
2221            .field("index_prices", &self.index_prices)
2222            .field("funding_rates", &self.funding_rates)
2223            .field("instrument_statuses", &self.instrument_statuses)
2224            .field("bars", &self.bars)
2225            .field("greeks", &self.greeks)
2226            .field("option_greeks", &self.option_greeks)
2227            .field("yield_curves", &self.yield_curves)
2228            .field("accounts", &self.accounts)
2229            .field("orders", &self.orders)
2230            .field("order_lists", &self.order_lists)
2231            .field("positions", &self.positions)
2232            .field("position_snapshots", &self.position_snapshots)
2233            .finish()
2234    }
2235}
2236
2237impl Default for Cache {
2238    /// Creates a new default [`Cache`] instance.
2239    fn default() -> Self {
2240        Self::new(Some(CacheConfig::default()), None)
2241    }
2242}
2243
2244impl Cache {
2245    /// Creates a new [`Cache`] instance with optional configuration and database adapter.
2246    #[must_use]
2247    /// # Note
2248    ///
2249    /// Uses provided `CacheConfig` or defaults, and optional `CacheDatabaseAdapter` for persistence.
2250    ///
2251    /// # Panics
2252    ///
2253    /// Panics if the cache config has a zero tick or bar capacity.
2254    pub fn new(
2255        config: Option<CacheConfig>,
2256        database: Option<Box<dyn CacheDatabaseAdapter>>,
2257    ) -> Self {
2258        let config = config.unwrap_or_default();
2259        config.validate().expect("invalid `CacheConfig`");
2260
2261        Self {
2262            config,
2263            index: CacheIndex::default(),
2264            database,
2265            general: AHashMap::new(),
2266            currencies: AHashMap::new(),
2267            instruments: AHashMap::new(),
2268            synthetics: AHashMap::new(),
2269            books: AHashMap::new(),
2270            own_books: AHashMap::new(),
2271            quotes: AHashMap::new(),
2272            trades: AHashMap::new(),
2273            mark_xrates: AHashMap::new(),
2274            mark_prices: AHashMap::new(),
2275            index_prices: AHashMap::new(),
2276            funding_rates: AHashMap::new(),
2277            instrument_statuses: AHashMap::new(),
2278            bars: AHashMap::new(),
2279            greeks: AHashMap::new(),
2280            option_greeks: AHashMap::new(),
2281            yield_curves: AHashMap::new(),
2282            accounts: AHashMap::new(),
2283            orders: AHashMap::new(),
2284            order_lists: AHashMap::new(),
2285            positions: AHashMap::new(),
2286            position_snapshots: AHashMap::new(),
2287            position_snapshot_revisions: AHashMap::new(),
2288            #[cfg(feature = "defi")]
2289            defi: crate::defi::cache::DefiCache::default(),
2290        }
2291    }
2292
2293    /// Returns the cache instances memory address.
2294    #[must_use]
2295    pub fn memory_address(&self) -> String {
2296        format!("{:?}", std::ptr::from_ref(self))
2297    }
2298
2299    /// Sets the cache database adapter for persistence.
2300    ///
2301    /// This allows setting or replacing the database adapter after cache construction.
2302    pub fn set_database(&mut self, database: Box<dyn CacheDatabaseAdapter>) {
2303        let type_name = std::any::type_name_of_val(&*database);
2304        log::info!("Cache database adapter set: {type_name}");
2305        self.database = Some(database);
2306    }
2307
2308    // -- COMMANDS --------------------------------------------------------------------------------
2309
2310    /// Clears and reloads general entries from the database into the cache.
2311    ///
2312    /// # Errors
2313    ///
2314    /// Returns an error if loading general cache data fails.
2315    pub fn cache_general(&mut self) -> anyhow::Result<()> {
2316        self.general = match &mut self.database {
2317            Some(db) => db.load()?,
2318            None => AHashMap::new(),
2319        };
2320
2321        log::info!(
2322            "Cached {} general object(s) from database",
2323            self.general.len()
2324        );
2325        Ok(())
2326    }
2327
2328    /// Loads all core caches (currencies, instruments, accounts, orders, positions) from the database.
2329    ///
2330    /// # Errors
2331    ///
2332    /// Returns an error if loading all cache data fails.
2333    pub async fn cache_all(&mut self) -> anyhow::Result<()> {
2334        let cache_map = match &self.database {
2335            Some(db) => db.load_all().await?,
2336            None => CacheMap::default(),
2337        };
2338
2339        self.currencies = cache_map.currencies;
2340        self.instruments = cache_map.instruments;
2341        self.synthetics = cache_map.synthetics;
2342        self.accounts = cache_map
2343            .accounts
2344            .into_iter()
2345            .map(|(id, account)| (id, SharedCell::new(account)))
2346            .collect();
2347        self.orders = cache_map
2348            .orders
2349            .into_iter()
2350            .map(|(id, order)| (id, SharedCell::new(order)))
2351            .collect();
2352        self.positions = cache_map
2353            .positions
2354            .into_iter()
2355            .map(|(id, position)| (id, SharedCell::new(position)))
2356            .collect();
2357
2358        if let Some(db) = &self.database {
2359            self.index.order_position = db.load_index_order_position()?;
2360            self.index.order_client = db.load_index_order_client()?;
2361        }
2362
2363        self.cache_position_oms()?;
2364        self.assign_position_ids_to_contingencies();
2365        Ok(())
2366    }
2367
2368    /// Clears and reloads the currency cache from the database.
2369    ///
2370    /// # Errors
2371    ///
2372    /// Returns an error if loading currencies cache fails.
2373    pub async fn cache_currencies(&mut self) -> anyhow::Result<()> {
2374        self.currencies = match &mut self.database {
2375            Some(db) => db.load_currencies().await?,
2376            None => AHashMap::new(),
2377        };
2378
2379        log::info!("Cached {} currencies from database", self.general.len());
2380        Ok(())
2381    }
2382
2383    /// Clears and reloads the instrument cache from the database.
2384    ///
2385    /// # Errors
2386    ///
2387    /// Returns an error if loading instruments cache fails.
2388    pub async fn cache_instruments(&mut self) -> anyhow::Result<()> {
2389        self.instruments = match &mut self.database {
2390            Some(db) => db.load_instruments().await?,
2391            None => AHashMap::new(),
2392        };
2393
2394        log::info!("Cached {} instruments from database", self.general.len());
2395        Ok(())
2396    }
2397
2398    /// Clears and reloads the synthetic instrument cache from the database.
2399    ///
2400    /// # Errors
2401    ///
2402    /// Returns an error if loading synthetic instruments cache fails.
2403    pub async fn cache_synthetics(&mut self) -> anyhow::Result<()> {
2404        self.synthetics = match &mut self.database {
2405            Some(db) => db.load_synthetics().await?,
2406            None => AHashMap::new(),
2407        };
2408
2409        log::info!(
2410            "Cached {} synthetic instruments from database",
2411            self.general.len()
2412        );
2413        Ok(())
2414    }
2415
2416    /// Clears and reloads the account cache from the database.
2417    ///
2418    /// # Errors
2419    ///
2420    /// Returns an error if loading accounts cache fails.
2421    pub async fn cache_accounts(&mut self) -> anyhow::Result<()> {
2422        self.accounts = match &mut self.database {
2423            Some(db) => db
2424                .load_accounts()
2425                .await?
2426                .into_iter()
2427                .map(|(id, account)| (id, SharedCell::new(account)))
2428                .collect(),
2429            None => AHashMap::new(),
2430        };
2431
2432        log::info!(
2433            "Cached {} synthetic instruments from database",
2434            self.general.len()
2435        );
2436        Ok(())
2437    }
2438
2439    /// Clears and reloads the order cache from the database.
2440    ///
2441    /// # Errors
2442    ///
2443    /// Returns an error if loading orders cache fails.
2444    pub async fn cache_orders(&mut self) -> anyhow::Result<()> {
2445        self.orders = match &mut self.database {
2446            Some(db) => db
2447                .load_orders()
2448                .await?
2449                .into_iter()
2450                .map(|(id, order)| (id, SharedCell::new(order)))
2451                .collect(),
2452            None => AHashMap::new(),
2453        };
2454
2455        if let Some(db) = &self.database {
2456            self.index.order_position = db.load_index_order_position()?;
2457            self.index.order_client = db.load_index_order_client()?;
2458        }
2459
2460        log::info!("Cached {} orders from database", self.general.len());
2461
2462        self.assign_position_ids_to_contingencies();
2463        Ok(())
2464    }
2465
2466    /// Clears and reloads the position cache from the database.
2467    ///
2468    /// # Errors
2469    ///
2470    /// Returns an error if loading positions cache fails.
2471    pub async fn cache_positions(&mut self) -> anyhow::Result<()> {
2472        self.positions = match &mut self.database {
2473            Some(db) => db
2474                .load_positions()
2475                .await?
2476                .into_iter()
2477                .map(|(id, position)| (id, SharedCell::new(position)))
2478                .collect(),
2479            None => AHashMap::new(),
2480        };
2481
2482        self.cache_position_oms()?;
2483        log::info!("Cached {} positions from database", self.general.len());
2484        Ok(())
2485    }
2486
2487    fn cache_position_oms(&mut self) -> anyhow::Result<()> {
2488        let persisted = match &self.database {
2489            Some(database) => database.load()?,
2490            None => self.general.clone(),
2491        };
2492
2493        self.general
2494            .retain(|key, _| !key.starts_with(POSITION_OMS_KEY_PREFIX));
2495
2496        for (key, value) in persisted {
2497            if !key.starts_with(POSITION_OMS_KEY_PREFIX) {
2498                continue;
2499            }
2500            self.general.insert(key, value);
2501        }
2502
2503        self.index_position_oms();
2504        Ok(())
2505    }
2506
2507    /// Clears the current cache index and re-build.
2508    pub fn build_index(&mut self) {
2509        log::debug!("Building index");
2510
2511        // Index accounts
2512        for account_id in self.accounts.keys() {
2513            self.index
2514                .venue_account
2515                .insert(account_id.get_issuer(), *account_id);
2516        }
2517
2518        // Index orders
2519        for (client_order_id, order_cell) in &self.orders {
2520            let order = order_cell.borrow();
2521            let instrument_id = order.instrument_id();
2522            let venue = instrument_id.venue;
2523            let strategy_id = order.strategy_id();
2524
2525            // 1: Build index.venue_orders -> {Venue, {ClientOrderId}}
2526            self.index
2527                .venue_orders
2528                .entry(venue)
2529                .or_default()
2530                .insert(*client_order_id);
2531
2532            // 2: Build index.venue_order_ids -> {VenueOrderId, ClientOrderId}
2533            //    and index.client_order_ids -> {ClientOrderId, VenueOrderId}
2534            if let Some(venue_order_id) = order.venue_order_id() {
2535                self.index
2536                    .venue_order_ids
2537                    .insert(venue_order_id, *client_order_id);
2538                self.index
2539                    .client_order_ids
2540                    .insert(*client_order_id, venue_order_id);
2541            }
2542
2543            // 3: Build index.order_position -> {ClientOrderId, PositionId}
2544            if let Some(position_id) = order.position_id() {
2545                self.index
2546                    .order_position
2547                    .insert(*client_order_id, position_id);
2548            }
2549
2550            // 4: Build index.order_strategy -> {ClientOrderId, StrategyId}
2551            self.index
2552                .order_strategy
2553                .insert(*client_order_id, order.strategy_id());
2554
2555            // 5: Build index.instrument_orders -> {InstrumentId, {ClientOrderId}}
2556            self.index
2557                .instrument_orders
2558                .entry(instrument_id)
2559                .or_default()
2560                .insert(*client_order_id);
2561
2562            // 6: Build index.strategy_orders -> {StrategyId, {ClientOrderId}}
2563            self.index
2564                .strategy_orders
2565                .entry(strategy_id)
2566                .or_default()
2567                .insert(*client_order_id);
2568
2569            // 7: Build index.account_orders -> {AccountId, {ClientOrderId}}
2570            if let Some(account_id) = order.account_id() {
2571                self.index
2572                    .account_orders
2573                    .entry(account_id)
2574                    .or_default()
2575                    .insert(*client_order_id);
2576            }
2577
2578            // 8: Build index.exec_algorithm_orders -> {ExecAlgorithmId, {ClientOrderId}}
2579            if let Some(exec_algorithm_id) = order.exec_algorithm_id() {
2580                self.index
2581                    .exec_algorithm_orders
2582                    .entry(exec_algorithm_id)
2583                    .or_default()
2584                    .insert(*client_order_id);
2585            }
2586
2587            // 8: Build index.exec_spawn_orders -> {ClientOrderId, {ClientOrderId}}
2588            if let Some(exec_spawn_id) = order.exec_spawn_id() {
2589                self.index
2590                    .exec_spawn_orders
2591                    .entry(exec_spawn_id)
2592                    .or_default()
2593                    .insert(*client_order_id);
2594            }
2595
2596            // 9: Build index.orders -> {ClientOrderId}
2597            self.index.orders.insert(*client_order_id);
2598
2599            // 10: Build index.orders_active_local -> {ClientOrderId}
2600            if order.is_active_local() {
2601                self.index.orders_active_local.insert(*client_order_id);
2602            }
2603
2604            // 11: Build index.orders_open -> {ClientOrderId}
2605            if order.is_open() {
2606                self.index.orders_open.insert(*client_order_id);
2607            }
2608
2609            // 12: Build index.orders_closed -> {ClientOrderId}
2610            if order.is_closed() {
2611                self.index.orders_closed.insert(*client_order_id);
2612            }
2613
2614            // 13: Build index.orders_emulated -> {ClientOrderId}
2615            if let Some(emulation_trigger) = order.emulation_trigger()
2616                && emulation_trigger != TriggerType::NoTrigger
2617                && !order.is_closed()
2618            {
2619                self.index.orders_emulated.insert(*client_order_id);
2620            }
2621
2622            // 14: Build index.orders_inflight -> {ClientOrderId}
2623            if order.is_inflight() {
2624                self.index.orders_inflight.insert(*client_order_id);
2625            }
2626
2627            // 15: Build index.strategies -> {StrategyId}
2628            self.index.strategies.insert(strategy_id);
2629
2630            // 16: Build index.strategies -> {ExecAlgorithmId}
2631            if let Some(exec_algorithm_id) = order.exec_algorithm_id() {
2632                self.index.exec_algorithms.insert(exec_algorithm_id);
2633            }
2634        }
2635
2636        // Index positions
2637        for (position_id, position_cell) in &self.positions {
2638            let position = position_cell.borrow();
2639            let instrument_id = position.instrument_id;
2640            let venue = instrument_id.venue;
2641            let strategy_id = position.strategy_id;
2642
2643            // 1: Build index.venue_positions -> {Venue, {PositionId}}
2644            self.index
2645                .venue_positions
2646                .entry(venue)
2647                .or_default()
2648                .insert(*position_id);
2649
2650            // 2: Build index.position_strategy -> {PositionId, StrategyId}
2651            self.index
2652                .position_strategy
2653                .insert(*position_id, position.strategy_id);
2654
2655            // 3: Build index.position_orders -> {PositionId, {ClientOrderId}}
2656            self.index
2657                .position_orders
2658                .entry(*position_id)
2659                .or_default()
2660                .extend(position.client_order_ids());
2661
2662            // 4: Build index.instrument_positions -> {InstrumentId, {PositionId}}
2663            self.index
2664                .instrument_positions
2665                .entry(instrument_id)
2666                .or_default()
2667                .insert(*position_id);
2668
2669            // 5: Build index.strategy_positions -> {StrategyId, {PositionId}}
2670            self.index
2671                .strategy_positions
2672                .entry(strategy_id)
2673                .or_default()
2674                .insert(*position_id);
2675
2676            // 6: Build index.account_positions -> {AccountId, {PositionId}}
2677            self.index
2678                .account_positions
2679                .entry(position.account_id)
2680                .or_default()
2681                .insert(*position_id);
2682
2683            // 7: Build index.positions -> {PositionId}
2684            self.index.positions.insert(*position_id);
2685
2686            // 8: Build index.positions_open -> {PositionId}
2687            if position.is_open() {
2688                self.index.positions_open.insert(*position_id);
2689            }
2690
2691            // 9: Build index.positions_closed -> {PositionId}
2692            if position.is_closed() {
2693                self.index.positions_closed.insert(*position_id);
2694            }
2695
2696            // 10: Build index.strategies -> {StrategyId}
2697            self.index.strategies.insert(strategy_id);
2698        }
2699
2700        self.index_position_oms();
2701    }
2702
2703    fn index_position_oms(&mut self) {
2704        self.index.position_oms.clear();
2705
2706        for (key, value) in &self.general {
2707            let Some(position_id) = key.strip_prefix(POSITION_OMS_KEY_PREFIX) else {
2708                continue;
2709            };
2710            let position_id = PositionId::new(position_id);
2711            if !self.positions.contains_key(&position_id) {
2712                continue;
2713            }
2714
2715            match serde_json::from_slice::<OmsType>(value) {
2716                Ok(oms_type) => {
2717                    self.index.position_oms.insert(position_id, oms_type);
2718                }
2719                Err(e) => {
2720                    log::error!("Failed to decode position OMS for {position_id}: {e}");
2721                }
2722            }
2723        }
2724
2725        for position in self.positions.values().map(|cell| cell.borrow()) {
2726            if !self.index.position_oms.contains_key(&position.id)
2727                && position.id.as_str()
2728                    == format!("{}-{}", position.instrument_id, position.strategy_id)
2729            {
2730                self.index
2731                    .position_oms
2732                    .insert(position.id, OmsType::Netting);
2733            }
2734        }
2735    }
2736
2737    /// Returns whether the cache has a backing database.
2738    #[must_use]
2739    pub const fn has_backing(&self) -> bool {
2740        self.database.is_some()
2741    }
2742
2743    /// Loads persisted actor state.
2744    ///
2745    /// Returns `None` when the cache has no backing database.
2746    ///
2747    /// # Errors
2748    ///
2749    /// Returns an error if loading actor state fails.
2750    pub fn load_actor_state(
2751        &self,
2752        component_id: &ComponentId,
2753    ) -> anyhow::Result<Option<IndexMap<String, Vec<u8>>>> {
2754        self.database
2755            .as_ref()
2756            .map(|database| database.load_actor(component_id))
2757            .transpose()
2758            .map(|state| state.map(Self::decode_component_state))
2759    }
2760
2761    /// Loads persisted strategy state.
2762    ///
2763    /// Returns `None` when the cache has no backing database.
2764    ///
2765    /// # Errors
2766    ///
2767    /// Returns an error if loading strategy state fails.
2768    pub fn load_strategy_state(
2769        &self,
2770        strategy_id: &StrategyId,
2771    ) -> anyhow::Result<Option<IndexMap<String, Vec<u8>>>> {
2772        self.database
2773            .as_ref()
2774            .map(|database| database.load_strategy(strategy_id))
2775            .transpose()
2776            .map(|state| state.map(Self::decode_component_state))
2777    }
2778
2779    /// Persists actor state when the cache has a backing database.
2780    ///
2781    /// # Errors
2782    ///
2783    /// Returns an error if updating actor state fails.
2784    pub fn update_actor_state(
2785        &self,
2786        component_id: &ComponentId,
2787        state: &IndexMap<String, Vec<u8>>,
2788    ) -> anyhow::Result<()> {
2789        if let Some(database) = &self.database {
2790            database.update_actor(component_id, &Self::encode_component_state(state))?;
2791        }
2792        Ok(())
2793    }
2794
2795    /// Persists strategy state when the cache has a backing database.
2796    ///
2797    /// # Errors
2798    ///
2799    /// Returns an error if updating strategy state fails.
2800    pub fn update_strategy_state(
2801        &self,
2802        strategy_id: &StrategyId,
2803        state: &IndexMap<String, Vec<u8>>,
2804    ) -> anyhow::Result<()> {
2805        if let Some(database) = &self.database {
2806            database.update_strategy(strategy_id, &Self::encode_component_state(state))?;
2807        }
2808        Ok(())
2809    }
2810
2811    fn decode_component_state(state: AHashMap<String, Bytes>) -> IndexMap<String, Vec<u8>> {
2812        state
2813            .into_iter()
2814            .map(|(key, value)| (key, value.to_vec()))
2815            .collect()
2816    }
2817
2818    fn encode_component_state(state: &IndexMap<String, Vec<u8>>) -> AHashMap<String, Bytes> {
2819        state
2820            .iter()
2821            .map(|(key, value)| (key.clone(), Bytes::copy_from_slice(value)))
2822            .collect()
2823    }
2824
2825    // Calculate the unrealized profit and loss (PnL) for `position`.
2826    #[must_use]
2827    pub fn calculate_unrealized_pnl(&self, position: &Position) -> Option<Money> {
2828        let Some(quote) = self.quote(&position.instrument_id) else {
2829            log::warn!(
2830                "Cannot calculate unrealized PnL for {}, no quotes for {}",
2831                position.id,
2832                position.instrument_id
2833            );
2834            return None;
2835        };
2836
2837        // Use exit price for mark-to-market: longs exit at bid, shorts exit at ask
2838        let last = match position.side {
2839            PositionSide::Flat | PositionSide::NoPositionSide => {
2840                return Some(Money::zero(position.settlement_currency));
2841            }
2842            PositionSide::Long => quote.bid_price,
2843            PositionSide::Short => quote.ask_price,
2844        };
2845
2846        match position.try_unrealized_pnl(last) {
2847            Ok(pnl) => Some(pnl),
2848            Err(e) => {
2849                log::error!("Cannot calculate unrealized PnL for {}: {e}", position.id);
2850                None
2851            }
2852        }
2853    }
2854
2855    /// Checks integrity of data within the cache.
2856    ///
2857    /// All data should be loaded from the database prior to this call.
2858    /// If an error is found then a log error message will also be produced.
2859    ///
2860    /// # Panics
2861    ///
2862    /// Panics if failure calling system clock.
2863    #[must_use]
2864    pub fn check_integrity(&mut self) -> bool {
2865        let mut error_count = 0;
2866        let failure = "Integrity failure";
2867
2868        // Get current timestamp in microseconds
2869        let timestamp_us = SystemTime::now()
2870            .duration_since(UNIX_EPOCH)
2871            .expect("Time went backwards")
2872            .as_micros();
2873
2874        log::info!("Checking data integrity");
2875
2876        // Check object caches
2877        for account_id in self.accounts.keys() {
2878            if !self
2879                .index
2880                .venue_account
2881                .contains_key(&account_id.get_issuer())
2882            {
2883                log::error!(
2884                    "{failure} in accounts: {account_id} not found in `self.index.venue_account`",
2885                );
2886                error_count += 1;
2887            }
2888        }
2889
2890        for (client_order_id, order_cell) in &self.orders {
2891            let order = order_cell.borrow();
2892
2893            if !self.index.order_strategy.contains_key(client_order_id) {
2894                log::error!(
2895                    "{failure} in orders: {client_order_id} not found in `self.index.order_strategy`"
2896                );
2897                error_count += 1;
2898            }
2899
2900            if !self.index.orders.contains(client_order_id) {
2901                log::error!(
2902                    "{failure} in orders: {client_order_id} not found in `self.index.orders`",
2903                );
2904                error_count += 1;
2905            }
2906
2907            if order.is_inflight() && !self.index.orders_inflight.contains(client_order_id) {
2908                log::error!(
2909                    "{failure} in orders: {client_order_id} not found in `self.index.orders_inflight`",
2910                );
2911                error_count += 1;
2912            }
2913
2914            if order.is_active_local() && !self.index.orders_active_local.contains(client_order_id)
2915            {
2916                log::error!(
2917                    "{failure} in orders: {client_order_id} not found in `self.index.orders_active_local`",
2918                );
2919                error_count += 1;
2920            }
2921
2922            if order.is_open() && !self.index.orders_open.contains(client_order_id) {
2923                log::error!(
2924                    "{failure} in orders: {client_order_id} not found in `self.index.orders_open`",
2925                );
2926                error_count += 1;
2927            }
2928
2929            if order.is_closed() && !self.index.orders_closed.contains(client_order_id) {
2930                log::error!(
2931                    "{failure} in orders: {client_order_id} not found in `self.index.orders_closed`",
2932                );
2933                error_count += 1;
2934            }
2935
2936            if let Some(exec_algorithm_id) = order.exec_algorithm_id() {
2937                if !self
2938                    .index
2939                    .exec_algorithm_orders
2940                    .contains_key(&exec_algorithm_id)
2941                {
2942                    log::error!(
2943                        "{failure} in orders: {client_order_id} not found in `self.index.exec_algorithm_orders`",
2944                    );
2945                    error_count += 1;
2946                }
2947
2948                if order.exec_spawn_id().is_none()
2949                    && !self.index.exec_spawn_orders.contains_key(client_order_id)
2950                {
2951                    log::error!(
2952                        "{failure} in orders: {client_order_id} not found in `self.index.exec_spawn_orders`",
2953                    );
2954                    error_count += 1;
2955                }
2956            }
2957        }
2958
2959        for (position_id, position_cell) in &self.positions {
2960            let position = position_cell.borrow();
2961
2962            if !self.index.position_strategy.contains_key(position_id) {
2963                log::error!(
2964                    "{failure} in positions: {position_id} not found in `self.index.position_strategy`",
2965                );
2966                error_count += 1;
2967            }
2968
2969            if !self.index.position_orders.contains_key(position_id) {
2970                log::error!(
2971                    "{failure} in positions: {position_id} not found in `self.index.position_orders`",
2972                );
2973                error_count += 1;
2974            }
2975
2976            if !self.index.positions.contains(position_id) {
2977                log::error!(
2978                    "{failure} in positions: {position_id} not found in `self.index.positions`",
2979                );
2980                error_count += 1;
2981            }
2982
2983            if position.is_open() && !self.index.positions_open.contains(position_id) {
2984                log::error!(
2985                    "{failure} in positions: {position_id} not found in `self.index.positions_open`",
2986                );
2987                error_count += 1;
2988            }
2989
2990            if position.is_closed() && !self.index.positions_closed.contains(position_id) {
2991                log::error!(
2992                    "{failure} in positions: {position_id} not found in `self.index.positions_closed`",
2993                );
2994                error_count += 1;
2995            }
2996        }
2997
2998        // Check indexes
2999        for account_id in self.index.venue_account.values() {
3000            if !self.accounts.contains_key(account_id) {
3001                log::error!(
3002                    "{failure} in `index.venue_account`: {account_id} not found in `self.accounts`",
3003                );
3004                error_count += 1;
3005            }
3006        }
3007
3008        for client_order_id in self.index.venue_order_ids.values() {
3009            if !self.orders.contains_key(client_order_id) {
3010                log::error!(
3011                    "{failure} in `index.venue_order_ids`: {client_order_id} not found in `self.orders`",
3012                );
3013                error_count += 1;
3014            }
3015        }
3016
3017        for client_order_id in self.index.client_order_ids.keys() {
3018            if !self.orders.contains_key(client_order_id) {
3019                log::error!(
3020                    "{failure} in `index.client_order_ids`: {client_order_id} not found in `self.orders`",
3021                );
3022                error_count += 1;
3023            }
3024        }
3025
3026        for client_order_id in self.index.order_position.keys() {
3027            if !self.orders.contains_key(client_order_id) {
3028                log::error!(
3029                    "{failure} in `index.order_position`: {client_order_id} not found in `self.orders`",
3030                );
3031                error_count += 1;
3032            }
3033        }
3034
3035        // Check indexes
3036        for client_order_id in self.index.order_strategy.keys() {
3037            if !self.orders.contains_key(client_order_id) {
3038                log::error!(
3039                    "{failure} in `index.order_strategy`: {client_order_id} not found in `self.orders`",
3040                );
3041                error_count += 1;
3042            }
3043        }
3044
3045        for position_id in self.index.position_strategy.keys() {
3046            if !self.positions.contains_key(position_id) {
3047                log::error!(
3048                    "{failure} in `index.position_strategy`: {position_id} not found in `self.positions`",
3049                );
3050                error_count += 1;
3051            }
3052        }
3053
3054        for position_id in self.index.position_orders.keys() {
3055            if !self.positions.contains_key(position_id) {
3056                log::error!(
3057                    "{failure} in `index.position_orders`: {position_id} not found in `self.positions`",
3058                );
3059                error_count += 1;
3060            }
3061        }
3062
3063        for (instrument_id, client_order_ids) in &self.index.instrument_orders {
3064            for client_order_id in client_order_ids {
3065                if !self.orders.contains_key(client_order_id) {
3066                    log::error!(
3067                        "{failure} in `index.instrument_orders`: {instrument_id} not found in `self.orders`",
3068                    );
3069                    error_count += 1;
3070                }
3071            }
3072        }
3073
3074        for instrument_id in self.index.instrument_positions.keys() {
3075            if !self.index.instrument_orders.contains_key(instrument_id) {
3076                log::error!(
3077                    "{failure} in `index.instrument_positions`: {instrument_id} not found in `index.instrument_orders`",
3078                );
3079                error_count += 1;
3080            }
3081        }
3082
3083        for client_order_ids in self.index.strategy_orders.values() {
3084            for client_order_id in client_order_ids {
3085                if !self.orders.contains_key(client_order_id) {
3086                    log::error!(
3087                        "{failure} in `index.strategy_orders`: {client_order_id} not found in `self.orders`",
3088                    );
3089                    error_count += 1;
3090                }
3091            }
3092        }
3093
3094        for position_ids in self.index.strategy_positions.values() {
3095            for position_id in position_ids {
3096                if !self.positions.contains_key(position_id) {
3097                    log::error!(
3098                        "{failure} in `index.strategy_positions`: {position_id} not found in `self.positions`",
3099                    );
3100                    error_count += 1;
3101                }
3102            }
3103        }
3104
3105        for client_order_id in &self.index.orders {
3106            if !self.orders.contains_key(client_order_id) {
3107                log::error!(
3108                    "{failure} in `index.orders`: {client_order_id} not found in `self.orders`",
3109                );
3110                error_count += 1;
3111            }
3112        }
3113
3114        for client_order_id in &self.index.orders_emulated {
3115            if !self.orders.contains_key(client_order_id) {
3116                log::error!(
3117                    "{failure} in `index.orders_emulated`: {client_order_id} not found in `self.orders`",
3118                );
3119                error_count += 1;
3120            }
3121        }
3122
3123        for client_order_id in &self.index.orders_active_local {
3124            if !self.orders.contains_key(client_order_id) {
3125                log::error!(
3126                    "{failure} in `index.orders_active_local`: {client_order_id} not found in `self.orders`",
3127                );
3128                error_count += 1;
3129            }
3130        }
3131
3132        for client_order_id in &self.index.orders_inflight {
3133            if !self.orders.contains_key(client_order_id) {
3134                log::error!(
3135                    "{failure} in `index.orders_inflight`: {client_order_id} not found in `self.orders`",
3136                );
3137                error_count += 1;
3138            }
3139        }
3140
3141        for client_order_id in &self.index.orders_open {
3142            if !self.orders.contains_key(client_order_id) {
3143                log::error!(
3144                    "{failure} in `index.orders_open`: {client_order_id} not found in `self.orders`",
3145                );
3146                error_count += 1;
3147            }
3148        }
3149
3150        for client_order_id in &self.index.orders_closed {
3151            if !self.orders.contains_key(client_order_id) {
3152                log::error!(
3153                    "{failure} in `index.orders_closed`: {client_order_id} not found in `self.orders`",
3154                );
3155                error_count += 1;
3156            }
3157        }
3158
3159        for position_id in &self.index.positions {
3160            if !self.positions.contains_key(position_id) {
3161                log::error!(
3162                    "{failure} in `index.positions`: {position_id} not found in `self.positions`",
3163                );
3164                error_count += 1;
3165            }
3166        }
3167
3168        for position_id in &self.index.positions_open {
3169            if !self.positions.contains_key(position_id) {
3170                log::error!(
3171                    "{failure} in `index.positions_open`: {position_id} not found in `self.positions`",
3172                );
3173                error_count += 1;
3174            }
3175        }
3176
3177        for position_id in &self.index.positions_closed {
3178            if !self.positions.contains_key(position_id) {
3179                log::error!(
3180                    "{failure} in `index.positions_closed`: {position_id} not found in `self.positions`",
3181                );
3182                error_count += 1;
3183            }
3184        }
3185
3186        for strategy_id in &self.index.strategies {
3187            if !self.index.strategy_orders.contains_key(strategy_id) {
3188                log::error!(
3189                    "{failure} in `index.strategies`: {strategy_id} not found in `index.strategy_orders`",
3190                );
3191                error_count += 1;
3192            }
3193        }
3194
3195        for exec_algorithm_id in &self.index.exec_algorithms {
3196            if !self
3197                .index
3198                .exec_algorithm_orders
3199                .contains_key(exec_algorithm_id)
3200            {
3201                log::error!(
3202                    "{failure} in `index.exec_algorithms`: {exec_algorithm_id} not found in `index.exec_algorithm_orders`",
3203                );
3204                error_count += 1;
3205            }
3206        }
3207
3208        let total_us = SystemTime::now()
3209            .duration_since(UNIX_EPOCH)
3210            .expect("Time went backwards")
3211            .as_micros()
3212            - timestamp_us;
3213
3214        if error_count == 0 {
3215            log::info!("Integrity check passed in {total_us}μs");
3216            true
3217        } else {
3218            log::error!(
3219                "Integrity check failed with {error_count} error{} in {total_us}μs",
3220                if error_count == 1 { "" } else { "s" },
3221            );
3222            false
3223        }
3224    }
3225
3226    /// Checks for any residual open state and log warnings if any are found.
3227    ///
3228    ///'Open state' is considered to be open orders and open positions.
3229    #[must_use]
3230    pub fn check_residuals(&self) -> bool {
3231        log::debug!("Checking residuals");
3232
3233        let mut residuals = false;
3234
3235        // Check for any open orders
3236        for order in self.orders_open(None, None, None, None, None) {
3237            residuals = true;
3238            log::warn!("Residual {order}");
3239        }
3240
3241        // Check for any open positions
3242        for position in self.positions_open(None, None, None, None, None) {
3243            residuals = true;
3244            log::warn!("Residual {position}");
3245        }
3246
3247        residuals
3248    }
3249
3250    /// Purges all closed orders from the cache that are older than `buffer_secs`.
3251    ///
3252    ///
3253    /// Only orders that have been closed for at least this amount of time will be purged.
3254    /// A value of 0 means purge all closed orders regardless of when they were closed.
3255    pub fn purge_closed_orders(&mut self, ts_now: UnixNanos, buffer_secs: u64) {
3256        log::debug!(
3257            "Purging closed orders{}",
3258            if buffer_secs > 0 {
3259                format!(" with buffer_secs={buffer_secs}")
3260            } else {
3261                String::new()
3262            }
3263        );
3264
3265        let buffer_ns = secs_to_nanos_unchecked(buffer_secs as f64);
3266
3267        let mut affected_order_list_ids: AHashSet<OrderListId> = AHashSet::new();
3268
3269        'outer: for client_order_id in self.index.orders_closed.clone() {
3270            let purge_target = self.orders.get(&client_order_id).and_then(|order_cell| {
3271                let order = order_cell.borrow();
3272                if order.is_closed()
3273                    && let Some(ts_closed) = order.ts_closed()
3274                    && ts_closed + buffer_ns <= ts_now
3275                {
3276                    let linked = order.linked_order_ids().map(<[_]>::to_vec);
3277                    let order_list_id = order.order_list_id();
3278                    Some((linked, order_list_id))
3279                } else {
3280                    None
3281                }
3282            });
3283
3284            let Some((linked, order_list_id)) = purge_target else {
3285                continue;
3286            };
3287
3288            // Check any linked orders (contingency orders)
3289            if let Some(linked_order_ids) = linked {
3290                for linked_order_id in &linked_order_ids {
3291                    if let Some(linked_order_cell) = self.orders.get(linked_order_id)
3292                        && linked_order_cell.borrow().is_open()
3293                    {
3294                        // Do not purge if linked order still open
3295                        continue 'outer;
3296                    }
3297                }
3298            }
3299
3300            if let Some(order_list_id) = order_list_id {
3301                affected_order_list_ids.insert(order_list_id);
3302            }
3303
3304            self.purge_order(client_order_id);
3305        }
3306
3307        for order_list_id in affected_order_list_ids {
3308            if let Some(order_list) = self.order_lists.get(&order_list_id) {
3309                let all_purged = order_list
3310                    .client_order_ids
3311                    .iter()
3312                    .all(|id| !self.orders.contains_key(id));
3313
3314                if all_purged {
3315                    self.order_lists.remove(&order_list_id);
3316                    log::info!("Purged {order_list_id}");
3317                }
3318            }
3319        }
3320    }
3321
3322    /// Purges all closed positions from the cache that are older than `buffer_secs`.
3323    pub fn purge_closed_positions(&mut self, ts_now: UnixNanos, buffer_secs: u64) {
3324        log::debug!(
3325            "Purging closed positions{}",
3326            if buffer_secs > 0 {
3327                format!(" with buffer_secs={buffer_secs}")
3328            } else {
3329                String::new()
3330            }
3331        );
3332
3333        let buffer_ns = secs_to_nanos_unchecked(buffer_secs as f64);
3334
3335        for position_id in self.index.positions_closed.clone() {
3336            let should_purge = self.positions.get(&position_id).is_some_and(|cell| {
3337                let position = cell.borrow();
3338                position.is_closed()
3339                    && position
3340                        .ts_closed
3341                        .is_some_and(|ts_closed| ts_closed + buffer_ns <= ts_now)
3342            });
3343
3344            if should_purge {
3345                self.purge_position(position_id);
3346            }
3347        }
3348    }
3349
3350    /// Purges the order with the `client_order_id` from the cache (if found).
3351    ///
3352    /// For safety, an order is prevented from being purged if it's open.
3353    pub fn purge_order(&mut self, client_order_id: ClientOrderId) {
3354        struct OrderDetails {
3355            is_open: bool,
3356            instrument_id: InstrumentId,
3357            strategy_id: StrategyId,
3358            account_id: Option<AccountId>,
3359            exec_algorithm_id: Option<ExecAlgorithmId>,
3360            exec_spawn_id: Option<ClientOrderId>,
3361            position_id: Option<PositionId>,
3362            venue_order_id: Option<VenueOrderId>,
3363            venue_order_ids: Vec<VenueOrderId>,
3364        }
3365
3366        let order_cell = self.orders.get(&client_order_id).cloned();
3367        let order_details = order_cell.as_ref().map(|cell| {
3368            let order = cell.borrow();
3369            OrderDetails {
3370                is_open: order.is_open(),
3371                instrument_id: order.instrument_id(),
3372                strategy_id: order.strategy_id(),
3373                account_id: order.account_id(),
3374                exec_algorithm_id: order.exec_algorithm_id(),
3375                exec_spawn_id: order.exec_spawn_id(),
3376                position_id: order.position_id(),
3377                venue_order_id: order.venue_order_id(),
3378                venue_order_ids: order.venue_order_ids().into_iter().copied().collect(),
3379            }
3380        });
3381
3382        if order_details
3383            .as_ref()
3384            .is_some_and(|details| details.is_open)
3385        {
3386            log::warn!("Order {client_order_id} found open when purging, skipping purge");
3387            return;
3388        }
3389
3390        if order_details.is_some() {
3391            self.orders.remove(&client_order_id);
3392        } else {
3393            log::warn!("Order {client_order_id} not found when purging");
3394        }
3395
3396        let indexed_position_id = self.index.order_position.remove(&client_order_id);
3397        let indexed_strategy_id = self.index.order_strategy.remove(&client_order_id);
3398        self.index.order_client.remove(&client_order_id);
3399        let indexed_venue_order_id = self.index.client_order_ids.remove(&client_order_id);
3400
3401        if let Some(details) = &order_details {
3402            if let Some(venue_orders) = self
3403                .index
3404                .venue_orders
3405                .get_mut(&details.instrument_id.venue)
3406            {
3407                venue_orders.remove(&client_order_id);
3408                if venue_orders.is_empty() {
3409                    self.index.venue_orders.remove(&details.instrument_id.venue);
3410                }
3411            }
3412
3413            // As with the strategy buckets below, an absent bucket is left absent: recreating
3414            // it would suppress the `index.instrument_positions` integrity check.
3415            // As with the strategy buckets below, an absent bucket is left absent: recreating
3416            // it would suppress the `index.instrument_positions` integrity check.
3417            let instrument_orders_became_empty = self
3418                .index
3419                .instrument_orders
3420                .get_mut(&details.instrument_id)
3421                .is_some_and(|instrument_orders| {
3422                    instrument_orders.remove(&client_order_id);
3423                    instrument_orders.is_empty()
3424                });
3425
3426            let has_instrument_positions = self
3427                .index
3428                .instrument_positions
3429                .get(&details.instrument_id)
3430                .is_some_and(|positions| !positions.is_empty());
3431
3432            if instrument_orders_became_empty && !has_instrument_positions {
3433                self.index.instrument_orders.remove(&details.instrument_id);
3434            }
3435
3436            if let Some(exec_algorithm_id) = details.exec_algorithm_id {
3437                let became_empty = self
3438                    .index
3439                    .exec_algorithm_orders
3440                    .get_mut(&exec_algorithm_id)
3441                    .is_some_and(|orders| {
3442                        orders.remove(&client_order_id);
3443                        orders.is_empty()
3444                    });
3445
3446                if became_empty {
3447                    self.index.exec_algorithm_orders.remove(&exec_algorithm_id);
3448                    self.index.exec_algorithms.remove(&exec_algorithm_id);
3449                }
3450            }
3451
3452            if let Some(account_id) = details.account_id
3453                && let Some(account_orders) = self.index.account_orders.get_mut(&account_id)
3454            {
3455                account_orders.remove(&client_order_id);
3456                if account_orders.is_empty() {
3457                    self.index.account_orders.remove(&account_id);
3458                }
3459            }
3460
3461            if let Some(exec_spawn_id) = details.exec_spawn_id
3462                && let Some(spawn_orders) = self.index.exec_spawn_orders.get_mut(&exec_spawn_id)
3463            {
3464                spawn_orders.remove(&client_order_id);
3465                if spawn_orders.is_empty() {
3466                    self.index.exec_spawn_orders.remove(&exec_spawn_id);
3467                }
3468            }
3469        }
3470
3471        let mut position_ids = AHashSet::new();
3472        if let Some(position_id) = indexed_position_id {
3473            position_ids.insert(position_id);
3474        }
3475
3476        if let Some(position_id) = order_details
3477            .as_ref()
3478            .and_then(|details| details.position_id)
3479        {
3480            position_ids.insert(position_id);
3481        }
3482
3483        let mut strategy_ids = AHashSet::new();
3484        if let Some(strategy_id) = indexed_strategy_id {
3485            strategy_ids.insert(strategy_id);
3486        }
3487
3488        if let Some(details) = &order_details {
3489            strategy_ids.insert(details.strategy_id);
3490        }
3491
3492        for position_id in position_ids {
3493            if self.positions.contains_key(&position_id) {
3494                if let Some(position_orders) = self.index.position_orders.get_mut(&position_id) {
3495                    position_orders.remove(&client_order_id);
3496                }
3497                continue;
3498            }
3499
3500            let has_other_orders =
3501                if let Some(position_orders) = self.index.position_orders.get_mut(&position_id) {
3502                    position_orders.remove(&client_order_id);
3503                    !position_orders.is_empty()
3504                } else {
3505                    self.index
3506                        .order_position
3507                        .values()
3508                        .any(|candidate| *candidate == position_id)
3509                };
3510
3511            if has_other_orders {
3512                continue;
3513            }
3514
3515            self.index.position_orders.remove(&position_id);
3516            if let Some(strategy_id) = self.index.position_strategy.remove(&position_id) {
3517                strategy_ids.insert(strategy_id);
3518                if let Some(strategy_positions) =
3519                    self.index.strategy_positions.get_mut(&strategy_id)
3520                {
3521                    strategy_positions.remove(&position_id);
3522                    if strategy_positions.is_empty() {
3523                        self.index.strategy_positions.remove(&strategy_id);
3524                    }
3525                }
3526            }
3527
3528            if let Some(details) = &order_details
3529                && let Some(venue_positions) = self
3530                    .index
3531                    .venue_positions
3532                    .get_mut(&details.instrument_id.venue)
3533            {
3534                venue_positions.remove(&position_id);
3535                if venue_positions.is_empty() {
3536                    self.index
3537                        .venue_positions
3538                        .remove(&details.instrument_id.venue);
3539                }
3540            }
3541        }
3542
3543        for strategy_id in strategy_ids {
3544            // An absent reverse bucket is not an empty one: it means the index is already
3545            // inconsistent, possibly while another cached order still uses this strategy.
3546            // Retiring the registry entry here would both drop a live strategy from
3547            // `strategy_ids` and stop `check_integrity` reporting the missing bucket, so the
3548            // absent case is left exactly as found.
3549            let strategy_orders_became_empty = self
3550                .index
3551                .strategy_orders
3552                .get_mut(&strategy_id)
3553                .is_some_and(|strategy_orders| {
3554                    strategy_orders.remove(&client_order_id);
3555                    strategy_orders.is_empty()
3556                });
3557
3558            let has_positions = self
3559                .index
3560                .strategy_positions
3561                .get(&strategy_id)
3562                .is_some_and(|strategy_positions| !strategy_positions.is_empty());
3563
3564            if strategy_orders_became_empty && !has_positions {
3565                self.index.strategy_orders.remove(&strategy_id);
3566                self.index.strategies.remove(&strategy_id);
3567            }
3568        }
3569
3570        let mut venue_order_ids = AHashSet::new();
3571        if let Some(venue_order_id) = indexed_venue_order_id {
3572            venue_order_ids.insert(venue_order_id);
3573        }
3574
3575        if let Some(details) = &order_details {
3576            venue_order_ids.extend(details.venue_order_ids.iter().copied());
3577            if let Some(venue_order_id) = details.venue_order_id {
3578                venue_order_ids.insert(venue_order_id);
3579            }
3580        }
3581
3582        for venue_order_id in venue_order_ids {
3583            if self.index.venue_order_ids.get(&venue_order_id) == Some(&client_order_id) {
3584                self.index.venue_order_ids.remove(&venue_order_id);
3585            }
3586        }
3587
3588        self.index.exec_spawn_orders.remove(&client_order_id);
3589
3590        self.index.orders.remove(&client_order_id);
3591        self.index.orders_active_local.remove(&client_order_id);
3592        self.index.orders_open.remove(&client_order_id);
3593        self.index.orders_closed.remove(&client_order_id);
3594        self.index.orders_emulated.remove(&client_order_id);
3595        self.index.orders_inflight.remove(&client_order_id);
3596        self.index.orders_pending_cancel.remove(&client_order_id);
3597
3598        if order_details.is_some() {
3599            log::info!("Purged order {client_order_id}");
3600        }
3601    }
3602
3603    /// Purges the position with the `position_id` from the cache (if found).
3604    ///
3605    /// For safety, a position is prevented from being purged if it's open.
3606    pub fn purge_position(&mut self, position_id: PositionId) {
3607        // Snapshot the position so we can release the borrow before mutating indexes.
3608        let position = self
3609            .positions
3610            .get(&position_id)
3611            .map(|cell| cell.borrow().clone());
3612
3613        // Prevent purging open positions
3614        if let Some(ref pos) = position
3615            && pos.is_open()
3616        {
3617            log::warn!("Position {position_id} found open when purging, skipping purge");
3618            return;
3619        }
3620
3621        // If position exists in cache, remove it and clean up position-specific indices
3622        if let Some(ref pos) = position {
3623            self.positions.remove(&position_id);
3624
3625            // Remove from venue positions index
3626            if let Some(venue_positions) =
3627                self.index.venue_positions.get_mut(&pos.instrument_id.venue)
3628            {
3629                venue_positions.remove(&position_id);
3630                if venue_positions.is_empty() {
3631                    self.index.venue_positions.remove(&pos.instrument_id.venue);
3632                }
3633            }
3634
3635            // Remove from instrument positions index
3636            let instrument_positions_became_empty = self
3637                .index
3638                .instrument_positions
3639                .get_mut(&pos.instrument_id)
3640                .is_some_and(|positions| {
3641                    positions.remove(&position_id);
3642                    positions.is_empty()
3643                });
3644
3645            if instrument_positions_became_empty {
3646                self.index.instrument_positions.remove(&pos.instrument_id);
3647                let instrument_orders_empty = self
3648                    .index
3649                    .instrument_orders
3650                    .get(&pos.instrument_id)
3651                    .is_some_and(|orders| orders.is_empty());
3652
3653                if instrument_orders_empty {
3654                    self.index.instrument_orders.remove(&pos.instrument_id);
3655                }
3656            }
3657
3658            // Remove from strategy positions index
3659            let strategy_positions_became_empty = self
3660                .index
3661                .strategy_positions
3662                .get_mut(&pos.strategy_id)
3663                .is_some_and(|positions| {
3664                    positions.remove(&position_id);
3665                    positions.is_empty()
3666                });
3667
3668            if strategy_positions_became_empty {
3669                self.index.strategy_positions.remove(&pos.strategy_id);
3670                let strategy_orders_empty = self
3671                    .index
3672                    .strategy_orders
3673                    .get(&pos.strategy_id)
3674                    .is_some_and(|orders| orders.is_empty());
3675
3676                if strategy_orders_empty {
3677                    self.index.strategy_orders.remove(&pos.strategy_id);
3678                    self.index.strategies.remove(&pos.strategy_id);
3679                }
3680            }
3681
3682            // Remove from account positions index
3683            if let Some(account_positions) = self.index.account_positions.get_mut(&pos.account_id) {
3684                account_positions.remove(&position_id);
3685                if account_positions.is_empty() {
3686                    self.index.account_positions.remove(&pos.account_id);
3687                }
3688            }
3689
3690            // Remove position ID from orders that reference it
3691            for client_order_id in pos.client_order_ids() {
3692                self.index.order_position.remove(&client_order_id);
3693            }
3694
3695            log::info!("Purged position {position_id}");
3696        } else {
3697            log::warn!("Position {position_id} not found when purging");
3698        }
3699
3700        // Always clean up position indices (even if position not in cache)
3701        self.index.position_strategy.remove(&position_id);
3702        self.index.position_oms.remove(&position_id);
3703        self.index.position_orders.remove(&position_id);
3704        self.index.positions.remove(&position_id);
3705        self.index.positions_open.remove(&position_id);
3706        self.index.positions_closed.remove(&position_id);
3707
3708        // Always clean up position snapshots (even if position not in cache)
3709        self.position_snapshots.remove(&position_id);
3710        self.bump_position_snapshot_revision(position_id);
3711    }
3712
3713    /// Purges the instrument with the `instrument_id` from the cache (if found).
3714    ///
3715    /// All cache-owned data keyed by the instrument is removed: the instrument record,
3716    /// any synthetic with the same id, order book and own-order-book state, quote/trade
3717    /// histories, mark/index/funding price histories, instrument status, bars for any
3718    /// `BarType` referencing the instrument, and the `instrument_orders` /
3719    /// `instrument_positions` index entries.
3720    ///
3721    /// For safety, an instrument is prevented from being purged while any associated
3722    /// order is non-terminal (anything not in `orders_closed`, including
3723    /// initialized, submitted, accepted, emulated, released, or inflight states) or
3724    /// any associated position is non-closed.
3725    ///
3726    /// Active subscriptions and other live data-engine state are not touched here;
3727    /// those belong to the data and execution engines.
3728    ///
3729    /// # Warning
3730    ///
3731    /// Intended for actors and strategies that have their own lifecycle logic for
3732    /// deciding when an instrument is no longer needed. Purging an instrument that any
3733    /// other actor, strategy, or engine still relies on may cause incorrect behavior
3734    /// (missing instrument lookups, lost market-data history). The caller is
3735    /// responsible for ensuring the instrument is no longer in use before purging.
3736    fn purge_instrument_inner(&mut self, instrument_id: InstrumentId, skip_order_guard: bool) {
3737        #[cfg(feature = "defi")]
3738        let defi_found = self.defi.pools.contains_key(&instrument_id)
3739            || self.defi.pool_profilers.contains_key(&instrument_id);
3740        #[cfg(not(feature = "defi"))]
3741        let defi_found = false;
3742
3743        let found = self.instruments.contains_key(&instrument_id)
3744            || self.synthetics.contains_key(&instrument_id)
3745            || defi_found;
3746
3747        if !found {
3748            log::warn!("Instrument {instrument_id} not found when purging");
3749            return;
3750        }
3751
3752        if !skip_order_guard && let Some(orders) = self.index.instrument_orders.get(&instrument_id)
3753        {
3754            let has_non_terminal = orders
3755                .iter()
3756                .any(|client_order_id| !self.index.orders_closed.contains(client_order_id));
3757
3758            if has_non_terminal {
3759                log::warn!(
3760                    "Instrument {instrument_id} has non-terminal orders when purging, skipping purge"
3761                );
3762                return;
3763            }
3764        }
3765
3766        if let Some(positions) = self.index.instrument_positions.get(&instrument_id) {
3767            let has_non_closed = positions
3768                .iter()
3769                .any(|position_id| !self.index.positions_closed.contains(position_id));
3770
3771            if has_non_closed {
3772                log::warn!(
3773                    "Instrument {instrument_id} has non-closed positions when purging, skipping purge"
3774                );
3775                return;
3776            }
3777        }
3778
3779        self.instruments.remove(&instrument_id);
3780        self.synthetics.remove(&instrument_id);
3781        self.books.remove(&instrument_id);
3782        self.own_books.remove(&instrument_id);
3783        self.quotes.remove(&instrument_id);
3784        self.trades.remove(&instrument_id);
3785        self.mark_prices.remove(&instrument_id);
3786        self.index_prices.remove(&instrument_id);
3787        self.funding_rates.remove(&instrument_id);
3788        self.instrument_statuses.remove(&instrument_id);
3789        self.greeks.remove(&instrument_id);
3790        self.option_greeks.remove(&instrument_id);
3791
3792        self.bars
3793            .retain(|bar_type, _| bar_type.instrument_id() != instrument_id);
3794
3795        #[cfg(feature = "defi")]
3796        {
3797            self.defi.pools.remove(&instrument_id);
3798            self.defi.pool_profilers.remove(&instrument_id);
3799        }
3800
3801        self.index.instrument_orders.remove(&instrument_id);
3802        self.index.instrument_positions.remove(&instrument_id);
3803
3804        log::info!("Purged instrument {instrument_id}");
3805    }
3806
3807    /// Purges the instrument with the `instrument_id` from the cache.
3808    ///
3809    /// This refuses to purge when associated orders or positions remain in
3810    /// non-terminal state.
3811    pub fn purge_instrument(&mut self, instrument_id: InstrumentId) {
3812        self.purge_instrument_inner(instrument_id, false);
3813    }
3814
3815    /// Purges the instrument with the `instrument_id` from the cache while skipping the
3816    /// non-terminal order guard.
3817    ///
3818    /// This still refuses to purge when any associated position is non-closed. Intended
3819    /// for actors which own an instrument-expiration lifecycle and have already invalidated
3820    /// any remaining order state externally, but may still observe order-terminal events
3821    /// arriving later than the cleanup decision. During that window, the order objects may
3822    /// still exist even though `instrument_orders` is removed from the cache index.
3823    pub fn purge_instrument_skip_order_guard(&mut self, instrument_id: InstrumentId) {
3824        self.purge_instrument_inner(instrument_id, true);
3825    }
3826
3827    /// Purges all account state events which are outside the lookback window.
3828    ///
3829    /// Only events which are outside the lookback window will be purged.
3830    /// A value of 0 means purge all account state events.
3831    pub fn purge_account_events(&mut self, ts_now: UnixNanos, lookback_secs: u64) {
3832        log::debug!(
3833            "Purging account events{}",
3834            if lookback_secs > 0 {
3835                format!(" with lookback_secs={lookback_secs}")
3836            } else {
3837                String::new()
3838            }
3839        );
3840
3841        for account_cell in self.accounts.values() {
3842            let mut account = account_cell.borrow_mut();
3843            let event_count = account.event_count();
3844            account.purge_account_events(ts_now, lookback_secs);
3845            let count_diff = event_count - account.event_count();
3846            if count_diff > 0 {
3847                log::info!(
3848                    "Purged {} event(s) from account {}",
3849                    count_diff,
3850                    account.id()
3851                );
3852            }
3853        }
3854    }
3855
3856    /// Clears the caches index.
3857    pub fn clear_index(&mut self) {
3858        self.index.clear();
3859        log::debug!("Cleared index");
3860    }
3861
3862    /// Resets the cache.
3863    ///
3864    /// All stateful fields are reset to their initial value. Instruments,
3865    /// currencies, and synthetics are retained when `drop_instruments_on_reset`
3866    /// is `false` so that repeated backtest runs can reuse the same dataset.
3867    pub fn reset(&mut self) {
3868        log::debug!("Resetting cache");
3869
3870        self.general.clear();
3871        self.books.clear();
3872        self.own_books.clear();
3873        self.quotes.clear();
3874        self.trades.clear();
3875        self.mark_xrates.clear();
3876        self.mark_prices.clear();
3877        self.index_prices.clear();
3878        self.funding_rates.clear();
3879        self.instrument_statuses.clear();
3880        self.bars.clear();
3881        self.accounts.clear();
3882        self.orders.clear();
3883        self.order_lists.clear();
3884        self.positions.clear();
3885        self.position_snapshots.clear();
3886        self.position_snapshot_revisions.clear();
3887        self.greeks.clear();
3888        self.yield_curves.clear();
3889
3890        if self.config.drop_instruments_on_reset {
3891            self.currencies.clear();
3892            self.instruments.clear();
3893            self.synthetics.clear();
3894        }
3895
3896        #[cfg(feature = "defi")]
3897        {
3898            self.defi.pools.clear();
3899            self.defi.pool_profilers.clear();
3900        }
3901
3902        self.clear_index();
3903
3904        log::info!("Reset cache");
3905    }
3906
3907    /// Dispose of the cache which will close any underlying database adapter.
3908    ///
3909    /// If closing the database connection fails, an error is logged.
3910    pub fn dispose(&mut self) {
3911        self.reset();
3912
3913        if let Some(database) = &mut self.database
3914            && let Err(e) = database.close()
3915        {
3916            log::error!("Failed to close database during dispose: {e}");
3917        }
3918    }
3919
3920    /// Flushes the caches database which permanently removes all persisted data.
3921    ///
3922    /// If flushing the database connection fails, an error is logged.
3923    pub fn flush_db(&mut self) {
3924        if let Some(database) = &mut self.database
3925            && let Err(e) = database.flush()
3926        {
3927            log::error!("Failed to flush database: {e}");
3928        }
3929    }
3930
3931    /// Adds a raw bytes `value` to the cache under the `key`.
3932    ///
3933    /// The cache stores only raw bytes; interpretation is the caller's responsibility.
3934    ///
3935    /// # Errors
3936    ///
3937    /// Returns an error if persisting the entry to the backing database fails.
3938    pub fn add(&mut self, key: &str, value: Bytes) -> anyhow::Result<()> {
3939        check_valid_string_ascii(key, stringify!(key))?;
3940        check_predicate_false(value.is_empty(), stringify!(value))?;
3941
3942        log::debug!("Adding general {key}");
3943        self.general.insert(key.to_string(), value.clone());
3944
3945        if let Some(database) = &mut self.database {
3946            database.add(key.to_string(), value)?;
3947        }
3948        Ok(())
3949    }
3950
3951    /// Adds an `OrderBook` to the cache.
3952    ///
3953    /// # Errors
3954    ///
3955    /// Returns an error if persisting the order book to the backing database fails.
3956    pub fn add_order_book(&mut self, book: OrderBook) -> anyhow::Result<()> {
3957        log::debug!("Adding `OrderBook` {}", book.instrument_id);
3958
3959        if self.config.save_market_data
3960            && let Some(database) = &mut self.database
3961        {
3962            database.add_order_book(&book)?;
3963        }
3964
3965        self.books.insert(book.instrument_id, book);
3966        Ok(())
3967    }
3968
3969    /// Adds an `OwnOrderBook` to the cache.
3970    ///
3971    /// # Errors
3972    ///
3973    /// Returns an error if persisting the own order book fails.
3974    pub fn add_own_order_book(&mut self, own_book: OwnOrderBook) -> anyhow::Result<()> {
3975        log::debug!("Adding `OwnOrderBook` {}", own_book.instrument_id);
3976
3977        self.own_books.insert(own_book.instrument_id, own_book);
3978        Ok(())
3979    }
3980
3981    /// Adds the `mark_price` update to the cache.
3982    ///
3983    /// # Errors
3984    ///
3985    /// Returns an error if persisting the mark price to the backing database fails.
3986    pub fn add_mark_price(&mut self, mark_price: MarkPriceUpdate) -> anyhow::Result<()> {
3987        log::debug!("Adding `MarkPriceUpdate` for {}", mark_price.instrument_id);
3988
3989        if self.config.save_market_data {
3990            // TODO: Placeholder and return Result for consistency
3991        }
3992
3993        let mark_prices_deque = self
3994            .mark_prices
3995            .entry(mark_price.instrument_id)
3996            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
3997        mark_prices_deque.push_front(mark_price);
3998        Ok(())
3999    }
4000
4001    /// Adds the `index_price` update to the cache.
4002    ///
4003    /// # Errors
4004    ///
4005    /// Returns an error if persisting the index price to the backing database fails.
4006    pub fn add_index_price(&mut self, index_price: IndexPriceUpdate) -> anyhow::Result<()> {
4007        log::debug!(
4008            "Adding `IndexPriceUpdate` for {}",
4009            index_price.instrument_id
4010        );
4011
4012        if self.config.save_market_data {
4013            // TODO: Placeholder and return Result for consistency
4014        }
4015
4016        let index_prices_deque = self
4017            .index_prices
4018            .entry(index_price.instrument_id)
4019            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4020        index_prices_deque.push_front(index_price);
4021        Ok(())
4022    }
4023
4024    /// Adds the `funding_rate` update to the cache.
4025    ///
4026    /// # Errors
4027    ///
4028    /// Returns an error if persisting the funding rate update to the backing database fails.
4029    pub fn add_funding_rate(&mut self, funding_rate: FundingRateUpdate) -> anyhow::Result<()> {
4030        log::debug!(
4031            "Adding `FundingRateUpdate` for {}",
4032            funding_rate.instrument_id
4033        );
4034
4035        if self.config.save_market_data {
4036            // TODO: Placeholder and return Result for consistency
4037        }
4038
4039        let funding_rates_deque = self
4040            .funding_rates
4041            .entry(funding_rate.instrument_id)
4042            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4043        funding_rates_deque.push_front(funding_rate);
4044        Ok(())
4045    }
4046
4047    /// Adds the given `funding rates` to the cache.
4048    ///
4049    /// # Errors
4050    ///
4051    /// Returns an error if persisting the trade ticks to the backing database fails.
4052    pub fn add_funding_rates(&mut self, funding_rates: &[FundingRateUpdate]) -> anyhow::Result<()> {
4053        check_slice_not_empty(funding_rates, stringify!(funding_rates))?;
4054
4055        let instrument_id = funding_rates[0].instrument_id;
4056        log::debug!(
4057            "Adding `FundingRateUpdate`[{}] {instrument_id}",
4058            funding_rates.len()
4059        );
4060
4061        if self.config.save_market_data
4062            && let Some(database) = &mut self.database
4063        {
4064            for funding_rate in funding_rates {
4065                database.add_funding_rate(funding_rate)?;
4066            }
4067        }
4068
4069        let funding_rate_deque = self
4070            .funding_rates
4071            .entry(instrument_id)
4072            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4073
4074        for funding_rate in funding_rates {
4075            funding_rate_deque.push_front(*funding_rate);
4076        }
4077        Ok(())
4078    }
4079
4080    /// Adds the `instrument_status` update to the cache.
4081    ///
4082    /// # Errors
4083    ///
4084    /// Returns an error if persisting the instrument status to the backing database fails.
4085    pub fn add_instrument_status(&mut self, status: InstrumentStatus) -> anyhow::Result<()> {
4086        log::debug!("Adding `InstrumentStatus` for {}", status.instrument_id);
4087
4088        if self.config.save_market_data {
4089            // TODO: Placeholder and return Result for consistency
4090        }
4091
4092        let statuses_deque = self
4093            .instrument_statuses
4094            .entry(status.instrument_id)
4095            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4096        statuses_deque.push_front(status);
4097        Ok(())
4098    }
4099
4100    /// Adds the `quote` tick to the cache.
4101    ///
4102    /// # Errors
4103    ///
4104    /// Returns an error if persisting the quote tick to the backing database fails.
4105    pub fn add_quote(&mut self, quote: QuoteTick) -> anyhow::Result<()> {
4106        log::debug!("Adding `QuoteTick` {}", quote.instrument_id);
4107
4108        if self.config.save_market_data
4109            && let Some(database) = &mut self.database
4110        {
4111            database.add_quote(&quote)?;
4112        }
4113
4114        let quotes_deque = self
4115            .quotes
4116            .entry(quote.instrument_id)
4117            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4118        quotes_deque.push_front(quote);
4119        Ok(())
4120    }
4121
4122    /// Adds the `quotes` to the cache.
4123    ///
4124    /// # Errors
4125    ///
4126    /// Returns an error if persisting the quote ticks to the backing database fails.
4127    pub fn add_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
4128        check_slice_not_empty(quotes, stringify!(quotes))?;
4129
4130        let instrument_id = quotes[0].instrument_id;
4131        log::debug!("Adding `QuoteTick`[{}] {instrument_id}", quotes.len());
4132
4133        if self.config.save_market_data
4134            && let Some(database) = &mut self.database
4135        {
4136            for quote in quotes {
4137                database.add_quote(quote)?;
4138            }
4139        }
4140
4141        let quotes_deque = self
4142            .quotes
4143            .entry(instrument_id)
4144            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4145
4146        for quote in quotes {
4147            quotes_deque.push_front(*quote);
4148        }
4149        Ok(())
4150    }
4151
4152    /// Adds the `trade` tick to the cache.
4153    ///
4154    /// # Errors
4155    ///
4156    /// Returns an error if persisting the trade tick to the backing database fails.
4157    pub fn add_trade(&mut self, trade: TradeTick) -> anyhow::Result<()> {
4158        log::debug!("Adding `TradeTick` {}", trade.instrument_id);
4159
4160        if self.config.save_market_data
4161            && let Some(database) = &mut self.database
4162        {
4163            database.add_trade(&trade)?;
4164        }
4165
4166        let trades_deque = self
4167            .trades
4168            .entry(trade.instrument_id)
4169            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4170        trades_deque.push_front(trade);
4171        Ok(())
4172    }
4173
4174    /// Adds the give `trades` to the cache.
4175    ///
4176    /// # Errors
4177    ///
4178    /// Returns an error if persisting the trade ticks to the backing database fails.
4179    pub fn add_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
4180        check_slice_not_empty(trades, stringify!(trades))?;
4181
4182        let instrument_id = trades[0].instrument_id;
4183        log::debug!("Adding `TradeTick`[{}] {instrument_id}", trades.len());
4184
4185        if self.config.save_market_data
4186            && let Some(database) = &mut self.database
4187        {
4188            for trade in trades {
4189                database.add_trade(trade)?;
4190            }
4191        }
4192
4193        let trades_deque = self
4194            .trades
4195            .entry(instrument_id)
4196            .or_insert_with(|| BoundedVecDeque::new(self.config.tick_capacity));
4197
4198        for trade in trades {
4199            trades_deque.push_front(*trade);
4200        }
4201        Ok(())
4202    }
4203
4204    /// Adds the `bar` to the cache.
4205    ///
4206    /// # Errors
4207    ///
4208    /// Returns an error if persisting the bar to the backing database fails.
4209    pub fn add_bar(&mut self, bar: Bar) -> anyhow::Result<()> {
4210        log::debug!("Adding `Bar` {}", bar.bar_type);
4211
4212        if self.config.save_market_data
4213            && let Some(database) = &mut self.database
4214        {
4215            database.add_bar(&bar)?;
4216        }
4217
4218        let bars = self
4219            .bars
4220            .entry(bar.bar_type)
4221            .or_insert_with(|| BoundedVecDeque::new(self.config.bar_capacity));
4222        bars.push_front(bar);
4223        Ok(())
4224    }
4225
4226    /// Adds the `bars` to the cache.
4227    ///
4228    /// # Errors
4229    ///
4230    /// Returns an error if persisting the bars to the backing database fails.
4231    pub fn add_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
4232        check_slice_not_empty(bars, stringify!(bars))?;
4233
4234        let bar_type = bars[0].bar_type;
4235        log::debug!("Adding `Bar`[{}] {bar_type}", bars.len());
4236
4237        if self.config.save_market_data
4238            && let Some(database) = &mut self.database
4239        {
4240            for bar in bars {
4241                database.add_bar(bar)?;
4242            }
4243        }
4244
4245        let bars_deque = self
4246            .bars
4247            .entry(bar_type)
4248            .or_insert_with(|| BoundedVecDeque::new(self.config.bar_capacity));
4249
4250        for bar in bars {
4251            bars_deque.push_front(*bar);
4252        }
4253        Ok(())
4254    }
4255
4256    /// Adds the `greeks` data to the cache.
4257    ///
4258    /// # Errors
4259    ///
4260    /// Returns an error if persisting the greeks data to the backing database fails.
4261    pub fn add_greeks(&mut self, greeks: GreeksData) -> anyhow::Result<()> {
4262        log::debug!("Adding `GreeksData` {}", greeks.instrument_id);
4263
4264        if self.config.save_market_data
4265            && let Some(_database) = &mut self.database
4266        {
4267            // TODO: Implement database.add_greeks(&greeks) when database adapter is updated
4268        }
4269
4270        self.greeks.insert(greeks.instrument_id, greeks);
4271        Ok(())
4272    }
4273
4274    /// Gets the greeks data for the `instrument_id`.
4275    pub fn greeks(&self, instrument_id: &InstrumentId) -> Option<GreeksData> {
4276        self.greeks.get(instrument_id).cloned()
4277    }
4278
4279    /// Adds exchange-provided option greeks to the cache.
4280    pub fn add_option_greeks(&mut self, greeks: OptionGreeks) {
4281        log::debug!("Adding `OptionGreeks` {}", greeks.instrument_id);
4282        self.option_greeks.insert(greeks.instrument_id, greeks);
4283    }
4284
4285    /// Gets a reference to the exchange-provided option greeks for the `instrument_id`.
4286    #[must_use]
4287    pub fn option_greeks(&self, instrument_id: &InstrumentId) -> Option<&OptionGreeks> {
4288        self.option_greeks.get(instrument_id)
4289    }
4290
4291    /// Adds the `yield_curve` data to the cache.
4292    ///
4293    /// # Errors
4294    ///
4295    /// Returns an error if persisting the yield curve data to the backing database fails.
4296    pub fn add_yield_curve(&mut self, yield_curve: YieldCurveData) -> anyhow::Result<()> {
4297        log::debug!("Adding `YieldCurveData` {}", yield_curve.curve_name);
4298
4299        if self.config.save_market_data
4300            && let Some(_database) = &mut self.database
4301        {
4302            // TODO: Implement database.add_yield_curve(&yield_curve) when database adapter is updated
4303        }
4304
4305        self.yield_curves
4306            .insert(yield_curve.curve_name.clone(), yield_curve);
4307        Ok(())
4308    }
4309
4310    /// Gets the yield curve for the `key`.
4311    pub fn yield_curve(&self, key: &str) -> Option<Box<dyn Fn(f64) -> f64>> {
4312        self.yield_curves.get(key).map(|curve| {
4313            let curve_clone = curve.clone();
4314            Box::new(move |expiry_in_years: f64| curve_clone.get_rate(expiry_in_years))
4315                as Box<dyn Fn(f64) -> f64>
4316        })
4317    }
4318
4319    /// Adds the `currency` to the cache.
4320    ///
4321    /// # Errors
4322    ///
4323    /// Returns an error if persisting the currency to the backing database fails.
4324    pub fn add_currency(&mut self, currency: Currency) -> anyhow::Result<()> {
4325        if self.currencies.contains_key(&currency.code) {
4326            return Ok(());
4327        }
4328        log::debug!("Adding `Currency` {}", currency.code);
4329
4330        if let Some(database) = &mut self.database {
4331            database.add_currency(&currency)?;
4332        }
4333
4334        self.currencies.insert(currency.code, currency);
4335        Ok(())
4336    }
4337
4338    /// Adds the `instrument` to the cache.
4339    ///
4340    /// # Errors
4341    ///
4342    /// Returns an error if persisting the instrument to the backing database fails.
4343    pub fn add_instrument(&mut self, instrument: InstrumentAny) -> anyhow::Result<()> {
4344        log::debug!("Adding `Instrument` {}", instrument.id());
4345
4346        // Ensure currencies exist in cache - safe to call repeatedly as add_currency is idempotent
4347        if let Some(base_currency) = instrument.base_currency() {
4348            self.add_currency(base_currency)?;
4349        }
4350        self.add_currency(instrument.quote_currency())?;
4351        self.add_currency(instrument.settlement_currency())?;
4352
4353        if let Some(database) = &mut self.database {
4354            database.add_instrument(&instrument)?;
4355        }
4356
4357        self.instruments.insert(instrument.id(), instrument);
4358        Ok(())
4359    }
4360
4361    /// Adds the `synthetic` instrument to the cache.
4362    ///
4363    /// # Errors
4364    ///
4365    /// Returns an error if persisting the synthetic instrument to the backing database fails.
4366    pub fn add_synthetic(&mut self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
4367        log::debug!("Adding `SyntheticInstrument` {}", synthetic.id);
4368
4369        if let Some(database) = &mut self.database {
4370            database.add_synthetic(&synthetic)?;
4371        }
4372
4373        self.synthetics.insert(synthetic.id, synthetic);
4374        Ok(())
4375    }
4376
4377    /// Adds the `account` to the cache.
4378    ///
4379    /// # Errors
4380    ///
4381    /// Returns an error if persisting the account to the backing database fails.
4382    pub fn add_account(&mut self, account: AccountAny) -> anyhow::Result<()> {
4383        log::debug!("Adding `Account` {}", account.id());
4384
4385        if let Some(database) = &mut self.database {
4386            database.add_account(&account)?;
4387        }
4388
4389        let account_id = account.id();
4390        self.accounts.insert(account_id, SharedCell::new(account));
4391        self.index
4392            .venue_account
4393            .insert(account_id.get_issuer(), account_id);
4394        Ok(())
4395    }
4396
4397    /// Indexes the `client_order_id` with the `venue_order_id`.
4398    ///
4399    /// The `overwrite` parameter determines whether to overwrite any existing cached identifier.
4400    ///
4401    /// # Errors
4402    ///
4403    /// Returns an error if the client already has a different venue order ID and `overwrite` is
4404    /// false, or if the venue order ID is owned by a different client order.
4405    pub fn add_venue_order_id(
4406        &mut self,
4407        client_order_id: &ClientOrderId,
4408        venue_order_id: &VenueOrderId,
4409        overwrite: bool,
4410    ) -> anyhow::Result<()> {
4411        self.validate_venue_order_id_claim(client_order_id, venue_order_id, overwrite)?;
4412
4413        self.index
4414            .client_order_ids
4415            .insert(*client_order_id, *venue_order_id);
4416        self.index
4417            .venue_order_ids
4418            .insert(*venue_order_id, *client_order_id);
4419
4420        Ok(())
4421    }
4422
4423    fn validate_venue_order_id_claim(
4424        &self,
4425        client_order_id: &ClientOrderId,
4426        venue_order_id: &VenueOrderId,
4427        overwrite: bool,
4428    ) -> anyhow::Result<()> {
4429        self.validate_venue_order_id_ownership(client_order_id, venue_order_id)?;
4430
4431        if let Some(existing_venue_order_id) = self.index.client_order_ids.get(client_order_id)
4432            && !overwrite
4433            && existing_venue_order_id != venue_order_id
4434        {
4435            anyhow::bail!(
4436                "Existing {existing_venue_order_id} for {client_order_id}
4437                    did not match the given {venue_order_id}.
4438                    If you are writing a test then try a different `venue_order_id`,
4439                    otherwise this is probably a bug."
4440            );
4441        }
4442
4443        Ok(())
4444    }
4445
4446    fn validate_venue_order_id_ownership(
4447        &self,
4448        client_order_id: &ClientOrderId,
4449        venue_order_id: &VenueOrderId,
4450    ) -> anyhow::Result<()> {
4451        if let Some(existing_client_order_id) = self.index.venue_order_ids.get(venue_order_id)
4452            && existing_client_order_id != client_order_id
4453        {
4454            return Err(VenueOrderIdOwnershipError {
4455                venue_order_id: *venue_order_id,
4456                existing_client_order_id: *existing_client_order_id,
4457                claimant_client_order_id: *client_order_id,
4458            }
4459            .into());
4460        }
4461
4462        Ok(())
4463    }
4464
4465    /// Adds the `order` to the cache indexed with any given identifiers.
4466    ///
4467    /// # Parameters
4468    ///
4469    /// `override_existing`: If the added order should 'override' any existing order and replace
4470    /// it in the cache. This is currently used for emulated orders which are
4471    /// being released and transformed into another type.
4472    ///
4473    /// # Errors
4474    ///
4475    /// Returns an error if not `replace_existing` and the `order.client_order_id` is already contained in the cache.
4476    pub fn add_order(
4477        &mut self,
4478        order: OrderAny,
4479        position_id: Option<PositionId>,
4480        client_id: Option<ClientId>,
4481        replace_existing: bool,
4482    ) -> anyhow::Result<()> {
4483        let instrument_id = order.instrument_id();
4484        let venue = instrument_id.venue;
4485        let client_order_id = order.client_order_id();
4486        let strategy_id = order.strategy_id();
4487        let exec_algorithm_id = order.exec_algorithm_id();
4488        let exec_spawn_id = order.exec_spawn_id();
4489
4490        if !replace_existing {
4491            check_key_not_in_map(
4492                &client_order_id,
4493                &self.orders,
4494                stringify!(client_order_id),
4495                stringify!(orders),
4496            )?;
4497        }
4498
4499        log::debug!("Adding {order:?}");
4500
4501        self.index.orders.insert(client_order_id);
4502
4503        if order.is_active_local() {
4504            self.index.orders_active_local.insert(client_order_id);
4505        }
4506        self.index
4507            .order_strategy
4508            .insert(client_order_id, strategy_id);
4509        self.index.strategies.insert(strategy_id);
4510
4511        // Update venue -> orders index
4512        self.index
4513            .venue_orders
4514            .entry(venue)
4515            .or_default()
4516            .insert(client_order_id);
4517
4518        // Update instrument -> orders index
4519        self.index
4520            .instrument_orders
4521            .entry(instrument_id)
4522            .or_default()
4523            .insert(client_order_id);
4524
4525        // Update strategy -> orders index
4526        self.index
4527            .strategy_orders
4528            .entry(strategy_id)
4529            .or_default()
4530            .insert(client_order_id);
4531
4532        // Update account -> orders index (if account_id known at creation)
4533        if let Some(account_id) = order.account_id() {
4534            self.index
4535                .account_orders
4536                .entry(account_id)
4537                .or_default()
4538                .insert(client_order_id);
4539        }
4540
4541        // Update exec_algorithm -> orders index
4542        if let Some(exec_algorithm_id) = exec_algorithm_id {
4543            self.index.exec_algorithms.insert(exec_algorithm_id);
4544
4545            self.index
4546                .exec_algorithm_orders
4547                .entry(exec_algorithm_id)
4548                .or_default()
4549                .insert(client_order_id);
4550        }
4551
4552        // Update exec_spawn -> orders index
4553        if let Some(exec_spawn_id) = exec_spawn_id {
4554            self.index
4555                .exec_spawn_orders
4556                .entry(exec_spawn_id)
4557                .or_default()
4558                .insert(client_order_id);
4559        }
4560
4561        // Update emulation index
4562        if let Some(emulation_trigger) = order.emulation_trigger()
4563            && emulation_trigger != TriggerType::NoTrigger
4564        {
4565            self.index.orders_emulated.insert(client_order_id);
4566        }
4567
4568        // Index position ID if provided
4569        if let Some(position_id) = position_id {
4570            self.add_position_id(
4571                &position_id,
4572                &order.instrument_id().venue,
4573                &client_order_id,
4574                &strategy_id,
4575            )?;
4576        }
4577
4578        // Index client ID if provided
4579        if let Some(client_id) = client_id {
4580            self.index.order_client.insert(client_order_id, client_id);
4581            log::debug!("Indexed {client_id:?}");
4582        }
4583
4584        if let Some(database) = &mut self.database {
4585            database.add_order(&order, client_id)?;
4586            // TODO: Implement
4587            // if self.config.snapshot_orders {
4588            //     database.snapshot_order_state(order)?;
4589            // }
4590        }
4591
4592        match self.orders.get(&client_order_id) {
4593            // Reuse the existing cell on replace so the canonical entry stays in place
4594            // rather than orphaning a stale cell.
4595            Some(order_cell) => *order_cell.borrow_mut() = order,
4596            None => {
4597                self.orders.insert(client_order_id, SharedCell::new(order));
4598            }
4599        }
4600
4601        Ok(())
4602    }
4603
4604    /// Adds the `order_list` to the cache.
4605    ///
4606    /// # Errors
4607    ///
4608    /// Returns an error if the order list ID is already contained in the cache.
4609    pub fn add_order_list(&mut self, order_list: OrderList) -> anyhow::Result<()> {
4610        let order_list_id = order_list.id;
4611        check_key_not_in_map(
4612            &order_list_id,
4613            &self.order_lists,
4614            stringify!(order_list_id),
4615            stringify!(order_lists),
4616        )?;
4617
4618        log::debug!("Adding {order_list:?}");
4619        self.order_lists.insert(order_list_id, order_list);
4620        Ok(())
4621    }
4622
4623    /// Indexes the `position_id` with the other given IDs.
4624    ///
4625    /// # Errors
4626    ///
4627    /// Returns an error if indexing position ID in the backing database fails.
4628    pub fn add_position_id(
4629        &mut self,
4630        position_id: &PositionId,
4631        venue: &Venue,
4632        client_order_id: &ClientOrderId,
4633        strategy_id: &StrategyId,
4634    ) -> anyhow::Result<()> {
4635        self.index
4636            .order_position
4637            .insert(*client_order_id, *position_id);
4638
4639        // Index: ClientOrderId -> PositionId
4640        if let Some(database) = &mut self.database {
4641            database.index_order_position(*client_order_id, *position_id)?;
4642        }
4643
4644        // Index: PositionId -> StrategyId
4645        self.index
4646            .position_strategy
4647            .insert(*position_id, *strategy_id);
4648
4649        // Index: PositionId -> set[ClientOrderId]
4650        self.index
4651            .position_orders
4652            .entry(*position_id)
4653            .or_default()
4654            .insert(*client_order_id);
4655
4656        // Index: StrategyId -> set[PositionId]
4657        self.index
4658            .strategy_positions
4659            .entry(*strategy_id)
4660            .or_default()
4661            .insert(*position_id);
4662
4663        // Index: Venue -> set[PositionId]
4664        self.index
4665            .venue_positions
4666            .entry(*venue)
4667            .or_default()
4668            .insert(*position_id);
4669
4670        Ok(())
4671    }
4672
4673    // Propagates parent OTO `position_id` to contingent children that are missing one.
4674    //
4675    // Recovers from a partial-write window during fill handling: the fill-time path in the
4676    // execution engine assigns `position_id` to each contingent child in a non-atomic loop
4677    // (`set_position_id` then `add_position_id`), so a crash mid-loop can leave the database
4678    // with the parent updated and some children un-updated. This pass re-applies any missing
4679    // assignments after load. Mirrors the Cython behaviour at
4680    // `nautilus_trader/cache/cache.pyx::_assign_position_id_to_contingencies`.
4681    fn assign_position_ids_to_contingencies(&mut self) {
4682        let mut assignments: Vec<(PositionId, ClientOrderId)> = Vec::new();
4683
4684        for parent_order_cell in self.orders.values() {
4685            let parent = parent_order_cell.borrow();
4686            if parent.contingency_type() != Some(ContingencyType::Oto) {
4687                continue;
4688            }
4689            let Some(parent_position_id) = parent.position_id() else {
4690                continue;
4691            };
4692            let Some(linked_order_ids) = parent.linked_order_ids() else {
4693                continue;
4694            };
4695
4696            for client_order_id in linked_order_ids {
4697                match self.orders.get(client_order_id) {
4698                    None => {
4699                        log::error!("Contingency order {client_order_id} not found");
4700                    }
4701                    Some(contingent_order_cell) => {
4702                        if contingent_order_cell.borrow().position_id().is_none() {
4703                            assignments.push((parent_position_id, *client_order_id));
4704                        }
4705                    }
4706                }
4707            }
4708        }
4709
4710        for (position_id, client_order_id) in assignments {
4711            let Some((venue, strategy_id)) = self.orders.get(&client_order_id).map(|order_cell| {
4712                let mut contingent = order_cell.borrow_mut();
4713                contingent.set_position_id(Some(position_id));
4714                (contingent.instrument_id().venue, contingent.strategy_id())
4715            }) else {
4716                continue;
4717            };
4718
4719            // Re-indexing through `add_position_id` also replays the database write, making the
4720            // recovered assignment durable across another restart.
4721            if let Err(e) =
4722                self.add_position_id(&position_id, &venue, &client_order_id, &strategy_id)
4723            {
4724                log::error!("Failed to re-index {client_order_id} -> {position_id}: {e}");
4725            }
4726        }
4727    }
4728
4729    /// Adds the `position` to the cache.
4730    ///
4731    /// # Errors
4732    ///
4733    /// Returns an error if persisting the position to the backing database fails.
4734    pub fn add_position(&mut self, position: &Position, oms_type: OmsType) -> anyhow::Result<()> {
4735        self.positions
4736            .insert(position.id, SharedCell::new(position.clone()));
4737        self.index.position_oms.insert(position.id, oms_type);
4738        self.index.positions.insert(position.id);
4739        self.index.positions_open.insert(position.id);
4740        self.index.positions_closed.remove(&position.id); // Cleanup for NETTING reopen
4741
4742        log::debug!("Adding {position}");
4743
4744        self.add_position_id(
4745            &position.id,
4746            &position.instrument_id.venue,
4747            &position.opening_order_id,
4748            &position.strategy_id,
4749        )?;
4750
4751        let venue = position.instrument_id.venue;
4752        let venue_positions = self.index.venue_positions.entry(venue).or_default();
4753        venue_positions.insert(position.id);
4754
4755        // Index: InstrumentId -> AHashSet
4756        let instrument_id = position.instrument_id;
4757        let instrument_positions = self
4758            .index
4759            .instrument_positions
4760            .entry(instrument_id)
4761            .or_default();
4762        instrument_positions.insert(position.id);
4763
4764        // Index: AccountId -> AHashSet<PositionId>
4765        self.index
4766            .account_positions
4767            .entry(position.account_id)
4768            .or_default()
4769            .insert(position.id);
4770
4771        if let Some(database) = &mut self.database {
4772            database.add_position(position)?;
4773            // TODO: Implement position snapshots
4774            // if self.snapshot_positions {
4775            //     database.snapshot_position_state(
4776            //         position,
4777            //         position.ts_last,
4778            //         self.calculate_unrealized_pnl(&position),
4779            //     )?;
4780            // }
4781        }
4782
4783        let key = position_oms_key(position.id);
4784        let value = Bytes::from(serde_json::to_vec(&oms_type)?);
4785        self.add(&key, value)?;
4786
4787        Ok(())
4788    }
4789
4790    /// Updates the `account` in the cache.
4791    ///
4792    /// Reuses the existing cell when present so any held [`AccountRef`] handles continue to point
4793    /// at the canonical entry; only inserts a new cell when the account is unknown.
4794    ///
4795    /// # Errors
4796    ///
4797    /// Returns an error if updating the account in the database fails.
4798    pub fn update_account(&mut self, account: &AccountAny) -> anyhow::Result<()> {
4799        let account_id = account.id();
4800        match self.accounts.get(&account_id) {
4801            Some(account_cell) => *account_cell.borrow_mut() = account.clone(),
4802            None => {
4803                self.accounts
4804                    .insert(account_id, SharedCell::new(account.clone()));
4805            }
4806        }
4807
4808        if let Some(database) = &mut self.database {
4809            database.update_account(account)?;
4810        }
4811        Ok(())
4812    }
4813
4814    /// Removes the `account` from the cache and returns it.
4815    ///
4816    /// This supports hot paths which need owned account mutation without
4817    /// cloning the account event history. The cache is the sole owner of the
4818    /// account cell (the field is private and accessors only hand out
4819    /// lifetime-scoped [`AccountRef`] borrows), so the value is moved out of
4820    /// its cell rather than cloned.
4821    ///
4822    /// # Panics
4823    ///
4824    /// Panics if the cache no longer holds the only strong handle to the
4825    /// account cell. This indicates an internal invariant violation: some
4826    /// component cloned the underlying [`SharedCell`] and held it past the
4827    /// scope of a single cache method.
4828    #[must_use]
4829    pub fn take_account(&mut self, account_id: &AccountId) -> Option<AccountAny> {
4830        self.accounts.remove(account_id).map(|cell| {
4831            let rc: Rc<RefCell<AccountAny>> = cell.into();
4832            Rc::try_unwrap(rc).map_or_else(
4833                |_| panic!("take_account: cache must be sole owner of {account_id} cell"),
4834                RefCell::into_inner,
4835            )
4836        })
4837    }
4838
4839    /// Caches the `account` in memory without updating the database.
4840    pub fn cache_account_owned(&mut self, account: AccountAny) {
4841        let account_id = account.id();
4842        self.index
4843            .venue_account
4844            .insert(account_id.get_issuer(), account_id);
4845        match self.accounts.get(&account_id) {
4846            Some(account_cell) => *account_cell.borrow_mut() = account,
4847            None => {
4848                self.accounts.insert(account_id, SharedCell::new(account));
4849            }
4850        }
4851    }
4852
4853    /// Updates the `account` in the cache, taking ownership of the updated account.
4854    ///
4855    /// # Errors
4856    ///
4857    /// Returns an error if updating the account in the database fails.
4858    pub fn update_account_owned(&mut self, account: AccountAny) -> anyhow::Result<()> {
4859        let account_id = account.id();
4860        self.cache_account_owned(account);
4861
4862        if let Some(database) = &mut self.database {
4863            let Some(account_cell) = self.accounts.get(&account_id) else {
4864                anyhow::bail!("Account {account_id} not found after cache update");
4865            };
4866            database.update_account(&account_cell.borrow())?;
4867        }
4868        Ok(())
4869    }
4870
4871    /// Applies an account state event to the cached account.
4872    ///
4873    /// Mutates the cached account in place to avoid cloning the account event
4874    /// history on the hot path; long-running sessions accumulate many events
4875    /// per account, so a snapshot-clone here would be O(history) per update.
4876    ///
4877    /// # Errors
4878    ///
4879    /// Returns an error if applying or persisting the account state fails.
4880    pub fn update_account_state(&mut self, event: &AccountState) -> anyhow::Result<()> {
4881        let Some(cell) = self.accounts.get(&event.account_id) else {
4882            return self.add_account(AccountAny::from_events(std::slice::from_ref(event))?);
4883        };
4884
4885        cell.borrow_mut().apply(event.clone())?;
4886
4887        if let Some(database) = &mut self.database {
4888            database.update_account(&cell.borrow())?;
4889        }
4890        Ok(())
4891    }
4892
4893    /// Replaces the cached `order` from a non-event snapshot.
4894    ///
4895    /// Prefer [`Self::update_order`] for lifecycle state changes. Use this only for order state
4896    /// that is not represented by [`OrderEventAny`].
4897    ///
4898    /// # Errors
4899    ///
4900    /// Returns an error if updating the order indexes or database fails.
4901    pub fn replace_order(&mut self, order: &OrderAny) -> anyhow::Result<()> {
4902        self.refresh_order(order)?;
4903
4904        let client_order_id = order.client_order_id();
4905        match self.orders.get(&client_order_id) {
4906            // Reuse the existing cell so the canonical entry stays in place rather than
4907            // orphaning a stale cell.
4908            Some(order_cell) => *order_cell.borrow_mut() = order.clone(),
4909            None => {
4910                self.orders
4911                    .insert(client_order_id, SharedCell::new(order.clone()));
4912            }
4913        }
4914
4915        Ok(())
4916    }
4917
4918    /// Updates the cached order by applying an event and refreshing derived cache state.
4919    ///
4920    /// # Errors
4921    ///
4922    /// Returns an error if the order is not found or rejects the event.
4923    pub fn update_order(&mut self, event: &OrderEventAny) -> anyhow::Result<OrderAny> {
4924        let event_client_order_id = event.client_order_id();
4925        let client_order_id = if self.order_exists(&event_client_order_id) {
4926            event_client_order_id
4927        } else if let Some(venue_order_id) = event.venue_order_id() {
4928            self.index
4929                .venue_order_ids
4930                .get(&venue_order_id)
4931                .copied()
4932                .ok_or(OrderError::NotFound(event_client_order_id))?
4933        } else {
4934            return Err(OrderError::NotFound(event_client_order_id).into());
4935        };
4936
4937        let order_cell = self
4938            .orders
4939            .get(&client_order_id)
4940            .cloned()
4941            .ok_or(OrderError::NotFound(client_order_id))?;
4942
4943        // Apply on a snapshot first so a fallible `apply` (e.g. invalid state
4944        // transition) leaves the canonical cell untouched. On success we swap the
4945        // post-event value back into the cell so subsequent reads see the new state.
4946        let mut snapshot = order_cell.borrow().clone();
4947        snapshot.apply(event.clone())?;
4948
4949        // Preflight only reverse ownership. A same-client forward mismatch remains a logged
4950        // refresh inconsistency, while other refresh failures, such as a backing database error,
4951        // remain logged after the canonical state is committed.
4952        if let Some(venue_order_id) = snapshot.venue_order_id() {
4953            self.validate_venue_order_id_ownership(&client_order_id, &venue_order_id)?;
4954        }
4955
4956        *order_cell.borrow_mut() = snapshot.clone();
4957
4958        if let Err(e) = self.refresh_order(&snapshot) {
4959            log::error!("Error updating order in cache: {e}");
4960        }
4961
4962        Ok(snapshot)
4963    }
4964
4965    fn refresh_order(&mut self, order: &OrderAny) -> anyhow::Result<()> {
4966        let client_order_id = order.client_order_id();
4967
4968        // Claim the venue order ID before mutating any other derived state. An updated event may
4969        // change the current ID for the same client order, while historical reverse aliases remain.
4970        if let Some(venue_order_id) = order.venue_order_id() {
4971            let overwrite = matches!(order.last_event(), OrderEventAny::Updated(_));
4972            if let Err(e) = self.add_venue_order_id(&client_order_id, &venue_order_id, overwrite) {
4973                if e.is::<VenueOrderIdOwnershipError>() {
4974                    return Err(e);
4975                }
4976                log::error!("Error indexing venue order ID in cache: {e}");
4977            }
4978        }
4979
4980        if order.is_active_local() {
4981            self.index.orders_active_local.insert(client_order_id);
4982        } else {
4983            self.index.orders_active_local.remove(&client_order_id);
4984        }
4985
4986        // Update in-flight state
4987        if order.is_inflight() {
4988            self.index.orders_inflight.insert(client_order_id);
4989        } else {
4990            self.index.orders_inflight.remove(&client_order_id);
4991        }
4992
4993        // Update open/closed state
4994        if order.is_open() {
4995            self.index.orders_closed.remove(&client_order_id);
4996            self.index.orders_open.insert(client_order_id);
4997        } else if order.is_closed() {
4998            self.index.orders_open.remove(&client_order_id);
4999            self.index.orders_pending_cancel.remove(&client_order_id);
5000            self.index.orders_closed.insert(client_order_id);
5001        }
5002
5003        // A cancel rejection resolves the outstanding cancel request
5004        if matches!(order.last_event(), OrderEventAny::CancelRejected(_)) {
5005            self.index.orders_pending_cancel.remove(&client_order_id);
5006        }
5007
5008        // Update emulation index
5009        if let Some(emulation_trigger) = order.emulation_trigger()
5010            && emulation_trigger != TriggerType::NoTrigger
5011            && !order.is_closed()
5012        {
5013            self.index.orders_emulated.insert(client_order_id);
5014        } else {
5015            self.index.orders_emulated.remove(&client_order_id);
5016        }
5017
5018        // Update account orders index when account_id becomes available
5019        if let Some(account_id) = order.account_id() {
5020            self.index
5021                .account_orders
5022                .entry(account_id)
5023                .or_default()
5024                .insert(client_order_id);
5025        }
5026
5027        // Update own book
5028        if !self.own_books.is_empty() {
5029            let own_book = self.own_order_book(&order.instrument_id());
5030            if (own_book.is_some() && order.is_closed()) || should_handle_own_book_order(order) {
5031                self.update_own_order_book(order);
5032            }
5033        }
5034
5035        if let Some(database) = &mut self.database {
5036            database.update_order(order.last_event())?;
5037            // TODO: Implement order snapshots
5038            // if self.snapshot_orders {
5039            //     database.snapshot_order_state(order)?;
5040            // }
5041        }
5042
5043        Ok(())
5044    }
5045
5046    /// Updates the `order` as pending cancel locally.
5047    pub fn update_order_pending_cancel_local(&mut self, order: &OrderAny) {
5048        self.index
5049            .orders_pending_cancel
5050            .insert(order.client_order_id());
5051    }
5052
5053    /// Updates the `position` in the cache.
5054    ///
5055    /// Reuses the existing cell when present so any held [`PositionRef`] handles continue to point
5056    /// at the canonical entry; only inserts a new cell when the position is unknown.
5057    ///
5058    /// # Errors
5059    ///
5060    /// Returns an error if updating the position in the database fails.
5061    pub fn update_position(&mut self, position: &Position) -> anyhow::Result<()> {
5062        // Update open/closed state
5063
5064        if position.is_open() {
5065            self.index.positions_open.insert(position.id);
5066            self.index.positions_closed.remove(&position.id);
5067        } else {
5068            self.index.positions_closed.insert(position.id);
5069            self.index.positions_open.remove(&position.id);
5070        }
5071
5072        if let Some(database) = &mut self.database {
5073            database.update_position(position)?;
5074            // TODO: Implement order snapshots
5075            // if self.snapshot_orders {
5076            //     database.snapshot_order_state(order)?;
5077            // }
5078        }
5079
5080        match self.positions.get(&position.id) {
5081            Some(position_cell) => *position_cell.borrow_mut() = position.clone(),
5082            None => {
5083                self.positions
5084                    .insert(position.id, SharedCell::new(position.clone()));
5085            }
5086        }
5087
5088        Ok(())
5089    }
5090
5091    /// Gets the OMS type for the `position_id`.
5092    #[must_use]
5093    pub fn oms_type(&self, position_id: &PositionId) -> Option<OmsType> {
5094        self.index.position_oms.get(position_id).copied()
5095    }
5096
5097    /// Snapshots the `order` state in the database.
5098    ///
5099    /// # Errors
5100    ///
5101    /// Returns an error if snapshotting the order state fails.
5102    pub fn snapshot_order_state(&self, order: &OrderAny) -> anyhow::Result<()> {
5103        let Some(database) = &self.database else {
5104            log::warn!(
5105                "Cannot snapshot order state for {} (no database configured)",
5106                order.client_order_id()
5107            );
5108            return Ok(());
5109        };
5110
5111        database.snapshot_order_state(order)
5112    }
5113
5114    // -- IDENTIFIER QUERIES ----------------------------------------------------------------------
5115
5116    // Collects references to the index sets that constrain an order query.
5117    //
5118    // Returns:
5119    // - `FilterSources::Unfiltered` when no filter is provided (the caller should iterate
5120    //   the full bucket).
5121    // - `FilterSources::Empty` when a filter is provided but the index has no entry for it
5122    //   (the resolved set is unconditionally empty, no further work needed).
5123    // - `FilterSources::Sets` with borrowed references to each filter source set.
5124    fn collect_order_filter_sources<'a>(
5125        &'a self,
5126        venue: Option<&Venue>,
5127        instrument_id: Option<&InstrumentId>,
5128        strategy_id: Option<&StrategyId>,
5129        account_id: Option<&AccountId>,
5130    ) -> FilterSources<'a, ClientOrderId> {
5131        let mut sources: Vec<&AHashSet<ClientOrderId>> = Vec::with_capacity(4);
5132
5133        if let Some(venue) = venue {
5134            match self.index.venue_orders.get(venue) {
5135                Some(set) => sources.push(set),
5136                None => return FilterSources::Empty,
5137            }
5138        }
5139
5140        if let Some(instrument_id) = instrument_id {
5141            match self.index.instrument_orders.get(instrument_id) {
5142                Some(set) => sources.push(set),
5143                None => return FilterSources::Empty,
5144            }
5145        }
5146
5147        if let Some(strategy_id) = strategy_id {
5148            match self.index.strategy_orders.get(strategy_id) {
5149                Some(set) => sources.push(set),
5150                None => return FilterSources::Empty,
5151            }
5152        }
5153
5154        if let Some(account_id) = account_id {
5155            match self.index.account_orders.get(account_id) {
5156                Some(set) => sources.push(set),
5157                None => return FilterSources::Empty,
5158            }
5159        }
5160
5161        if sources.is_empty() {
5162            FilterSources::Unfiltered
5163        } else {
5164            FilterSources::Sets(sources)
5165        }
5166    }
5167
5168    fn collect_position_filter_sources<'a>(
5169        &'a self,
5170        venue: Option<&Venue>,
5171        instrument_id: Option<&InstrumentId>,
5172        strategy_id: Option<&StrategyId>,
5173        account_id: Option<&AccountId>,
5174    ) -> FilterSources<'a, PositionId> {
5175        let mut sources: Vec<&AHashSet<PositionId>> = Vec::with_capacity(4);
5176
5177        if let Some(venue) = venue {
5178            match self.index.venue_positions.get(venue) {
5179                Some(set) => sources.push(set),
5180                None => return FilterSources::Empty,
5181            }
5182        }
5183
5184        if let Some(instrument_id) = instrument_id {
5185            match self.index.instrument_positions.get(instrument_id) {
5186                Some(set) => sources.push(set),
5187                None => return FilterSources::Empty,
5188            }
5189        }
5190
5191        if let Some(strategy_id) = strategy_id {
5192            match self.index.strategy_positions.get(strategy_id) {
5193                Some(set) => sources.push(set),
5194                None => return FilterSources::Empty,
5195            }
5196        }
5197
5198        if let Some(account_id) = account_id {
5199            match self.index.account_positions.get(account_id) {
5200                Some(set) => sources.push(set),
5201                None => return FilterSources::Empty,
5202            }
5203        }
5204
5205        if sources.is_empty() {
5206            FilterSources::Unfiltered
5207        } else {
5208            FilterSources::Sets(sources)
5209        }
5210    }
5211
5212    // Materializes the `ClientOrderId`s in `bucket` matching the optional filter parameters.
5213    //
5214    // Folds the bucket into the filter sources and runs a single size-ordered intersection,
5215    // avoiding the legacy two-step build-filter-set + bucket-intersection that allocated and
5216    // rehashed twice.
5217    fn query_orders_in_bucket(
5218        &self,
5219        bucket: &AHashSet<ClientOrderId>,
5220        venue: Option<&Venue>,
5221        instrument_id: Option<&InstrumentId>,
5222        strategy_id: Option<&StrategyId>,
5223        account_id: Option<&AccountId>,
5224    ) -> AHashSet<ClientOrderId> {
5225        match self.collect_order_filter_sources(venue, instrument_id, strategy_id, account_id) {
5226            FilterSources::Empty => AHashSet::new(),
5227            FilterSources::Unfiltered => bucket.clone(),
5228            FilterSources::Sets(sources) => intersect_pair_or_many(bucket, sources),
5229        }
5230    }
5231
5232    fn query_positions_in_bucket(
5233        &self,
5234        bucket: &AHashSet<PositionId>,
5235        venue: Option<&Venue>,
5236        instrument_id: Option<&InstrumentId>,
5237        strategy_id: Option<&StrategyId>,
5238        account_id: Option<&AccountId>,
5239    ) -> AHashSet<PositionId> {
5240        match self.collect_position_filter_sources(venue, instrument_id, strategy_id, account_id) {
5241            FilterSources::Empty => AHashSet::new(),
5242            FilterSources::Unfiltered => bucket.clone(),
5243            FilterSources::Sets(sources) => intersect_pair_or_many(bucket, sources),
5244        }
5245    }
5246
5247    // Returns a borrowed or owned view of the orders in `bucket` matching the optional filter
5248    // parameters. Avoids cloning the bucket when no filter narrows it.
5249    fn view_orders_in_bucket<'a>(
5250        &'a self,
5251        bucket: &'a AHashSet<ClientOrderId>,
5252        venue: Option<&Venue>,
5253        instrument_id: Option<&InstrumentId>,
5254        strategy_id: Option<&StrategyId>,
5255        account_id: Option<&AccountId>,
5256    ) -> Cow<'a, AHashSet<ClientOrderId>> {
5257        match self.collect_order_filter_sources(venue, instrument_id, strategy_id, account_id) {
5258            FilterSources::Empty => Cow::Owned(AHashSet::new()),
5259            FilterSources::Unfiltered => Cow::Borrowed(bucket),
5260            FilterSources::Sets(sources) => Cow::Owned(intersect_pair_or_many(bucket, sources)),
5261        }
5262    }
5263
5264    fn view_positions_in_bucket<'a>(
5265        &'a self,
5266        bucket: &'a AHashSet<PositionId>,
5267        venue: Option<&Venue>,
5268        instrument_id: Option<&InstrumentId>,
5269        strategy_id: Option<&StrategyId>,
5270        account_id: Option<&AccountId>,
5271    ) -> Cow<'a, AHashSet<PositionId>> {
5272        match self.collect_position_filter_sources(venue, instrument_id, strategy_id, account_id) {
5273            FilterSources::Empty => Cow::Owned(AHashSet::new()),
5274            FilterSources::Unfiltered => Cow::Borrowed(bucket),
5275            FilterSources::Sets(sources) => Cow::Owned(intersect_pair_or_many(bucket, sources)),
5276        }
5277    }
5278
5279    // Returns a lazy iterator yielding the [`ClientOrderId`]s in `bucket` matching the optional
5280    // filter parameters. Avoids any [`Vec`] or [`AHashSet`] materialization in the result path,
5281    // and (for multi-filter calls) drives intersection from the smallest source while looking
5282    // up membership in the rest.
5283    fn iter_orders_in_bucket<'a>(
5284        &'a self,
5285        bucket: &'a AHashSet<ClientOrderId>,
5286        venue: Option<&Venue>,
5287        instrument_id: Option<&InstrumentId>,
5288        strategy_id: Option<&StrategyId>,
5289        account_id: Option<&AccountId>,
5290    ) -> Box<dyn Iterator<Item = ClientOrderId> + 'a> {
5291        match self.collect_order_filter_sources(venue, instrument_id, strategy_id, account_id) {
5292            FilterSources::Empty => Box::new(std::iter::empty()),
5293            FilterSources::Unfiltered => Box::new(bucket.iter().copied()),
5294            FilterSources::Sets(mut sources) => {
5295                sources.push(bucket);
5296                sources.sort_unstable_by_key(|s| s.len());
5297                let driver = sources[0];
5298                let rest: Vec<&'a AHashSet<ClientOrderId>> = sources[1..].to_vec();
5299                Box::new(
5300                    driver
5301                        .iter()
5302                        .copied()
5303                        .filter(move |id| rest.iter().all(|s| s.contains(id))),
5304                )
5305            }
5306        }
5307    }
5308
5309    fn iter_positions_in_bucket<'a>(
5310        &'a self,
5311        bucket: &'a AHashSet<PositionId>,
5312        venue: Option<&Venue>,
5313        instrument_id: Option<&InstrumentId>,
5314        strategy_id: Option<&StrategyId>,
5315        account_id: Option<&AccountId>,
5316    ) -> Box<dyn Iterator<Item = PositionId> + 'a> {
5317        match self.collect_position_filter_sources(venue, instrument_id, strategy_id, account_id) {
5318            FilterSources::Empty => Box::new(std::iter::empty()),
5319            FilterSources::Unfiltered => Box::new(bucket.iter().copied()),
5320            FilterSources::Sets(mut sources) => {
5321                sources.push(bucket);
5322                sources.sort_unstable_by_key(|s| s.len());
5323                let driver = sources[0];
5324                let rest: Vec<&'a AHashSet<PositionId>> = sources[1..].to_vec();
5325                Box::new(
5326                    driver
5327                        .iter()
5328                        .copied()
5329                        .filter(move |id| rest.iter().all(|s| s.contains(id))),
5330                )
5331            }
5332        }
5333    }
5334
5335    // Counts orders in `bucket` matching the optional filter parameters.
5336    //
5337    // Drives intersection from the smallest filter source (or the bucket itself when no filter
5338    // is provided) and short-circuits by counting rather than collecting. With a side filter,
5339    // each candidate order is borrowed via its cell only long enough to inspect the side.
5340    fn count_orders_in_bucket(
5341        &self,
5342        bucket: &AHashSet<ClientOrderId>,
5343        venue: Option<&Venue>,
5344        instrument_id: Option<&InstrumentId>,
5345        strategy_id: Option<&StrategyId>,
5346        account_id: Option<&AccountId>,
5347        side: Option<OrderSide>,
5348    ) -> usize {
5349        let side = side.unwrap_or(OrderSide::NoOrderSide);
5350
5351        match self.collect_order_filter_sources(venue, instrument_id, strategy_id, account_id) {
5352            FilterSources::Empty => 0,
5353            FilterSources::Unfiltered => {
5354                if side == OrderSide::NoOrderSide {
5355                    bucket.len()
5356                } else {
5357                    bucket
5358                        .iter()
5359                        .filter(|id| self.order_side_matches(id, side))
5360                        .count()
5361                }
5362            }
5363            FilterSources::Sets(mut sources) => {
5364                sources.push(bucket);
5365                sources.sort_unstable_by_key(|s| s.len());
5366                let driver = sources[0];
5367                let rest = &sources[1..];
5368
5369                driver
5370                    .iter()
5371                    .filter(|id| rest.iter().all(|s| s.contains(id)))
5372                    .filter(|id| {
5373                        side == OrderSide::NoOrderSide || self.order_side_matches(id, side)
5374                    })
5375                    .count()
5376            }
5377        }
5378    }
5379
5380    fn count_positions_in_bucket(
5381        &self,
5382        bucket: &AHashSet<PositionId>,
5383        venue: Option<&Venue>,
5384        instrument_id: Option<&InstrumentId>,
5385        strategy_id: Option<&StrategyId>,
5386        account_id: Option<&AccountId>,
5387        side: Option<PositionSide>,
5388    ) -> usize {
5389        let side = side.unwrap_or(PositionSide::NoPositionSide);
5390
5391        match self.collect_position_filter_sources(venue, instrument_id, strategy_id, account_id) {
5392            FilterSources::Empty => 0,
5393            FilterSources::Unfiltered => {
5394                if side == PositionSide::NoPositionSide {
5395                    bucket.len()
5396                } else {
5397                    bucket
5398                        .iter()
5399                        .filter(|id| self.position_side_matches(id, side))
5400                        .count()
5401                }
5402            }
5403            FilterSources::Sets(mut sources) => {
5404                sources.push(bucket);
5405                sources.sort_unstable_by_key(|s| s.len());
5406                let driver = sources[0];
5407                let rest = &sources[1..];
5408
5409                driver
5410                    .iter()
5411                    .filter(|id| rest.iter().all(|s| s.contains(id)))
5412                    .filter(|id| {
5413                        side == PositionSide::NoPositionSide || self.position_side_matches(id, side)
5414                    })
5415                    .count()
5416            }
5417        }
5418    }
5419
5420    // Returns whether any order in `bucket` matches the optional filter parameters.
5421    //
5422    // Mirrors `count_orders_in_bucket` but short-circuits on the first match. Useful for
5423    // `is_empty`-style gating in hot paths where the caller only needs to know whether at
5424    // least one matching order exists.
5425    fn any_orders_in_bucket(
5426        &self,
5427        bucket: &AHashSet<ClientOrderId>,
5428        venue: Option<&Venue>,
5429        instrument_id: Option<&InstrumentId>,
5430        strategy_id: Option<&StrategyId>,
5431        account_id: Option<&AccountId>,
5432        side: Option<OrderSide>,
5433    ) -> bool {
5434        let side = side.unwrap_or(OrderSide::NoOrderSide);
5435
5436        match self.collect_order_filter_sources(venue, instrument_id, strategy_id, account_id) {
5437            FilterSources::Empty => false,
5438            FilterSources::Unfiltered => {
5439                if side == OrderSide::NoOrderSide {
5440                    !bucket.is_empty()
5441                } else {
5442                    bucket.iter().any(|id| self.order_side_matches(id, side))
5443                }
5444            }
5445            FilterSources::Sets(mut sources) => {
5446                sources.push(bucket);
5447                sources.sort_unstable_by_key(|s| s.len());
5448                let driver = sources[0];
5449                let rest = &sources[1..];
5450
5451                driver
5452                    .iter()
5453                    .filter(|id| rest.iter().all(|s| s.contains(id)))
5454                    .any(|id| side == OrderSide::NoOrderSide || self.order_side_matches(id, side))
5455            }
5456        }
5457    }
5458
5459    fn any_positions_in_bucket(
5460        &self,
5461        bucket: &AHashSet<PositionId>,
5462        venue: Option<&Venue>,
5463        instrument_id: Option<&InstrumentId>,
5464        strategy_id: Option<&StrategyId>,
5465        account_id: Option<&AccountId>,
5466        side: Option<PositionSide>,
5467    ) -> bool {
5468        let side = side.unwrap_or(PositionSide::NoPositionSide);
5469
5470        match self.collect_position_filter_sources(venue, instrument_id, strategy_id, account_id) {
5471            FilterSources::Empty => false,
5472            FilterSources::Unfiltered => {
5473                if side == PositionSide::NoPositionSide {
5474                    !bucket.is_empty()
5475                } else {
5476                    bucket.iter().any(|id| self.position_side_matches(id, side))
5477                }
5478            }
5479            FilterSources::Sets(mut sources) => {
5480                sources.push(bucket);
5481                sources.sort_unstable_by_key(|s| s.len());
5482                let driver = sources[0];
5483                let rest = &sources[1..];
5484
5485                driver
5486                    .iter()
5487                    .filter(|id| rest.iter().all(|s| s.contains(id)))
5488                    .any(|id| {
5489                        side == PositionSide::NoPositionSide || self.position_side_matches(id, side)
5490                    })
5491            }
5492        }
5493    }
5494
5495    fn order_side_matches(&self, client_order_id: &ClientOrderId, side: OrderSide) -> bool {
5496        self.orders
5497            .get(client_order_id)
5498            .is_some_and(|cell| cell.borrow().order_side() == side)
5499    }
5500
5501    fn position_side_matches(&self, position_id: &PositionId, side: PositionSide) -> bool {
5502        self.positions
5503            .get(position_id)
5504            .is_some_and(|cell| cell.borrow().side == side)
5505    }
5506
5507    /// Retrieves orders corresponding to the `client_order_ids`, optionally filtering by `side`.
5508    ///
5509    /// # Panics
5510    ///
5511    /// Panics if any `client_order_id` in the set is not found in the cache.
5512    fn get_orders_for_ids(
5513        &self,
5514        client_order_ids: &AHashSet<ClientOrderId>,
5515        side: Option<OrderSide>,
5516    ) -> Vec<OrderRef<'_>> {
5517        let side = side.unwrap_or(OrderSide::NoOrderSide);
5518        let mut orders = Vec::new();
5519
5520        for client_order_id in client_order_ids {
5521            let order_cell = self
5522                .orders
5523                .get(client_order_id)
5524                .unwrap_or_else(|| panic!("Order {client_order_id} not found"));
5525            let order = OrderRef::new(order_cell.borrow());
5526
5527            if side == OrderSide::NoOrderSide || side == order.order_side() {
5528                orders.push(order);
5529            }
5530        }
5531
5532        // Sort so callers receive a deterministic Vec across runs; the
5533        // underlying client_order_ids set is AHash-backed.
5534        orders.sort_by_key(|o| o.client_order_id());
5535        orders
5536    }
5537
5538    /// Retrieves positions corresponding to the `position_ids`, optionally filtering by `side`.
5539    ///
5540    /// Each [`PositionRef`] in the returned vector borrows its underlying cell; mutating any of
5541    /// those positions while the vector is alive will panic at runtime. Drop the vector before
5542    /// issuing writes.
5543    ///
5544    /// # Panics
5545    ///
5546    /// Panics if any `position_id` in the set is not found in the cache.
5547    fn get_positions_for_ids(
5548        &self,
5549        position_ids: &AHashSet<PositionId>,
5550        side: Option<PositionSide>,
5551    ) -> Vec<PositionRef<'_>> {
5552        let side = side.unwrap_or(PositionSide::NoPositionSide);
5553        let mut positions = Vec::new();
5554
5555        for position_id in position_ids {
5556            let position_cell = self
5557                .positions
5558                .get(position_id)
5559                .unwrap_or_else(|| panic!("Position {position_id} not found"));
5560            let position = PositionRef::new(position_cell.borrow());
5561
5562            if side == PositionSide::NoPositionSide || side == position.side {
5563                positions.push(position);
5564            }
5565        }
5566
5567        // Sort so callers receive a deterministic Vec across runs; the
5568        // underlying position_ids set is AHash-backed.
5569        positions.sort_by_key(|p| p.id);
5570        positions
5571    }
5572
5573    /// Returns the `ClientOrderId`s of all orders.
5574    #[must_use]
5575    pub fn client_order_ids(
5576        &self,
5577        venue: Option<&Venue>,
5578        instrument_id: Option<&InstrumentId>,
5579        strategy_id: Option<&StrategyId>,
5580        account_id: Option<&AccountId>,
5581    ) -> AHashSet<ClientOrderId> {
5582        self.query_orders_in_bucket(
5583            &self.index.orders,
5584            venue,
5585            instrument_id,
5586            strategy_id,
5587            account_id,
5588        )
5589    }
5590
5591    /// Returns the `ClientOrderId`s of all open orders.
5592    #[must_use]
5593    pub fn client_order_ids_open(
5594        &self,
5595        venue: Option<&Venue>,
5596        instrument_id: Option<&InstrumentId>,
5597        strategy_id: Option<&StrategyId>,
5598        account_id: Option<&AccountId>,
5599    ) -> AHashSet<ClientOrderId> {
5600        self.query_orders_in_bucket(
5601            &self.index.orders_open,
5602            venue,
5603            instrument_id,
5604            strategy_id,
5605            account_id,
5606        )
5607    }
5608
5609    /// Returns the `ClientOrderId`s of all closed orders.
5610    #[must_use]
5611    pub fn client_order_ids_closed(
5612        &self,
5613        venue: Option<&Venue>,
5614        instrument_id: Option<&InstrumentId>,
5615        strategy_id: Option<&StrategyId>,
5616        account_id: Option<&AccountId>,
5617    ) -> AHashSet<ClientOrderId> {
5618        self.query_orders_in_bucket(
5619            &self.index.orders_closed,
5620            venue,
5621            instrument_id,
5622            strategy_id,
5623            account_id,
5624        )
5625    }
5626
5627    /// Returns the `ClientOrderId`s of all locally active orders.
5628    ///
5629    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
5630    /// (a superset of emulated orders).
5631    #[must_use]
5632    pub fn client_order_ids_active_local(
5633        &self,
5634        venue: Option<&Venue>,
5635        instrument_id: Option<&InstrumentId>,
5636        strategy_id: Option<&StrategyId>,
5637        account_id: Option<&AccountId>,
5638    ) -> AHashSet<ClientOrderId> {
5639        self.query_orders_in_bucket(
5640            &self.index.orders_active_local,
5641            venue,
5642            instrument_id,
5643            strategy_id,
5644            account_id,
5645        )
5646    }
5647
5648    /// Returns the `ClientOrderId`s of all emulated orders.
5649    #[must_use]
5650    pub fn client_order_ids_emulated(
5651        &self,
5652        venue: Option<&Venue>,
5653        instrument_id: Option<&InstrumentId>,
5654        strategy_id: Option<&StrategyId>,
5655        account_id: Option<&AccountId>,
5656    ) -> AHashSet<ClientOrderId> {
5657        self.query_orders_in_bucket(
5658            &self.index.orders_emulated,
5659            venue,
5660            instrument_id,
5661            strategy_id,
5662            account_id,
5663        )
5664    }
5665
5666    /// Returns the `ClientOrderId`s of all in-flight orders.
5667    #[must_use]
5668    pub fn client_order_ids_inflight(
5669        &self,
5670        venue: Option<&Venue>,
5671        instrument_id: Option<&InstrumentId>,
5672        strategy_id: Option<&StrategyId>,
5673        account_id: Option<&AccountId>,
5674    ) -> AHashSet<ClientOrderId> {
5675        self.query_orders_in_bucket(
5676            &self.index.orders_inflight,
5677            venue,
5678            instrument_id,
5679            strategy_id,
5680            account_id,
5681        )
5682    }
5683
5684    /// Returns `PositionId`s of all positions.
5685    #[must_use]
5686    pub fn position_ids(
5687        &self,
5688        venue: Option<&Venue>,
5689        instrument_id: Option<&InstrumentId>,
5690        strategy_id: Option<&StrategyId>,
5691        account_id: Option<&AccountId>,
5692    ) -> AHashSet<PositionId> {
5693        self.query_positions_in_bucket(
5694            &self.index.positions,
5695            venue,
5696            instrument_id,
5697            strategy_id,
5698            account_id,
5699        )
5700    }
5701
5702    /// Returns the `PositionId`s of all open positions.
5703    #[must_use]
5704    pub fn position_open_ids(
5705        &self,
5706        venue: Option<&Venue>,
5707        instrument_id: Option<&InstrumentId>,
5708        strategy_id: Option<&StrategyId>,
5709        account_id: Option<&AccountId>,
5710    ) -> AHashSet<PositionId> {
5711        self.query_positions_in_bucket(
5712            &self.index.positions_open,
5713            venue,
5714            instrument_id,
5715            strategy_id,
5716            account_id,
5717        )
5718    }
5719
5720    /// Returns the `PositionId`s of all closed positions.
5721    #[must_use]
5722    pub fn position_closed_ids(
5723        &self,
5724        venue: Option<&Venue>,
5725        instrument_id: Option<&InstrumentId>,
5726        strategy_id: Option<&StrategyId>,
5727        account_id: Option<&AccountId>,
5728    ) -> AHashSet<PositionId> {
5729        self.query_positions_in_bucket(
5730            &self.index.positions_closed,
5731            venue,
5732            instrument_id,
5733            strategy_id,
5734            account_id,
5735        )
5736    }
5737
5738    /// Returns a borrowed view over the [`ClientOrderId`]s of all orders matching the optional
5739    /// filter parameters.
5740    ///
5741    /// The returned [`Cow`] borrows the underlying index when no filter is provided and only
5742    /// allocates an owned [`AHashSet`] when an intersection is required. Prefer this over
5743    /// [`Self::client_order_ids`] when the caller only needs to iterate or read membership.
5744    #[must_use]
5745    pub fn client_order_ids_view(
5746        &self,
5747        venue: Option<&Venue>,
5748        instrument_id: Option<&InstrumentId>,
5749        strategy_id: Option<&StrategyId>,
5750        account_id: Option<&AccountId>,
5751    ) -> Cow<'_, AHashSet<ClientOrderId>> {
5752        self.view_orders_in_bucket(
5753            &self.index.orders,
5754            venue,
5755            instrument_id,
5756            strategy_id,
5757            account_id,
5758        )
5759    }
5760
5761    /// Returns a borrowed view over the [`ClientOrderId`]s of all open orders.
5762    #[must_use]
5763    pub fn client_order_ids_open_view(
5764        &self,
5765        venue: Option<&Venue>,
5766        instrument_id: Option<&InstrumentId>,
5767        strategy_id: Option<&StrategyId>,
5768        account_id: Option<&AccountId>,
5769    ) -> Cow<'_, AHashSet<ClientOrderId>> {
5770        self.view_orders_in_bucket(
5771            &self.index.orders_open,
5772            venue,
5773            instrument_id,
5774            strategy_id,
5775            account_id,
5776        )
5777    }
5778
5779    /// Returns a borrowed view over the [`ClientOrderId`]s of all closed orders.
5780    #[must_use]
5781    pub fn client_order_ids_closed_view(
5782        &self,
5783        venue: Option<&Venue>,
5784        instrument_id: Option<&InstrumentId>,
5785        strategy_id: Option<&StrategyId>,
5786        account_id: Option<&AccountId>,
5787    ) -> Cow<'_, AHashSet<ClientOrderId>> {
5788        self.view_orders_in_bucket(
5789            &self.index.orders_closed,
5790            venue,
5791            instrument_id,
5792            strategy_id,
5793            account_id,
5794        )
5795    }
5796
5797    /// Returns a borrowed view over the [`ClientOrderId`]s of all locally active orders.
5798    #[must_use]
5799    pub fn client_order_ids_active_local_view(
5800        &self,
5801        venue: Option<&Venue>,
5802        instrument_id: Option<&InstrumentId>,
5803        strategy_id: Option<&StrategyId>,
5804        account_id: Option<&AccountId>,
5805    ) -> Cow<'_, AHashSet<ClientOrderId>> {
5806        self.view_orders_in_bucket(
5807            &self.index.orders_active_local,
5808            venue,
5809            instrument_id,
5810            strategy_id,
5811            account_id,
5812        )
5813    }
5814
5815    /// Returns a borrowed view over the [`ClientOrderId`]s of all emulated orders.
5816    #[must_use]
5817    pub fn client_order_ids_emulated_view(
5818        &self,
5819        venue: Option<&Venue>,
5820        instrument_id: Option<&InstrumentId>,
5821        strategy_id: Option<&StrategyId>,
5822        account_id: Option<&AccountId>,
5823    ) -> Cow<'_, AHashSet<ClientOrderId>> {
5824        self.view_orders_in_bucket(
5825            &self.index.orders_emulated,
5826            venue,
5827            instrument_id,
5828            strategy_id,
5829            account_id,
5830        )
5831    }
5832
5833    /// Returns a borrowed view over the [`ClientOrderId`]s of all in-flight orders.
5834    #[must_use]
5835    pub fn client_order_ids_inflight_view(
5836        &self,
5837        venue: Option<&Venue>,
5838        instrument_id: Option<&InstrumentId>,
5839        strategy_id: Option<&StrategyId>,
5840        account_id: Option<&AccountId>,
5841    ) -> Cow<'_, AHashSet<ClientOrderId>> {
5842        self.view_orders_in_bucket(
5843            &self.index.orders_inflight,
5844            venue,
5845            instrument_id,
5846            strategy_id,
5847            account_id,
5848        )
5849    }
5850
5851    /// Returns a borrowed view over the [`PositionId`]s of all positions.
5852    #[must_use]
5853    pub fn position_ids_view(
5854        &self,
5855        venue: Option<&Venue>,
5856        instrument_id: Option<&InstrumentId>,
5857        strategy_id: Option<&StrategyId>,
5858        account_id: Option<&AccountId>,
5859    ) -> Cow<'_, AHashSet<PositionId>> {
5860        self.view_positions_in_bucket(
5861            &self.index.positions,
5862            venue,
5863            instrument_id,
5864            strategy_id,
5865            account_id,
5866        )
5867    }
5868
5869    /// Returns a borrowed view over the [`PositionId`]s of all open positions.
5870    #[must_use]
5871    pub fn position_open_ids_view(
5872        &self,
5873        venue: Option<&Venue>,
5874        instrument_id: Option<&InstrumentId>,
5875        strategy_id: Option<&StrategyId>,
5876        account_id: Option<&AccountId>,
5877    ) -> Cow<'_, AHashSet<PositionId>> {
5878        self.view_positions_in_bucket(
5879            &self.index.positions_open,
5880            venue,
5881            instrument_id,
5882            strategy_id,
5883            account_id,
5884        )
5885    }
5886
5887    /// Returns a borrowed view over the [`PositionId`]s of all closed positions.
5888    #[must_use]
5889    pub fn position_closed_ids_view(
5890        &self,
5891        venue: Option<&Venue>,
5892        instrument_id: Option<&InstrumentId>,
5893        strategy_id: Option<&StrategyId>,
5894        account_id: Option<&AccountId>,
5895    ) -> Cow<'_, AHashSet<PositionId>> {
5896        self.view_positions_in_bucket(
5897            &self.index.positions_closed,
5898            venue,
5899            instrument_id,
5900            strategy_id,
5901            account_id,
5902        )
5903    }
5904
5905    /// Returns a lazy iterator yielding [`ClientOrderId`]s of all orders matching the optional
5906    /// filter parameters.
5907    ///
5908    /// Avoids the [`AHashSet`] allocation performed by [`Self::client_order_ids`]. Useful when
5909    /// the caller iterates the result once and discards it.
5910    pub fn iter_client_order_ids(
5911        &self,
5912        venue: Option<&Venue>,
5913        instrument_id: Option<&InstrumentId>,
5914        strategy_id: Option<&StrategyId>,
5915        account_id: Option<&AccountId>,
5916    ) -> Box<dyn Iterator<Item = ClientOrderId> + '_> {
5917        self.iter_orders_in_bucket(
5918            &self.index.orders,
5919            venue,
5920            instrument_id,
5921            strategy_id,
5922            account_id,
5923        )
5924    }
5925
5926    /// Returns a lazy iterator yielding [`ClientOrderId`]s of all open orders.
5927    pub fn iter_client_order_ids_open(
5928        &self,
5929        venue: Option<&Venue>,
5930        instrument_id: Option<&InstrumentId>,
5931        strategy_id: Option<&StrategyId>,
5932        account_id: Option<&AccountId>,
5933    ) -> Box<dyn Iterator<Item = ClientOrderId> + '_> {
5934        self.iter_orders_in_bucket(
5935            &self.index.orders_open,
5936            venue,
5937            instrument_id,
5938            strategy_id,
5939            account_id,
5940        )
5941    }
5942
5943    /// Returns a lazy iterator yielding [`ClientOrderId`]s of all closed orders.
5944    pub fn iter_client_order_ids_closed(
5945        &self,
5946        venue: Option<&Venue>,
5947        instrument_id: Option<&InstrumentId>,
5948        strategy_id: Option<&StrategyId>,
5949        account_id: Option<&AccountId>,
5950    ) -> Box<dyn Iterator<Item = ClientOrderId> + '_> {
5951        self.iter_orders_in_bucket(
5952            &self.index.orders_closed,
5953            venue,
5954            instrument_id,
5955            strategy_id,
5956            account_id,
5957        )
5958    }
5959
5960    /// Returns a lazy iterator yielding [`ClientOrderId`]s of all locally active orders.
5961    pub fn iter_client_order_ids_active_local(
5962        &self,
5963        venue: Option<&Venue>,
5964        instrument_id: Option<&InstrumentId>,
5965        strategy_id: Option<&StrategyId>,
5966        account_id: Option<&AccountId>,
5967    ) -> Box<dyn Iterator<Item = ClientOrderId> + '_> {
5968        self.iter_orders_in_bucket(
5969            &self.index.orders_active_local,
5970            venue,
5971            instrument_id,
5972            strategy_id,
5973            account_id,
5974        )
5975    }
5976
5977    /// Returns a lazy iterator yielding [`ClientOrderId`]s of all emulated orders.
5978    pub fn iter_client_order_ids_emulated(
5979        &self,
5980        venue: Option<&Venue>,
5981        instrument_id: Option<&InstrumentId>,
5982        strategy_id: Option<&StrategyId>,
5983        account_id: Option<&AccountId>,
5984    ) -> Box<dyn Iterator<Item = ClientOrderId> + '_> {
5985        self.iter_orders_in_bucket(
5986            &self.index.orders_emulated,
5987            venue,
5988            instrument_id,
5989            strategy_id,
5990            account_id,
5991        )
5992    }
5993
5994    /// Returns a lazy iterator yielding [`ClientOrderId`]s of all in-flight orders.
5995    pub fn iter_client_order_ids_inflight(
5996        &self,
5997        venue: Option<&Venue>,
5998        instrument_id: Option<&InstrumentId>,
5999        strategy_id: Option<&StrategyId>,
6000        account_id: Option<&AccountId>,
6001    ) -> Box<dyn Iterator<Item = ClientOrderId> + '_> {
6002        self.iter_orders_in_bucket(
6003            &self.index.orders_inflight,
6004            venue,
6005            instrument_id,
6006            strategy_id,
6007            account_id,
6008        )
6009    }
6010
6011    /// Returns a lazy iterator yielding [`PositionId`]s of all positions matching the filters.
6012    pub fn iter_position_ids(
6013        &self,
6014        venue: Option<&Venue>,
6015        instrument_id: Option<&InstrumentId>,
6016        strategy_id: Option<&StrategyId>,
6017        account_id: Option<&AccountId>,
6018    ) -> Box<dyn Iterator<Item = PositionId> + '_> {
6019        self.iter_positions_in_bucket(
6020            &self.index.positions,
6021            venue,
6022            instrument_id,
6023            strategy_id,
6024            account_id,
6025        )
6026    }
6027
6028    /// Returns a lazy iterator yielding [`PositionId`]s of all open positions.
6029    pub fn iter_position_open_ids(
6030        &self,
6031        venue: Option<&Venue>,
6032        instrument_id: Option<&InstrumentId>,
6033        strategy_id: Option<&StrategyId>,
6034        account_id: Option<&AccountId>,
6035    ) -> Box<dyn Iterator<Item = PositionId> + '_> {
6036        self.iter_positions_in_bucket(
6037            &self.index.positions_open,
6038            venue,
6039            instrument_id,
6040            strategy_id,
6041            account_id,
6042        )
6043    }
6044
6045    /// Returns a lazy iterator yielding [`PositionId`]s of all closed positions.
6046    pub fn iter_position_closed_ids(
6047        &self,
6048        venue: Option<&Venue>,
6049        instrument_id: Option<&InstrumentId>,
6050        strategy_id: Option<&StrategyId>,
6051        account_id: Option<&AccountId>,
6052    ) -> Box<dyn Iterator<Item = PositionId> + '_> {
6053        self.iter_positions_in_bucket(
6054            &self.index.positions_closed,
6055            venue,
6056            instrument_id,
6057            strategy_id,
6058            account_id,
6059        )
6060    }
6061
6062    /// Returns the `ComponentId`s of all actors.
6063    #[must_use]
6064    pub fn actor_ids(&self) -> AHashSet<ComponentId> {
6065        self.index.actors.clone()
6066    }
6067
6068    /// Returns the `StrategyId`s of all strategies.
6069    #[must_use]
6070    pub fn strategy_ids(&self) -> AHashSet<StrategyId> {
6071        self.index.strategies.clone()
6072    }
6073
6074    /// Returns the `ExecAlgorithmId`s of all execution algorithms.
6075    #[must_use]
6076    pub fn exec_algorithm_ids(&self) -> AHashSet<ExecAlgorithmId> {
6077        self.index.exec_algorithms.clone()
6078    }
6079
6080    // -- ORDER QUERIES ---------------------------------------------------------------------------
6081
6082    /// Gets a borrow of the order with the `client_order_id` (if found).
6083    ///
6084    /// The returned [`OrderRef`] is tied to the cache borrow's scope and panics at runtime if
6085    /// held across a mutation of the same order. Drop the borrow before dispatching events; if
6086    /// post-event state is required, perform a fresh lookup. Use [`Self::order_owned`] when an
6087    /// owned snapshot is needed for a boundary handover.
6088    #[must_use]
6089    pub fn order_ref(&self, client_order_id: &ClientOrderId) -> Option<OrderRef<'_>> {
6090        self.orders
6091            .get(client_order_id)
6092            .map(|order_cell| OrderRef::new(order_cell.borrow()))
6093    }
6094
6095    /// Gets a borrow of the order with the `client_order_id` (if found).
6096    ///
6097    /// Prefer [`Self::order_ref`] in new native code.
6098    #[must_use]
6099    pub fn order(&self, client_order_id: &ClientOrderId) -> Option<OrderRef<'_>> {
6100        self.order_ref(client_order_id)
6101    }
6102
6103    /// Gets a borrow of the order with the `client_order_id`.
6104    ///
6105    /// # Errors
6106    ///
6107    /// Returns [`OrderLookupError::NotFound`] when the order is not present in the cache.
6108    pub fn try_order_ref(
6109        &self,
6110        client_order_id: &ClientOrderId,
6111    ) -> Result<OrderRef<'_>, OrderLookupError> {
6112        self.orders
6113            .get(client_order_id)
6114            .map(|order_cell| OrderRef::new(order_cell.borrow()))
6115            .ok_or_else(|| OrderLookupError::not_found(*client_order_id))
6116    }
6117
6118    /// Gets a borrow of the order with the `client_order_id`.
6119    ///
6120    /// Prefer [`Self::try_order_ref`] in new native code.
6121    ///
6122    /// # Errors
6123    ///
6124    /// Returns [`OrderLookupError::NotFound`] when the order is not present in the cache.
6125    pub fn try_order(
6126        &self,
6127        client_order_id: &ClientOrderId,
6128    ) -> Result<OrderRef<'_>, OrderLookupError> {
6129        self.try_order_ref(client_order_id)
6130    }
6131
6132    /// Gets an exclusive write borrow of the order with the `client_order_id` (if found).
6133    ///
6134    /// Requires `&mut Cache` so cache writes are reachable only by privileged crates that hold
6135    /// `Rc<RefCell<Cache>>` directly. Adapter-facing code receives [`CacheView`], which only
6136    /// exposes immutable cache borrows and therefore cannot reach this method.
6137    ///
6138    /// While the returned [`OrderRefMut`] is alive, no other read or write of the same order is
6139    /// permitted. Drop the borrow before dispatching events or taking any other cache borrow that
6140    /// may re-enter the same order.
6141    #[must_use]
6142    pub fn order_mut(&mut self, client_order_id: &ClientOrderId) -> Option<OrderRefMut<'_>> {
6143        self.orders
6144            .get(client_order_id)
6145            .map(|order_cell| OrderRefMut::new(order_cell.borrow_mut()))
6146    }
6147
6148    /// Gets an owned copy of the order with the `client_order_id` (if found).
6149    ///
6150    /// Use when downstream needs an owned [`OrderAny`] that crosses a boundary (for example, an
6151    /// adapter `get_order` API). The copy will not reflect later cache mutations.
6152    #[must_use]
6153    pub fn order_owned(&self, client_order_id: &ClientOrderId) -> Option<OrderAny> {
6154        self.orders
6155            .get(client_order_id)
6156            .map(|order_cell| order_cell.borrow().clone())
6157    }
6158
6159    /// Gets an owned snapshot of the order with the `client_order_id`.
6160    ///
6161    /// # Errors
6162    ///
6163    /// Returns [`OrderLookupError::NotFound`] when the order is not present in the cache.
6164    pub fn try_order_owned(
6165        &self,
6166        client_order_id: &ClientOrderId,
6167    ) -> Result<OrderAny, OrderLookupError> {
6168        self.try_order_ref(client_order_id)
6169            .map(|order| order.cloned())
6170    }
6171
6172    /// Gets cloned orders for the given `client_order_ids`, logging an error for any missing.
6173    #[must_use]
6174    pub fn orders_for_ids(
6175        &self,
6176        client_order_ids: &[ClientOrderId],
6177        context: &dyn Display,
6178    ) -> Vec<OrderAny> {
6179        let mut orders = Vec::with_capacity(client_order_ids.len());
6180        for id in client_order_ids {
6181            match self.orders.get(id) {
6182                Some(order_cell) => orders.push(order_cell.borrow().clone()),
6183                None => log::error!("Order {id} not found in cache for {context}"),
6184            }
6185        }
6186        orders
6187    }
6188
6189    /// Gets a reference to the client order ID for the `venue_order_id` (if found).
6190    #[must_use]
6191    pub fn client_order_id(&self, venue_order_id: &VenueOrderId) -> Option<&ClientOrderId> {
6192        self.index.venue_order_ids.get(venue_order_id)
6193    }
6194
6195    /// Gets a reference to the venue order ID for the `client_order_id` (if found).
6196    #[must_use]
6197    pub fn venue_order_id(&self, client_order_id: &ClientOrderId) -> Option<&VenueOrderId> {
6198        self.index.client_order_ids.get(client_order_id)
6199    }
6200
6201    /// Gets a reference to the client ID indexed for then `client_order_id` (if found).
6202    #[must_use]
6203    pub fn client_id(&self, client_order_id: &ClientOrderId) -> Option<&ClientId> {
6204        self.index.order_client.get(client_order_id)
6205    }
6206
6207    /// Returns borrows of all orders matching the optional filter parameters.
6208    ///
6209    /// Each [`Ref`] in the returned vector borrows its underlying cell; mutating any of
6210    /// those orders while the vector is alive will panic at runtime. Drop the vector
6211    /// before issuing writes.
6212    #[must_use]
6213    pub fn orders_refs(
6214        &self,
6215        venue: Option<&Venue>,
6216        instrument_id: Option<&InstrumentId>,
6217        strategy_id: Option<&StrategyId>,
6218        account_id: Option<&AccountId>,
6219        side: Option<OrderSide>,
6220    ) -> Vec<OrderRef<'_>> {
6221        let client_order_ids = self.client_order_ids(venue, instrument_id, strategy_id, account_id);
6222        self.get_orders_for_ids(&client_order_ids, side)
6223    }
6224
6225    /// Returns borrows of all orders matching the optional filter parameters.
6226    ///
6227    /// Prefer [`Self::orders_refs`] in new native code.
6228    #[must_use]
6229    pub fn orders(
6230        &self,
6231        venue: Option<&Venue>,
6232        instrument_id: Option<&InstrumentId>,
6233        strategy_id: Option<&StrategyId>,
6234        account_id: Option<&AccountId>,
6235        side: Option<OrderSide>,
6236    ) -> Vec<OrderRef<'_>> {
6237        self.orders_refs(venue, instrument_id, strategy_id, account_id, side)
6238    }
6239
6240    /// Returns borrows of all open orders matching the optional filter parameters.
6241    #[must_use]
6242    pub fn orders_open_refs(
6243        &self,
6244        venue: Option<&Venue>,
6245        instrument_id: Option<&InstrumentId>,
6246        strategy_id: Option<&StrategyId>,
6247        account_id: Option<&AccountId>,
6248        side: Option<OrderSide>,
6249    ) -> Vec<OrderRef<'_>> {
6250        let client_order_ids =
6251            self.client_order_ids_open(venue, instrument_id, strategy_id, account_id);
6252        self.get_orders_for_ids(&client_order_ids, side)
6253    }
6254
6255    /// Returns borrows of all open orders matching the optional filter parameters.
6256    ///
6257    /// Prefer [`Self::orders_open_refs`] in new native code.
6258    #[must_use]
6259    pub fn orders_open(
6260        &self,
6261        venue: Option<&Venue>,
6262        instrument_id: Option<&InstrumentId>,
6263        strategy_id: Option<&StrategyId>,
6264        account_id: Option<&AccountId>,
6265        side: Option<OrderSide>,
6266    ) -> Vec<OrderRef<'_>> {
6267        self.orders_open_refs(venue, instrument_id, strategy_id, account_id, side)
6268    }
6269
6270    /// Returns borrows of all closed orders matching the optional filter parameters.
6271    #[must_use]
6272    pub fn orders_closed_refs(
6273        &self,
6274        venue: Option<&Venue>,
6275        instrument_id: Option<&InstrumentId>,
6276        strategy_id: Option<&StrategyId>,
6277        account_id: Option<&AccountId>,
6278        side: Option<OrderSide>,
6279    ) -> Vec<OrderRef<'_>> {
6280        let client_order_ids =
6281            self.client_order_ids_closed(venue, instrument_id, strategy_id, account_id);
6282        self.get_orders_for_ids(&client_order_ids, side)
6283    }
6284
6285    /// Returns borrows of all closed orders matching the optional filter parameters.
6286    ///
6287    /// Prefer [`Self::orders_closed_refs`] in new native code.
6288    #[must_use]
6289    pub fn orders_closed(
6290        &self,
6291        venue: Option<&Venue>,
6292        instrument_id: Option<&InstrumentId>,
6293        strategy_id: Option<&StrategyId>,
6294        account_id: Option<&AccountId>,
6295        side: Option<OrderSide>,
6296    ) -> Vec<OrderRef<'_>> {
6297        self.orders_closed_refs(venue, instrument_id, strategy_id, account_id, side)
6298    }
6299
6300    /// Returns borrows of all locally active orders matching the optional filter parameters.
6301    ///
6302    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
6303    /// (a superset of emulated orders).
6304    #[must_use]
6305    pub fn orders_active_local_refs(
6306        &self,
6307        venue: Option<&Venue>,
6308        instrument_id: Option<&InstrumentId>,
6309        strategy_id: Option<&StrategyId>,
6310        account_id: Option<&AccountId>,
6311        side: Option<OrderSide>,
6312    ) -> Vec<OrderRef<'_>> {
6313        let client_order_ids =
6314            self.client_order_ids_active_local(venue, instrument_id, strategy_id, account_id);
6315        self.get_orders_for_ids(&client_order_ids, side)
6316    }
6317
6318    /// Returns borrows of all locally active orders matching the optional filter parameters.
6319    ///
6320    /// Prefer [`Self::orders_active_local_refs`] in new native code.
6321    #[must_use]
6322    pub fn orders_active_local(
6323        &self,
6324        venue: Option<&Venue>,
6325        instrument_id: Option<&InstrumentId>,
6326        strategy_id: Option<&StrategyId>,
6327        account_id: Option<&AccountId>,
6328        side: Option<OrderSide>,
6329    ) -> Vec<OrderRef<'_>> {
6330        self.orders_active_local_refs(venue, instrument_id, strategy_id, account_id, side)
6331    }
6332
6333    /// Returns borrows of all emulated orders matching the optional filter parameters.
6334    #[must_use]
6335    pub fn orders_emulated_refs(
6336        &self,
6337        venue: Option<&Venue>,
6338        instrument_id: Option<&InstrumentId>,
6339        strategy_id: Option<&StrategyId>,
6340        account_id: Option<&AccountId>,
6341        side: Option<OrderSide>,
6342    ) -> Vec<OrderRef<'_>> {
6343        let client_order_ids =
6344            self.client_order_ids_emulated(venue, instrument_id, strategy_id, account_id);
6345        self.get_orders_for_ids(&client_order_ids, side)
6346    }
6347
6348    /// Returns borrows of all emulated orders matching the optional filter parameters.
6349    ///
6350    /// Prefer [`Self::orders_emulated_refs`] in new native code.
6351    #[must_use]
6352    pub fn orders_emulated(
6353        &self,
6354        venue: Option<&Venue>,
6355        instrument_id: Option<&InstrumentId>,
6356        strategy_id: Option<&StrategyId>,
6357        account_id: Option<&AccountId>,
6358        side: Option<OrderSide>,
6359    ) -> Vec<OrderRef<'_>> {
6360        self.orders_emulated_refs(venue, instrument_id, strategy_id, account_id, side)
6361    }
6362
6363    /// Returns borrows of all in-flight orders matching the optional filter parameters.
6364    #[must_use]
6365    pub fn orders_inflight_refs(
6366        &self,
6367        venue: Option<&Venue>,
6368        instrument_id: Option<&InstrumentId>,
6369        strategy_id: Option<&StrategyId>,
6370        account_id: Option<&AccountId>,
6371        side: Option<OrderSide>,
6372    ) -> Vec<OrderRef<'_>> {
6373        let client_order_ids =
6374            self.client_order_ids_inflight(venue, instrument_id, strategy_id, account_id);
6375        self.get_orders_for_ids(&client_order_ids, side)
6376    }
6377
6378    /// Returns borrows of all in-flight orders matching the optional filter parameters.
6379    ///
6380    /// Prefer [`Self::orders_inflight_refs`] in new native code.
6381    #[must_use]
6382    pub fn orders_inflight(
6383        &self,
6384        venue: Option<&Venue>,
6385        instrument_id: Option<&InstrumentId>,
6386        strategy_id: Option<&StrategyId>,
6387        account_id: Option<&AccountId>,
6388        side: Option<OrderSide>,
6389    ) -> Vec<OrderRef<'_>> {
6390        self.orders_inflight_refs(venue, instrument_id, strategy_id, account_id, side)
6391    }
6392
6393    /// Returns borrows of all orders for the `position_id`.
6394    #[must_use]
6395    pub fn orders_for_position(&self, position_id: &PositionId) -> Vec<OrderRef<'_>> {
6396        match self.index.position_orders.get(position_id) {
6397            Some(client_order_ids) => self.get_orders_for_ids(client_order_ids, None),
6398            None => Vec::new(),
6399        }
6400    }
6401
6402    /// Returns whether an order with the `client_order_id` exists.
6403    #[must_use]
6404    pub fn order_exists(&self, client_order_id: &ClientOrderId) -> bool {
6405        self.index.orders.contains(client_order_id)
6406    }
6407
6408    /// Returns whether an order with the `client_order_id` is open.
6409    #[must_use]
6410    pub fn is_order_open(&self, client_order_id: &ClientOrderId) -> bool {
6411        self.index.orders_open.contains(client_order_id)
6412    }
6413
6414    /// Returns whether an order with the `client_order_id` is closed.
6415    #[must_use]
6416    pub fn is_order_closed(&self, client_order_id: &ClientOrderId) -> bool {
6417        self.index.orders_closed.contains(client_order_id)
6418    }
6419
6420    /// Returns whether an order with the `client_order_id` is locally active.
6421    ///
6422    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
6423    /// (a superset of emulated orders).
6424    #[must_use]
6425    pub fn is_order_active_local(&self, client_order_id: &ClientOrderId) -> bool {
6426        self.index.orders_active_local.contains(client_order_id)
6427    }
6428
6429    /// Returns whether an order with the `client_order_id` is emulated.
6430    #[must_use]
6431    pub fn is_order_emulated(&self, client_order_id: &ClientOrderId) -> bool {
6432        self.index.orders_emulated.contains(client_order_id)
6433    }
6434
6435    /// Returns whether an order with the `client_order_id` is in-flight.
6436    #[must_use]
6437    pub fn is_order_inflight(&self, client_order_id: &ClientOrderId) -> bool {
6438        self.index.orders_inflight.contains(client_order_id)
6439    }
6440
6441    /// Returns whether an order with the `client_order_id` is `PENDING_CANCEL` locally.
6442    #[must_use]
6443    pub fn is_order_pending_cancel_local(&self, client_order_id: &ClientOrderId) -> bool {
6444        self.index.orders_pending_cancel.contains(client_order_id)
6445    }
6446
6447    /// Returns the count of all open orders.
6448    #[must_use]
6449    pub fn orders_open_count(
6450        &self,
6451        venue: Option<&Venue>,
6452        instrument_id: Option<&InstrumentId>,
6453        strategy_id: Option<&StrategyId>,
6454        account_id: Option<&AccountId>,
6455        side: Option<OrderSide>,
6456    ) -> usize {
6457        self.count_orders_in_bucket(
6458            &self.index.orders_open,
6459            venue,
6460            instrument_id,
6461            strategy_id,
6462            account_id,
6463            side,
6464        )
6465    }
6466
6467    /// Returns the count of all closed orders.
6468    #[must_use]
6469    pub fn orders_closed_count(
6470        &self,
6471        venue: Option<&Venue>,
6472        instrument_id: Option<&InstrumentId>,
6473        strategy_id: Option<&StrategyId>,
6474        account_id: Option<&AccountId>,
6475        side: Option<OrderSide>,
6476    ) -> usize {
6477        self.count_orders_in_bucket(
6478            &self.index.orders_closed,
6479            venue,
6480            instrument_id,
6481            strategy_id,
6482            account_id,
6483            side,
6484        )
6485    }
6486
6487    /// Returns the count of all locally active orders.
6488    ///
6489    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state
6490    /// (a superset of emulated orders).
6491    #[must_use]
6492    pub fn orders_active_local_count(
6493        &self,
6494        venue: Option<&Venue>,
6495        instrument_id: Option<&InstrumentId>,
6496        strategy_id: Option<&StrategyId>,
6497        account_id: Option<&AccountId>,
6498        side: Option<OrderSide>,
6499    ) -> usize {
6500        self.count_orders_in_bucket(
6501            &self.index.orders_active_local,
6502            venue,
6503            instrument_id,
6504            strategy_id,
6505            account_id,
6506            side,
6507        )
6508    }
6509
6510    /// Returns the count of all emulated orders.
6511    #[must_use]
6512    pub fn orders_emulated_count(
6513        &self,
6514        venue: Option<&Venue>,
6515        instrument_id: Option<&InstrumentId>,
6516        strategy_id: Option<&StrategyId>,
6517        account_id: Option<&AccountId>,
6518        side: Option<OrderSide>,
6519    ) -> usize {
6520        self.count_orders_in_bucket(
6521            &self.index.orders_emulated,
6522            venue,
6523            instrument_id,
6524            strategy_id,
6525            account_id,
6526            side,
6527        )
6528    }
6529
6530    /// Returns the count of all in-flight orders.
6531    #[must_use]
6532    pub fn orders_inflight_count(
6533        &self,
6534        venue: Option<&Venue>,
6535        instrument_id: Option<&InstrumentId>,
6536        strategy_id: Option<&StrategyId>,
6537        account_id: Option<&AccountId>,
6538        side: Option<OrderSide>,
6539    ) -> usize {
6540        self.count_orders_in_bucket(
6541            &self.index.orders_inflight,
6542            venue,
6543            instrument_id,
6544            strategy_id,
6545            account_id,
6546            side,
6547        )
6548    }
6549
6550    /// Returns the count of all orders.
6551    #[must_use]
6552    pub fn orders_total_count(
6553        &self,
6554        venue: Option<&Venue>,
6555        instrument_id: Option<&InstrumentId>,
6556        strategy_id: Option<&StrategyId>,
6557        account_id: Option<&AccountId>,
6558        side: Option<OrderSide>,
6559    ) -> usize {
6560        self.count_orders_in_bucket(
6561            &self.index.orders,
6562            venue,
6563            instrument_id,
6564            strategy_id,
6565            account_id,
6566            side,
6567        )
6568    }
6569
6570    /// Returns whether any open order matches the optional filter parameters.
6571    ///
6572    /// Short-circuits on the first match, avoiding the full intersection walk performed by
6573    /// [`Self::orders_open_count`]. Prefer this over `orders_open_count(...) > 0` when only
6574    /// existence matters.
6575    #[must_use]
6576    pub fn has_orders_open(
6577        &self,
6578        venue: Option<&Venue>,
6579        instrument_id: Option<&InstrumentId>,
6580        strategy_id: Option<&StrategyId>,
6581        account_id: Option<&AccountId>,
6582        side: Option<OrderSide>,
6583    ) -> bool {
6584        self.any_orders_in_bucket(
6585            &self.index.orders_open,
6586            venue,
6587            instrument_id,
6588            strategy_id,
6589            account_id,
6590            side,
6591        )
6592    }
6593
6594    /// Returns whether any closed order matches the optional filter parameters.
6595    #[must_use]
6596    pub fn has_orders_closed(
6597        &self,
6598        venue: Option<&Venue>,
6599        instrument_id: Option<&InstrumentId>,
6600        strategy_id: Option<&StrategyId>,
6601        account_id: Option<&AccountId>,
6602        side: Option<OrderSide>,
6603    ) -> bool {
6604        self.any_orders_in_bucket(
6605            &self.index.orders_closed,
6606            venue,
6607            instrument_id,
6608            strategy_id,
6609            account_id,
6610            side,
6611        )
6612    }
6613
6614    /// Returns whether any locally active order matches the optional filter parameters.
6615    ///
6616    /// Locally active orders are in the `INITIALIZED`, `EMULATED`, or `RELEASED` state.
6617    #[must_use]
6618    pub fn has_orders_active_local(
6619        &self,
6620        venue: Option<&Venue>,
6621        instrument_id: Option<&InstrumentId>,
6622        strategy_id: Option<&StrategyId>,
6623        account_id: Option<&AccountId>,
6624        side: Option<OrderSide>,
6625    ) -> bool {
6626        self.any_orders_in_bucket(
6627            &self.index.orders_active_local,
6628            venue,
6629            instrument_id,
6630            strategy_id,
6631            account_id,
6632            side,
6633        )
6634    }
6635
6636    /// Returns whether any emulated order matches the optional filter parameters.
6637    #[must_use]
6638    pub fn has_orders_emulated(
6639        &self,
6640        venue: Option<&Venue>,
6641        instrument_id: Option<&InstrumentId>,
6642        strategy_id: Option<&StrategyId>,
6643        account_id: Option<&AccountId>,
6644        side: Option<OrderSide>,
6645    ) -> bool {
6646        self.any_orders_in_bucket(
6647            &self.index.orders_emulated,
6648            venue,
6649            instrument_id,
6650            strategy_id,
6651            account_id,
6652            side,
6653        )
6654    }
6655
6656    /// Returns whether any in-flight order matches the optional filter parameters.
6657    #[must_use]
6658    pub fn has_orders_inflight(
6659        &self,
6660        venue: Option<&Venue>,
6661        instrument_id: Option<&InstrumentId>,
6662        strategy_id: Option<&StrategyId>,
6663        account_id: Option<&AccountId>,
6664        side: Option<OrderSide>,
6665    ) -> bool {
6666        self.any_orders_in_bucket(
6667            &self.index.orders_inflight,
6668            venue,
6669            instrument_id,
6670            strategy_id,
6671            account_id,
6672            side,
6673        )
6674    }
6675
6676    /// Returns whether any order (in any state) matches the optional filter parameters.
6677    #[must_use]
6678    pub fn has_orders(
6679        &self,
6680        venue: Option<&Venue>,
6681        instrument_id: Option<&InstrumentId>,
6682        strategy_id: Option<&StrategyId>,
6683        account_id: Option<&AccountId>,
6684        side: Option<OrderSide>,
6685    ) -> bool {
6686        self.any_orders_in_bucket(
6687            &self.index.orders,
6688            venue,
6689            instrument_id,
6690            strategy_id,
6691            account_id,
6692            side,
6693        )
6694    }
6695
6696    /// Returns the order list for the `order_list_id`.
6697    #[must_use]
6698    pub fn order_list(&self, order_list_id: &OrderListId) -> Option<&OrderList> {
6699        self.order_lists.get(order_list_id)
6700    }
6701
6702    /// Returns the order list for the `order_list_id`.
6703    ///
6704    /// # Errors
6705    ///
6706    /// Returns [`OrderListLookupError::NotFound`] when the order list is not present in the cache.
6707    pub fn try_order_list(
6708        &self,
6709        order_list_id: &OrderListId,
6710    ) -> Result<&OrderList, OrderListLookupError> {
6711        self.order_lists
6712            .get(order_list_id)
6713            .ok_or_else(|| OrderListLookupError::not_found(*order_list_id))
6714    }
6715
6716    /// Returns all order lists matching the optional filter parameters.
6717    #[must_use]
6718    pub fn order_lists(
6719        &self,
6720        venue: Option<&Venue>,
6721        instrument_id: Option<&InstrumentId>,
6722        strategy_id: Option<&StrategyId>,
6723        account_id: Option<&AccountId>,
6724    ) -> Vec<&OrderList> {
6725        let mut order_lists = self.order_lists.values().collect::<Vec<&OrderList>>();
6726
6727        if let Some(venue) = venue {
6728            order_lists.retain(|ol| &ol.instrument_id.venue == venue);
6729        }
6730
6731        if let Some(instrument_id) = instrument_id {
6732            order_lists.retain(|ol| &ol.instrument_id == instrument_id);
6733        }
6734
6735        if let Some(strategy_id) = strategy_id {
6736            order_lists.retain(|ol| &ol.strategy_id == strategy_id);
6737        }
6738
6739        if let Some(account_id) = account_id {
6740            order_lists.retain(|ol| {
6741                ol.client_order_ids.iter().any(|client_order_id| {
6742                    self.orders.get(client_order_id).is_some_and(|order_cell| {
6743                        order_cell.borrow().account_id().as_ref() == Some(account_id)
6744                    })
6745                })
6746            });
6747        }
6748
6749        order_lists
6750    }
6751
6752    /// Returns whether an order list with the `order_list_id` exists.
6753    #[must_use]
6754    pub fn order_list_exists(&self, order_list_id: &OrderListId) -> bool {
6755        self.order_lists.contains_key(order_list_id)
6756    }
6757
6758    // -- EXEC ALGORITHM QUERIES ------------------------------------------------------------------
6759
6760    /// Returns references to all orders associated with the `exec_algorithm_id` matching the
6761    /// optional filter parameters.
6762    #[must_use]
6763    pub fn orders_for_exec_algorithm(
6764        &self,
6765        exec_algorithm_id: &ExecAlgorithmId,
6766        venue: Option<&Venue>,
6767        instrument_id: Option<&InstrumentId>,
6768        strategy_id: Option<&StrategyId>,
6769        account_id: Option<&AccountId>,
6770        side: Option<OrderSide>,
6771    ) -> Vec<OrderRef<'_>> {
6772        let Some(exec_algorithm_order_ids) =
6773            self.index.exec_algorithm_orders.get(exec_algorithm_id)
6774        else {
6775            return Vec::new();
6776        };
6777
6778        let filtered = self.query_orders_in_bucket(
6779            exec_algorithm_order_ids,
6780            venue,
6781            instrument_id,
6782            strategy_id,
6783            account_id,
6784        );
6785        self.get_orders_for_ids(&filtered, side)
6786    }
6787
6788    /// Returns references to all orders with the `exec_spawn_id`.
6789    #[must_use]
6790    pub fn orders_for_exec_spawn(&self, exec_spawn_id: &ClientOrderId) -> Vec<OrderRef<'_>> {
6791        match self.index.exec_spawn_orders.get(exec_spawn_id) {
6792            Some(ids) => self.get_orders_for_ids(ids, None),
6793            None => Vec::new(),
6794        }
6795    }
6796
6797    /// Returns the total order quantity for the `exec_spawn_id`.
6798    #[must_use]
6799    pub fn exec_spawn_total_quantity(
6800        &self,
6801        exec_spawn_id: &ClientOrderId,
6802        active_only: bool,
6803    ) -> Option<Quantity> {
6804        let exec_spawn_orders = self.orders_for_exec_spawn(exec_spawn_id);
6805
6806        let mut total_quantity: Option<Quantity> = None;
6807
6808        for spawn_order in exec_spawn_orders {
6809            if active_only && spawn_order.is_closed() {
6810                continue;
6811            }
6812
6813            match total_quantity.as_mut() {
6814                Some(total) => *total = *total + spawn_order.quantity(),
6815                None => total_quantity = Some(spawn_order.quantity()),
6816            }
6817        }
6818
6819        total_quantity
6820    }
6821
6822    /// Returns the total filled quantity for all orders with the `exec_spawn_id`.
6823    #[must_use]
6824    pub fn exec_spawn_total_filled_qty(
6825        &self,
6826        exec_spawn_id: &ClientOrderId,
6827        active_only: bool,
6828    ) -> Option<Quantity> {
6829        let exec_spawn_orders = self.orders_for_exec_spawn(exec_spawn_id);
6830
6831        let mut total_quantity: Option<Quantity> = None;
6832
6833        for spawn_order in exec_spawn_orders {
6834            if active_only && spawn_order.is_closed() {
6835                continue;
6836            }
6837
6838            match total_quantity.as_mut() {
6839                Some(total) => *total = *total + spawn_order.filled_qty(),
6840                None => total_quantity = Some(spawn_order.filled_qty()),
6841            }
6842        }
6843
6844        total_quantity
6845    }
6846
6847    /// Returns the total leaves quantity for all orders with the `exec_spawn_id`.
6848    #[must_use]
6849    pub fn exec_spawn_total_leaves_qty(
6850        &self,
6851        exec_spawn_id: &ClientOrderId,
6852        active_only: bool,
6853    ) -> Option<Quantity> {
6854        let exec_spawn_orders = self.orders_for_exec_spawn(exec_spawn_id);
6855
6856        let mut total_quantity: Option<Quantity> = None;
6857
6858        for spawn_order in exec_spawn_orders {
6859            if active_only && spawn_order.is_closed() {
6860                continue;
6861            }
6862
6863            match total_quantity.as_mut() {
6864                Some(total) => *total = *total + spawn_order.leaves_qty(),
6865                None => total_quantity = Some(spawn_order.leaves_qty()),
6866            }
6867        }
6868
6869        total_quantity
6870    }
6871
6872    // -- POSITION QUERIES ------------------------------------------------------------------------
6873
6874    /// Returns a borrow of the position with the `position_id` (if found).
6875    #[must_use]
6876    pub fn position_ref(&self, position_id: &PositionId) -> Option<PositionRef<'_>> {
6877        self.positions
6878            .get(position_id)
6879            .map(|position_cell| PositionRef::new(position_cell.borrow()))
6880    }
6881
6882    /// Returns a borrow of the position with the `position_id` (if found).
6883    ///
6884    /// Prefer [`Self::position_ref`] in new native code.
6885    #[must_use]
6886    pub fn position(&self, position_id: &PositionId) -> Option<PositionRef<'_>> {
6887        self.position_ref(position_id)
6888    }
6889
6890    /// Returns a borrow of the position with the `position_id`.
6891    ///
6892    /// # Errors
6893    ///
6894    /// Returns [`PositionLookupError::NotFound`] when the position is not present in the cache.
6895    pub fn try_position_ref(
6896        &self,
6897        position_id: &PositionId,
6898    ) -> Result<PositionRef<'_>, PositionLookupError> {
6899        self.positions
6900            .get(position_id)
6901            .map(|position_cell| PositionRef::new(position_cell.borrow()))
6902            .ok_or_else(|| PositionLookupError::not_found(*position_id))
6903    }
6904
6905    /// Returns a borrow of the position with the `position_id`.
6906    ///
6907    /// Prefer [`Self::try_position_ref`] in new native code.
6908    ///
6909    /// # Errors
6910    ///
6911    /// Returns [`PositionLookupError::NotFound`] when the position is not present in the cache.
6912    pub fn try_position(
6913        &self,
6914        position_id: &PositionId,
6915    ) -> Result<PositionRef<'_>, PositionLookupError> {
6916        self.try_position_ref(position_id)
6917    }
6918
6919    /// Gets an exclusive write borrow of the position with the `position_id` (if found).
6920    ///
6921    /// Requires `&mut Cache` so cache writes are reachable only by privileged crates that hold
6922    /// `Rc<RefCell<Cache>>` directly. Adapter-facing code receives [`CacheView`], which only
6923    /// exposes immutable cache borrows and therefore cannot reach this method.
6924    ///
6925    /// While the returned [`PositionRefMut`] is alive, no other read or write of the same position
6926    /// is permitted. Drop the borrow before dispatching events or taking any other cache borrow
6927    /// that may re-enter the same position.
6928    #[must_use]
6929    pub fn position_mut(&mut self, position_id: &PositionId) -> Option<PositionRefMut<'_>> {
6930        self.positions
6931            .get(position_id)
6932            .map(|position_cell| PositionRefMut::new(position_cell.borrow_mut()))
6933    }
6934
6935    /// Gets an owned copy of the position with the `position_id` (if found).
6936    ///
6937    /// Use when downstream needs an owned [`Position`] that crosses a boundary. The copy will not
6938    /// reflect later cache mutations.
6939    #[must_use]
6940    pub fn position_owned(&self, position_id: &PositionId) -> Option<Position> {
6941        self.positions
6942            .get(position_id)
6943            .map(|position_cell| position_cell.borrow().clone())
6944    }
6945
6946    /// Returns a borrow of the position for the `client_order_id` (if found).
6947    #[must_use]
6948    pub fn position_for_order_ref(
6949        &self,
6950        client_order_id: &ClientOrderId,
6951    ) -> Option<PositionRef<'_>> {
6952        self.index
6953            .order_position
6954            .get(client_order_id)
6955            .and_then(|position_id| self.positions.get(position_id))
6956            .map(|position_cell| PositionRef::new(position_cell.borrow()))
6957    }
6958
6959    /// Returns a borrow of the position for the `client_order_id` (if found).
6960    ///
6961    /// Prefer [`Self::position_for_order_ref`] in new native code.
6962    #[must_use]
6963    pub fn position_for_order(&self, client_order_id: &ClientOrderId) -> Option<PositionRef<'_>> {
6964        self.position_for_order_ref(client_order_id)
6965    }
6966
6967    /// Returns a reference to the position ID for the `client_order_id` (if found).
6968    #[must_use]
6969    pub fn position_id(&self, client_order_id: &ClientOrderId) -> Option<&PositionId> {
6970        self.index.order_position.get(client_order_id)
6971    }
6972
6973    /// Returns borrows of all positions matching the optional filter parameters.
6974    ///
6975    /// Each [`PositionRef`] in the returned vector borrows its underlying cell; mutating any of
6976    /// those positions while the vector is alive will panic at runtime. Drop the vector before
6977    /// issuing writes.
6978    #[must_use]
6979    pub fn positions_refs(
6980        &self,
6981        venue: Option<&Venue>,
6982        instrument_id: Option<&InstrumentId>,
6983        strategy_id: Option<&StrategyId>,
6984        account_id: Option<&AccountId>,
6985        side: Option<PositionSide>,
6986    ) -> Vec<PositionRef<'_>> {
6987        let position_ids = self.position_ids(venue, instrument_id, strategy_id, account_id);
6988        self.get_positions_for_ids(&position_ids, side)
6989    }
6990
6991    /// Returns borrows of all positions matching the optional filter parameters.
6992    ///
6993    /// Prefer [`Self::positions_refs`] in new native code.
6994    #[must_use]
6995    pub fn positions(
6996        &self,
6997        venue: Option<&Venue>,
6998        instrument_id: Option<&InstrumentId>,
6999        strategy_id: Option<&StrategyId>,
7000        account_id: Option<&AccountId>,
7001        side: Option<PositionSide>,
7002    ) -> Vec<PositionRef<'_>> {
7003        self.positions_refs(venue, instrument_id, strategy_id, account_id, side)
7004    }
7005
7006    /// Returns borrows of all open positions matching the optional filter parameters.
7007    #[must_use]
7008    pub fn positions_open_refs(
7009        &self,
7010        venue: Option<&Venue>,
7011        instrument_id: Option<&InstrumentId>,
7012        strategy_id: Option<&StrategyId>,
7013        account_id: Option<&AccountId>,
7014        side: Option<PositionSide>,
7015    ) -> Vec<PositionRef<'_>> {
7016        let position_ids = self.position_open_ids(venue, instrument_id, strategy_id, account_id);
7017        self.get_positions_for_ids(&position_ids, side)
7018    }
7019
7020    /// Returns borrows of all open positions matching the optional filter parameters.
7021    ///
7022    /// Prefer [`Self::positions_open_refs`] in new native code.
7023    #[must_use]
7024    pub fn positions_open(
7025        &self,
7026        venue: Option<&Venue>,
7027        instrument_id: Option<&InstrumentId>,
7028        strategy_id: Option<&StrategyId>,
7029        account_id: Option<&AccountId>,
7030        side: Option<PositionSide>,
7031    ) -> Vec<PositionRef<'_>> {
7032        self.positions_open_refs(venue, instrument_id, strategy_id, account_id, side)
7033    }
7034
7035    /// Returns borrows of all closed positions matching the optional filter parameters.
7036    #[must_use]
7037    pub fn positions_closed_refs(
7038        &self,
7039        venue: Option<&Venue>,
7040        instrument_id: Option<&InstrumentId>,
7041        strategy_id: Option<&StrategyId>,
7042        account_id: Option<&AccountId>,
7043        side: Option<PositionSide>,
7044    ) -> Vec<PositionRef<'_>> {
7045        let position_ids = self.position_closed_ids(venue, instrument_id, strategy_id, account_id);
7046        self.get_positions_for_ids(&position_ids, side)
7047    }
7048
7049    /// Returns borrows of all closed positions matching the optional filter parameters.
7050    ///
7051    /// Prefer [`Self::positions_closed_refs`] in new native code.
7052    #[must_use]
7053    pub fn positions_closed(
7054        &self,
7055        venue: Option<&Venue>,
7056        instrument_id: Option<&InstrumentId>,
7057        strategy_id: Option<&StrategyId>,
7058        account_id: Option<&AccountId>,
7059        side: Option<PositionSide>,
7060    ) -> Vec<PositionRef<'_>> {
7061        self.positions_closed_refs(venue, instrument_id, strategy_id, account_id, side)
7062    }
7063
7064    /// Returns whether a position with the `position_id` exists.
7065    #[must_use]
7066    pub fn position_exists(&self, position_id: &PositionId) -> bool {
7067        self.index.positions.contains(position_id)
7068    }
7069
7070    /// Returns whether a position with the `position_id` is open.
7071    #[must_use]
7072    pub fn is_position_open(&self, position_id: &PositionId) -> bool {
7073        self.index.positions_open.contains(position_id)
7074    }
7075
7076    /// Returns whether a position with the `position_id` is closed.
7077    #[must_use]
7078    pub fn is_position_closed(&self, position_id: &PositionId) -> bool {
7079        self.index.positions_closed.contains(position_id)
7080    }
7081
7082    /// Returns the count of all open positions.
7083    #[must_use]
7084    pub fn positions_open_count(
7085        &self,
7086        venue: Option<&Venue>,
7087        instrument_id: Option<&InstrumentId>,
7088        strategy_id: Option<&StrategyId>,
7089        account_id: Option<&AccountId>,
7090        side: Option<PositionSide>,
7091    ) -> usize {
7092        self.count_positions_in_bucket(
7093            &self.index.positions_open,
7094            venue,
7095            instrument_id,
7096            strategy_id,
7097            account_id,
7098            side,
7099        )
7100    }
7101
7102    /// Returns the count of all closed positions.
7103    #[must_use]
7104    pub fn positions_closed_count(
7105        &self,
7106        venue: Option<&Venue>,
7107        instrument_id: Option<&InstrumentId>,
7108        strategy_id: Option<&StrategyId>,
7109        account_id: Option<&AccountId>,
7110        side: Option<PositionSide>,
7111    ) -> usize {
7112        self.count_positions_in_bucket(
7113            &self.index.positions_closed,
7114            venue,
7115            instrument_id,
7116            strategy_id,
7117            account_id,
7118            side,
7119        )
7120    }
7121
7122    /// Returns the count of all positions.
7123    #[must_use]
7124    pub fn positions_total_count(
7125        &self,
7126        venue: Option<&Venue>,
7127        instrument_id: Option<&InstrumentId>,
7128        strategy_id: Option<&StrategyId>,
7129        account_id: Option<&AccountId>,
7130        side: Option<PositionSide>,
7131    ) -> usize {
7132        self.count_positions_in_bucket(
7133            &self.index.positions,
7134            venue,
7135            instrument_id,
7136            strategy_id,
7137            account_id,
7138            side,
7139        )
7140    }
7141
7142    /// Returns whether any open position matches the optional filter parameters.
7143    ///
7144    /// Short-circuits on the first match, avoiding the full intersection walk performed by
7145    /// [`Self::positions_open_count`]. Prefer this over `positions_open_count(...) > 0` when
7146    /// only existence matters.
7147    #[must_use]
7148    pub fn has_positions_open(
7149        &self,
7150        venue: Option<&Venue>,
7151        instrument_id: Option<&InstrumentId>,
7152        strategy_id: Option<&StrategyId>,
7153        account_id: Option<&AccountId>,
7154        side: Option<PositionSide>,
7155    ) -> bool {
7156        self.any_positions_in_bucket(
7157            &self.index.positions_open,
7158            venue,
7159            instrument_id,
7160            strategy_id,
7161            account_id,
7162            side,
7163        )
7164    }
7165
7166    /// Returns whether any closed position matches the optional filter parameters.
7167    #[must_use]
7168    pub fn has_positions_closed(
7169        &self,
7170        venue: Option<&Venue>,
7171        instrument_id: Option<&InstrumentId>,
7172        strategy_id: Option<&StrategyId>,
7173        account_id: Option<&AccountId>,
7174        side: Option<PositionSide>,
7175    ) -> bool {
7176        self.any_positions_in_bucket(
7177            &self.index.positions_closed,
7178            venue,
7179            instrument_id,
7180            strategy_id,
7181            account_id,
7182            side,
7183        )
7184    }
7185
7186    /// Returns whether any position (open or closed) matches the optional filter parameters.
7187    #[must_use]
7188    pub fn has_positions(
7189        &self,
7190        venue: Option<&Venue>,
7191        instrument_id: Option<&InstrumentId>,
7192        strategy_id: Option<&StrategyId>,
7193        account_id: Option<&AccountId>,
7194        side: Option<PositionSide>,
7195    ) -> bool {
7196        self.any_positions_in_bucket(
7197            &self.index.positions,
7198            venue,
7199            instrument_id,
7200            strategy_id,
7201            account_id,
7202            side,
7203        )
7204    }
7205
7206    // -- STRATEGY QUERIES ------------------------------------------------------------------------
7207
7208    /// Gets a reference to the strategy ID for the `client_order_id` (if found).
7209    #[must_use]
7210    pub fn strategy_id_for_order(&self, client_order_id: &ClientOrderId) -> Option<&StrategyId> {
7211        self.index.order_strategy.get(client_order_id)
7212    }
7213
7214    /// Gets a reference to the strategy ID for the `position_id` (if found).
7215    #[must_use]
7216    pub fn strategy_id_for_position(&self, position_id: &PositionId) -> Option<&StrategyId> {
7217        self.index.position_strategy.get(position_id)
7218    }
7219
7220    // -- GENERAL ---------------------------------------------------------------------------------
7221
7222    /// Gets a reference to the general value for the `key` (if found).
7223    ///
7224    /// # Errors
7225    ///
7226    /// Returns an error if the `key` is invalid.
7227    pub fn get(&self, key: &str) -> anyhow::Result<Option<&Bytes>> {
7228        check_valid_string_ascii(key, stringify!(key))?;
7229
7230        Ok(self.general.get(key))
7231    }
7232
7233    // -- DATA QUERIES ----------------------------------------------------------------------------
7234
7235    /// Returns the price for the `instrument_id` and `price_type` (if found).
7236    ///
7237    /// # Panics
7238    ///
7239    /// Panics if `price_type` is [`PriceType::Mid`] and the quote price precision is already at
7240    /// the maximum fixed precision.
7241    #[must_use]
7242    pub fn price(&self, instrument_id: &InstrumentId, price_type: PriceType) -> Option<Price> {
7243        match price_type {
7244            PriceType::Bid => self
7245                .quotes
7246                .get(instrument_id)
7247                .and_then(|quotes| quotes.front().map(|quote| quote.bid_price)),
7248            PriceType::Ask => self
7249                .quotes
7250                .get(instrument_id)
7251                .and_then(|quotes| quotes.front().map(|quote| quote.ask_price)),
7252            PriceType::Mid => self.quotes.get(instrument_id).and_then(|quotes| {
7253                quotes.front().map(|quote| {
7254                    let mid = (quote.ask_price.as_decimal() + quote.bid_price.as_decimal())
7255                        / Decimal::TWO;
7256
7257                    Price::from_decimal_dp(mid, quote.bid_price.precision + 1)
7258                        .expect("Invalid mid price for Cache::price")
7259                })
7260            }),
7261            PriceType::Last => self
7262                .trades
7263                .get(instrument_id)
7264                .and_then(|trades| trades.front().map(|trade| trade.price)),
7265            PriceType::Mark => self
7266                .mark_prices
7267                .get(instrument_id)
7268                .and_then(|marks| marks.front().map(|mark| mark.value)),
7269        }
7270    }
7271
7272    /// Gets all quotes for the `instrument_id`.
7273    #[must_use]
7274    pub fn quotes(&self, instrument_id: &InstrumentId) -> Option<Vec<QuoteTick>> {
7275        self.quotes
7276            .get(instrument_id)
7277            .map(|quotes| quotes.iter().copied().collect())
7278    }
7279
7280    /// Gets all trades for the `instrument_id`.
7281    #[must_use]
7282    pub fn trades(&self, instrument_id: &InstrumentId) -> Option<Vec<TradeTick>> {
7283        self.trades
7284            .get(instrument_id)
7285            .map(|trades| trades.iter().copied().collect())
7286    }
7287
7288    /// Gets all mark price updates for the `instrument_id`.
7289    #[must_use]
7290    pub fn mark_prices(&self, instrument_id: &InstrumentId) -> Option<Vec<MarkPriceUpdate>> {
7291        self.mark_prices
7292            .get(instrument_id)
7293            .map(|mark_prices| mark_prices.iter().copied().collect())
7294    }
7295
7296    /// Gets all index price updates for the `instrument_id`.
7297    #[must_use]
7298    pub fn index_prices(&self, instrument_id: &InstrumentId) -> Option<Vec<IndexPriceUpdate>> {
7299        self.index_prices
7300            .get(instrument_id)
7301            .map(|index_prices| index_prices.iter().copied().collect())
7302    }
7303
7304    /// Gets all funding rate updates for the `instrument_id`.
7305    #[must_use]
7306    pub fn funding_rates(&self, instrument_id: &InstrumentId) -> Option<Vec<FundingRateUpdate>> {
7307        self.funding_rates
7308            .get(instrument_id)
7309            .map(|funding_rates| funding_rates.iter().copied().collect())
7310    }
7311
7312    /// Gets all instrument status updates for the `instrument_id`.
7313    #[must_use]
7314    pub fn instrument_statuses(
7315        &self,
7316        instrument_id: &InstrumentId,
7317    ) -> Option<Vec<InstrumentStatus>> {
7318        self.instrument_statuses
7319            .get(instrument_id)
7320            .map(|statuses| statuses.iter().copied().collect())
7321    }
7322
7323    /// Gets all bars for the `bar_type`.
7324    #[must_use]
7325    pub fn bars(&self, bar_type: &BarType) -> Option<Vec<Bar>> {
7326        self.bars
7327            .get(bar_type)
7328            .map(|bars| bars.iter().copied().collect())
7329    }
7330
7331    /// Gets a reference to the order book for the `instrument_id`.
7332    #[must_use]
7333    pub fn order_book(&self, instrument_id: &InstrumentId) -> Option<&OrderBook> {
7334        self.books.get(instrument_id)
7335    }
7336
7337    /// Gets a reference to the order book for the `instrument_id`.
7338    ///
7339    /// # Errors
7340    ///
7341    /// Returns [`OrderBookLookupError::NotFound`] when the order book is not present in the cache.
7342    pub fn try_order_book(
7343        &self,
7344        instrument_id: &InstrumentId,
7345    ) -> Result<&OrderBook, OrderBookLookupError> {
7346        self.books
7347            .get(instrument_id)
7348            .ok_or_else(|| OrderBookLookupError::not_found(*instrument_id))
7349    }
7350
7351    /// Gets a reference to the order book for the `instrument_id`.
7352    #[must_use]
7353    pub fn order_book_mut(&mut self, instrument_id: &InstrumentId) -> Option<&mut OrderBook> {
7354        self.books.get_mut(instrument_id)
7355    }
7356
7357    /// Gets a reference to the own order book for the `instrument_id`.
7358    #[must_use]
7359    pub fn own_order_book(&self, instrument_id: &InstrumentId) -> Option<&OwnOrderBook> {
7360        self.own_books.get(instrument_id)
7361    }
7362
7363    /// Gets a reference to the own order book for the `instrument_id`.
7364    ///
7365    /// # Errors
7366    ///
7367    /// Returns [`OwnOrderBookLookupError::NotFound`] when the own order book is not present in the
7368    /// cache.
7369    pub fn try_own_order_book(
7370        &self,
7371        instrument_id: &InstrumentId,
7372    ) -> Result<&OwnOrderBook, OwnOrderBookLookupError> {
7373        self.own_books
7374            .get(instrument_id)
7375            .ok_or_else(|| OwnOrderBookLookupError::not_found(*instrument_id))
7376    }
7377
7378    /// Gets a reference to the own order book for the `instrument_id`.
7379    #[must_use]
7380    pub fn own_order_book_mut(
7381        &mut self,
7382        instrument_id: &InstrumentId,
7383    ) -> Option<&mut OwnOrderBook> {
7384        self.own_books.get_mut(instrument_id)
7385    }
7386
7387    /// Gets a reference to the latest quote for the `instrument_id`.
7388    #[must_use]
7389    pub fn quote(&self, instrument_id: &InstrumentId) -> Option<&QuoteTick> {
7390        self.quotes
7391            .get(instrument_id)
7392            .and_then(|quotes| quotes.front())
7393    }
7394
7395    /// Gets a reference to the quote at `index` for the `instrument_id`.
7396    ///
7397    /// Index 0 is the most recent.
7398    #[must_use]
7399    pub fn quote_at_index(&self, instrument_id: &InstrumentId, index: usize) -> Option<&QuoteTick> {
7400        self.quotes
7401            .get(instrument_id)
7402            .and_then(|quotes| quotes.get(index))
7403    }
7404
7405    /// Gets a reference to the latest trade for the `instrument_id`.
7406    #[must_use]
7407    pub fn trade(&self, instrument_id: &InstrumentId) -> Option<&TradeTick> {
7408        self.trades
7409            .get(instrument_id)
7410            .and_then(|trades| trades.front())
7411    }
7412
7413    /// Gets a reference to the trade at `index` for the `instrument_id`.
7414    ///
7415    /// Index 0 is the most recent.
7416    #[must_use]
7417    pub fn trade_at_index(&self, instrument_id: &InstrumentId, index: usize) -> Option<&TradeTick> {
7418        self.trades
7419            .get(instrument_id)
7420            .and_then(|trades| trades.get(index))
7421    }
7422
7423    /// Gets a reference to the latest mark price update for the `instrument_id`.
7424    #[must_use]
7425    pub fn mark_price(&self, instrument_id: &InstrumentId) -> Option<&MarkPriceUpdate> {
7426        self.mark_prices
7427            .get(instrument_id)
7428            .and_then(|mark_prices| mark_prices.front())
7429    }
7430
7431    /// Gets a reference to the latest index price update for the `instrument_id`.
7432    #[must_use]
7433    pub fn index_price(&self, instrument_id: &InstrumentId) -> Option<&IndexPriceUpdate> {
7434        self.index_prices
7435            .get(instrument_id)
7436            .and_then(|index_prices| index_prices.front())
7437    }
7438
7439    /// Gets a reference to the latest funding rate update for the `instrument_id`.
7440    #[must_use]
7441    pub fn funding_rate(&self, instrument_id: &InstrumentId) -> Option<&FundingRateUpdate> {
7442        self.funding_rates
7443            .get(instrument_id)
7444            .and_then(|funding_rates| funding_rates.front())
7445    }
7446
7447    /// Gets a reference to the latest instrument status update for the `instrument_id`.
7448    #[must_use]
7449    pub fn instrument_status(&self, instrument_id: &InstrumentId) -> Option<&InstrumentStatus> {
7450        self.instrument_statuses
7451            .get(instrument_id)
7452            .and_then(|statuses| statuses.front())
7453    }
7454
7455    /// Gets a reference to the latest bar for the `bar_type`.
7456    #[must_use]
7457    pub fn bar(&self, bar_type: &BarType) -> Option<&Bar> {
7458        self.bars.get(bar_type).and_then(|bars| bars.front())
7459    }
7460
7461    /// Gets a reference to the bar at `index` for the `bar_type`.
7462    ///
7463    /// Index 0 is the most recent.
7464    #[must_use]
7465    pub fn bar_at_index(&self, bar_type: &BarType, index: usize) -> Option<&Bar> {
7466        self.bars.get(bar_type).and_then(|bars| bars.get(index))
7467    }
7468
7469    /// Gets the order book update count for the `instrument_id`.
7470    #[must_use]
7471    pub fn book_update_count(&self, instrument_id: &InstrumentId) -> usize {
7472        self.books
7473            .get(instrument_id)
7474            .map_or(0, |book| book.update_count) as usize
7475    }
7476
7477    /// Gets the quote tick count for the `instrument_id`.
7478    #[must_use]
7479    pub fn quote_count(&self, instrument_id: &InstrumentId) -> usize {
7480        self.quotes
7481            .get(instrument_id)
7482            .map_or(0, BoundedVecDeque::len)
7483    }
7484
7485    /// Gets the trade tick count for the `instrument_id`.
7486    #[must_use]
7487    pub fn trade_count(&self, instrument_id: &InstrumentId) -> usize {
7488        self.trades
7489            .get(instrument_id)
7490            .map_or(0, BoundedVecDeque::len)
7491    }
7492
7493    /// Gets the mark price update count for the `instrument_id`.
7494    #[must_use]
7495    pub fn mark_price_count(&self, instrument_id: &InstrumentId) -> usize {
7496        self.mark_prices
7497            .get(instrument_id)
7498            .map_or(0, BoundedVecDeque::len)
7499    }
7500
7501    /// Gets the index price update count for the `instrument_id`.
7502    #[must_use]
7503    pub fn index_price_count(&self, instrument_id: &InstrumentId) -> usize {
7504        self.index_prices
7505            .get(instrument_id)
7506            .map_or(0, BoundedVecDeque::len)
7507    }
7508
7509    /// Gets the funding rate update count for the `instrument_id`.
7510    #[must_use]
7511    pub fn funding_rate_count(&self, instrument_id: &InstrumentId) -> usize {
7512        self.funding_rates
7513            .get(instrument_id)
7514            .map_or(0, BoundedVecDeque::len)
7515    }
7516
7517    /// Gets the instrument status update count for the `instrument_id`.
7518    #[must_use]
7519    pub fn instrument_status_count(&self, instrument_id: &InstrumentId) -> usize {
7520        self.instrument_statuses
7521            .get(instrument_id)
7522            .map_or(0, BoundedVecDeque::len)
7523    }
7524
7525    /// Gets the bar count for the `instrument_id`.
7526    #[must_use]
7527    pub fn bar_count(&self, bar_type: &BarType) -> usize {
7528        self.bars.get(bar_type).map_or(0, BoundedVecDeque::len)
7529    }
7530
7531    /// Returns whether the cache contains an order book for the `instrument_id`.
7532    #[must_use]
7533    pub fn has_order_book(&self, instrument_id: &InstrumentId) -> bool {
7534        self.books.contains_key(instrument_id)
7535    }
7536
7537    /// Returns whether the cache contains quotes for the `instrument_id`.
7538    #[must_use]
7539    pub fn has_quote_ticks(&self, instrument_id: &InstrumentId) -> bool {
7540        self.quote_count(instrument_id) > 0
7541    }
7542
7543    /// Returns whether the cache contains trades for the `instrument_id`.
7544    #[must_use]
7545    pub fn has_trade_ticks(&self, instrument_id: &InstrumentId) -> bool {
7546        self.trade_count(instrument_id) > 0
7547    }
7548
7549    /// Returns whether the cache contains mark price updates for the `instrument_id`.
7550    #[must_use]
7551    pub fn has_mark_prices(&self, instrument_id: &InstrumentId) -> bool {
7552        self.mark_price_count(instrument_id) > 0
7553    }
7554
7555    /// Returns whether the cache contains index price updates for the `instrument_id`.
7556    #[must_use]
7557    pub fn has_index_prices(&self, instrument_id: &InstrumentId) -> bool {
7558        self.index_price_count(instrument_id) > 0
7559    }
7560
7561    /// Returns whether the cache contains funding rate updates for the `instrument_id`.
7562    #[must_use]
7563    pub fn has_funding_rates(&self, instrument_id: &InstrumentId) -> bool {
7564        self.funding_rate_count(instrument_id) > 0
7565    }
7566
7567    /// Returns whether the cache contains instrument status updates for the `instrument_id`.
7568    #[must_use]
7569    pub fn has_instrument_statuses(&self, instrument_id: &InstrumentId) -> bool {
7570        self.instrument_status_count(instrument_id) > 0
7571    }
7572
7573    /// Returns whether the cache contains bars for the `bar_type`.
7574    #[must_use]
7575    pub fn has_bars(&self, bar_type: &BarType) -> bool {
7576        self.bar_count(bar_type) > 0
7577    }
7578
7579    #[must_use]
7580    pub fn get_xrate(
7581        &self,
7582        venue: Venue,
7583        from_currency: Currency,
7584        to_currency: Currency,
7585        price_type: PriceType,
7586    ) -> Option<Decimal> {
7587        match self.try_get_xrate(venue, from_currency, to_currency, price_type) {
7588            Ok(rate) => rate,
7589            Err(e) => {
7590                log::error!("Failed to calculate xrate: {e}");
7591                None
7592            }
7593        }
7594    }
7595
7596    /// Tries to calculate the exchange rate without logging calculation errors.
7597    ///
7598    /// # Errors
7599    ///
7600    /// Returns an error when the cached quotes cannot form a valid exchange
7601    /// rate calculation.
7602    pub fn try_get_xrate(
7603        &self,
7604        venue: Venue,
7605        from_currency: Currency,
7606        to_currency: Currency,
7607        price_type: PriceType,
7608    ) -> anyhow::Result<Option<Decimal>> {
7609        if from_currency == to_currency {
7610            // When the source and target currencies are identical,
7611            // no conversion is needed; return an exchange rate of one.
7612            return Ok(Some(Decimal::ONE));
7613        }
7614
7615        let (bid_quote, ask_quote) = self.build_quote_table(&venue);
7616
7617        get_exchange_rate(
7618            from_currency.code,
7619            to_currency.code,
7620            price_type,
7621            bid_quote,
7622            ask_quote,
7623        )
7624    }
7625
7626    fn build_quote_table(
7627        &self,
7628        venue: &Venue,
7629    ) -> (AHashMap<Ustr, Decimal>, AHashMap<Ustr, Decimal>) {
7630        let mut bid_quotes = AHashMap::new();
7631        let mut ask_quotes = AHashMap::new();
7632
7633        for instrument_id in self.instruments.keys() {
7634            if instrument_id.venue != *venue {
7635                continue;
7636            }
7637
7638            let (bid_price, ask_price) = if let Some(ticks) = self.quotes.get(instrument_id) {
7639                if let Some(tick) = ticks.front() {
7640                    (tick.bid_price, tick.ask_price)
7641                } else {
7642                    continue; // Empty ticks vector
7643                }
7644            } else {
7645                // Multiple bar types may exist per instrument: select the most recently added
7646                // bar per side, preferring the greatest ts_init for determinism and breaking
7647                // ties by bar type.
7648                let mut latest_bid: Option<(&BarType, &Bar)> = None;
7649                let mut latest_ask: Option<(&BarType, &Bar)> = None;
7650
7651                for (bar_type, bars) in &self.bars {
7652                    if bar_type.instrument_id() != *instrument_id {
7653                        continue;
7654                    }
7655
7656                    let Some(bar) = bars.front() else {
7657                        continue;
7658                    };
7659
7660                    let slot = match bar_type.spec().price_type {
7661                        PriceType::Bid => &mut latest_bid,
7662                        PriceType::Ask => &mut latest_ask,
7663                        _ => continue,
7664                    };
7665
7666                    if slot.is_none_or(|(current_type, current)| {
7667                        (current.ts_init, current_type) < (bar.ts_init, bar_type)
7668                    }) {
7669                        *slot = Some((bar_type, bar));
7670                    }
7671                }
7672
7673                match (latest_bid, latest_ask) {
7674                    (Some((_, bid_bar)), Some((_, ask_bar))) => (bid_bar.close, ask_bar.close),
7675                    _ => continue,
7676                }
7677            };
7678
7679            bid_quotes.insert(instrument_id.symbol.inner(), bid_price.as_decimal());
7680            ask_quotes.insert(instrument_id.symbol.inner(), ask_price.as_decimal());
7681        }
7682
7683        (bid_quotes, ask_quotes)
7684    }
7685
7686    /// Returns the mark exchange rate for the given currency pair, or `None` if not set.
7687    #[must_use]
7688    pub fn get_mark_xrate(&self, from_currency: Currency, to_currency: Currency) -> Option<f64> {
7689        self.mark_xrates.get(&(from_currency, to_currency)).copied()
7690    }
7691
7692    /// Sets the mark exchange rate for the given currency pair and automatically sets the inverse rate.
7693    ///
7694    /// # Panics
7695    ///
7696    /// Panics if `xrate` is not positive.
7697    pub fn set_mark_xrate(&mut self, from_currency: Currency, to_currency: Currency, xrate: f64) {
7698        assert!(xrate > 0.0, "xrate was zero");
7699        self.mark_xrates.insert((from_currency, to_currency), xrate);
7700        self.mark_xrates
7701            .insert((to_currency, from_currency), 1.0 / xrate);
7702    }
7703
7704    /// Clears the mark exchange rate for the given currency pair direction.
7705    ///
7706    /// Removes only the `(from_currency, to_currency)` entry; the inverse rate written
7707    /// by [`Self::set_mark_xrate`] is retained until cleared separately or
7708    /// [`Self::clear_mark_xrates`] is called.
7709    pub fn clear_mark_xrate(&mut self, from_currency: Currency, to_currency: Currency) {
7710        let _ = self.mark_xrates.remove(&(from_currency, to_currency));
7711    }
7712
7713    /// Clears all mark exchange rates.
7714    pub fn clear_mark_xrates(&mut self) {
7715        self.mark_xrates.clear();
7716    }
7717
7718    /// Returns a reference to the currency for the `code` (if found).
7719    #[must_use]
7720    pub fn currency(&self, code: &Ustr) -> Option<&Currency> {
7721        self.currencies.get(code)
7722    }
7723
7724    /// Returns a reference to the currency for the `code`.
7725    ///
7726    /// # Errors
7727    ///
7728    /// Returns [`CurrencyLookupError::NotFound`] when the currency is not present in the cache.
7729    pub fn try_currency(&self, code: &Ustr) -> Result<&Currency, CurrencyLookupError> {
7730        self.currencies
7731            .get(code)
7732            .ok_or_else(|| CurrencyLookupError::not_found(*code))
7733    }
7734
7735    // -- INSTRUMENT QUERIES ----------------------------------------------------------------------
7736
7737    /// Returns a reference to the instrument for the `instrument_id` (if found).
7738    #[must_use]
7739    pub fn instrument(&self, instrument_id: &InstrumentId) -> Option<&InstrumentAny> {
7740        self.instruments.get(instrument_id)
7741    }
7742
7743    /// Returns a reference to the instrument for the `instrument_id`.
7744    ///
7745    /// # Errors
7746    ///
7747    /// Returns [`InstrumentLookupError::NotFound`] when the instrument is not present in the cache.
7748    pub fn try_instrument(
7749        &self,
7750        instrument_id: &InstrumentId,
7751    ) -> Result<&InstrumentAny, InstrumentLookupError> {
7752        self.instruments
7753            .get(instrument_id)
7754            .ok_or_else(|| InstrumentLookupError::not_found(*instrument_id))
7755    }
7756
7757    /// Returns references to all instrument IDs for the `venue`.
7758    #[must_use]
7759    pub fn instrument_ids(&self, venue: Option<&Venue>) -> Vec<&InstrumentId> {
7760        match venue {
7761            Some(v) => self.instruments.keys().filter(|i| &i.venue == v).collect(),
7762            None => self.instruments.keys().collect(),
7763        }
7764    }
7765
7766    /// Returns references to all instruments for the `venue`.
7767    #[must_use]
7768    pub fn instruments(&self, venue: &Venue, underlying: Option<&Ustr>) -> Vec<&InstrumentAny> {
7769        self.instruments
7770            .values()
7771            .filter(|i| &i.id().venue == venue)
7772            .filter(|i| underlying.is_none_or(|u| i.underlying() == Some(*u)))
7773            .collect()
7774    }
7775
7776    /// Returns references to all instruments for the `venue` whose underlying
7777    /// equals `root` and whose [`InstrumentClass`] equals `class`.
7778    ///
7779    /// Use when expanding a parent-symbol subscription: filtering by class as
7780    /// well as root prevents leaves of a different class (e.g. options when
7781    /// the user asked for futures, or vice versa) from being pulled in.
7782    #[must_use]
7783    pub fn instruments_by_parent(
7784        &self,
7785        venue: &Venue,
7786        root: &Ustr,
7787        class: InstrumentClass,
7788    ) -> Vec<&InstrumentAny> {
7789        self.instruments
7790            .values()
7791            .filter(|i| &i.id().venue == venue)
7792            .filter(|i| i.underlying() == Some(*root))
7793            .filter(|i| i.instrument_class() == class)
7794            .collect()
7795    }
7796
7797    /// Returns references to all bar types contained in the cache.
7798    #[must_use]
7799    pub fn bar_types(
7800        &self,
7801        instrument_id: Option<&InstrumentId>,
7802        price_type: Option<&PriceType>,
7803        aggregation_source: AggregationSource,
7804    ) -> Vec<&BarType> {
7805        let mut bar_types = self
7806            .bars
7807            .keys()
7808            .filter(|bar_type| bar_type.aggregation_source() == aggregation_source)
7809            .collect::<Vec<&BarType>>();
7810
7811        if let Some(instrument_id) = instrument_id {
7812            bar_types.retain(|bar_type| bar_type.instrument_id() == *instrument_id);
7813        }
7814
7815        if let Some(price_type) = price_type {
7816            bar_types.retain(|bar_type| &bar_type.spec().price_type == price_type);
7817        }
7818
7819        bar_types
7820    }
7821
7822    // -- SYNTHETIC QUERIES -----------------------------------------------------------------------
7823
7824    /// Returns a reference to the synthetic instrument for the `instrument_id` (if found).
7825    #[must_use]
7826    pub fn synthetic(&self, instrument_id: &InstrumentId) -> Option<&SyntheticInstrument> {
7827        self.synthetics.get(instrument_id)
7828    }
7829
7830    /// Returns a reference to the synthetic instrument for the `instrument_id`.
7831    ///
7832    /// # Errors
7833    ///
7834    /// Returns [`SyntheticInstrumentLookupError::NotFound`] when the synthetic instrument is not
7835    /// present in the cache.
7836    pub fn try_synthetic(
7837        &self,
7838        instrument_id: &InstrumentId,
7839    ) -> Result<&SyntheticInstrument, SyntheticInstrumentLookupError> {
7840        self.synthetics
7841            .get(instrument_id)
7842            .ok_or_else(|| SyntheticInstrumentLookupError::not_found(*instrument_id))
7843    }
7844
7845    /// Returns references to instrument IDs for all synthetic instruments contained in the cache.
7846    #[must_use]
7847    pub fn synthetic_ids(&self) -> Vec<&InstrumentId> {
7848        self.synthetics.keys().collect()
7849    }
7850
7851    /// Returns references to all synthetic instruments contained in the cache.
7852    #[must_use]
7853    pub fn synthetics(&self) -> Vec<&SyntheticInstrument> {
7854        self.synthetics.values().collect()
7855    }
7856
7857    // -- ACCOUNT QUERIES -----------------------------------------------------------------------
7858
7859    /// Returns a borrow of the account for the `account_id` (if found).
7860    #[must_use]
7861    pub fn account_ref(&self, account_id: &AccountId) -> Option<AccountRef<'_>> {
7862        self.accounts
7863            .get(account_id)
7864            .map(|account_cell| AccountRef::new(account_cell.borrow()))
7865    }
7866
7867    /// Returns a borrow of the account for the `account_id` (if found).
7868    ///
7869    /// Prefer [`Self::account_ref`] in new native code.
7870    #[must_use]
7871    pub fn account(&self, account_id: &AccountId) -> Option<AccountRef<'_>> {
7872        self.account_ref(account_id)
7873    }
7874
7875    /// Returns a borrow of the account for the `account_id`.
7876    ///
7877    /// # Errors
7878    ///
7879    /// Returns [`AccountLookupError::NotFound`] when the account is not present in the cache.
7880    pub fn try_account_ref(
7881        &self,
7882        account_id: &AccountId,
7883    ) -> Result<AccountRef<'_>, AccountLookupError> {
7884        self.accounts
7885            .get(account_id)
7886            .map(|account_cell| AccountRef::new(account_cell.borrow()))
7887            .ok_or_else(|| AccountLookupError::not_found(*account_id))
7888    }
7889
7890    /// Returns a borrow of the account for the `account_id`.
7891    ///
7892    /// Prefer [`Self::try_account_ref`] in new native code.
7893    ///
7894    /// # Errors
7895    ///
7896    /// Returns [`AccountLookupError::NotFound`] when the account is not present in the cache.
7897    pub fn try_account(
7898        &self,
7899        account_id: &AccountId,
7900    ) -> Result<AccountRef<'_>, AccountLookupError> {
7901        self.try_account_ref(account_id)
7902    }
7903
7904    /// Gets an exclusive write borrow of the account with the `account_id` (if found).
7905    ///
7906    /// Requires `&mut Cache` so cache writes are reachable only by privileged crates that hold
7907    /// `Rc<RefCell<Cache>>` directly. Adapter-facing code receives [`CacheView`], which only
7908    /// exposes immutable cache borrows and therefore cannot reach this method.
7909    ///
7910    /// While the returned [`AccountRefMut`] is alive, no other read or write of the same account
7911    /// is permitted. Drop the borrow before dispatching events or taking any other cache borrow
7912    /// that may re-enter the same account.
7913    #[must_use]
7914    pub fn account_mut(&mut self, account_id: &AccountId) -> Option<AccountRefMut<'_>> {
7915        self.accounts
7916            .get(account_id)
7917            .map(|account_cell| AccountRefMut::new(account_cell.borrow_mut()))
7918    }
7919
7920    /// Gets an owned snapshot of the account with the `account_id` (if found).
7921    ///
7922    /// Use when downstream needs an owned [`AccountAny`] that crosses a boundary. The snapshot
7923    /// will not reflect later cache mutations.
7924    #[must_use]
7925    pub fn account_owned(&self, account_id: &AccountId) -> Option<AccountAny> {
7926        self.accounts
7927            .get(account_id)
7928            .map(|account_cell| account_cell.borrow().clone())
7929    }
7930
7931    /// Returns a borrow of the account for the `venue` (if found).
7932    #[must_use]
7933    pub fn account_for_venue(&self, venue: &Venue) -> Option<AccountRef<'_>> {
7934        self.index
7935            .venue_account
7936            .get(venue)
7937            .and_then(|account_id| self.accounts.get(account_id))
7938            .map(|account_cell| AccountRef::new(account_cell.borrow()))
7939    }
7940
7941    /// Returns an owned snapshot of the account for the `venue` (if found).
7942    ///
7943    /// Use when downstream needs an owned [`AccountAny`] that crosses a boundary. The snapshot
7944    /// will not reflect later cache mutations.
7945    #[must_use]
7946    pub fn account_for_venue_owned(&self, venue: &Venue) -> Option<AccountAny> {
7947        self.index
7948            .venue_account
7949            .get(venue)
7950            .and_then(|account_id| self.accounts.get(account_id))
7951            .map(|account_cell| account_cell.borrow().clone())
7952    }
7953
7954    /// Returns a reference to the account ID for the `venue` (if found).
7955    #[must_use]
7956    pub fn account_id(&self, venue: &Venue) -> Option<&AccountId> {
7957        self.index.venue_account.get(venue)
7958    }
7959
7960    /// Returns borrows of all accounts for the `account_id`.
7961    ///
7962    /// Each [`AccountRef`] in the returned vector borrows its underlying cell; mutating any of
7963    /// those accounts while the vector is alive will panic at runtime. Drop the vector before
7964    /// issuing writes.
7965    #[must_use]
7966    pub fn accounts(&self, account_id: &AccountId) -> Vec<AccountRef<'_>> {
7967        self.accounts
7968            .values()
7969            .filter(|account_cell| &account_cell.borrow().id() == account_id)
7970            .map(|account_cell| AccountRef::new(account_cell.borrow()))
7971            .collect()
7972    }
7973
7974    /// Returns owned copies of every account in the cache.
7975    #[must_use]
7976    pub fn accounts_all_owned(&self) -> Vec<AccountAny> {
7977        self.accounts
7978            .values()
7979            .map(|account_cell| account_cell.borrow().clone())
7980            .collect()
7981    }
7982
7983    /// Updates the own order book with an order.
7984    ///
7985    /// This method adds, updates, or removes an order from the own order book
7986    /// based on the order's current state.
7987    ///
7988    /// Orders without prices (MARKET, etc.) are skipped as they cannot be
7989    /// represented in own books.
7990    pub fn update_own_order_book(&mut self, order: &OrderAny) {
7991        if !order.has_price() {
7992            return;
7993        }
7994
7995        let instrument_id = order.instrument_id();
7996
7997        if !self.own_books.contains_key(&instrument_id) {
7998            if order.is_closed() {
7999                return;
8000            }
8001
8002            self.own_books
8003                .insert(instrument_id, OwnOrderBook::new(instrument_id));
8004        }
8005
8006        let Some(own_book) = self.own_books.get_mut(&instrument_id) else {
8007            return;
8008        };
8009
8010        let own_book_order = order.to_own_book_order();
8011
8012        if order.is_closed() {
8013            if let Err(e) = own_book.delete(own_book_order) {
8014                log::debug!(
8015                    "Failed to delete order {} from own book: {e}",
8016                    order.client_order_id(),
8017                );
8018            } else {
8019                log::debug!("Deleted order {} from own book", order.client_order_id());
8020            }
8021        } else {
8022            // Add or update the order in the own book
8023            if let Err(e) = own_book.update(own_book_order) {
8024                log::debug!(
8025                    "Failed to update order {} in own book: {e}; inserting instead",
8026                    order.client_order_id(),
8027                );
8028                own_book.add(own_book_order);
8029            }
8030            log::debug!("Updated order {} in own book", order.client_order_id());
8031        }
8032    }
8033
8034    /// Force removal of an order from own order books and clean up all indexes.
8035    ///
8036    /// This method is used when order event application fails and we need to ensure
8037    /// terminal orders are properly cleaned up from own books and all relevant indexes.
8038    /// Replicates the index cleanup that `update_order` performs for closed orders.
8039    pub fn force_remove_from_own_order_book(&mut self, client_order_id: &ClientOrderId) {
8040        let Some(order_cell) = self.orders.get(client_order_id) else {
8041            return;
8042        };
8043        let order = order_cell.borrow();
8044        let instrument_id = order.instrument_id();
8045        let own_book_order = if order.has_price() {
8046            Some(order.to_own_book_order())
8047        } else {
8048            None
8049        };
8050        drop(order);
8051
8052        self.index.orders_open.remove(client_order_id);
8053        self.index.orders_pending_cancel.remove(client_order_id);
8054        self.index.orders_inflight.remove(client_order_id);
8055        self.index.orders_emulated.remove(client_order_id);
8056        self.index.orders_active_local.remove(client_order_id);
8057
8058        if let Some(own_book) = self.own_books.get_mut(&instrument_id)
8059            && let Some(own_book_order) = own_book_order
8060        {
8061            if let Err(e) = own_book.delete(own_book_order) {
8062                log::debug!("Could not force delete {client_order_id} from own book: {e}");
8063            } else {
8064                log::debug!("Force deleted {client_order_id} from own book");
8065            }
8066        }
8067
8068        self.index.orders_closed.insert(*client_order_id);
8069    }
8070
8071    /// Audit all own order books against open and inflight order indexes.
8072    ///
8073    /// Ensures closed orders are removed from own order books. This includes both
8074    /// orders tracked in `orders_open` (`ACCEPTED`, `TRIGGERED`, `PENDING_*`, `PARTIALLY_FILLED`)
8075    /// and `orders_inflight` (`INITIALIZED`, `SUBMITTED`) to prevent false positives
8076    /// during venue latency windows.
8077    pub fn audit_own_order_books(&mut self) {
8078        log::debug!("Starting own books audit");
8079        let start = std::time::Instant::now();
8080
8081        // Build union of open and inflight orders for audit,
8082        // this prevents false positives for SUBMITTED orders during venue latency.
8083        let valid_order_ids: AHashSet<ClientOrderId> = self
8084            .index
8085            .orders_open
8086            .union(&self.index.orders_inflight)
8087            .copied()
8088            .collect();
8089
8090        for own_book in self.own_books.values_mut() {
8091            own_book.audit_open_orders(&valid_order_ids);
8092        }
8093
8094        log::debug!("Completed own books audit in {:?}", start.elapsed());
8095    }
8096}
8097
8098const POSITION_OMS_KEY_PREFIX: &str = "position_oms:";
8099
8100fn position_oms_key(position_id: PositionId) -> String {
8101    format!("{POSITION_OMS_KEY_PREFIX}{position_id}")
8102}