Skip to main content

nautilus_common/cache/
database.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//! Provides a `Cache` database backing.
17
18use std::fmt::Debug;
19
20use ahash::AHashMap;
21use bytes::Bytes;
22use nautilus_core::{UUID4, UnixNanos};
23use nautilus_model::{
24    accounts::AccountAny,
25    data::{
26        Bar, CustomData, DataType, FundingRateUpdate, InstrumentClose, QuoteTick, TradeTick,
27        greeks::{GreeksData, YieldCurve},
28    },
29    events::{OrderEventAny, OrderSnapshot, position::snapshot::PositionSnapshot},
30    identifiers::{
31        AccountId, ActorId, ClientId, ClientOrderId, InstrumentId, PositionId, StrategyId,
32        TraderId, VenueOrderId,
33    },
34    instruments::{InstrumentAny, SyntheticInstrument},
35    orderbook::OrderBook,
36    orders::OrderAny,
37    position::Position,
38    types::{Currency, Money},
39};
40use ustr::Ustr;
41
42use super::config::CacheConfig;
43use crate::signal::Signal;
44
45/// Registers currencies loaded from a cache database, keeping the registry's definition.
46///
47/// A `Money` or `Currency` decoded afterwards resolves its code through the global registry, so a
48/// loaded currency must reach it before the dependent payloads are read. `Currency::register` does
49/// not overwrite, so a built-in constant or a currency an adapter registered first keeps its
50/// definition; the map takes that definition too, so the cache and the decoded payloads agree, and a
51/// stored record that differs from it is logged.
52///
53/// # Errors
54///
55/// Returns an error if `Currency::register` does; it does not at present, so the result only
56/// keeps callers forward-compatible.
57pub fn register_loaded_currencies(currencies: &mut AHashMap<Ustr, Currency>) -> anyhow::Result<()> {
58    for (code, currency) in currencies.iter_mut() {
59        Currency::register(*currency, false)?;
60
61        let Some(registered) = Currency::try_from_str(code) else {
62            continue;
63        };
64        let mut differences: Vec<String> = Vec::new();
65        if registered.precision != currency.precision {
66            differences.push(format!(
67                "precision {} vs registered {}",
68                currency.precision, registered.precision
69            ));
70        }
71
72        if registered.iso4217 != currency.iso4217 {
73            differences.push(format!(
74                "iso4217 {} vs registered {}",
75                currency.iso4217, registered.iso4217
76            ));
77        }
78
79        if registered.name != currency.name {
80            differences.push(format!(
81                "name {} vs registered {}",
82                currency.name, registered.name
83            ));
84        }
85
86        if registered.currency_type != currency.currency_type {
87            differences.push(format!(
88                "type {:?} vs registered {:?}",
89                currency.currency_type, registered.currency_type
90            ));
91        }
92
93        if !differences.is_empty() {
94            log::warn!(
95                "Stored currency {code} differs from the registered definition ({}); using the registered one",
96                differences.join(", "),
97            );
98            *currency = registered;
99        }
100    }
101
102    Ok(())
103}
104
105#[derive(Debug, Default)]
106pub struct CacheMap {
107    pub currencies: AHashMap<Ustr, Currency>,
108    pub instruments: AHashMap<InstrumentId, InstrumentAny>,
109    pub instrument_closes: AHashMap<InstrumentId, InstrumentClose>,
110    pub synthetics: AHashMap<InstrumentId, SyntheticInstrument>,
111    pub accounts: AHashMap<AccountId, AccountAny>,
112    pub orders: AHashMap<ClientOrderId, OrderAny>,
113    pub positions: AHashMap<PositionId, Position>,
114    pub greeks: AHashMap<InstrumentId, GreeksData>,
115    pub yield_curves: AHashMap<String, YieldCurve>,
116}
117
118/// Factory for constructing cache database adapters at runtime.
119///
120/// Implementations own the concrete database configuration and return the transport-neutral
121/// [`CacheDatabaseAdapter`] surface used by the cache.
122#[async_trait::async_trait]
123pub trait CacheDatabaseFactory: Debug + Send + Sync {
124    /// Creates a cache database adapter for the given cache runtime.
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if adapter construction or connection setup fails.
129    async fn create(
130        &self,
131        trader_id: TraderId,
132        instance_id: UUID4,
133        config: CacheConfig,
134    ) -> anyhow::Result<Box<dyn CacheDatabaseAdapter>>;
135}
136
137#[async_trait::async_trait]
138pub trait CacheDatabaseAdapter {
139    /// Closes the cache database connection.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if the database fails to close properly.
144    fn close(&mut self) -> anyhow::Result<()>;
145
146    /// Clears persisted data according to the backing's scope.
147    ///
148    /// Redis clears the entire logical database, including other traders and user keys.
149    /// PostgreSQL removes only this trader's rows and saved component state.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if removing the persisted data fails.
154    fn flush(&mut self) -> anyhow::Result<()>;
155
156    /// Loads all cached data into memory.
157    ///
158    /// # Errors
159    ///
160    /// Returns an error if loading data from the database fails.
161    async fn load_all(&self) -> anyhow::Result<CacheMap>;
162
163    /// Loads raw key-value data from the database.
164    ///
165    /// # Errors
166    ///
167    /// Returns an error if the load operation fails.
168    fn load(&self) -> anyhow::Result<AHashMap<String, Bytes>>;
169
170    /// Loads all currencies from the cache.
171    ///
172    /// # Errors
173    ///
174    /// Returns an error if loading currencies fails.
175    async fn load_currencies(&self) -> anyhow::Result<AHashMap<Ustr, Currency>>;
176
177    /// Loads all instruments from the cache.
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if loading instruments fails.
182    async fn load_instruments(&self) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>>;
183
184    /// Loads all instrument closes from the cache.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error if loading instrument closes fails.
189    async fn load_instrument_closes(
190        &self,
191    ) -> anyhow::Result<AHashMap<InstrumentId, InstrumentClose>>;
192
193    /// Loads all synthetic instruments from the cache.
194    ///
195    /// # Errors
196    ///
197    /// Returns an error if loading synthetic instruments fails.
198    async fn load_synthetics(&self) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>>;
199
200    /// Loads all accounts from the cache.
201    ///
202    /// # Errors
203    ///
204    /// Returns an error if loading accounts fails.
205    async fn load_accounts(&self) -> anyhow::Result<AHashMap<AccountId, AccountAny>>;
206
207    /// Loads all orders from the cache.
208    ///
209    /// # Errors
210    ///
211    /// Returns an error if loading orders fails.
212    async fn load_orders(&self) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>>;
213
214    /// Loads all positions from the cache.
215    ///
216    /// # Errors
217    ///
218    /// Returns an error if loading positions fails.
219    async fn load_positions(&self) -> anyhow::Result<AHashMap<PositionId, Position>>;
220
221    /// Loads all [`GreeksData`] from the cache.
222    ///
223    /// # Errors
224    ///
225    /// Returns an error if loading greeks data fails or the adapter does not support it.
226    async fn load_greeks(&self) -> anyhow::Result<AHashMap<InstrumentId, GreeksData>> {
227        anyhow::bail!("Loading greeks for cache adapter not supported")
228    }
229
230    /// Loads all [`YieldCurve`] from the cache.
231    ///
232    /// # Errors
233    ///
234    /// Returns an error if loading yield curve data fails or the adapter does not support it.
235    async fn load_yield_curves(&self) -> anyhow::Result<AHashMap<String, YieldCurve>> {
236        anyhow::bail!("Loading yield curves for cache adapter not supported")
237    }
238
239    /// Loads mapping from order IDs to position IDs.
240    ///
241    /// # Errors
242    ///
243    /// Returns an error if loading the index order-position mapping fails.
244    fn load_index_order_position(&self) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>>;
245
246    /// Loads mapping from order IDs to client IDs.
247    ///
248    /// # Errors
249    ///
250    /// Returns an error if loading the index order-client mapping fails.
251    fn load_index_order_client(&self) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>>;
252
253    /// Loads a single currency by code.
254    ///
255    /// # Errors
256    ///
257    /// Returns an error if loading a single currency fails.
258    async fn load_currency(&self, code: &Ustr) -> anyhow::Result<Option<Currency>>;
259
260    /// Loads a single instrument by ID.
261    ///
262    /// # Errors
263    ///
264    /// Returns an error if loading a single instrument fails.
265    async fn load_instrument(
266        &self,
267        instrument_id: &InstrumentId,
268    ) -> anyhow::Result<Option<InstrumentAny>>;
269
270    /// Loads a single synthetic instrument by ID.
271    ///
272    /// # Errors
273    ///
274    /// Returns an error if loading a single synthetic instrument fails.
275    async fn load_synthetic(
276        &self,
277        instrument_id: &InstrumentId,
278    ) -> anyhow::Result<Option<SyntheticInstrument>>;
279
280    /// Loads an instrument for a synchronous cache caller.
281    ///
282    /// # Errors
283    ///
284    /// Returns an error if loading fails or synchronous reads are unsupported.
285    fn load_instrument_sync(
286        &self,
287        _instrument_id: &InstrumentId,
288    ) -> anyhow::Result<Option<InstrumentAny>> {
289        anyhow::bail!("Synchronous instrument loading is not supported by this cache database")
290    }
291
292    /// Loads a synthetic instrument for a synchronous cache caller.
293    ///
294    /// # Errors
295    ///
296    /// Returns an error if loading fails or synchronous reads are unsupported.
297    fn load_synthetic_sync(
298        &self,
299        _instrument_id: &InstrumentId,
300    ) -> anyhow::Result<Option<SyntheticInstrument>> {
301        anyhow::bail!("Synchronous synthetic loading is not supported by this cache database")
302    }
303
304    /// Loads a single account by ID.
305    ///
306    /// # Errors
307    ///
308    /// Returns an error if loading a single account fails.
309    async fn load_account(&self, account_id: &AccountId) -> anyhow::Result<Option<AccountAny>>;
310
311    /// Loads a single order by client order ID.
312    ///
313    /// # Errors
314    ///
315    /// Returns an error if loading a single order fails.
316    async fn load_order(&self, client_order_id: &ClientOrderId)
317    -> anyhow::Result<Option<OrderAny>>;
318
319    /// Loads a single position by position ID.
320    ///
321    /// # Errors
322    ///
323    /// Returns an error if loading a single position fails.
324    async fn load_position(&self, position_id: &PositionId) -> anyhow::Result<Option<Position>>;
325
326    /// Loads actor state by actor ID.
327    ///
328    /// # Errors
329    ///
330    /// Returns an error if loading actor state fails.
331    fn load_actor(&self, actor_id: &ActorId) -> anyhow::Result<AHashMap<String, Bytes>>;
332
333    /// Loads strategy state by strategy ID.
334    ///
335    /// # Errors
336    ///
337    /// Returns an error if loading strategy state fails.
338    fn load_strategy(&self, strategy_id: &StrategyId) -> anyhow::Result<AHashMap<String, Bytes>>;
339
340    /// Loads signals by name.
341    ///
342    /// # Errors
343    ///
344    /// Returns an error if loading signals fails.
345    fn load_signals(&self, name: &str) -> anyhow::Result<Vec<Signal>>;
346
347    /// Loads custom data by data type.
348    ///
349    /// # Errors
350    ///
351    /// Returns an error if loading custom data fails.
352    fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>>;
353
354    /// Loads an order snapshot by client order ID.
355    ///
356    /// # Errors
357    ///
358    /// Returns an error if loading the order snapshot fails.
359    fn load_order_snapshot(
360        &self,
361        client_order_id: &ClientOrderId,
362    ) -> anyhow::Result<Option<OrderSnapshot>>;
363
364    /// Loads a position snapshot by position ID.
365    ///
366    /// # Errors
367    ///
368    /// Returns an error if loading the position snapshot fails.
369    fn load_position_snapshot(
370        &self,
371        position_id: &PositionId,
372    ) -> anyhow::Result<Option<PositionSnapshot>>;
373
374    /// Loads quote ticks by instrument ID.
375    ///
376    /// # Errors
377    ///
378    /// Returns an error if loading quotes fails.
379    fn load_quotes(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<QuoteTick>>;
380
381    /// Loads trade ticks by instrument ID.
382    ///
383    /// # Errors
384    ///
385    /// Returns an error if loading trades fails.
386    fn load_trades(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>>;
387
388    /// Loads funding rate updates by instrument ID.
389    ///
390    /// # Errors
391    ///
392    /// Returns an error if loading funding rates fails.
393    fn load_funding_rates(
394        &self,
395        instrument_id: &InstrumentId,
396    ) -> anyhow::Result<Vec<FundingRateUpdate>>;
397
398    /// Loads bars by instrument ID.
399    ///
400    /// # Errors
401    ///
402    /// Returns an error if loading bars fails.
403    fn load_bars(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<Bar>>;
404
405    /// Adds a generic key-value pair to the cache.
406    ///
407    /// # Errors
408    ///
409    /// Returns an error if adding a generic key/value fails.
410    fn add(&self, key: String, value: Bytes) -> anyhow::Result<()>;
411
412    /// Adds a currency to the cache.
413    ///
414    /// # Errors
415    ///
416    /// Returns an error if adding a currency fails.
417    fn add_currency(&self, currency: &Currency) -> anyhow::Result<()>;
418
419    /// Adds an instrument to the cache.
420    ///
421    /// # Errors
422    ///
423    /// Returns an error if adding an instrument fails.
424    fn add_instrument(&self, instrument: &InstrumentAny) -> anyhow::Result<()>;
425
426    /// Persists a token instrument, checking legacy replacement before committing it.
427    ///
428    /// # Errors
429    ///
430    /// Returns an error if integrity or migration checks fail, or the backend lacks DeFi support.
431    #[cfg(feature = "defi")]
432    fn add_defi_instrument(
433        &self,
434        _instrument: &InstrumentAny,
435        _migration: crate::defi::cache::InstrumentMigration,
436    ) -> anyhow::Result<()> {
437        anyhow::bail!("DeFi instrument persistence is unsupported by this backend")
438    }
439
440    /// Adds an instrument close to the cache, replacing any existing value for the instrument.
441    /// Implementations must queue persistence without waiting for the database operation to
442    /// complete.
443    ///
444    /// # Errors
445    ///
446    /// Returns an error if the instrument close cannot be queued for persistence.
447    fn add_instrument_close(&self, close: &InstrumentClose) -> anyhow::Result<()>;
448
449    /// Adds a synthetic instrument to the cache.
450    ///
451    /// # Errors
452    ///
453    /// Returns an error if adding a synthetic instrument fails.
454    fn add_synthetic(&self, synthetic: &SyntheticInstrument) -> anyhow::Result<()>;
455
456    /// Adds an account to the cache.
457    ///
458    /// # Errors
459    ///
460    /// Returns an error if adding an account fails.
461    fn add_account(&self, account: &AccountAny) -> anyhow::Result<()>;
462
463    /// Adds an order to the cache.
464    ///
465    /// # Errors
466    ///
467    /// Returns an error if adding an order fails.
468    fn add_order(&self, order: &OrderAny, client_id: Option<ClientId>) -> anyhow::Result<()>;
469
470    /// Adds an order snapshot to the cache.
471    ///
472    /// # Errors
473    ///
474    /// Returns an error if adding an order snapshot fails.
475    fn add_order_snapshot(&self, snapshot: &OrderSnapshot) -> anyhow::Result<()>;
476
477    /// Adds a position to the cache.
478    ///
479    /// # Errors
480    ///
481    /// Returns an error if adding a position fails.
482    fn add_position(&self, position: &Position) -> anyhow::Result<()>;
483
484    /// Adds a position snapshot to the cache.
485    ///
486    /// # Errors
487    ///
488    /// Returns an error if adding a position snapshot fails.
489    fn add_position_snapshot(&self, snapshot: &PositionSnapshot) -> anyhow::Result<()>;
490
491    /// Adds an order book to the cache.
492    ///
493    /// # Errors
494    ///
495    /// Returns an error if adding an order book fails.
496    fn add_order_book(&self, order_book: &OrderBook) -> anyhow::Result<()>;
497
498    /// Adds a signal to the cache.
499    ///
500    /// # Errors
501    ///
502    /// Returns an error if adding a signal fails.
503    fn add_signal(&self, signal: &Signal) -> anyhow::Result<()>;
504
505    /// Adds custom data to the cache.
506    ///
507    /// # Errors
508    ///
509    /// Returns an error if adding custom data fails.
510    fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()>;
511
512    /// Adds a quote tick to the cache.
513    ///
514    /// # Errors
515    ///
516    /// Returns an error if adding a quote tick fails.
517    fn add_quote(&self, quote: &QuoteTick) -> anyhow::Result<()>;
518
519    /// Adds a trade tick to the cache.
520    ///
521    /// # Errors
522    ///
523    /// Returns an error if adding a trade tick fails.
524    fn add_trade(&self, trade: &TradeTick) -> anyhow::Result<()>;
525
526    /// Adds a funding rate update to the cache.
527    ///
528    /// # Errors
529    ///
530    /// Returns an error if adding a funding rate update fails.
531    fn add_funding_rate(&self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()>;
532
533    /// Adds a bar to the cache.
534    ///
535    /// # Errors
536    ///
537    /// Returns an error if adding a bar fails.
538    fn add_bar(&self, bar: &Bar) -> anyhow::Result<()>;
539
540    /// Adds greeks data to the cache.
541    ///
542    /// # Errors
543    ///
544    /// Returns an error if adding greeks data fails or the adapter does not support it.
545    fn add_greeks(&self, _greeks: &GreeksData) -> anyhow::Result<()> {
546        anyhow::bail!("Saving greeks for cache adapter not supported")
547    }
548
549    /// Adds yield curve data to the cache.
550    ///
551    /// # Errors
552    ///
553    /// Returns an error if adding yield curve data fails or the adapter does not support it.
554    fn add_yield_curve(&self, _yield_curve: &YieldCurve) -> anyhow::Result<()> {
555        anyhow::bail!("Saving yield curves for cache adapter not supported")
556    }
557
558    /// Deletes actor state from the cache.
559    ///
560    /// # Errors
561    ///
562    /// Returns an error if deleting actor state fails.
563    fn delete_actor(&self, actor_id: &ActorId) -> anyhow::Result<()>;
564
565    /// Deletes strategy state from the cache.
566    ///
567    /// # Errors
568    ///
569    /// Returns an error if deleting strategy state fails.
570    fn delete_strategy(&self, component_id: &StrategyId) -> anyhow::Result<()>;
571
572    /// Deletes an order from the cache.
573    ///
574    /// # Errors
575    ///
576    /// Returns an error if deleting an order fails.
577    fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()>;
578
579    /// Deletes a position from the cache.
580    ///
581    /// # Errors
582    ///
583    /// Returns an error if deleting a position fails.
584    fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()>;
585
586    /// Requests deletion of an account event from the database.
587    ///
588    /// Redis and PostgreSQL retain the full account history for balance and margin replay;
589    /// their implementations log the request and return success without deleting it.
590    ///
591    /// # Errors
592    ///
593    /// Returns an error if deleting account events fails.
594    fn delete_account_event(&self, account_id: &AccountId, event_id: &str) -> anyhow::Result<()>;
595
596    /// Indexes a venue order ID with its client order ID.
597    ///
598    /// # Errors
599    ///
600    /// Returns an error if indexing venue order ID fails.
601    fn index_venue_order_id(
602        &self,
603        client_order_id: ClientOrderId,
604        venue_order_id: VenueOrderId,
605    ) -> anyhow::Result<()>;
606
607    /// Indexes an order-position mapping.
608    ///
609    /// # Errors
610    ///
611    /// Returns an error if indexing order-position mapping fails.
612    fn index_order_position(
613        &self,
614        client_order_id: ClientOrderId,
615        position_id: PositionId,
616    ) -> anyhow::Result<()>;
617
618    /// Indexes order-client mappings as one batch operation.
619    ///
620    /// # Errors
621    ///
622    /// Returns an error if batch order-client indexing is unsupported or cannot be enqueued.
623    fn index_order_clients(&self, claims: &[(ClientOrderId, ClientId)]) -> anyhow::Result<()> {
624        if claims.is_empty() {
625            return Ok(());
626        }
627
628        anyhow::bail!("Batch order-client indexing is not supported by this cache database")
629    }
630
631    /// Updates actor state in the cache.
632    ///
633    /// # Errors
634    ///
635    /// Returns an error if updating actor state fails.
636    fn update_actor(
637        &self,
638        actor_id: &ActorId,
639        state: &AHashMap<String, Bytes>,
640    ) -> anyhow::Result<()>;
641
642    /// Updates strategy state in the cache.
643    ///
644    /// # Errors
645    ///
646    /// Returns an error if updating strategy state fails.
647    fn update_strategy(
648        &self,
649        strategy_id: &StrategyId,
650        state: &AHashMap<String, Bytes>,
651    ) -> anyhow::Result<()>;
652
653    /// Updates an account in the cache.
654    ///
655    /// # Errors
656    ///
657    /// Returns an error if updating an account fails.
658    fn update_account(&self, account: &AccountAny) -> anyhow::Result<()>;
659
660    /// Updates an order in the cache with an order event.
661    ///
662    /// # Errors
663    ///
664    /// Returns an error if updating an order fails.
665    fn update_order(&self, order_event: &OrderEventAny) -> anyhow::Result<()>;
666
667    /// Updates a position in the cache.
668    ///
669    /// # Errors
670    ///
671    /// Returns an error if updating a position fails.
672    fn update_position(&self, position: &Position) -> anyhow::Result<()>;
673
674    /// Creates a snapshot of order state.
675    ///
676    /// # Errors
677    ///
678    /// Returns an error if snapshotting order state fails.
679    fn snapshot_order_state(&self, order: &OrderAny) -> anyhow::Result<()>;
680
681    /// Creates a snapshot of position state.
682    ///
683    /// # Errors
684    ///
685    /// Returns an error if snapshotting position state fails.
686    fn snapshot_position_state(
687        &self,
688        position: &Position,
689        ts_snapshot: UnixNanos,
690        unrealized_pnl: Option<Money>,
691    ) -> anyhow::Result<()>;
692
693    /// Records a heartbeat timestamp.
694    ///
695    /// # Errors
696    ///
697    /// Returns an error if heartbeat recording fails.
698    fn heartbeat(&self, timestamp: UnixNanos) -> anyhow::Result<()>;
699}