1use std::{collections::VecDeque, fmt::Debug, ops::ControlFlow, pin::Pin, time::Duration};
17
18use ahash::AHashMap;
19use bytes::Bytes;
20use nautilus_common::{
21 cache::{
22 CacheConfig,
23 database::{CacheDatabaseAdapter, CacheDatabaseFactory, CacheMap},
24 },
25 live::get_runtime,
26 logging::{log_task_awaiting, log_task_started, log_task_stopped},
27 signal::Signal,
28};
29use nautilus_core::{UUID4, UnixNanos};
30use nautilus_model::{
31 accounts::AccountAny,
32 data::{Bar, CustomData, DataType, FundingRateUpdate, QuoteTick, TradeTick},
33 events::{
34 AccountState, OrderEventAny, OrderFilled, OrderInitialized, OrderSnapshot,
35 position::snapshot::PositionSnapshot,
36 },
37 identifiers::{
38 AccountId, ClientId, ClientOrderId, ComponentId, InstrumentId, PositionId, StrategyId,
39 TraderId, VenueOrderId,
40 },
41 instruments::{Instrument, InstrumentAny, SyntheticInstrument},
42 orderbook::OrderBook,
43 orders::{Order, OrderAny},
44 position::Position,
45 types::{Currency, Money},
46};
47use serde::{Deserialize, Serialize};
48use sqlx::{PgPool, postgres::PgConnectOptions};
49use tokio::{time::Instant, try_join};
50use ustr::Ustr;
51
52use crate::sql::{
53 pg::{connect_pg, get_postgres_connect_options},
54 queries::DatabaseQueries,
55};
56
57const CACHE_PROCESS: &str = "cache-process";
59
60#[derive(Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(default, deny_unknown_fields)]
65#[cfg_attr(
66 feature = "python",
67 pyo3::pyclass(
68 module = "nautilus_trader.core.nautilus_pyo3.infrastructure",
69 from_py_object
70 )
71)]
72#[cfg_attr(
73 feature = "python",
74 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.infrastructure")
75)]
76pub struct PostgresCacheConfig {
77 pub host: Option<String>,
79 pub port: Option<u16>,
81 pub username: Option<String>,
83 pub password: Option<String>,
85 pub database: Option<String>,
87}
88
89impl Debug for PostgresCacheConfig {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 let redacted = self.password.as_ref().map(|_| "***");
92 f.debug_struct(stringify!(PostgresCacheConfig))
93 .field("host", &self.host)
94 .field("port", &self.port)
95 .field("username", &self.username)
96 .field("password", &redacted)
97 .field("database", &self.database)
98 .finish()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use rstest::rstest;
105 use serde_json::json;
106
107 use super::*;
108
109 #[rstest]
110 fn test_default_postgres_cache_config() {
111 let config = PostgresCacheConfig::default();
112
113 assert_eq!(config.host, None);
114 assert_eq!(config.port, None);
115 assert_eq!(config.username, None);
116 assert_eq!(config.password, None);
117 assert_eq!(config.database, None);
118 }
119
120 #[rstest]
121 fn test_deserialize_postgres_cache_config() {
122 let config_json = json!({
123 "host": "localhost",
124 "port": 5432,
125 "username": "user",
126 "password": "pass",
127 "database": "nautilus"
128 });
129
130 let config: PostgresCacheConfig = serde_json::from_value(config_json).unwrap();
131
132 assert_eq!(config.host, Some("localhost".to_string()));
133 assert_eq!(config.port, Some(5432));
134 assert_eq!(config.username, Some("user".to_string()));
135 assert_eq!(config.password, Some("pass".to_string()));
136 assert_eq!(config.database, Some("nautilus".to_string()));
137 }
138
139 #[rstest]
140 fn test_deserialize_postgres_cache_config_rejects_type_selector() {
141 let config_json = json!({
142 "type": "postgres",
143 });
144
145 let error = serde_json::from_value::<PostgresCacheConfig>(config_json).unwrap_err();
146
147 assert!(error.to_string().contains("unknown field `type`"));
148 }
149}
150
151#[async_trait::async_trait]
152impl CacheDatabaseFactory for PostgresCacheConfig {
153 async fn create(
154 &self,
155 _trader_id: TraderId,
156 _instance_id: UUID4,
157 _config: CacheConfig,
158 ) -> anyhow::Result<Box<dyn CacheDatabaseAdapter>> {
159 let database = PostgresCacheDatabase::connect(
160 self.host.clone(),
161 self.port,
162 self.username.clone(),
163 self.password.clone(),
164 self.database.clone(),
165 )
166 .await?;
167 Ok(Box::new(database))
168 }
169}
170
171#[derive(Debug)]
172#[cfg_attr(
173 feature = "python",
174 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.infrastructure")
175)]
176pub struct PostgresCacheDatabase {
177 pub pool: PgPool,
178 tx: tokio::sync::mpsc::UnboundedSender<DatabaseQuery>,
179 handle: tokio::task::JoinHandle<()>,
180}
181
182#[allow(
183 clippy::large_enum_variant,
184 reason = "variant sizes vary with feature unification; allow stays silent when the lint does not fire"
185)]
186#[derive(Debug, Clone)]
187pub enum DatabaseQuery {
188 Close,
189 Add(String, Vec<u8>),
190 AddCurrency(Currency),
191 AddInstrument(InstrumentAny),
192 AddOrder(OrderInitialized, Option<ClientId>),
193 AddOrderSnapshot(OrderSnapshot),
194 AddPosition(PositionId, OrderFilled),
195 AddPositionSnapshot(PositionSnapshot),
196 AddAccount(AccountState, bool),
197 AddSignal(Signal),
198 AddCustom(CustomData),
199 AddQuote(QuoteTick),
200 AddTrade(TradeTick),
201 AddBar(Bar),
202 UpdateOrder(OrderEventAny),
203 UpdatePosition(OrderFilled),
204 IndexOrderPosition(ClientOrderId, PositionId),
205}
206
207impl PostgresCacheDatabase {
208 pub async fn connect(
218 host: Option<String>,
219 port: Option<u16>,
220 username: Option<String>,
221 password: Option<String>,
222 database: Option<String>,
223 ) -> Result<Self, sqlx::Error> {
224 let pg_connect_options =
225 get_postgres_connect_options(host, port, username, password, database);
226 let pool = connect_pg(pg_connect_options.clone().into()).await.unwrap();
227 let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<DatabaseQuery>();
228
229 let handle = get_runtime().spawn(async move {
230 Box::pin(Self::process_commands(
231 rx,
232 pg_connect_options.clone().into(),
233 ))
234 .await;
235 });
236 Ok(Self { pool, tx, handle })
237 }
238
239 async fn process_commands(
240 mut rx: tokio::sync::mpsc::UnboundedReceiver<DatabaseQuery>,
241 pg_connect_options: PgConnectOptions,
242 ) {
243 log_task_started(CACHE_PROCESS);
244
245 let pool = connect_pg(pg_connect_options).await.unwrap();
246
247 let mut buffer: VecDeque<DatabaseQuery> = VecDeque::new();
249
250 let buffer_interval = Duration::from_millis(0);
252
253 let flush_timer = tokio::time::sleep(buffer_interval);
257 tokio::pin!(flush_timer);
258
259 loop {
261 tokio::select! {
262 maybe_msg = rx.recv() => {
263 let result = Box::pin(handle_query(
264 maybe_msg,
265 &mut buffer,
266 buffer_interval,
267 &pool,
268 ))
269 .await;
270
271 if result.is_break() {
272 break;
273 }
274 }
275 () = &mut flush_timer, if !buffer_interval.is_zero() => {
276 flush_buffer(&mut buffer, &pool, &mut flush_timer, buffer_interval).await;
277 }
278 }
279 }
280
281 if !buffer.is_empty() {
282 drain_buffer(&pool, &mut buffer).await;
283 }
284
285 log_task_stopped(CACHE_PROCESS);
286 }
287}
288
289async fn handle_query(
290 maybe_msg: Option<DatabaseQuery>,
291 buffer: &mut VecDeque<DatabaseQuery>,
292 buffer_interval: Duration,
293 pool: &PgPool,
294) -> ControlFlow<()> {
295 let Some(msg) = maybe_msg else {
296 log::debug!("Command channel closed");
297 return ControlFlow::Break(());
298 };
299
300 if matches!(msg, DatabaseQuery::Close) {
301 if !buffer.is_empty() {
302 drain_buffer(pool, buffer).await;
303 }
304 return ControlFlow::Break(());
305 }
306
307 buffer.push_back(msg);
308
309 if buffer_interval.is_zero() {
310 drain_buffer(pool, buffer).await;
311 }
312
313 ControlFlow::Continue(())
314}
315
316async fn flush_buffer(
317 buffer: &mut VecDeque<DatabaseQuery>,
318 pool: &PgPool,
319 flush_timer: &mut Pin<&mut tokio::time::Sleep>,
320 buffer_interval: Duration,
321) {
322 if !buffer.is_empty() {
323 drain_buffer(pool, buffer).await;
324 }
325 flush_timer.as_mut().reset(Instant::now() + buffer_interval);
326}
327
328pub async fn get_pg_cache_database() -> anyhow::Result<PostgresCacheDatabase> {
334 let connect_options = get_postgres_connect_options(None, None, None, None, None);
335 Ok(PostgresCacheDatabase::connect(
336 Some(connect_options.host),
337 Some(connect_options.port),
338 Some(connect_options.username),
339 Some(connect_options.password),
340 Some(connect_options.database),
341 )
342 .await?)
343}
344
345#[async_trait::async_trait]
346impl CacheDatabaseAdapter for PostgresCacheDatabase {
347 fn close(&mut self) -> anyhow::Result<()> {
348 let pool = self.pool.clone();
349 let (tx, rx) = std::sync::mpsc::channel();
350
351 log::debug!("Closing connection pool");
352
353 tokio::task::block_in_place(|| {
354 get_runtime().block_on(async {
355 pool.close().await;
356
357 if let Err(e) = tx.send(()) {
358 log::error!("Error closing pool: {e:?}");
359 }
360 });
361 });
362
363 if let Err(e) = self.tx.send(DatabaseQuery::Close) {
365 log::warn!("Error sending close: {e:?}");
366 }
367
368 log_task_awaiting("cache-write");
369
370 tokio::task::block_in_place(|| {
371 if let Err(e) = get_runtime().block_on(&mut self.handle) {
372 log::error!("Error awaiting task 'cache-write': {e:?}");
373 }
374 });
375
376 log::debug!("Closed");
377
378 Ok(rx.recv()?)
379 }
380
381 fn flush(&mut self) -> anyhow::Result<()> {
382 let pool = self.pool.clone();
383 let (tx, rx) = std::sync::mpsc::channel();
384
385 tokio::task::block_in_place(|| {
386 get_runtime().block_on(async {
387 if let Err(e) = DatabaseQueries::truncate(&pool).await {
388 log::error!("Error flushing pool: {e:?}");
389 }
390
391 if let Err(e) = tx.send(()) {
392 log::error!("Error sending flush result: {e:?}");
393 }
394 });
395 });
396
397 Ok(rx.recv()?)
398 }
399
400 async fn load_all(&self) -> anyhow::Result<CacheMap> {
401 let (currencies, instruments, synthetics, accounts, orders, positions) = try_join!(
402 self.load_currencies(),
403 self.load_instruments(),
404 self.load_synthetics(),
405 self.load_accounts(),
406 self.load_orders(),
407 self.load_positions()
408 )
409 .map_err(|e| anyhow::anyhow!("Error loading cache data: {e}"))?;
410
411 let greeks = AHashMap::new();
414 let yield_curves = AHashMap::new();
415
416 Ok(CacheMap {
417 currencies,
418 instruments,
419 synthetics,
420 accounts,
421 orders,
422 positions,
423 greeks,
424 yield_curves,
425 })
426 }
427
428 fn load(&self) -> anyhow::Result<AHashMap<String, Bytes>> {
429 let pool = self.pool.clone();
430 let (tx, rx) = std::sync::mpsc::channel();
431
432 tokio::spawn(async move {
433 let result = DatabaseQueries::load(&pool).await;
434 match result {
435 Ok(items) => {
436 let mapping = items
437 .into_iter()
438 .map(|(k, v)| (k, Bytes::from(v)))
439 .collect();
440
441 if let Err(e) = tx.send(mapping) {
442 log::error!("Failed to send general items: {e:?}");
443 }
444 }
445 Err(e) => {
446 log::error!("Failed to load general items: {e:?}");
447 if let Err(e) = tx.send(AHashMap::new()) {
448 log::error!("Failed to send empty general items: {e:?}");
449 }
450 }
451 }
452 });
453 Ok(rx.recv()?)
454 }
455
456 async fn load_currencies(&self) -> anyhow::Result<AHashMap<Ustr, Currency>> {
457 let pool = self.pool.clone();
458 let (tx, rx) = std::sync::mpsc::channel();
459
460 tokio::spawn(async move {
461 let result = DatabaseQueries::load_currencies(&pool).await;
462 match result {
463 Ok(currencies) => {
464 let mapping = currencies
465 .into_iter()
466 .map(|currency| (currency.code, currency))
467 .collect();
468
469 if let Err(e) = tx.send(mapping) {
470 log::error!("Failed to send currencies: {e:?}");
471 }
472 }
473 Err(e) => {
474 log::error!("Failed to load currencies: {e:?}");
475 if let Err(e) = tx.send(AHashMap::new()) {
476 log::error!("Failed to send empty currencies: {e:?}");
477 }
478 }
479 }
480 });
481 Ok(rx.recv()?)
482 }
483
484 async fn load_instruments(&self) -> anyhow::Result<AHashMap<InstrumentId, InstrumentAny>> {
485 let pool = self.pool.clone();
486 let (tx, rx) = std::sync::mpsc::channel();
487
488 tokio::spawn(async move {
489 let result = DatabaseQueries::load_instruments(&pool).await;
490 match result {
491 Ok(instruments) => {
492 let mapping = instruments
493 .into_iter()
494 .map(|instrument| (instrument.id(), instrument))
495 .collect();
496
497 if let Err(e) = tx.send(mapping) {
498 log::error!("Failed to send instruments: {e:?}");
499 }
500 }
501 Err(e) => {
502 log::error!("Failed to load instruments: {e:?}");
503 if let Err(e) = tx.send(AHashMap::new()) {
504 log::error!("Failed to send empty instruments: {e:?}");
505 }
506 }
507 }
508 });
509 Ok(rx.recv()?)
510 }
511
512 async fn load_synthetics(&self) -> anyhow::Result<AHashMap<InstrumentId, SyntheticInstrument>> {
513 todo!()
514 }
515
516 async fn load_accounts(&self) -> anyhow::Result<AHashMap<AccountId, AccountAny>> {
517 let pool = self.pool.clone();
518 let (tx, rx) = std::sync::mpsc::channel();
519
520 tokio::spawn(async move {
521 let result = DatabaseQueries::load_accounts(&pool).await;
522 match result {
523 Ok(accounts) => {
524 let mapping = accounts
525 .into_iter()
526 .map(|account| (account.id(), account))
527 .collect();
528
529 if let Err(e) = tx.send(mapping) {
530 log::error!("Failed to send accounts: {e:?}");
531 }
532 }
533 Err(e) => {
534 log::error!("Failed to load accounts: {e:?}");
535 if let Err(e) = tx.send(AHashMap::new()) {
536 log::error!("Failed to send empty accounts: {e:?}");
537 }
538 }
539 }
540 });
541 Ok(rx.recv()?)
542 }
543
544 async fn load_orders(&self) -> anyhow::Result<AHashMap<ClientOrderId, OrderAny>> {
545 let pool = self.pool.clone();
546 let (tx, rx) = std::sync::mpsc::channel();
547
548 tokio::spawn(async move {
549 let result = DatabaseQueries::load_orders(&pool).await;
550 match result {
551 Ok(orders) => {
552 let mapping = orders
553 .into_iter()
554 .map(|order| (order.client_order_id(), order))
555 .collect();
556
557 if let Err(e) = tx.send(mapping) {
558 log::error!("Failed to send orders: {e:?}");
559 }
560 }
561 Err(e) => {
562 log::error!("Failed to load orders: {e:?}");
563 if let Err(e) = tx.send(AHashMap::new()) {
564 log::error!("Failed to send empty orders: {e:?}");
565 }
566 }
567 }
568 });
569 Ok(rx.recv()?)
570 }
571
572 async fn load_positions(&self) -> anyhow::Result<AHashMap<PositionId, Position>> {
573 let pool = self.pool.clone();
574 let (tx, rx) = std::sync::mpsc::channel();
575
576 tokio::spawn(async move {
577 let result = DatabaseQueries::load_positions(&pool)
578 .await
579 .map(|positions| {
580 positions
581 .into_iter()
582 .map(|position| (position.id, position))
583 .collect()
584 });
585
586 if let Err(e) = tx.send(result) {
587 log::error!("Failed to send positions: {e:?}");
588 }
589 });
590 rx.recv()?
591 }
592
593 fn load_index_order_position(&self) -> anyhow::Result<AHashMap<ClientOrderId, PositionId>> {
594 let pool = self.pool.clone();
595 let (tx, rx) = std::sync::mpsc::channel();
596
597 tokio::spawn(async move {
598 let result = DatabaseQueries::load_index_order_position(&pool).await;
599 match result {
600 Ok(index) => {
601 if let Err(e) = tx.send(index) {
602 log::error!("Failed to send load_index_order_position result: {e:?}");
603 }
604 }
605 Err(e) => {
606 log::error!("Failed to run query load_index_order_position: {e:?}");
607 if let Err(e) = tx.send(AHashMap::new()) {
608 log::error!("Failed to send empty load_index_order_position result: {e:?}");
609 }
610 }
611 }
612 });
613 Ok(rx.recv()?)
614 }
615
616 fn load_index_order_client(&self) -> anyhow::Result<AHashMap<ClientOrderId, ClientId>> {
617 let pool = self.pool.clone();
618 let (tx, rx) = std::sync::mpsc::channel();
619
620 tokio::spawn(async move {
621 let result = DatabaseQueries::load_distinct_order_event_client_ids(&pool).await;
622 match result {
623 Ok(currency) => {
624 if let Err(e) = tx.send(currency) {
625 log::error!("Failed to send load_index_order_client result: {e:?}");
626 }
627 }
628 Err(e) => {
629 log::error!("Failed to run query load_distinct_order_event_client_ids: {e:?}");
630 if let Err(e) = tx.send(AHashMap::new()) {
631 log::error!("Failed to send empty load_index_order_client result: {e:?}");
632 }
633 }
634 }
635 });
636 Ok(rx.recv()?)
637 }
638
639 async fn load_currency(&self, code: &Ustr) -> anyhow::Result<Option<Currency>> {
640 let pool = self.pool.clone();
641 let code = code.to_owned(); let (tx, rx) = std::sync::mpsc::channel();
643
644 tokio::spawn(async move {
645 let result = DatabaseQueries::load_currency(&pool, &code).await;
646 match result {
647 Ok(currency) => {
648 if let Err(e) = tx.send(currency) {
649 log::error!("Failed to send currency {code}: {e:?}");
650 }
651 }
652 Err(e) => {
653 log::error!("Failed to load currency {code}: {e:?}");
654 if let Err(e) = tx.send(None) {
655 log::error!("Failed to send None for currency {code}: {e:?}");
656 }
657 }
658 }
659 });
660 Ok(rx.recv()?)
661 }
662
663 async fn load_instrument(
664 &self,
665 instrument_id: &InstrumentId,
666 ) -> anyhow::Result<Option<InstrumentAny>> {
667 let pool = self.pool.clone();
668 let instrument_id = instrument_id.to_owned(); let (tx, rx) = std::sync::mpsc::channel();
670
671 tokio::spawn(async move {
672 let result = DatabaseQueries::load_instrument(&pool, &instrument_id).await;
673 match result {
674 Ok(instrument) => {
675 if let Err(e) = tx.send(instrument) {
676 log::error!("Failed to send instrument {instrument_id}: {e:?}");
677 }
678 }
679 Err(e) => {
680 log::error!("Failed to load instrument {instrument_id}: {e:?}");
681 if let Err(e) = tx.send(None) {
682 log::error!("Failed to send None for instrument {instrument_id}: {e:?}");
683 }
684 }
685 }
686 });
687 Ok(rx.recv()?)
688 }
689
690 async fn load_synthetic(
691 &self,
692 _instrument_id: &InstrumentId,
693 ) -> anyhow::Result<Option<SyntheticInstrument>> {
694 todo!()
695 }
696
697 async fn load_account(&self, account_id: &AccountId) -> anyhow::Result<Option<AccountAny>> {
698 let pool = self.pool.clone();
699 let account_id = account_id.to_owned();
700 let (tx, rx) = std::sync::mpsc::channel();
701
702 tokio::spawn(async move {
703 let result = DatabaseQueries::load_account(&pool, &account_id).await;
704 match result {
705 Ok(account) => {
706 if let Err(e) = tx.send(account) {
707 log::error!("Failed to send account {account_id}: {e:?}");
708 }
709 }
710 Err(e) => {
711 log::error!("Failed to load account {account_id}: {e:?}");
712 if let Err(e) = tx.send(None) {
713 log::error!("Failed to send None for account {account_id}: {e:?}");
714 }
715 }
716 }
717 });
718 Ok(rx.recv()?)
719 }
720
721 async fn load_order(
722 &self,
723 client_order_id: &ClientOrderId,
724 ) -> anyhow::Result<Option<OrderAny>> {
725 let pool = self.pool.clone();
726 let client_order_id = client_order_id.to_owned();
727 let (tx, rx) = std::sync::mpsc::channel();
728
729 tokio::spawn(async move {
730 let result = DatabaseQueries::load_order(&pool, &client_order_id).await;
731 match result {
732 Ok(order) => {
733 if let Err(e) = tx.send(order) {
734 log::error!("Failed to send order {client_order_id}: {e:?}");
735 }
736 }
737 Err(e) => {
738 log::error!("Failed to load order {client_order_id}: {e:?}");
739 let _ = tx.send(None);
740 }
741 }
742 });
743 Ok(rx.recv()?)
744 }
745
746 async fn load_position(&self, position_id: &PositionId) -> anyhow::Result<Option<Position>> {
747 let pool = self.pool.clone();
748 let position_id = position_id.to_owned();
749 let (tx, rx) = std::sync::mpsc::channel();
750
751 tokio::spawn(async move {
752 let result = DatabaseQueries::load_position(&pool, &position_id).await;
753 if let Err(e) = tx.send(result) {
754 log::error!("Failed to send position {position_id}: {e:?}");
755 }
756 });
757 rx.recv()?
758 }
759
760 fn load_actor(&self, _component_id: &ComponentId) -> anyhow::Result<AHashMap<String, Bytes>> {
761 todo!()
762 }
763
764 fn delete_actor(&self, _component_id: &ComponentId) -> anyhow::Result<()> {
765 todo!()
766 }
767
768 fn load_strategy(&self, _strategy_id: &StrategyId) -> anyhow::Result<AHashMap<String, Bytes>> {
769 todo!()
770 }
771
772 fn delete_strategy(&self, _strategy_id: &StrategyId) -> anyhow::Result<()> {
773 todo!()
774 }
775
776 fn delete_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<()> {
777 anyhow::bail!(
778 "delete_order not implemented for PostgreSQL cache adapter: {client_order_id}"
779 )
780 }
781
782 fn delete_position(&self, position_id: &PositionId) -> anyhow::Result<()> {
783 anyhow::bail!("delete_position not implemented for PostgreSQL cache adapter: {position_id}")
784 }
785
786 fn delete_account_event(&self, account_id: &AccountId, event_id: &str) -> anyhow::Result<()> {
787 anyhow::bail!(
788 "delete_account_event not implemented for PostgreSQL cache adapter: {account_id}, {event_id}"
789 )
790 }
791
792 fn add(&self, key: String, value: Bytes) -> anyhow::Result<()> {
793 let query = DatabaseQuery::Add(key, value.into());
794 self.tx
795 .send(query)
796 .map_err(|e| anyhow::anyhow!("Failed to send query to database message handler: {e}"))
797 }
798
799 fn add_currency(&self, currency: &Currency) -> anyhow::Result<()> {
800 let query = DatabaseQuery::AddCurrency(*currency);
801 self.tx.send(query).map_err(|e| {
802 anyhow::anyhow!("Failed to query add_currency to database message handler: {e}")
803 })
804 }
805
806 fn add_instrument(&self, instrument: &InstrumentAny) -> anyhow::Result<()> {
807 let query = DatabaseQuery::AddInstrument(instrument.clone());
808 self.tx.send(query).map_err(|e| {
809 anyhow::anyhow!("Failed to send query add_instrument to database message handler: {e}")
810 })
811 }
812
813 fn add_synthetic(&self, _synthetic: &SyntheticInstrument) -> anyhow::Result<()> {
814 todo!()
815 }
816
817 fn add_account(&self, account: &AccountAny) -> anyhow::Result<()> {
818 let query = DatabaseQuery::AddAccount(account_last_event(account)?, false);
819 self.tx.send(query).map_err(|e| {
820 anyhow::anyhow!("Failed to send query add_account to database message handler: {e}")
821 })
822 }
823
824 fn add_order(&self, order: &OrderAny, client_id: Option<ClientId>) -> anyhow::Result<()> {
825 let query = DatabaseQuery::AddOrder(order_initialized_event(order), client_id);
826 self.tx.send(query).map_err(|e| {
827 anyhow::anyhow!("Failed to send query add_order to database message handler: {e}")
828 })
829 }
830
831 fn add_order_snapshot(&self, snapshot: &OrderSnapshot) -> anyhow::Result<()> {
832 let query = DatabaseQuery::AddOrderSnapshot(snapshot.to_owned());
833 self.tx.send(query).map_err(|e| {
834 anyhow::anyhow!(
835 "Failed to send query add_order_snapshot to database message handler: {e}"
836 )
837 })
838 }
839
840 fn add_position(&self, position: &Position) -> anyhow::Result<()> {
841 let event = position_last_event(position)?;
842 let query = DatabaseQuery::AddPosition(position.id, event);
843 self.tx.send(query).map_err(|e| {
844 anyhow::anyhow!("Failed to send query add_position to database message handler: {e}")
845 })
846 }
847
848 fn add_position_snapshot(&self, snapshot: &PositionSnapshot) -> anyhow::Result<()> {
849 let query = DatabaseQuery::AddPositionSnapshot(snapshot.to_owned());
850 self.tx.send(query).map_err(|e| {
851 anyhow::anyhow!(
852 "Failed to send query add_position_snapshot to database message handler: {e}"
853 )
854 })
855 }
856
857 fn add_order_book(&self, _order_book: &OrderBook) -> anyhow::Result<()> {
858 todo!()
859 }
860
861 fn add_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
862 let query = DatabaseQuery::AddQuote(quote.to_owned());
863 self.tx.send(query).map_err(|e| {
864 anyhow::anyhow!("Failed to send query add_quote to database message handler: {e}")
865 })
866 }
867
868 fn load_quotes(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<QuoteTick>> {
869 let pool = self.pool.clone();
870 let instrument_id = instrument_id.to_owned();
871 let (tx, rx) = std::sync::mpsc::channel();
872
873 tokio::spawn(async move {
874 let result = DatabaseQueries::load_quotes(&pool, &instrument_id).await;
875 match result {
876 Ok(quotes) => {
877 if let Err(e) = tx.send(quotes) {
878 log::error!("Failed to send quotes for instrument {instrument_id}: {e:?}");
879 }
880 }
881 Err(e) => {
882 log::error!("Failed to load quotes for instrument {instrument_id}: {e:?}");
883 if let Err(e) = tx.send(Vec::new()) {
884 log::error!(
885 "Failed to send empty quotes for instrument {instrument_id}: {e:?}"
886 );
887 }
888 }
889 }
890 });
891 Ok(rx.recv()?)
892 }
893
894 fn add_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
895 let query = DatabaseQuery::AddTrade(trade.to_owned());
896 self.tx.send(query).map_err(|e| {
897 anyhow::anyhow!("Failed to send query add_trade to database message handler: {e}")
898 })
899 }
900
901 fn load_trades(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<TradeTick>> {
902 let pool = self.pool.clone();
903 let instrument_id = instrument_id.to_owned();
904 let (tx, rx) = std::sync::mpsc::channel();
905
906 tokio::spawn(async move {
907 let result = DatabaseQueries::load_trades(&pool, &instrument_id).await;
908 match result {
909 Ok(trades) => {
910 if let Err(e) = tx.send(trades) {
911 log::error!("Failed to send trades for instrument {instrument_id}: {e:?}");
912 }
913 }
914 Err(e) => {
915 log::error!("Failed to load trades for instrument {instrument_id}: {e:?}");
916 if let Err(e) = tx.send(Vec::new()) {
917 log::error!(
918 "Failed to send empty trades for instrument {instrument_id}: {e:?}"
919 );
920 }
921 }
922 }
923 });
924 Ok(rx.recv()?)
925 }
926
927 fn add_funding_rate(&self, _funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
928 anyhow::bail!("add_funding_rate not implemented for PostgreSQL cache adapter")
929 }
930
931 fn load_funding_rates(
932 &self,
933 _instrument_id: &InstrumentId,
934 ) -> anyhow::Result<Vec<FundingRateUpdate>> {
935 anyhow::bail!("load_funding_rates not implemented for PostgreSQL cache adapter")
936 }
937
938 fn add_bar(&self, bar: &Bar) -> anyhow::Result<()> {
939 let query = DatabaseQuery::AddBar(bar.to_owned());
940 self.tx.send(query).map_err(|e| {
941 anyhow::anyhow!("Failed to send query add_bar to database message handler: {e}")
942 })
943 }
944
945 fn load_bars(&self, instrument_id: &InstrumentId) -> anyhow::Result<Vec<Bar>> {
946 let pool = self.pool.clone();
947 let instrument_id = instrument_id.to_owned();
948 let (tx, rx) = std::sync::mpsc::channel();
949
950 tokio::spawn(async move {
951 let result = DatabaseQueries::load_bars(&pool, &instrument_id).await;
952 match result {
953 Ok(bars) => {
954 if let Err(e) = tx.send(bars) {
955 log::error!("Failed to send bars for instrument {instrument_id}: {e:?}");
956 }
957 }
958 Err(e) => {
959 log::error!("Failed to load bars for instrument {instrument_id}: {e:?}");
960 if let Err(e) = tx.send(Vec::new()) {
961 log::error!(
962 "Failed to send empty bars for instrument {instrument_id}: {e:?}"
963 );
964 }
965 }
966 }
967 });
968 Ok(rx.recv()?)
969 }
970
971 fn add_signal(&self, signal: &Signal) -> anyhow::Result<()> {
972 let query = DatabaseQuery::AddSignal(signal.to_owned());
973 self.tx.send(query).map_err(|e| {
974 anyhow::anyhow!("Failed to send query add_signal to database message handler: {e}")
975 })
976 }
977
978 fn load_signals(&self, name: &str) -> anyhow::Result<Vec<Signal>> {
979 let pool = self.pool.clone();
980 let name = name.to_owned();
981 let (tx, rx) = std::sync::mpsc::channel();
982
983 tokio::spawn(async move {
984 let result = DatabaseQueries::load_signals(&pool, &name).await;
985 match result {
986 Ok(signals) => {
987 if let Err(e) = tx.send(signals) {
988 log::error!("Failed to send signals for '{name}': {e:?}");
989 }
990 }
991 Err(e) => {
992 log::error!("Failed to load signals for '{name}': {e:?}");
993 if let Err(e) = tx.send(Vec::new()) {
994 log::error!("Failed to send empty signals for '{name}': {e:?}");
995 }
996 }
997 }
998 });
999 Ok(rx.recv()?)
1000 }
1001
1002 fn add_custom_data(&self, data: &CustomData) -> anyhow::Result<()> {
1003 let query = DatabaseQuery::AddCustom(data.to_owned());
1004 self.tx.send(query).map_err(|e| {
1005 anyhow::anyhow!("Failed to send query add_signal to database message handler: {e}")
1006 })
1007 }
1008
1009 fn load_custom_data(&self, data_type: &DataType) -> anyhow::Result<Vec<CustomData>> {
1010 let pool = self.pool.clone();
1011 let data_type = data_type.to_owned();
1012 let (tx, rx) = std::sync::mpsc::channel();
1013
1014 tokio::spawn(async move {
1015 let result = DatabaseQueries::load_custom_data(&pool, &data_type).await;
1016 match result {
1017 Ok(signals) => {
1018 if let Err(e) = tx.send(signals) {
1019 log::error!("Failed to send custom data for '{data_type}': {e:?}");
1020 }
1021 }
1022 Err(e) => {
1023 log::error!("Failed to load custom data for '{data_type}': {e:?}");
1024 if let Err(e) = tx.send(Vec::new()) {
1025 log::error!("Failed to send empty custom data for '{data_type}': {e:?}");
1026 }
1027 }
1028 }
1029 });
1030 Ok(rx.recv()?)
1031 }
1032
1033 fn load_order_snapshot(
1034 &self,
1035 client_order_id: &ClientOrderId,
1036 ) -> anyhow::Result<Option<OrderSnapshot>> {
1037 let pool = self.pool.clone();
1038 let client_order_id = client_order_id.to_owned();
1039 let (tx, rx) = std::sync::mpsc::channel();
1040
1041 tokio::spawn(async move {
1042 let result = DatabaseQueries::load_order_snapshot(&pool, &client_order_id).await;
1043 match result {
1044 Ok(snapshot) => {
1045 if let Err(e) = tx.send(snapshot) {
1046 log::error!("Failed to send order snapshot {client_order_id}: {e:?}");
1047 }
1048 }
1049 Err(e) => {
1050 log::error!("Failed to load order snapshot {client_order_id}: {e:?}");
1051 if let Err(e) = tx.send(None) {
1052 log::error!(
1053 "Failed to send None for order snapshot {client_order_id}: {e:?}"
1054 );
1055 }
1056 }
1057 }
1058 });
1059 Ok(rx.recv()?)
1060 }
1061
1062 fn load_position_snapshot(
1063 &self,
1064 position_id: &PositionId,
1065 ) -> anyhow::Result<Option<PositionSnapshot>> {
1066 let pool = self.pool.clone();
1067 let position_id = position_id.to_owned();
1068 let (tx, rx) = std::sync::mpsc::channel();
1069
1070 tokio::spawn(async move {
1071 let result = DatabaseQueries::load_position_snapshot(&pool, &position_id).await;
1072 match result {
1073 Ok(snapshot) => {
1074 if let Err(e) = tx.send(snapshot) {
1075 log::error!("Failed to send position snapshot {position_id}: {e:?}");
1076 }
1077 }
1078 Err(e) => {
1079 log::error!("Failed to load position snapshot {position_id}: {e:?}");
1080 if let Err(e) = tx.send(None) {
1081 log::error!(
1082 "Failed to send None for position snapshot {position_id}: {e:?}"
1083 );
1084 }
1085 }
1086 }
1087 });
1088 Ok(rx.recv()?)
1089 }
1090
1091 fn index_venue_order_id(
1092 &self,
1093 _client_order_id: ClientOrderId,
1094 _venue_order_id: VenueOrderId,
1095 ) -> anyhow::Result<()> {
1096 todo!()
1097 }
1098
1099 fn index_order_position(
1100 &self,
1101 client_order_id: ClientOrderId,
1102 position_id: PositionId,
1103 ) -> anyhow::Result<()> {
1104 let query = DatabaseQuery::IndexOrderPosition(client_order_id, position_id);
1105 self.tx.send(query).map_err(|e| {
1106 anyhow::anyhow!(
1107 "Failed to send query index_order_position to database message handler: {e}"
1108 )
1109 })
1110 }
1111
1112 fn update_actor(
1113 &self,
1114 _component_id: &ComponentId,
1115 _state: &AHashMap<String, Bytes>,
1116 ) -> anyhow::Result<()> {
1117 todo!()
1118 }
1119
1120 fn update_strategy(
1121 &self,
1122 _strategy_id: &StrategyId,
1123 _state: &AHashMap<String, Bytes>,
1124 ) -> anyhow::Result<()> {
1125 todo!()
1126 }
1127
1128 fn update_account(&self, account: &AccountAny) -> anyhow::Result<()> {
1129 let query = DatabaseQuery::AddAccount(account_last_event(account)?, true);
1130 self.tx.send(query).map_err(|e| {
1131 anyhow::anyhow!("Failed to send query add_account to database message handler: {e}")
1132 })
1133 }
1134
1135 fn update_order(&self, event: &OrderEventAny) -> anyhow::Result<()> {
1136 let query = DatabaseQuery::UpdateOrder(event.clone());
1137 self.tx.send(query).map_err(|e| {
1138 anyhow::anyhow!("Failed to send query update_order to database message handler: {e}")
1139 })
1140 }
1141
1142 fn update_position(&self, position: &Position) -> anyhow::Result<()> {
1143 let query = DatabaseQuery::UpdatePosition(position_last_event(position)?);
1144 self.tx.send(query).map_err(|e| {
1145 anyhow::anyhow!("Failed to send query update_position to database message handler: {e}")
1146 })
1147 }
1148
1149 fn snapshot_order_state(&self, _order: &OrderAny) -> anyhow::Result<()> {
1150 todo!()
1151 }
1152
1153 fn snapshot_position_state(
1154 &self,
1155 _position: &Position,
1156 _ts_snapshot: UnixNanos,
1157 _unrealized_pnl: Option<Money>,
1158 ) -> anyhow::Result<()> {
1159 todo!()
1160 }
1161
1162 fn heartbeat(&self, _timestamp: UnixNanos) -> anyhow::Result<()> {
1163 todo!()
1164 }
1165}
1166
1167fn account_last_event(account: &AccountAny) -> anyhow::Result<AccountState> {
1168 account
1169 .last_event()
1170 .ok_or_else(|| anyhow::anyhow!("Cannot persist account with no events: {}", account.id()))
1171}
1172
1173fn order_initialized_event(order: &OrderAny) -> OrderInitialized {
1174 order.init_event().clone()
1175}
1176
1177fn position_last_event(position: &Position) -> anyhow::Result<OrderFilled> {
1178 position
1179 .last_event()
1180 .ok_or_else(|| anyhow::anyhow!("Cannot persist position with no events: {}", position.id))
1181}
1182
1183#[expect(
1184 clippy::too_many_lines,
1185 reason = "database command dispatch enumerates each cache query variant explicitly"
1186)]
1187async fn drain_buffer(pool: &PgPool, buffer: &mut VecDeque<DatabaseQuery>) {
1188 for cmd in buffer.drain(..) {
1189 let result: anyhow::Result<()> = match cmd {
1190 DatabaseQuery::Close => Ok(()),
1191 DatabaseQuery::Add(key, value) => DatabaseQueries::add(pool, key, value).await,
1192 DatabaseQuery::AddCurrency(currency) => {
1193 DatabaseQueries::add_currency(pool, currency).await
1194 }
1195 DatabaseQuery::AddInstrument(instrument_any) => match instrument_any {
1196 InstrumentAny::Betting(instrument) => {
1197 DatabaseQueries::add_instrument(pool, "BETTING", Box::new(instrument)).await
1198 }
1199 InstrumentAny::BinaryOption(instrument) => {
1200 DatabaseQueries::add_instrument(pool, "BINARY_OPTION", Box::new(instrument))
1201 .await
1202 }
1203 InstrumentAny::CryptoFuture(instrument) => {
1204 DatabaseQueries::add_instrument(pool, "CRYPTO_FUTURE", Box::new(instrument))
1205 .await
1206 }
1207 InstrumentAny::CryptoFuturesSpread(instrument) => {
1208 DatabaseQueries::add_instrument(
1209 pool,
1210 "CRYPTO_FUTURES_SPREAD",
1211 Box::new(instrument),
1212 )
1213 .await
1214 }
1215 InstrumentAny::CryptoOption(instrument) => {
1216 DatabaseQueries::add_instrument(pool, "CRYPTO_OPTION", Box::new(instrument))
1217 .await
1218 }
1219 InstrumentAny::CryptoOptionSpread(instrument) => {
1220 DatabaseQueries::add_instrument(
1221 pool,
1222 "CRYPTO_OPTION_SPREAD",
1223 Box::new(instrument),
1224 )
1225 .await
1226 }
1227 InstrumentAny::CryptoPerpetual(instrument) => {
1228 DatabaseQueries::add_instrument(pool, "CRYPTO_PERPETUAL", Box::new(instrument))
1229 .await
1230 }
1231 InstrumentAny::CurrencyPair(instrument) => {
1232 DatabaseQueries::add_instrument(pool, "CURRENCY_PAIR", Box::new(instrument))
1233 .await
1234 }
1235 InstrumentAny::Equity(equity) => {
1236 DatabaseQueries::add_instrument(pool, "EQUITY", Box::new(equity)).await
1237 }
1238 InstrumentAny::FuturesContract(instrument) => {
1239 DatabaseQueries::add_instrument(pool, "FUTURES_CONTRACT", Box::new(instrument))
1240 .await
1241 }
1242 InstrumentAny::FuturesSpread(instrument) => {
1243 DatabaseQueries::add_instrument(pool, "FUTURES_SPREAD", Box::new(instrument))
1244 .await
1245 }
1246 InstrumentAny::OptionContract(instrument) => {
1247 DatabaseQueries::add_instrument(pool, "OPTION_CONTRACT", Box::new(instrument))
1248 .await
1249 }
1250 InstrumentAny::Commodity(instrument) => {
1251 DatabaseQueries::add_instrument(pool, "COMMODITY", Box::new(instrument)).await
1252 }
1253 InstrumentAny::IndexInstrument(instrument) => {
1254 DatabaseQueries::add_instrument(pool, "INDEX_INSTRUMENT", Box::new(instrument))
1255 .await
1256 }
1257 InstrumentAny::Cfd(instrument) => {
1258 DatabaseQueries::add_instrument(pool, "CFD", Box::new(instrument)).await
1259 }
1260 InstrumentAny::OptionSpread(instrument) => {
1261 DatabaseQueries::add_instrument(pool, "OPTION_SPREAD", Box::new(instrument))
1262 .await
1263 }
1264 InstrumentAny::PerpetualContract(instrument) => {
1265 DatabaseQueries::add_instrument(
1266 pool,
1267 "PERPETUAL_CONTRACT",
1268 Box::new(instrument),
1269 )
1270 .await
1271 }
1272 InstrumentAny::TokenizedAsset(instrument) => {
1273 DatabaseQueries::add_instrument(pool, "TOKENIZED_ASSET", Box::new(instrument))
1274 .await
1275 }
1276 },
1277 DatabaseQuery::AddOrder(event, client_id) => {
1278 DatabaseQueries::add_order(pool, event, client_id).await
1279 }
1280 DatabaseQuery::AddOrderSnapshot(snapshot) => {
1281 DatabaseQueries::add_order_snapshot(pool, snapshot).await
1282 }
1283 DatabaseQuery::AddPosition(position_id, event) => {
1284 DatabaseQueries::add_position(pool, position_id, &event).await
1285 }
1286 DatabaseQuery::AddPositionSnapshot(snapshot) => {
1287 DatabaseQueries::add_position_snapshot(pool, snapshot).await
1288 }
1289 DatabaseQuery::AddAccount(event, updated) => {
1290 DatabaseQueries::add_account(pool, updated, event).await
1291 }
1292 DatabaseQuery::AddSignal(signal) => DatabaseQueries::add_signal(pool, &signal).await,
1293 DatabaseQuery::AddCustom(data) => DatabaseQueries::add_custom_data(pool, &data).await,
1294 DatabaseQuery::AddQuote(quote) => DatabaseQueries::add_quote(pool, "e).await,
1295 DatabaseQuery::AddTrade(trade) => DatabaseQueries::add_trade(pool, &trade).await,
1296 DatabaseQuery::AddBar(bar) => DatabaseQueries::add_bar(pool, &bar).await,
1297 DatabaseQuery::UpdateOrder(event) => {
1298 DatabaseQueries::add_order_event(pool, event.into_boxed(), None).await
1299 }
1300 DatabaseQuery::UpdatePosition(event) => {
1301 DatabaseQueries::update_position(pool, &event).await
1302 }
1303 DatabaseQuery::IndexOrderPosition(client_order_id, position_id) => {
1304 DatabaseQueries::index_order_position(pool, client_order_id, position_id).await
1305 }
1306 };
1307
1308 if let Err(e) = result {
1309 log::error!("Error on query: {e:?}");
1310 }
1311 }
1312}