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 /// Flushes any pending changes to the database.
147 ///
148 /// # Errors
149 ///
150 /// Returns an error if flushing changes fails.
151 fn flush(&mut self) -> anyhow::Result<()>;
152
153 /// Loads all cached data into memory.
154 ///
155 /// # Errors
156 ///
157 /// Returns an error if loading data from the database fails.
158 async fn load_all(&self) -> anyhow::Result<CacheMap>;
159
160 /// Loads raw key-value data from the database.
161 ///
162 /// # Errors
163 ///
164 /// Returns an error if the load operation fails.
165 fn load(&self) -> anyhow::Result<AHashMap<String, Bytes>>;
166
167 /// Loads all currencies from the cache.
168 ///
169 /// # Errors
170 ///
171 /// Returns an error if loading currencies fails.
172 async fn load_currencies(&self) -> anyhow::Result<AHashMap<Ustr, Currency>>;
173
174 /// Loads all instruments from the cache.
175 ///
176 /// # Errors
177 ///
178 /// Returns an error if loading instruments fails.
179 async fn load_instruments(&self) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>>;
180
181 /// Loads all instrument closes from the cache.
182 ///
183 /// # Errors
184 ///
185 /// Returns an error if loading instrument closes fails.
186 async fn load_instrument_closes(
187 &self,
188 ) -> anyhow::Result<AHashMap<InstrumentId, InstrumentClose>>;
189
190 /// Loads all synthetic instruments from the cache.
191 ///
192 /// # Errors
193 ///
194 /// Returns an error if loading synthetic instruments fails.
195 async fn load_synthetics(&self) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>>;
196
197 /// Loads all accounts from the cache.
198 ///
199 /// # Errors
200 ///
201 /// Returns an error if loading accounts fails.
202 async fn load_accounts(&self) -> anyhow::Result<AHashMap<AccountId, AccountAny>>;
203
204 /// Loads all orders from the cache.
205 ///
206 /// # Errors
207 ///
208 /// Returns an error if loading orders fails.
209 async fn load_orders(&self) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>>;
210
211 /// Loads all positions from the cache.
212 ///
213 /// # Errors
214 ///
215 /// Returns an error if loading positions fails.
216 async fn load_positions(&self) -> anyhow::Result<AHashMap<PositionId, Position>>;
217
218 /// Loads all [`GreeksData`] from the cache.
219 ///
220 /// # Errors
221 ///
222 /// Returns an error if loading greeks data fails.
223 async fn load_greeks(&self) -> anyhow::Result<AHashMap<InstrumentId, GreeksData>> {
224 Ok(AHashMap::new())
225 }
226
227 /// Loads all [`YieldCurve`] from the cache.
228 ///
229 /// # Errors
230 ///
231 /// Returns an error if loading yield curve data fails.
232 async fn load_yield_curves(&self) -> anyhow::Result<AHashMap<String, YieldCurve>> {
233 Ok(AHashMap::new())
234 }
235
236 /// Loads mapping from order IDs to position IDs.
237 ///
238 /// # Errors
239 ///
240 /// Returns an error if loading the index order-position mapping fails.
241 fn load_index_order_position(&self) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>>;
242
243 /// Loads mapping from order IDs to client IDs.
244 ///
245 /// # Errors
246 ///
247 /// Returns an error if loading the index order-client mapping fails.
248 fn load_index_order_client(&self) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>>;
249
250 /// Loads a single currency by code.
251 ///
252 /// # Errors
253 ///
254 /// Returns an error if loading a single currency fails.
255 async fn load_currency(&self, code: &Ustr) -> anyhow::Result<Option<Currency>>;
256
257 /// Loads a single instrument by ID.
258 ///
259 /// # Errors
260 ///
261 /// Returns an error if loading a single instrument fails.
262 async fn load_instrument(
263 &self,
264 instrument_id: &InstrumentId,
265 ) -> anyhow::Result<Option<InstrumentAny>>;
266
267 /// Loads a single synthetic instrument by ID.
268 ///
269 /// # Errors
270 ///
271 /// Returns an error if loading a single synthetic instrument fails.
272 async fn load_synthetic(
273 &self,
274 instrument_id: &InstrumentId,
275 ) -> anyhow::Result<Option<SyntheticInstrument>>;
276
277 /// Loads a single account by ID.
278 ///
279 /// # Errors
280 ///
281 /// Returns an error if loading a single account fails.
282 async fn load_account(&self, account_id: &AccountId) -> anyhow::Result<Option<AccountAny>>;
283
284 /// Loads a single order by client order ID.
285 ///
286 /// # Errors
287 ///
288 /// Returns an error if loading a single order fails.
289 async fn load_order(&self, client_order_id: &ClientOrderId)
290 -> anyhow::Result<Option<OrderAny>>;
291
292 /// Loads a single position by position ID.
293 ///
294 /// # Errors
295 ///
296 /// Returns an error if loading a single position fails.
297 async fn load_position(&self, position_id: &PositionId) -> anyhow::Result<Option<Position>>;
298
299 /// Loads actor state by actor ID.
300 ///
301 /// # Errors
302 ///
303 /// Returns an error if loading actor state fails.
304 fn load_actor(&self, actor_id: &ActorId) -> anyhow::Result<AHashMap<String, Bytes>>;
305
306 /// Loads strategy state by strategy ID.
307 ///
308 /// # Errors
309 ///
310 /// Returns an error if loading strategy state fails.
311 fn load_strategy(&self, strategy_id: &StrategyId) -> anyhow::Result<AHashMap<String, Bytes>>;
312
313 /// Loads signals by name.
314 ///
315 /// # Errors
316 ///
317 /// Returns an error if loading signals fails.
318 fn load_signals(&self, name: &str) -> anyhow::Result<Vec<Signal>>;
319
320 /// Loads custom data by data type.
321 ///
322 /// # Errors
323 ///
324 /// Returns an error if loading custom data fails.
325 fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>>;
326
327 /// Loads an order snapshot by client order ID.
328 ///
329 /// # Errors
330 ///
331 /// Returns an error if loading the order snapshot fails.
332 fn load_order_snapshot(
333 &self,
334 client_order_id: &ClientOrderId,
335 ) -> anyhow::Result<Option<OrderSnapshot>>;
336
337 /// Loads a position snapshot by position ID.
338 ///
339 /// # Errors
340 ///
341 /// Returns an error if loading the position snapshot fails.
342 fn load_position_snapshot(
343 &self,
344 position_id: &PositionId,
345 ) -> anyhow::Result<Option<PositionSnapshot>>;
346
347 /// Loads quote ticks by instrument ID.
348 ///
349 /// # Errors
350 ///
351 /// Returns an error if loading quotes fails.
352 fn load_quotes(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<QuoteTick>>;
353
354 /// Loads trade ticks by instrument ID.
355 ///
356 /// # Errors
357 ///
358 /// Returns an error if loading trades fails.
359 fn load_trades(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>>;
360
361 /// Loads funding rate updates by instrument ID.
362 ///
363 /// # Errors
364 ///
365 /// Returns an error if loading funding rates fails.
366 fn load_funding_rates(
367 &self,
368 instrument_id: &InstrumentId,
369 ) -> anyhow::Result<Vec<FundingRateUpdate>>;
370
371 /// Loads bars by instrument ID.
372 ///
373 /// # Errors
374 ///
375 /// Returns an error if loading bars fails.
376 fn load_bars(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<Bar>>;
377
378 /// Adds a generic key-value pair to the cache.
379 ///
380 /// # Errors
381 ///
382 /// Returns an error if adding a generic key/value fails.
383 fn add(&self, key: String, value: Bytes) -> anyhow::Result<()>;
384
385 /// Adds a currency to the cache.
386 ///
387 /// # Errors
388 ///
389 /// Returns an error if adding a currency fails.
390 fn add_currency(&self, currency: &Currency) -> anyhow::Result<()>;
391
392 /// Adds an instrument to the cache.
393 ///
394 /// # Errors
395 ///
396 /// Returns an error if adding an instrument fails.
397 fn add_instrument(&self, instrument: &InstrumentAny) -> anyhow::Result<()>;
398
399 /// Adds an instrument close to the cache, replacing any existing value for the instrument.
400 /// Implementations must queue persistence without waiting for the database operation to
401 /// complete.
402 ///
403 /// # Errors
404 ///
405 /// Returns an error if the instrument close cannot be queued for persistence.
406 fn add_instrument_close(&self, close: &InstrumentClose) -> anyhow::Result<()>;
407
408 /// Adds a synthetic instrument to the cache.
409 ///
410 /// # Errors
411 ///
412 /// Returns an error if adding a synthetic instrument fails.
413 fn add_synthetic(&self, synthetic: &SyntheticInstrument) -> anyhow::Result<()>;
414
415 /// Adds an account to the cache.
416 ///
417 /// # Errors
418 ///
419 /// Returns an error if adding an account fails.
420 fn add_account(&self, account: &AccountAny) -> anyhow::Result<()>;
421
422 /// Adds an order to the cache.
423 ///
424 /// # Errors
425 ///
426 /// Returns an error if adding an order fails.
427 fn add_order(&self, order: &OrderAny, client_id: Option<ClientId>) -> anyhow::Result<()>;
428
429 /// Adds an order snapshot to the cache.
430 ///
431 /// # Errors
432 ///
433 /// Returns an error if adding an order snapshot fails.
434 fn add_order_snapshot(&self, snapshot: &OrderSnapshot) -> anyhow::Result<()>;
435
436 /// Adds a position to the cache.
437 ///
438 /// # Errors
439 ///
440 /// Returns an error if adding a position fails.
441 fn add_position(&self, position: &Position) -> anyhow::Result<()>;
442
443 /// Adds a position snapshot to the cache.
444 ///
445 /// # Errors
446 ///
447 /// Returns an error if adding a position snapshot fails.
448 fn add_position_snapshot(&self, snapshot: &PositionSnapshot) -> anyhow::Result<()>;
449
450 /// Adds an order book to the cache.
451 ///
452 /// # Errors
453 ///
454 /// Returns an error if adding an order book fails.
455 fn add_order_book(&self, order_book: &OrderBook) -> anyhow::Result<()>;
456
457 /// Adds a signal to the cache.
458 ///
459 /// # Errors
460 ///
461 /// Returns an error if adding a signal fails.
462 fn add_signal(&self, signal: &Signal) -> anyhow::Result<()>;
463
464 /// Adds custom data to the cache.
465 ///
466 /// # Errors
467 ///
468 /// Returns an error if adding custom data fails.
469 fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()>;
470
471 /// Adds a quote tick to the cache.
472 ///
473 /// # Errors
474 ///
475 /// Returns an error if adding a quote tick fails.
476 fn add_quote(&self, quote: &QuoteTick) -> anyhow::Result<()>;
477
478 /// Adds a trade tick to the cache.
479 ///
480 /// # Errors
481 ///
482 /// Returns an error if adding a trade tick fails.
483 fn add_trade(&self, trade: &TradeTick) -> anyhow::Result<()>;
484
485 /// Adds a funding rate update to the cache.
486 ///
487 /// # Errors
488 ///
489 /// Returns an error if adding a funding rate update fails.
490 fn add_funding_rate(&self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()>;
491
492 /// Adds a bar to the cache.
493 ///
494 /// # Errors
495 ///
496 /// Returns an error if adding a bar fails.
497 fn add_bar(&self, bar: &Bar) -> anyhow::Result<()>;
498
499 /// Adds greeks data to the cache.
500 ///
501 /// # Errors
502 ///
503 /// Returns an error if adding greeks data fails.
504 fn add_greeks(&self, _greeks: &GreeksData) -> anyhow::Result<()> {
505 Ok(())
506 }
507
508 /// Adds yield curve data to the cache.
509 ///
510 /// # Errors
511 ///
512 /// Returns an error if adding yield curve data fails.
513 fn add_yield_curve(&self, _yield_curve: &YieldCurve) -> anyhow::Result<()> {
514 Ok(())
515 }
516
517 /// Deletes actor state from the cache.
518 ///
519 /// # Errors
520 ///
521 /// Returns an error if deleting actor state fails.
522 fn delete_actor(&self, actor_id: &ActorId) -> anyhow::Result<()>;
523
524 /// Deletes strategy state from the cache.
525 ///
526 /// # Errors
527 ///
528 /// Returns an error if deleting strategy state fails.
529 fn delete_strategy(&self, component_id: &StrategyId) -> anyhow::Result<()>;
530
531 /// Deletes an order from the cache.
532 ///
533 /// # Errors
534 ///
535 /// Returns an error if deleting an order fails.
536 fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()>;
537
538 /// Deletes a position from the cache.
539 ///
540 /// # Errors
541 ///
542 /// Returns an error if deleting a position fails.
543 fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()>;
544
545 /// Deletes an account event from the cache.
546 ///
547 /// # Errors
548 ///
549 /// Returns an error if deleting account events fails.
550 fn delete_account_event(&self, account_id: &AccountId, event_id: &str) -> anyhow::Result<()>;
551
552 /// Indexes a venue order ID with its client order ID.
553 ///
554 /// # Errors
555 ///
556 /// Returns an error if indexing venue order ID fails.
557 fn index_venue_order_id(
558 &self,
559 client_order_id: ClientOrderId,
560 venue_order_id: VenueOrderId,
561 ) -> anyhow::Result<()>;
562
563 /// Indexes an order-position mapping.
564 ///
565 /// # Errors
566 ///
567 /// Returns an error if indexing order-position mapping fails.
568 fn index_order_position(
569 &self,
570 client_order_id: ClientOrderId,
571 position_id: PositionId,
572 ) -> anyhow::Result<()>;
573
574 /// Indexes order-client mappings as one batch operation.
575 ///
576 /// # Errors
577 ///
578 /// Returns an error if batch order-client indexing is unsupported or cannot be enqueued.
579 fn index_order_clients(&self, claims: &[(ClientOrderId, ClientId)]) -> anyhow::Result<()> {
580 if claims.is_empty() {
581 return Ok(());
582 }
583
584 anyhow::bail!("Batch order-client indexing is not supported by this cache database")
585 }
586
587 /// Updates actor state in the cache.
588 ///
589 /// # Errors
590 ///
591 /// Returns an error if updating actor state fails.
592 fn update_actor(
593 &self,
594 actor_id: &ActorId,
595 state: &AHashMap<String, Bytes>,
596 ) -> anyhow::Result<()>;
597
598 /// Updates strategy state in the cache.
599 ///
600 /// # Errors
601 ///
602 /// Returns an error if updating strategy state fails.
603 fn update_strategy(
604 &self,
605 strategy_id: &StrategyId,
606 state: &AHashMap<String, Bytes>,
607 ) -> anyhow::Result<()>;
608
609 /// Updates an account in the cache.
610 ///
611 /// # Errors
612 ///
613 /// Returns an error if updating an account fails.
614 fn update_account(&self, account: &AccountAny) -> anyhow::Result<()>;
615
616 /// Updates an order in the cache with an order event.
617 ///
618 /// # Errors
619 ///
620 /// Returns an error if updating an order fails.
621 fn update_order(&self, order_event: &OrderEventAny) -> anyhow::Result<()>;
622
623 /// Updates a position in the cache.
624 ///
625 /// # Errors
626 ///
627 /// Returns an error if updating a position fails.
628 fn update_position(&self, position: &Position) -> anyhow::Result<()>;
629
630 /// Creates a snapshot of order state.
631 ///
632 /// # Errors
633 ///
634 /// Returns an error if snapshotting order state fails.
635 fn snapshot_order_state(&self, order: &OrderAny) -> anyhow::Result<()>;
636
637 /// Creates a snapshot of position state.
638 ///
639 /// # Errors
640 ///
641 /// Returns an error if snapshotting position state fails.
642 fn snapshot_position_state(
643 &self,
644 position: &Position,
645 ts_snapshot: UnixNanos,
646 unrealized_pnl: Option<Money>,
647 ) -> anyhow::Result<()>;
648
649 /// Records a heartbeat timestamp.
650 ///
651 /// # Errors
652 ///
653 /// Returns an error if heartbeat recording fails.
654 fn heartbeat(&self, timestamp: UnixNanos) -> anyhow::Result<()>;
655}