1use std::{
17 any::Any,
18 cell::{Ref, RefCell, RefMut},
19 collections::HashMap,
20 fmt::Debug,
21 num::NonZeroUsize,
22 rc::Rc,
23 sync::Arc,
24};
25
26use ahash::{AHashMap, AHashSet};
27use indexmap::IndexMap;
28use jiff::Timestamp;
29use nautilus_core::{Params, UUID4, UnixNanos, correctness::check_predicate_true};
30#[cfg(feature = "defi")]
31use nautilus_model::defi::{
32 Block, Blockchain, Pool, PoolLiquidityUpdate, PoolSwap, data::PoolFeeCollect, data::PoolFlash,
33};
34use nautilus_model::{
35 data::{
36 Bar, BarType, CustomData, DataType, FundingRateUpdate, IndexPriceUpdate, InstrumentStatus,
37 MarkPriceUpdate, OrderBookDelta, OrderBookDeltas, OrderBookDepth, QuoteTick, TradeTick,
38 close::InstrumentClose,
39 option_chain::{OptionChainSlice, OptionGreeks, StrikeRange},
40 },
41 enums::BookType,
42 identifiers::{ActorId, ClientId, ComponentId, InstrumentId, OptionSeriesId, TraderId, Venue},
43 instruments::{InstrumentAny, SyntheticInstrument},
44 orderbook::OrderBook,
45};
46use serde::{Deserialize, Serialize};
47use ustr::Ustr;
48
49use super::{
50 Actor,
51 binding::DataActorBinding,
52 indicators::{Indicators, SharedActorIndicator},
53 registry::try_get_actor_unchecked,
54};
55#[cfg(feature = "defi")]
56use crate::defi;
57#[cfg(feature = "defi")]
58#[allow(unused_imports)]
59use crate::defi::data_actor as _; #[cfg(feature = "python")]
61use crate::python::msgbus::PyMessageBusScope;
62use crate::{
63 actor::requests::{RequestLifecycle, ResponseScope},
64 cache::{Cache, CacheApi},
65 clock::{Clock, ClockApi},
66 component::{Component, ComponentAccessError},
67 enums::{ComponentState, ComponentTrigger},
68 logging::{CMD, RECV, REQ, SEND},
69 messages::{
70 data::{
71 BarsResponse, BookDeltasResponse, BookDepthResponse, BookResponse, CustomDataResponse,
72 DataCommand, DataResponse, DataResponseEnvelope, FundingRatesResponse,
73 InstrumentResponse, InstrumentsResponse, IntoBarTypes, IntoInstrumentIds,
74 QuotesResponse, RequestBars, RequestBookDeltas, RequestBookDepth, RequestBookSnapshot,
75 RequestCommand, RequestCustomData, RequestFundingRates, RequestInstrument,
76 RequestInstruments, RequestQuotes, RequestTrades, SubscribeBars, SubscribeBookDeltas,
77 SubscribeBookDepth, SubscribeBookSnapshots, SubscribeCommand, SubscribeCustomData,
78 SubscribeFundingRates, SubscribeIndexPrices, SubscribeInstrument,
79 SubscribeInstrumentClose, SubscribeInstrumentStatus, SubscribeInstruments,
80 SubscribeMarkPrices, SubscribeOptionChain, SubscribeOptionGreeks, SubscribeQuotes,
81 SubscribeTrades, TradesResponse, UnsubscribeBars, UnsubscribeBookDeltas,
82 UnsubscribeBookDepth, UnsubscribeBookSnapshots, UnsubscribeCommand,
83 UnsubscribeCustomData, UnsubscribeFundingRates, UnsubscribeIndexPrices,
84 UnsubscribeInstrument, UnsubscribeInstrumentClose, UnsubscribeInstrumentStatus,
85 UnsubscribeInstruments, UnsubscribeMarkPrices, UnsubscribeOptionChain,
86 UnsubscribeOptionGreeks, UnsubscribeQuotes, UnsubscribeTrades,
87 ensure_routable_bar_types, ensure_routable_instrument_ids, ensure_subscription_routes,
88 is_parent_subscription, quote_routing,
89 },
90 system::{QueueStateChanged, ShutdownSystem, SocketStateChanged},
91 },
92 msgbus::{
93 self, MStr, Pattern, ShareableMessageHandler, Topic, TypedHandler, get_message_bus,
94 switchboard::{
95 MessagingSwitchboard, get_bars_topic, get_book_deltas_pattern, get_book_deltas_topic,
96 get_book_depth_pattern, get_book_depth_topic, get_book_snapshots_topic,
97 get_custom_subscription_topics, get_custom_topic, get_funding_rate_topic,
98 get_index_price_topic, get_instrument_close_topic, get_instrument_status_topic,
99 get_instrument_topic, get_instruments_pattern, get_mark_price_topic,
100 get_option_chain_topic, get_option_greeks_topic, get_quotes_topic, get_signal_pattern,
101 get_trades_topic,
102 },
103 },
104 runner::SystemChannel,
105 signal::Signal,
106 timer::{TimeEvent, TimeEventCallback},
107};
108#[cfg(feature = "live")]
109use crate::{
110 live::try_get_system_command_sender,
111 messages::{
112 SystemCommand,
113 system::{ReconnectSocket, socket_endpoint},
114 },
115};
116
117#[derive(Debug, Clone, Deserialize, Serialize)]
119#[serde(default, deny_unknown_fields)]
120#[cfg_attr(
121 feature = "python",
122 pyo3::pyclass(module = "nautilus_trader.common", subclass, from_py_object)
123)]
124#[cfg_attr(
125 feature = "python",
126 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")
127)]
128pub struct DataActorConfig {
129 pub actor_id: Option<ActorId>,
131 pub log_events: bool,
133 pub log_commands: bool,
135}
136
137impl Default for DataActorConfig {
138 fn default() -> Self {
139 Self {
140 actor_id: None,
141 log_events: true,
142 log_commands: true,
143 }
144 }
145}
146
147#[derive(Debug, Clone, Deserialize, Serialize)]
149#[serde(deny_unknown_fields)]
150#[cfg_attr(
151 feature = "python",
152 pyo3::pyclass(module = "nautilus_trader.common", from_py_object)
153)]
154#[cfg_attr(
155 feature = "python",
156 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")
157)]
158pub struct ImportableActorConfig {
159 pub actor_path: String,
161 pub config_path: String,
163 pub config: HashMap<String, serde_json::Value>,
165}
166
167pub trait DataActorNative {
179 fn core(&self) -> &DataActorCore;
181
182 fn core_mut(&mut self) -> &mut DataActorCore;
184
185 fn clock_mut(&mut self) -> RefMut<'_, dyn Clock> {
191 self.try_clock_mut().unwrap_or_else(|e| panic!("{e}"))
192 }
193
194 fn try_clock_mut(&mut self) -> Result<RefMut<'_, dyn Clock>, ComponentAccessError> {
200 self.core_mut()
201 .clock
202 .as_ref()
203 .ok_or(ComponentAccessError::NotRegistered {
204 resource: "clock",
205 operation: "clock_mut",
206 })?
207 .try_borrow_mut()
208 .map_err(|_| ComponentAccessError::WriteConflict {
209 resource: "clock",
210 operation: "clock_mut",
211 })
212 }
213
214 fn clock_rc(&self) -> Rc<RefCell<dyn Clock>> {
220 self.core()
221 .clock
222 .as_ref()
223 .expect("DataActor must be registered before accessing clock")
224 .clone()
225 }
226
227 fn cache_ref(&self) -> Ref<'_, Cache> {
233 self.try_cache_ref().unwrap_or_else(|e| panic!("{e}"))
234 }
235
236 fn try_cache_ref(&self) -> Result<Ref<'_, Cache>, ComponentAccessError> {
242 self.core()
243 .cache
244 .as_ref()
245 .ok_or(ComponentAccessError::NotRegistered {
246 resource: "cache",
247 operation: "cache_ref",
248 })?
249 .try_borrow()
250 .map_err(|_| ComponentAccessError::ReadConflict {
251 resource: "cache",
252 operation: "cache_ref",
253 })
254 }
255
256 fn cache_rc(&self) -> Rc<RefCell<Cache>> {
262 self.core()
263 .cache
264 .as_ref()
265 .expect("DataActor must be registered before accessing cache")
266 .clone()
267 }
268}
269
270pub trait DataActor {
277 fn actor_id(&self) -> ActorId
283 where
284 Self: DataActorBinding,
285 {
286 self.binding_actor_id()
287 }
288
289 fn trader_id(&self) -> Option<TraderId>
291 where
292 Self: DataActorNative,
293 {
294 self.core().trader_id()
295 }
296
297 fn is_registered(&self) -> bool
299 where
300 Self: DataActorNative,
301 {
302 self.core().is_registered()
303 }
304
305 fn config(&self) -> &DataActorConfig
307 where
308 Self: DataActorNative,
309 {
310 &self.core().config
311 }
312
313 fn on_save(&self) -> anyhow::Result<IndexMap<String, Vec<u8>>> {
319 Ok(IndexMap::new())
320 }
321
322 #[allow(unused_variables)]
328 fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {
329 Ok(())
330 }
331
332 fn on_start(&mut self) -> anyhow::Result<()> {
338 log::warn!(
339 "The `on_start` handler was called when not overridden, \
340 it's expected that any actions required when starting the actor \
341 occur here, such as subscribing/requesting data"
342 );
343 Ok(())
344 }
345
346 fn on_stop(&mut self) -> anyhow::Result<()> {
352 log::warn!(
353 "The `on_stop` handler was called when not overridden, \
354 it's expected that any actions required when stopping the actor \
355 occur here, such as unsubscribing from data",
356 );
357 Ok(())
358 }
359
360 fn on_resume(&mut self) -> anyhow::Result<()> {
366 log::warn!(
367 "The `on_resume` handler was called when not overridden, \
368 it's expected that any actions required when resuming the actor \
369 following a stop occur here"
370 );
371 Ok(())
372 }
373
374 fn on_reset(&mut self) -> anyhow::Result<()> {
380 log::warn!(
381 "The `on_reset` handler was called when not overridden, \
382 it's expected that any actions required when resetting the actor \
383 occur here, such as resetting indicators and other state"
384 );
385 Ok(())
386 }
387
388 fn on_dispose(&mut self) -> anyhow::Result<()> {
394 Ok(())
395 }
396
397 fn on_degrade(&mut self) -> anyhow::Result<()> {
403 Ok(())
404 }
405
406 fn on_fault(&mut self) -> anyhow::Result<()> {
412 Ok(())
413 }
414
415 #[allow(unused_variables)]
421 fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
422 Ok(())
423 }
424
425 #[allow(unused_variables)]
431 fn on_data(&mut self, data: &CustomData) -> anyhow::Result<()> {
432 Ok(())
433 }
434
435 #[allow(unused_variables)]
441 fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
442 Ok(())
443 }
444
445 #[allow(unused_variables)]
451 fn on_queue_state(&mut self, event: &QueueStateChanged) -> anyhow::Result<()> {
452 Ok(())
453 }
454
455 #[allow(unused_variables)]
461 fn on_socket_state(&mut self, event: &SocketStateChanged) -> anyhow::Result<()> {
462 Ok(())
463 }
464
465 #[allow(unused_variables)]
471 fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
472 Ok(())
473 }
474
475 #[allow(unused_variables)]
481 fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
482 Ok(())
483 }
484
485 #[allow(unused_variables)]
491 fn on_book_depth(&mut self, depth: &OrderBookDepth) -> anyhow::Result<()> {
492 Ok(())
493 }
494
495 #[allow(unused_variables)]
501 fn on_book(&mut self, order_book: &OrderBook) -> anyhow::Result<()> {
502 Ok(())
503 }
504
505 #[allow(unused_variables)]
511 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
512 Ok(())
513 }
514
515 #[allow(unused_variables)]
521 fn on_trade(&mut self, tick: &TradeTick) -> anyhow::Result<()> {
522 Ok(())
523 }
524
525 #[allow(unused_variables)]
531 fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
532 Ok(())
533 }
534
535 #[allow(unused_variables)]
541 fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
542 Ok(())
543 }
544
545 #[allow(unused_variables)]
551 fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
552 Ok(())
553 }
554
555 #[allow(unused_variables)]
561 fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
562 Ok(())
563 }
564
565 #[allow(unused_variables)]
571 fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
572 Ok(())
573 }
574
575 #[allow(unused_variables)]
581 fn on_option_chain(&mut self, slice: &OptionChainSlice) -> anyhow::Result<()> {
582 Ok(())
583 }
584
585 #[allow(unused_variables)]
591 fn on_instrument_status(&mut self, data: &InstrumentStatus) -> anyhow::Result<()> {
592 Ok(())
593 }
594
595 #[allow(unused_variables)]
601 fn on_instrument_close(&mut self, update: &InstrumentClose) -> anyhow::Result<()> {
602 Ok(())
603 }
604
605 #[cfg(feature = "defi")]
606 #[allow(unused_variables)]
612 fn on_block(&mut self, block: &Block) -> anyhow::Result<()> {
613 Ok(())
614 }
615
616 #[cfg(feature = "defi")]
617 #[allow(unused_variables)]
623 fn on_pool(&mut self, pool: &Pool) -> anyhow::Result<()> {
624 Ok(())
625 }
626
627 #[cfg(feature = "defi")]
628 #[allow(unused_variables)]
634 fn on_pool_swap(&mut self, swap: &PoolSwap) -> anyhow::Result<()> {
635 Ok(())
636 }
637
638 #[cfg(feature = "defi")]
639 #[allow(unused_variables)]
645 fn on_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate) -> anyhow::Result<()> {
646 Ok(())
647 }
648
649 #[cfg(feature = "defi")]
650 #[allow(unused_variables)]
656 fn on_pool_fee_collect(&mut self, collect: &PoolFeeCollect) -> anyhow::Result<()> {
657 Ok(())
658 }
659
660 #[cfg(feature = "defi")]
661 #[allow(unused_variables)]
667 fn on_pool_flash(&mut self, flash: &PoolFlash) -> anyhow::Result<()> {
668 Ok(())
669 }
670
671 #[allow(unused_variables)]
680 fn on_historical_data(&mut self, data: &dyn Any) -> anyhow::Result<()> {
681 Ok(())
682 }
683
684 #[allow(unused_variables)]
690 fn on_historical_book_deltas(&mut self, deltas: &[OrderBookDelta]) -> anyhow::Result<()> {
691 Ok(())
692 }
693
694 #[allow(unused_variables)]
700 fn on_historical_book_depth(&mut self, depths: &[OrderBookDepth]) -> anyhow::Result<()> {
701 Ok(())
702 }
703
704 #[allow(unused_variables)]
710 fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
711 Ok(())
712 }
713
714 #[allow(unused_variables)]
720 fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
721 Ok(())
722 }
723
724 #[allow(unused_variables)]
730 fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
731 Ok(())
732 }
733
734 #[allow(unused_variables)]
740 fn on_historical_mark_prices(&mut self, mark_prices: &[MarkPriceUpdate]) -> anyhow::Result<()> {
741 Ok(())
742 }
743
744 #[allow(unused_variables)]
750 fn on_historical_index_prices(
751 &mut self,
752 index_prices: &[IndexPriceUpdate],
753 ) -> anyhow::Result<()> {
754 Ok(())
755 }
756
757 #[allow(unused_variables)]
763 fn on_historical_funding_rates(
764 &mut self,
765 funding_rates: &[FundingRateUpdate],
766 ) -> anyhow::Result<()> {
767 Ok(())
768 }
769
770 fn clock(&self) -> ClockApi<'_>
777 where
778 Self: DataActorBinding,
779 {
780 self.binding_clock()
781 }
782
783 fn cache(&self) -> CacheApi<'_>
785 where
786 Self: DataActorNative,
787 {
788 self.core().cache_api()
789 }
790
791 fn shutdown_system(&self, reason: Option<String>)
797 where
798 Self: DataActorNative,
799 {
800 self.core().shutdown_system(reason);
801 }
802
803 fn publish_data(&self, data_type: &DataType, data: &CustomData)
812 where
813 Self: DataActorNative,
814 {
815 self.core().publish_data(data_type, data);
816 }
817
818 fn publish_signal(&self, name: &str, value: String, ts_event: UnixNanos)
824 where
825 Self: DataActorNative,
826 {
827 self.core().publish_signal(name, value, ts_event);
828 }
829
830 fn add_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()>
842 where
843 Self: DataActorNative,
844 {
845 self.core().add_synthetic(synthetic)
846 }
847
848 fn update_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()>
860 where
861 Self: DataActorNative,
862 {
863 self.core().update_synthetic(synthetic)
864 }
865
866 fn handle_time_event(&mut self, event: &TimeEvent)
868 where
869 Self: Component,
870 {
871 log_received(&event);
872
873 if self.not_running() {
874 log_not_running(&event);
875 return;
876 }
877
878 if let Err(e) = DataActor::on_time_event(self, event) {
879 log_error(&e);
880 }
881 }
882
883 fn handle_data(&mut self, data: &CustomData)
885 where
886 Self: Component,
887 {
888 log_received(&data);
889
890 if self.not_running() {
891 log_not_running(&data);
892 return;
893 }
894
895 if let Err(e) = self.on_data(data) {
896 log_error(&e);
897 }
898 }
899
900 fn handle_signal(&mut self, signal: &Signal)
902 where
903 Self: Component,
904 {
905 log_received(&signal);
906
907 if self.not_running() {
908 log_not_running(&signal);
909 return;
910 }
911
912 if let Err(e) = self.on_signal(signal) {
913 log_error(&e);
914 }
915 }
916
917 fn handle_queue_state(&mut self, event: &QueueStateChanged)
919 where
920 Self: Component,
921 {
922 log_received(&event);
923
924 if self.not_running() {
925 log_not_running(&event);
926 return;
927 }
928
929 if let Err(e) = self.on_queue_state(event) {
930 log_error(&e);
931 }
932 }
933
934 fn handle_socket_state(&mut self, event: &SocketStateChanged)
936 where
937 Self: Component,
938 {
939 log_received(&event);
940
941 if self.not_running() {
942 log_not_running(&event);
943 return;
944 }
945
946 if let Err(e) = self.on_socket_state(event) {
947 log_error(&e);
948 }
949 }
950
951 fn handle_instrument(&mut self, instrument: &InstrumentAny)
953 where
954 Self: Component,
955 {
956 log_received(&instrument);
957
958 if self.not_running() {
959 log_not_running(&instrument);
960 return;
961 }
962
963 if let Err(e) = self.on_instrument(instrument) {
964 log_error(&e);
965 }
966 }
967
968 fn handle_book_deltas(&mut self, deltas: &OrderBookDeltas)
970 where
971 Self: Component,
972 {
973 log_received(&deltas);
974
975 if self.not_running() {
976 log_not_running(&deltas);
977 return;
978 }
979
980 if let Err(e) = self.on_book_deltas(deltas) {
981 log_error(&e);
982 }
983 }
984
985 fn handle_book_depth(&mut self, depth: &OrderBookDepth)
987 where
988 Self: Component,
989 {
990 log_received(&depth);
991
992 if self.not_running() {
993 log_not_running(&depth);
994 return;
995 }
996
997 if let Err(e) = self.on_book_depth(depth) {
998 log_error(&e);
999 }
1000 }
1001
1002 fn handle_book(&mut self, book: &OrderBook)
1004 where
1005 Self: Component,
1006 {
1007 log_received(&book);
1008
1009 if self.not_running() {
1010 log_not_running(&book);
1011 return;
1012 }
1013
1014 if let Err(e) = self.on_book(book) {
1015 log_error(&e);
1016 }
1017 }
1018
1019 fn handle_quote(&mut self, quote: &QuoteTick)
1021 where
1022 Self: DataActorNative + Component,
1023 {
1024 log_received("e);
1025
1026 if let Err(e) = self.core().handle_indicators_for_quote(quote) {
1027 log_error(&e);
1028 return;
1029 }
1030
1031 if self.not_running() {
1032 log_not_running("e);
1033 return;
1034 }
1035
1036 if let Err(e) = self.on_quote(quote) {
1037 log_error(&e);
1038 }
1039 }
1040
1041 fn handle_trade(&mut self, trade: &TradeTick)
1043 where
1044 Self: DataActorNative + Component,
1045 {
1046 log_received(&trade);
1047
1048 if let Err(e) = self.core().handle_indicators_for_trade(trade) {
1049 log_error(&e);
1050 return;
1051 }
1052
1053 if self.not_running() {
1054 log_not_running(&trade);
1055 return;
1056 }
1057
1058 if let Err(e) = self.on_trade(trade) {
1059 log_error(&e);
1060 }
1061 }
1062
1063 fn handle_bar(&mut self, bar: &Bar)
1065 where
1066 Self: DataActorNative + Component,
1067 {
1068 log_received(&bar);
1069
1070 if let Err(e) = self.core().handle_indicators_for_bar(bar) {
1071 log_error(&e);
1072 return;
1073 }
1074
1075 if self.not_running() {
1076 log_not_running(&bar);
1077 return;
1078 }
1079
1080 if let Err(e) = self.on_bar(bar) {
1081 log_error(&e);
1082 }
1083 }
1084
1085 fn handle_mark_price(&mut self, mark_price: &MarkPriceUpdate)
1087 where
1088 Self: Component,
1089 {
1090 log_received(&mark_price);
1091
1092 if self.not_running() {
1093 log_not_running(&mark_price);
1094 return;
1095 }
1096
1097 if let Err(e) = self.on_mark_price(mark_price) {
1098 log_error(&e);
1099 }
1100 }
1101
1102 fn handle_index_price(&mut self, index_price: &IndexPriceUpdate)
1104 where
1105 Self: Component,
1106 {
1107 log_received(&index_price);
1108
1109 if self.not_running() {
1110 log_not_running(&index_price);
1111 return;
1112 }
1113
1114 if let Err(e) = self.on_index_price(index_price) {
1115 log_error(&e);
1116 }
1117 }
1118
1119 fn handle_funding_rate(&mut self, funding_rate: &FundingRateUpdate)
1121 where
1122 Self: Component,
1123 {
1124 log_received(&funding_rate);
1125
1126 if self.not_running() {
1127 log_not_running(&funding_rate);
1128 return;
1129 }
1130
1131 if let Err(e) = self.on_funding_rate(funding_rate) {
1132 log_error(&e);
1133 }
1134 }
1135
1136 fn handle_option_greeks(&mut self, greeks: &OptionGreeks)
1138 where
1139 Self: Component,
1140 {
1141 log_received(&greeks);
1142
1143 if self.not_running() {
1144 log_not_running(&greeks);
1145 return;
1146 }
1147
1148 if let Err(e) = self.on_option_greeks(greeks) {
1149 log_error(&e);
1150 }
1151 }
1152
1153 fn handle_option_chain(&mut self, slice: &OptionChainSlice)
1155 where
1156 Self: Component,
1157 {
1158 log_received(&slice);
1159
1160 if self.not_running() {
1161 log_not_running(&slice);
1162 return;
1163 }
1164
1165 if let Err(e) = self.on_option_chain(slice) {
1166 log_error(&e);
1167 }
1168 }
1169
1170 fn handle_instrument_status(&mut self, status: &InstrumentStatus)
1172 where
1173 Self: Component,
1174 {
1175 log_received(&status);
1176
1177 if self.not_running() {
1178 log_not_running(&status);
1179 return;
1180 }
1181
1182 if let Err(e) = self.on_instrument_status(status) {
1183 log_error(&e);
1184 }
1185 }
1186
1187 fn handle_instrument_close(&mut self, close: &InstrumentClose)
1189 where
1190 Self: Component,
1191 {
1192 log_received(&close);
1193
1194 if self.not_running() {
1195 log_not_running(&close);
1196 return;
1197 }
1198
1199 if let Err(e) = self.on_instrument_close(close) {
1200 log_error(&e);
1201 }
1202 }
1203
1204 #[cfg(feature = "defi")]
1205 fn handle_block(&mut self, block: &Block)
1207 where
1208 Self: Component,
1209 {
1210 log_received(&block);
1211
1212 if self.not_running() {
1213 log_not_running(&block);
1214 return;
1215 }
1216
1217 if let Err(e) = self.on_block(block) {
1218 log_error(&e);
1219 }
1220 }
1221
1222 #[cfg(feature = "defi")]
1223 fn handle_pool(&mut self, pool: &Pool)
1225 where
1226 Self: Component,
1227 {
1228 log_received(&pool);
1229
1230 if self.not_running() {
1231 log_not_running(&pool);
1232 return;
1233 }
1234
1235 if let Err(e) = self.on_pool(pool) {
1236 log_error(&e);
1237 }
1238 }
1239
1240 #[cfg(feature = "defi")]
1241 fn handle_pool_swap(&mut self, swap: &PoolSwap)
1243 where
1244 Self: Component,
1245 {
1246 log_received(&swap);
1247
1248 if self.not_running() {
1249 log_not_running(&swap);
1250 return;
1251 }
1252
1253 if let Err(e) = self.on_pool_swap(swap) {
1254 log_error(&e);
1255 }
1256 }
1257
1258 #[cfg(feature = "defi")]
1259 fn handle_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate)
1261 where
1262 Self: Component,
1263 {
1264 log_received(&update);
1265
1266 if self.not_running() {
1267 log_not_running(&update);
1268 return;
1269 }
1270
1271 if let Err(e) = self.on_pool_liquidity_update(update) {
1272 log_error(&e);
1273 }
1274 }
1275
1276 #[cfg(feature = "defi")]
1277 fn handle_pool_fee_collect(&mut self, collect: &PoolFeeCollect)
1279 where
1280 Self: Component,
1281 {
1282 log_received(&collect);
1283
1284 if self.not_running() {
1285 log_not_running(&collect);
1286 return;
1287 }
1288
1289 if let Err(e) = self.on_pool_fee_collect(collect) {
1290 log_error(&e);
1291 }
1292 }
1293
1294 #[cfg(feature = "defi")]
1295 fn handle_pool_flash(&mut self, flash: &PoolFlash)
1297 where
1298 Self: Component,
1299 {
1300 log_received(&flash);
1301
1302 if self.not_running() {
1303 log_not_running(&flash);
1304 return;
1305 }
1306
1307 if let Err(e) = self.on_pool_flash(flash) {
1308 log_error(&e);
1309 }
1310 }
1311
1312 fn handle_historical_data(&mut self, data: &dyn Any) {
1314 log_received(&data);
1315
1316 if let Err(e) = self.on_historical_data(data) {
1317 log_error(&e);
1318 }
1319 }
1320
1321 fn handle_response(&mut self, envelope: &DataResponseEnvelope)
1323 where
1324 Self: DataActorNative,
1325 {
1326 let Some(scope) = self.core().begin_response(envelope.correlation_id()) else {
1327 return;
1328 };
1329
1330 if let Some(response) = envelope.response() {
1331 match response {
1332 DataResponse::Data(r) => self.handle_data_response(r),
1333 DataResponse::Instrument(r) => self.handle_instrument_response(r),
1334 DataResponse::Instruments(r) => self.handle_instruments_response(r),
1335 DataResponse::Book(r) => self.handle_book_response(r),
1336 DataResponse::BookDeltas(r) => self.handle_book_deltas_response(r),
1337 DataResponse::BookDepth(r) => self.handle_book_depth_response(r),
1338 DataResponse::Quotes(r) => self.handle_quotes_response(r),
1339 DataResponse::Trades(r) => self.handle_trades_response(r),
1340 DataResponse::FundingRates(r) => self.handle_funding_rates_response(r),
1341 DataResponse::Bars(r) => self.handle_bars_response(r),
1342 DataResponse::OptionChainReferencePrice(_) => {}
1343 }
1344 }
1345
1346 if self.core().complete_request(envelope, &scope)
1347 && let Err(e) = self.on_data_response(envelope)
1348 {
1349 log_error(&e);
1350 }
1351
1352 drop(scope);
1353 self.drain_request_groups();
1354 }
1355
1356 #[doc(hidden)]
1358 fn drain_request_groups(&mut self)
1359 where
1360 Self: DataActorNative,
1361 {
1362 while let Some((envelope, _handler)) = self.core().next_group_response() {
1363 let Some(scope) = self.core().begin_response(envelope.correlation_id()) else {
1364 continue;
1365 };
1366
1367 if self.core().complete_request(&envelope, &scope)
1368 && let Err(e) = self.on_data_response(&envelope)
1369 {
1370 log_error(&e);
1371 }
1372 }
1373 }
1374
1375 fn on_data_response(&mut self, _response: &DataResponseEnvelope) -> anyhow::Result<()> {
1384 Ok(())
1385 }
1386
1387 #[doc(hidden)]
1389 fn response_handler(&self) -> TypedHandler<DataResponseEnvelope>
1390 where
1391 Self: DataActorNative + Debug + Sized + 'static,
1392 {
1393 let actor_id = self.core().actor_id().inner();
1394 TypedHandler::from(move |response: &DataResponseEnvelope| {
1395 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1396 actor.handle_response(response);
1397 } else {
1398 log::error!("Actor {actor_id} not found for response handling");
1399 }
1400 })
1401 }
1402
1403 fn handle_data_response(&mut self, resp: &CustomDataResponse) {
1405 if let Some(data) = resp.data.as_ref().downcast_ref::<Vec<CustomData>>() {
1406 log_received_bulk("CustomDataResponse", &resp.correlation_id, data.len());
1407 log::trace!("{RECV} {resp:?}");
1408 } else {
1409 log_received(&resp);
1410 }
1411
1412 if let Err(e) = self.on_historical_data(resp.data.as_ref()) {
1413 log_error(&e);
1414 }
1415 }
1416
1417 fn handle_instrument_response(&mut self, resp: &InstrumentResponse) {
1419 log_received(&resp);
1420
1421 if let Err(e) = self.on_instrument(&resp.data) {
1422 log_error(&e);
1423 }
1424 }
1425
1426 fn handle_instruments_response(&mut self, resp: &InstrumentsResponse) {
1428 log_received_bulk("InstrumentsResponse", &resp.correlation_id, resp.data.len());
1429 log::trace!("{RECV} {resp:?}");
1430
1431 for inst in &resp.data {
1432 if let Err(e) = self.on_instrument(inst) {
1433 log_error(&e);
1434 }
1435 }
1436 }
1437
1438 fn handle_book_response(&mut self, resp: &BookResponse) {
1440 log_received(&resp);
1441
1442 if let Err(e) = self.on_book(&resp.data) {
1443 log_error(&e);
1444 }
1445 }
1446
1447 fn handle_book_deltas_response(&mut self, resp: &BookDeltasResponse) {
1449 log_received_bulk("BookDeltasResponse", &resp.correlation_id, resp.data.len());
1450 log::trace!("{RECV} {resp:?}");
1451
1452 if let Err(e) = self.on_historical_book_deltas(&resp.data) {
1453 log_error(&e);
1454 }
1455 }
1456
1457 fn handle_book_depth_response(&mut self, resp: &BookDepthResponse) {
1459 log_received_bulk("BookDepthResponse", &resp.correlation_id, resp.data.len());
1460 log::trace!("{RECV} {resp:?}");
1461
1462 if let Err(e) = self.on_historical_book_depth(&resp.data) {
1463 log_error(&e);
1464 }
1465 }
1466
1467 fn handle_quotes_response(&mut self, resp: &QuotesResponse)
1469 where
1470 Self: DataActorNative,
1471 {
1472 log_received_bulk("QuotesResponse", &resp.correlation_id, resp.data.len());
1473 log::trace!("{RECV} {resp:?}");
1474
1475 if let Err(e) = self.core().handle_indicators_for_quotes(&resp.data) {
1476 log_error(&e);
1477 return;
1478 }
1479
1480 if let Err(e) = self.on_historical_quotes(&resp.data) {
1481 log_error(&e);
1482 }
1483 }
1484
1485 fn handle_trades_response(&mut self, resp: &TradesResponse)
1487 where
1488 Self: DataActorNative,
1489 {
1490 log_received_bulk("TradesResponse", &resp.correlation_id, resp.data.len());
1491 log::trace!("{RECV} {resp:?}");
1492
1493 if let Err(e) = self.core().handle_indicators_for_trades(&resp.data) {
1494 log_error(&e);
1495 return;
1496 }
1497
1498 if let Err(e) = self.on_historical_trades(&resp.data) {
1499 log_error(&e);
1500 }
1501 }
1502
1503 fn handle_bars_response(&mut self, resp: &BarsResponse)
1505 where
1506 Self: DataActorNative,
1507 {
1508 log_received_bulk("BarsResponse", &resp.correlation_id, resp.data.len());
1509 log::trace!("{RECV} {resp:?}");
1510
1511 if let Err(e) = self.core().handle_indicators_for_bars(&resp.data) {
1512 log_error(&e);
1513 return;
1514 }
1515
1516 if let Err(e) = self.on_historical_bars(&resp.data) {
1517 log_error(&e);
1518 }
1519 }
1520
1521 fn handle_funding_rates_response(&mut self, resp: &FundingRatesResponse) {
1523 log_received_bulk(
1524 "FundingRatesResponse",
1525 &resp.correlation_id,
1526 resp.data.len(),
1527 );
1528 log::trace!("{RECV} {resp:?}");
1529
1530 if let Err(e) = self.on_historical_funding_rates(&resp.data) {
1531 log_error(&e);
1532 }
1533 }
1534
1535 fn subscribe_data(
1537 &mut self,
1538 data_type: DataType,
1539 client_id: Option<ClientId>,
1540 params: Option<Params>,
1541 ) where
1542 Self: DataActorNative,
1543 Self: 'static + Debug + Sized,
1544 {
1545 let actor_id = self.core().actor_id().inner();
1546 let handler = ShareableMessageHandler::from_typed(move |data: &CustomData| {
1547 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1548 actor.handle_data(data);
1549 } else {
1550 log::error!("Actor {actor_id} not found for data handling");
1551 }
1552 });
1553
1554 DataActorCore::subscribe_data(self.core_mut(), handler, data_type, client_id, params);
1555 }
1556
1557 fn subscribe_signal(&mut self, name: &str, priority: Option<u32>)
1572 where
1573 Self: DataActorNative,
1574 Self: 'static + Debug + Sized,
1575 {
1576 let actor_id = self.core().actor_id().inner();
1577 let handler = ShareableMessageHandler::from_typed(move |data: &CustomData| {
1580 if let Some(signal) = data.data.as_any().downcast_ref::<Signal>() {
1581 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1582 actor.handle_signal(signal);
1583 } else {
1584 log::error!("Actor {actor_id} not found for signal handling");
1585 }
1586 }
1587 });
1588
1589 DataActorCore::subscribe_signal(self.core_mut(), handler, name, priority);
1590 }
1591
1592 fn subscribe_queue_state(&mut self, channel: Option<SystemChannel>, priority: Option<u32>)
1600 where
1601 Self: DataActorNative,
1602 Self: 'static + Debug + Sized,
1603 {
1604 let actor_id = self.core().actor_id().inner();
1605 let handler = ShareableMessageHandler::from_typed(move |event: &QueueStateChanged| {
1606 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1607 actor.handle_queue_state(event);
1608 } else {
1609 log::error!("Actor {actor_id} not found for queue state change handling");
1610 }
1611 });
1612
1613 DataActorCore::subscribe_queue_state(self.core_mut(), handler, channel, priority);
1614 }
1615
1616 fn subscribe_socket_state(
1625 &mut self,
1626 client_id: Option<ClientId>,
1627 endpoint: Option<&str>,
1628 priority: Option<u32>,
1629 ) where
1630 Self: DataActorNative,
1631 Self: 'static + Debug + Sized,
1632 {
1633 let actor_id = self.core().actor_id().inner();
1634 let handler = ShareableMessageHandler::from_typed(move |event: &SocketStateChanged| {
1635 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1636 actor.handle_socket_state(event);
1637 } else {
1638 log::error!("Actor {actor_id} not found for socket state change handling");
1639 }
1640 });
1641
1642 DataActorCore::subscribe_socket_state(
1643 self.core_mut(),
1644 handler,
1645 client_id,
1646 endpoint,
1647 priority,
1648 );
1649 }
1650
1651 fn subscribe_quotes(
1662 &mut self,
1663 instrument_ids: impl IntoInstrumentIds,
1664 client_id: Option<ClientId>,
1665 params: Option<Params>,
1666 ) where
1667 Self: DataActorNative,
1668 Self: 'static + Debug + Sized,
1669 {
1670 let actor_id = self.core().actor_id().inner();
1671 let handler = TypedHandler::from(move |quote: &QuoteTick| {
1672 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1673 actor.handle_quote(quote);
1674 } else {
1675 log::error!("Actor {actor_id} not found for quote handling");
1676 }
1677 });
1678
1679 DataActorCore::subscribe_quotes(
1680 self.core_mut(),
1681 handler,
1682 instrument_ids,
1683 client_id,
1684 params,
1685 )
1686 .expect("invalid quote subscription identifier list");
1687 }
1688
1689 fn subscribe_instruments(
1691 &mut self,
1692 venue: Venue,
1693 client_id: Option<ClientId>,
1694 params: Option<Params>,
1695 ) where
1696 Self: DataActorNative,
1697 Self: 'static + Debug + Sized,
1698 {
1699 let actor_id = self.core().actor_id().inner();
1700 let pattern = get_instruments_pattern(venue);
1701
1702 let handler = TypedHandler::from(move |instrument: &InstrumentAny| {
1703 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1704 actor.handle_instrument(instrument);
1705 } else {
1706 log::error!("Actor {actor_id} not found for instruments handling");
1707 }
1708 });
1709
1710 DataActorCore::subscribe_instruments(
1711 self.core_mut(),
1712 pattern,
1713 handler,
1714 venue,
1715 client_id,
1716 params,
1717 );
1718 }
1719
1720 fn subscribe_instrument(
1731 &mut self,
1732 instrument_ids: impl IntoInstrumentIds,
1733 client_id: Option<ClientId>,
1734 params: Option<Params>,
1735 ) where
1736 Self: DataActorNative,
1737 Self: 'static + Debug + Sized,
1738 {
1739 let actor_id = self.core().actor_id().inner();
1740 let handler = TypedHandler::from(move |instrument: &InstrumentAny| {
1741 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1742 actor.handle_instrument(instrument);
1743 } else {
1744 log::error!("Actor {actor_id} not found for instrument handling");
1745 }
1746 });
1747
1748 DataActorCore::subscribe_instrument(
1749 self.core_mut(),
1750 handler,
1751 instrument_ids,
1752 client_id,
1753 params,
1754 )
1755 .expect("invalid instrument subscription identifier list");
1756 }
1757
1758 fn subscribe_book_deltas(
1773 &mut self,
1774 instrument_ids: impl IntoInstrumentIds,
1775 book_type: BookType,
1776 depth: Option<NonZeroUsize>,
1777 client_id: Option<ClientId>,
1778 managed: bool,
1779 params: Option<Params>,
1780 ) where
1781 Self: DataActorNative,
1782 Self: 'static + Debug + Sized,
1783 {
1784 let actor_id = self.core().actor_id().inner();
1785 let handler = TypedHandler::from(move |deltas: &OrderBookDeltas| {
1786 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1787 actor.handle_book_deltas(deltas);
1788 } else {
1789 log::error!("Actor {actor_id} not found for book deltas handling");
1790 }
1791 });
1792
1793 DataActorCore::subscribe_book_deltas(
1794 self.core_mut(),
1795 handler,
1796 instrument_ids,
1797 book_type,
1798 depth,
1799 client_id,
1800 managed,
1801 params,
1802 )
1803 .expect("invalid instrument subscription identifier list");
1804 }
1805
1806 fn subscribe_book_depth(
1824 &mut self,
1825 instrument_ids: impl IntoInstrumentIds,
1826 book_type: BookType,
1827 depth: Option<NonZeroUsize>,
1828 client_id: Option<ClientId>,
1829 managed: bool,
1830 params: Option<Params>,
1831 ) where
1832 Self: DataActorNative,
1833 Self: 'static + Debug + Sized,
1834 {
1835 let actor_id = self.core().actor_id().inner();
1836 let handler = TypedHandler::from(move |depth: &OrderBookDepth| {
1837 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1838 actor.handle_book_depth(depth);
1839 } else {
1840 log::error!("Actor {actor_id} not found for book depth handling");
1841 }
1842 });
1843
1844 DataActorCore::subscribe_book_depth(
1845 self.core_mut(),
1846 handler,
1847 instrument_ids,
1848 book_type,
1849 depth,
1850 client_id,
1851 managed,
1852 params,
1853 )
1854 .expect("invalid instrument subscription identifier list");
1855 }
1856
1857 fn subscribe_book_at_interval(
1868 &mut self,
1869 instrument_ids: impl IntoInstrumentIds,
1870 book_type: BookType,
1871 depth: Option<NonZeroUsize>,
1872 interval_ms: NonZeroUsize,
1873 client_id: Option<ClientId>,
1874 params: Option<Params>,
1875 ) where
1876 Self: DataActorNative,
1877 Self: 'static + Debug + Sized,
1878 {
1879 let actor_id = self.core().actor_id().inner();
1880 let handler = TypedHandler::from(move |book: &OrderBook| {
1881 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1882 actor.handle_book(book);
1883 } else {
1884 log::error!("Actor {actor_id} not found for book handling");
1885 }
1886 });
1887
1888 DataActorCore::subscribe_book_at_interval(
1889 self.core_mut(),
1890 handler,
1891 instrument_ids,
1892 book_type,
1893 depth,
1894 interval_ms,
1895 client_id,
1896 params,
1897 )
1898 .expect("invalid instrument subscription identifier list");
1899 }
1900
1901 fn subscribe_trades(
1911 &mut self,
1912 instrument_ids: impl IntoInstrumentIds,
1913 client_id: Option<ClientId>,
1914 params: Option<Params>,
1915 ) where
1916 Self: DataActorNative,
1917 Self: 'static + Debug + Sized,
1918 {
1919 let actor_id = self.core().actor_id().inner();
1920 let handler = TypedHandler::from(move |trade: &TradeTick| {
1921 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1922 actor.handle_trade(trade);
1923 } else {
1924 log::error!("Actor {actor_id} not found for trade handling");
1925 }
1926 });
1927
1928 DataActorCore::subscribe_trades(
1929 self.core_mut(),
1930 handler,
1931 instrument_ids,
1932 client_id,
1933 params,
1934 )
1935 .expect("invalid trade subscription identifier list");
1936 }
1937
1938 fn subscribe_bars(
1946 &mut self,
1947 bar_types: impl IntoBarTypes,
1948 client_id: Option<ClientId>,
1949 params: Option<Params>,
1950 ) where
1951 Self: DataActorNative,
1952 Self: 'static + Debug + Sized,
1953 {
1954 let actor_id = self.core().actor_id().inner();
1955 let handler = TypedHandler::from(move |bar: &Bar| {
1956 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1957 actor.handle_bar(bar);
1958 } else {
1959 log::error!("Actor {actor_id} not found for bar handling");
1960 }
1961 });
1962
1963 DataActorCore::subscribe_bars(self.core_mut(), handler, bar_types, client_id, params)
1964 .expect("invalid bar subscription type list");
1965 }
1966
1967 fn subscribe_mark_prices(
1977 &mut self,
1978 instrument_ids: impl IntoInstrumentIds,
1979 client_id: Option<ClientId>,
1980 params: Option<Params>,
1981 ) where
1982 Self: DataActorNative,
1983 Self: 'static + Debug + Sized,
1984 {
1985 let actor_id = self.core().actor_id().inner();
1986 let handler = TypedHandler::from(move |mark_price: &MarkPriceUpdate| {
1987 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1988 actor.handle_mark_price(mark_price);
1989 } else {
1990 log::error!("Actor {actor_id} not found for mark price handling");
1991 }
1992 });
1993
1994 DataActorCore::subscribe_mark_prices(
1995 self.core_mut(),
1996 handler,
1997 instrument_ids,
1998 client_id,
1999 params,
2000 )
2001 .expect("invalid instrument subscription identifier list");
2002 }
2003
2004 fn subscribe_index_prices(
2014 &mut self,
2015 instrument_ids: impl IntoInstrumentIds,
2016 client_id: Option<ClientId>,
2017 params: Option<Params>,
2018 ) where
2019 Self: DataActorNative,
2020 Self: 'static + Debug + Sized,
2021 {
2022 let actor_id = self.core().actor_id().inner();
2023 let handler = TypedHandler::from(move |index_price: &IndexPriceUpdate| {
2024 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2025 actor.handle_index_price(index_price);
2026 } else {
2027 log::error!("Actor {actor_id} not found for index price handling");
2028 }
2029 });
2030
2031 DataActorCore::subscribe_index_prices(
2032 self.core_mut(),
2033 handler,
2034 instrument_ids,
2035 client_id,
2036 params,
2037 )
2038 .expect("invalid instrument subscription identifier list");
2039 }
2040
2041 fn subscribe_funding_rates(
2051 &mut self,
2052 instrument_ids: impl IntoInstrumentIds,
2053 client_id: Option<ClientId>,
2054 params: Option<Params>,
2055 ) where
2056 Self: DataActorNative,
2057 Self: 'static + Debug + Sized,
2058 {
2059 let actor_id = self.core().actor_id().inner();
2060 let handler = TypedHandler::from(move |funding_rate: &FundingRateUpdate| {
2061 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2062 actor.handle_funding_rate(funding_rate);
2063 } else {
2064 log::error!("Actor {actor_id} not found for funding rate handling");
2065 }
2066 });
2067
2068 DataActorCore::subscribe_funding_rates(
2069 self.core_mut(),
2070 handler,
2071 instrument_ids,
2072 client_id,
2073 params,
2074 )
2075 .expect("invalid instrument subscription identifier list");
2076 }
2077
2078 fn subscribe_option_greeks(
2089 &mut self,
2090 instrument_ids: impl IntoInstrumentIds,
2091 client_id: Option<ClientId>,
2092 params: Option<Params>,
2093 ) where
2094 Self: DataActorNative,
2095 Self: 'static + Debug + Sized,
2096 {
2097 let actor_id = self.core().actor_id().inner();
2098 let handler = TypedHandler::from(move |option_greeks: &OptionGreeks| {
2099 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2100 actor.handle_option_greeks(option_greeks);
2101 } else {
2102 log::error!("Actor {actor_id} not found for option greeks handling");
2103 }
2104 });
2105
2106 DataActorCore::subscribe_option_greeks(
2107 self.core_mut(),
2108 handler,
2109 instrument_ids,
2110 client_id,
2111 params,
2112 )
2113 .expect("invalid instrument subscription identifier list");
2114 }
2115
2116 fn subscribe_instrument_status(
2127 &mut self,
2128 instrument_ids: impl IntoInstrumentIds,
2129 client_id: Option<ClientId>,
2130 params: Option<Params>,
2131 ) where
2132 Self: DataActorNative,
2133 Self: 'static + Debug + Sized,
2134 {
2135 let actor_id = self.core().actor_id().inner();
2136 let handler = ShareableMessageHandler::from_typed(move |status: &InstrumentStatus| {
2137 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2138 actor.handle_instrument_status(status);
2139 } else {
2140 log::error!("Actor {actor_id} not found for instrument status handling");
2141 }
2142 });
2143
2144 DataActorCore::subscribe_instrument_status(
2145 self.core_mut(),
2146 handler,
2147 instrument_ids,
2148 client_id,
2149 params,
2150 )
2151 .expect("invalid instrument subscription identifier list");
2152 }
2153
2154 fn subscribe_instrument_close(
2165 &mut self,
2166 instrument_ids: impl IntoInstrumentIds,
2167 client_id: Option<ClientId>,
2168 params: Option<Params>,
2169 ) where
2170 Self: DataActorNative,
2171 Self: 'static + Debug + Sized,
2172 {
2173 let actor_id = self.core().actor_id().inner();
2174 let handler = ShareableMessageHandler::from_typed(move |close: &InstrumentClose| {
2175 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2176 actor.handle_instrument_close(close);
2177 } else {
2178 log::error!("Actor {actor_id} not found for instrument close handling");
2179 }
2180 });
2181
2182 DataActorCore::subscribe_instrument_close(
2183 self.core_mut(),
2184 handler,
2185 instrument_ids,
2186 client_id,
2187 params,
2188 )
2189 .expect("invalid instrument subscription identifier list");
2190 }
2191
2192 fn subscribe_option_chain(
2197 &mut self,
2198 series_id: OptionSeriesId,
2199 strike_range: StrikeRange,
2200 snapshot_interval_ms: Option<u64>,
2201 client_id: Option<ClientId>,
2202 params: Option<Params>,
2203 ) where
2204 Self: DataActorNative,
2205 Self: 'static + Debug + Sized,
2206 {
2207 let actor_id = self.core().actor_id().inner();
2208 let topic = get_option_chain_topic(series_id);
2209
2210 let handler = TypedHandler::from(move |slice: &OptionChainSlice| {
2211 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2212 actor.handle_option_chain(slice);
2213 } else {
2214 log::error!("Actor {actor_id} not found for option chain handling");
2215 }
2216 });
2217
2218 DataActorCore::subscribe_option_chain(
2219 self.core_mut(),
2220 topic,
2221 handler,
2222 series_id,
2223 strike_range,
2224 snapshot_interval_ms,
2225 client_id,
2226 params,
2227 );
2228 }
2229
2230 #[cfg(feature = "defi")]
2231 fn subscribe_blocks(
2233 &mut self,
2234 chain: Blockchain,
2235 client_id: Option<ClientId>,
2236 params: Option<Params>,
2237 ) where
2238 Self: DataActorNative,
2239 Self: 'static + Debug + Sized,
2240 {
2241 let actor_id = self.core().actor_id().inner();
2242 let topic = defi::switchboard::get_defi_blocks_topic(chain);
2243
2244 let handler = TypedHandler::from(move |block: &Block| {
2245 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2246 actor.handle_block(block);
2247 } else {
2248 log::error!("Actor {actor_id} not found for block handling");
2249 }
2250 });
2251
2252 DataActorCore::subscribe_blocks(self.core_mut(), topic, handler, chain, client_id, params);
2253 }
2254
2255 #[cfg(feature = "defi")]
2256 fn subscribe_pool(
2258 &mut self,
2259 instrument_id: InstrumentId,
2260 client_id: Option<ClientId>,
2261 params: Option<Params>,
2262 ) where
2263 Self: DataActorNative,
2264 Self: 'static + Debug + Sized,
2265 {
2266 let actor_id = self.core().actor_id().inner();
2267 let topic = defi::switchboard::get_defi_pool_topic(instrument_id);
2268
2269 let handler = TypedHandler::from(move |pool: &Pool| {
2270 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2271 actor.handle_pool(pool);
2272 } else {
2273 log::error!("Actor {actor_id} not found for pool handling");
2274 }
2275 });
2276
2277 DataActorCore::subscribe_pool(
2278 self.core_mut(),
2279 topic,
2280 handler,
2281 instrument_id,
2282 client_id,
2283 params,
2284 );
2285 }
2286
2287 #[cfg(feature = "defi")]
2288 fn subscribe_pool_swaps(
2290 &mut self,
2291 instrument_id: InstrumentId,
2292 client_id: Option<ClientId>,
2293 params: Option<Params>,
2294 ) where
2295 Self: DataActorNative,
2296 Self: 'static + Debug + Sized,
2297 {
2298 let actor_id = self.core().actor_id().inner();
2299 let topic = defi::switchboard::get_defi_pool_swaps_topic(instrument_id);
2300
2301 let handler = TypedHandler::from(move |swap: &PoolSwap| {
2302 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2303 actor.handle_pool_swap(swap);
2304 } else {
2305 log::error!("Actor {actor_id} not found for pool swap handling");
2306 }
2307 });
2308
2309 DataActorCore::subscribe_pool_swaps(
2310 self.core_mut(),
2311 topic,
2312 handler,
2313 instrument_id,
2314 client_id,
2315 params,
2316 );
2317 }
2318
2319 #[cfg(feature = "defi")]
2320 fn subscribe_pool_liquidity_updates(
2322 &mut self,
2323 instrument_id: InstrumentId,
2324 client_id: Option<ClientId>,
2325 params: Option<Params>,
2326 ) where
2327 Self: DataActorNative,
2328 Self: 'static + Debug + Sized,
2329 {
2330 let actor_id = self.core().actor_id().inner();
2331 let topic = defi::switchboard::get_defi_liquidity_topic(instrument_id);
2332
2333 let handler = TypedHandler::from(move |update: &PoolLiquidityUpdate| {
2334 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2335 actor.handle_pool_liquidity_update(update);
2336 } else {
2337 log::error!("Actor {actor_id} not found for pool liquidity update handling");
2338 }
2339 });
2340
2341 DataActorCore::subscribe_pool_liquidity_updates(
2342 self.core_mut(),
2343 topic,
2344 handler,
2345 instrument_id,
2346 client_id,
2347 params,
2348 );
2349 }
2350
2351 #[cfg(feature = "defi")]
2352 fn subscribe_pool_fee_collects(
2354 &mut self,
2355 instrument_id: InstrumentId,
2356 client_id: Option<ClientId>,
2357 params: Option<Params>,
2358 ) where
2359 Self: DataActorNative,
2360 Self: 'static + Debug + Sized,
2361 {
2362 let actor_id = self.core().actor_id().inner();
2363 let topic = defi::switchboard::get_defi_collect_topic(instrument_id);
2364
2365 let handler = TypedHandler::from(move |collect: &PoolFeeCollect| {
2366 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2367 actor.handle_pool_fee_collect(collect);
2368 } else {
2369 log::error!("Actor {actor_id} not found for pool fee collect handling");
2370 }
2371 });
2372
2373 DataActorCore::subscribe_pool_fee_collects(
2374 self.core_mut(),
2375 topic,
2376 handler,
2377 instrument_id,
2378 client_id,
2379 params,
2380 );
2381 }
2382
2383 #[cfg(feature = "defi")]
2384 fn subscribe_pool_flash_events(
2386 &mut self,
2387 instrument_id: InstrumentId,
2388 client_id: Option<ClientId>,
2389 params: Option<Params>,
2390 ) where
2391 Self: DataActorNative,
2392 Self: 'static + Debug + Sized,
2393 {
2394 let actor_id = self.core().actor_id().inner();
2395 let topic = defi::switchboard::get_defi_flash_topic(instrument_id);
2396
2397 let handler = TypedHandler::from(move |flash: &PoolFlash| {
2398 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2399 actor.handle_pool_flash(flash);
2400 } else {
2401 log::error!("Actor {actor_id} not found for pool flash handling");
2402 }
2403 });
2404
2405 DataActorCore::subscribe_pool_flash_events(
2406 self.core_mut(),
2407 topic,
2408 handler,
2409 instrument_id,
2410 client_id,
2411 params,
2412 );
2413 }
2414
2415 fn unsubscribe_data(
2417 &mut self,
2418 data_type: DataType,
2419 client_id: Option<ClientId>,
2420 params: Option<Params>,
2421 ) where
2422 Self: DataActorNative,
2423 Self: 'static + Debug + Sized,
2424 {
2425 DataActorCore::unsubscribe_data(self.core_mut(), data_type, client_id, params);
2426 }
2427
2428 fn unsubscribe_signal(&mut self, name: &str)
2430 where
2431 Self: DataActorNative,
2432 Self: 'static + Debug + Sized,
2433 {
2434 DataActorCore::unsubscribe_signal(self.core_mut(), name);
2435 }
2436
2437 fn unsubscribe_queue_state(&mut self, channel: Option<SystemChannel>)
2441 where
2442 Self: DataActorNative,
2443 Self: 'static + Debug + Sized,
2444 {
2445 DataActorCore::unsubscribe_queue_state(self.core_mut(), channel);
2446 }
2447
2448 fn unsubscribe_socket_state(&mut self, client_id: Option<ClientId>, endpoint: Option<&str>)
2452 where
2453 Self: DataActorNative,
2454 Self: 'static + Debug + Sized,
2455 {
2456 DataActorCore::unsubscribe_socket_state(self.core_mut(), client_id, endpoint);
2457 }
2458
2459 fn unsubscribe_instruments(
2461 &mut self,
2462 venue: Venue,
2463 client_id: Option<ClientId>,
2464 params: Option<Params>,
2465 ) where
2466 Self: DataActorNative,
2467 Self: 'static + Debug + Sized,
2468 {
2469 DataActorCore::unsubscribe_instruments(self.core_mut(), venue, client_id, params);
2470 }
2471
2472 fn unsubscribe_instrument(
2482 &mut self,
2483 instrument_ids: impl IntoInstrumentIds,
2484 client_id: Option<ClientId>,
2485 params: Option<Params>,
2486 ) where
2487 Self: DataActorNative,
2488 Self: 'static + Debug + Sized,
2489 {
2490 DataActorCore::unsubscribe_instrument(self.core_mut(), instrument_ids, client_id, params)
2491 .expect("invalid instrument subscription identifier list");
2492 }
2493
2494 fn unsubscribe_book_deltas(
2504 &mut self,
2505 instrument_ids: impl IntoInstrumentIds,
2506 client_id: Option<ClientId>,
2507 params: Option<Params>,
2508 ) where
2509 Self: DataActorNative,
2510 Self: 'static + Debug + Sized,
2511 {
2512 DataActorCore::unsubscribe_book_deltas(self.core_mut(), instrument_ids, client_id, params)
2513 .expect("invalid instrument subscription identifier list");
2514 }
2515
2516 fn unsubscribe_book_depth(
2526 &mut self,
2527 instrument_ids: impl IntoInstrumentIds,
2528 client_id: Option<ClientId>,
2529 params: Option<Params>,
2530 ) where
2531 Self: DataActorNative,
2532 Self: 'static + Debug + Sized,
2533 {
2534 DataActorCore::unsubscribe_book_depth(self.core_mut(), instrument_ids, client_id, params)
2535 .expect("invalid instrument subscription identifier list");
2536 }
2537
2538 fn unsubscribe_book_at_interval(
2548 &mut self,
2549 instrument_ids: impl IntoInstrumentIds,
2550 interval_ms: NonZeroUsize,
2551 client_id: Option<ClientId>,
2552 params: Option<Params>,
2553 ) where
2554 Self: DataActorNative,
2555 Self: 'static + Debug + Sized,
2556 {
2557 DataActorCore::unsubscribe_book_at_interval(
2558 self.core_mut(),
2559 instrument_ids,
2560 interval_ms,
2561 client_id,
2562 params,
2563 )
2564 .expect("invalid instrument subscription identifier list");
2565 }
2566
2567 fn unsubscribe_quotes(
2577 &mut self,
2578 instrument_ids: impl IntoInstrumentIds,
2579 client_id: Option<ClientId>,
2580 params: Option<Params>,
2581 ) where
2582 Self: DataActorNative,
2583 Self: 'static + Debug + Sized,
2584 {
2585 DataActorCore::unsubscribe_quotes(self.core_mut(), instrument_ids, client_id, params)
2586 .expect("invalid quote subscription identifier list");
2587 }
2588
2589 fn unsubscribe_trades(
2599 &mut self,
2600 instrument_ids: impl IntoInstrumentIds,
2601 client_id: Option<ClientId>,
2602 params: Option<Params>,
2603 ) where
2604 Self: DataActorNative,
2605 Self: 'static + Debug + Sized,
2606 {
2607 DataActorCore::unsubscribe_trades(self.core_mut(), instrument_ids, client_id, params)
2608 .expect("invalid trade subscription identifier list");
2609 }
2610
2611 fn unsubscribe_bars(
2619 &mut self,
2620 bar_types: impl IntoBarTypes,
2621 client_id: Option<ClientId>,
2622 params: Option<Params>,
2623 ) where
2624 Self: DataActorNative,
2625 Self: 'static + Debug + Sized,
2626 {
2627 DataActorCore::unsubscribe_bars(self.core_mut(), bar_types, client_id, params)
2628 .expect("invalid bar subscription type list");
2629 }
2630
2631 fn unsubscribe_mark_prices(
2641 &mut self,
2642 instrument_ids: impl IntoInstrumentIds,
2643 client_id: Option<ClientId>,
2644 params: Option<Params>,
2645 ) where
2646 Self: DataActorNative,
2647 Self: 'static + Debug + Sized,
2648 {
2649 DataActorCore::unsubscribe_mark_prices(self.core_mut(), instrument_ids, client_id, params)
2650 .expect("invalid instrument subscription identifier list");
2651 }
2652
2653 fn unsubscribe_index_prices(
2663 &mut self,
2664 instrument_ids: impl IntoInstrumentIds,
2665 client_id: Option<ClientId>,
2666 params: Option<Params>,
2667 ) where
2668 Self: DataActorNative,
2669 Self: 'static + Debug + Sized,
2670 {
2671 DataActorCore::unsubscribe_index_prices(self.core_mut(), instrument_ids, client_id, params)
2672 .expect("invalid instrument subscription identifier list");
2673 }
2674
2675 fn unsubscribe_funding_rates(
2685 &mut self,
2686 instrument_ids: impl IntoInstrumentIds,
2687 client_id: Option<ClientId>,
2688 params: Option<Params>,
2689 ) where
2690 Self: DataActorNative,
2691 Self: 'static + Debug + Sized,
2692 {
2693 DataActorCore::unsubscribe_funding_rates(
2694 self.core_mut(),
2695 instrument_ids,
2696 client_id,
2697 params,
2698 )
2699 .expect("invalid instrument subscription identifier list");
2700 }
2701
2702 fn unsubscribe_option_greeks(
2712 &mut self,
2713 instrument_ids: impl IntoInstrumentIds,
2714 client_id: Option<ClientId>,
2715 params: Option<Params>,
2716 ) where
2717 Self: DataActorNative,
2718 Self: 'static + Debug + Sized,
2719 {
2720 DataActorCore::unsubscribe_option_greeks(
2721 self.core_mut(),
2722 instrument_ids,
2723 client_id,
2724 params,
2725 )
2726 .expect("invalid instrument subscription identifier list");
2727 }
2728
2729 fn unsubscribe_instrument_status(
2739 &mut self,
2740 instrument_ids: impl IntoInstrumentIds,
2741 client_id: Option<ClientId>,
2742 params: Option<Params>,
2743 ) where
2744 Self: DataActorNative,
2745 Self: 'static + Debug + Sized,
2746 {
2747 DataActorCore::unsubscribe_instrument_status(
2748 self.core_mut(),
2749 instrument_ids,
2750 client_id,
2751 params,
2752 )
2753 .expect("invalid instrument subscription identifier list");
2754 }
2755
2756 fn unsubscribe_instrument_close(
2766 &mut self,
2767 instrument_ids: impl IntoInstrumentIds,
2768 client_id: Option<ClientId>,
2769 params: Option<Params>,
2770 ) where
2771 Self: DataActorNative,
2772 Self: 'static + Debug + Sized,
2773 {
2774 DataActorCore::unsubscribe_instrument_close(
2775 self.core_mut(),
2776 instrument_ids,
2777 client_id,
2778 params,
2779 )
2780 .expect("invalid instrument subscription identifier list");
2781 }
2782
2783 fn unsubscribe_option_chain(&mut self, series_id: OptionSeriesId, client_id: Option<ClientId>)
2785 where
2786 Self: DataActorNative,
2787 Self: 'static + Debug + Sized,
2788 {
2789 DataActorCore::unsubscribe_option_chain(self.core_mut(), series_id, client_id);
2790 }
2791
2792 #[cfg(feature = "defi")]
2793 fn unsubscribe_blocks(
2795 &mut self,
2796 chain: Blockchain,
2797 client_id: Option<ClientId>,
2798 params: Option<Params>,
2799 ) where
2800 Self: DataActorNative,
2801 Self: 'static + Debug + Sized,
2802 {
2803 DataActorCore::unsubscribe_blocks(self.core_mut(), chain, client_id, params);
2804 }
2805
2806 #[cfg(feature = "defi")]
2807 fn unsubscribe_pool(
2809 &mut self,
2810 instrument_id: InstrumentId,
2811 client_id: Option<ClientId>,
2812 params: Option<Params>,
2813 ) where
2814 Self: DataActorNative,
2815 Self: 'static + Debug + Sized,
2816 {
2817 DataActorCore::unsubscribe_pool(self.core_mut(), instrument_id, client_id, params);
2818 }
2819
2820 #[cfg(feature = "defi")]
2821 fn unsubscribe_pool_swaps(
2823 &mut self,
2824 instrument_id: InstrumentId,
2825 client_id: Option<ClientId>,
2826 params: Option<Params>,
2827 ) where
2828 Self: DataActorNative,
2829 Self: 'static + Debug + Sized,
2830 {
2831 DataActorCore::unsubscribe_pool_swaps(self.core_mut(), instrument_id, client_id, params);
2832 }
2833
2834 #[cfg(feature = "defi")]
2835 fn unsubscribe_pool_liquidity_updates(
2837 &mut self,
2838 instrument_id: InstrumentId,
2839 client_id: Option<ClientId>,
2840 params: Option<Params>,
2841 ) where
2842 Self: DataActorNative,
2843 Self: 'static + Debug + Sized,
2844 {
2845 DataActorCore::unsubscribe_pool_liquidity_updates(
2846 self.core_mut(),
2847 instrument_id,
2848 client_id,
2849 params,
2850 );
2851 }
2852
2853 #[cfg(feature = "defi")]
2854 fn unsubscribe_pool_fee_collects(
2856 &mut self,
2857 instrument_id: InstrumentId,
2858 client_id: Option<ClientId>,
2859 params: Option<Params>,
2860 ) where
2861 Self: DataActorNative,
2862 Self: 'static + Debug + Sized,
2863 {
2864 DataActorCore::unsubscribe_pool_fee_collects(
2865 self.core_mut(),
2866 instrument_id,
2867 client_id,
2868 params,
2869 );
2870 }
2871
2872 #[cfg(feature = "defi")]
2873 fn unsubscribe_pool_flash_events(
2875 &mut self,
2876 instrument_id: InstrumentId,
2877 client_id: Option<ClientId>,
2878 params: Option<Params>,
2879 ) where
2880 Self: DataActorNative,
2881 Self: 'static + Debug + Sized,
2882 {
2883 DataActorCore::unsubscribe_pool_flash_events(
2884 self.core_mut(),
2885 instrument_id,
2886 client_id,
2887 params,
2888 );
2889 }
2890
2891 fn request_data(
2897 &mut self,
2898 data_type: DataType,
2899 client_id: ClientId,
2900 start: Option<Timestamp>,
2901 end: Option<Timestamp>,
2902 limit: Option<NonZeroUsize>,
2903 params: Option<Params>,
2904 ) -> anyhow::Result<UUID4>
2905 where
2906 Self: DataActorNative,
2907 Self: 'static + Debug + Sized,
2908 {
2909 let handler = self.response_handler();
2910
2911 DataActorCore::request_data(
2912 self.core_mut(),
2913 data_type,
2914 client_id,
2915 start,
2916 end,
2917 limit,
2918 params,
2919 handler,
2920 )
2921 }
2922
2923 fn request_instrument(
2929 &mut self,
2930 instrument_ids: impl IntoInstrumentIds,
2931 start: Option<Timestamp>,
2932 end: Option<Timestamp>,
2933 client_id: Option<ClientId>,
2934 params: Option<Params>,
2935 ) -> anyhow::Result<UUID4>
2936 where
2937 Self: DataActorNative,
2938 Self: 'static + Debug + Sized,
2939 {
2940 let handler = self.response_handler();
2941
2942 DataActorCore::request_instrument(
2943 self.core_mut(),
2944 instrument_ids,
2945 start,
2946 end,
2947 client_id,
2948 params,
2949 handler,
2950 )
2951 }
2952
2953 fn request_instruments(
2959 &mut self,
2960 venue: Option<Venue>,
2961 start: Option<Timestamp>,
2962 end: Option<Timestamp>,
2963 client_id: Option<ClientId>,
2964 params: Option<Params>,
2965 ) -> anyhow::Result<UUID4>
2966 where
2967 Self: DataActorNative,
2968 Self: 'static + Debug + Sized,
2969 {
2970 let handler = self.response_handler();
2971
2972 DataActorCore::request_instruments(
2973 self.core_mut(),
2974 venue,
2975 start,
2976 end,
2977 client_id,
2978 params,
2979 handler,
2980 )
2981 }
2982
2983 fn request_book_snapshot(
2989 &mut self,
2990 instrument_ids: impl IntoInstrumentIds,
2991 depth: Option<NonZeroUsize>,
2992 client_id: Option<ClientId>,
2993 params: Option<Params>,
2994 ) -> anyhow::Result<UUID4>
2995 where
2996 Self: DataActorNative,
2997 Self: 'static + Debug + Sized,
2998 {
2999 let handler = self.response_handler();
3000
3001 DataActorCore::request_book_snapshot(
3002 self.core_mut(),
3003 instrument_ids,
3004 depth,
3005 client_id,
3006 params,
3007 handler,
3008 )
3009 }
3010
3011 fn request_book_deltas(
3017 &mut self,
3018 instrument_ids: impl IntoInstrumentIds,
3019 start: Option<Timestamp>,
3020 end: Option<Timestamp>,
3021 limit: Option<NonZeroUsize>,
3022 client_id: Option<ClientId>,
3023 params: Option<Params>,
3024 ) -> anyhow::Result<UUID4>
3025 where
3026 Self: DataActorNative,
3027 Self: 'static + Debug + Sized,
3028 {
3029 let handler = self.response_handler();
3030
3031 DataActorCore::request_book_deltas(
3032 self.core_mut(),
3033 instrument_ids,
3034 start,
3035 end,
3036 limit,
3037 client_id,
3038 params,
3039 handler,
3040 )
3041 }
3042
3043 #[expect(clippy::too_many_arguments)]
3049 fn request_book_depth(
3050 &mut self,
3051 instrument_ids: impl IntoInstrumentIds,
3052 start: Option<Timestamp>,
3053 end: Option<Timestamp>,
3054 limit: Option<NonZeroUsize>,
3055 depth: Option<NonZeroUsize>,
3056 client_id: Option<ClientId>,
3057 params: Option<Params>,
3058 ) -> anyhow::Result<UUID4>
3059 where
3060 Self: DataActorNative,
3061 Self: 'static + Debug + Sized,
3062 {
3063 let handler = self.response_handler();
3064
3065 DataActorCore::request_book_depth(
3066 self.core_mut(),
3067 instrument_ids,
3068 start,
3069 end,
3070 limit,
3071 depth,
3072 client_id,
3073 params,
3074 handler,
3075 )
3076 }
3077
3078 fn request_quotes(
3084 &mut self,
3085 instrument_ids: impl IntoInstrumentIds,
3086 start: Option<Timestamp>,
3087 end: Option<Timestamp>,
3088 limit: Option<NonZeroUsize>,
3089 client_id: Option<ClientId>,
3090 params: Option<Params>,
3091 ) -> anyhow::Result<UUID4>
3092 where
3093 Self: DataActorNative,
3094 Self: 'static + Debug + Sized,
3095 {
3096 let handler = self.response_handler();
3097
3098 DataActorCore::request_quotes(
3099 self.core_mut(),
3100 instrument_ids,
3101 start,
3102 end,
3103 limit,
3104 client_id,
3105 params,
3106 handler,
3107 )
3108 }
3109
3110 fn request_trades(
3116 &mut self,
3117 instrument_ids: impl IntoInstrumentIds,
3118 start: Option<Timestamp>,
3119 end: Option<Timestamp>,
3120 limit: Option<NonZeroUsize>,
3121 client_id: Option<ClientId>,
3122 params: Option<Params>,
3123 ) -> anyhow::Result<UUID4>
3124 where
3125 Self: DataActorNative,
3126 Self: 'static + Debug + Sized,
3127 {
3128 let handler = self.response_handler();
3129
3130 DataActorCore::request_trades(
3131 self.core_mut(),
3132 instrument_ids,
3133 start,
3134 end,
3135 limit,
3136 client_id,
3137 params,
3138 handler,
3139 )
3140 }
3141
3142 fn request_bars(
3148 &mut self,
3149 bar_types: impl IntoBarTypes,
3150 start: Option<Timestamp>,
3151 end: Option<Timestamp>,
3152 limit: Option<NonZeroUsize>,
3153 client_id: Option<ClientId>,
3154 params: Option<Params>,
3155 ) -> anyhow::Result<UUID4>
3156 where
3157 Self: DataActorNative,
3158 Self: 'static + Debug + Sized,
3159 {
3160 let handler = self.response_handler();
3161
3162 DataActorCore::request_bars(
3163 self.core_mut(),
3164 bar_types,
3165 start,
3166 end,
3167 limit,
3168 client_id,
3169 params,
3170 handler,
3171 )
3172 }
3173
3174 fn request_funding_rates(
3180 &mut self,
3181 instrument_ids: impl IntoInstrumentIds,
3182 start: Option<Timestamp>,
3183 end: Option<Timestamp>,
3184 limit: Option<NonZeroUsize>,
3185 client_id: Option<ClientId>,
3186 params: Option<Params>,
3187 ) -> anyhow::Result<UUID4>
3188 where
3189 Self: DataActorNative,
3190 Self: 'static + Debug + Sized,
3191 {
3192 let handler = self.response_handler();
3193
3194 DataActorCore::request_funding_rates(
3195 self.core_mut(),
3196 instrument_ids,
3197 start,
3198 end,
3199 limit,
3200 client_id,
3201 params,
3202 handler,
3203 )
3204 }
3205
3206 fn request_group(
3228 &mut self,
3229 request_ids: Vec<UUID4>,
3230 request_id: Option<UUID4>,
3231 ) -> anyhow::Result<UUID4>
3232 where
3233 Self: DataActorNative,
3234 {
3235 self.core().check_registered();
3236 let id = self
3237 .core()
3238 .register_request_group(request_ids, request_id, None)?;
3239 self.drain_request_groups();
3240 Ok(id)
3241 }
3242
3243 #[cfg(feature = "live")]
3254 fn reconnect_socket(&self, client_id: ClientId, endpoint: &str) -> anyhow::Result<()>
3255 where
3256 Self: DataActorNative,
3257 {
3258 DataActorCore::reconnect_socket(self.core(), client_id, endpoint)
3259 }
3260}
3261
3262impl<T> Actor for T
3264where
3265 T: DataActor + DataActorNative + Debug + 'static,
3266{
3267 fn id(&self) -> Ustr {
3268 self.core().actor_id.inner()
3269 }
3270
3271 #[allow(unused_variables)]
3272 fn handle(&mut self, msg: &dyn Any) {
3273 }
3275
3276 fn as_any(&self) -> &dyn Any {
3277 self
3278 }
3279}
3280
3281impl<T> Component for T
3282where
3283 T: DataActor + DataActorNative + Debug + 'static,
3284{
3285 fn component_id(&self) -> ComponentId {
3286 ComponentId::from(self.core().actor_id)
3287 }
3288
3289 fn release_subscriptions(&mut self) {
3290 self.core().clear_pending_requests();
3291 self.core_mut().unsubscribe_all();
3292 }
3293
3294 fn state(&self) -> ComponentState {
3295 self.core().state
3296 }
3297
3298 fn transition_state(&mut self, trigger: ComponentTrigger) -> anyhow::Result<()> {
3299 let core = self.core_mut();
3300 core.state = core.state.transition(&trigger)?;
3301
3302 #[cfg(feature = "python")]
3303 if core.state == ComponentState::Disposed {
3304 core.message_bus.invalidate();
3305 }
3306
3307 log::info!(
3308 component = core.actor_id.inner().as_str();
3309 "{}",
3310 core.state.variant_name()
3311 );
3312 Ok(())
3313 }
3314
3315 fn register(
3316 &mut self,
3317 trader_id: TraderId,
3318 clock: Rc<RefCell<dyn Clock>>,
3319 cache: Rc<RefCell<Cache>>,
3320 ) -> anyhow::Result<()> {
3321 DataActorCore::register(self.core_mut(), trader_id, clock.clone(), cache)?;
3322
3323 let actor_id = self.core().actor_id().inner();
3325 let callback = TimeEventCallback::from(move |event: TimeEvent| {
3326 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
3327 actor.handle_time_event(&event);
3328 } else {
3329 log::error!("Actor {actor_id} not found for time event handling");
3330 }
3331 });
3332
3333 clock.borrow_mut().register_default_handler(callback);
3334
3335 self.initialize()
3336 }
3337
3338 fn on_start(&mut self) -> anyhow::Result<()> {
3339 DataActor::on_start(self)
3340 }
3341
3342 fn on_stop(&mut self) -> anyhow::Result<()> {
3343 DataActor::on_stop(self)
3344 }
3345
3346 fn on_resume(&mut self) -> anyhow::Result<()> {
3347 DataActor::on_resume(self)
3348 }
3349
3350 fn on_degrade(&mut self) -> anyhow::Result<()> {
3351 DataActor::on_degrade(self)
3352 }
3353
3354 fn on_fault(&mut self) -> anyhow::Result<()> {
3355 DataActor::on_fault(self)
3356 }
3357
3358 fn on_reset(&mut self) -> anyhow::Result<()> {
3359 DataActor::on_reset(self)
3360 }
3361
3362 fn on_dispose(&mut self) -> anyhow::Result<()> {
3363 DataActor::on_dispose(self)
3364 }
3365}
3366
3367#[derive(Clone)]
3369#[allow(dead_code, reason = "TODO: Under development (signal_classes)")]
3370pub struct DataActorCore {
3371 pub actor_id: ActorId,
3373 pub config: DataActorConfig,
3375 trader_id: Option<TraderId>,
3376 clock: Option<Rc<RefCell<dyn Clock>>>, cache: Option<Rc<RefCell<Cache>>>, state: ComponentState,
3379 topic_handlers: AHashMap<MStr<Pattern>, Subscription<ShareableMessageHandler>>,
3380 instrument_handlers: AHashMap<MStr<Pattern>, Subscription<TypedHandler<InstrumentAny>>>,
3381 deltas_handlers: AHashMap<MStr<Pattern>, Subscription<TypedHandler<OrderBookDeltas>>>,
3382 depth_handlers: AHashMap<MStr<Pattern>, Subscription<TypedHandler<OrderBookDepth>>>,
3383 book_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<OrderBook>>>,
3384 quote_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<QuoteTick>>>,
3385 trade_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<TradeTick>>>,
3386 bar_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<Bar>>>,
3387 mark_price_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<MarkPriceUpdate>>>,
3388 index_price_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<IndexPriceUpdate>>>,
3389 funding_rate_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<FundingRateUpdate>>>,
3390 option_greeks_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<OptionGreeks>>>,
3391 option_chain_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<OptionChainSlice>>>,
3392 indicators: Indicators,
3393 warning_events: AHashSet<String>, requests: RefCell<RequestLifecycle>,
3395 signal_classes: AHashMap<String, String>,
3396 #[cfg(feature = "defi")]
3397 block_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<Block>>>,
3398 #[cfg(feature = "defi")]
3399 pool_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<Pool>>>,
3400 #[cfg(feature = "defi")]
3401 pool_swap_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolSwap>>>,
3402 #[cfg(feature = "defi")]
3403 pool_liquidity_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolLiquidityUpdate>>>,
3404 #[cfg(feature = "defi")]
3405 pool_collect_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolFeeCollect>>>,
3406 #[cfg(feature = "defi")]
3407 pool_flash_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolFlash>>>,
3408 #[cfg(feature = "python")]
3409 message_bus: Rc<PyMessageBusScope>,
3410}
3411
3412#[derive(Clone)]
3413struct Subscription<T> {
3414 handler: T,
3415 command: Option<DataCommand>,
3416}
3417
3418impl Debug for DataActorCore {
3419 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3420 f.debug_struct(stringify!(DataActorCore))
3421 .field("actor_id", &self.actor_id)
3422 .field("config", &self.config)
3423 .field("state", &self.state)
3424 .field("trader_id", &self.trader_id)
3425 .finish()
3426 }
3427}
3428
3429impl DataActorCore {
3430 pub(crate) fn add_subscription_any(
3433 &mut self,
3434 topic: impl Into<MStr<Pattern>>,
3435 handler: ShareableMessageHandler,
3436 priority: Option<u32>,
3437 command: Option<DataCommand>,
3438 ) -> bool {
3439 let pattern: MStr<Pattern> = topic.into();
3440 if let Some(subscription) = self.topic_handlers.get_mut(&pattern) {
3441 if subscription.command.is_none() && command.is_some() {
3442 subscription.command = command;
3443 return true;
3444 }
3445
3446 log::warn!(
3447 "Actor {} attempted duplicate subscription to topic '{pattern}'",
3448 self.actor_id,
3449 );
3450 return false;
3451 }
3452
3453 self.topic_handlers.insert(
3454 pattern,
3455 Subscription {
3456 handler: handler.clone(),
3457 command,
3458 },
3459 );
3460 msgbus::subscribe_any(pattern, handler, priority);
3461 true
3462 }
3463
3464 pub(crate) fn remove_subscription_any(
3468 &mut self,
3469 topic: impl Into<MStr<Pattern>>,
3470 ) -> Option<DataCommand> {
3471 let pattern: MStr<Pattern> = topic.into();
3472 if let Some(subscription) = self.topic_handlers.remove(&pattern) {
3473 msgbus::unsubscribe_any(pattern, &subscription.handler);
3474 subscription.command
3475 } else {
3476 log::warn!(
3477 "Actor {} attempted to unsubscribe from topic '{pattern}' when not subscribed",
3478 self.actor_id,
3479 );
3480 None
3481 }
3482 }
3483
3484 pub(crate) fn add_quote_subscription(
3485 &mut self,
3486 topic: MStr<Topic>,
3487 handler: TypedHandler<QuoteTick>,
3488 command: DataCommand,
3489 ) -> bool {
3490 if self.quote_handlers.contains_key(&topic) {
3491 log::warn!(
3492 "Actor {} attempted duplicate quote subscription to '{topic}'",
3493 self.actor_id
3494 );
3495 return false;
3496 }
3497 self.quote_handlers.insert(
3498 topic,
3499 Subscription {
3500 handler: handler.clone(),
3501 command: Some(command),
3502 },
3503 );
3504 msgbus::subscribe_quotes(topic.into(), handler, None);
3505 true
3506 }
3507
3508 #[allow(dead_code)]
3509 pub(crate) fn remove_quote_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3510 let subscription = self.quote_handlers.remove(&topic)?;
3511 msgbus::unsubscribe_quotes(topic.into(), &subscription.handler);
3512 subscription.command
3513 }
3514
3515 pub(crate) fn add_trade_subscription(
3516 &mut self,
3517 topic: MStr<Topic>,
3518 handler: TypedHandler<TradeTick>,
3519 command: DataCommand,
3520 ) -> bool {
3521 if self.trade_handlers.contains_key(&topic) {
3522 log::warn!(
3523 "Actor {} attempted duplicate trade subscription to '{topic}'",
3524 self.actor_id
3525 );
3526 return false;
3527 }
3528 self.trade_handlers.insert(
3529 topic,
3530 Subscription {
3531 handler: handler.clone(),
3532 command: Some(command),
3533 },
3534 );
3535 msgbus::subscribe_trades(topic.into(), handler, None);
3536 true
3537 }
3538
3539 #[allow(dead_code)]
3540 pub(crate) fn remove_trade_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3541 let subscription = self.trade_handlers.remove(&topic)?;
3542 msgbus::unsubscribe_trades(topic.into(), &subscription.handler);
3543 subscription.command
3544 }
3545
3546 pub(crate) fn add_bar_subscription(
3547 &mut self,
3548 topic: MStr<Topic>,
3549 handler: TypedHandler<Bar>,
3550 command: DataCommand,
3551 ) -> bool {
3552 if self.bar_handlers.contains_key(&topic) {
3553 return false;
3554 }
3555
3556 self.bar_handlers.insert(
3557 topic,
3558 Subscription {
3559 handler: handler.clone(),
3560 command: Some(command),
3561 },
3562 );
3563 msgbus::subscribe_bars(topic.into(), handler, None);
3564 true
3565 }
3566
3567 fn remove_bar_subscription(&mut self, bar_type: BarType) -> Option<DataCommand> {
3568 let topic = get_bars_topic(bar_type.standard());
3569 let subscription = self.bar_handlers.remove(&topic)?;
3570 msgbus::unsubscribe_bars(topic.into(), &subscription.handler);
3571 subscription.command
3572 }
3573
3574 pub(crate) fn add_deltas_subscription(
3575 &mut self,
3576 pattern: MStr<Pattern>,
3577 handler: TypedHandler<OrderBookDeltas>,
3578 command: DataCommand,
3579 ) -> bool {
3580 if self.deltas_handlers.contains_key(&pattern) {
3581 log::warn!(
3582 "Actor {} attempted duplicate deltas subscription to '{pattern}'",
3583 self.actor_id
3584 );
3585 return false;
3586 }
3587 self.deltas_handlers.insert(
3588 pattern,
3589 Subscription {
3590 handler: handler.clone(),
3591 command: Some(command),
3592 },
3593 );
3594 msgbus::subscribe_book_deltas(pattern, handler, None);
3595 true
3596 }
3597
3598 #[allow(dead_code)]
3599 pub(crate) fn remove_deltas_subscription(
3600 &mut self,
3601 pattern: MStr<Pattern>,
3602 ) -> Option<DataCommand> {
3603 let subscription = self.deltas_handlers.remove(&pattern)?;
3604 msgbus::unsubscribe_book_deltas(pattern, &subscription.handler);
3605 subscription.command
3606 }
3607
3608 pub(crate) fn add_depth_subscription(
3609 &mut self,
3610 pattern: MStr<Pattern>,
3611 handler: TypedHandler<OrderBookDepth>,
3612 command: DataCommand,
3613 ) -> bool {
3614 if self.depth_handlers.contains_key(&pattern) {
3615 log::warn!(
3616 "Actor {} attempted duplicate depth subscription to '{pattern}'",
3617 self.actor_id
3618 );
3619 return false;
3620 }
3621 self.depth_handlers.insert(
3622 pattern,
3623 Subscription {
3624 handler: handler.clone(),
3625 command: Some(command),
3626 },
3627 );
3628 msgbus::subscribe_book_depth(pattern, handler, None);
3629 true
3630 }
3631
3632 pub(crate) fn remove_depth_subscription(
3633 &mut self,
3634 pattern: MStr<Pattern>,
3635 ) -> Option<DataCommand> {
3636 let subscription = self.depth_handlers.remove(&pattern)?;
3637 msgbus::unsubscribe_book_depth(pattern, &subscription.handler);
3638 subscription.command
3639 }
3640
3641 pub(crate) fn add_instrument_subscription(
3642 &mut self,
3643 pattern: MStr<Pattern>,
3644 handler: TypedHandler<InstrumentAny>,
3645 command: DataCommand,
3646 ) -> bool {
3647 if self.instrument_handlers.contains_key(&pattern) {
3648 log::warn!(
3649 "Actor {} attempted duplicate instrument subscription to '{pattern}'",
3650 self.actor_id
3651 );
3652 return false;
3653 }
3654 self.instrument_handlers.insert(
3655 pattern,
3656 Subscription {
3657 handler: handler.clone(),
3658 command: Some(command),
3659 },
3660 );
3661 msgbus::subscribe_instruments(pattern, handler, None);
3662 true
3663 }
3664
3665 #[allow(dead_code)]
3666 pub(crate) fn remove_instrument_subscription(
3667 &mut self,
3668 pattern: MStr<Pattern>,
3669 ) -> Option<DataCommand> {
3670 let subscription = self.instrument_handlers.remove(&pattern)?;
3671 msgbus::unsubscribe_instruments(pattern, &subscription.handler);
3672 subscription.command
3673 }
3674
3675 pub(crate) fn add_instrument_close_subscription(
3676 &mut self,
3677 topic: MStr<Topic>,
3678 handler: ShareableMessageHandler,
3679 command: DataCommand,
3680 ) -> bool {
3681 let pattern: MStr<Pattern> = topic.into();
3682 if self.topic_handlers.contains_key(&pattern) {
3683 log::warn!(
3684 "Actor {} attempted duplicate instrument close subscription to '{topic}'",
3685 self.actor_id
3686 );
3687 return false;
3688 }
3689 self.topic_handlers.insert(
3690 pattern,
3691 Subscription {
3692 handler: handler.clone(),
3693 command: Some(command),
3694 },
3695 );
3696 msgbus::subscribe_any(pattern, handler, None);
3697 true
3698 }
3699
3700 #[allow(dead_code)]
3701 pub(crate) fn remove_instrument_close_subscription(
3702 &mut self,
3703 topic: MStr<Topic>,
3704 ) -> Option<DataCommand> {
3705 let pattern: MStr<Pattern> = topic.into();
3706 let subscription = self.topic_handlers.remove(&pattern)?;
3707 msgbus::unsubscribe_any(pattern, &subscription.handler);
3708 subscription.command
3709 }
3710
3711 pub(crate) fn add_book_snapshot_subscription(
3712 &mut self,
3713 topic: MStr<Topic>,
3714 handler: TypedHandler<OrderBook>,
3715 command: DataCommand,
3716 ) -> bool {
3717 if self.book_handlers.contains_key(&topic) {
3718 log::warn!(
3719 "Actor {} attempted duplicate book snapshot subscription to '{topic}'",
3720 self.actor_id
3721 );
3722 return false;
3723 }
3724 self.book_handlers.insert(
3725 topic,
3726 Subscription {
3727 handler: handler.clone(),
3728 command: Some(command),
3729 },
3730 );
3731 msgbus::subscribe_book_snapshots(topic.into(), handler, None);
3732 true
3733 }
3734
3735 #[allow(dead_code)]
3736 pub(crate) fn remove_book_snapshot_subscription(
3737 &mut self,
3738 topic: MStr<Topic>,
3739 ) -> Option<DataCommand> {
3740 let subscription = self.book_handlers.remove(&topic)?;
3741 msgbus::unsubscribe_book_snapshots(topic.into(), &subscription.handler);
3742 subscription.command
3743 }
3744
3745 pub(crate) fn add_mark_price_subscription(
3746 &mut self,
3747 topic: MStr<Topic>,
3748 handler: TypedHandler<MarkPriceUpdate>,
3749 command: DataCommand,
3750 ) -> bool {
3751 if self.mark_price_handlers.contains_key(&topic) {
3752 log::warn!(
3753 "Actor {} attempted duplicate mark price subscription to '{topic}'",
3754 self.actor_id
3755 );
3756 return false;
3757 }
3758 self.mark_price_handlers.insert(
3759 topic,
3760 Subscription {
3761 handler: handler.clone(),
3762 command: Some(command),
3763 },
3764 );
3765 msgbus::subscribe_mark_prices(topic.into(), handler, None);
3766 true
3767 }
3768
3769 #[allow(dead_code)]
3770 pub(crate) fn remove_mark_price_subscription(
3771 &mut self,
3772 topic: MStr<Topic>,
3773 ) -> Option<DataCommand> {
3774 let subscription = self.mark_price_handlers.remove(&topic)?;
3775 msgbus::unsubscribe_mark_prices(topic.into(), &subscription.handler);
3776 subscription.command
3777 }
3778
3779 pub(crate) fn add_index_price_subscription(
3780 &mut self,
3781 topic: MStr<Topic>,
3782 handler: TypedHandler<IndexPriceUpdate>,
3783 command: DataCommand,
3784 ) -> bool {
3785 if self.index_price_handlers.contains_key(&topic) {
3786 log::warn!(
3787 "Actor {} attempted duplicate index price subscription to '{topic}'",
3788 self.actor_id
3789 );
3790 return false;
3791 }
3792 self.index_price_handlers.insert(
3793 topic,
3794 Subscription {
3795 handler: handler.clone(),
3796 command: Some(command),
3797 },
3798 );
3799 msgbus::subscribe_index_prices(topic.into(), handler, None);
3800 true
3801 }
3802
3803 #[allow(dead_code)]
3804 pub(crate) fn remove_index_price_subscription(
3805 &mut self,
3806 topic: MStr<Topic>,
3807 ) -> Option<DataCommand> {
3808 let subscription = self.index_price_handlers.remove(&topic)?;
3809 msgbus::unsubscribe_index_prices(topic.into(), &subscription.handler);
3810 subscription.command
3811 }
3812
3813 pub(crate) fn add_funding_rate_subscription(
3814 &mut self,
3815 topic: MStr<Topic>,
3816 handler: TypedHandler<FundingRateUpdate>,
3817 command: DataCommand,
3818 ) -> bool {
3819 if self.funding_rate_handlers.contains_key(&topic) {
3820 log::warn!(
3821 "Actor {} attempted duplicate funding rate subscription to '{topic}'",
3822 self.actor_id
3823 );
3824 return false;
3825 }
3826 self.funding_rate_handlers.insert(
3827 topic,
3828 Subscription {
3829 handler: handler.clone(),
3830 command: Some(command),
3831 },
3832 );
3833 msgbus::subscribe_funding_rates(topic.into(), handler, None);
3834 true
3835 }
3836
3837 #[allow(dead_code)]
3838 pub(crate) fn remove_funding_rate_subscription(
3839 &mut self,
3840 topic: MStr<Topic>,
3841 ) -> Option<DataCommand> {
3842 let subscription = self.funding_rate_handlers.remove(&topic)?;
3843 msgbus::unsubscribe_funding_rates(topic.into(), &subscription.handler);
3844 subscription.command
3845 }
3846
3847 pub(crate) fn add_option_greeks_subscription(
3848 &mut self,
3849 topic: MStr<Topic>,
3850 handler: TypedHandler<OptionGreeks>,
3851 command: DataCommand,
3852 ) -> bool {
3853 if self.option_greeks_handlers.contains_key(&topic) {
3854 log::warn!(
3855 "Actor {} attempted duplicate option greeks subscription to '{topic}'",
3856 self.actor_id
3857 );
3858 return false;
3859 }
3860 self.option_greeks_handlers.insert(
3861 topic,
3862 Subscription {
3863 handler: handler.clone(),
3864 command: Some(command),
3865 },
3866 );
3867 msgbus::subscribe_option_greeks(topic.into(), handler, None);
3868 true
3869 }
3870
3871 #[allow(dead_code)]
3872 pub(crate) fn remove_option_greeks_subscription(
3873 &mut self,
3874 topic: MStr<Topic>,
3875 ) -> Option<DataCommand> {
3876 let subscription = self.option_greeks_handlers.remove(&topic)?;
3877 msgbus::unsubscribe_option_greeks(topic.into(), &subscription.handler);
3878 subscription.command
3879 }
3880
3881 pub(crate) fn set_option_chain_subscription(
3882 &mut self,
3883 topic: MStr<Topic>,
3884 handler: TypedHandler<OptionChainSlice>,
3885 command: DataCommand,
3886 ) {
3887 if let Some(subscription) = self.option_chain_handlers.get_mut(&topic) {
3888 subscription.command = Some(command);
3889 return;
3890 }
3891
3892 self.option_chain_handlers.insert(
3893 topic,
3894 Subscription {
3895 handler: handler.clone(),
3896 command: Some(command),
3897 },
3898 );
3899 msgbus::subscribe_option_chain(topic.into(), handler, None);
3900 }
3901
3902 pub(crate) fn remove_option_chain_subscription(
3903 &mut self,
3904 topic: MStr<Topic>,
3905 ) -> Option<DataCommand> {
3906 let subscription = self.option_chain_handlers.remove(&topic)?;
3907 msgbus::unsubscribe_option_chain(topic.into(), &subscription.handler);
3908 subscription.command
3909 }
3910
3911 #[cfg(feature = "defi")]
3912 pub(crate) fn add_block_subscription(
3913 &mut self,
3914 topic: MStr<Topic>,
3915 handler: TypedHandler<Block>,
3916 command: DataCommand,
3917 ) -> bool {
3918 if self.block_handlers.contains_key(&topic) {
3919 log::warn!(
3920 "Actor {} attempted duplicate block subscription to '{topic}'",
3921 self.actor_id
3922 );
3923 return false;
3924 }
3925 self.block_handlers.insert(
3926 topic,
3927 Subscription {
3928 handler: handler.clone(),
3929 command: Some(command),
3930 },
3931 );
3932 msgbus::subscribe_defi_blocks(topic.into(), handler, None);
3933 true
3934 }
3935
3936 #[cfg(feature = "defi")]
3937 #[allow(dead_code)]
3938 pub(crate) fn remove_block_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3939 let subscription = self.block_handlers.remove(&topic)?;
3940 msgbus::unsubscribe_defi_blocks(topic.into(), &subscription.handler);
3941 subscription.command
3942 }
3943
3944 #[cfg(feature = "defi")]
3945 pub(crate) fn add_pool_subscription(
3946 &mut self,
3947 topic: MStr<Topic>,
3948 handler: TypedHandler<Pool>,
3949 command: DataCommand,
3950 ) -> bool {
3951 if self.pool_handlers.contains_key(&topic) {
3952 log::warn!(
3953 "Actor {} attempted duplicate pool subscription to '{topic}'",
3954 self.actor_id
3955 );
3956 return false;
3957 }
3958 self.pool_handlers.insert(
3959 topic,
3960 Subscription {
3961 handler: handler.clone(),
3962 command: Some(command),
3963 },
3964 );
3965 msgbus::subscribe_defi_pools(topic.into(), handler, None);
3966 true
3967 }
3968
3969 #[cfg(feature = "defi")]
3970 #[allow(dead_code)]
3971 pub(crate) fn remove_pool_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3972 let subscription = self.pool_handlers.remove(&topic)?;
3973 msgbus::unsubscribe_defi_pools(topic.into(), &subscription.handler);
3974 subscription.command
3975 }
3976
3977 #[cfg(feature = "defi")]
3978 pub(crate) fn add_pool_swap_subscription(
3979 &mut self,
3980 topic: MStr<Topic>,
3981 handler: TypedHandler<PoolSwap>,
3982 command: DataCommand,
3983 ) -> bool {
3984 if self.pool_swap_handlers.contains_key(&topic) {
3985 log::warn!(
3986 "Actor {} attempted duplicate pool swap subscription to '{topic}'",
3987 self.actor_id
3988 );
3989 return false;
3990 }
3991 self.pool_swap_handlers.insert(
3992 topic,
3993 Subscription {
3994 handler: handler.clone(),
3995 command: Some(command),
3996 },
3997 );
3998 msgbus::subscribe_defi_swaps(topic.into(), handler, None);
3999 true
4000 }
4001
4002 #[cfg(feature = "defi")]
4003 #[allow(dead_code)]
4004 pub(crate) fn remove_pool_swap_subscription(
4005 &mut self,
4006 topic: MStr<Topic>,
4007 ) -> Option<DataCommand> {
4008 let subscription = self.pool_swap_handlers.remove(&topic)?;
4009 msgbus::unsubscribe_defi_swaps(topic.into(), &subscription.handler);
4010 subscription.command
4011 }
4012
4013 #[cfg(feature = "defi")]
4014 pub(crate) fn add_pool_liquidity_subscription(
4015 &mut self,
4016 topic: MStr<Topic>,
4017 handler: TypedHandler<PoolLiquidityUpdate>,
4018 command: DataCommand,
4019 ) -> bool {
4020 if self.pool_liquidity_handlers.contains_key(&topic) {
4021 log::warn!(
4022 "Actor {} attempted duplicate pool liquidity subscription to '{topic}'",
4023 self.actor_id
4024 );
4025 return false;
4026 }
4027 self.pool_liquidity_handlers.insert(
4028 topic,
4029 Subscription {
4030 handler: handler.clone(),
4031 command: Some(command),
4032 },
4033 );
4034 msgbus::subscribe_defi_liquidity(topic.into(), handler, None);
4035 true
4036 }
4037
4038 #[cfg(feature = "defi")]
4039 #[allow(dead_code)]
4040 pub(crate) fn remove_pool_liquidity_subscription(
4041 &mut self,
4042 topic: MStr<Topic>,
4043 ) -> Option<DataCommand> {
4044 let subscription = self.pool_liquidity_handlers.remove(&topic)?;
4045 msgbus::unsubscribe_defi_liquidity(topic.into(), &subscription.handler);
4046 subscription.command
4047 }
4048
4049 #[cfg(feature = "defi")]
4050 pub(crate) fn add_pool_collect_subscription(
4051 &mut self,
4052 topic: MStr<Topic>,
4053 handler: TypedHandler<PoolFeeCollect>,
4054 command: DataCommand,
4055 ) -> bool {
4056 if self.pool_collect_handlers.contains_key(&topic) {
4057 log::warn!(
4058 "Actor {} attempted duplicate pool collect subscription to '{topic}'",
4059 self.actor_id
4060 );
4061 return false;
4062 }
4063 self.pool_collect_handlers.insert(
4064 topic,
4065 Subscription {
4066 handler: handler.clone(),
4067 command: Some(command),
4068 },
4069 );
4070 msgbus::subscribe_defi_collects(topic.into(), handler, None);
4071 true
4072 }
4073
4074 #[cfg(feature = "defi")]
4075 #[allow(dead_code)]
4076 pub(crate) fn remove_pool_collect_subscription(
4077 &mut self,
4078 topic: MStr<Topic>,
4079 ) -> Option<DataCommand> {
4080 let subscription = self.pool_collect_handlers.remove(&topic)?;
4081 msgbus::unsubscribe_defi_collects(topic.into(), &subscription.handler);
4082 subscription.command
4083 }
4084
4085 #[cfg(feature = "defi")]
4086 pub(crate) fn add_pool_flash_subscription(
4087 &mut self,
4088 topic: MStr<Topic>,
4089 handler: TypedHandler<PoolFlash>,
4090 command: DataCommand,
4091 ) -> bool {
4092 if self.pool_flash_handlers.contains_key(&topic) {
4093 log::warn!(
4094 "Actor {} attempted duplicate pool flash subscription to '{topic}'",
4095 self.actor_id
4096 );
4097 return false;
4098 }
4099 self.pool_flash_handlers.insert(
4100 topic,
4101 Subscription {
4102 handler: handler.clone(),
4103 command: Some(command),
4104 },
4105 );
4106 msgbus::subscribe_defi_flash(topic.into(), handler, None);
4107 true
4108 }
4109
4110 #[cfg(feature = "defi")]
4111 #[allow(dead_code)]
4112 pub(crate) fn remove_pool_flash_subscription(
4113 &mut self,
4114 topic: MStr<Topic>,
4115 ) -> Option<DataCommand> {
4116 let subscription = self.pool_flash_handlers.remove(&topic)?;
4117 msgbus::unsubscribe_defi_flash(topic.into(), &subscription.handler);
4118 subscription.command
4119 }
4120
4121 pub(crate) fn unsubscribe_all(&mut self) {
4126 let mut commands = Vec::new();
4127
4128 Self::drain_subscriptions(
4129 std::mem::take(&mut self.topic_handlers),
4130 &mut commands,
4131 msgbus::unsubscribe_any,
4132 );
4133 Self::drain_subscriptions(
4134 std::mem::take(&mut self.instrument_handlers),
4135 &mut commands,
4136 msgbus::unsubscribe_instruments,
4137 );
4138 Self::drain_subscriptions(
4139 std::mem::take(&mut self.deltas_handlers),
4140 &mut commands,
4141 msgbus::unsubscribe_book_deltas,
4142 );
4143 Self::drain_subscriptions(
4144 std::mem::take(&mut self.depth_handlers),
4145 &mut commands,
4146 msgbus::unsubscribe_book_depth,
4147 );
4148 Self::drain_subscriptions(
4149 std::mem::take(&mut self.book_handlers),
4150 &mut commands,
4151 |topic, handler| msgbus::unsubscribe_book_snapshots(topic.into(), handler),
4152 );
4153 Self::drain_subscriptions(
4154 std::mem::take(&mut self.quote_handlers),
4155 &mut commands,
4156 |topic, handler| msgbus::unsubscribe_quotes(topic.into(), handler),
4157 );
4158 Self::drain_subscriptions(
4159 std::mem::take(&mut self.trade_handlers),
4160 &mut commands,
4161 |topic, handler| msgbus::unsubscribe_trades(topic.into(), handler),
4162 );
4163 Self::drain_subscriptions(
4164 std::mem::take(&mut self.bar_handlers),
4165 &mut commands,
4166 |topic, handler| msgbus::unsubscribe_bars(topic.into(), handler),
4167 );
4168 Self::drain_subscriptions(
4169 std::mem::take(&mut self.mark_price_handlers),
4170 &mut commands,
4171 |topic, handler| msgbus::unsubscribe_mark_prices(topic.into(), handler),
4172 );
4173 Self::drain_subscriptions(
4174 std::mem::take(&mut self.index_price_handlers),
4175 &mut commands,
4176 |topic, handler| msgbus::unsubscribe_index_prices(topic.into(), handler),
4177 );
4178 Self::drain_subscriptions(
4179 std::mem::take(&mut self.funding_rate_handlers),
4180 &mut commands,
4181 |topic, handler| msgbus::unsubscribe_funding_rates(topic.into(), handler),
4182 );
4183 Self::drain_subscriptions(
4184 std::mem::take(&mut self.option_greeks_handlers),
4185 &mut commands,
4186 |topic, handler| msgbus::unsubscribe_option_greeks(topic.into(), handler),
4187 );
4188 Self::drain_subscriptions(
4189 std::mem::take(&mut self.option_chain_handlers),
4190 &mut commands,
4191 |topic, handler| msgbus::unsubscribe_option_chain(topic.into(), handler),
4192 );
4193
4194 #[cfg(feature = "defi")]
4195 self.unsubscribe_all_defi(&mut commands);
4196
4197 let mut retained = Vec::new();
4198 commands.retain(|command| {
4199 if matches!(command, DataCommand::Subscribe(subscribe)
4200 if subscribe.instrument_ids().is_some() || subscribe.bar_types().is_some())
4201 {
4202 retained.push(command.clone());
4203 false
4204 } else {
4205 true
4206 }
4207 });
4208
4209 self.release_subscriptions(retained);
4210
4211 for command in commands {
4212 if let Some(command) = command.into_unsubscribe(UUID4::new(), self.timestamp_ns()) {
4213 self.send_data_cmd(command);
4214 }
4215 }
4216 #[cfg(feature = "python")]
4217 self.message_bus.clear();
4218 }
4219
4220 #[cfg(feature = "defi")]
4221 fn unsubscribe_all_defi(&mut self, commands: &mut Vec<DataCommand>) {
4222 Self::drain_subscriptions(
4223 std::mem::take(&mut self.block_handlers),
4224 commands,
4225 |topic, handler| msgbus::unsubscribe_defi_blocks(topic.into(), handler),
4226 );
4227 Self::drain_subscriptions(
4228 std::mem::take(&mut self.pool_handlers),
4229 commands,
4230 |topic, handler| msgbus::unsubscribe_defi_pools(topic.into(), handler),
4231 );
4232 Self::drain_subscriptions(
4233 std::mem::take(&mut self.pool_swap_handlers),
4234 commands,
4235 |topic, handler| msgbus::unsubscribe_defi_swaps(topic.into(), handler),
4236 );
4237 Self::drain_subscriptions(
4238 std::mem::take(&mut self.pool_liquidity_handlers),
4239 commands,
4240 |topic, handler| msgbus::unsubscribe_defi_liquidity(topic.into(), handler),
4241 );
4242 Self::drain_subscriptions(
4243 std::mem::take(&mut self.pool_collect_handlers),
4244 commands,
4245 |topic, handler| msgbus::unsubscribe_defi_collects(topic.into(), handler),
4246 );
4247 Self::drain_subscriptions(
4248 std::mem::take(&mut self.pool_flash_handlers),
4249 commands,
4250 |topic, handler| msgbus::unsubscribe_defi_flash(topic.into(), handler),
4251 );
4252 }
4253
4254 fn drain_subscriptions<K, T>(
4255 subscriptions: AHashMap<K, Subscription<T>>,
4256 commands: &mut Vec<DataCommand>,
4257 mut unsubscribe: impl FnMut(K, &T),
4258 ) where
4259 K: AsRef<str>,
4260 {
4261 let mut subscriptions = subscriptions.into_iter().collect::<Vec<_>>();
4262 subscriptions.sort_unstable_by(|(left, _), (right, _)| left.as_ref().cmp(right.as_ref()));
4263
4264 for (key, subscription) in subscriptions {
4265 unsubscribe(key, &subscription.handler);
4266 commands.extend(subscription.command);
4267 }
4268 }
4269
4270 pub fn new(config: DataActorConfig) -> Self {
4272 let actor_id = config.actor_id.unwrap_or_else(Self::default_actor_id);
4273
4274 Self {
4275 actor_id,
4276 config,
4277 trader_id: None, clock: None, cache: None, state: ComponentState::default(),
4281 topic_handlers: AHashMap::new(),
4282 instrument_handlers: AHashMap::new(),
4283 deltas_handlers: AHashMap::new(),
4284 depth_handlers: AHashMap::new(),
4285 book_handlers: AHashMap::new(),
4286 quote_handlers: AHashMap::new(),
4287 trade_handlers: AHashMap::new(),
4288 bar_handlers: AHashMap::new(),
4289 mark_price_handlers: AHashMap::new(),
4290 index_price_handlers: AHashMap::new(),
4291 funding_rate_handlers: AHashMap::new(),
4292 option_greeks_handlers: AHashMap::new(),
4293 option_chain_handlers: AHashMap::new(),
4294 indicators: Indicators::default(),
4295 warning_events: AHashSet::new(),
4296 requests: RefCell::new(RequestLifecycle::default()),
4297 signal_classes: AHashMap::new(),
4298 #[cfg(feature = "defi")]
4299 block_handlers: AHashMap::new(),
4300 #[cfg(feature = "defi")]
4301 pool_handlers: AHashMap::new(),
4302 #[cfg(feature = "defi")]
4303 pool_swap_handlers: AHashMap::new(),
4304 #[cfg(feature = "defi")]
4305 pool_liquidity_handlers: AHashMap::new(),
4306 #[cfg(feature = "defi")]
4307 pool_collect_handlers: AHashMap::new(),
4308 #[cfg(feature = "defi")]
4309 pool_flash_handlers: AHashMap::new(),
4310 #[cfg(feature = "python")]
4311 message_bus: Rc::default(),
4312 }
4313 }
4314
4315 #[must_use]
4317 pub fn registered_indicators(&self) -> Vec<SharedActorIndicator> {
4318 self.indicators.registered_indicators()
4319 }
4320
4321 pub fn indicators_initialized(&self) -> anyhow::Result<bool> {
4327 self.indicators.initialized()
4328 }
4329
4330 pub fn register_indicator_for_quote_ticks(
4332 &mut self,
4333 instrument_id: InstrumentId,
4334 indicator: SharedActorIndicator,
4335 ) {
4336 self.indicators
4337 .register_indicator_for_quote_ticks(instrument_id, indicator);
4338 }
4339
4340 pub fn register_indicator_for_trade_ticks(
4342 &mut self,
4343 instrument_id: InstrumentId,
4344 indicator: SharedActorIndicator,
4345 ) {
4346 self.indicators
4347 .register_indicator_for_trade_ticks(instrument_id, indicator);
4348 }
4349
4350 pub fn register_indicator_for_bars(
4352 &mut self,
4353 bar_type: BarType,
4354 indicator: SharedActorIndicator,
4355 ) {
4356 self.indicators
4357 .register_indicator_for_bars(bar_type, indicator);
4358 }
4359
4360 pub(crate) fn handle_indicators_for_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
4361 self.indicators.handle_quote(quote)
4362 }
4363
4364 pub(crate) fn handle_indicators_for_quotes(&self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
4365 self.indicators.handle_quotes(quotes)
4366 }
4367
4368 pub(crate) fn handle_indicators_for_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
4369 self.indicators.handle_trade(trade)
4370 }
4371
4372 pub(crate) fn handle_indicators_for_trades(&self, trades: &[TradeTick]) -> anyhow::Result<()> {
4373 self.indicators.handle_trades(trades)
4374 }
4375
4376 pub(crate) fn handle_indicators_for_bar(&self, bar: &Bar) -> anyhow::Result<()> {
4377 self.indicators.handle_bar(bar)
4378 }
4379
4380 pub(crate) fn handle_indicators_for_bars(&self, bars: &[Bar]) -> anyhow::Result<()> {
4381 self.indicators.handle_bars(bars)
4382 }
4383
4384 #[must_use]
4386 pub fn mem_address(&self) -> String {
4387 format!("{self:p}")
4388 }
4389
4390 pub fn state(&self) -> ComponentState {
4392 self.state
4393 }
4394
4395 pub fn trader_id(&self) -> Option<TraderId> {
4397 self.trader_id
4398 }
4399
4400 pub fn actor_id(&self) -> ActorId {
4402 self.actor_id
4403 }
4404
4405 fn default_actor_id() -> ActorId {
4406 ActorId::from(stringify!(DataActor))
4407 }
4408
4409 pub fn timestamp_ns(&self) -> UnixNanos {
4411 self.clock_ref().timestamp_ns()
4412 }
4413
4414 pub(super) fn clock_api(&self) -> ClockApi<'_> {
4415 let clock = self.clock.as_ref().unwrap_or_else(|| {
4416 panic!(
4417 "DataActor {} must be registered before calling `clock()` - trader_id: {:?}",
4418 self.actor_id, self.trader_id
4419 )
4420 });
4421 ClockApi::new(clock.as_ref())
4422 }
4423
4424 fn clock_ref(&self) -> Ref<'_, dyn Clock> {
4425 self.clock
4426 .as_ref()
4427 .unwrap_or_else(|| {
4428 panic!(
4429 "DataActor {} must be registered before calling `clock_ref()` - trader_id: {:?}",
4430 self.actor_id, self.trader_id
4431 )
4432 })
4433 .borrow()
4434 }
4435
4436 fn cache_api(&self) -> CacheApi<'_> {
4437 let cache = self.cache.as_ref().unwrap_or_else(|| {
4438 panic!(
4439 "DataActor {} must be registered before calling `cache()` - trader_id: {:?}",
4440 self.actor_id, self.trader_id
4441 )
4442 });
4443 CacheApi::new(cache.as_ref())
4444 }
4445
4446 pub fn register(
4453 &mut self,
4454 trader_id: TraderId,
4455 clock: Rc<RefCell<dyn Clock>>,
4456 cache: Rc<RefCell<Cache>>,
4457 ) -> anyhow::Result<()> {
4458 if let Some(existing_trader_id) = self.trader_id {
4459 anyhow::bail!(
4460 "DataActor {} already registered with trader {existing_trader_id}",
4461 self.actor_id
4462 );
4463 }
4464
4465 {
4467 let _timestamp = clock.borrow().timestamp_ns();
4468 }
4469
4470 {
4472 let _cache_borrow = cache.borrow();
4473 }
4474
4475 #[cfg(feature = "python")]
4476 self.message_bus.register();
4477
4478 self.trader_id = Some(trader_id);
4479 self.clock = Some(clock);
4480 self.cache = Some(cache);
4481
4482 if !self.is_properly_registered() {
4484 anyhow::bail!(
4485 "DataActor {} registration incomplete - validation failed",
4486 self.actor_id
4487 );
4488 }
4489
4490 log::debug!("Registered {} with trader {trader_id}", self.actor_id);
4491 Ok(())
4492 }
4493
4494 pub fn register_warning_event(&mut self, event_type: &str) {
4496 self.warning_events.insert(event_type.to_string());
4497 log::debug!("Registered event type '{event_type}' for warning logs");
4498 }
4499
4500 pub fn deregister_warning_event(&mut self, event_type: &str) {
4502 self.warning_events.remove(event_type);
4503 log::debug!("Deregistered event type '{event_type}' from warning logs");
4504 }
4505
4506 #[cfg(feature = "python")]
4508 pub fn message_bus(&self) -> Rc<PyMessageBusScope> {
4509 Rc::clone(&self.message_bus)
4510 }
4511
4512 pub fn is_registered(&self) -> bool {
4513 self.trader_id.is_some()
4514 }
4515
4516 pub(crate) fn check_registered(&self) {
4517 assert!(
4518 self.is_registered(),
4519 "Actor has not been registered with a Trader"
4520 );
4521 }
4522
4523 fn is_properly_registered(&self) -> bool {
4525 self.trader_id.is_some() && self.clock.is_some() && self.cache.is_some()
4526 }
4527
4528 pub(crate) fn send_data_cmd(&self, command: DataCommand) {
4529 if self.config.log_commands {
4530 log::info!("{CMD}{SEND} {command:?}");
4531 }
4532
4533 let endpoint = MessagingSwitchboard::data_engine_queue_execute();
4534 msgbus::send_data_command(endpoint, command);
4535 }
4536
4537 pub(crate) fn send_unsubscribe_cmd(
4538 &self,
4539 retained: Option<DataCommand>,
4540 fallback: DataCommand,
4541 ) {
4542 let Some(retained) = retained else {
4543 return;
4544 };
4545 let command = retained
4546 .into_unsubscribe(UUID4::new(), self.timestamp_ns())
4547 .unwrap_or(fallback);
4548 self.send_data_cmd(command);
4549 }
4550
4551 #[allow(dead_code)]
4552 fn send_data_req(&self, request: &RequestCommand) {
4553 if self.config.log_commands {
4554 log::info!("{REQ}{SEND} {request:?}");
4555 }
4556
4557 let endpoint = MessagingSwitchboard::data_engine_queue_execute();
4560 msgbus::send_any(endpoint, request.as_any());
4561 }
4562
4563 pub fn shutdown_system(&self, reason: Option<String>) {
4569 self.check_registered();
4570
4571 let command = ShutdownSystem::new(
4573 self.trader_id().unwrap(),
4574 self.actor_id.inner(),
4575 reason,
4576 UUID4::new(),
4577 self.timestamp_ns(),
4578 None, );
4580
4581 let topic = MessagingSwitchboard::shutdown_system_topic();
4582 msgbus::publish_any(topic, command.as_any());
4583 }
4584
4585 pub fn publish_data(&self, data_type: &DataType, data: &CustomData) {
4595 self.check_registered();
4596
4597 let topic = get_custom_topic(data_type);
4598 msgbus::publish_any(topic, data);
4599 }
4600
4601 pub fn publish_signal(&self, name: &str, value: String, ts_event: UnixNanos) {
4613 self.check_registered();
4614
4615 let now = self.timestamp_ns();
4616 let ts_event = if ts_event.as_u64() == 0 {
4617 now
4618 } else {
4619 ts_event
4620 };
4621 let signal = Signal::new(Ustr::from(name), value, ts_event, now);
4622
4623 let data_type = DataType::new(
4624 &format!(
4625 "Signal{}",
4626 nautilus_core::string::conversions::title_case(name)
4627 ),
4628 None,
4629 None,
4630 );
4631 let data = CustomData::new(Arc::new(signal), data_type);
4632 let topic = get_custom_topic(&data.data_type);
4633 msgbus::publish_any(topic, &data);
4634 }
4635
4636 pub fn add_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
4644 self.check_registered();
4645
4646 let cache = self.cache_rc();
4647 if cache.borrow().synthetic(&synthetic.id).is_some() {
4648 anyhow::bail!("`synthetic` {} already exists", synthetic.id);
4649 }
4650 cache.borrow_mut().add_synthetic(synthetic)
4651 }
4652
4653 pub fn update_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
4661 self.check_registered();
4662
4663 let cache = self.cache_rc();
4664 if cache.borrow().synthetic(&synthetic.id).is_none() {
4665 anyhow::bail!("`synthetic` {} does not exist", synthetic.id);
4666 }
4667 cache.borrow_mut().add_synthetic(synthetic)
4668 }
4669
4670 pub fn subscribe_data(
4676 &mut self,
4677 handler: ShareableMessageHandler,
4678 data_type: DataType,
4679 client_id: Option<ClientId>,
4680 params: Option<Params>,
4681 ) {
4682 assert!(
4683 self.is_properly_registered(),
4684 "DataActor {} is not properly registered - trader_id: {:?}, clock: {}, cache: {}",
4685 self.actor_id,
4686 self.trader_id,
4687 self.clock.is_some(),
4688 self.cache.is_some()
4689 );
4690
4691 let mut topics = get_custom_subscription_topics(&data_type);
4692 let Some(topic) = topics.pop() else {
4693 return;
4694 };
4695
4696 for alias in topics {
4697 self.add_subscription_any(alias, handler.clone(), None, None);
4698 }
4699
4700 if client_id.is_none() {
4702 self.add_subscription_any(topic, handler, None, None);
4703 return;
4704 }
4705
4706 let command = DataCommand::Subscribe(SubscribeCommand::Data(SubscribeCustomData {
4707 data_type,
4708 client_id,
4709 venue: None,
4710 command_id: UUID4::new(),
4711 ts_init: self.timestamp_ns(),
4712 correlation_id: None,
4713 params,
4714 }));
4715
4716 if self.add_subscription_any(topic, handler, None, Some(command.clone())) {
4717 self.send_data_cmd(command);
4718 }
4719 }
4720
4721 pub fn subscribe_signal(
4729 &mut self,
4730 handler: ShareableMessageHandler,
4731 name: &str,
4732 priority: Option<u32>,
4733 ) {
4734 self.check_registered();
4735
4736 let pattern = get_signal_pattern(name);
4737 if self.topic_handlers.contains_key(&pattern) {
4738 log::warn!(
4739 "Actor {} attempted duplicate signal subscription to '{pattern}'",
4740 self.actor_id,
4741 );
4742 return;
4743 }
4744 self.topic_handlers.insert(
4745 pattern,
4746 Subscription {
4747 handler: handler.clone(),
4748 command: None,
4749 },
4750 );
4751 msgbus::subscribe_any(pattern, handler, priority);
4752 }
4753
4754 pub fn subscribe_queue_state(
4760 &mut self,
4761 handler: ShareableMessageHandler,
4762 channel: Option<SystemChannel>,
4763 priority: Option<u32>,
4764 ) {
4765 self.check_registered();
4766
4767 let topic = MessagingSwitchboard::queue_state_changed_pattern(channel);
4768 self.add_subscription_any(topic, handler, priority, None);
4769 }
4770
4771 pub fn subscribe_socket_state(
4777 &mut self,
4778 handler: ShareableMessageHandler,
4779 client_id: Option<ClientId>,
4780 endpoint: Option<&str>,
4781 priority: Option<u32>,
4782 ) {
4783 self.check_registered();
4784
4785 let topic = MessagingSwitchboard::socket_state_changed_pattern(client_id, endpoint);
4786 self.add_subscription_any(topic, handler, priority, None);
4787 }
4788
4789 pub fn subscribe_quotes(
4798 &mut self,
4799 handler: TypedHandler<QuoteTick>,
4800 instrument_ids: impl IntoInstrumentIds,
4801 client_id: Option<ClientId>,
4802 params: Option<Params>,
4803 ) -> anyhow::Result<()> {
4804 self.check_registered();
4805 let instrument_ids = instrument_ids.into_instrument_ids()?;
4806 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
4807 quote_routing(
4808 &instrument_ids,
4809 client_id.as_ref(),
4810 params.as_ref(),
4811 &self.cache_ref(),
4812 )?;
4813 let first_venue = instrument_ids[0].venue;
4814 let venue = instrument_ids
4815 .iter()
4816 .all(|id| id.venue == first_venue)
4817 .then_some(first_venue);
4818
4819 let command = SubscribeQuotes {
4820 instrument_ids,
4821 client_id,
4822 venue,
4823 command_id: UUID4::new(),
4824 ts_init: self.timestamp_ns(),
4825 correlation_id: None,
4826 params,
4827 };
4828
4829 let mut acquired = Vec::with_capacity(command.instrument_ids.len());
4830 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
4831 for (instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
4832 let member = command.with_instrument_ids(*instrument_id);
4833
4834 let topic = get_quotes_topic(*instrument_id);
4835 if self.add_quote_subscription(
4836 topic,
4837 handler,
4838 DataCommand::Subscribe(SubscribeCommand::Quotes(member)),
4839 ) {
4840 acquired.push(*instrument_id);
4841 }
4842 }
4843
4844 if !acquired.is_empty() {
4845 let dispatch = command.with_instrument_ids(acquired);
4846 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Quotes(dispatch)));
4847 }
4848
4849 Ok(())
4850 }
4851
4852 pub fn subscribe_instruments(
4854 &mut self,
4855 pattern: MStr<Pattern>,
4856 handler: TypedHandler<InstrumentAny>,
4857 venue: Venue,
4858 client_id: Option<ClientId>,
4859 params: Option<Params>,
4860 ) {
4861 self.check_registered();
4862
4863 let command = DataCommand::Subscribe(SubscribeCommand::Instruments(SubscribeInstruments {
4864 client_id,
4865 venue,
4866 command_id: UUID4::new(),
4867 ts_init: self.timestamp_ns(),
4868 correlation_id: None,
4869 params,
4870 }));
4871
4872 if self.add_instrument_subscription(pattern, handler, command.clone()) {
4873 self.send_data_cmd(command);
4874 }
4875 }
4876
4877 pub fn subscribe_instrument(
4886 &mut self,
4887 handler: TypedHandler<InstrumentAny>,
4888 instrument_ids: impl IntoInstrumentIds,
4889 client_id: Option<ClientId>,
4890 params: Option<Params>,
4891 ) -> anyhow::Result<()> {
4892 self.check_registered();
4893 let instrument_ids = instrument_ids.into_instrument_ids()?;
4894 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
4895 let venue = instrument_ids
4896 .iter()
4897 .all(|id| id.venue == instrument_ids[0].venue)
4898 .then_some(instrument_ids[0].venue);
4899
4900 let command = SubscribeInstrument {
4901 instrument_ids,
4902 client_id,
4903 venue,
4904 command_id: UUID4::new(),
4905 ts_init: self.timestamp_ns(),
4906 correlation_id: None,
4907 params,
4908 };
4909
4910 let mut acquired = Vec::new();
4911 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
4912 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
4913 let member = command.with_instrument_ids(instrument_id);
4914
4915 let topic = get_instrument_topic(instrument_id);
4916 if self.add_instrument_subscription(
4917 topic.into(),
4918 handler,
4919 DataCommand::Subscribe(SubscribeCommand::Instrument(member)),
4920 ) {
4921 acquired.push(instrument_id);
4922 }
4923 }
4924
4925 if !acquired.is_empty() {
4926 let dispatch = command.with_instrument_ids(acquired);
4927 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Instrument(
4928 dispatch,
4929 )));
4930 }
4931
4932 Ok(())
4933 }
4934
4935 #[expect(clippy::too_many_arguments)]
4944 pub fn subscribe_book_deltas(
4945 &mut self,
4946 handler: TypedHandler<OrderBookDeltas>,
4947 instrument_ids: impl IntoInstrumentIds,
4948 book_type: BookType,
4949 depth: Option<NonZeroUsize>,
4950 client_id: Option<ClientId>,
4951 managed: bool,
4952 params: Option<Params>,
4953 ) -> anyhow::Result<()> {
4954 self.check_registered();
4955 let instrument_ids = instrument_ids.into_instrument_ids()?;
4956 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
4957 let venue = instrument_ids
4958 .iter()
4959 .all(|id| id.venue == instrument_ids[0].venue)
4960 .then_some(instrument_ids[0].venue);
4961
4962 let command = SubscribeBookDeltas {
4963 instrument_ids,
4964 book_type,
4965 client_id,
4966 venue,
4967 command_id: UUID4::new(),
4968 ts_init: self.timestamp_ns(),
4969 depth,
4970 managed,
4971 correlation_id: None,
4972 params,
4973 };
4974
4975 let mut acquired = Vec::new();
4976 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
4977 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
4978 let member = command.with_instrument_ids(instrument_id);
4979 let topic = if is_parent_subscription(command.params.as_ref()) {
4980 get_book_deltas_pattern(instrument_id)
4981 } else {
4982 get_book_deltas_topic(instrument_id).into()
4983 };
4984
4985 if self.add_deltas_subscription(
4986 topic,
4987 handler,
4988 DataCommand::Subscribe(SubscribeCommand::BookDeltas(member)),
4989 ) {
4990 acquired.push(instrument_id);
4991 }
4992 }
4993
4994 if !acquired.is_empty() {
4995 let dispatch = command.with_instrument_ids(acquired);
4996 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::BookDeltas(
4997 dispatch,
4998 )));
4999 }
5000
5001 Ok(())
5002 }
5003
5004 #[expect(clippy::too_many_arguments)]
5013 pub fn subscribe_book_depth(
5014 &mut self,
5015 handler: TypedHandler<OrderBookDepth>,
5016 instrument_ids: impl IntoInstrumentIds,
5017 book_type: BookType,
5018 depth: Option<NonZeroUsize>,
5019 client_id: Option<ClientId>,
5020 managed: bool,
5021 params: Option<Params>,
5022 ) -> anyhow::Result<()> {
5023 self.check_registered();
5024 let instrument_ids = instrument_ids.into_instrument_ids()?;
5025 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5026 let venue = instrument_ids
5027 .iter()
5028 .all(|id| id.venue == instrument_ids[0].venue)
5029 .then_some(instrument_ids[0].venue);
5030
5031 let command = SubscribeBookDepth {
5032 instrument_ids,
5033 book_type,
5034 client_id,
5035 venue,
5036 command_id: UUID4::new(),
5037 ts_init: self.timestamp_ns(),
5038 depth,
5039 managed,
5040 correlation_id: None,
5041 params,
5042 };
5043
5044 let mut acquired = Vec::new();
5045 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5046 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5047 let member = command.with_instrument_ids(instrument_id);
5048 let topic = if is_parent_subscription(command.params.as_ref()) {
5049 get_book_depth_pattern(instrument_id)
5050 } else {
5051 get_book_depth_topic(instrument_id).into()
5052 };
5053
5054 if self.add_depth_subscription(
5055 topic,
5056 handler,
5057 DataCommand::Subscribe(SubscribeCommand::BookDepth(member)),
5058 ) {
5059 acquired.push(instrument_id);
5060 }
5061 }
5062
5063 if !acquired.is_empty() {
5064 let dispatch = command.with_instrument_ids(acquired);
5065 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::BookDepth(
5066 dispatch,
5067 )));
5068 }
5069
5070 Ok(())
5071 }
5072
5073 #[expect(clippy::too_many_arguments)]
5082 pub fn subscribe_book_at_interval(
5083 &mut self,
5084 handler: TypedHandler<OrderBook>,
5085 instrument_ids: impl IntoInstrumentIds,
5086 book_type: BookType,
5087 depth: Option<NonZeroUsize>,
5088 interval_ms: NonZeroUsize,
5089 client_id: Option<ClientId>,
5090 params: Option<Params>,
5091 ) -> anyhow::Result<()> {
5092 self.check_registered();
5093 let instrument_ids = instrument_ids.into_instrument_ids()?;
5094 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5095 let venue = instrument_ids
5096 .iter()
5097 .all(|id| id.venue == instrument_ids[0].venue)
5098 .then_some(instrument_ids[0].venue);
5099
5100 let command = SubscribeBookSnapshots {
5101 instrument_ids,
5102 book_type,
5103 client_id,
5104 venue,
5105 command_id: UUID4::new(),
5106 ts_init: self.timestamp_ns(),
5107 depth,
5108 interval_ms,
5109 correlation_id: None,
5110 params,
5111 };
5112
5113 let mut acquired = Vec::new();
5114 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5115 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5116 let member = command.with_instrument_ids(instrument_id);
5117
5118 let topic = get_book_snapshots_topic(instrument_id, interval_ms);
5119 if self.add_book_snapshot_subscription(
5120 topic,
5121 handler,
5122 DataCommand::Subscribe(SubscribeCommand::BookSnapshots(member)),
5123 ) {
5124 acquired.push(instrument_id);
5125 }
5126 }
5127
5128 if !acquired.is_empty() {
5129 let dispatch = command.with_instrument_ids(acquired);
5130 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::BookSnapshots(
5131 dispatch,
5132 )));
5133 }
5134
5135 Ok(())
5136 }
5137
5138 pub fn subscribe_trades(
5146 &mut self,
5147 handler: TypedHandler<TradeTick>,
5148 instrument_ids: impl IntoInstrumentIds,
5149 client_id: Option<ClientId>,
5150 params: Option<Params>,
5151 ) -> anyhow::Result<()> {
5152 self.check_registered();
5153 let instrument_ids = instrument_ids.into_instrument_ids()?;
5154 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5155 let mut groups: IndexMap<Option<Venue>, Vec<InstrumentId>> = IndexMap::new();
5156 for id in instrument_ids {
5157 groups
5158 .entry(id.is_synthetic().then_some(id.venue))
5159 .or_default()
5160 .push(id);
5161 }
5162
5163 let contexts = std::iter::repeat_n((handler, params), groups.len());
5164 for ((venue, ids), (handler, params)) in groups.into_iter().zip(contexts) {
5165 let venue = venue.or_else(|| {
5166 ids.iter()
5167 .all(|id| id.venue == ids[0].venue)
5168 .then_some(ids[0].venue)
5169 });
5170
5171 let command = SubscribeTrades::new(
5172 ids,
5173 client_id,
5174 venue,
5175 UUID4::new(),
5176 self.timestamp_ns(),
5177 None,
5178 params,
5179 );
5180 let mut acquired = Vec::new();
5181
5182 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5183 for (id, handler) in command.instrument_ids.iter().zip(handlers) {
5184 let member = command.with_instrument_ids(*id);
5185
5186 let topic = get_trades_topic(*id);
5187 if self.add_trade_subscription(
5188 topic,
5189 handler,
5190 DataCommand::Subscribe(SubscribeCommand::Trades(member)),
5191 ) {
5192 acquired.push(*id);
5193 }
5194 }
5195
5196 if !acquired.is_empty() {
5197 let dispatch = command.with_instrument_ids(acquired);
5198 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Trades(dispatch)));
5199 }
5200 }
5201
5202 Ok(())
5203 }
5204
5205 #[expect(
5211 clippy::needless_pass_by_value,
5212 reason = "preserves the owned bar handler API"
5213 )]
5214 pub fn subscribe_bars(
5215 &mut self,
5216 handler: TypedHandler<Bar>,
5217 bar_types: impl IntoBarTypes,
5218 client_id: Option<ClientId>,
5219 params: Option<Params>,
5220 ) -> anyhow::Result<()> {
5221 self.check_registered();
5222 let bar_types = bar_types.into_bar_types()?;
5223 ensure_routable_bar_types(&bar_types, client_id.is_some())?;
5224 let first_venue = bar_types[0].instrument_id().venue;
5225 let venue = bar_types
5226 .iter()
5227 .all(|bt| bt.instrument_id().venue == first_venue)
5228 .then_some(first_venue);
5229
5230 let command = SubscribeBars::new(
5231 bar_types,
5232 client_id,
5233 venue,
5234 UUID4::new(),
5235 self.timestamp_ns(),
5236 None,
5237 params,
5238 );
5239 let mut acquired = Vec::new();
5240
5241 for bar_type in &command.bar_types {
5242 let member = command.with_bar_types(*bar_type);
5243 let topic = get_bars_topic(bar_type.standard());
5244 if self.add_bar_subscription(
5245 topic,
5246 handler.clone(),
5247 DataCommand::Subscribe(SubscribeCommand::Bars(member)),
5248 ) {
5249 acquired.push(*bar_type);
5250 }
5251 }
5252
5253 if !acquired.is_empty() {
5254 let dispatch = command.with_bar_types(acquired);
5255 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Bars(dispatch)));
5256 }
5257
5258 Ok(())
5259 }
5260
5261 pub fn subscribe_mark_prices(
5269 &mut self,
5270 handler: TypedHandler<MarkPriceUpdate>,
5271 instrument_ids: impl IntoInstrumentIds,
5272 client_id: Option<ClientId>,
5273 params: Option<Params>,
5274 ) -> anyhow::Result<()> {
5275 self.check_registered();
5276 let instrument_ids = instrument_ids.into_instrument_ids()?;
5277 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5278 let venue = instrument_ids
5279 .iter()
5280 .all(|id| id.venue == instrument_ids[0].venue)
5281 .then_some(instrument_ids[0].venue);
5282
5283 let command = SubscribeMarkPrices {
5284 instrument_ids,
5285 client_id,
5286 venue,
5287 command_id: UUID4::new(),
5288 ts_init: self.timestamp_ns(),
5289 correlation_id: None,
5290 params,
5291 };
5292
5293 let mut acquired = Vec::new();
5294 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5295 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5296 let member = command.with_instrument_ids(instrument_id);
5297
5298 let topic = get_mark_price_topic(instrument_id);
5299 if self.add_mark_price_subscription(
5300 topic,
5301 handler,
5302 DataCommand::Subscribe(SubscribeCommand::MarkPrices(member)),
5303 ) {
5304 acquired.push(instrument_id);
5305 }
5306 }
5307
5308 if !acquired.is_empty() {
5309 let dispatch = command.with_instrument_ids(acquired);
5310 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::MarkPrices(
5311 dispatch,
5312 )));
5313 }
5314
5315 Ok(())
5316 }
5317
5318 pub fn subscribe_index_prices(
5326 &mut self,
5327 handler: TypedHandler<IndexPriceUpdate>,
5328 instrument_ids: impl IntoInstrumentIds,
5329 client_id: Option<ClientId>,
5330 params: Option<Params>,
5331 ) -> anyhow::Result<()> {
5332 self.check_registered();
5333 let instrument_ids = instrument_ids.into_instrument_ids()?;
5334 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5335 let venue = instrument_ids
5336 .iter()
5337 .all(|id| id.venue == instrument_ids[0].venue)
5338 .then_some(instrument_ids[0].venue);
5339
5340 let command = SubscribeIndexPrices {
5341 instrument_ids,
5342 client_id,
5343 venue,
5344 command_id: UUID4::new(),
5345 ts_init: self.timestamp_ns(),
5346 correlation_id: None,
5347 params,
5348 };
5349
5350 let mut acquired = Vec::new();
5351 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5352 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5353 let member = command.with_instrument_ids(instrument_id);
5354
5355 let topic = get_index_price_topic(instrument_id);
5356 if self.add_index_price_subscription(
5357 topic,
5358 handler,
5359 DataCommand::Subscribe(SubscribeCommand::IndexPrices(member)),
5360 ) {
5361 acquired.push(instrument_id);
5362 }
5363 }
5364
5365 if !acquired.is_empty() {
5366 let dispatch = command.with_instrument_ids(acquired);
5367 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::IndexPrices(
5368 dispatch,
5369 )));
5370 }
5371
5372 Ok(())
5373 }
5374
5375 pub fn subscribe_funding_rates(
5383 &mut self,
5384 handler: TypedHandler<FundingRateUpdate>,
5385 instrument_ids: impl IntoInstrumentIds,
5386 client_id: Option<ClientId>,
5387 params: Option<Params>,
5388 ) -> anyhow::Result<()> {
5389 self.check_registered();
5390 let instrument_ids = instrument_ids.into_instrument_ids()?;
5391 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5392 let venue = instrument_ids
5393 .iter()
5394 .all(|id| id.venue == instrument_ids[0].venue)
5395 .then_some(instrument_ids[0].venue);
5396
5397 let command = SubscribeFundingRates {
5398 instrument_ids,
5399 client_id,
5400 venue,
5401 command_id: UUID4::new(),
5402 ts_init: self.timestamp_ns(),
5403 correlation_id: None,
5404 params,
5405 };
5406
5407 let mut acquired = Vec::new();
5408 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5409 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5410 let member = command.with_instrument_ids(instrument_id);
5411
5412 let topic = get_funding_rate_topic(instrument_id);
5413 if self.add_funding_rate_subscription(
5414 topic,
5415 handler,
5416 DataCommand::Subscribe(SubscribeCommand::FundingRates(member)),
5417 ) {
5418 acquired.push(instrument_id);
5419 }
5420 }
5421
5422 if !acquired.is_empty() {
5423 let dispatch = command.with_instrument_ids(acquired);
5424 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::FundingRates(
5425 dispatch,
5426 )));
5427 }
5428
5429 Ok(())
5430 }
5431
5432 pub fn subscribe_option_greeks(
5441 &mut self,
5442 handler: TypedHandler<OptionGreeks>,
5443 instrument_ids: impl IntoInstrumentIds,
5444 client_id: Option<ClientId>,
5445 params: Option<Params>,
5446 ) -> anyhow::Result<()> {
5447 self.check_registered();
5448 let instrument_ids = instrument_ids.into_instrument_ids()?;
5449 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5450 let venue = instrument_ids
5451 .iter()
5452 .all(|id| id.venue == instrument_ids[0].venue)
5453 .then_some(instrument_ids[0].venue);
5454
5455 let command = SubscribeOptionGreeks {
5456 instrument_ids,
5457 client_id,
5458 venue,
5459 command_id: UUID4::new(),
5460 ts_init: self.timestamp_ns(),
5461 correlation_id: None,
5462 params,
5463 };
5464
5465 let mut acquired = Vec::new();
5466 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5467 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5468 let member = command.with_instrument_ids(instrument_id);
5469
5470 let topic = get_option_greeks_topic(instrument_id);
5471 if self.add_option_greeks_subscription(
5472 topic,
5473 handler,
5474 DataCommand::Subscribe(SubscribeCommand::OptionGreeks(member)),
5475 ) {
5476 acquired.push(instrument_id);
5477 }
5478 }
5479
5480 if !acquired.is_empty() {
5481 let dispatch = command.with_instrument_ids(acquired);
5482 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::OptionGreeks(
5483 dispatch,
5484 )));
5485 }
5486
5487 Ok(())
5488 }
5489
5490 pub fn subscribe_instrument_status(
5499 &mut self,
5500 handler: ShareableMessageHandler,
5501 instrument_ids: impl IntoInstrumentIds,
5502 client_id: Option<ClientId>,
5503 params: Option<Params>,
5504 ) -> anyhow::Result<()> {
5505 self.check_registered();
5506 let instrument_ids = instrument_ids.into_instrument_ids()?;
5507 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5508 let venue = instrument_ids
5509 .iter()
5510 .all(|id| id.venue == instrument_ids[0].venue)
5511 .then_some(instrument_ids[0].venue);
5512
5513 let command = SubscribeInstrumentStatus {
5514 instrument_ids,
5515 client_id,
5516 venue,
5517 command_id: UUID4::new(),
5518 ts_init: self.timestamp_ns(),
5519 correlation_id: None,
5520 params,
5521 };
5522
5523 let mut acquired = Vec::new();
5524 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5525 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5526 let member = command.with_instrument_ids(instrument_id);
5527
5528 let topic = get_instrument_status_topic(instrument_id);
5529 if self.add_subscription_any(
5530 topic,
5531 handler,
5532 None,
5533 Some(DataCommand::Subscribe(SubscribeCommand::InstrumentStatus(
5534 member,
5535 ))),
5536 ) {
5537 acquired.push(instrument_id);
5538 }
5539 }
5540
5541 if !acquired.is_empty() {
5542 let dispatch = command.with_instrument_ids(acquired);
5543 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::InstrumentStatus(
5544 dispatch,
5545 )));
5546 }
5547
5548 Ok(())
5549 }
5550
5551 pub fn subscribe_instrument_close(
5560 &mut self,
5561 handler: ShareableMessageHandler,
5562 instrument_ids: impl IntoInstrumentIds,
5563 client_id: Option<ClientId>,
5564 params: Option<Params>,
5565 ) -> anyhow::Result<()> {
5566 self.check_registered();
5567 let instrument_ids = instrument_ids.into_instrument_ids()?;
5568 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5569 let venue = instrument_ids
5570 .iter()
5571 .all(|id| id.venue == instrument_ids[0].venue)
5572 .then_some(instrument_ids[0].venue);
5573
5574 let command = SubscribeInstrumentClose {
5575 instrument_ids,
5576 client_id,
5577 venue,
5578 command_id: UUID4::new(),
5579 ts_init: self.timestamp_ns(),
5580 correlation_id: None,
5581 params,
5582 };
5583
5584 let mut acquired = Vec::new();
5585 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5586 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5587 let member = command.with_instrument_ids(instrument_id);
5588
5589 let topic = get_instrument_close_topic(instrument_id);
5590 if self.add_instrument_close_subscription(
5591 topic,
5592 handler,
5593 DataCommand::Subscribe(SubscribeCommand::InstrumentClose(member)),
5594 ) {
5595 acquired.push(instrument_id);
5596 }
5597 }
5598
5599 if !acquired.is_empty() {
5600 let dispatch = command.with_instrument_ids(acquired);
5601 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::InstrumentClose(
5602 dispatch,
5603 )));
5604 }
5605
5606 Ok(())
5607 }
5608
5609 fn validate_subscription(
5610 &self,
5611 instrument_ids: &[InstrumentId],
5612 client_id: Option<&ClientId>,
5613 ) -> anyhow::Result<()> {
5614 ensure_routable_instrument_ids(instrument_ids, client_id.is_some())?;
5615 ensure_subscription_routes(instrument_ids, client_id, &self.cache_ref())
5616 }
5617
5618 #[expect(
5620 clippy::too_many_arguments,
5621 reason = "subscription command mirrors the option chain request fields"
5622 )]
5623 pub fn subscribe_option_chain(
5624 &mut self,
5625 topic: MStr<Topic>,
5626 handler: TypedHandler<OptionChainSlice>,
5627 series_id: OptionSeriesId,
5628 strike_range: StrikeRange,
5629 snapshot_interval_ms: Option<u64>,
5630 client_id: Option<ClientId>,
5631 params: Option<Params>,
5632 ) {
5633 self.check_registered();
5634
5635 let correlation_id = self
5636 .option_chain_handlers
5637 .get(&topic)
5638 .and_then(|subscription| match subscription.command.as_ref() {
5639 Some(DataCommand::Subscribe(SubscribeCommand::OptionChain(command))) => {
5640 Some(command.correlation_id.unwrap_or(command.command_id))
5641 }
5642 _ => None,
5643 });
5644
5645 let mut subscribe = SubscribeOptionChain::new(
5646 series_id,
5647 strike_range,
5648 snapshot_interval_ms,
5649 UUID4::new(),
5650 self.timestamp_ns(),
5651 client_id,
5652 Some(series_id.venue),
5653 params,
5654 );
5655 subscribe.correlation_id = correlation_id;
5656 let command = DataCommand::Subscribe(SubscribeCommand::OptionChain(subscribe));
5657
5658 self.set_option_chain_subscription(topic, handler, command.clone());
5659 self.send_data_cmd(command);
5660 }
5661
5662 pub fn unsubscribe_data(
5664 &mut self,
5665 data_type: DataType,
5666 client_id: Option<ClientId>,
5667 params: Option<Params>,
5668 ) {
5669 self.check_registered();
5670
5671 let mut retained = None;
5672
5673 for topic in get_custom_subscription_topics(&data_type) {
5674 if let Some(command) = self.remove_subscription_any(topic) {
5675 retained = Some(command);
5676 }
5677 }
5678
5679 if client_id.is_none() && retained.is_none() {
5680 return;
5681 }
5682
5683 let command = UnsubscribeCommand::Data(UnsubscribeCustomData {
5684 data_type,
5685 client_id,
5686 venue: None,
5687 command_id: UUID4::new(),
5688 ts_init: self.timestamp_ns(),
5689 correlation_id: None,
5690 params,
5691 });
5692
5693 self.send_unsubscribe_cmd(retained, DataCommand::Unsubscribe(command));
5694 }
5695
5696 pub fn unsubscribe_signal(&mut self, name: &str) {
5702 self.check_registered();
5703
5704 let pattern = get_signal_pattern(name);
5705 if let Some(subscription) = self.topic_handlers.remove(&pattern) {
5706 msgbus::unsubscribe_any(pattern, &subscription.handler);
5707 } else {
5708 log::warn!(
5709 "Actor {} attempted to unsubscribe from signal pattern '{pattern}' when not subscribed",
5710 self.actor_id,
5711 );
5712 }
5713 }
5714
5715 pub fn unsubscribe_queue_state(&mut self, channel: Option<SystemChannel>) {
5721 self.check_registered();
5722
5723 let topic = MessagingSwitchboard::queue_state_changed_pattern(channel);
5724 let _ = self.remove_subscription_any(topic);
5725 }
5726
5727 pub fn unsubscribe_socket_state(
5733 &mut self,
5734 client_id: Option<ClientId>,
5735 endpoint: Option<&str>,
5736 ) {
5737 self.check_registered();
5738
5739 let topic = MessagingSwitchboard::socket_state_changed_pattern(client_id, endpoint);
5740 let _ = self.remove_subscription_any(topic);
5741 }
5742
5743 pub fn unsubscribe_instruments(
5745 &mut self,
5746 venue: Venue,
5747 client_id: Option<ClientId>,
5748 params: Option<Params>,
5749 ) {
5750 self.check_registered();
5751
5752 let pattern = get_instruments_pattern(venue);
5753 let retained = self.remove_instrument_subscription(pattern);
5754
5755 let command = UnsubscribeCommand::Instruments(UnsubscribeInstruments {
5756 client_id,
5757 venue,
5758 command_id: UUID4::new(),
5759 ts_init: self.timestamp_ns(),
5760 correlation_id: None,
5761 params,
5762 });
5763
5764 self.send_unsubscribe_cmd(retained, DataCommand::Unsubscribe(command));
5765 }
5766
5767 pub fn unsubscribe_instrument(
5775 &mut self,
5776 instrument_ids: impl IntoInstrumentIds,
5777 client_id: Option<ClientId>,
5778 _params: Option<Params>,
5779 ) -> anyhow::Result<()> {
5780 self.check_registered();
5781 let instrument_ids = instrument_ids.into_instrument_ids()?;
5782 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5783 let mut retained = Vec::new();
5784
5785 for instrument_id in instrument_ids {
5786 let topic = get_instrument_topic(instrument_id);
5787 if let Some(command) = self.remove_instrument_subscription(topic.into()) {
5788 retained.push(command);
5789 }
5790 }
5791
5792 self.release_subscriptions(retained);
5793 Ok(())
5794 }
5795
5796 #[expect(
5804 clippy::needless_pass_by_value,
5805 reason = "preserves the instrument unsubscribe API"
5806 )]
5807 pub fn unsubscribe_book_deltas(
5808 &mut self,
5809 instrument_ids: impl IntoInstrumentIds,
5810 client_id: Option<ClientId>,
5811 params: Option<Params>,
5812 ) -> anyhow::Result<()> {
5813 self.check_registered();
5814 let instrument_ids = instrument_ids.into_instrument_ids()?;
5815 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5816 let mut retained = Vec::new();
5817
5818 for instrument_id in instrument_ids {
5819 let topic = if is_parent_subscription(params.as_ref()) {
5820 get_book_deltas_pattern(instrument_id)
5821 } else {
5822 get_book_deltas_topic(instrument_id).into()
5823 };
5824
5825 if let Some(command) = self.remove_deltas_subscription(topic) {
5826 retained.push(command);
5827 }
5828 }
5829
5830 self.release_subscriptions(retained);
5831 Ok(())
5832 }
5833
5834 #[expect(
5842 clippy::needless_pass_by_value,
5843 reason = "preserves the instrument unsubscribe API"
5844 )]
5845 pub fn unsubscribe_book_depth(
5846 &mut self,
5847 instrument_ids: impl IntoInstrumentIds,
5848 client_id: Option<ClientId>,
5849 params: Option<Params>,
5850 ) -> anyhow::Result<()> {
5851 self.check_registered();
5852 let instrument_ids = instrument_ids.into_instrument_ids()?;
5853 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5854 let mut retained = Vec::new();
5855
5856 for instrument_id in instrument_ids {
5857 let topic = if is_parent_subscription(params.as_ref()) {
5858 get_book_depth_pattern(instrument_id)
5859 } else {
5860 get_book_depth_topic(instrument_id).into()
5861 };
5862
5863 if let Some(command) = self.remove_depth_subscription(topic) {
5864 retained.push(command);
5865 }
5866 }
5867
5868 self.release_subscriptions(retained);
5869 Ok(())
5870 }
5871
5872 pub fn unsubscribe_book_at_interval(
5880 &mut self,
5881 instrument_ids: impl IntoInstrumentIds,
5882 interval_ms: NonZeroUsize,
5883 client_id: Option<ClientId>,
5884 _params: Option<Params>,
5885 ) -> anyhow::Result<()> {
5886 self.check_registered();
5887 let instrument_ids = instrument_ids.into_instrument_ids()?;
5888 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5889 let mut retained = Vec::new();
5890
5891 for instrument_id in instrument_ids {
5892 let topic = get_book_snapshots_topic(instrument_id, interval_ms);
5893 if let Some(command) = self.remove_book_snapshot_subscription(topic) {
5894 retained.push(command);
5895 }
5896 }
5897
5898 self.release_subscriptions(retained);
5899 Ok(())
5900 }
5901
5902 pub fn unsubscribe_quotes(
5912 &mut self,
5913 instrument_ids: impl IntoInstrumentIds,
5914 client_id: Option<ClientId>,
5915 _params: Option<Params>,
5916 ) -> anyhow::Result<()> {
5917 self.check_registered();
5918 let instrument_ids = instrument_ids.into_instrument_ids()?;
5919 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5920 let mut retained = Vec::new();
5921 for instrument_id in instrument_ids {
5922 retained.extend(self.remove_quote_subscription(get_quotes_topic(instrument_id)));
5923 }
5924
5925 self.release_subscriptions(retained);
5926 Ok(())
5927 }
5928
5929 pub fn unsubscribe_trades(
5937 &mut self,
5938 instrument_ids: impl IntoInstrumentIds,
5939 client_id: Option<ClientId>,
5940 _params: Option<Params>,
5941 ) -> anyhow::Result<()> {
5942 self.check_registered();
5943 let instrument_ids = instrument_ids.into_instrument_ids()?;
5944 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5945 let mut retained = Vec::new();
5946 for instrument_id in instrument_ids {
5947 retained.extend(self.remove_trade_subscription(get_trades_topic(instrument_id)));
5948 }
5949
5950 self.release_subscriptions(retained);
5951 Ok(())
5952 }
5953
5954 fn release_subscriptions(&self, retained: Vec<DataCommand>) {
5955 let ts_init = self.timestamp_ns();
5956 let mut releases = IndexMap::new();
5957
5958 for retained in retained {
5959 let DataCommand::Subscribe(subscribe) = &retained else {
5960 unreachable!("instrument subscriptions retain a subscribe command");
5961 };
5962
5963 let acquisition_id = subscribe.command_id();
5964
5965 let Some(DataCommand::Unsubscribe(command)) =
5966 retained.into_unsubscribe(acquisition_id, ts_init)
5967 else {
5968 unreachable!("instrument subscriptions retain a subscribe command");
5969 };
5970
5971 releases
5972 .entry(acquisition_id)
5973 .or_insert_with(Vec::new)
5974 .push(command);
5975 }
5976
5977 for group in releases.into_values() {
5978 for command in UnsubscribeCommand::group(group) {
5979 let command_id = UUID4::new();
5980
5981 let command = match command {
5982 UnsubscribeCommand::Instrument(command) => {
5983 UnsubscribeCommand::Instrument(UnsubscribeInstrument {
5984 command_id,
5985 ..command
5986 })
5987 }
5988 UnsubscribeCommand::BookDeltas(command) => {
5989 UnsubscribeCommand::BookDeltas(UnsubscribeBookDeltas {
5990 command_id,
5991 ..command
5992 })
5993 }
5994 UnsubscribeCommand::BookDepth(command) => {
5995 UnsubscribeCommand::BookDepth(UnsubscribeBookDepth {
5996 command_id,
5997 ..command
5998 })
5999 }
6000 UnsubscribeCommand::BookSnapshots(command) => {
6001 UnsubscribeCommand::BookSnapshots(UnsubscribeBookSnapshots {
6002 command_id,
6003 ..command
6004 })
6005 }
6006 UnsubscribeCommand::Quotes(command) => {
6007 UnsubscribeCommand::Quotes(UnsubscribeQuotes {
6008 command_id,
6009 ..command
6010 })
6011 }
6012 UnsubscribeCommand::Trades(command) => {
6013 UnsubscribeCommand::Trades(UnsubscribeTrades {
6014 command_id,
6015 ..command
6016 })
6017 }
6018 UnsubscribeCommand::Bars(command) => {
6019 UnsubscribeCommand::Bars(UnsubscribeBars {
6020 command_id,
6021 ..command
6022 })
6023 }
6024 UnsubscribeCommand::MarkPrices(command) => {
6025 UnsubscribeCommand::MarkPrices(UnsubscribeMarkPrices {
6026 command_id,
6027 ..command
6028 })
6029 }
6030 UnsubscribeCommand::IndexPrices(command) => {
6031 UnsubscribeCommand::IndexPrices(UnsubscribeIndexPrices {
6032 command_id,
6033 ..command
6034 })
6035 }
6036 UnsubscribeCommand::FundingRates(command) => {
6037 UnsubscribeCommand::FundingRates(UnsubscribeFundingRates {
6038 command_id,
6039 ..command
6040 })
6041 }
6042 UnsubscribeCommand::InstrumentStatus(command) => {
6043 UnsubscribeCommand::InstrumentStatus(UnsubscribeInstrumentStatus {
6044 command_id,
6045 ..command
6046 })
6047 }
6048 UnsubscribeCommand::InstrumentClose(command) => {
6049 UnsubscribeCommand::InstrumentClose(UnsubscribeInstrumentClose {
6050 command_id,
6051 ..command
6052 })
6053 }
6054 UnsubscribeCommand::OptionGreeks(command) => {
6055 UnsubscribeCommand::OptionGreeks(UnsubscribeOptionGreeks {
6056 command_id,
6057 ..command
6058 })
6059 }
6060 _ => unreachable!("instrument releases retain instrument commands"),
6061 };
6062
6063 self.send_data_cmd(DataCommand::Unsubscribe(command));
6064 }
6065 }
6066 }
6067
6068 pub fn unsubscribe_bars(
6074 &mut self,
6075 bar_types: impl IntoBarTypes,
6076 client_id: Option<ClientId>,
6077 _params: Option<Params>,
6078 ) -> anyhow::Result<()> {
6079 self.check_registered();
6080 let bar_types = bar_types.into_bar_types()?;
6081 ensure_routable_bar_types(&bar_types, client_id.is_some())?;
6082 let retained = bar_types
6083 .into_iter()
6084 .filter_map(|bar_type| self.remove_bar_subscription(bar_type))
6085 .collect();
6086 self.release_subscriptions(retained);
6087 Ok(())
6088 }
6089
6090 pub fn unsubscribe_mark_prices(
6098 &mut self,
6099 instrument_ids: impl IntoInstrumentIds,
6100 client_id: Option<ClientId>,
6101 _params: Option<Params>,
6102 ) -> anyhow::Result<()> {
6103 self.check_registered();
6104 let instrument_ids = instrument_ids.into_instrument_ids()?;
6105 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6106 let mut retained = Vec::new();
6107
6108 for instrument_id in instrument_ids {
6109 let topic = get_mark_price_topic(instrument_id);
6110 if let Some(command) = self.remove_mark_price_subscription(topic) {
6111 retained.push(command);
6112 }
6113 }
6114
6115 self.release_subscriptions(retained);
6116 Ok(())
6117 }
6118
6119 pub fn unsubscribe_index_prices(
6127 &mut self,
6128 instrument_ids: impl IntoInstrumentIds,
6129 client_id: Option<ClientId>,
6130 _params: Option<Params>,
6131 ) -> anyhow::Result<()> {
6132 self.check_registered();
6133 let instrument_ids = instrument_ids.into_instrument_ids()?;
6134 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6135 let mut retained = Vec::new();
6136
6137 for instrument_id in instrument_ids {
6138 let topic = get_index_price_topic(instrument_id);
6139 if let Some(command) = self.remove_index_price_subscription(topic) {
6140 retained.push(command);
6141 }
6142 }
6143
6144 self.release_subscriptions(retained);
6145 Ok(())
6146 }
6147
6148 pub fn unsubscribe_funding_rates(
6156 &mut self,
6157 instrument_ids: impl IntoInstrumentIds,
6158 client_id: Option<ClientId>,
6159 _params: Option<Params>,
6160 ) -> anyhow::Result<()> {
6161 self.check_registered();
6162 let instrument_ids = instrument_ids.into_instrument_ids()?;
6163 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6164 let mut retained = Vec::new();
6165
6166 for instrument_id in instrument_ids {
6167 let topic = get_funding_rate_topic(instrument_id);
6168 if let Some(command) = self.remove_funding_rate_subscription(topic) {
6169 retained.push(command);
6170 }
6171 }
6172
6173 self.release_subscriptions(retained);
6174 Ok(())
6175 }
6176
6177 pub fn unsubscribe_option_greeks(
6185 &mut self,
6186 instrument_ids: impl IntoInstrumentIds,
6187 client_id: Option<ClientId>,
6188 _params: Option<Params>,
6189 ) -> anyhow::Result<()> {
6190 self.check_registered();
6191 let instrument_ids = instrument_ids.into_instrument_ids()?;
6192 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6193 let mut retained = Vec::new();
6194
6195 for instrument_id in instrument_ids {
6196 let topic = get_option_greeks_topic(instrument_id);
6197 if let Some(command) = self.remove_option_greeks_subscription(topic) {
6198 retained.push(command);
6199 }
6200 }
6201
6202 self.release_subscriptions(retained);
6203 Ok(())
6204 }
6205
6206 pub fn unsubscribe_instrument_status(
6214 &mut self,
6215 instrument_ids: impl IntoInstrumentIds,
6216 client_id: Option<ClientId>,
6217 _params: Option<Params>,
6218 ) -> anyhow::Result<()> {
6219 self.check_registered();
6220 let instrument_ids = instrument_ids.into_instrument_ids()?;
6221 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6222 let mut retained = Vec::new();
6223
6224 for instrument_id in instrument_ids {
6225 let topic = get_instrument_status_topic(instrument_id);
6226 if let Some(command) = self.remove_subscription_any(topic) {
6227 retained.push(command);
6228 }
6229 }
6230
6231 self.release_subscriptions(retained);
6232 Ok(())
6233 }
6234
6235 pub fn unsubscribe_instrument_close(
6243 &mut self,
6244 instrument_ids: impl IntoInstrumentIds,
6245 client_id: Option<ClientId>,
6246 _params: Option<Params>,
6247 ) -> anyhow::Result<()> {
6248 self.check_registered();
6249 let instrument_ids = instrument_ids.into_instrument_ids()?;
6250 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6251 let mut retained = Vec::new();
6252
6253 for instrument_id in instrument_ids {
6254 let topic = get_instrument_close_topic(instrument_id);
6255 if let Some(command) = self.remove_instrument_close_subscription(topic) {
6256 retained.push(command);
6257 }
6258 }
6259
6260 self.release_subscriptions(retained);
6261 Ok(())
6262 }
6263
6264 pub fn unsubscribe_option_chain(
6266 &mut self,
6267 series_id: OptionSeriesId,
6268 client_id: Option<ClientId>,
6269 ) {
6270 self.check_registered();
6271
6272 let topic = get_option_chain_topic(series_id);
6273 let retained = self.remove_option_chain_subscription(topic);
6274
6275 let command = UnsubscribeCommand::OptionChain(UnsubscribeOptionChain::new(
6276 series_id,
6277 UUID4::new(),
6278 self.timestamp_ns(),
6279 client_id,
6280 Some(series_id.venue),
6281 ));
6282
6283 self.send_unsubscribe_cmd(retained, DataCommand::Unsubscribe(command));
6284 }
6285
6286 #[expect(clippy::too_many_arguments)]
6292 pub fn request_data(
6293 &self,
6294 data_type: DataType,
6295 client_id: ClientId,
6296 start: Option<Timestamp>,
6297 end: Option<Timestamp>,
6298 limit: Option<NonZeroUsize>,
6299 params: Option<Params>,
6300 handler: TypedHandler<DataResponseEnvelope>,
6301 ) -> anyhow::Result<UUID4> {
6302 self.check_registered();
6303
6304 let now = self.clock_ref().utc_now();
6305 check_timestamps(now, start, end)?;
6306
6307 let request_id = UUID4::new();
6308 let command = RequestCommand::Data(RequestCustomData {
6309 client_id,
6310 data_type,
6311 start,
6312 end,
6313 limit,
6314 request_id,
6315 ts_init: self.timestamp_ns(),
6316 params,
6317 });
6318
6319 self.register_request_handler(command.request_id(), handler)?;
6320
6321 self.send_data_cmd(DataCommand::Request(command));
6322
6323 Ok(request_id)
6324 }
6325
6326 pub fn request_instrument(
6332 &self,
6333 instrument_ids: impl IntoInstrumentIds,
6334 start: Option<Timestamp>,
6335 end: Option<Timestamp>,
6336 client_id: Option<ClientId>,
6337 params: Option<Params>,
6338 handler: TypedHandler<DataResponseEnvelope>,
6339 ) -> anyhow::Result<UUID4> {
6340 self.check_registered();
6341
6342 let instrument_ids = instrument_ids.into_instrument_ids()?;
6343 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6344
6345 let now = self.clock_ref().utc_now();
6346 check_timestamps(now, start, end)?;
6347
6348 let request_id = UUID4::new();
6349 let command = RequestCommand::Instrument(RequestInstrument {
6350 instrument_ids,
6351 start,
6352 end,
6353 client_id,
6354 request_id,
6355 ts_init: now.into(),
6356 params,
6357 });
6358
6359 self.register_request_handler(command.request_id(), handler)?;
6360
6361 self.send_data_cmd(DataCommand::Request(command));
6362
6363 Ok(request_id)
6364 }
6365
6366 pub fn request_instruments(
6372 &self,
6373 venue: Option<Venue>,
6374 start: Option<Timestamp>,
6375 end: Option<Timestamp>,
6376 client_id: Option<ClientId>,
6377 params: Option<Params>,
6378 handler: TypedHandler<DataResponseEnvelope>,
6379 ) -> anyhow::Result<UUID4> {
6380 self.check_registered();
6381
6382 let now = self.clock_ref().utc_now();
6383 check_timestamps(now, start, end)?;
6384
6385 let request_id = UUID4::new();
6386 let command = RequestCommand::Instruments(RequestInstruments {
6387 venue,
6388 start,
6389 end,
6390 client_id,
6391 request_id,
6392 ts_init: now.into(),
6393 params,
6394 });
6395
6396 self.register_request_handler(command.request_id(), handler)?;
6397
6398 self.send_data_cmd(DataCommand::Request(command));
6399
6400 Ok(request_id)
6401 }
6402
6403 pub fn request_book_snapshot(
6409 &self,
6410 instrument_ids: impl IntoInstrumentIds,
6411 depth: Option<NonZeroUsize>,
6412 client_id: Option<ClientId>,
6413 params: Option<Params>,
6414 handler: TypedHandler<DataResponseEnvelope>,
6415 ) -> anyhow::Result<UUID4> {
6416 self.check_registered();
6417
6418 let instrument_ids = instrument_ids.into_instrument_ids()?;
6419 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6420
6421 let request_id = UUID4::new();
6422 let command = RequestCommand::BookSnapshot(RequestBookSnapshot {
6423 instrument_ids,
6424 depth,
6425 client_id,
6426 request_id,
6427 ts_init: self.timestamp_ns(),
6428 params,
6429 });
6430
6431 self.register_request_handler(command.request_id(), handler)?;
6432
6433 self.send_data_cmd(DataCommand::Request(command));
6434
6435 Ok(request_id)
6436 }
6437
6438 #[expect(clippy::too_many_arguments)]
6444 pub fn request_book_deltas(
6445 &self,
6446 instrument_ids: impl IntoInstrumentIds,
6447 start: Option<Timestamp>,
6448 end: Option<Timestamp>,
6449 limit: Option<NonZeroUsize>,
6450 client_id: Option<ClientId>,
6451 params: Option<Params>,
6452 handler: TypedHandler<DataResponseEnvelope>,
6453 ) -> anyhow::Result<UUID4> {
6454 self.check_registered();
6455
6456 let instrument_ids = instrument_ids.into_instrument_ids()?;
6457 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6458
6459 let now = self.clock_ref().utc_now();
6460 check_timestamps(now, start, end)?;
6461
6462 let request_id = UUID4::new();
6463 let command = RequestCommand::BookDeltas(RequestBookDeltas {
6464 instrument_ids,
6465 start,
6466 end,
6467 limit,
6468 client_id,
6469 request_id,
6470 ts_init: now.into(),
6471 params,
6472 });
6473
6474 self.register_request_handler(command.request_id(), handler)?;
6475
6476 self.send_data_cmd(DataCommand::Request(command));
6477
6478 Ok(request_id)
6479 }
6480
6481 #[expect(clippy::too_many_arguments)]
6487 pub fn request_book_depth(
6488 &self,
6489 instrument_ids: impl IntoInstrumentIds,
6490 start: Option<Timestamp>,
6491 end: Option<Timestamp>,
6492 limit: Option<NonZeroUsize>,
6493 depth: Option<NonZeroUsize>,
6494 client_id: Option<ClientId>,
6495 params: Option<Params>,
6496 handler: TypedHandler<DataResponseEnvelope>,
6497 ) -> anyhow::Result<UUID4> {
6498 self.check_registered();
6499
6500 let instrument_ids = instrument_ids.into_instrument_ids()?;
6501 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6502
6503 let now = self.clock_ref().utc_now();
6504 check_timestamps(now, start, end)?;
6505
6506 let request_id = UUID4::new();
6507 let command = RequestCommand::BookDepth(RequestBookDepth {
6508 instrument_ids,
6509 start,
6510 end,
6511 limit,
6512 depth,
6513 client_id,
6514 request_id,
6515 ts_init: now.into(),
6516 params,
6517 });
6518
6519 self.register_request_handler(command.request_id(), handler)?;
6520
6521 self.send_data_cmd(DataCommand::Request(command));
6522
6523 Ok(request_id)
6524 }
6525
6526 #[expect(clippy::too_many_arguments)]
6532 pub fn request_quotes(
6533 &self,
6534 instrument_ids: impl IntoInstrumentIds,
6535 start: Option<Timestamp>,
6536 end: Option<Timestamp>,
6537 limit: Option<NonZeroUsize>,
6538 client_id: Option<ClientId>,
6539 params: Option<Params>,
6540 handler: TypedHandler<DataResponseEnvelope>,
6541 ) -> anyhow::Result<UUID4> {
6542 self.check_registered();
6543
6544 let instrument_ids = instrument_ids.into_instrument_ids()?;
6545 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6546 anyhow::ensure!(
6547 instrument_ids.len() == 1 || limit.is_none(),
6548 "A record limit is unsupported for multi-instrument quotes requests",
6549 );
6550
6551 let now = self.clock_ref().utc_now();
6552 check_timestamps(now, start, end)?;
6553
6554 let request_id = UUID4::new();
6555 let command = RequestCommand::Quotes(RequestQuotes {
6556 instrument_ids,
6557 start,
6558 end,
6559 limit,
6560 client_id,
6561 request_id,
6562 ts_init: now.into(),
6563 params,
6564 });
6565
6566 self.register_request_handler(command.request_id(), handler)?;
6567
6568 self.send_data_cmd(DataCommand::Request(command));
6569
6570 Ok(request_id)
6571 }
6572
6573 #[expect(clippy::too_many_arguments)]
6579 pub fn request_trades(
6580 &self,
6581 instrument_ids: impl IntoInstrumentIds,
6582 start: Option<Timestamp>,
6583 end: Option<Timestamp>,
6584 limit: Option<NonZeroUsize>,
6585 client_id: Option<ClientId>,
6586 params: Option<Params>,
6587 handler: TypedHandler<DataResponseEnvelope>,
6588 ) -> anyhow::Result<UUID4> {
6589 self.check_registered();
6590
6591 let instrument_ids = instrument_ids.into_instrument_ids()?;
6592 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6593 anyhow::ensure!(
6594 instrument_ids.len() == 1 || limit.is_none(),
6595 "A record limit is unsupported for multi-instrument trades requests",
6596 );
6597
6598 let now = self.clock_ref().utc_now();
6599 check_timestamps(now, start, end)?;
6600
6601 let request_id = UUID4::new();
6602 let command = RequestCommand::Trades(RequestTrades {
6603 instrument_ids,
6604 start,
6605 end,
6606 limit,
6607 client_id,
6608 request_id,
6609 ts_init: now.into(),
6610 params,
6611 });
6612
6613 self.register_request_handler(command.request_id(), handler)?;
6614
6615 self.send_data_cmd(DataCommand::Request(command));
6616
6617 Ok(request_id)
6618 }
6619
6620 #[expect(clippy::too_many_arguments)]
6626 pub fn request_bars(
6627 &self,
6628 bar_types: impl IntoBarTypes,
6629 start: Option<Timestamp>,
6630 end: Option<Timestamp>,
6631 limit: Option<NonZeroUsize>,
6632 client_id: Option<ClientId>,
6633 params: Option<Params>,
6634 handler: TypedHandler<DataResponseEnvelope>,
6635 ) -> anyhow::Result<UUID4> {
6636 self.check_registered();
6637
6638 let bar_types = bar_types.into_bar_types()?;
6639 ensure_routable_bar_types(&bar_types, client_id.is_some())?;
6640
6641 for bar_type in &bar_types {
6642 anyhow::ensure!(
6643 bar_type.is_standard(),
6644 "Composite bar types are not supported for `request_bars`, was {bar_type}; \
6645 request aggregation via the `bar_types` params instead",
6646 );
6647 }
6648
6649 let now = self.clock_ref().utc_now();
6650 check_timestamps(now, start, end)?;
6651
6652 let request_id = UUID4::new();
6653 let command = RequestCommand::Bars(RequestBars {
6654 bar_types,
6655 start,
6656 end,
6657 limit,
6658 client_id,
6659 request_id,
6660 ts_init: now.into(),
6661 params,
6662 });
6663
6664 self.register_request_handler(command.request_id(), handler)?;
6665
6666 self.send_data_cmd(DataCommand::Request(command));
6667
6668 Ok(request_id)
6669 }
6670
6671 #[expect(clippy::too_many_arguments)]
6677 pub fn request_funding_rates(
6678 &self,
6679 instrument_ids: impl IntoInstrumentIds,
6680 start: Option<Timestamp>,
6681 end: Option<Timestamp>,
6682 limit: Option<NonZeroUsize>,
6683 client_id: Option<ClientId>,
6684 params: Option<Params>,
6685 handler: TypedHandler<DataResponseEnvelope>,
6686 ) -> anyhow::Result<UUID4> {
6687 self.check_registered();
6688
6689 let instrument_ids = instrument_ids.into_instrument_ids()?;
6690 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6691
6692 let now = self.clock_ref().utc_now();
6693 check_timestamps(now, start, end)?;
6694
6695 let request_id = UUID4::new();
6696 let command = RequestCommand::FundingRates(RequestFundingRates {
6697 instrument_ids,
6698 start,
6699 end,
6700 limit,
6701 client_id,
6702 request_id,
6703 ts_init: now.into(),
6704 params,
6705 });
6706
6707 self.register_request_handler(command.request_id(), handler)?;
6708
6709 self.send_data_cmd(DataCommand::Request(command));
6710
6711 Ok(request_id)
6712 }
6713
6714 fn register_request_handler(
6715 &self,
6716 request_id: &UUID4,
6717 handler: TypedHandler<DataResponseEnvelope>,
6718 ) -> anyhow::Result<()> {
6719 get_message_bus()
6720 .borrow_mut()
6721 .register_response_handler(request_id, handler)?;
6722 self.requests.borrow_mut().register(*request_id);
6723 Ok(())
6724 }
6725
6726 #[must_use]
6728 pub fn is_pending_request(&self, request_id: &UUID4) -> bool {
6729 self.requests.borrow().is_pending(request_id)
6730 }
6731
6732 #[must_use]
6734 pub fn pending_requests(&self) -> Vec<UUID4> {
6735 self.requests.borrow().pending()
6736 }
6737
6738 pub(crate) fn register_request_group(
6739 &self,
6740 request_ids: Vec<UUID4>,
6741 request_id: Option<UUID4>,
6742 handler: Option<TypedHandler<DataResponseEnvelope>>,
6743 ) -> anyhow::Result<UUID4> {
6744 self.requests
6745 .borrow_mut()
6746 .register_group(request_ids, request_id, handler)
6747 }
6748
6749 pub(crate) fn begin_response(&self, request_id: &UUID4) -> Option<ResponseScope> {
6750 self.requests.borrow().begin(request_id)
6751 }
6752
6753 #[cfg(feature = "python")]
6754 pub(crate) fn is_response_current(&self, scope: &ResponseScope) -> bool {
6755 self.requests.borrow().is_current(scope)
6756 }
6757
6758 pub(crate) fn complete_request(
6759 &self,
6760 response: &DataResponseEnvelope,
6761 scope: &ResponseScope,
6762 ) -> bool {
6763 self.requests.borrow_mut().complete(response, scope)
6764 }
6765
6766 pub(crate) fn next_group_response(
6767 &self,
6768 ) -> Option<(
6769 DataResponseEnvelope,
6770 Option<TypedHandler<DataResponseEnvelope>>,
6771 )> {
6772 self.requests.borrow_mut().next_group_response()
6773 }
6774
6775 fn clear_pending_requests(&self) {
6776 let (ids, mut handlers) = self.requests.borrow_mut().clear();
6777 {
6778 let bus = get_message_bus();
6779 let mut bus = bus.borrow_mut();
6780 handlers.extend(ids.iter().filter_map(|id| bus.take_response_handler(id)));
6781 }
6782
6783 drop(handlers);
6784 }
6785
6786 #[cfg(feature = "live")]
6793 pub fn reconnect_socket(&self, client_id: ClientId, endpoint: &str) -> anyhow::Result<()> {
6794 let endpoint = socket_endpoint(endpoint)?;
6795
6796 if !self.is_properly_registered() {
6797 anyhow::bail!(
6798 "Actor {} has not been registered with a Trader",
6799 self.actor_id
6800 );
6801 }
6802
6803 let sender = try_get_system_command_sender()
6804 .ok_or_else(|| anyhow::anyhow!("Live runner system command channel is unavailable"))?;
6805 let trader_id = self
6806 .trader_id
6807 .ok_or_else(|| anyhow::anyhow!("Actor {} has no trader ID", self.actor_id))?;
6808 let command = ReconnectSocket::new(trader_id, client_id, endpoint, self.timestamp_ns());
6809 sender
6810 .send(SystemCommand::ReconnectSocket(command))
6811 .map_err(|_| anyhow::anyhow!("Live runner system command channel is closed"))?;
6812 Ok(())
6813 }
6814
6815 #[cfg(test)]
6816 pub fn quote_handler_count(&self) -> usize {
6817 self.quote_handlers.len()
6818 }
6819
6820 #[cfg(test)]
6821 pub fn trade_handler_count(&self) -> usize {
6822 self.trade_handlers.len()
6823 }
6824
6825 #[cfg(test)]
6826 pub fn bar_handler_count(&self) -> usize {
6827 self.bar_handlers.len()
6828 }
6829
6830 #[cfg(test)]
6831 pub fn deltas_handler_count(&self) -> usize {
6832 self.deltas_handlers.len()
6833 }
6834
6835 #[cfg(test)]
6836 pub fn depth_handler_count(&self) -> usize {
6837 self.depth_handlers.len()
6838 }
6839
6840 #[cfg(test)]
6841 pub fn has_quote_handler(&self, topic: &str) -> bool {
6842 self.quote_handlers
6843 .contains_key(&MStr::<Topic>::from(topic))
6844 }
6845
6846 #[cfg(test)]
6847 pub fn has_trade_handler(&self, topic: &str) -> bool {
6848 self.trade_handlers
6849 .contains_key(&MStr::<Topic>::from(topic))
6850 }
6851
6852 #[cfg(test)]
6853 pub fn has_bar_handler(&self, topic: &str) -> bool {
6854 self.bar_handlers.contains_key(&MStr::<Topic>::from(topic))
6855 }
6856
6857 #[cfg(test)]
6858 pub fn has_deltas_handler(&self, pattern: &str) -> bool {
6859 self.deltas_handlers
6860 .contains_key(&MStr::<Pattern>::from(pattern))
6861 }
6862
6863 #[cfg(test)]
6864 pub fn has_depth_handler(&self, pattern: &str) -> bool {
6865 self.depth_handlers
6866 .contains_key(&MStr::<Pattern>::from(pattern))
6867 }
6868}
6869
6870impl DataActorNative for DataActorCore {
6871 fn core(&self) -> &DataActorCore {
6872 self
6873 }
6874
6875 fn core_mut(&mut self) -> &mut DataActorCore {
6876 self
6877 }
6878}
6879
6880fn check_timestamps(
6881 now: Timestamp,
6882 start: Option<Timestamp>,
6883 end: Option<Timestamp>,
6884) -> anyhow::Result<()> {
6885 if let Some(start) = start {
6886 check_predicate_true(start <= now, "start was > now")?;
6887 }
6888
6889 if let Some(end) = end {
6890 check_predicate_true(end <= now, "end was > now")?;
6891 }
6892
6893 if let (Some(start), Some(end)) = (start, end) {
6894 check_predicate_true(start <= end, "start was > end")?;
6895 }
6896
6897 Ok(())
6898}
6899
6900fn log_error(e: &anyhow::Error) {
6901 log::error!("{e}");
6902}
6903
6904fn log_not_running<T>(msg: &T)
6905where
6906 T: Debug,
6907{
6908 log::trace!("Received message when not running - skipping {msg:?}");
6909}
6910
6911fn log_received<T>(msg: &T)
6912where
6913 T: Debug,
6914{
6915 log::debug!("{RECV} {msg:?}");
6916}
6917
6918fn log_received_bulk(kind: &str, correlation_id: &UUID4, records: usize) {
6919 log::debug!("{RECV} {kind} correlation_id={correlation_id} records={records}");
6920}