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