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