1use std::{collections::HashMap, str::FromStr};
17
18use ahash::AHashMap;
19use bytes::Bytes;
20use futures::future::join_all;
21use jiff::Timestamp;
22use nautilus_common::{cache::database::CacheMap, enums::SerializationEncoding};
23use nautilus_model::{
24 accounts::AccountAny,
25 data::{CustomData, DataType, HasTsInit, InstrumentClose},
26 events::{AccountState, OrderEventAny, OrderFilled, PositionSnapshot},
27 identifiers::{AccountId, ClientId, ClientOrderId, InstrumentId, PositionId},
28 instruments::{InstrumentAny, SyntheticInstrument},
29 orders::OrderAny,
30 position::Position,
31 types::Currency,
32};
33use redis::{AsyncCommands, aio::ConnectionManager};
34use serde::{Serialize, de::DeserializeOwned};
35use serde_json::Value;
36use ustr::Ustr;
37
38use super::get_index_key;
39
40const INDEX: &str = "index";
42const GENERAL: &str = "general";
43const CURRENCIES: &str = "currencies";
44const INSTRUMENTS: &str = "instruments";
45const INSTRUMENT_CLOSES: &str = "instrument_closes";
46const SYNTHETICS: &str = "synthetics";
47const ACCOUNTS: &str = "accounts";
48const ORDERS: &str = "orders";
49const POSITIONS: &str = "positions";
50const SNAPSHOTS: &str = "snapshots";
51const ACTORS: &str = "actors";
52const STRATEGIES: &str = "strategies";
53const CUSTOM: &str = "custom";
54const REDIS_DELIMITER: char = ':';
55
56const INDEX_ORDER_IDS: &str = "index:order_ids";
58const INDEX_ORDER_POSITION: &str = "index:order_position";
59const INDEX_ORDER_CLIENT: &str = "index:order_client";
60const INDEX_ORDERS: &str = "index:orders";
61const INDEX_ORDERS_OPEN: &str = "index:orders_open";
62const INDEX_ORDERS_CLOSED: &str = "index:orders_closed";
63const INDEX_ORDERS_EMULATED: &str = "index:orders_emulated";
64const INDEX_ORDERS_INFLIGHT: &str = "index:orders_inflight";
65const INDEX_POSITIONS: &str = "index:positions";
66const INDEX_POSITIONS_OPEN: &str = "index:positions_open";
67const INDEX_POSITIONS_CLOSED: &str = "index:positions_closed";
68
69#[derive(Debug)]
70pub struct DatabaseQueries;
71
72impl DatabaseQueries {
73 pub fn serialize_payload<T: Serialize>(
79 encoding: SerializationEncoding,
80 payload: &T,
81 ) -> anyhow::Result<Vec<u8>> {
82 match encoding {
83 SerializationEncoding::MsgPack => {
84 let mut value = serde_json::to_value(payload)?;
85 convert_timestamps(&mut value);
86 rmp_serde::to_vec(&value)
87 .map_err(|e| anyhow::anyhow!("Failed to serialize msgpack `payload`: {e}"))
88 }
89 SerializationEncoding::Json => {
90 let mut value = serde_json::to_value(payload)?;
91 convert_timestamps(&mut value);
92 serde_json::to_vec(&value)
93 .map_err(|e| anyhow::anyhow!("Failed to serialize json `payload`: {e}"))
94 }
95 SerializationEncoding::Sbe => {
96 anyhow::bail!("SBE encoding is not supported for Redis cache payloads")
97 }
98 SerializationEncoding::Capnp => {
99 anyhow::bail!("Cap'n Proto encoding is not supported for Redis cache payloads")
100 }
101 }
102 }
103
104 pub fn deserialize_payload<T: DeserializeOwned>(
110 encoding: SerializationEncoding,
111 payload: &[u8],
112 ) -> anyhow::Result<T> {
113 let mut value = match encoding {
114 SerializationEncoding::MsgPack => rmp_serde::from_slice(payload)
115 .map_err(|e| anyhow::anyhow!("Failed to deserialize msgpack `payload`: {e}"))?,
116 SerializationEncoding::Json => serde_json::from_slice(payload)
117 .map_err(|e| anyhow::anyhow!("Failed to deserialize json `payload`: {e}"))?,
118 SerializationEncoding::Sbe => {
119 anyhow::bail!("SBE encoding is not supported for Redis cache payloads")
120 }
121 SerializationEncoding::Capnp => {
122 anyhow::bail!("Cap'n Proto encoding is not supported for Redis cache payloads")
123 }
124 };
125
126 convert_timestamp_strings(&mut value);
127
128 serde_json::from_value(value)
129 .map_err(|e| anyhow::anyhow!("Failed to convert value to target type: {e}"))
130 }
131
132 pub async fn scan_keys(
138 con: &mut ConnectionManager,
139 pattern: String,
140 ) -> anyhow::Result<Vec<String>> {
141 let mut result = Vec::new();
142 let mut cursor = 0u64;
143
144 loop {
145 let scan_result: (u64, Vec<String>) = redis::cmd("SCAN")
146 .arg(cursor)
147 .arg("MATCH")
148 .arg(&pattern)
149 .arg("COUNT")
150 .arg(5000)
151 .query_async(con)
152 .await?;
153
154 let (new_cursor, keys) = scan_result;
155 result.extend(keys);
156
157 if new_cursor == 0 {
159 break;
160 }
161
162 cursor = new_cursor;
163 }
164
165 Ok(result)
166 }
167
168 pub async fn read_bulk(
174 con: &ConnectionManager,
175 keys: &[String],
176 ) -> anyhow::Result<Vec<Option<Bytes>>> {
177 if keys.is_empty() {
178 return Ok(vec![]);
179 }
180
181 let mut con = con.clone();
182
183 let results: Vec<Option<Vec<u8>>> =
185 redis::cmd("MGET").arg(keys).query_async(&mut con).await?;
186
187 let bytes_results: Vec<Option<Bytes>> = results
189 .into_iter()
190 .map(|opt| opt.map(Bytes::from))
191 .collect();
192
193 Ok(bytes_results)
194 }
195
196 pub async fn read_bulk_batched(
205 con: &ConnectionManager,
206 keys: &[String],
207 batch_size: usize,
208 ) -> anyhow::Result<Vec<Option<Bytes>>> {
209 if batch_size == 0 {
210 anyhow::bail!("`batch_size` must be greater than zero");
211 }
212
213 if keys.is_empty() {
214 return Ok(vec![]);
215 }
216
217 let mut all_results: Vec<Option<Bytes>> = Vec::with_capacity(keys.len());
218
219 for chunk in keys.chunks(batch_size) {
220 let mut con = con.clone();
221
222 let results: Vec<Option<Vec<u8>>> =
223 redis::cmd("MGET").arg(chunk).query_async(&mut con).await?;
224
225 all_results.extend(results.into_iter().map(|opt| opt.map(Bytes::from)));
226 }
227
228 Ok(all_results)
229 }
230
231 pub async fn read(
237 con: &ConnectionManager,
238 trader_key: &str,
239 key: &str,
240 ) -> anyhow::Result<Vec<Bytes>> {
241 let collection = Self::get_collection_key(key)?;
242 let full_key = format!("{trader_key}{REDIS_DELIMITER}{key}");
243
244 let mut con = con.clone();
245
246 match collection {
247 INDEX => Self::read_index(&mut con, &full_key).await,
248 GENERAL | CURRENCIES | INSTRUMENTS | INSTRUMENT_CLOSES | SYNTHETICS | ACTORS
249 | STRATEGIES => Self::read_string(&mut con, &full_key).await,
250 ACCOUNTS | ORDERS | POSITIONS | SNAPSHOTS => Self::read_list(&mut con, &full_key).await,
251 _ => anyhow::bail!("Unsupported operation: `read` for collection '{collection}'"),
252 }
253 }
254
255 pub async fn load_all(
261 con: &ConnectionManager,
262 encoding: SerializationEncoding,
263 trader_key: &str,
264 ) -> anyhow::Result<CacheMap> {
265 let (currencies, instruments, instrument_closes, synthetics, accounts, orders, positions) =
266 tokio::try_join!(
267 Self::load_currencies(con, trader_key, encoding),
268 Self::load_instruments(con, trader_key, encoding),
269 Self::load_instrument_closes(con, trader_key, encoding),
270 Self::load_synthetics(con, trader_key, encoding),
271 Self::load_accounts(con, trader_key, encoding),
272 Self::load_orders(con, trader_key, encoding),
273 Self::load_positions(con, trader_key, encoding)
274 )
275 .map_err(|e| anyhow::anyhow!("Error loading cache data: {e}"))?;
276
277 let greeks = AHashMap::new();
280 let yield_curves = AHashMap::new();
281
282 Ok(CacheMap {
283 currencies,
284 instruments,
285 instrument_closes,
286 synthetics,
287 accounts,
288 orders,
289 positions,
290 greeks,
291 yield_curves,
292 })
293 }
294
295 pub async fn load_currencies(
301 con: &ConnectionManager,
302 trader_key: &str,
303 encoding: SerializationEncoding,
304 ) -> anyhow::Result<AHashMap<Ustr, Currency>> {
305 let mut currencies = AHashMap::new();
306 let pattern = format!("{trader_key}{REDIS_DELIMITER}{CURRENCIES}*");
307 log::debug!("Loading {pattern}");
308
309 let mut con = con.clone();
310 let keys = Self::scan_keys(&mut con, pattern).await?;
311
312 if keys.is_empty() {
313 return Ok(currencies);
314 }
315
316 let bulk_values = Self::read_bulk(&con, &keys).await?;
318
319 for (key, value_opt) in keys.iter().zip(bulk_values.iter()) {
321 let currency_code = if let Some(code) = key.as_str().rsplit(':').next() {
322 Ustr::from(code)
323 } else {
324 log::error!("Invalid key format: {key}");
325 continue;
326 };
327
328 if let Some(value_bytes) = value_opt {
329 match Self::deserialize_payload(encoding, value_bytes) {
330 Ok(currency) => {
331 currencies.insert(currency_code, currency);
332 }
333 Err(e) => {
334 log::error!("Failed to deserialize currency {currency_code}: {e}");
335 }
336 }
337 } else {
338 log::error!("Currency not found in Redis: {currency_code}");
339 }
340 }
341
342 log::debug!("Loaded {} currencies(s)", currencies.len());
343
344 Ok(currencies)
345 }
346
347 pub async fn load_instruments(
358 con: &ConnectionManager,
359 trader_key: &str,
360 encoding: SerializationEncoding,
361 ) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>> {
362 let mut instruments = AHashMap::new();
363 let prefix = format!("{trader_key}{REDIS_DELIMITER}{INSTRUMENTS}{REDIS_DELIMITER}");
364 let pattern = format!("{prefix}*");
365 log::debug!("Loading {pattern}");
366
367 let mut con = con.clone();
368 let keys = Self::scan_keys(&mut con, pattern).await?;
369
370 let futures: Vec<_> = keys
371 .iter()
372 .map(|key| {
373 let con = con.clone();
374 let prefix = &prefix;
375 async move {
376 let instrument_id = parse_instrument_key(key, prefix);
377
378 let Ok(instrument_id) = instrument_id else {
379 log::error!("Failed to parse InstrumentId from Redis key: {key}");
380 return None;
381 };
382
383 match Self::load_instrument(&con, trader_key, &instrument_id, encoding).await {
384 Ok(Some(instrument)) => Some((instrument_id, instrument)),
385 Ok(None) => {
386 log::error!("Instrument not found: {instrument_id}");
387 None
388 }
389 Err(e) => {
390 log::error!("Failed to load instrument {instrument_id}: {e}");
391 None
392 }
393 }
394 }
395 })
396 .collect();
397
398 instruments.extend(join_all(futures).await.into_iter().flatten());
400 log::debug!("Loaded {} instruments(s)", instruments.len());
401
402 Ok(instruments)
403 }
404
405 pub async fn load_instrument_closes(
413 con: &ConnectionManager,
414 trader_key: &str,
415 encoding: SerializationEncoding,
416 ) -> anyhow::Result<AHashMap<InstrumentId, InstrumentClose>> {
417 let prefix = format!("{trader_key}{REDIS_DELIMITER}{INSTRUMENT_CLOSES}{REDIS_DELIMITER}");
418 let pattern = format!("{prefix}*");
419 log::debug!("Loading {pattern}");
420
421 let mut con = con.clone();
422 let keys = Self::scan_keys(&mut con, pattern).await?;
423 let values = Self::read_bulk(&con, &keys).await?;
424 let mut closes = AHashMap::with_capacity(keys.len());
425
426 for (key, value) in keys.into_iter().zip(values) {
427 let instrument_id = parse_instrument_key(&key, &prefix)?;
428 let value = value
429 .ok_or_else(|| anyhow::anyhow!("Instrument close not found in Redis: {key}"))?;
430 let close: InstrumentClose = Self::deserialize_payload(encoding, &value)?;
431 anyhow::ensure!(
432 close.instrument_id == instrument_id,
433 "Instrument close key ID {instrument_id} did not match payload ID {}",
434 close.instrument_id,
435 );
436 closes.insert(instrument_id, close);
437 }
438
439 log::debug!("Loaded {} instrument close(s)", closes.len());
440 Ok(closes)
441 }
442
443 pub async fn load_synthetics(
449 con: &ConnectionManager,
450 trader_key: &str,
451 encoding: SerializationEncoding,
452 ) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>> {
453 let mut synthetics = AHashMap::new();
454 let pattern = format!("{trader_key}{REDIS_DELIMITER}{SYNTHETICS}*");
455 log::debug!("Loading {pattern}");
456
457 let mut con = con.clone();
458 let keys = Self::scan_keys(&mut con, pattern).await?;
459
460 let futures: Vec<_> = keys
461 .iter()
462 .map(|key| {
463 let con = con.clone();
464 async move {
465 let instrument_id = key
466 .as_str()
467 .rsplit(':')
468 .next()
469 .ok_or_else(|| {
470 log::error!("Invalid key format: {key}");
471 "Invalid key format"
472 })
473 .and_then(|code| {
474 InstrumentId::from_str(code).map_err(|e| {
475 log::error!("Failed to parse InstrumentId for {key}: {e}");
476 "Invalid instrument ID"
477 })
478 });
479
480 let Ok(instrument_id) = instrument_id else {
481 return None;
482 };
483
484 match Self::load_synthetic(&con, trader_key, &instrument_id, encoding).await {
485 Ok(Some(synthetic)) => Some((instrument_id, synthetic)),
486 Ok(None) => {
487 log::error!("Synthetic not found: {instrument_id}");
488 None
489 }
490 Err(e) => {
491 log::error!("Failed to load synthetic {instrument_id}: {e}");
492 None
493 }
494 }
495 }
496 })
497 .collect();
498
499 synthetics.extend(join_all(futures).await.into_iter().flatten());
501 log::debug!("Loaded {} synthetics(s)", synthetics.len());
502
503 Ok(synthetics)
504 }
505
506 pub async fn load_accounts(
517 con: &ConnectionManager,
518 trader_key: &str,
519 encoding: SerializationEncoding,
520 ) -> anyhow::Result<AHashMap<AccountId, AccountAny>> {
521 let mut accounts = AHashMap::new();
522 let pattern = format!("{trader_key}{REDIS_DELIMITER}{ACCOUNTS}*");
523 log::debug!("Loading {pattern}");
524
525 let mut con = con.clone();
526 let keys = Self::scan_keys(&mut con, pattern).await?;
527
528 let futures: Vec<_> = keys
529 .iter()
530 .map(|key| {
531 let con = con.clone();
532 async move {
533 let account_id = if let Some(code) = key.as_str().rsplit(':').next() {
534 AccountId::from(code)
535 } else {
536 log::error!("Invalid key format: {key}");
537 return None;
538 };
539
540 match Self::load_account(&con, trader_key, &account_id, encoding).await {
541 Ok(Some(account)) => Some((account_id, account)),
542 Ok(None) => {
543 log::error!("Account not found: {account_id}");
544 None
545 }
546 Err(e) => {
547 log::error!("Failed to load account {account_id}: {e}");
548 None
549 }
550 }
551 }
552 })
553 .collect();
554
555 accounts.extend(join_all(futures).await.into_iter().flatten());
557 log::debug!("Loaded {} accounts(s)", accounts.len());
558
559 Ok(accounts)
560 }
561
562 pub async fn load_orders(
573 con: &ConnectionManager,
574 trader_key: &str,
575 encoding: SerializationEncoding,
576 ) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>> {
577 let mut orders = AHashMap::new();
578 let pattern = format!("{trader_key}{REDIS_DELIMITER}{ORDERS}*");
579 log::debug!("Loading {pattern}");
580
581 let mut con = con.clone();
582 let keys = Self::scan_keys(&mut con, pattern).await?;
583
584 let futures: Vec<_> = keys
585 .iter()
586 .map(|key| {
587 let con = con.clone();
588 async move {
589 let client_order_id = if let Some(code) = key.as_str().rsplit(':').next() {
590 ClientOrderId::from(code)
591 } else {
592 log::error!("Invalid key format: {key}");
593 return None;
594 };
595
596 match Self::load_order(&con, trader_key, &client_order_id, encoding).await {
597 Ok(Some(order)) => Some((client_order_id, order)),
598 Ok(None) => {
599 log::error!("Order not found: {client_order_id}");
600 None
601 }
602 Err(e) => {
603 log::error!("Failed to load order {client_order_id}: {e}");
604 None
605 }
606 }
607 }
608 })
609 .collect();
610
611 orders.extend(join_all(futures).await.into_iter().flatten());
613 log::debug!("Loaded {} order(s)", orders.len());
614
615 Ok(orders)
616 }
617
618 pub async fn load_positions(
629 con: &ConnectionManager,
630 trader_key: &str,
631 encoding: SerializationEncoding,
632 ) -> anyhow::Result<AHashMap<PositionId, Position>> {
633 let mut positions = AHashMap::new();
634 let pattern = format!("{trader_key}{REDIS_DELIMITER}{POSITIONS}*");
635 log::debug!("Loading {pattern}");
636
637 let mut con = con.clone();
638 let keys = Self::scan_keys(&mut con, pattern).await?;
639
640 let futures: Vec<_> = keys
641 .iter()
642 .map(|key| {
643 let con = con.clone();
644 async move {
645 let position_id = if let Some(code) = key.as_str().rsplit(':').next() {
646 PositionId::from(code)
647 } else {
648 log::error!("Invalid key format: {key}");
649 return None;
650 };
651
652 match Self::load_position(&con, trader_key, &position_id, encoding).await {
653 Ok(Some(position)) => Some((position_id, position)),
654 Ok(None) => {
655 log::error!("Position not found: {position_id}");
656 None
657 }
658 Err(e) => {
659 log::error!("Failed to load position {position_id}: {e}");
660 None
661 }
662 }
663 }
664 })
665 .collect();
666
667 positions.extend(join_all(futures).await.into_iter().flatten());
669 log::debug!("Loaded {} position(s)", positions.len());
670
671 Ok(positions)
672 }
673
674 pub async fn load_index_order_position(
680 con: &ConnectionManager,
681 trader_key: &str,
682 ) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>> {
683 let index = Self::read_index_hash(con, trader_key, INDEX_ORDER_POSITION).await?;
684 Ok(index
685 .into_iter()
686 .map(|(k, v)| {
687 (
688 ClientOrderId::from(k.as_str()),
689 PositionId::from(v.as_str()),
690 )
691 })
692 .collect())
693 }
694
695 pub async fn load_index_order_client(
701 con: &ConnectionManager,
702 trader_key: &str,
703 ) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>> {
704 let index = Self::read_index_hash(con, trader_key, INDEX_ORDER_CLIENT).await?;
705 Ok(index
706 .into_iter()
707 .map(|(k, v)| (ClientOrderId::from(k.as_str()), ClientId::from(v.as_str())))
708 .collect())
709 }
710
711 async fn read_index_hash(
712 con: &ConnectionManager,
713 trader_key: &str,
714 key: &str,
715 ) -> anyhow::Result<HashMap<String, String>> {
716 let result = Self::read(con, trader_key, key).await?;
717 if result.is_empty() {
718 return Ok(HashMap::new());
719 }
720
721 serde_json::from_slice(&result[0])
722 .map_err(|e| anyhow::anyhow!("Failed to parse index hash '{key}': {e}"))
723 }
724
725 pub async fn load_custom_data(
735 con: &ConnectionManager,
736 trader_key: &str,
737 data_type: &DataType,
738 ) -> anyhow::Result<Vec<CustomData>> {
739 let pattern = format!("{trader_key}{REDIS_DELIMITER}{CUSTOM}*");
740 log::debug!("Loading custom data {pattern}");
741
742 let mut con = con.clone();
743 let keys = Self::scan_keys(&mut con, pattern).await?;
744
745 if keys.is_empty() {
746 return Ok(Vec::new());
747 }
748
749 let values = Self::read_bulk(&con, &keys).await?;
750 let request_type_name = data_type.type_name();
751 let request_short = request_type_name
752 .rsplit([':', '.'])
753 .next()
754 .unwrap_or(request_type_name);
755 let request_identifier = data_type.identifier().unwrap_or("");
756
757 let mut results = Vec::new();
758
759 for value_opt in values {
760 let Some(value_bytes) = value_opt else {
761 continue;
762 };
763 let custom = match CustomData::from_json_bytes(value_bytes.as_ref()) {
764 Ok(c) => c,
765 Err(e) => {
766 log::warn!("Failed to deserialize custom data from Redis: {e}");
767 continue;
768 }
769 };
770 let stored_type_name = custom.data_type.type_name();
771 let type_match =
772 stored_type_name == request_type_name || stored_type_name == request_short;
773 let identifier_match =
774 custom.data_type.identifier().unwrap_or("") == request_identifier;
775 let metadata_match = match (data_type.metadata(), custom.data_type.metadata()) {
776 (None, None) => true,
777 (Some(a), Some(b)) => serde_json::to_value(a).ok() == serde_json::to_value(b).ok(),
778 _ => false,
779 };
780
781 if type_match && identifier_match && metadata_match {
782 results.push(custom);
783 }
784 }
785
786 results.sort_by_key(HasTsInit::ts_init);
787 log::debug!("Loaded {} custom data item(s)", results.len());
788 Ok(results)
789 }
790
791 pub async fn load_currency(
797 con: &ConnectionManager,
798 trader_key: &str,
799 code: &Ustr,
800 encoding: SerializationEncoding,
801 ) -> anyhow::Result<Option<Currency>> {
802 let key = format!("{CURRENCIES}{REDIS_DELIMITER}{code}");
803 let result = Self::read(con, trader_key, &key).await?;
804
805 if result.is_empty() {
806 return Ok(None);
807 }
808
809 let currency = Self::deserialize_payload(encoding, &result[0])?;
810 Ok(currency)
811 }
812
813 pub async fn load_instrument(
819 con: &ConnectionManager,
820 trader_key: &str,
821 instrument_id: &InstrumentId,
822 encoding: SerializationEncoding,
823 ) -> anyhow::Result<Option<InstrumentAny>> {
824 let key = format!("{INSTRUMENTS}{REDIS_DELIMITER}{instrument_id}");
825 let result = Self::read(con, trader_key, &key).await?;
826 if result.is_empty() {
827 return Ok(None);
828 }
829
830 let instrument: InstrumentAny = Self::deserialize_payload(encoding, &result[0])?;
831 Ok(Some(instrument))
832 }
833
834 pub async fn load_synthetic(
840 con: &ConnectionManager,
841 trader_key: &str,
842 instrument_id: &InstrumentId,
843 encoding: SerializationEncoding,
844 ) -> anyhow::Result<Option<SyntheticInstrument>> {
845 let key = format!("{SYNTHETICS}{REDIS_DELIMITER}{instrument_id}");
846 let result = Self::read(con, trader_key, &key).await?;
847 if result.is_empty() {
848 return Ok(None);
849 }
850
851 let synthetic: SyntheticInstrument = Self::deserialize_payload(encoding, &result[0])?;
852 Ok(Some(synthetic))
853 }
854
855 pub async fn load_account(
861 con: &ConnectionManager,
862 trader_key: &str,
863 account_id: &AccountId,
864 encoding: SerializationEncoding,
865 ) -> anyhow::Result<Option<AccountAny>> {
866 let key = format!("{ACCOUNTS}{REDIS_DELIMITER}{account_id}");
867 let result = Self::read(con, trader_key, &key).await?;
868 if result.is_empty() {
869 return Ok(None);
870 }
871
872 let events: Vec<AccountState> = result
873 .iter()
874 .map(|payload| Self::deserialize_payload(encoding, payload))
875 .collect::<anyhow::Result<_>>()?;
876 let account = AccountAny::from_events(&events)?;
877 Ok(Some(account))
878 }
879
880 pub async fn load_order(
886 con: &ConnectionManager,
887 trader_key: &str,
888 client_order_id: &ClientOrderId,
889 encoding: SerializationEncoding,
890 ) -> anyhow::Result<Option<OrderAny>> {
891 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
892 let result = Self::read(con, trader_key, &key).await?;
893 if result.is_empty() {
894 return Ok(None);
895 }
896
897 let events: Vec<OrderEventAny> = result
898 .iter()
899 .map(|payload| Self::deserialize_payload(encoding, payload))
900 .collect::<anyhow::Result<_>>()?;
901 let order = OrderAny::from_events(events)?;
902 Ok(Some(order))
903 }
904
905 pub async fn load_position(
911 con: &ConnectionManager,
912 trader_key: &str,
913 position_id: &PositionId,
914 encoding: SerializationEncoding,
915 ) -> anyhow::Result<Option<Position>> {
916 let snapshot_key =
917 format!("{SNAPSHOTS}{REDIS_DELIMITER}{POSITIONS}{REDIS_DELIMITER}{position_id}");
918 let snapshots = Self::read(con, trader_key, &snapshot_key).await?;
919 for payload in snapshots.iter().rev() {
920 let snapshot: PositionSnapshot = Self::deserialize_payload(encoding, payload)?;
921 if let Some(replay_state) = snapshot.replay_state {
922 return serde_json::from_value(replay_state)
923 .map(Some)
924 .map_err(|e| anyhow::anyhow!("Failed to decode position replay state: {e}"));
925 }
926 }
927
928 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
929 let result = Self::read(con, trader_key, &key).await?;
930 if result.is_empty() {
931 return Ok(None);
932 }
933
934 let fills: Vec<OrderFilled> = result
935 .iter()
936 .map(|payload| Self::deserialize_payload(encoding, payload))
937 .collect::<anyhow::Result<_>>()?;
938 let Some((first_fill, remaining_fills)) = fills.split_first() else {
939 return Ok(None);
940 };
941 let Some(instrument) =
942 Self::load_instrument(con, trader_key, &first_fill.instrument_id, encoding).await?
943 else {
944 log::error!(
945 "Instrument not found for position {position_id}: {}",
946 first_fill.instrument_id
947 );
948 return Ok(None);
949 };
950
951 let mut position = Position::new(&instrument, first_fill.clone());
952 for fill in remaining_fills {
953 if position.trade_ids().contains(&fill.trade_id) {
954 anyhow::bail!(
955 "Duplicate fill event for position {position_id}: {}",
956 fill.trade_id
957 );
958 }
959 position.apply(fill);
960 }
961
962 Ok(Some(position))
963 }
964
965 fn get_collection_key(key: &str) -> anyhow::Result<&str> {
966 key.split_once(REDIS_DELIMITER)
967 .map(|(collection, _)| collection)
968 .ok_or_else(|| {
969 anyhow::anyhow!("Invalid `key`, missing a '{REDIS_DELIMITER}' delimiter, was {key}")
970 })
971 }
972
973 async fn read_index(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
974 let index_key = get_index_key(key)?;
975 match index_key {
976 INDEX_ORDER_IDS
977 | INDEX_ORDERS
978 | INDEX_ORDERS_OPEN
979 | INDEX_ORDERS_CLOSED
980 | INDEX_ORDERS_EMULATED
981 | INDEX_ORDERS_INFLIGHT
982 | INDEX_POSITIONS
983 | INDEX_POSITIONS_OPEN
984 | INDEX_POSITIONS_CLOSED => Self::read_set(conn, key).await,
985 INDEX_ORDER_POSITION | INDEX_ORDER_CLIENT => Self::read_hset(conn, key).await,
986 _ => anyhow::bail!("Index unknown '{index_key}' on read"),
987 }
988 }
989
990 async fn read_string(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
991 let result: Vec<u8> = conn.get(key).await?;
992
993 if result.is_empty() {
994 Ok(vec![])
995 } else {
996 Ok(vec![Bytes::from(result)])
997 }
998 }
999
1000 async fn read_set(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
1001 let result: Vec<Bytes> = conn.smembers(key).await?;
1002 Ok(result)
1003 }
1004
1005 async fn read_hset(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
1006 let result: HashMap<String, String> = conn.hgetall(key).await?;
1007 let json = serde_json::to_string(&result)?;
1008 Ok(vec![Bytes::from(json.into_bytes())])
1009 }
1010
1011 async fn read_list(conn: &mut ConnectionManager, key: &str) -> anyhow::Result<Vec<Bytes>> {
1012 let result: Vec<Bytes> = conn.lrange(key, 0, -1).await?;
1013 Ok(result)
1014 }
1015}
1016
1017fn parse_instrument_key(key: &str, prefix: &str) -> anyhow::Result<InstrumentId> {
1018 let value = key
1019 .strip_prefix(prefix)
1020 .filter(|value| !value.is_empty())
1021 .ok_or_else(|| anyhow::anyhow!("Invalid instrument key '{key}'"))?;
1022 InstrumentId::from_str(value)
1023 .map_err(|e| anyhow::anyhow!("Failed to parse instrument ID from key '{key}': {e}"))
1024}
1025
1026fn is_timestamp_field(key: &str) -> bool {
1027 let expire_match = key == "expire_time_ns";
1028 let ts_match = key.starts_with("ts_");
1029 expire_match || ts_match
1030}
1031
1032fn convert_timestamps(value: &mut Value) {
1033 match value {
1034 Value::Object(map) => {
1035 for (key, v) in map {
1036 if is_timestamp_field(key)
1037 && let Value::Number(n) = v
1038 && let Some(n) = n.as_u64()
1039 {
1040 let dt = Timestamp::from_nanosecond(i128::from(n))
1041 .expect("UnixNanos is within Jiff's timestamp range");
1042 *v = Value::String(format!("{dt:.9}"));
1043 }
1044 convert_timestamps(v);
1045 }
1046 }
1047 Value::Array(arr) => {
1048 for item in arr {
1049 convert_timestamps(item);
1050 }
1051 }
1052 _ => {}
1053 }
1054}
1055
1056fn convert_timestamp_strings(value: &mut Value) {
1057 match value {
1058 Value::Object(map) => {
1059 for (key, v) in map {
1060 if is_timestamp_field(key)
1061 && let Value::String(s) = v
1062 && let Ok(dt) = s.parse::<Timestamp>()
1063 {
1064 let nanos = u64::try_from(dt.as_nanosecond()).expect("Invalid timestamp");
1065 *v = Value::Number(nanos.into());
1066 }
1067 convert_timestamp_strings(v);
1068 }
1069 }
1070 Value::Array(arr) => {
1071 for item in arr {
1072 convert_timestamp_strings(item);
1073 }
1074 }
1075 _ => {}
1076 }
1077}
1078
1079#[cfg(test)]
1080mod tests {
1081 use std::str::FromStr;
1082
1083 use nautilus_common::enums::SerializationEncoding;
1084 use nautilus_core::UnixNanos;
1085 use nautilus_model::{
1086 enums::{AccountType, CurrencyType},
1087 events::AccountState,
1088 identifiers::{AccountId, InstrumentId},
1089 types::{AccountBalance, Currency, Money},
1090 };
1091 use rstest::rstest;
1092 use serde::Deserialize;
1093
1094 use super::{DatabaseQueries, parse_instrument_key};
1095
1096 #[derive(Debug, Deserialize, PartialEq, Eq)]
1097 struct TimestampPayload {
1098 ts_event: UnixNanos,
1099 ts_init: UnixNanos,
1100 }
1101
1102 #[rstest]
1103 #[case(SerializationEncoding::Json)]
1104 #[case(SerializationEncoding::MsgPack)]
1105 fn test_deserialize_chrono_timestamp_payload(#[case] encoding: SerializationEncoding) {
1106 let json = include_bytes!("../../test_data/redis_cache_timestamp_chrono.json");
1107 let payload = match encoding {
1108 SerializationEncoding::Json => json.to_vec(),
1109 SerializationEncoding::MsgPack => {
1110 let value = serde_json::from_slice::<serde_json::Value>(json).unwrap();
1111 rmp_serde::to_vec(&value).unwrap()
1112 }
1113 _ => unreachable!(),
1114 };
1115
1116 let result =
1117 DatabaseQueries::deserialize_payload::<TimestampPayload>(encoding, &payload).unwrap();
1118
1119 assert_eq!(
1120 result,
1121 TimestampPayload {
1122 ts_event: UnixNanos::from(1_123_456_789),
1123 ts_init: UnixNanos::from(2_987_654_321),
1124 }
1125 );
1126 }
1127
1128 #[rstest]
1129 #[case(SerializationEncoding::Json)]
1130 #[case(SerializationEncoding::MsgPack)]
1131 fn test_wallet_account_state_round_trips_unregistered_currency(
1132 #[case] encoding: SerializationEncoding,
1133 ) {
1134 let currency = Currency::new(
1135 "ENG729C",
1136 6,
1137 0,
1138 "Cache round-trip token",
1139 CurrencyType::Crypto,
1140 );
1141 let total = Money::from_mantissa_exponent(123_456_789, -6, currency);
1142 let state = AccountState::new(
1143 AccountId::new("WALLET-CACHE-001"),
1144 AccountType::Wallet,
1145 vec![AccountBalance::new(total, Money::zero(currency), total)],
1146 vec![],
1147 true,
1148 nautilus_core::UUID4::new(),
1149 UnixNanos::from(1),
1150 UnixNanos::from(2),
1151 None,
1152 );
1153 assert!(Currency::try_from_str("ENG729C").is_none());
1154
1155 let payload = DatabaseQueries::serialize_payload(encoding, &state).unwrap();
1156 let restored: AccountState =
1157 DatabaseQueries::deserialize_payload(encoding, &payload).unwrap();
1158 let restored = restored.balances[0];
1159
1160 assert_eq!(restored.total.raw(), total.raw());
1161 assert_eq!(restored.locked.raw(), 0);
1162 assert_eq!(restored.free.raw(), total.raw());
1163 assert_eq!(restored.currency.code, currency.code);
1164 assert_eq!(restored.currency.precision, currency.precision);
1165 assert_eq!(restored.currency.iso4217, currency.iso4217);
1166 assert_eq!(restored.currency.name, currency.name);
1167 assert_eq!(restored.currency.currency_type, currency.currency_type);
1168 assert!(Currency::try_from_str("ENG729C").is_none());
1169 }
1170
1171 #[rstest]
1172 #[case("0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443.Arbitrum:UniswapV3")]
1173 #[case(concat!(
1174 "0xc9bc8043294146424a4e4607d8ad837d",
1175 "6a659142822bbaaabc83bb57e7447461.Arbitrum:UniswapV4",
1176 ))]
1177 fn test_parse_instrument_key_preserves_colons_in_venue(#[case] value: &str) {
1178 let prefix = "TRADER-001:instruments:";
1179 let key = format!("{prefix}{value}");
1180
1181 let result = parse_instrument_key(&key, prefix).unwrap();
1182
1183 assert_eq!(result, InstrumentId::from_str(value).unwrap());
1184 }
1185}