1use std::{
37 collections::VecDeque,
38 fmt::{Debug, Write as _},
39 ops::ControlFlow,
40 pin::Pin,
41 sync::mpsc::{self, SyncSender},
42 time::Duration,
43};
44
45use ahash::AHashMap;
46use anyhow::Context;
47use bytes::Bytes;
48use chrono::{DateTime, Utc};
49use nautilus_common::{
50 cache::{
51 CacheConfig,
52 database::{CacheDatabaseAdapter, CacheDatabaseFactory, CacheMap},
53 },
54 enums::SerializationEncoding,
55 live::get_runtime,
56 logging::{log_task_awaiting, log_task_started, log_task_stopped},
57 signal::Signal,
58};
59use nautilus_core::{UUID4, UnixNanos, correctness::check_slice_not_empty};
60use nautilus_cryptography::providers::install_cryptographic_provider;
61use nautilus_model::{
62 accounts::AccountAny,
63 data::{Bar, CustomData, DataType, FundingRateUpdate, HasTsInit, QuoteTick, TradeTick},
64 enums::TriggerType,
65 events::{
66 AccountState, OrderEventAny, OrderFilled, OrderSnapshot,
67 position::snapshot::PositionSnapshot,
68 },
69 identifiers::{
70 AccountId, ClientId, ClientOrderId, ComponentId, InstrumentId, PositionId, StrategyId,
71 TraderId, VenueOrderId,
72 },
73 instruments::{Instrument, InstrumentAny, SyntheticInstrument},
74 orderbook::OrderBook,
75 orders::{Order, OrderAny},
76 position::Position,
77 types::{Currency, Money},
78};
79use redis::{AsyncCommands, Pipeline, aio::ConnectionManager};
80use serde::{Deserialize, Serialize};
81use ustr::Ustr;
82
83use super::{REDIS_DELIMITER, REDIS_FLUSHDB, get_index_key};
84use crate::redis::{RedisConnectionConfig, create_redis_connection, queries::DatabaseQueries};
85
86const CACHE_READ: &str = "cache-read";
88const CACHE_WRITE: &str = "cache-write";
89const CACHE_PROCESS: &str = "cache-process";
90
91const FAILED_TX_CHANNEL: &str = "Failed to send to channel";
93
94const INDEX: &str = "index";
96const GENERAL: &str = "general";
97const CURRENCIES: &str = "currencies";
98const INSTRUMENTS: &str = "instruments";
99const SYNTHETICS: &str = "synthetics";
100const ACCOUNTS: &str = "accounts";
101const ORDERS: &str = "orders";
102const POSITIONS: &str = "positions";
103const ACTORS: &str = "actors";
104const STRATEGIES: &str = "strategies";
105const SNAPSHOTS: &str = "snapshots";
106const HEALTH: &str = "health";
107const CUSTOM: &str = "custom";
108
109const INDEX_ORDER_IDS: &str = "index:order_ids";
111const INDEX_ORDER_POSITION: &str = "index:order_position";
112const INDEX_ORDER_CLIENT: &str = "index:order_client";
113const INDEX_ORDERS: &str = "index:orders";
114const INDEX_ORDERS_OPEN: &str = "index:orders_open";
115const INDEX_ORDERS_CLOSED: &str = "index:orders_closed";
116const INDEX_ORDERS_EMULATED: &str = "index:orders_emulated";
117const INDEX_ORDERS_INFLIGHT: &str = "index:orders_inflight";
118const INDEX_POSITIONS: &str = "index:positions";
119const INDEX_POSITIONS_OPEN: &str = "index:positions_open";
120const INDEX_POSITIONS_CLOSED: &str = "index:positions_closed";
121
122#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(default, deny_unknown_fields)]
127#[cfg_attr(
128 feature = "python",
129 pyo3::pyclass(
130 module = "nautilus_trader.core.nautilus_pyo3.infrastructure",
131 from_py_object
132 )
133)]
134#[cfg_attr(
135 feature = "python",
136 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.infrastructure")
137)]
138pub struct RedisCacheConfig {
139 pub host: Option<String>,
141 pub port: Option<u16>,
143 pub username: Option<String>,
145 pub password: Option<String>,
147 pub ssl: bool,
149 pub connection_timeout: u16,
151 pub response_timeout: u16,
153 pub number_of_retries: usize,
155 pub exponent_base: u64,
157 pub max_delay: u64,
159 pub factor: u64,
161}
162
163impl Debug for RedisCacheConfig {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 let redacted = self.password.as_ref().map(|_| "***");
166 f.debug_struct(stringify!(RedisCacheConfig))
167 .field("host", &self.host)
168 .field("port", &self.port)
169 .field("username", &self.username)
170 .field("password", &redacted)
171 .field("ssl", &self.ssl)
172 .field("connection_timeout", &self.connection_timeout)
173 .field("response_timeout", &self.response_timeout)
174 .field("number_of_retries", &self.number_of_retries)
175 .field("exponent_base", &self.exponent_base)
176 .field("max_delay", &self.max_delay)
177 .field("factor", &self.factor)
178 .finish()
179 }
180}
181
182impl Default for RedisCacheConfig {
183 fn default() -> Self {
184 Self {
185 host: None,
186 port: None,
187 username: None,
188 password: None,
189 ssl: false,
190 connection_timeout: 20,
191 response_timeout: 20,
192 number_of_retries: 100,
193 exponent_base: 2,
194 max_delay: 1000,
195 factor: 2,
196 }
197 }
198}
199
200impl RedisConnectionConfig for RedisCacheConfig {
201 fn host(&self) -> Option<&str> {
202 self.host.as_deref()
203 }
204
205 fn port(&self) -> Option<u16> {
206 self.port
207 }
208
209 fn username(&self) -> Option<&str> {
210 self.username.as_deref()
211 }
212
213 fn password(&self) -> Option<&str> {
214 self.password.as_deref()
215 }
216
217 fn ssl(&self) -> bool {
218 self.ssl
219 }
220
221 fn connection_timeout(&self) -> u16 {
222 self.connection_timeout
223 }
224
225 fn response_timeout(&self) -> u16 {
226 self.response_timeout
227 }
228
229 fn number_of_retries(&self) -> usize {
230 self.number_of_retries
231 }
232
233 fn exponent_base(&self) -> u64 {
234 self.exponent_base
235 }
236
237 fn max_delay(&self) -> u64 {
238 self.max_delay
239 }
240
241 fn factor(&self) -> u64 {
242 self.factor
243 }
244}
245
246#[derive(Clone, Debug)]
248pub enum DatabaseOperation {
249 Insert,
250 Update,
251 UpdateOrder,
252 ReplaceList,
253 Delete,
254 Flush(SyncSender<()>),
255 Close,
256}
257
258#[derive(Clone, Debug)]
260pub struct DatabaseCommand {
261 pub op_type: DatabaseOperation,
263 pub key: Option<String>,
265 pub payload: Option<Vec<Bytes>>,
267}
268
269impl DatabaseCommand {
270 #[must_use]
272 pub const fn new(op_type: DatabaseOperation, key: String, payload: Option<Vec<Bytes>>) -> Self {
273 Self {
274 op_type,
275 key: Some(key),
276 payload,
277 }
278 }
279
280 #[must_use]
282 pub const fn close() -> Self {
283 Self {
284 op_type: DatabaseOperation::Close,
285 key: None,
286 payload: None,
287 }
288 }
289}
290
291#[cfg_attr(
292 feature = "python",
293 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.infrastructure")
294)]
295pub struct RedisCacheDatabase {
296 pub con: ConnectionManager,
297 pub trader_id: TraderId,
298 pub trader_key: String,
299 pub encoding: SerializationEncoding,
300 pub bulk_read_batch_size: Option<usize>,
301 tx: tokio::sync::mpsc::UnboundedSender<DatabaseCommand>,
302 handle: Option<tokio::task::JoinHandle<()>>,
303}
304
305impl Debug for RedisCacheDatabase {
306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307 f.debug_struct(stringify!(RedisCacheDatabase))
308 .field("trader_id", &self.trader_id)
309 .field("encoding", &self.encoding)
310 .finish_non_exhaustive()
311 }
312}
313
314impl RedisCacheDatabase {
315 pub async fn new(
324 trader_id: TraderId,
325 instance_id: UUID4,
326 config: CacheConfig,
327 database: RedisCacheConfig,
328 ) -> anyhow::Result<Self> {
329 install_cryptographic_provider();
330
331 let con = create_redis_connection(CACHE_READ, &database).await?;
332
333 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<DatabaseCommand>();
334 let trader_key = get_trader_key(trader_id, instance_id, &config);
335 let trader_key_clone = trader_key.clone();
336 let encoding = config.encoding;
337 let bulk_read_batch_size = config.bulk_read_batch_size;
338
339 let handle = get_runtime().spawn(async move {
340 if let Err(e) =
341 process_commands(rx, trader_key_clone, config.clone(), database.clone()).await
342 {
343 log::error!("Error in task '{CACHE_PROCESS}': {e}");
344 }
345 });
346
347 Ok(Self {
348 con,
349 trader_id,
350 trader_key,
351 encoding,
352 bulk_read_batch_size,
353 tx,
354 handle: Some(handle),
355 })
356 }
357
358 #[must_use]
359 pub const fn get_encoding(&self) -> SerializationEncoding {
360 self.encoding
361 }
362
363 #[must_use]
364 pub fn get_trader_key(&self) -> &str {
365 &self.trader_key
366 }
367
368 pub fn close(&mut self) {
369 log::debug!("Closing");
370
371 let Some(handle) = self.handle.take() else {
372 log::debug!("Already closed");
373 return;
374 };
375
376 if let Err(e) = self.tx.send(DatabaseCommand::close()) {
377 log::debug!("Error sending close command: {e:?}");
378 }
379
380 log_task_awaiting(CACHE_PROCESS);
381
382 let (tx, rx) = mpsc::sync_channel(1);
383
384 get_runtime().spawn(async move {
385 if let Err(e) = handle.await {
386 log::error!("Error awaiting task '{CACHE_PROCESS}': {e:?}");
387 }
388 let _ = tx.send(());
389 });
390 let _ = blocking_recv(&rx);
391
392 log::debug!("Closed");
393 }
394
395 pub async fn flushdb(&mut self) {
396 if let Err(e) = redis::cmd(REDIS_FLUSHDB)
397 .query_async::<()>(&mut self.con)
398 .await
399 {
400 log::error!("Failed to flush database: {e:?}");
401 }
402 }
403
404 pub fn flushdb_sync(&self) -> anyhow::Result<()> {
411 let (reply_tx, reply_rx) = mpsc::sync_channel(1);
412 let cmd = DatabaseCommand {
413 op_type: DatabaseOperation::Flush(reply_tx),
414 key: None,
415 payload: None,
416 };
417 self.tx
418 .send(cmd)
419 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))?;
420 blocking_recv(&reply_rx).map_err(|e| anyhow::anyhow!("Failed to flush database: {e}"))?;
421 Ok(())
422 }
423
424 pub async fn keys(&mut self, pattern: &str) -> anyhow::Result<Vec<String>> {
430 let pattern = format!("{}{REDIS_DELIMITER}{pattern}", self.trader_key);
431 DatabaseQueries::scan_keys(&mut self.con, pattern).await
432 }
433
434 pub async fn read(&mut self, key: &str) -> anyhow::Result<Vec<Bytes>> {
440 DatabaseQueries::read(&self.con, &self.trader_key, key).await
441 }
442
443 pub async fn read_bulk(&mut self, keys: &[String]) -> anyhow::Result<Vec<Option<Bytes>>> {
449 match self.bulk_read_batch_size {
450 Some(batch_size) => {
451 DatabaseQueries::read_bulk_batched(&self.con, keys, batch_size).await
452 }
453 None => DatabaseQueries::read_bulk(&self.con, keys).await,
454 }
455 }
456
457 pub fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>> {
467 let con = self.con.clone();
468 let trader_key = self.trader_key.clone();
469 let data_type = data_type.clone();
470 let (tx, rx) = mpsc::channel();
471
472 get_runtime().spawn(async move {
473 let result = DatabaseQueries::load_custom_data(&con, &trader_key, &data_type).await;
474 if let Err(e) = tx.send(result) {
475 log::error!("Failed to send custom data result for '{data_type}': {e:?}");
476 }
477 });
478
479 blocking_recv(&rx).map_err(|e| anyhow::anyhow!("load_custom_data channel closed: {e}"))?
480 }
481
482 pub fn insert(&self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
488 let op = DatabaseCommand::new(DatabaseOperation::Insert, key, payload);
489 match self.tx.send(op) {
490 Ok(()) => Ok(()),
491 Err(e) => anyhow::bail!("{FAILED_TX_CHANNEL}: {e}"),
492 }
493 }
494
495 pub fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
501 let json_bytes = serde_json::to_vec(data)
502 .map_err(|e| anyhow::anyhow!("CustomData serialization failed: {e}"))?;
503 let ts_init = data.ts_init().as_u64();
504 let key = format!(
505 "{CUSTOM}{REDIS_DELIMITER}{:020}{REDIS_DELIMITER}{}",
506 ts_init,
507 UUID4::new()
508 );
509 self.insert(key, Some(vec![Bytes::from(json_bytes)]))
510 }
511
512 pub fn update(&mut self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
518 let op = DatabaseCommand::new(DatabaseOperation::Update, key, payload);
519 match self.tx.send(op) {
520 Ok(()) => Ok(()),
521 Err(e) => anyhow::bail!("{FAILED_TX_CHANNEL}: {e}"),
522 }
523 }
524
525 pub fn delete(&mut self, key: String, payload: Option<Vec<Bytes>>) -> anyhow::Result<()> {
531 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, payload);
532 match self.tx.send(op) {
533 Ok(()) => Ok(()),
534 Err(e) => anyhow::bail!("{FAILED_TX_CHANNEL}: {e}"),
535 }
536 }
537
538 pub fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
544 let order_id_bytes = Bytes::from(client_order_id.to_string());
545
546 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
548 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
549 self.tx
550 .send(op)
551 .map_err(|e| anyhow::anyhow!("Failed to send delete order command: {e}"))?;
552
553 let index_keys = [
555 INDEX_ORDER_IDS,
556 INDEX_ORDERS,
557 INDEX_ORDERS_OPEN,
558 INDEX_ORDERS_CLOSED,
559 INDEX_ORDERS_EMULATED,
560 INDEX_ORDERS_INFLIGHT,
561 ];
562
563 for index_key in &index_keys {
564 let key = (*index_key).to_string();
565 let payload = vec![order_id_bytes.clone()];
566 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
567 self.tx
568 .send(op)
569 .map_err(|e| anyhow::anyhow!("Failed to send delete order index command: {e}"))?;
570 }
571
572 let hash_indexes = [INDEX_ORDER_POSITION, INDEX_ORDER_CLIENT];
574 for index_key in &hash_indexes {
575 let key = (*index_key).to_string();
576 let payload = vec![order_id_bytes.clone()];
577 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
578 self.tx.send(op).map_err(|e| {
579 anyhow::anyhow!("Failed to send delete order hash index command: {e}")
580 })?;
581 }
582
583 Ok(())
584 }
585
586 pub fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()> {
592 let position_id_bytes = Bytes::from(position_id.to_string());
593
594 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
596 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
597 self.tx
598 .send(op)
599 .map_err(|e| anyhow::anyhow!("Failed to send delete position command: {e}"))?;
600
601 let index_keys = [
603 INDEX_POSITIONS,
604 INDEX_POSITIONS_OPEN,
605 INDEX_POSITIONS_CLOSED,
606 ];
607
608 for index_key in &index_keys {
609 let key = (*index_key).to_string();
610 let payload = vec![position_id_bytes.clone()];
611 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
612 self.tx.send(op).map_err(|e| {
613 anyhow::anyhow!("Failed to send delete position index command: {e}")
614 })?;
615 }
616
617 Ok(())
618 }
619
620 pub fn delete_account_event(
626 &self,
627 _account_id: &AccountId,
628 _event_id: &str,
629 ) -> anyhow::Result<()> {
630 log::warn!("Deleting account events currently a no-op (pending redesign)");
631 Ok(())
632 }
633}
634
635fn blocking_recv<T>(rx: &mpsc::Receiver<T>) -> Result<T, mpsc::RecvError> {
636 let on_nautilus_runtime =
637 tokio::runtime::Handle::try_current().is_ok_and(|h| h.id() == get_runtime().handle().id());
638
639 if on_nautilus_runtime {
640 tokio::task::block_in_place(|| rx.recv())
641 } else {
642 rx.recv()
643 }
644}
645
646async fn process_commands(
647 mut rx: tokio::sync::mpsc::UnboundedReceiver<DatabaseCommand>,
648 trader_key: String,
649 config: CacheConfig,
650 database: RedisCacheConfig,
651) -> anyhow::Result<()> {
652 log_task_started(CACHE_PROCESS);
653
654 let mut con = create_redis_connection(CACHE_WRITE, &database).await?;
655
656 let mut buffer: VecDeque<DatabaseCommand> = VecDeque::new();
658 let buffer_interval = Duration::from_millis(config.buffer_interval_ms.unwrap_or(0) as u64);
659
660 let flush_timer = tokio::time::sleep(buffer_interval);
664 tokio::pin!(flush_timer);
665
666 loop {
668 tokio::select! {
669 maybe_cmd = rx.recv() => {
670 let result = handle_command(
671 maybe_cmd,
672 &mut buffer,
673 buffer_interval,
674 &mut con,
675 &trader_key,
676 config.encoding,
677 ).await;
678
679 if result.is_break() {
680 break;
681 }
682 }
683 () = &mut flush_timer, if !buffer_interval.is_zero() => {
684 flush_buffer(
685 &mut buffer,
686 &mut con,
687 &trader_key,
688 config.encoding,
689 &mut flush_timer,
690 buffer_interval,
691 ).await;
692 }
693 }
694 }
695
696 if !buffer.is_empty() {
698 drain_buffer(&mut con, &trader_key, config.encoding, &mut buffer).await;
699 }
700
701 log_task_stopped(CACHE_PROCESS);
702 Ok(())
703}
704
705async fn handle_command(
706 maybe_cmd: Option<DatabaseCommand>,
707 buffer: &mut VecDeque<DatabaseCommand>,
708 buffer_interval: Duration,
709 con: &mut ConnectionManager,
710 trader_key: &str,
711 encoding: SerializationEncoding,
712) -> ControlFlow<()> {
713 let Some(cmd) = maybe_cmd else {
714 log::debug!("Command channel closed");
715 return ControlFlow::Break(());
716 };
717
718 log::trace!("Received {cmd:?}");
719
720 match cmd.op_type {
721 DatabaseOperation::Close => {
722 if !buffer.is_empty() {
723 drain_buffer(con, trader_key, encoding, buffer).await;
724 }
725 return ControlFlow::Break(());
726 }
727 DatabaseOperation::Flush(reply_tx) => {
728 if !buffer.is_empty() {
729 drain_buffer(con, trader_key, encoding, buffer).await;
730 }
731
732 if let Err(e) = redis::cmd(REDIS_FLUSHDB).query_async::<()>(con).await {
733 log::error!("Failed to flush database: {e:?}");
734 }
735 let _ = reply_tx.send(());
736 return ControlFlow::Continue(());
737 }
738 _ => {}
739 }
740
741 buffer.push_back(cmd);
742
743 if buffer_interval.is_zero() {
744 drain_buffer(con, trader_key, encoding, buffer).await;
745 }
746
747 ControlFlow::Continue(())
748}
749
750async fn flush_buffer(
751 buffer: &mut VecDeque<DatabaseCommand>,
752 con: &mut ConnectionManager,
753 trader_key: &str,
754 encoding: SerializationEncoding,
755 flush_timer: &mut Pin<&mut tokio::time::Sleep>,
756 buffer_interval: Duration,
757) {
758 if !buffer.is_empty() {
759 drain_buffer(con, trader_key, encoding, buffer).await;
760 }
761 flush_timer
762 .as_mut()
763 .reset(tokio::time::Instant::now() + buffer_interval);
764}
765
766async fn drain_buffer(
767 conn: &mut ConnectionManager,
768 trader_key: &str,
769 encoding: SerializationEncoding,
770 buffer: &mut VecDeque<DatabaseCommand>,
771) {
772 let mut pipe = redis::pipe();
773 pipe.atomic();
774 let mut has_pending_ops = false;
775
776 for msg in buffer.drain(..) {
777 let Some(key) = msg.key else {
778 log::error!("Null key found for message: {msg:?}");
779 continue;
780 };
781 let collection = match get_collection_key(&key) {
782 Ok(collection) => collection,
783 Err(e) => {
784 log::error!("{e}");
785 continue; }
787 };
788
789 let key = format!("{trader_key}{REDIS_DELIMITER}{key}");
790
791 match msg.op_type {
792 DatabaseOperation::Insert => {
793 if let Some(payload) = msg.payload {
794 log::debug!("Processing INSERT for collection: {collection}, key: {key}");
795 if let Err(e) = insert(&mut pipe, collection, &key, &payload) {
796 log::error!("{e}");
797 } else {
798 has_pending_ops = true;
799 }
800 } else {
801 log::error!("Null `payload` for `insert`");
802 }
803 }
804 DatabaseOperation::Update => {
805 if let Some(payload) = msg.payload {
806 log::debug!("Processing UPDATE for collection: {collection}, key: {key}");
807 if let Err(e) = update(&mut pipe, collection, &key, &payload) {
808 log::error!("{e}");
809 } else {
810 has_pending_ops = true;
811 }
812 } else {
813 log::error!("Null `payload` for `update`");
814 }
815 }
816 DatabaseOperation::UpdateOrder => {
817 flush_pending_pipeline(conn, &mut pipe, &mut has_pending_ops).await;
818
819 if let Some(payload) = msg.payload {
820 log::debug!("Processing UPDATE_ORDER for key: {key}");
821 if let Err(e) =
822 update_order_event_log(conn, trader_key, encoding, &key, &payload).await
823 {
824 log::error!("{e}");
825 }
826 } else {
827 log::error!("Null `payload` for `update_order`");
828 }
829 }
830 DatabaseOperation::ReplaceList => {
831 if let Some(payload) = msg.payload {
832 log::debug!("Processing REPLACE_LIST for key: {key}");
833 if let Err(e) = replace_list_operation(&mut pipe, collection, &key, &payload) {
834 log::error!("{e}");
835 } else {
836 has_pending_ops = true;
837 }
838 } else {
839 log::error!("Null `payload` for `replace_list`");
840 }
841 }
842 DatabaseOperation::Delete => {
843 log::debug!(
844 "Processing DELETE for collection: {}, key: {}, payload: {:?}",
845 collection,
846 key,
847 msg.payload.as_ref().map(std::vec::Vec::len)
848 );
849 if let Err(e) = delete(&mut pipe, collection, &key, msg.payload) {
851 log::error!("{e}");
852 } else {
853 has_pending_ops = true;
854 }
855 }
856 DatabaseOperation::Close => panic!("Close command should not be drained"),
857 DatabaseOperation::Flush(_) => panic!("Flush command should not be drained"),
858 }
859 }
860
861 flush_pending_pipeline(conn, &mut pipe, &mut has_pending_ops).await;
862}
863
864async fn flush_pending_pipeline(
865 conn: &mut ConnectionManager,
866 pipe: &mut Pipeline,
867 has_pending_ops: &mut bool,
868) {
869 if !*has_pending_ops {
870 return;
871 }
872
873 if let Err(e) = pipe.query_async::<()>(conn).await {
874 log::error!("{e}");
875 }
876
877 *pipe = redis::pipe();
878 pipe.atomic();
879 *has_pending_ops = false;
880}
881
882async fn update_order_event_log(
883 conn: &mut ConnectionManager,
884 trader_key: &str,
885 encoding: SerializationEncoding,
886 key: &str,
887 value: &[Bytes],
888) -> anyhow::Result<()> {
889 check_slice_not_empty(value, stringify!(value))?;
890
891 let result: Vec<Bytes> = conn.lrange(key, 0, -1).await?;
892 if result.is_empty() {
893 log::warn!("Cannot update order in Redis, no existing state at {key}");
894 return Ok(());
895 }
896
897 let mut append_pipe = redis::pipe();
898 append_pipe.atomic();
899 update_list(&mut append_pipe, key, value[0].as_ref());
900 append_pipe.query_async::<()>(conn).await?;
901
902 let mut events: Vec<OrderEventAny> = result
903 .iter()
904 .map(|payload| DatabaseQueries::deserialize_payload(encoding, payload))
905 .collect::<anyhow::Result<_>>()
906 .with_context(|| {
907 format!(
908 "Order event append succeeded for {key}, but index replay failed decoding history"
909 )
910 })?;
911 let event: OrderEventAny = DatabaseQueries::deserialize_payload(encoding, value[0].as_ref())
912 .with_context(|| {
913 format!(
914 "Order event append succeeded for {key}, but index replay failed decoding appended event"
915 )
916 })?;
917 events.push(event);
918 let order = OrderAny::from_events(events).with_context(|| {
919 format!("Order event append succeeded for {key}, but index replay failed rebuilding order")
920 })?;
921
922 let mut pipe = redis::pipe();
923 pipe.atomic();
924 update_order_indexes(&mut pipe, trader_key, &order);
925 pipe.query_async::<()>(conn).await?;
926
927 Ok(())
928}
929
930fn insert(pipe: &mut Pipeline, collection: &str, key: &str, value: &[Bytes]) -> anyhow::Result<()> {
931 check_slice_not_empty(value, stringify!(value))?;
932
933 match collection {
934 INDEX => insert_index(pipe, key, value),
935 GENERAL | CURRENCIES | INSTRUMENTS | SYNTHETICS | ACTORS | STRATEGIES | HEALTH | CUSTOM => {
936 insert_string(pipe, key, value[0].as_ref());
937 Ok(())
938 }
939 ACCOUNTS | ORDERS | POSITIONS | SNAPSHOTS => {
940 insert_list(pipe, key, value[0].as_ref());
941 Ok(())
942 }
943 _ => anyhow::bail!("Unsupported operation: `insert` for collection '{collection}'"),
944 }
945}
946
947fn insert_index(pipe: &mut Pipeline, key: &str, value: &[Bytes]) -> anyhow::Result<()> {
948 let index_key = get_index_key(key)?;
949 match index_key {
950 INDEX_ORDER_IDS
951 | INDEX_ORDERS
952 | INDEX_ORDERS_OPEN
953 | INDEX_ORDERS_CLOSED
954 | INDEX_ORDERS_EMULATED
955 | INDEX_ORDERS_INFLIGHT
956 | INDEX_POSITIONS
957 | INDEX_POSITIONS_OPEN
958 | INDEX_POSITIONS_CLOSED => {
959 insert_set(pipe, key, value[0].as_ref());
960 Ok(())
961 }
962 INDEX_ORDER_POSITION | INDEX_ORDER_CLIENT => {
963 insert_hset(pipe, key, value[0].as_ref(), value[1].as_ref());
964 Ok(())
965 }
966 _ => anyhow::bail!("Index unknown '{index_key}' on insert"),
967 }
968}
969
970fn insert_string(pipe: &mut Pipeline, key: &str, value: &[u8]) {
971 pipe.set(key, value);
972}
973
974fn insert_set(pipe: &mut Pipeline, key: &str, value: &[u8]) {
975 pipe.sadd(key, value);
976}
977
978fn insert_hset(pipe: &mut Pipeline, key: &str, name: &[u8], value: &[u8]) {
979 pipe.hset(key, name, value);
980}
981
982fn insert_list(pipe: &mut Pipeline, key: &str, value: &[u8]) {
983 pipe.rpush(key, value);
984}
985
986fn replace_list(pipe: &mut Pipeline, key: &str, value: &[u8]) {
987 pipe.del(key);
988 pipe.rpush(key, value);
989}
990
991fn replace_list_operation(
992 pipe: &mut Pipeline,
993 collection: &str,
994 key: &str,
995 value: &[Bytes],
996) -> anyhow::Result<()> {
997 check_slice_not_empty(value, stringify!(value))?;
998
999 match collection {
1000 ACCOUNTS | ORDERS | POSITIONS => {
1001 replace_list(pipe, key, value[0].as_ref());
1002 Ok(())
1003 }
1004 _ => anyhow::bail!("Unsupported operation: `replace_list` for collection '{collection}'"),
1005 }
1006}
1007
1008fn update(pipe: &mut Pipeline, collection: &str, key: &str, value: &[Bytes]) -> anyhow::Result<()> {
1009 check_slice_not_empty(value, stringify!(value))?;
1010
1011 match collection {
1012 ACCOUNTS | ORDERS | POSITIONS => {
1013 update_list(pipe, key, value[0].as_ref());
1014 Ok(())
1015 }
1016 _ => anyhow::bail!("Unsupported operation: `update` for collection '{collection}'"),
1017 }
1018}
1019
1020fn update_list(pipe: &mut Pipeline, key: &str, value: &[u8]) {
1021 pipe.rpush_exists(key, value);
1022}
1023
1024fn delete(
1025 pipe: &mut Pipeline,
1026 collection: &str,
1027 key: &str,
1028 value: Option<Vec<Bytes>>,
1029) -> anyhow::Result<()> {
1030 log::debug!(
1031 "delete: collection={}, key={}, has_payload={}",
1032 collection,
1033 key,
1034 value.is_some()
1035 );
1036
1037 match collection {
1038 INDEX => delete_from_index(pipe, key, value),
1039 ORDERS | POSITIONS | ACCOUNTS | ACTORS | STRATEGIES => {
1040 delete_string(pipe, key);
1041 Ok(())
1042 }
1043 _ => anyhow::bail!("Unsupported operation: `delete` for collection '{collection}'"),
1044 }
1045}
1046
1047fn delete_from_index(
1048 pipe: &mut Pipeline,
1049 key: &str,
1050 value: Option<Vec<Bytes>>,
1051) -> anyhow::Result<()> {
1052 let value = value.ok_or_else(|| anyhow::anyhow!("Empty `payload` for `delete` '{key}'"))?;
1053 let index_key = get_index_key(key)?;
1054
1055 match index_key {
1056 INDEX_ORDER_IDS
1057 | INDEX_ORDERS
1058 | INDEX_ORDERS_OPEN
1059 | INDEX_ORDERS_CLOSED
1060 | INDEX_ORDERS_EMULATED
1061 | INDEX_ORDERS_INFLIGHT
1062 | INDEX_POSITIONS
1063 | INDEX_POSITIONS_OPEN
1064 | INDEX_POSITIONS_CLOSED => {
1065 remove_from_set(pipe, key, value[0].as_ref());
1066 Ok(())
1067 }
1068 INDEX_ORDER_POSITION | INDEX_ORDER_CLIENT => {
1069 remove_from_hash(pipe, key, value[0].as_ref());
1070 Ok(())
1071 }
1072 _ => anyhow::bail!("Unsupported index operation: remove from '{index_key}'"),
1073 }
1074}
1075
1076fn remove_from_set(pipe: &mut Pipeline, key: &str, member: &[u8]) {
1077 pipe.srem(key, member);
1078}
1079
1080fn remove_from_hash(pipe: &mut Pipeline, key: &str, field: &[u8]) {
1081 pipe.hdel(key, field);
1082}
1083
1084fn delete_string(pipe: &mut Pipeline, key: &str) {
1085 pipe.del(key);
1086}
1087
1088fn full_redis_key(trader_key: &str, key: &str) -> String {
1089 format!("{trader_key}{REDIS_DELIMITER}{key}")
1090}
1091
1092fn update_order_indexes(pipe: &mut Pipeline, trader_key: &str, order: &OrderAny) {
1093 let client_order_id = order.client_order_id();
1094 let order_id_bytes = client_order_id.to_string();
1095
1096 insert_set(
1097 pipe,
1098 &full_redis_key(trader_key, INDEX_ORDERS),
1099 order_id_bytes.as_bytes(),
1100 );
1101
1102 if order.venue_order_id().is_some() {
1103 insert_set(
1104 pipe,
1105 &full_redis_key(trader_key, INDEX_ORDER_IDS),
1106 order_id_bytes.as_bytes(),
1107 );
1108 }
1109
1110 if order.is_inflight() {
1111 insert_set(
1112 pipe,
1113 &full_redis_key(trader_key, INDEX_ORDERS_INFLIGHT),
1114 order_id_bytes.as_bytes(),
1115 );
1116 } else {
1117 remove_from_set(
1118 pipe,
1119 &full_redis_key(trader_key, INDEX_ORDERS_INFLIGHT),
1120 order_id_bytes.as_bytes(),
1121 );
1122 }
1123
1124 if order.is_open() {
1125 remove_from_set(
1126 pipe,
1127 &full_redis_key(trader_key, INDEX_ORDERS_CLOSED),
1128 order_id_bytes.as_bytes(),
1129 );
1130 insert_set(
1131 pipe,
1132 &full_redis_key(trader_key, INDEX_ORDERS_OPEN),
1133 order_id_bytes.as_bytes(),
1134 );
1135 } else if order.is_closed() {
1136 remove_from_set(
1137 pipe,
1138 &full_redis_key(trader_key, INDEX_ORDERS_OPEN),
1139 order_id_bytes.as_bytes(),
1140 );
1141 insert_set(
1142 pipe,
1143 &full_redis_key(trader_key, INDEX_ORDERS_CLOSED),
1144 order_id_bytes.as_bytes(),
1145 );
1146 }
1147
1148 if order
1149 .emulation_trigger()
1150 .is_some_and(|trigger| trigger != TriggerType::NoTrigger)
1151 && !order.is_closed()
1152 {
1153 insert_set(
1154 pipe,
1155 &full_redis_key(trader_key, INDEX_ORDERS_EMULATED),
1156 order_id_bytes.as_bytes(),
1157 );
1158 } else {
1159 remove_from_set(
1160 pipe,
1161 &full_redis_key(trader_key, INDEX_ORDERS_EMULATED),
1162 order_id_bytes.as_bytes(),
1163 );
1164 }
1165}
1166
1167fn format_timestamp(timestamp: UnixNanos) -> String {
1168 let dt = DateTime::<Utc>::from_timestamp_nanos(timestamp.as_u64().cast_signed());
1169 dt.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)
1170}
1171
1172fn get_trader_key(trader_id: TraderId, instance_id: UUID4, config: &CacheConfig) -> String {
1173 let mut key = String::new();
1174
1175 if config.use_trader_prefix {
1176 key.push_str("trader-");
1177 }
1178
1179 key.push_str(trader_id.as_str());
1180
1181 if config.use_instance_id {
1182 key.push(REDIS_DELIMITER);
1183 write!(key, "{instance_id}").expect("writing to String cannot fail");
1184 }
1185
1186 key
1187}
1188
1189fn get_collection_key(key: &str) -> anyhow::Result<&str> {
1190 key.split_once(REDIS_DELIMITER)
1191 .map(|(collection, _)| collection)
1192 .ok_or_else(|| {
1193 anyhow::anyhow!("Invalid `key`, missing a '{REDIS_DELIMITER}' delimiter, was {key}")
1194 })
1195}
1196
1197#[derive(Debug)]
1198pub struct RedisCacheDatabaseAdapter {
1199 pub database: RedisCacheDatabase,
1200}
1201
1202impl RedisCacheDatabaseAdapter {
1203 fn encoding(&self) -> SerializationEncoding {
1204 self.database.get_encoding()
1205 }
1206
1207 fn send_command(
1208 &self,
1209 op_type: DatabaseOperation,
1210 key: String,
1211 payload: Option<Vec<Bytes>>,
1212 ) -> anyhow::Result<()> {
1213 let op = DatabaseCommand::new(op_type, key, payload);
1214 self.database
1215 .tx
1216 .send(op)
1217 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1218 }
1219
1220 fn append_list(&self, key: String, payload: Bytes) -> anyhow::Result<()> {
1221 self.send_command(DatabaseOperation::Update, key, Some(vec![payload]))
1222 }
1223
1224 fn serialize_account_event(&self, account: &AccountAny) -> anyhow::Result<Bytes> {
1225 let event: AccountState = account.last_event().ok_or_else(|| {
1226 anyhow::anyhow!("Cannot persist account with no events: {}", account.id())
1227 })?;
1228 let payload = DatabaseQueries::serialize_payload(self.encoding(), &event)?;
1229 Ok(Bytes::from(payload))
1230 }
1231
1232 fn serialize_order_event(&self, order_event: &OrderEventAny) -> anyhow::Result<Bytes> {
1233 let payload = DatabaseQueries::serialize_payload(self.encoding(), order_event)?;
1234 Ok(Bytes::from(payload))
1235 }
1236
1237 fn serialize_position_event(&self, position: &Position) -> anyhow::Result<Bytes> {
1238 let event: OrderFilled = position.last_event().ok_or_else(|| {
1239 anyhow::anyhow!("Cannot persist position with no events: {}", position.id)
1240 })?;
1241 let payload = DatabaseQueries::serialize_payload(self.encoding(), &event)?;
1242 Ok(Bytes::from(payload))
1243 }
1244
1245 fn load_state(&self, key: String) -> anyhow::Result<AHashMap<String, Bytes>> {
1246 let mut con = self.database.con.clone();
1247 let trader_key = self.database.trader_key.clone();
1248 let encoding = self.encoding();
1249 let (tx, rx) = mpsc::channel();
1250
1251 get_runtime().spawn(async move {
1252 let result = async {
1253 let full_key = format!("{trader_key}{REDIS_DELIMITER}{key}");
1254 let value: Option<Bytes> = con.get(&full_key).await?;
1255 let Some(value) = value else {
1256 return Ok(AHashMap::new());
1257 };
1258
1259 DatabaseQueries::deserialize_payload(encoding, &value)
1260 }
1261 .await;
1262
1263 if let Err(e) = tx.send(result) {
1264 log::error!("Failed to send state load result for '{key}': {e:?}");
1265 }
1266 });
1267
1268 blocking_recv(&rx).map_err(|e| anyhow::anyhow!("load_state channel closed: {e}"))?
1269 }
1270
1271 fn update_state(&self, key: String, state: &AHashMap<String, Bytes>) -> anyhow::Result<()> {
1272 let payload = DatabaseQueries::serialize_payload(self.encoding(), state)?;
1273 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1274 }
1275
1276 fn replace_list(&self, key: String, payload: Bytes) -> anyhow::Result<()> {
1277 self.send_command(DatabaseOperation::ReplaceList, key, Some(vec![payload]))
1278 }
1279}
1280
1281#[async_trait::async_trait]
1282impl CacheDatabaseFactory for RedisCacheConfig {
1283 async fn create(
1284 &self,
1285 trader_id: TraderId,
1286 instance_id: UUID4,
1287 config: CacheConfig,
1288 ) -> anyhow::Result<Box<dyn CacheDatabaseAdapter>> {
1289 let database =
1290 RedisCacheDatabase::new(trader_id, instance_id, config, self.clone()).await?;
1291 Ok(Box::new(RedisCacheDatabaseAdapter { database }))
1292 }
1293}
1294
1295#[async_trait::async_trait]
1296impl CacheDatabaseAdapter for RedisCacheDatabaseAdapter {
1297 fn close(&mut self) -> anyhow::Result<()> {
1298 self.database.close();
1299 Ok(())
1300 }
1301
1302 fn flush(&mut self) -> anyhow::Result<()> {
1303 self.database.flushdb_sync()
1304 }
1305
1306 async fn load_all(&self) -> anyhow::Result<CacheMap> {
1307 log::debug!("Loading all data");
1308
1309 let (
1310 currencies,
1311 instruments,
1312 synthetics,
1313 accounts,
1314 orders,
1315 positions,
1316 greeks,
1317 yield_curves,
1318 ) = tokio::try_join!(
1319 self.load_currencies(),
1320 self.load_instruments(),
1321 self.load_synthetics(),
1322 self.load_accounts(),
1323 self.load_orders(),
1324 self.load_positions(),
1325 self.load_greeks(),
1326 self.load_yield_curves()
1327 )
1328 .map_err(|e| anyhow::anyhow!("Error loading cache data: {e}"))?;
1329
1330 Ok(CacheMap {
1331 currencies,
1332 instruments,
1333 synthetics,
1334 accounts,
1335 orders,
1336 positions,
1337 greeks,
1338 yield_curves,
1339 })
1340 }
1341
1342 fn load(&self) -> anyhow::Result<AHashMap<String, Bytes>> {
1343 let con = self.database.con.clone();
1344 let trader_key = self.database.trader_key.clone();
1345 let (tx, rx) = mpsc::channel();
1346
1347 get_runtime().spawn(async move {
1348 let result = async {
1349 let pattern = format!("{trader_key}{REDIS_DELIMITER}{GENERAL}:*");
1350 let mut con_scan = con.clone();
1351 let keys = DatabaseQueries::scan_keys(&mut con_scan, pattern).await?;
1352 if keys.is_empty() {
1353 return Ok(AHashMap::new());
1354 }
1355
1356 let values = DatabaseQueries::read_bulk(&con, &keys).await?;
1357 let prefix = format!("{trader_key}{REDIS_DELIMITER}{GENERAL}{REDIS_DELIMITER}");
1358 let mut general = AHashMap::new();
1359
1360 for (key, value) in keys.into_iter().zip(values) {
1361 let Some(value) = value else {
1362 continue;
1363 };
1364
1365 if let Some(clean_key) = key.strip_prefix(&prefix) {
1366 general.insert(clean_key.to_string(), value);
1367 }
1368 }
1369
1370 Ok(general)
1371 }
1372 .await;
1373
1374 if let Err(e) = tx.send(result) {
1375 log::error!("Failed to send general load result: {e:?}");
1376 }
1377 });
1378
1379 blocking_recv(&rx).map_err(|e| anyhow::anyhow!("load channel closed: {e}"))?
1380 }
1381
1382 async fn load_currencies(&self) -> anyhow::Result<AHashMap<Ustr, Currency>> {
1383 DatabaseQueries::load_currencies(
1384 &self.database.con,
1385 &self.database.trader_key,
1386 self.encoding(),
1387 )
1388 .await
1389 }
1390
1391 async fn load_instruments(&self) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>> {
1392 DatabaseQueries::load_instruments(
1393 &self.database.con,
1394 &self.database.trader_key,
1395 self.encoding(),
1396 )
1397 .await
1398 }
1399
1400 async fn load_synthetics(&self) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>> {
1401 DatabaseQueries::load_synthetics(
1402 &self.database.con,
1403 &self.database.trader_key,
1404 self.encoding(),
1405 )
1406 .await
1407 }
1408
1409 async fn load_accounts(&self) -> anyhow::Result<AHashMap<AccountId, AccountAny>> {
1410 DatabaseQueries::load_accounts(
1411 &self.database.con,
1412 &self.database.trader_key,
1413 self.encoding(),
1414 )
1415 .await
1416 }
1417
1418 async fn load_orders(&self) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>> {
1419 DatabaseQueries::load_orders(
1420 &self.database.con,
1421 &self.database.trader_key,
1422 self.encoding(),
1423 )
1424 .await
1425 }
1426
1427 async fn load_positions(&self) -> anyhow::Result<AHashMap<PositionId, Position>> {
1428 DatabaseQueries::load_positions(
1429 &self.database.con,
1430 &self.database.trader_key,
1431 self.encoding(),
1432 )
1433 .await
1434 }
1435
1436 fn load_index_order_position(&self) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>> {
1437 let con = self.database.con.clone();
1438 let trader_key = self.database.trader_key.clone();
1439 let (tx, rx) = mpsc::channel();
1440
1441 get_runtime().spawn(async move {
1442 let result = DatabaseQueries::load_index_order_position(&con, &trader_key).await;
1443 if let Err(e) = tx.send(result) {
1444 log::error!("Failed to send load_index_order_position result: {e:?}");
1445 }
1446 });
1447
1448 blocking_recv(&rx)
1449 .map_err(|e| anyhow::anyhow!("load_index_order_position channel closed: {e}"))?
1450 }
1451
1452 fn load_index_order_client(&self) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>> {
1453 let con = self.database.con.clone();
1454 let trader_key = self.database.trader_key.clone();
1455 let (tx, rx) = mpsc::channel();
1456
1457 get_runtime().spawn(async move {
1458 let result = DatabaseQueries::load_index_order_client(&con, &trader_key).await;
1459 if let Err(e) = tx.send(result) {
1460 log::error!("Failed to send load_index_order_client result: {e:?}");
1461 }
1462 });
1463
1464 blocking_recv(&rx)
1465 .map_err(|e| anyhow::anyhow!("load_index_order_client channel closed: {e}"))?
1466 }
1467
1468 async fn load_currency(&self, code: &Ustr) -> anyhow::Result<Option<Currency>> {
1469 DatabaseQueries::load_currency(
1470 &self.database.con,
1471 &self.database.trader_key,
1472 code,
1473 self.encoding(),
1474 )
1475 .await
1476 }
1477
1478 async fn load_instrument(
1479 &self,
1480 instrument_id: &InstrumentId,
1481 ) -> anyhow::Result<Option<InstrumentAny>> {
1482 DatabaseQueries::load_instrument(
1483 &self.database.con,
1484 &self.database.trader_key,
1485 instrument_id,
1486 self.encoding(),
1487 )
1488 .await
1489 }
1490
1491 async fn load_synthetic(
1492 &self,
1493 instrument_id: &InstrumentId,
1494 ) -> anyhow::Result<Option<SyntheticInstrument>> {
1495 DatabaseQueries::load_synthetic(
1496 &self.database.con,
1497 &self.database.trader_key,
1498 instrument_id,
1499 self.encoding(),
1500 )
1501 .await
1502 }
1503
1504 async fn load_account(&self, account_id: &AccountId) -> anyhow::Result<Option<AccountAny>> {
1505 DatabaseQueries::load_account(
1506 &self.database.con,
1507 &self.database.trader_key,
1508 account_id,
1509 self.encoding(),
1510 )
1511 .await
1512 }
1513
1514 async fn load_order(
1515 &self,
1516 client_order_id: &ClientOrderId,
1517 ) -> anyhow::Result<Option<OrderAny>> {
1518 DatabaseQueries::load_order(
1519 &self.database.con,
1520 &self.database.trader_key,
1521 client_order_id,
1522 self.encoding(),
1523 )
1524 .await
1525 }
1526
1527 async fn load_position(&self, position_id: &PositionId) -> anyhow::Result<Option<Position>> {
1528 DatabaseQueries::load_position(
1529 &self.database.con,
1530 &self.database.trader_key,
1531 position_id,
1532 self.encoding(),
1533 )
1534 .await
1535 }
1536
1537 fn load_actor(&self, component_id: &ComponentId) -> anyhow::Result<AHashMap<String, Bytes>> {
1538 let key = format!("{ACTORS}{REDIS_DELIMITER}{component_id}{REDIS_DELIMITER}state");
1539 self.load_state(key)
1540 }
1541
1542 fn load_strategy(&self, strategy_id: &StrategyId) -> anyhow::Result<AHashMap<String, Bytes>> {
1543 let key = format!("{STRATEGIES}{REDIS_DELIMITER}{strategy_id}{REDIS_DELIMITER}state");
1544 self.load_state(key)
1545 }
1546
1547 fn load_signals(&self, _name: &str) -> anyhow::Result<Vec<Signal>> {
1548 anyhow::bail!("Loading signals from Redis cache adapter not supported")
1549 }
1550
1551 fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>> {
1552 self.database.load_custom_data(data_type)
1553 }
1554
1555 fn load_order_snapshot(
1556 &self,
1557 _client_order_id: &ClientOrderId,
1558 ) -> anyhow::Result<Option<OrderSnapshot>> {
1559 anyhow::bail!("Loading order snapshots from Redis cache adapter not supported")
1560 }
1561
1562 fn load_position_snapshot(
1563 &self,
1564 _position_id: &PositionId,
1565 ) -> anyhow::Result<Option<PositionSnapshot>> {
1566 anyhow::bail!("Loading position snapshots from Redis cache adapter not supported")
1567 }
1568
1569 fn load_quotes(&self, _instrument_id: &InstrumentId) -> anyhow::Result<Vec<QuoteTick>> {
1570 anyhow::bail!("Loading quote data for Redis cache adapter not supported")
1571 }
1572
1573 fn load_trades(&self, _instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>> {
1574 anyhow::bail!("Loading market data for Redis cache adapter not supported")
1575 }
1576
1577 fn load_funding_rates(
1578 &self,
1579 _instrument_id: &InstrumentId,
1580 ) -> anyhow::Result<Vec<FundingRateUpdate>> {
1581 anyhow::bail!("Loading market data for Redis cache adapter not supported")
1582 }
1583
1584 fn load_bars(&self, _instrument_id: &InstrumentId) -> anyhow::Result<Vec<Bar>> {
1585 anyhow::bail!("Loading market data for Redis cache adapter not supported")
1586 }
1587
1588 fn add(&self, key: String, value: Bytes) -> anyhow::Result<()> {
1589 let key = format!("{GENERAL}{REDIS_DELIMITER}{key}");
1590 self.database.insert(key, Some(vec![value]))
1591 }
1592
1593 fn add_currency(&self, currency: &Currency) -> anyhow::Result<()> {
1594 let key = format!("{CURRENCIES}{REDIS_DELIMITER}{}", currency.code);
1595 let payload = DatabaseQueries::serialize_payload(self.encoding(), currency)?;
1596 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1597 }
1598
1599 fn add_instrument(&self, instrument: &InstrumentAny) -> anyhow::Result<()> {
1600 let key = format!("{INSTRUMENTS}{REDIS_DELIMITER}{}", instrument.id());
1601 let payload = DatabaseQueries::serialize_payload(self.encoding(), instrument)?;
1602 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1603 }
1604
1605 fn add_synthetic(&self, synthetic: &SyntheticInstrument) -> anyhow::Result<()> {
1606 let key = format!("{SYNTHETICS}{REDIS_DELIMITER}{}", synthetic.id);
1607 let payload = DatabaseQueries::serialize_payload(self.encoding(), synthetic)?;
1608 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1609 }
1610
1611 fn add_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1612 let account_id = account.id();
1613 let key = format!("{ACCOUNTS}{REDIS_DELIMITER}{account_id}");
1614
1615 let payload = self.serialize_account_event(account)?;
1616 self.database.insert(key, Some(vec![payload]))
1617 }
1618
1619 fn add_order(&self, order: &OrderAny, client_id: Option<ClientId>) -> anyhow::Result<()> {
1620 let client_order_id = order.client_order_id();
1621 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
1622
1623 let event = OrderEventAny::Initialized(order.init_event().clone());
1624 let payload = self.serialize_order_event(&event)?;
1625 self.replace_list(key, payload)?;
1626
1627 let order_id_bytes = Bytes::from(client_order_id.to_string());
1628 self.database
1629 .insert(INDEX_ORDERS.to_string(), Some(vec![order_id_bytes.clone()]))?;
1630
1631 if order
1632 .emulation_trigger()
1633 .is_some_and(|trigger| trigger != TriggerType::NoTrigger)
1634 {
1635 self.database.insert(
1636 INDEX_ORDERS_EMULATED.to_string(),
1637 Some(vec![order_id_bytes.clone()]),
1638 )?;
1639 }
1640
1641 if let Some(client_id) = client_id {
1642 self.database.insert(
1643 INDEX_ORDER_CLIENT.to_string(),
1644 Some(vec![order_id_bytes, Bytes::from(client_id.to_string())]),
1645 )?;
1646 }
1647
1648 Ok(())
1649 }
1650
1651 fn add_order_snapshot(&self, snapshot: &OrderSnapshot) -> anyhow::Result<()> {
1652 let key = format!(
1653 "{SNAPSHOTS}{REDIS_DELIMITER}{ORDERS}{REDIS_DELIMITER}{}",
1654 snapshot.client_order_id
1655 );
1656 let payload = DatabaseQueries::serialize_payload(self.encoding(), snapshot)?;
1657 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1658 }
1659
1660 fn add_position(&self, position: &Position) -> anyhow::Result<()> {
1661 let position_id = position.id;
1662 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
1663
1664 let payload = self.serialize_position_event(position)?;
1665 self.replace_list(key, payload)?;
1666
1667 let position_id_bytes = Bytes::from(position_id.to_string());
1668 self.database.insert(
1669 INDEX_POSITIONS.to_string(),
1670 Some(vec![position_id_bytes.clone()]),
1671 )?;
1672 self.database.insert(
1673 INDEX_POSITIONS_OPEN.to_string(),
1674 Some(vec![position_id_bytes.clone()]),
1675 )?;
1676 self.send_command(
1677 DatabaseOperation::Delete,
1678 INDEX_POSITIONS_CLOSED.to_string(),
1679 Some(vec![position_id_bytes]),
1680 )?;
1681
1682 Ok(())
1683 }
1684
1685 fn add_position_snapshot(&self, snapshot: &PositionSnapshot) -> anyhow::Result<()> {
1686 let key = format!(
1687 "{SNAPSHOTS}{REDIS_DELIMITER}{POSITIONS}{REDIS_DELIMITER}{}",
1688 snapshot.position_id
1689 );
1690 let payload = DatabaseQueries::serialize_payload(self.encoding(), snapshot)?;
1691 self.database.insert(key, Some(vec![Bytes::from(payload)]))
1692 }
1693
1694 fn add_order_book(&self, _order_book: &OrderBook) -> anyhow::Result<()> {
1695 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1696 }
1697
1698 fn add_signal(&self, _signal: &Signal) -> anyhow::Result<()> {
1699 anyhow::bail!("Saving signals for Redis cache adapter not supported")
1700 }
1701
1702 fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
1703 let json_bytes = serde_json::to_vec(data)
1704 .map_err(|e| anyhow::anyhow!("CustomData serialization failed: {e}"))?;
1705 let ts_init = data.ts_init().as_u64();
1706 let key = format!(
1707 "{CUSTOM}{REDIS_DELIMITER}{:020}{REDIS_DELIMITER}{}",
1708 ts_init,
1709 UUID4::new()
1710 );
1711 self.database
1712 .insert(key, Some(vec![Bytes::from(json_bytes)]))
1713 }
1714
1715 fn add_quote(&self, _quote: &QuoteTick) -> anyhow::Result<()> {
1716 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1717 }
1718
1719 fn add_trade(&self, _trade: &TradeTick) -> anyhow::Result<()> {
1720 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1721 }
1722
1723 fn add_funding_rate(&self, _funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
1724 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1725 }
1726
1727 fn add_bar(&self, _bar: &Bar) -> anyhow::Result<()> {
1728 anyhow::bail!("Saving market data for Redis cache adapter not supported")
1729 }
1730
1731 fn delete_actor(&self, component_id: &ComponentId) -> anyhow::Result<()> {
1732 let key = format!("{ACTORS}{REDIS_DELIMITER}{component_id}{REDIS_DELIMITER}state");
1733 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
1734 self.database
1735 .tx
1736 .send(op)
1737 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1738 }
1739
1740 fn delete_strategy(&self, component_id: &StrategyId) -> anyhow::Result<()> {
1741 let key = format!("{STRATEGIES}{REDIS_DELIMITER}{component_id}{REDIS_DELIMITER}state");
1742 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
1743 self.database
1744 .tx
1745 .send(op)
1746 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1747 }
1748
1749 fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
1750 let order_id_bytes = Bytes::from(client_order_id.to_string());
1751
1752 log::debug!("Deleting order: {client_order_id} from Redis");
1753 log::debug!("Trader key: {}", self.database.trader_key);
1754
1755 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
1757 log::debug!("Deleting order key: {key}");
1758 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
1759 self.database
1760 .tx
1761 .send(op)
1762 .map_err(|e| anyhow::anyhow!("Failed to send delete order command: {e}"))?;
1763
1764 let index_keys = [
1766 INDEX_ORDER_IDS,
1767 INDEX_ORDERS,
1768 INDEX_ORDERS_OPEN,
1769 INDEX_ORDERS_CLOSED,
1770 INDEX_ORDERS_EMULATED,
1771 INDEX_ORDERS_INFLIGHT,
1772 ];
1773
1774 for index_key in &index_keys {
1775 let key = (*index_key).to_string();
1776 log::debug!("Deleting from index: {key} (order_id: {client_order_id})");
1777 let payload = vec![order_id_bytes.clone()];
1778 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
1779 self.database
1780 .tx
1781 .send(op)
1782 .map_err(|e| anyhow::anyhow!("Failed to send delete order index command: {e}"))?;
1783 }
1784
1785 let hash_indexes = [INDEX_ORDER_POSITION, INDEX_ORDER_CLIENT];
1787 for index_key in &hash_indexes {
1788 let key = (*index_key).to_string();
1789 log::debug!("Deleting from hash index: {key} (order_id: {client_order_id})");
1790 let payload = vec![order_id_bytes.clone()];
1791 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
1792 self.database.tx.send(op).map_err(|e| {
1793 anyhow::anyhow!("Failed to send delete order hash index command: {e}")
1794 })?;
1795 }
1796
1797 log::debug!("Sent all delete commands for order: {client_order_id}");
1798 Ok(())
1799 }
1800
1801 fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()> {
1802 let position_id_bytes = Bytes::from(position_id.to_string());
1803
1804 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
1806 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, None);
1807 self.database
1808 .tx
1809 .send(op)
1810 .map_err(|e| anyhow::anyhow!("Failed to send delete position command: {e}"))?;
1811
1812 let index_keys = [
1814 INDEX_POSITIONS,
1815 INDEX_POSITIONS_OPEN,
1816 INDEX_POSITIONS_CLOSED,
1817 ];
1818
1819 for index_key in &index_keys {
1820 let key = (*index_key).to_string();
1821 let payload = vec![position_id_bytes.clone()];
1822 let op = DatabaseCommand::new(DatabaseOperation::Delete, key, Some(payload));
1823 self.database.tx.send(op).map_err(|e| {
1824 anyhow::anyhow!("Failed to send delete position index command: {e}")
1825 })?;
1826 }
1827
1828 Ok(())
1829 }
1830
1831 fn delete_account_event(&self, account_id: &AccountId, event_id: &str) -> anyhow::Result<()> {
1832 log::warn!(
1833 "Deleting account events currently a no-op (pending redesign), {account_id}: {event_id}"
1834 );
1835 Ok(())
1836 }
1837
1838 fn index_venue_order_id(
1839 &self,
1840 client_order_id: ClientOrderId,
1841 venue_order_id: VenueOrderId,
1842 ) -> anyhow::Result<()> {
1843 self.database.insert(
1844 INDEX_ORDER_IDS.to_string(),
1845 Some(vec![Bytes::from(client_order_id.to_string())]),
1846 )?;
1847 log::debug!("Indexed {client_order_id:?} -> {venue_order_id:?}");
1848 Ok(())
1849 }
1850
1851 fn index_order_position(
1852 &self,
1853 client_order_id: ClientOrderId,
1854 position_id: PositionId,
1855 ) -> anyhow::Result<()> {
1856 self.database.insert(
1857 INDEX_ORDER_POSITION.to_string(),
1858 Some(vec![
1859 Bytes::from(client_order_id.to_string()),
1860 Bytes::from(position_id.to_string()),
1861 ]),
1862 )
1863 }
1864
1865 fn update_actor(
1866 &self,
1867 component_id: &ComponentId,
1868 state: &AHashMap<String, Bytes>,
1869 ) -> anyhow::Result<()> {
1870 let key = format!("{ACTORS}{REDIS_DELIMITER}{component_id}{REDIS_DELIMITER}state");
1871 self.update_state(key, state)
1872 }
1873
1874 fn update_strategy(
1875 &self,
1876 strategy_id: &StrategyId,
1877 state: &AHashMap<String, Bytes>,
1878 ) -> anyhow::Result<()> {
1879 let key = format!("{STRATEGIES}{REDIS_DELIMITER}{strategy_id}{REDIS_DELIMITER}state");
1880 self.update_state(key, state)
1881 }
1882
1883 fn update_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1884 let account_id = account.id();
1885 let key = format!("{ACCOUNTS}{REDIS_DELIMITER}{account_id}");
1886 let payload = self.serialize_account_event(account)?;
1887 self.append_list(key, payload)
1888 }
1889
1890 fn update_order(&self, order_event: &OrderEventAny) -> anyhow::Result<()> {
1891 let client_order_id = order_event.client_order_id();
1892 let key = format!("{ORDERS}{REDIS_DELIMITER}{client_order_id}");
1893 let payload = DatabaseQueries::serialize_payload(self.encoding(), order_event)?;
1894 let op = DatabaseCommand::new(
1895 DatabaseOperation::UpdateOrder,
1896 key,
1897 Some(vec![Bytes::from(payload)]),
1898 );
1899 self.database
1900 .tx
1901 .send(op)
1902 .map_err(|e| anyhow::anyhow!("{FAILED_TX_CHANNEL}: {e}"))
1903 }
1904
1905 fn update_position(&self, position: &Position) -> anyhow::Result<()> {
1906 let position_id = position.id;
1907 let key = format!("{POSITIONS}{REDIS_DELIMITER}{position_id}");
1908 let payload = self.serialize_position_event(position)?;
1909 self.append_list(key, payload)?;
1910
1911 let position_id_bytes = Bytes::from(position_id.to_string());
1912
1913 if position.is_open() {
1914 self.database.insert(
1915 INDEX_POSITIONS_OPEN.to_string(),
1916 Some(vec![position_id_bytes.clone()]),
1917 )?;
1918 self.send_command(
1919 DatabaseOperation::Delete,
1920 INDEX_POSITIONS_CLOSED.to_string(),
1921 Some(vec![position_id_bytes]),
1922 )?;
1923 } else if position.is_closed() {
1924 self.database.insert(
1925 INDEX_POSITIONS_CLOSED.to_string(),
1926 Some(vec![position_id_bytes.clone()]),
1927 )?;
1928 self.send_command(
1929 DatabaseOperation::Delete,
1930 INDEX_POSITIONS_OPEN.to_string(),
1931 Some(vec![position_id_bytes]),
1932 )?;
1933 }
1934
1935 Ok(())
1936 }
1937
1938 fn snapshot_order_state(&self, order: &OrderAny) -> anyhow::Result<()> {
1939 let snapshot = OrderSnapshot::from(order.clone());
1940 self.add_order_snapshot(&snapshot)
1941 }
1942
1943 fn snapshot_position_state(
1944 &self,
1945 position: &Position,
1946 ts_snapshot: UnixNanos,
1947 unrealized_pnl: Option<Money>,
1948 ) -> anyhow::Result<()> {
1949 let mut snapshot = PositionSnapshot::from(position, unrealized_pnl);
1950 snapshot.ts_init = ts_snapshot;
1951 self.add_position_snapshot(&snapshot)
1952 }
1953
1954 fn heartbeat(&self, timestamp: UnixNanos) -> anyhow::Result<()> {
1955 let timestamp = format_timestamp(timestamp);
1956 self.database.insert(
1957 format!("{HEALTH}{REDIS_DELIMITER}heartbeat"),
1958 Some(vec![Bytes::from(timestamp)]),
1959 )
1960 }
1961}
1962
1963#[cfg(test)]
1964mod tests {
1965 use rstest::rstest;
1966
1967 use super::*;
1968
1969 #[rstest]
1970 fn test_get_trader_key_with_prefix_and_instance_id() {
1971 let trader_id = TraderId::from("tester-123");
1972 let instance_id = UUID4::new();
1973 let config = CacheConfig {
1974 use_instance_id: true,
1975 ..Default::default()
1976 };
1977
1978 let key = get_trader_key(trader_id, instance_id, &config);
1979 assert!(key.starts_with("trader-tester-123:"));
1980 assert!(key.ends_with(&instance_id.to_string()));
1981 }
1982
1983 #[rstest]
1984 fn test_get_collection_key_valid() {
1985 let key = "collection:123";
1986 assert_eq!(get_collection_key(key).unwrap(), "collection");
1987 }
1988
1989 #[rstest]
1990 fn test_get_collection_key_invalid() {
1991 let key = "no_delimiter";
1992 assert!(get_collection_key(key).is_err());
1993 }
1994}