Skip to main content

nautilus_infrastructure/redis/
queries.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
16use 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
40// Collection keys
41const 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
56// Index keys
57const 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    /// Serializes the given `payload` using the specified `encoding` to a byte vector.
74    ///
75    /// # Errors
76    ///
77    /// Returns an error if serialization to the chosen encoding fails.
78    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    /// Deserializes the given byte slice `payload` into type `T` using the specified `encoding`.
105    ///
106    /// # Errors
107    ///
108    /// Returns an error if deserialization from the chosen encoding fails or converting to the target type fails.
109    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    /// Scans Redis for keys matching the given `pattern`.
133    ///
134    /// # Errors
135    ///
136    /// Returns an error if the Redis scan operation fails.
137    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 cursor is 0, we've completed the full scan
158            if new_cursor == 0 {
159                break;
160            }
161
162            cursor = new_cursor;
163        }
164
165        Ok(result)
166    }
167
168    /// Bulk reads multiple keys from Redis using MGET for efficiency.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the underlying Redis MGET operation fails.
173    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        // Use MGET to fetch all keys in a single network operation
184        let results: Vec<Option<Vec<u8>>> =
185            redis::cmd("MGET").arg(keys).query_async(&mut con).await?;
186
187        // Convert Vec<u8> to Bytes
188        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    /// Bulk reads multiple keys from Redis using MGET, batched into chunks.
197    ///
198    /// Keys are batched into chunks of `batch_size` to avoid exceeding Redis
199    /// request size limits on some providers.
200    ///
201    /// # Errors
202    ///
203    /// Returns an error if `batch_size` is zero or if the underlying Redis MGET operation fails.
204    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    /// Reads raw byte payloads for `key` under `trader_key` from Redis.
232    ///
233    /// # Errors
234    ///
235    /// Returns an error if the underlying Redis read operation fails or if the collection is unsupported.
236    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    /// Loads all cache data (currencies, instruments, synthetics, accounts, orders, positions) for `trader_key`.
256    ///
257    /// # Errors
258    ///
259    /// Returns an error if loading any of the individual caches fails or combining data fails.
260    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        // For now, we don't load greeks and yield curves from the database
278        // This will be implemented in the future
279        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    /// Loads all currencies for `trader_key` using the specified `encoding`.
296    ///
297    /// # Errors
298    ///
299    /// Returns an error if scanning keys or reading currency data fails.
300    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        // Use bulk loading with MGET for efficiency
317        let bulk_values = Self::read_bulk(&con, &keys).await?;
318
319        // Process the bulk results
320        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    /// Loads all instruments for `trader_key` using the specified `encoding`.
348    ///
349    /// # Errors
350    ///
351    /// Returns an error if scanning keys or reading instrument data fails.
352    /// Loads all instruments for `trader_key` using the specified `encoding`.
353    ///
354    /// # Errors
355    ///
356    /// Returns an error if scanning keys or reading instrument data fails.
357    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        // Insert all Instrument_id (key) and Instrument (value) into the HashMap, filtering out None values.
399        instruments.extend(join_all(futures).await.into_iter().flatten());
400        log::debug!("Loaded {} instruments(s)", instruments.len());
401
402        Ok(instruments)
403    }
404
405    /// Loads all instrument closes for `trader_key`.
406    ///
407    /// Missing or invalid close data fails the load so recovery cannot silently omit a close.
408    ///
409    /// # Errors
410    ///
411    /// Returns an error if scanning, reading, parsing, or deserializing instrument closes fails.
412    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    /// Loads all synthetic instruments for `trader_key` using the specified `encoding`.
444    ///
445    /// # Errors
446    ///
447    /// Returns an error if scanning keys or reading synthetic instrument data fails.
448    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        // Insert all Instrument_id (key) and Synthetic (value) into the HashMap, filtering out None values.
500        synthetics.extend(join_all(futures).await.into_iter().flatten());
501        log::debug!("Loaded {} synthetics(s)", synthetics.len());
502
503        Ok(synthetics)
504    }
505
506    /// Loads all accounts for `trader_key` using the specified `encoding`.
507    ///
508    /// # Errors
509    ///
510    /// Returns an error if scanning keys or reading account data fails.
511    /// Loads all accounts for `trader_key` using the specified `encoding`.
512    ///
513    /// # Errors
514    ///
515    /// Returns an error if scanning keys or reading account data fails.
516    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        // Insert all Account_id (key) and Account (value) into the HashMap, filtering out None values.
556        accounts.extend(join_all(futures).await.into_iter().flatten());
557        log::debug!("Loaded {} accounts(s)", accounts.len());
558
559        Ok(accounts)
560    }
561
562    /// Loads all orders for `trader_key` using the specified `encoding`.
563    ///
564    /// # Errors
565    ///
566    /// Returns an error if scanning keys or reading order data fails.
567    /// Loads all orders for `trader_key` using the specified `encoding`.
568    ///
569    /// # Errors
570    ///
571    /// Returns an error if scanning keys or reading order data fails.
572    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        // Insert all Client-Order-Id (key) and Order (value) into the HashMap, filtering out None values.
612        orders.extend(join_all(futures).await.into_iter().flatten());
613        log::debug!("Loaded {} order(s)", orders.len());
614
615        Ok(orders)
616    }
617
618    /// Loads all positions for `trader_key` using the specified `encoding`.
619    ///
620    /// # Errors
621    ///
622    /// Returns an error if scanning keys or reading position data fails.
623    /// Loads all positions for `trader_key` using the specified `encoding`.
624    ///
625    /// # Errors
626    ///
627    /// Returns an error if scanning keys or reading position data fails.
628    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        // Insert all Position_id (key) and Position (value) into the HashMap, filtering out None values.
668        positions.extend(join_all(futures).await.into_iter().flatten());
669        log::debug!("Loaded {} position(s)", positions.len());
670
671        Ok(positions)
672    }
673
674    /// Loads the order ID to position ID index for `trader_key`.
675    ///
676    /// # Errors
677    ///
678    /// Returns an error if reading or parsing the index fails.
679    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    /// Loads the order ID to execution client ID index for `trader_key`.
696    ///
697    /// # Errors
698    ///
699    /// Returns an error if reading or parsing the index fails.
700    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    /// Loads all custom data for `trader_key` matching the given `data_type`.
726    ///
727    /// Keys are stored as `custom:<ts_init_020>:<uuid>`; value is full `CustomData` JSON.
728    /// Scans all custom keys, deserializes, filters by `type_name` (full or short), metadata,
729    /// and identifier to match SQL semantics, then sorts by `ts_init` ascending.
730    ///
731    /// # Errors
732    ///
733    /// Returns an error if scanning, bulk read, or deserialization fails.
734    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    /// Loads a single currency for `trader_key` and `code` using the specified `encoding`.
792    ///
793    /// # Errors
794    ///
795    /// Returns an error if the underlying read or deserialization fails.
796    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    /// Loads a single instrument for `trader_key` and `instrument_id` using the specified `encoding`.
814    ///
815    /// # Errors
816    ///
817    /// Returns an error if the underlying read or deserialization fails.
818    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    /// Loads a single synthetic instrument for `trader_key` and `instrument_id` using the specified `encoding`.
835    ///
836    /// # Errors
837    ///
838    /// Returns an error if the underlying read or deserialization fails.
839    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    /// Loads a single account for `trader_key` and `account_id` using the specified `encoding`.
856    ///
857    /// # Errors
858    ///
859    /// Returns an error if the underlying read or deserialization fails.
860    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    /// Loads a single order for `trader_key` and `client_order_id` using the specified `encoding`.
881    ///
882    /// # Errors
883    ///
884    /// Returns an error if the underlying read or deserialization fails.
885    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    /// Loads a single position for `trader_key` and `position_id` using the specified `encoding`.
906    ///
907    /// # Errors
908    ///
909    /// Returns an error if the underlying read or deserialization fails.
910    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}