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