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 dispatch_start(&mut self) -> anyhow::Result<()>
194 where
195 Self: DataActor,
196 {
197 DataActor::on_start(self)
198 }
199
200 fn clock_mut(&mut self) -> RefMut<'_, dyn Clock> {
206 self.try_clock_mut().unwrap_or_else(|e| panic!("{e}"))
207 }
208
209 fn try_clock_mut(&mut self) -> Result<RefMut<'_, dyn Clock>, ComponentAccessError> {
215 self.core_mut()
216 .clock
217 .as_ref()
218 .ok_or(ComponentAccessError::NotRegistered {
219 resource: "clock",
220 operation: "clock_mut",
221 })?
222 .try_borrow_mut()
223 .map_err(|_| ComponentAccessError::WriteConflict {
224 resource: "clock",
225 operation: "clock_mut",
226 })
227 }
228
229 fn clock_rc(&self) -> Rc<RefCell<dyn Clock>> {
235 self.core()
236 .clock
237 .as_ref()
238 .expect("DataActor must be registered before accessing clock")
239 .clone()
240 }
241
242 fn cache_ref(&self) -> Ref<'_, Cache> {
248 self.try_cache_ref().unwrap_or_else(|e| panic!("{e}"))
249 }
250
251 fn try_cache_ref(&self) -> Result<Ref<'_, Cache>, ComponentAccessError> {
257 self.core()
258 .cache
259 .as_ref()
260 .ok_or(ComponentAccessError::NotRegistered {
261 resource: "cache",
262 operation: "cache_ref",
263 })?
264 .try_borrow()
265 .map_err(|_| ComponentAccessError::ReadConflict {
266 resource: "cache",
267 operation: "cache_ref",
268 })
269 }
270
271 fn cache_rc(&self) -> Rc<RefCell<Cache>> {
277 self.core()
278 .cache
279 .as_ref()
280 .expect("DataActor must be registered before accessing cache")
281 .clone()
282 }
283}
284
285pub trait DataActor {
292 fn actor_id(&self) -> ActorId
298 where
299 Self: DataActorBinding,
300 {
301 self.binding_actor_id()
302 }
303
304 fn trader_id(&self) -> Option<TraderId>
306 where
307 Self: DataActorNative,
308 {
309 self.core().trader_id()
310 }
311
312 fn is_registered(&self) -> bool
314 where
315 Self: DataActorNative,
316 {
317 self.core().is_registered()
318 }
319
320 fn config(&self) -> &DataActorConfig
322 where
323 Self: DataActorNative,
324 {
325 &self.core().config
326 }
327
328 fn on_save(&self) -> anyhow::Result<IndexMap<String, Vec<u8>>> {
334 Ok(IndexMap::new())
335 }
336
337 #[allow(unused_variables)]
343 fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {
344 Ok(())
345 }
346
347 fn on_start(&mut self) -> anyhow::Result<()> {
353 log::warn!(
354 "The `on_start` handler was called when not overridden, \
355 it's expected that any actions required when starting the actor \
356 occur here, such as subscribing/requesting data"
357 );
358 Ok(())
359 }
360
361 fn on_stop(&mut self) -> anyhow::Result<()> {
367 log::warn!(
368 "The `on_stop` handler was called when not overridden, \
369 it's expected that any actions required when stopping the actor \
370 occur here, such as unsubscribing from data",
371 );
372 Ok(())
373 }
374
375 fn on_resume(&mut self) -> anyhow::Result<()> {
381 log::warn!(
382 "The `on_resume` handler was called when not overridden, \
383 it's expected that any actions required when resuming the actor \
384 following a stop occur here"
385 );
386 Ok(())
387 }
388
389 fn on_reset(&mut self) -> anyhow::Result<()> {
395 log::warn!(
396 "The `on_reset` handler was called when not overridden, \
397 it's expected that any actions required when resetting the actor \
398 occur here, such as resetting indicators and other state"
399 );
400 Ok(())
401 }
402
403 fn on_dispose(&mut self) -> anyhow::Result<()> {
409 Ok(())
410 }
411
412 fn on_degrade(&mut self) -> anyhow::Result<()> {
418 Ok(())
419 }
420
421 fn on_fault(&mut self) -> anyhow::Result<()> {
427 Ok(())
428 }
429
430 #[allow(unused_variables)]
436 fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
437 Ok(())
438 }
439
440 #[allow(unused_variables)]
446 fn on_data(&mut self, data: &CustomData) -> anyhow::Result<()> {
447 Ok(())
448 }
449
450 #[allow(unused_variables)]
456 fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
457 Ok(())
458 }
459
460 #[allow(unused_variables)]
466 fn on_queue_state(&mut self, event: &QueueStateChanged) -> anyhow::Result<()> {
467 Ok(())
468 }
469
470 #[allow(unused_variables)]
476 fn on_socket_state(&mut self, event: &SocketStateChanged) -> anyhow::Result<()> {
477 Ok(())
478 }
479
480 #[allow(unused_variables)]
486 fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
487 Ok(())
488 }
489
490 #[allow(unused_variables)]
496 fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
497 Ok(())
498 }
499
500 #[allow(unused_variables)]
506 fn on_book_depth(&mut self, depth: &OrderBookDepth) -> anyhow::Result<()> {
507 Ok(())
508 }
509
510 #[allow(unused_variables)]
516 fn on_book(&mut self, order_book: &OrderBook) -> anyhow::Result<()> {
517 Ok(())
518 }
519
520 #[allow(unused_variables)]
526 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
527 Ok(())
528 }
529
530 #[allow(unused_variables)]
536 fn on_trade(&mut self, tick: &TradeTick) -> anyhow::Result<()> {
537 Ok(())
538 }
539
540 #[allow(unused_variables)]
546 fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
547 Ok(())
548 }
549
550 #[allow(unused_variables)]
556 fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
557 Ok(())
558 }
559
560 #[allow(unused_variables)]
566 fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
567 Ok(())
568 }
569
570 #[allow(unused_variables)]
576 fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
577 Ok(())
578 }
579
580 #[allow(unused_variables)]
586 fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
587 Ok(())
588 }
589
590 #[allow(unused_variables)]
596 fn on_option_chain(&mut self, slice: &OptionChainSlice) -> anyhow::Result<()> {
597 Ok(())
598 }
599
600 #[allow(unused_variables)]
606 fn on_instrument_status(&mut self, data: &InstrumentStatus) -> anyhow::Result<()> {
607 Ok(())
608 }
609
610 #[allow(unused_variables)]
616 fn on_instrument_close(&mut self, update: &InstrumentClose) -> anyhow::Result<()> {
617 Ok(())
618 }
619
620 #[cfg(feature = "defi")]
621 #[allow(unused_variables)]
627 fn on_block(&mut self, block: &Block) -> anyhow::Result<()> {
628 Ok(())
629 }
630
631 #[cfg(feature = "defi")]
632 #[allow(unused_variables)]
638 fn on_pool(&mut self, pool: &Pool) -> anyhow::Result<()> {
639 Ok(())
640 }
641
642 #[cfg(feature = "defi")]
643 #[allow(unused_variables)]
649 fn on_pool_swap(&mut self, swap: &PoolSwap) -> anyhow::Result<()> {
650 Ok(())
651 }
652
653 #[cfg(feature = "defi")]
654 #[allow(unused_variables)]
660 fn on_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate) -> anyhow::Result<()> {
661 Ok(())
662 }
663
664 #[cfg(feature = "defi")]
665 #[allow(unused_variables)]
671 fn on_pool_fee_collect(&mut self, collect: &PoolFeeCollect) -> anyhow::Result<()> {
672 Ok(())
673 }
674
675 #[cfg(feature = "defi")]
676 #[allow(unused_variables)]
682 fn on_pool_flash(&mut self, flash: &PoolFlash) -> anyhow::Result<()> {
683 Ok(())
684 }
685
686 #[allow(unused_variables)]
695 fn on_historical_data(&mut self, data: &dyn Any) -> anyhow::Result<()> {
696 Ok(())
697 }
698
699 #[allow(unused_variables)]
705 fn on_historical_book_deltas(&mut self, deltas: &[OrderBookDelta]) -> anyhow::Result<()> {
706 Ok(())
707 }
708
709 #[allow(unused_variables)]
715 fn on_historical_book_depth(&mut self, depths: &[OrderBookDepth]) -> anyhow::Result<()> {
716 Ok(())
717 }
718
719 #[allow(unused_variables)]
725 fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
726 Ok(())
727 }
728
729 #[allow(unused_variables)]
735 fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
736 Ok(())
737 }
738
739 #[allow(unused_variables)]
745 fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
746 Ok(())
747 }
748
749 #[allow(unused_variables)]
755 fn on_historical_mark_prices(&mut self, mark_prices: &[MarkPriceUpdate]) -> anyhow::Result<()> {
756 Ok(())
757 }
758
759 #[allow(unused_variables)]
765 fn on_historical_index_prices(
766 &mut self,
767 index_prices: &[IndexPriceUpdate],
768 ) -> anyhow::Result<()> {
769 Ok(())
770 }
771
772 #[allow(unused_variables)]
778 fn on_historical_funding_rates(
779 &mut self,
780 funding_rates: &[FundingRateUpdate],
781 ) -> anyhow::Result<()> {
782 Ok(())
783 }
784
785 fn clock(&self) -> ClockApi<'_>
792 where
793 Self: DataActorBinding,
794 {
795 self.binding_clock()
796 }
797
798 fn cache(&self) -> CacheApi<'_>
800 where
801 Self: DataActorNative,
802 {
803 self.core().cache_api()
804 }
805
806 fn shutdown_system(&self, reason: Option<String>)
812 where
813 Self: DataActorNative,
814 {
815 self.core().shutdown_system(reason);
816 }
817
818 fn publish_data(&self, data_type: &DataType, data: &CustomData)
827 where
828 Self: DataActorNative,
829 {
830 self.core().publish_data(data_type, data);
831 }
832
833 fn publish_signal(&self, name: &str, value: String, ts_event: UnixNanos)
839 where
840 Self: DataActorNative,
841 {
842 self.core().publish_signal(name, value, ts_event);
843 }
844
845 fn add_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()>
859 where
860 Self: DataActorNative,
861 {
862 self.core().add_synthetic(synthetic)
863 }
864
865 fn update_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()>
879 where
880 Self: DataActorNative,
881 {
882 self.core().update_synthetic(synthetic)
883 }
884
885 fn handle_time_event(&mut self, event: &TimeEvent)
887 where
888 Self: Component,
889 {
890 log_received(&event);
891
892 if self.not_running() {
893 log_not_running(&event);
894 return;
895 }
896
897 if let Err(e) = DataActor::on_time_event(self, event) {
898 log_error(&e);
899 }
900 }
901
902 fn handle_data(&mut self, data: &CustomData)
904 where
905 Self: Component,
906 {
907 log_received(&data);
908
909 if self.not_running() {
910 log_not_running(&data);
911 return;
912 }
913
914 if let Err(e) = self.on_data(data) {
915 log_error(&e);
916 }
917 }
918
919 fn handle_signal(&mut self, signal: &Signal)
921 where
922 Self: Component,
923 {
924 log_received(&signal);
925
926 if self.not_running() {
927 log_not_running(&signal);
928 return;
929 }
930
931 if let Err(e) = self.on_signal(signal) {
932 log_error(&e);
933 }
934 }
935
936 fn handle_queue_state(&mut self, event: &QueueStateChanged)
938 where
939 Self: Component,
940 {
941 log_received(&event);
942
943 if self.not_running() {
944 log_not_running(&event);
945 return;
946 }
947
948 if let Err(e) = self.on_queue_state(event) {
949 log_error(&e);
950 }
951 }
952
953 fn handle_socket_state(&mut self, event: &SocketStateChanged)
955 where
956 Self: Component,
957 {
958 log_received(&event);
959
960 if self.not_running() {
961 log_not_running(&event);
962 return;
963 }
964
965 if let Err(e) = self.on_socket_state(event) {
966 log_error(&e);
967 }
968 }
969
970 fn handle_instrument(&mut self, instrument: &InstrumentAny)
972 where
973 Self: Component,
974 {
975 log_received(&instrument);
976
977 if self.not_running() {
978 log_not_running(&instrument);
979 return;
980 }
981
982 if let Err(e) = self.on_instrument(instrument) {
983 log_error(&e);
984 }
985 }
986
987 fn handle_book_deltas(&mut self, deltas: &OrderBookDeltas)
989 where
990 Self: Component,
991 {
992 log_received(&deltas);
993
994 if self.not_running() {
995 log_not_running(&deltas);
996 return;
997 }
998
999 if let Err(e) = self.on_book_deltas(deltas) {
1000 log_error(&e);
1001 }
1002 }
1003
1004 fn handle_book_depth(&mut self, depth: &OrderBookDepth)
1006 where
1007 Self: Component,
1008 {
1009 log_received(&depth);
1010
1011 if self.not_running() {
1012 log_not_running(&depth);
1013 return;
1014 }
1015
1016 if let Err(e) = self.on_book_depth(depth) {
1017 log_error(&e);
1018 }
1019 }
1020
1021 fn handle_book(&mut self, book: &OrderBook)
1023 where
1024 Self: Component,
1025 {
1026 log_received(&book);
1027
1028 if self.not_running() {
1029 log_not_running(&book);
1030 return;
1031 }
1032
1033 if let Err(e) = self.on_book(book) {
1034 log_error(&e);
1035 }
1036 }
1037
1038 fn handle_quote(&mut self, quote: &QuoteTick)
1040 where
1041 Self: DataActorNative + Component,
1042 {
1043 log_received("e);
1044
1045 if let Err(e) = self.core().handle_indicators_for_quote(quote) {
1046 log_error(&e);
1047 return;
1048 }
1049
1050 if self.not_running() {
1051 log_not_running("e);
1052 return;
1053 }
1054
1055 if let Err(e) = self.on_quote(quote) {
1056 log_error(&e);
1057 }
1058 }
1059
1060 fn handle_trade(&mut self, trade: &TradeTick)
1062 where
1063 Self: DataActorNative + Component,
1064 {
1065 log_received(&trade);
1066
1067 if let Err(e) = self.core().handle_indicators_for_trade(trade) {
1068 log_error(&e);
1069 return;
1070 }
1071
1072 if self.not_running() {
1073 log_not_running(&trade);
1074 return;
1075 }
1076
1077 if let Err(e) = self.on_trade(trade) {
1078 log_error(&e);
1079 }
1080 }
1081
1082 fn handle_bar(&mut self, bar: &Bar)
1084 where
1085 Self: DataActorNative + Component,
1086 {
1087 log_received(&bar);
1088
1089 if let Err(e) = self.core().handle_indicators_for_bar(bar) {
1090 log_error(&e);
1091 return;
1092 }
1093
1094 if self.not_running() {
1095 log_not_running(&bar);
1096 return;
1097 }
1098
1099 if let Err(e) = self.on_bar(bar) {
1100 log_error(&e);
1101 }
1102 }
1103
1104 fn handle_mark_price(&mut self, mark_price: &MarkPriceUpdate)
1106 where
1107 Self: Component,
1108 {
1109 log_received(&mark_price);
1110
1111 if self.not_running() {
1112 log_not_running(&mark_price);
1113 return;
1114 }
1115
1116 if let Err(e) = self.on_mark_price(mark_price) {
1117 log_error(&e);
1118 }
1119 }
1120
1121 fn handle_index_price(&mut self, index_price: &IndexPriceUpdate)
1123 where
1124 Self: Component,
1125 {
1126 log_received(&index_price);
1127
1128 if self.not_running() {
1129 log_not_running(&index_price);
1130 return;
1131 }
1132
1133 if let Err(e) = self.on_index_price(index_price) {
1134 log_error(&e);
1135 }
1136 }
1137
1138 fn handle_funding_rate(&mut self, funding_rate: &FundingRateUpdate)
1140 where
1141 Self: Component,
1142 {
1143 log_received(&funding_rate);
1144
1145 if self.not_running() {
1146 log_not_running(&funding_rate);
1147 return;
1148 }
1149
1150 if let Err(e) = self.on_funding_rate(funding_rate) {
1151 log_error(&e);
1152 }
1153 }
1154
1155 fn handle_option_greeks(&mut self, greeks: &OptionGreeks)
1157 where
1158 Self: Component,
1159 {
1160 log_received(&greeks);
1161
1162 if self.not_running() {
1163 log_not_running(&greeks);
1164 return;
1165 }
1166
1167 if let Err(e) = self.on_option_greeks(greeks) {
1168 log_error(&e);
1169 }
1170 }
1171
1172 fn handle_option_chain(&mut self, slice: &OptionChainSlice)
1174 where
1175 Self: Component,
1176 {
1177 log_received(&slice);
1178
1179 if self.not_running() {
1180 log_not_running(&slice);
1181 return;
1182 }
1183
1184 if let Err(e) = self.on_option_chain(slice) {
1185 log_error(&e);
1186 }
1187 }
1188
1189 fn handle_instrument_status(&mut self, status: &InstrumentStatus)
1191 where
1192 Self: Component,
1193 {
1194 log_received(&status);
1195
1196 if self.not_running() {
1197 log_not_running(&status);
1198 return;
1199 }
1200
1201 if let Err(e) = self.on_instrument_status(status) {
1202 log_error(&e);
1203 }
1204 }
1205
1206 fn handle_instrument_close(&mut self, close: &InstrumentClose)
1208 where
1209 Self: Component,
1210 {
1211 log_received(&close);
1212
1213 if self.not_running() {
1214 log_not_running(&close);
1215 return;
1216 }
1217
1218 if let Err(e) = self.on_instrument_close(close) {
1219 log_error(&e);
1220 }
1221 }
1222
1223 #[cfg(feature = "defi")]
1224 fn handle_block(&mut self, block: &Block)
1226 where
1227 Self: Component,
1228 {
1229 log_received(&block);
1230
1231 if self.not_running() {
1232 log_not_running(&block);
1233 return;
1234 }
1235
1236 if let Err(e) = self.on_block(block) {
1237 log_error(&e);
1238 }
1239 }
1240
1241 #[cfg(feature = "defi")]
1242 fn handle_pool(&mut self, pool: &Pool)
1244 where
1245 Self: Component,
1246 {
1247 log_received(&pool);
1248
1249 if self.not_running() {
1250 log_not_running(&pool);
1251 return;
1252 }
1253
1254 if let Err(e) = self.on_pool(pool) {
1255 log_error(&e);
1256 }
1257 }
1258
1259 #[cfg(feature = "defi")]
1260 fn handle_pool_swap(&mut self, swap: &PoolSwap)
1262 where
1263 Self: Component,
1264 {
1265 log_received(&swap);
1266
1267 if self.not_running() {
1268 log_not_running(&swap);
1269 return;
1270 }
1271
1272 if let Err(e) = self.on_pool_swap(swap) {
1273 log_error(&e);
1274 }
1275 }
1276
1277 #[cfg(feature = "defi")]
1278 fn handle_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate)
1280 where
1281 Self: Component,
1282 {
1283 log_received(&update);
1284
1285 if self.not_running() {
1286 log_not_running(&update);
1287 return;
1288 }
1289
1290 if let Err(e) = self.on_pool_liquidity_update(update) {
1291 log_error(&e);
1292 }
1293 }
1294
1295 #[cfg(feature = "defi")]
1296 fn handle_pool_fee_collect(&mut self, collect: &PoolFeeCollect)
1298 where
1299 Self: Component,
1300 {
1301 log_received(&collect);
1302
1303 if self.not_running() {
1304 log_not_running(&collect);
1305 return;
1306 }
1307
1308 if let Err(e) = self.on_pool_fee_collect(collect) {
1309 log_error(&e);
1310 }
1311 }
1312
1313 #[cfg(feature = "defi")]
1314 fn handle_pool_flash(&mut self, flash: &PoolFlash)
1316 where
1317 Self: Component,
1318 {
1319 log_received(&flash);
1320
1321 if self.not_running() {
1322 log_not_running(&flash);
1323 return;
1324 }
1325
1326 if let Err(e) = self.on_pool_flash(flash) {
1327 log_error(&e);
1328 }
1329 }
1330
1331 fn handle_historical_data(&mut self, data: &dyn Any) {
1333 log_received(&data);
1334
1335 if let Err(e) = self.on_historical_data(data) {
1336 log_error(&e);
1337 }
1338 }
1339
1340 fn handle_response(&mut self, envelope: &DataResponseEnvelope)
1342 where
1343 Self: DataActorNative,
1344 {
1345 let Some(scope) = self.core().begin_response(envelope.correlation_id()) else {
1346 return;
1347 };
1348
1349 if let Some(response) = envelope.response() {
1350 match response {
1351 DataResponse::Data(r) => self.handle_data_response(r),
1352 DataResponse::Instrument(r) => self.handle_instrument_response(r),
1353 DataResponse::Instruments(r) => self.handle_instruments_response(r),
1354 DataResponse::Book(r) => self.handle_book_response(r),
1355 DataResponse::BookDeltas(r) => self.handle_book_deltas_response(r),
1356 DataResponse::BookDepth(r) => self.handle_book_depth_response(r),
1357 DataResponse::Quotes(r) => self.handle_quotes_response(r),
1358 DataResponse::Trades(r) => self.handle_trades_response(r),
1359 DataResponse::FundingRates(r) => self.handle_funding_rates_response(r),
1360 DataResponse::Bars(r) => self.handle_bars_response(r),
1361 DataResponse::OptionChainReferencePrice(_) => {}
1362 }
1363 }
1364
1365 if self.core().complete_request(envelope, &scope)
1366 && let Err(e) = self.on_data_response(envelope)
1367 {
1368 log_error(&e);
1369 }
1370
1371 drop(scope);
1372 self.drain_request_groups();
1373 }
1374
1375 #[doc(hidden)]
1377 fn drain_request_groups(&mut self)
1378 where
1379 Self: DataActorNative,
1380 {
1381 while let Some((envelope, _handler)) = self.core().next_group_response() {
1382 let Some(scope) = self.core().begin_response(envelope.correlation_id()) else {
1383 continue;
1384 };
1385
1386 if self.core().complete_request(&envelope, &scope)
1387 && let Err(e) = self.on_data_response(&envelope)
1388 {
1389 log_error(&e);
1390 }
1391 }
1392 }
1393
1394 fn on_data_response(&mut self, _response: &DataResponseEnvelope) -> anyhow::Result<()> {
1403 Ok(())
1404 }
1405
1406 #[doc(hidden)]
1408 fn response_handler(&self) -> TypedHandler<DataResponseEnvelope>
1409 where
1410 Self: DataActorNative + Debug + Sized + 'static,
1411 {
1412 let actor_id = self.core().actor_id().inner();
1413 TypedHandler::from(move |response: &DataResponseEnvelope| {
1414 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1415 actor.handle_response(response);
1416 } else {
1417 log::error!("Actor {actor_id} not found for response handling");
1418 }
1419 })
1420 }
1421
1422 fn handle_data_response(&mut self, resp: &CustomDataResponse) {
1424 if let Some(data) = resp.data.as_ref().downcast_ref::<Vec<CustomData>>() {
1425 log_received_bulk("CustomDataResponse", &resp.correlation_id, data.len());
1426 log::trace!("{RECV} {resp:?}");
1427 } else {
1428 log_received(&resp);
1429 }
1430
1431 if let Err(e) = self.on_historical_data(resp.data.as_ref()) {
1432 log_error(&e);
1433 }
1434 }
1435
1436 fn handle_instrument_response(&mut self, resp: &InstrumentResponse) {
1438 log_received(&resp);
1439
1440 if let Err(e) = self.on_instrument(&resp.data) {
1441 log_error(&e);
1442 }
1443 }
1444
1445 fn handle_instruments_response(&mut self, resp: &InstrumentsResponse) {
1447 log_received_bulk("InstrumentsResponse", &resp.correlation_id, resp.data.len());
1448 log::trace!("{RECV} {resp:?}");
1449
1450 for inst in &resp.data {
1451 if let Err(e) = self.on_instrument(inst) {
1452 log_error(&e);
1453 }
1454 }
1455 }
1456
1457 fn handle_book_response(&mut self, resp: &BookResponse) {
1459 log_received(&resp);
1460
1461 if let Err(e) = self.on_book(&resp.data) {
1462 log_error(&e);
1463 }
1464 }
1465
1466 fn handle_book_deltas_response(&mut self, resp: &BookDeltasResponse) {
1468 log_received_bulk("BookDeltasResponse", &resp.correlation_id, resp.data.len());
1469 log::trace!("{RECV} {resp:?}");
1470
1471 if let Err(e) = self.on_historical_book_deltas(&resp.data) {
1472 log_error(&e);
1473 }
1474 }
1475
1476 fn handle_book_depth_response(&mut self, resp: &BookDepthResponse) {
1478 log_received_bulk("BookDepthResponse", &resp.correlation_id, resp.data.len());
1479 log::trace!("{RECV} {resp:?}");
1480
1481 if let Err(e) = self.on_historical_book_depth(&resp.data) {
1482 log_error(&e);
1483 }
1484 }
1485
1486 fn handle_quotes_response(&mut self, resp: &QuotesResponse)
1488 where
1489 Self: DataActorNative,
1490 {
1491 log_received_bulk("QuotesResponse", &resp.correlation_id, resp.data.len());
1492 log::trace!("{RECV} {resp:?}");
1493
1494 if let Err(e) = self.core().handle_indicators_for_quotes(&resp.data) {
1495 log_error(&e);
1496 return;
1497 }
1498
1499 if let Err(e) = self.on_historical_quotes(&resp.data) {
1500 log_error(&e);
1501 }
1502 }
1503
1504 fn handle_trades_response(&mut self, resp: &TradesResponse)
1506 where
1507 Self: DataActorNative,
1508 {
1509 log_received_bulk("TradesResponse", &resp.correlation_id, resp.data.len());
1510 log::trace!("{RECV} {resp:?}");
1511
1512 if let Err(e) = self.core().handle_indicators_for_trades(&resp.data) {
1513 log_error(&e);
1514 return;
1515 }
1516
1517 if let Err(e) = self.on_historical_trades(&resp.data) {
1518 log_error(&e);
1519 }
1520 }
1521
1522 fn handle_bars_response(&mut self, resp: &BarsResponse)
1524 where
1525 Self: DataActorNative,
1526 {
1527 log_received_bulk("BarsResponse", &resp.correlation_id, resp.data.len());
1528 log::trace!("{RECV} {resp:?}");
1529
1530 if let Err(e) = self.core().handle_indicators_for_bars(&resp.data) {
1531 log_error(&e);
1532 return;
1533 }
1534
1535 if let Err(e) = self.on_historical_bars(&resp.data) {
1536 log_error(&e);
1537 }
1538 }
1539
1540 fn handle_funding_rates_response(&mut self, resp: &FundingRatesResponse) {
1542 log_received_bulk(
1543 "FundingRatesResponse",
1544 &resp.correlation_id,
1545 resp.data.len(),
1546 );
1547 log::trace!("{RECV} {resp:?}");
1548
1549 if let Err(e) = self.on_historical_funding_rates(&resp.data) {
1550 log_error(&e);
1551 }
1552 }
1553
1554 fn subscribe_data(
1556 &mut self,
1557 data_type: DataType,
1558 client_id: Option<ClientId>,
1559 params: Option<Params>,
1560 ) where
1561 Self: DataActorNative,
1562 Self: 'static + Debug + Sized,
1563 {
1564 let actor_id = self.core().actor_id().inner();
1565 let handler = ShareableMessageHandler::from_typed(move |data: &CustomData| {
1566 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1567 actor.handle_data(data);
1568 } else {
1569 log::error!("Actor {actor_id} not found for data handling");
1570 }
1571 });
1572
1573 DataActorCore::subscribe_data(self.core_mut(), handler, data_type, client_id, params);
1574 }
1575
1576 fn subscribe_signal(&mut self, name: &str, priority: Option<u32>)
1591 where
1592 Self: DataActorNative,
1593 Self: 'static + Debug + Sized,
1594 {
1595 let actor_id = self.core().actor_id().inner();
1596 let handler = ShareableMessageHandler::from_typed(move |data: &CustomData| {
1599 if let Some(signal) = data.data.as_any().downcast_ref::<Signal>() {
1600 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1601 actor.handle_signal(signal);
1602 } else {
1603 log::error!("Actor {actor_id} not found for signal handling");
1604 }
1605 }
1606 });
1607
1608 DataActorCore::subscribe_signal(self.core_mut(), handler, name, priority);
1609 }
1610
1611 fn subscribe_queue_state(&mut self, channel: Option<SystemChannel>, priority: Option<u32>)
1619 where
1620 Self: DataActorNative,
1621 Self: 'static + Debug + Sized,
1622 {
1623 let actor_id = self.core().actor_id().inner();
1624 let handler = ShareableMessageHandler::from_typed(move |event: &QueueStateChanged| {
1625 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1626 actor.handle_queue_state(event);
1627 } else {
1628 log::error!("Actor {actor_id} not found for queue state change handling");
1629 }
1630 });
1631
1632 DataActorCore::subscribe_queue_state(self.core_mut(), handler, channel, priority);
1633 }
1634
1635 fn subscribe_socket_state(
1644 &mut self,
1645 client_id: Option<ClientId>,
1646 endpoint: Option<&str>,
1647 priority: Option<u32>,
1648 ) where
1649 Self: DataActorNative,
1650 Self: 'static + Debug + Sized,
1651 {
1652 let actor_id = self.core().actor_id().inner();
1653 let handler = ShareableMessageHandler::from_typed(move |event: &SocketStateChanged| {
1654 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1655 actor.handle_socket_state(event);
1656 } else {
1657 log::error!("Actor {actor_id} not found for socket state change handling");
1658 }
1659 });
1660
1661 DataActorCore::subscribe_socket_state(
1662 self.core_mut(),
1663 handler,
1664 client_id,
1665 endpoint,
1666 priority,
1667 );
1668 }
1669
1670 fn subscribe_quotes(
1681 &mut self,
1682 instrument_ids: impl IntoInstrumentIds,
1683 client_id: Option<ClientId>,
1684 params: Option<Params>,
1685 ) where
1686 Self: DataActorNative,
1687 Self: 'static + Debug + Sized,
1688 {
1689 let actor_id = self.core().actor_id().inner();
1690 let handler = TypedHandler::from(move |quote: &QuoteTick| {
1691 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1692 actor.handle_quote(quote);
1693 } else {
1694 log::error!("Actor {actor_id} not found for quote handling");
1695 }
1696 });
1697
1698 DataActorCore::subscribe_quotes(
1699 self.core_mut(),
1700 handler,
1701 instrument_ids,
1702 client_id,
1703 params,
1704 )
1705 .expect("invalid quote subscription identifier list");
1706 }
1707
1708 fn subscribe_instruments(
1710 &mut self,
1711 venue: Venue,
1712 client_id: Option<ClientId>,
1713 params: Option<Params>,
1714 ) where
1715 Self: DataActorNative,
1716 Self: 'static + Debug + Sized,
1717 {
1718 let actor_id = self.core().actor_id().inner();
1719 let pattern = get_instruments_pattern(venue);
1720
1721 let handler = TypedHandler::from(move |instrument: &InstrumentAny| {
1722 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1723 actor.handle_instrument(instrument);
1724 } else {
1725 log::error!("Actor {actor_id} not found for instruments handling");
1726 }
1727 });
1728
1729 DataActorCore::subscribe_instruments(
1730 self.core_mut(),
1731 pattern,
1732 handler,
1733 venue,
1734 client_id,
1735 params,
1736 );
1737 }
1738
1739 fn subscribe_instrument(
1750 &mut self,
1751 instrument_ids: impl IntoInstrumentIds,
1752 client_id: Option<ClientId>,
1753 params: Option<Params>,
1754 ) where
1755 Self: DataActorNative,
1756 Self: 'static + Debug + Sized,
1757 {
1758 let actor_id = self.core().actor_id().inner();
1759 let handler = TypedHandler::from(move |instrument: &InstrumentAny| {
1760 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1761 actor.handle_instrument(instrument);
1762 } else {
1763 log::error!("Actor {actor_id} not found for instrument handling");
1764 }
1765 });
1766
1767 DataActorCore::subscribe_instrument(
1768 self.core_mut(),
1769 handler,
1770 instrument_ids,
1771 client_id,
1772 params,
1773 )
1774 .expect("invalid instrument subscription identifier list");
1775 }
1776
1777 fn subscribe_book_deltas(
1792 &mut self,
1793 instrument_ids: impl IntoInstrumentIds,
1794 book_type: BookType,
1795 depth: Option<NonZeroUsize>,
1796 client_id: Option<ClientId>,
1797 managed: bool,
1798 params: Option<Params>,
1799 ) where
1800 Self: DataActorNative,
1801 Self: 'static + Debug + Sized,
1802 {
1803 let actor_id = self.core().actor_id().inner();
1804 let handler = TypedHandler::from(move |deltas: &OrderBookDeltas| {
1805 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1806 actor.handle_book_deltas(deltas);
1807 } else {
1808 log::error!("Actor {actor_id} not found for book deltas handling");
1809 }
1810 });
1811
1812 DataActorCore::subscribe_book_deltas(
1813 self.core_mut(),
1814 handler,
1815 instrument_ids,
1816 book_type,
1817 depth,
1818 client_id,
1819 managed,
1820 params,
1821 )
1822 .expect("invalid instrument subscription identifier list");
1823 }
1824
1825 fn subscribe_book_depth(
1843 &mut self,
1844 instrument_ids: impl IntoInstrumentIds,
1845 book_type: BookType,
1846 depth: Option<NonZeroUsize>,
1847 client_id: Option<ClientId>,
1848 managed: bool,
1849 params: Option<Params>,
1850 ) where
1851 Self: DataActorNative,
1852 Self: 'static + Debug + Sized,
1853 {
1854 let actor_id = self.core().actor_id().inner();
1855 let handler = TypedHandler::from(move |depth: &OrderBookDepth| {
1856 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1857 actor.handle_book_depth(depth);
1858 } else {
1859 log::error!("Actor {actor_id} not found for book depth handling");
1860 }
1861 });
1862
1863 DataActorCore::subscribe_book_depth(
1864 self.core_mut(),
1865 handler,
1866 instrument_ids,
1867 book_type,
1868 depth,
1869 client_id,
1870 managed,
1871 params,
1872 )
1873 .expect("invalid instrument subscription identifier list");
1874 }
1875
1876 fn subscribe_book_at_interval(
1887 &mut self,
1888 instrument_ids: impl IntoInstrumentIds,
1889 book_type: BookType,
1890 depth: Option<NonZeroUsize>,
1891 interval_ms: NonZeroUsize,
1892 client_id: Option<ClientId>,
1893 params: Option<Params>,
1894 ) where
1895 Self: DataActorNative,
1896 Self: 'static + Debug + Sized,
1897 {
1898 let actor_id = self.core().actor_id().inner();
1899 let handler = TypedHandler::from(move |book: &OrderBook| {
1900 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1901 actor.handle_book(book);
1902 } else {
1903 log::error!("Actor {actor_id} not found for book handling");
1904 }
1905 });
1906
1907 DataActorCore::subscribe_book_at_interval(
1908 self.core_mut(),
1909 handler,
1910 instrument_ids,
1911 book_type,
1912 depth,
1913 interval_ms,
1914 client_id,
1915 params,
1916 )
1917 .expect("invalid instrument subscription identifier list");
1918 }
1919
1920 fn subscribe_trades(
1930 &mut self,
1931 instrument_ids: impl IntoInstrumentIds,
1932 client_id: Option<ClientId>,
1933 params: Option<Params>,
1934 ) where
1935 Self: DataActorNative,
1936 Self: 'static + Debug + Sized,
1937 {
1938 let actor_id = self.core().actor_id().inner();
1939 let handler = TypedHandler::from(move |trade: &TradeTick| {
1940 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1941 actor.handle_trade(trade);
1942 } else {
1943 log::error!("Actor {actor_id} not found for trade handling");
1944 }
1945 });
1946
1947 DataActorCore::subscribe_trades(
1948 self.core_mut(),
1949 handler,
1950 instrument_ids,
1951 client_id,
1952 params,
1953 )
1954 .expect("invalid trade subscription identifier list");
1955 }
1956
1957 fn subscribe_bars(
1965 &mut self,
1966 bar_types: impl IntoBarTypes,
1967 client_id: Option<ClientId>,
1968 params: Option<Params>,
1969 ) where
1970 Self: DataActorNative,
1971 Self: 'static + Debug + Sized,
1972 {
1973 let actor_id = self.core().actor_id().inner();
1974 let handler = TypedHandler::from(move |bar: &Bar| {
1975 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1976 actor.handle_bar(bar);
1977 } else {
1978 log::error!("Actor {actor_id} not found for bar handling");
1979 }
1980 });
1981
1982 DataActorCore::subscribe_bars(self.core_mut(), handler, bar_types, client_id, params)
1983 .expect("invalid bar subscription type list");
1984 }
1985
1986 fn subscribe_mark_prices(
1996 &mut self,
1997 instrument_ids: impl IntoInstrumentIds,
1998 client_id: Option<ClientId>,
1999 params: Option<Params>,
2000 ) where
2001 Self: DataActorNative,
2002 Self: 'static + Debug + Sized,
2003 {
2004 let actor_id = self.core().actor_id().inner();
2005 let handler = TypedHandler::from(move |mark_price: &MarkPriceUpdate| {
2006 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2007 actor.handle_mark_price(mark_price);
2008 } else {
2009 log::error!("Actor {actor_id} not found for mark price handling");
2010 }
2011 });
2012
2013 DataActorCore::subscribe_mark_prices(
2014 self.core_mut(),
2015 handler,
2016 instrument_ids,
2017 client_id,
2018 params,
2019 )
2020 .expect("invalid instrument subscription identifier list");
2021 }
2022
2023 fn subscribe_index_prices(
2033 &mut self,
2034 instrument_ids: impl IntoInstrumentIds,
2035 client_id: Option<ClientId>,
2036 params: Option<Params>,
2037 ) where
2038 Self: DataActorNative,
2039 Self: 'static + Debug + Sized,
2040 {
2041 let actor_id = self.core().actor_id().inner();
2042 let handler = TypedHandler::from(move |index_price: &IndexPriceUpdate| {
2043 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2044 actor.handle_index_price(index_price);
2045 } else {
2046 log::error!("Actor {actor_id} not found for index price handling");
2047 }
2048 });
2049
2050 DataActorCore::subscribe_index_prices(
2051 self.core_mut(),
2052 handler,
2053 instrument_ids,
2054 client_id,
2055 params,
2056 )
2057 .expect("invalid instrument subscription identifier list");
2058 }
2059
2060 fn subscribe_funding_rates(
2070 &mut self,
2071 instrument_ids: impl IntoInstrumentIds,
2072 client_id: Option<ClientId>,
2073 params: Option<Params>,
2074 ) where
2075 Self: DataActorNative,
2076 Self: 'static + Debug + Sized,
2077 {
2078 let actor_id = self.core().actor_id().inner();
2079 let handler = TypedHandler::from(move |funding_rate: &FundingRateUpdate| {
2080 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2081 actor.handle_funding_rate(funding_rate);
2082 } else {
2083 log::error!("Actor {actor_id} not found for funding rate handling");
2084 }
2085 });
2086
2087 DataActorCore::subscribe_funding_rates(
2088 self.core_mut(),
2089 handler,
2090 instrument_ids,
2091 client_id,
2092 params,
2093 )
2094 .expect("invalid instrument subscription identifier list");
2095 }
2096
2097 fn subscribe_option_greeks(
2108 &mut self,
2109 instrument_ids: impl IntoInstrumentIds,
2110 client_id: Option<ClientId>,
2111 params: Option<Params>,
2112 ) where
2113 Self: DataActorNative,
2114 Self: 'static + Debug + Sized,
2115 {
2116 let actor_id = self.core().actor_id().inner();
2117 let handler = TypedHandler::from(move |option_greeks: &OptionGreeks| {
2118 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2119 actor.handle_option_greeks(option_greeks);
2120 } else {
2121 log::error!("Actor {actor_id} not found for option greeks handling");
2122 }
2123 });
2124
2125 DataActorCore::subscribe_option_greeks(
2126 self.core_mut(),
2127 handler,
2128 instrument_ids,
2129 client_id,
2130 params,
2131 )
2132 .expect("invalid instrument subscription identifier list");
2133 }
2134
2135 fn subscribe_instrument_status(
2146 &mut self,
2147 instrument_ids: impl IntoInstrumentIds,
2148 client_id: Option<ClientId>,
2149 params: Option<Params>,
2150 ) where
2151 Self: DataActorNative,
2152 Self: 'static + Debug + Sized,
2153 {
2154 let actor_id = self.core().actor_id().inner();
2155 let handler = ShareableMessageHandler::from_typed(move |status: &InstrumentStatus| {
2156 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2157 actor.handle_instrument_status(status);
2158 } else {
2159 log::error!("Actor {actor_id} not found for instrument status handling");
2160 }
2161 });
2162
2163 DataActorCore::subscribe_instrument_status(
2164 self.core_mut(),
2165 handler,
2166 instrument_ids,
2167 client_id,
2168 params,
2169 )
2170 .expect("invalid instrument subscription identifier list");
2171 }
2172
2173 fn subscribe_instrument_close(
2184 &mut self,
2185 instrument_ids: impl IntoInstrumentIds,
2186 client_id: Option<ClientId>,
2187 params: Option<Params>,
2188 ) where
2189 Self: DataActorNative,
2190 Self: 'static + Debug + Sized,
2191 {
2192 let actor_id = self.core().actor_id().inner();
2193 let handler = ShareableMessageHandler::from_typed(move |close: &InstrumentClose| {
2194 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2195 actor.handle_instrument_close(close);
2196 } else {
2197 log::error!("Actor {actor_id} not found for instrument close handling");
2198 }
2199 });
2200
2201 DataActorCore::subscribe_instrument_close(
2202 self.core_mut(),
2203 handler,
2204 instrument_ids,
2205 client_id,
2206 params,
2207 )
2208 .expect("invalid instrument subscription identifier list");
2209 }
2210
2211 fn subscribe_option_chain(
2220 &mut self,
2221 series_id: OptionSeriesId,
2222 strike_range: StrikeRange,
2223 snapshot_interval_ms: Option<u64>,
2224 client_id: Option<ClientId>,
2225 params: Option<Params>,
2226 ) -> anyhow::Result<()>
2227 where
2228 Self: DataActorNative,
2229 Self: 'static + Debug + Sized,
2230 {
2231 let actor_id = self.core().actor_id().inner();
2232 let topic = get_option_chain_topic(series_id);
2233
2234 let handler = TypedHandler::from(move |slice: &OptionChainSlice| {
2235 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2236 actor.handle_option_chain(slice);
2237 } else {
2238 log::error!("Actor {actor_id} not found for option chain handling");
2239 }
2240 });
2241
2242 DataActorCore::subscribe_option_chain(
2243 self.core_mut(),
2244 topic,
2245 handler,
2246 series_id,
2247 strike_range,
2248 snapshot_interval_ms,
2249 client_id,
2250 params,
2251 )
2252 }
2253
2254 #[cfg(feature = "defi")]
2255 fn subscribe_blocks(
2257 &mut self,
2258 chain: Blockchain,
2259 client_id: Option<ClientId>,
2260 params: Option<Params>,
2261 ) where
2262 Self: DataActorNative,
2263 Self: 'static + Debug + Sized,
2264 {
2265 let actor_id = self.core().actor_id().inner();
2266 let topic = defi::switchboard::get_defi_blocks_topic(chain);
2267
2268 let handler = TypedHandler::from(move |block: &Block| {
2269 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2270 actor.handle_block(block);
2271 } else {
2272 log::error!("Actor {actor_id} not found for block handling");
2273 }
2274 });
2275
2276 DataActorCore::subscribe_blocks(self.core_mut(), topic, handler, chain, client_id, params);
2277 }
2278
2279 #[cfg(feature = "defi")]
2280 fn subscribe_pool(
2282 &mut self,
2283 instrument_id: InstrumentId,
2284 client_id: Option<ClientId>,
2285 params: Option<Params>,
2286 ) where
2287 Self: DataActorNative,
2288 Self: 'static + Debug + Sized,
2289 {
2290 let actor_id = self.core().actor_id().inner();
2291 let topic = defi::switchboard::get_defi_pool_topic(instrument_id);
2292
2293 let handler = TypedHandler::from(move |pool: &Pool| {
2294 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2295 actor.handle_pool(pool);
2296 } else {
2297 log::error!("Actor {actor_id} not found for pool handling");
2298 }
2299 });
2300
2301 DataActorCore::subscribe_pool(
2302 self.core_mut(),
2303 topic,
2304 handler,
2305 instrument_id,
2306 client_id,
2307 params,
2308 );
2309 }
2310
2311 #[cfg(feature = "defi")]
2312 fn subscribe_pool_swaps(
2314 &mut self,
2315 instrument_id: InstrumentId,
2316 client_id: Option<ClientId>,
2317 params: Option<Params>,
2318 ) where
2319 Self: DataActorNative,
2320 Self: 'static + Debug + Sized,
2321 {
2322 let actor_id = self.core().actor_id().inner();
2323 let topic = defi::switchboard::get_defi_pool_swaps_topic(instrument_id);
2324
2325 let handler = TypedHandler::from(move |swap: &PoolSwap| {
2326 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2327 actor.handle_pool_swap(swap);
2328 } else {
2329 log::error!("Actor {actor_id} not found for pool swap handling");
2330 }
2331 });
2332
2333 DataActorCore::subscribe_pool_swaps(
2334 self.core_mut(),
2335 topic,
2336 handler,
2337 instrument_id,
2338 client_id,
2339 params,
2340 );
2341 }
2342
2343 #[cfg(feature = "defi")]
2344 fn subscribe_pool_liquidity_updates(
2346 &mut self,
2347 instrument_id: InstrumentId,
2348 client_id: Option<ClientId>,
2349 params: Option<Params>,
2350 ) where
2351 Self: DataActorNative,
2352 Self: 'static + Debug + Sized,
2353 {
2354 let actor_id = self.core().actor_id().inner();
2355 let topic = defi::switchboard::get_defi_liquidity_topic(instrument_id);
2356
2357 let handler = TypedHandler::from(move |update: &PoolLiquidityUpdate| {
2358 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2359 actor.handle_pool_liquidity_update(update);
2360 } else {
2361 log::error!("Actor {actor_id} not found for pool liquidity update handling");
2362 }
2363 });
2364
2365 DataActorCore::subscribe_pool_liquidity_updates(
2366 self.core_mut(),
2367 topic,
2368 handler,
2369 instrument_id,
2370 client_id,
2371 params,
2372 );
2373 }
2374
2375 #[cfg(feature = "defi")]
2376 fn subscribe_pool_fee_collects(
2378 &mut self,
2379 instrument_id: InstrumentId,
2380 client_id: Option<ClientId>,
2381 params: Option<Params>,
2382 ) where
2383 Self: DataActorNative,
2384 Self: 'static + Debug + Sized,
2385 {
2386 let actor_id = self.core().actor_id().inner();
2387 let topic = defi::switchboard::get_defi_collect_topic(instrument_id);
2388
2389 let handler = TypedHandler::from(move |collect: &PoolFeeCollect| {
2390 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2391 actor.handle_pool_fee_collect(collect);
2392 } else {
2393 log::error!("Actor {actor_id} not found for pool fee collect handling");
2394 }
2395 });
2396
2397 DataActorCore::subscribe_pool_fee_collects(
2398 self.core_mut(),
2399 topic,
2400 handler,
2401 instrument_id,
2402 client_id,
2403 params,
2404 );
2405 }
2406
2407 #[cfg(feature = "defi")]
2408 fn subscribe_pool_flash_events(
2410 &mut self,
2411 instrument_id: InstrumentId,
2412 client_id: Option<ClientId>,
2413 params: Option<Params>,
2414 ) where
2415 Self: DataActorNative,
2416 Self: 'static + Debug + Sized,
2417 {
2418 let actor_id = self.core().actor_id().inner();
2419 let topic = defi::switchboard::get_defi_flash_topic(instrument_id);
2420
2421 let handler = TypedHandler::from(move |flash: &PoolFlash| {
2422 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2423 actor.handle_pool_flash(flash);
2424 } else {
2425 log::error!("Actor {actor_id} not found for pool flash handling");
2426 }
2427 });
2428
2429 DataActorCore::subscribe_pool_flash_events(
2430 self.core_mut(),
2431 topic,
2432 handler,
2433 instrument_id,
2434 client_id,
2435 params,
2436 );
2437 }
2438
2439 fn unsubscribe_data(
2441 &mut self,
2442 data_type: DataType,
2443 client_id: Option<ClientId>,
2444 params: Option<Params>,
2445 ) where
2446 Self: DataActorNative,
2447 Self: 'static + Debug + Sized,
2448 {
2449 DataActorCore::unsubscribe_data(self.core_mut(), data_type, client_id, params);
2450 }
2451
2452 fn unsubscribe_signal(&mut self, name: &str)
2454 where
2455 Self: DataActorNative,
2456 Self: 'static + Debug + Sized,
2457 {
2458 DataActorCore::unsubscribe_signal(self.core_mut(), name);
2459 }
2460
2461 fn unsubscribe_queue_state(&mut self, channel: Option<SystemChannel>)
2465 where
2466 Self: DataActorNative,
2467 Self: 'static + Debug + Sized,
2468 {
2469 DataActorCore::unsubscribe_queue_state(self.core_mut(), channel);
2470 }
2471
2472 fn unsubscribe_socket_state(&mut self, client_id: Option<ClientId>, endpoint: Option<&str>)
2476 where
2477 Self: DataActorNative,
2478 Self: 'static + Debug + Sized,
2479 {
2480 DataActorCore::unsubscribe_socket_state(self.core_mut(), client_id, endpoint);
2481 }
2482
2483 fn unsubscribe_instruments(
2485 &mut self,
2486 venue: Venue,
2487 client_id: Option<ClientId>,
2488 params: Option<Params>,
2489 ) where
2490 Self: DataActorNative,
2491 Self: 'static + Debug + Sized,
2492 {
2493 DataActorCore::unsubscribe_instruments(self.core_mut(), venue, client_id, params);
2494 }
2495
2496 fn unsubscribe_instrument(
2506 &mut self,
2507 instrument_ids: impl IntoInstrumentIds,
2508 client_id: Option<ClientId>,
2509 params: Option<Params>,
2510 ) where
2511 Self: DataActorNative,
2512 Self: 'static + Debug + Sized,
2513 {
2514 DataActorCore::unsubscribe_instrument(self.core_mut(), instrument_ids, client_id, params)
2515 .expect("invalid instrument subscription identifier list");
2516 }
2517
2518 fn unsubscribe_book_deltas(
2528 &mut self,
2529 instrument_ids: impl IntoInstrumentIds,
2530 client_id: Option<ClientId>,
2531 params: Option<Params>,
2532 ) where
2533 Self: DataActorNative,
2534 Self: 'static + Debug + Sized,
2535 {
2536 DataActorCore::unsubscribe_book_deltas(self.core_mut(), instrument_ids, client_id, params)
2537 .expect("invalid instrument subscription identifier list");
2538 }
2539
2540 fn unsubscribe_book_depth(
2550 &mut self,
2551 instrument_ids: impl IntoInstrumentIds,
2552 client_id: Option<ClientId>,
2553 params: Option<Params>,
2554 ) where
2555 Self: DataActorNative,
2556 Self: 'static + Debug + Sized,
2557 {
2558 DataActorCore::unsubscribe_book_depth(self.core_mut(), instrument_ids, client_id, params)
2559 .expect("invalid instrument subscription identifier list");
2560 }
2561
2562 fn unsubscribe_book_at_interval(
2572 &mut self,
2573 instrument_ids: impl IntoInstrumentIds,
2574 interval_ms: NonZeroUsize,
2575 client_id: Option<ClientId>,
2576 params: Option<Params>,
2577 ) where
2578 Self: DataActorNative,
2579 Self: 'static + Debug + Sized,
2580 {
2581 DataActorCore::unsubscribe_book_at_interval(
2582 self.core_mut(),
2583 instrument_ids,
2584 interval_ms,
2585 client_id,
2586 params,
2587 )
2588 .expect("invalid instrument subscription identifier list");
2589 }
2590
2591 fn unsubscribe_quotes(
2601 &mut self,
2602 instrument_ids: impl IntoInstrumentIds,
2603 client_id: Option<ClientId>,
2604 params: Option<Params>,
2605 ) where
2606 Self: DataActorNative,
2607 Self: 'static + Debug + Sized,
2608 {
2609 DataActorCore::unsubscribe_quotes(self.core_mut(), instrument_ids, client_id, params)
2610 .expect("invalid quote subscription identifier list");
2611 }
2612
2613 fn unsubscribe_trades(
2623 &mut self,
2624 instrument_ids: impl IntoInstrumentIds,
2625 client_id: Option<ClientId>,
2626 params: Option<Params>,
2627 ) where
2628 Self: DataActorNative,
2629 Self: 'static + Debug + Sized,
2630 {
2631 DataActorCore::unsubscribe_trades(self.core_mut(), instrument_ids, client_id, params)
2632 .expect("invalid trade subscription identifier list");
2633 }
2634
2635 fn unsubscribe_bars(
2643 &mut self,
2644 bar_types: impl IntoBarTypes,
2645 client_id: Option<ClientId>,
2646 params: Option<Params>,
2647 ) where
2648 Self: DataActorNative,
2649 Self: 'static + Debug + Sized,
2650 {
2651 DataActorCore::unsubscribe_bars(self.core_mut(), bar_types, client_id, params)
2652 .expect("invalid bar subscription type list");
2653 }
2654
2655 fn unsubscribe_mark_prices(
2665 &mut self,
2666 instrument_ids: impl IntoInstrumentIds,
2667 client_id: Option<ClientId>,
2668 params: Option<Params>,
2669 ) where
2670 Self: DataActorNative,
2671 Self: 'static + Debug + Sized,
2672 {
2673 DataActorCore::unsubscribe_mark_prices(self.core_mut(), instrument_ids, client_id, params)
2674 .expect("invalid instrument subscription identifier list");
2675 }
2676
2677 fn unsubscribe_index_prices(
2687 &mut self,
2688 instrument_ids: impl IntoInstrumentIds,
2689 client_id: Option<ClientId>,
2690 params: Option<Params>,
2691 ) where
2692 Self: DataActorNative,
2693 Self: 'static + Debug + Sized,
2694 {
2695 DataActorCore::unsubscribe_index_prices(self.core_mut(), instrument_ids, client_id, params)
2696 .expect("invalid instrument subscription identifier list");
2697 }
2698
2699 fn unsubscribe_funding_rates(
2709 &mut self,
2710 instrument_ids: impl IntoInstrumentIds,
2711 client_id: Option<ClientId>,
2712 params: Option<Params>,
2713 ) where
2714 Self: DataActorNative,
2715 Self: 'static + Debug + Sized,
2716 {
2717 DataActorCore::unsubscribe_funding_rates(
2718 self.core_mut(),
2719 instrument_ids,
2720 client_id,
2721 params,
2722 )
2723 .expect("invalid instrument subscription identifier list");
2724 }
2725
2726 fn unsubscribe_option_greeks(
2736 &mut self,
2737 instrument_ids: impl IntoInstrumentIds,
2738 client_id: Option<ClientId>,
2739 params: Option<Params>,
2740 ) where
2741 Self: DataActorNative,
2742 Self: 'static + Debug + Sized,
2743 {
2744 DataActorCore::unsubscribe_option_greeks(
2745 self.core_mut(),
2746 instrument_ids,
2747 client_id,
2748 params,
2749 )
2750 .expect("invalid instrument subscription identifier list");
2751 }
2752
2753 fn unsubscribe_instrument_status(
2763 &mut self,
2764 instrument_ids: impl IntoInstrumentIds,
2765 client_id: Option<ClientId>,
2766 params: Option<Params>,
2767 ) where
2768 Self: DataActorNative,
2769 Self: 'static + Debug + Sized,
2770 {
2771 DataActorCore::unsubscribe_instrument_status(
2772 self.core_mut(),
2773 instrument_ids,
2774 client_id,
2775 params,
2776 )
2777 .expect("invalid instrument subscription identifier list");
2778 }
2779
2780 fn unsubscribe_instrument_close(
2790 &mut self,
2791 instrument_ids: impl IntoInstrumentIds,
2792 client_id: Option<ClientId>,
2793 params: Option<Params>,
2794 ) where
2795 Self: DataActorNative,
2796 Self: 'static + Debug + Sized,
2797 {
2798 DataActorCore::unsubscribe_instrument_close(
2799 self.core_mut(),
2800 instrument_ids,
2801 client_id,
2802 params,
2803 )
2804 .expect("invalid instrument subscription identifier list");
2805 }
2806
2807 fn unsubscribe_option_chain(&mut self, series_id: OptionSeriesId, client_id: Option<ClientId>)
2809 where
2810 Self: DataActorNative,
2811 Self: 'static + Debug + Sized,
2812 {
2813 DataActorCore::unsubscribe_option_chain(self.core_mut(), series_id, client_id);
2814 }
2815
2816 #[cfg(feature = "defi")]
2817 fn unsubscribe_blocks(
2819 &mut self,
2820 chain: Blockchain,
2821 client_id: Option<ClientId>,
2822 params: Option<Params>,
2823 ) where
2824 Self: DataActorNative,
2825 Self: 'static + Debug + Sized,
2826 {
2827 DataActorCore::unsubscribe_blocks(self.core_mut(), chain, client_id, params);
2828 }
2829
2830 #[cfg(feature = "defi")]
2831 fn unsubscribe_pool(
2833 &mut self,
2834 instrument_id: InstrumentId,
2835 client_id: Option<ClientId>,
2836 params: Option<Params>,
2837 ) where
2838 Self: DataActorNative,
2839 Self: 'static + Debug + Sized,
2840 {
2841 DataActorCore::unsubscribe_pool(self.core_mut(), instrument_id, client_id, params);
2842 }
2843
2844 #[cfg(feature = "defi")]
2845 fn unsubscribe_pool_swaps(
2847 &mut self,
2848 instrument_id: InstrumentId,
2849 client_id: Option<ClientId>,
2850 params: Option<Params>,
2851 ) where
2852 Self: DataActorNative,
2853 Self: 'static + Debug + Sized,
2854 {
2855 DataActorCore::unsubscribe_pool_swaps(self.core_mut(), instrument_id, client_id, params);
2856 }
2857
2858 #[cfg(feature = "defi")]
2859 fn unsubscribe_pool_liquidity_updates(
2861 &mut self,
2862 instrument_id: InstrumentId,
2863 client_id: Option<ClientId>,
2864 params: Option<Params>,
2865 ) where
2866 Self: DataActorNative,
2867 Self: 'static + Debug + Sized,
2868 {
2869 DataActorCore::unsubscribe_pool_liquidity_updates(
2870 self.core_mut(),
2871 instrument_id,
2872 client_id,
2873 params,
2874 );
2875 }
2876
2877 #[cfg(feature = "defi")]
2878 fn unsubscribe_pool_fee_collects(
2880 &mut self,
2881 instrument_id: InstrumentId,
2882 client_id: Option<ClientId>,
2883 params: Option<Params>,
2884 ) where
2885 Self: DataActorNative,
2886 Self: 'static + Debug + Sized,
2887 {
2888 DataActorCore::unsubscribe_pool_fee_collects(
2889 self.core_mut(),
2890 instrument_id,
2891 client_id,
2892 params,
2893 );
2894 }
2895
2896 #[cfg(feature = "defi")]
2897 fn unsubscribe_pool_flash_events(
2899 &mut self,
2900 instrument_id: InstrumentId,
2901 client_id: Option<ClientId>,
2902 params: Option<Params>,
2903 ) where
2904 Self: DataActorNative,
2905 Self: 'static + Debug + Sized,
2906 {
2907 DataActorCore::unsubscribe_pool_flash_events(
2908 self.core_mut(),
2909 instrument_id,
2910 client_id,
2911 params,
2912 );
2913 }
2914
2915 fn request_data(
2921 &mut self,
2922 data_type: DataType,
2923 client_id: ClientId,
2924 start: Option<Timestamp>,
2925 end: Option<Timestamp>,
2926 limit: Option<NonZeroUsize>,
2927 params: Option<Params>,
2928 ) -> anyhow::Result<UUID4>
2929 where
2930 Self: DataActorNative,
2931 Self: 'static + Debug + Sized,
2932 {
2933 let handler = self.response_handler();
2934
2935 DataActorCore::request_data(
2936 self.core_mut(),
2937 data_type,
2938 client_id,
2939 start,
2940 end,
2941 limit,
2942 params,
2943 handler,
2944 )
2945 }
2946
2947 fn request_instrument(
2953 &mut self,
2954 instrument_ids: impl IntoInstrumentIds,
2955 start: Option<Timestamp>,
2956 end: Option<Timestamp>,
2957 client_id: Option<ClientId>,
2958 params: Option<Params>,
2959 ) -> anyhow::Result<UUID4>
2960 where
2961 Self: DataActorNative,
2962 Self: 'static + Debug + Sized,
2963 {
2964 let handler = self.response_handler();
2965
2966 DataActorCore::request_instrument(
2967 self.core_mut(),
2968 instrument_ids,
2969 start,
2970 end,
2971 client_id,
2972 params,
2973 handler,
2974 )
2975 }
2976
2977 fn request_instruments(
2983 &mut self,
2984 venue: Option<Venue>,
2985 start: Option<Timestamp>,
2986 end: Option<Timestamp>,
2987 client_id: Option<ClientId>,
2988 params: Option<Params>,
2989 ) -> anyhow::Result<UUID4>
2990 where
2991 Self: DataActorNative,
2992 Self: 'static + Debug + Sized,
2993 {
2994 let handler = self.response_handler();
2995
2996 DataActorCore::request_instruments(
2997 self.core_mut(),
2998 venue,
2999 start,
3000 end,
3001 client_id,
3002 params,
3003 handler,
3004 )
3005 }
3006
3007 fn request_book_snapshot(
3013 &mut self,
3014 instrument_ids: impl IntoInstrumentIds,
3015 depth: Option<NonZeroUsize>,
3016 client_id: Option<ClientId>,
3017 params: Option<Params>,
3018 ) -> anyhow::Result<UUID4>
3019 where
3020 Self: DataActorNative,
3021 Self: 'static + Debug + Sized,
3022 {
3023 let handler = self.response_handler();
3024
3025 DataActorCore::request_book_snapshot(
3026 self.core_mut(),
3027 instrument_ids,
3028 depth,
3029 client_id,
3030 params,
3031 handler,
3032 )
3033 }
3034
3035 fn request_book_deltas(
3041 &mut self,
3042 instrument_ids: impl IntoInstrumentIds,
3043 start: Option<Timestamp>,
3044 end: Option<Timestamp>,
3045 limit: Option<NonZeroUsize>,
3046 client_id: Option<ClientId>,
3047 params: Option<Params>,
3048 ) -> anyhow::Result<UUID4>
3049 where
3050 Self: DataActorNative,
3051 Self: 'static + Debug + Sized,
3052 {
3053 let handler = self.response_handler();
3054
3055 DataActorCore::request_book_deltas(
3056 self.core_mut(),
3057 instrument_ids,
3058 start,
3059 end,
3060 limit,
3061 client_id,
3062 params,
3063 handler,
3064 )
3065 }
3066
3067 #[expect(clippy::too_many_arguments)]
3073 fn request_book_depth(
3074 &mut self,
3075 instrument_ids: impl IntoInstrumentIds,
3076 start: Option<Timestamp>,
3077 end: Option<Timestamp>,
3078 limit: Option<NonZeroUsize>,
3079 depth: Option<NonZeroUsize>,
3080 client_id: Option<ClientId>,
3081 params: Option<Params>,
3082 ) -> anyhow::Result<UUID4>
3083 where
3084 Self: DataActorNative,
3085 Self: 'static + Debug + Sized,
3086 {
3087 let handler = self.response_handler();
3088
3089 DataActorCore::request_book_depth(
3090 self.core_mut(),
3091 instrument_ids,
3092 start,
3093 end,
3094 limit,
3095 depth,
3096 client_id,
3097 params,
3098 handler,
3099 )
3100 }
3101
3102 fn request_quotes(
3108 &mut self,
3109 instrument_ids: impl IntoInstrumentIds,
3110 start: Option<Timestamp>,
3111 end: Option<Timestamp>,
3112 limit: Option<NonZeroUsize>,
3113 client_id: Option<ClientId>,
3114 params: Option<Params>,
3115 ) -> anyhow::Result<UUID4>
3116 where
3117 Self: DataActorNative,
3118 Self: 'static + Debug + Sized,
3119 {
3120 let handler = self.response_handler();
3121
3122 DataActorCore::request_quotes(
3123 self.core_mut(),
3124 instrument_ids,
3125 start,
3126 end,
3127 limit,
3128 client_id,
3129 params,
3130 handler,
3131 )
3132 }
3133
3134 fn request_trades(
3140 &mut self,
3141 instrument_ids: impl IntoInstrumentIds,
3142 start: Option<Timestamp>,
3143 end: Option<Timestamp>,
3144 limit: Option<NonZeroUsize>,
3145 client_id: Option<ClientId>,
3146 params: Option<Params>,
3147 ) -> anyhow::Result<UUID4>
3148 where
3149 Self: DataActorNative,
3150 Self: 'static + Debug + Sized,
3151 {
3152 let handler = self.response_handler();
3153
3154 DataActorCore::request_trades(
3155 self.core_mut(),
3156 instrument_ids,
3157 start,
3158 end,
3159 limit,
3160 client_id,
3161 params,
3162 handler,
3163 )
3164 }
3165
3166 fn request_bars(
3172 &mut self,
3173 bar_types: impl IntoBarTypes,
3174 start: Option<Timestamp>,
3175 end: Option<Timestamp>,
3176 limit: Option<NonZeroUsize>,
3177 client_id: Option<ClientId>,
3178 params: Option<Params>,
3179 ) -> anyhow::Result<UUID4>
3180 where
3181 Self: DataActorNative,
3182 Self: 'static + Debug + Sized,
3183 {
3184 let handler = self.response_handler();
3185
3186 DataActorCore::request_bars(
3187 self.core_mut(),
3188 bar_types,
3189 start,
3190 end,
3191 limit,
3192 client_id,
3193 params,
3194 handler,
3195 )
3196 }
3197
3198 fn request_funding_rates(
3204 &mut self,
3205 instrument_ids: impl IntoInstrumentIds,
3206 start: Option<Timestamp>,
3207 end: Option<Timestamp>,
3208 limit: Option<NonZeroUsize>,
3209 client_id: Option<ClientId>,
3210 params: Option<Params>,
3211 ) -> anyhow::Result<UUID4>
3212 where
3213 Self: DataActorNative,
3214 Self: 'static + Debug + Sized,
3215 {
3216 let handler = self.response_handler();
3217
3218 DataActorCore::request_funding_rates(
3219 self.core_mut(),
3220 instrument_ids,
3221 start,
3222 end,
3223 limit,
3224 client_id,
3225 params,
3226 handler,
3227 )
3228 }
3229
3230 fn request_group(
3252 &mut self,
3253 request_ids: Vec<UUID4>,
3254 request_id: Option<UUID4>,
3255 ) -> anyhow::Result<UUID4>
3256 where
3257 Self: DataActorNative,
3258 {
3259 self.core().check_registered();
3260 let id = self
3261 .core()
3262 .register_request_group(request_ids, request_id, None)?;
3263 self.drain_request_groups();
3264 Ok(id)
3265 }
3266
3267 #[cfg(feature = "live")]
3278 fn reconnect_socket(&self, client_id: ClientId, endpoint: &str) -> anyhow::Result<()>
3279 where
3280 Self: DataActorNative,
3281 {
3282 DataActorCore::reconnect_socket(self.core(), client_id, endpoint)
3283 }
3284}
3285
3286impl<T> Actor for T
3288where
3289 T: DataActor + DataActorNative + Debug + 'static,
3290{
3291 fn id(&self) -> Ustr {
3292 self.core().actor_id.inner()
3293 }
3294
3295 #[allow(unused_variables)]
3296 fn handle(&mut self, msg: &dyn Any) {
3297 }
3299
3300 fn as_any(&self) -> &dyn Any {
3301 self
3302 }
3303}
3304
3305impl<T> Component for T
3306where
3307 T: DataActor + DataActorNative + Debug + 'static,
3308{
3309 fn component_id(&self) -> ComponentId {
3310 ComponentId::from(self.core().actor_id)
3311 }
3312
3313 fn release_subscriptions(&mut self) {
3314 self.core().clear_pending_requests();
3315 self.core_mut().unsubscribe_all();
3316 }
3317
3318 fn state(&self) -> ComponentState {
3319 self.core().state
3320 }
3321
3322 fn transition_state(&mut self, trigger: ComponentTrigger) -> anyhow::Result<()> {
3323 let core = self.core_mut();
3324 core.state = core.state.transition(&trigger)?;
3325
3326 #[cfg(feature = "python")]
3327 if core.state == ComponentState::Disposed {
3328 core.message_bus.invalidate();
3329 }
3330
3331 log::info!(
3332 component = core.actor_id.inner().as_str();
3333 "{}",
3334 core.state.variant_name()
3335 );
3336 Ok(())
3337 }
3338
3339 fn register(
3340 &mut self,
3341 trader_id: TraderId,
3342 clock: Rc<RefCell<dyn Clock>>,
3343 cache: Rc<RefCell<Cache>>,
3344 ) -> anyhow::Result<()> {
3345 DataActorCore::register(self.core_mut(), trader_id, clock.clone(), cache)?;
3346
3347 let actor_id = self.core().actor_id().inner();
3349 let callback = TimeEventCallback::from(move |event: TimeEvent| {
3350 if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
3351 actor.handle_time_event(&event);
3352 } else {
3353 log::error!("Actor {actor_id} not found for time event handling");
3354 }
3355 });
3356
3357 clock.borrow_mut().register_default_handler(callback);
3358
3359 self.initialize()
3360 }
3361
3362 fn on_start(&mut self) -> anyhow::Result<()> {
3363 DataActorNative::dispatch_start(self)
3364 }
3365
3366 fn on_stop(&mut self) -> anyhow::Result<()> {
3367 DataActor::on_stop(self)
3368 }
3369
3370 fn on_resume(&mut self) -> anyhow::Result<()> {
3371 DataActor::on_resume(self)
3372 }
3373
3374 fn on_degrade(&mut self) -> anyhow::Result<()> {
3375 DataActor::on_degrade(self)
3376 }
3377
3378 fn on_fault(&mut self) -> anyhow::Result<()> {
3379 DataActor::on_fault(self)
3380 }
3381
3382 fn on_reset(&mut self) -> anyhow::Result<()> {
3383 DataActor::on_reset(self)
3384 }
3385
3386 fn on_dispose(&mut self) -> anyhow::Result<()> {
3387 DataActor::on_dispose(self)
3388 }
3389}
3390
3391#[derive(Clone)]
3393#[allow(dead_code, reason = "TODO: Under development (signal_classes)")]
3394pub struct DataActorCore {
3395 pub actor_id: ActorId,
3397 pub config: DataActorConfig,
3399 trader_id: Option<TraderId>,
3400 clock: Option<Rc<RefCell<dyn Clock>>>, cache: Option<Rc<RefCell<Cache>>>, state: ComponentState,
3403 topic_handlers: AHashMap<MStr<Pattern>, Subscription<ShareableMessageHandler>>,
3404 instrument_handlers: AHashMap<MStr<Pattern>, Subscription<TypedHandler<InstrumentAny>>>,
3405 deltas_handlers: AHashMap<MStr<Pattern>, Subscription<TypedHandler<OrderBookDeltas>>>,
3406 depth_handlers: AHashMap<MStr<Pattern>, Subscription<TypedHandler<OrderBookDepth>>>,
3407 book_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<OrderBook>>>,
3408 quote_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<QuoteTick>>>,
3409 trade_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<TradeTick>>>,
3410 bar_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<Bar>>>,
3411 mark_price_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<MarkPriceUpdate>>>,
3412 index_price_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<IndexPriceUpdate>>>,
3413 funding_rate_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<FundingRateUpdate>>>,
3414 option_greeks_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<OptionGreeks>>>,
3415 option_chain_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<OptionChainSlice>>>,
3416 indicators: Indicators,
3417 warning_events: AHashSet<String>, requests: RefCell<RequestLifecycle>,
3419 signal_classes: AHashMap<String, String>,
3420 #[cfg(feature = "defi")]
3421 block_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<Block>>>,
3422 #[cfg(feature = "defi")]
3423 pool_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<Pool>>>,
3424 #[cfg(feature = "defi")]
3425 pool_swap_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolSwap>>>,
3426 #[cfg(feature = "defi")]
3427 pool_liquidity_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolLiquidityUpdate>>>,
3428 #[cfg(feature = "defi")]
3429 pool_collect_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolFeeCollect>>>,
3430 #[cfg(feature = "defi")]
3431 pool_flash_handlers: AHashMap<MStr<Topic>, Subscription<TypedHandler<PoolFlash>>>,
3432 #[cfg(feature = "python")]
3433 message_bus: Rc<PyMessageBusScope>,
3434}
3435
3436#[derive(Clone)]
3437struct Subscription<T> {
3438 handler: T,
3439 command: Option<DataCommand>,
3440}
3441
3442impl Debug for DataActorCore {
3443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3444 f.debug_struct(stringify!(DataActorCore))
3445 .field("actor_id", &self.actor_id)
3446 .field("config", &self.config)
3447 .field("state", &self.state)
3448 .field("trader_id", &self.trader_id)
3449 .finish()
3450 }
3451}
3452
3453impl DataActorCore {
3454 pub(crate) fn add_subscription_any(
3457 &mut self,
3458 topic: impl Into<MStr<Pattern>>,
3459 handler: ShareableMessageHandler,
3460 priority: Option<u32>,
3461 command: Option<DataCommand>,
3462 ) -> bool {
3463 let pattern: MStr<Pattern> = topic.into();
3464 if let Some(subscription) = self.topic_handlers.get_mut(&pattern) {
3465 if subscription.command.is_none() && command.is_some() {
3466 subscription.command = command;
3467 return true;
3468 }
3469
3470 log::warn!(
3471 "Actor {} attempted duplicate subscription to topic '{pattern}'",
3472 self.actor_id,
3473 );
3474 return false;
3475 }
3476
3477 self.topic_handlers.insert(
3478 pattern,
3479 Subscription {
3480 handler: handler.clone(),
3481 command,
3482 },
3483 );
3484 msgbus::subscribe_any(pattern, handler, priority);
3485 true
3486 }
3487
3488 pub(crate) fn remove_subscription_any(
3492 &mut self,
3493 topic: impl Into<MStr<Pattern>>,
3494 ) -> Option<DataCommand> {
3495 let pattern: MStr<Pattern> = topic.into();
3496 if let Some(subscription) = self.topic_handlers.remove(&pattern) {
3497 msgbus::unsubscribe_any(pattern, &subscription.handler);
3498 subscription.command
3499 } else {
3500 log::warn!(
3501 "Actor {} attempted to unsubscribe from topic '{pattern}' when not subscribed",
3502 self.actor_id,
3503 );
3504 None
3505 }
3506 }
3507
3508 pub(crate) fn add_quote_subscription(
3509 &mut self,
3510 topic: MStr<Topic>,
3511 handler: TypedHandler<QuoteTick>,
3512 command: DataCommand,
3513 ) -> bool {
3514 if self.quote_handlers.contains_key(&topic) {
3515 log::warn!(
3516 "Actor {} attempted duplicate quote subscription to '{topic}'",
3517 self.actor_id
3518 );
3519 return false;
3520 }
3521 self.quote_handlers.insert(
3522 topic,
3523 Subscription {
3524 handler: handler.clone(),
3525 command: Some(command),
3526 },
3527 );
3528 msgbus::subscribe_quotes(topic.into(), handler, None);
3529 true
3530 }
3531
3532 #[allow(dead_code)]
3533 pub(crate) fn remove_quote_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3534 let subscription = self.quote_handlers.remove(&topic)?;
3535 msgbus::unsubscribe_quotes(topic.into(), &subscription.handler);
3536 subscription.command
3537 }
3538
3539 pub(crate) fn add_trade_subscription(
3540 &mut self,
3541 topic: MStr<Topic>,
3542 handler: TypedHandler<TradeTick>,
3543 command: DataCommand,
3544 ) -> bool {
3545 if self.trade_handlers.contains_key(&topic) {
3546 log::warn!(
3547 "Actor {} attempted duplicate trade subscription to '{topic}'",
3548 self.actor_id
3549 );
3550 return false;
3551 }
3552 self.trade_handlers.insert(
3553 topic,
3554 Subscription {
3555 handler: handler.clone(),
3556 command: Some(command),
3557 },
3558 );
3559 msgbus::subscribe_trades(topic.into(), handler, None);
3560 true
3561 }
3562
3563 #[allow(dead_code)]
3564 pub(crate) fn remove_trade_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3565 let subscription = self.trade_handlers.remove(&topic)?;
3566 msgbus::unsubscribe_trades(topic.into(), &subscription.handler);
3567 subscription.command
3568 }
3569
3570 pub(crate) fn add_bar_subscription(
3571 &mut self,
3572 topic: MStr<Topic>,
3573 handler: TypedHandler<Bar>,
3574 command: DataCommand,
3575 ) -> bool {
3576 if self.bar_handlers.contains_key(&topic) {
3577 return false;
3578 }
3579
3580 self.bar_handlers.insert(
3581 topic,
3582 Subscription {
3583 handler: handler.clone(),
3584 command: Some(command),
3585 },
3586 );
3587 msgbus::subscribe_bars(topic.into(), handler, None);
3588 true
3589 }
3590
3591 fn remove_bar_subscription(&mut self, bar_type: BarType) -> Option<DataCommand> {
3592 let topic = get_bars_topic(bar_type.standard());
3593 let subscription = self.bar_handlers.remove(&topic)?;
3594 msgbus::unsubscribe_bars(topic.into(), &subscription.handler);
3595 subscription.command
3596 }
3597
3598 pub(crate) fn add_deltas_subscription(
3599 &mut self,
3600 pattern: MStr<Pattern>,
3601 handler: TypedHandler<OrderBookDeltas>,
3602 command: DataCommand,
3603 ) -> bool {
3604 if self.deltas_handlers.contains_key(&pattern) {
3605 log::warn!(
3606 "Actor {} attempted duplicate deltas subscription to '{pattern}'",
3607 self.actor_id
3608 );
3609 return false;
3610 }
3611 self.deltas_handlers.insert(
3612 pattern,
3613 Subscription {
3614 handler: handler.clone(),
3615 command: Some(command),
3616 },
3617 );
3618 msgbus::subscribe_book_deltas(pattern, handler, None);
3619 true
3620 }
3621
3622 #[allow(dead_code)]
3623 pub(crate) fn remove_deltas_subscription(
3624 &mut self,
3625 pattern: MStr<Pattern>,
3626 ) -> Option<DataCommand> {
3627 let subscription = self.deltas_handlers.remove(&pattern)?;
3628 msgbus::unsubscribe_book_deltas(pattern, &subscription.handler);
3629 subscription.command
3630 }
3631
3632 pub(crate) fn add_depth_subscription(
3633 &mut self,
3634 pattern: MStr<Pattern>,
3635 handler: TypedHandler<OrderBookDepth>,
3636 command: DataCommand,
3637 ) -> bool {
3638 if self.depth_handlers.contains_key(&pattern) {
3639 log::warn!(
3640 "Actor {} attempted duplicate depth subscription to '{pattern}'",
3641 self.actor_id
3642 );
3643 return false;
3644 }
3645 self.depth_handlers.insert(
3646 pattern,
3647 Subscription {
3648 handler: handler.clone(),
3649 command: Some(command),
3650 },
3651 );
3652 msgbus::subscribe_book_depth(pattern, handler, None);
3653 true
3654 }
3655
3656 pub(crate) fn remove_depth_subscription(
3657 &mut self,
3658 pattern: MStr<Pattern>,
3659 ) -> Option<DataCommand> {
3660 let subscription = self.depth_handlers.remove(&pattern)?;
3661 msgbus::unsubscribe_book_depth(pattern, &subscription.handler);
3662 subscription.command
3663 }
3664
3665 pub(crate) fn add_instrument_subscription(
3666 &mut self,
3667 pattern: MStr<Pattern>,
3668 handler: TypedHandler<InstrumentAny>,
3669 command: DataCommand,
3670 ) -> bool {
3671 if self.instrument_handlers.contains_key(&pattern) {
3672 log::warn!(
3673 "Actor {} attempted duplicate instrument subscription to '{pattern}'",
3674 self.actor_id
3675 );
3676 return false;
3677 }
3678 self.instrument_handlers.insert(
3679 pattern,
3680 Subscription {
3681 handler: handler.clone(),
3682 command: Some(command),
3683 },
3684 );
3685 msgbus::subscribe_instruments(pattern, handler, None);
3686 true
3687 }
3688
3689 #[allow(dead_code)]
3690 pub(crate) fn remove_instrument_subscription(
3691 &mut self,
3692 pattern: MStr<Pattern>,
3693 ) -> Option<DataCommand> {
3694 let subscription = self.instrument_handlers.remove(&pattern)?;
3695 msgbus::unsubscribe_instruments(pattern, &subscription.handler);
3696 subscription.command
3697 }
3698
3699 pub(crate) fn add_instrument_close_subscription(
3700 &mut self,
3701 topic: MStr<Topic>,
3702 handler: ShareableMessageHandler,
3703 command: DataCommand,
3704 ) -> bool {
3705 let pattern: MStr<Pattern> = topic.into();
3706 if self.topic_handlers.contains_key(&pattern) {
3707 log::warn!(
3708 "Actor {} attempted duplicate instrument close subscription to '{topic}'",
3709 self.actor_id
3710 );
3711 return false;
3712 }
3713 self.topic_handlers.insert(
3714 pattern,
3715 Subscription {
3716 handler: handler.clone(),
3717 command: Some(command),
3718 },
3719 );
3720 msgbus::subscribe_any(pattern, handler, None);
3721 true
3722 }
3723
3724 #[allow(dead_code)]
3725 pub(crate) fn remove_instrument_close_subscription(
3726 &mut self,
3727 topic: MStr<Topic>,
3728 ) -> Option<DataCommand> {
3729 let pattern: MStr<Pattern> = topic.into();
3730 let subscription = self.topic_handlers.remove(&pattern)?;
3731 msgbus::unsubscribe_any(pattern, &subscription.handler);
3732 subscription.command
3733 }
3734
3735 pub(crate) fn add_book_snapshot_subscription(
3736 &mut self,
3737 topic: MStr<Topic>,
3738 handler: TypedHandler<OrderBook>,
3739 command: DataCommand,
3740 ) -> bool {
3741 if self.book_handlers.contains_key(&topic) {
3742 log::warn!(
3743 "Actor {} attempted duplicate book snapshot subscription to '{topic}'",
3744 self.actor_id
3745 );
3746 return false;
3747 }
3748 self.book_handlers.insert(
3749 topic,
3750 Subscription {
3751 handler: handler.clone(),
3752 command: Some(command),
3753 },
3754 );
3755 msgbus::subscribe_book_snapshots(topic.into(), handler, None);
3756 true
3757 }
3758
3759 #[allow(dead_code)]
3760 pub(crate) fn remove_book_snapshot_subscription(
3761 &mut self,
3762 topic: MStr<Topic>,
3763 ) -> Option<DataCommand> {
3764 let subscription = self.book_handlers.remove(&topic)?;
3765 msgbus::unsubscribe_book_snapshots(topic.into(), &subscription.handler);
3766 subscription.command
3767 }
3768
3769 pub(crate) fn add_mark_price_subscription(
3770 &mut self,
3771 topic: MStr<Topic>,
3772 handler: TypedHandler<MarkPriceUpdate>,
3773 command: DataCommand,
3774 ) -> bool {
3775 if self.mark_price_handlers.contains_key(&topic) {
3776 log::warn!(
3777 "Actor {} attempted duplicate mark price subscription to '{topic}'",
3778 self.actor_id
3779 );
3780 return false;
3781 }
3782 self.mark_price_handlers.insert(
3783 topic,
3784 Subscription {
3785 handler: handler.clone(),
3786 command: Some(command),
3787 },
3788 );
3789 msgbus::subscribe_mark_prices(topic.into(), handler, None);
3790 true
3791 }
3792
3793 #[allow(dead_code)]
3794 pub(crate) fn remove_mark_price_subscription(
3795 &mut self,
3796 topic: MStr<Topic>,
3797 ) -> Option<DataCommand> {
3798 let subscription = self.mark_price_handlers.remove(&topic)?;
3799 msgbus::unsubscribe_mark_prices(topic.into(), &subscription.handler);
3800 subscription.command
3801 }
3802
3803 pub(crate) fn add_index_price_subscription(
3804 &mut self,
3805 topic: MStr<Topic>,
3806 handler: TypedHandler<IndexPriceUpdate>,
3807 command: DataCommand,
3808 ) -> bool {
3809 if self.index_price_handlers.contains_key(&topic) {
3810 log::warn!(
3811 "Actor {} attempted duplicate index price subscription to '{topic}'",
3812 self.actor_id
3813 );
3814 return false;
3815 }
3816 self.index_price_handlers.insert(
3817 topic,
3818 Subscription {
3819 handler: handler.clone(),
3820 command: Some(command),
3821 },
3822 );
3823 msgbus::subscribe_index_prices(topic.into(), handler, None);
3824 true
3825 }
3826
3827 #[allow(dead_code)]
3828 pub(crate) fn remove_index_price_subscription(
3829 &mut self,
3830 topic: MStr<Topic>,
3831 ) -> Option<DataCommand> {
3832 let subscription = self.index_price_handlers.remove(&topic)?;
3833 msgbus::unsubscribe_index_prices(topic.into(), &subscription.handler);
3834 subscription.command
3835 }
3836
3837 pub(crate) fn add_funding_rate_subscription(
3838 &mut self,
3839 topic: MStr<Topic>,
3840 handler: TypedHandler<FundingRateUpdate>,
3841 command: DataCommand,
3842 ) -> bool {
3843 if self.funding_rate_handlers.contains_key(&topic) {
3844 log::warn!(
3845 "Actor {} attempted duplicate funding rate subscription to '{topic}'",
3846 self.actor_id
3847 );
3848 return false;
3849 }
3850 self.funding_rate_handlers.insert(
3851 topic,
3852 Subscription {
3853 handler: handler.clone(),
3854 command: Some(command),
3855 },
3856 );
3857 msgbus::subscribe_funding_rates(topic.into(), handler, None);
3858 true
3859 }
3860
3861 #[allow(dead_code)]
3862 pub(crate) fn remove_funding_rate_subscription(
3863 &mut self,
3864 topic: MStr<Topic>,
3865 ) -> Option<DataCommand> {
3866 let subscription = self.funding_rate_handlers.remove(&topic)?;
3867 msgbus::unsubscribe_funding_rates(topic.into(), &subscription.handler);
3868 subscription.command
3869 }
3870
3871 pub(crate) fn add_option_greeks_subscription(
3872 &mut self,
3873 topic: MStr<Topic>,
3874 handler: TypedHandler<OptionGreeks>,
3875 command: DataCommand,
3876 ) -> bool {
3877 if self.option_greeks_handlers.contains_key(&topic) {
3878 log::warn!(
3879 "Actor {} attempted duplicate option greeks subscription to '{topic}'",
3880 self.actor_id
3881 );
3882 return false;
3883 }
3884 self.option_greeks_handlers.insert(
3885 topic,
3886 Subscription {
3887 handler: handler.clone(),
3888 command: Some(command),
3889 },
3890 );
3891 msgbus::subscribe_option_greeks(topic.into(), handler, None);
3892 true
3893 }
3894
3895 #[allow(dead_code)]
3896 pub(crate) fn remove_option_greeks_subscription(
3897 &mut self,
3898 topic: MStr<Topic>,
3899 ) -> Option<DataCommand> {
3900 let subscription = self.option_greeks_handlers.remove(&topic)?;
3901 msgbus::unsubscribe_option_greeks(topic.into(), &subscription.handler);
3902 subscription.command
3903 }
3904
3905 pub(crate) fn set_option_chain_subscription(
3906 &mut self,
3907 topic: MStr<Topic>,
3908 handler: TypedHandler<OptionChainSlice>,
3909 command: DataCommand,
3910 ) {
3911 if let Some(subscription) = self.option_chain_handlers.get_mut(&topic) {
3912 subscription.command = Some(command);
3913 return;
3914 }
3915
3916 self.option_chain_handlers.insert(
3917 topic,
3918 Subscription {
3919 handler: handler.clone(),
3920 command: Some(command),
3921 },
3922 );
3923 msgbus::subscribe_option_chain(topic.into(), handler, None);
3924 }
3925
3926 pub(crate) fn remove_option_chain_subscription(
3927 &mut self,
3928 topic: MStr<Topic>,
3929 ) -> Option<DataCommand> {
3930 let subscription = self.option_chain_handlers.remove(&topic)?;
3931 msgbus::unsubscribe_option_chain(topic.into(), &subscription.handler);
3932 subscription.command
3933 }
3934
3935 #[cfg(feature = "defi")]
3936 pub(crate) fn add_block_subscription(
3937 &mut self,
3938 topic: MStr<Topic>,
3939 handler: TypedHandler<Block>,
3940 command: DataCommand,
3941 ) -> bool {
3942 if self.block_handlers.contains_key(&topic) {
3943 log::warn!(
3944 "Actor {} attempted duplicate block subscription to '{topic}'",
3945 self.actor_id
3946 );
3947 return false;
3948 }
3949 self.block_handlers.insert(
3950 topic,
3951 Subscription {
3952 handler: handler.clone(),
3953 command: Some(command),
3954 },
3955 );
3956 msgbus::subscribe_defi_blocks(topic.into(), handler, None);
3957 true
3958 }
3959
3960 #[cfg(feature = "defi")]
3961 #[allow(dead_code)]
3962 pub(crate) fn remove_block_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3963 let subscription = self.block_handlers.remove(&topic)?;
3964 msgbus::unsubscribe_defi_blocks(topic.into(), &subscription.handler);
3965 subscription.command
3966 }
3967
3968 #[cfg(feature = "defi")]
3969 pub(crate) fn add_pool_subscription(
3970 &mut self,
3971 topic: MStr<Topic>,
3972 handler: TypedHandler<Pool>,
3973 command: DataCommand,
3974 ) -> bool {
3975 if self.pool_handlers.contains_key(&topic) {
3976 log::warn!(
3977 "Actor {} attempted duplicate pool subscription to '{topic}'",
3978 self.actor_id
3979 );
3980 return false;
3981 }
3982 self.pool_handlers.insert(
3983 topic,
3984 Subscription {
3985 handler: handler.clone(),
3986 command: Some(command),
3987 },
3988 );
3989 msgbus::subscribe_defi_pools(topic.into(), handler, None);
3990 true
3991 }
3992
3993 #[cfg(feature = "defi")]
3994 #[allow(dead_code)]
3995 pub(crate) fn remove_pool_subscription(&mut self, topic: MStr<Topic>) -> Option<DataCommand> {
3996 let subscription = self.pool_handlers.remove(&topic)?;
3997 msgbus::unsubscribe_defi_pools(topic.into(), &subscription.handler);
3998 subscription.command
3999 }
4000
4001 #[cfg(feature = "defi")]
4002 pub(crate) fn add_pool_swap_subscription(
4003 &mut self,
4004 topic: MStr<Topic>,
4005 handler: TypedHandler<PoolSwap>,
4006 command: DataCommand,
4007 ) -> bool {
4008 if self.pool_swap_handlers.contains_key(&topic) {
4009 log::warn!(
4010 "Actor {} attempted duplicate pool swap subscription to '{topic}'",
4011 self.actor_id
4012 );
4013 return false;
4014 }
4015 self.pool_swap_handlers.insert(
4016 topic,
4017 Subscription {
4018 handler: handler.clone(),
4019 command: Some(command),
4020 },
4021 );
4022 msgbus::subscribe_defi_swaps(topic.into(), handler, None);
4023 true
4024 }
4025
4026 #[cfg(feature = "defi")]
4027 #[allow(dead_code)]
4028 pub(crate) fn remove_pool_swap_subscription(
4029 &mut self,
4030 topic: MStr<Topic>,
4031 ) -> Option<DataCommand> {
4032 let subscription = self.pool_swap_handlers.remove(&topic)?;
4033 msgbus::unsubscribe_defi_swaps(topic.into(), &subscription.handler);
4034 subscription.command
4035 }
4036
4037 #[cfg(feature = "defi")]
4038 pub(crate) fn add_pool_liquidity_subscription(
4039 &mut self,
4040 topic: MStr<Topic>,
4041 handler: TypedHandler<PoolLiquidityUpdate>,
4042 command: DataCommand,
4043 ) -> bool {
4044 if self.pool_liquidity_handlers.contains_key(&topic) {
4045 log::warn!(
4046 "Actor {} attempted duplicate pool liquidity subscription to '{topic}'",
4047 self.actor_id
4048 );
4049 return false;
4050 }
4051 self.pool_liquidity_handlers.insert(
4052 topic,
4053 Subscription {
4054 handler: handler.clone(),
4055 command: Some(command),
4056 },
4057 );
4058 msgbus::subscribe_defi_liquidity(topic.into(), handler, None);
4059 true
4060 }
4061
4062 #[cfg(feature = "defi")]
4063 #[allow(dead_code)]
4064 pub(crate) fn remove_pool_liquidity_subscription(
4065 &mut self,
4066 topic: MStr<Topic>,
4067 ) -> Option<DataCommand> {
4068 let subscription = self.pool_liquidity_handlers.remove(&topic)?;
4069 msgbus::unsubscribe_defi_liquidity(topic.into(), &subscription.handler);
4070 subscription.command
4071 }
4072
4073 #[cfg(feature = "defi")]
4074 pub(crate) fn add_pool_collect_subscription(
4075 &mut self,
4076 topic: MStr<Topic>,
4077 handler: TypedHandler<PoolFeeCollect>,
4078 command: DataCommand,
4079 ) -> bool {
4080 if self.pool_collect_handlers.contains_key(&topic) {
4081 log::warn!(
4082 "Actor {} attempted duplicate pool collect subscription to '{topic}'",
4083 self.actor_id
4084 );
4085 return false;
4086 }
4087 self.pool_collect_handlers.insert(
4088 topic,
4089 Subscription {
4090 handler: handler.clone(),
4091 command: Some(command),
4092 },
4093 );
4094 msgbus::subscribe_defi_collects(topic.into(), handler, None);
4095 true
4096 }
4097
4098 #[cfg(feature = "defi")]
4099 #[allow(dead_code)]
4100 pub(crate) fn remove_pool_collect_subscription(
4101 &mut self,
4102 topic: MStr<Topic>,
4103 ) -> Option<DataCommand> {
4104 let subscription = self.pool_collect_handlers.remove(&topic)?;
4105 msgbus::unsubscribe_defi_collects(topic.into(), &subscription.handler);
4106 subscription.command
4107 }
4108
4109 #[cfg(feature = "defi")]
4110 pub(crate) fn add_pool_flash_subscription(
4111 &mut self,
4112 topic: MStr<Topic>,
4113 handler: TypedHandler<PoolFlash>,
4114 command: DataCommand,
4115 ) -> bool {
4116 if self.pool_flash_handlers.contains_key(&topic) {
4117 log::warn!(
4118 "Actor {} attempted duplicate pool flash subscription to '{topic}'",
4119 self.actor_id
4120 );
4121 return false;
4122 }
4123 self.pool_flash_handlers.insert(
4124 topic,
4125 Subscription {
4126 handler: handler.clone(),
4127 command: Some(command),
4128 },
4129 );
4130 msgbus::subscribe_defi_flash(topic.into(), handler, None);
4131 true
4132 }
4133
4134 #[cfg(feature = "defi")]
4135 #[allow(dead_code)]
4136 pub(crate) fn remove_pool_flash_subscription(
4137 &mut self,
4138 topic: MStr<Topic>,
4139 ) -> Option<DataCommand> {
4140 let subscription = self.pool_flash_handlers.remove(&topic)?;
4141 msgbus::unsubscribe_defi_flash(topic.into(), &subscription.handler);
4142 subscription.command
4143 }
4144
4145 pub(crate) fn unsubscribe_all(&mut self) {
4150 let mut commands = Vec::new();
4151
4152 Self::drain_subscriptions(
4153 std::mem::take(&mut self.topic_handlers),
4154 &mut commands,
4155 msgbus::unsubscribe_any,
4156 );
4157 Self::drain_subscriptions(
4158 std::mem::take(&mut self.instrument_handlers),
4159 &mut commands,
4160 msgbus::unsubscribe_instruments,
4161 );
4162 Self::drain_subscriptions(
4163 std::mem::take(&mut self.deltas_handlers),
4164 &mut commands,
4165 msgbus::unsubscribe_book_deltas,
4166 );
4167 Self::drain_subscriptions(
4168 std::mem::take(&mut self.depth_handlers),
4169 &mut commands,
4170 msgbus::unsubscribe_book_depth,
4171 );
4172 Self::drain_subscriptions(
4173 std::mem::take(&mut self.book_handlers),
4174 &mut commands,
4175 |topic, handler| msgbus::unsubscribe_book_snapshots(topic.into(), handler),
4176 );
4177 Self::drain_subscriptions(
4178 std::mem::take(&mut self.quote_handlers),
4179 &mut commands,
4180 |topic, handler| msgbus::unsubscribe_quotes(topic.into(), handler),
4181 );
4182 Self::drain_subscriptions(
4183 std::mem::take(&mut self.trade_handlers),
4184 &mut commands,
4185 |topic, handler| msgbus::unsubscribe_trades(topic.into(), handler),
4186 );
4187 Self::drain_subscriptions(
4188 std::mem::take(&mut self.bar_handlers),
4189 &mut commands,
4190 |topic, handler| msgbus::unsubscribe_bars(topic.into(), handler),
4191 );
4192 Self::drain_subscriptions(
4193 std::mem::take(&mut self.mark_price_handlers),
4194 &mut commands,
4195 |topic, handler| msgbus::unsubscribe_mark_prices(topic.into(), handler),
4196 );
4197 Self::drain_subscriptions(
4198 std::mem::take(&mut self.index_price_handlers),
4199 &mut commands,
4200 |topic, handler| msgbus::unsubscribe_index_prices(topic.into(), handler),
4201 );
4202 Self::drain_subscriptions(
4203 std::mem::take(&mut self.funding_rate_handlers),
4204 &mut commands,
4205 |topic, handler| msgbus::unsubscribe_funding_rates(topic.into(), handler),
4206 );
4207 Self::drain_subscriptions(
4208 std::mem::take(&mut self.option_greeks_handlers),
4209 &mut commands,
4210 |topic, handler| msgbus::unsubscribe_option_greeks(topic.into(), handler),
4211 );
4212 Self::drain_subscriptions(
4213 std::mem::take(&mut self.option_chain_handlers),
4214 &mut commands,
4215 |topic, handler| msgbus::unsubscribe_option_chain(topic.into(), handler),
4216 );
4217
4218 #[cfg(feature = "defi")]
4219 self.unsubscribe_all_defi(&mut commands);
4220
4221 let mut retained = Vec::new();
4222 commands.retain(|command| {
4223 if matches!(command, DataCommand::Subscribe(subscribe)
4224 if subscribe.instrument_ids().is_some() || subscribe.bar_types().is_some())
4225 {
4226 retained.push(command.clone());
4227 false
4228 } else {
4229 true
4230 }
4231 });
4232
4233 self.release_subscriptions(retained);
4234
4235 for command in commands {
4236 if let Some(command) = command.into_unsubscribe(UUID4::new(), self.timestamp_ns()) {
4237 self.send_data_cmd(command);
4238 }
4239 }
4240 #[cfg(feature = "python")]
4241 self.message_bus.clear();
4242 }
4243
4244 #[cfg(feature = "defi")]
4245 fn unsubscribe_all_defi(&mut self, commands: &mut Vec<DataCommand>) {
4246 Self::drain_subscriptions(
4247 std::mem::take(&mut self.block_handlers),
4248 commands,
4249 |topic, handler| msgbus::unsubscribe_defi_blocks(topic.into(), handler),
4250 );
4251 Self::drain_subscriptions(
4252 std::mem::take(&mut self.pool_handlers),
4253 commands,
4254 |topic, handler| msgbus::unsubscribe_defi_pools(topic.into(), handler),
4255 );
4256 Self::drain_subscriptions(
4257 std::mem::take(&mut self.pool_swap_handlers),
4258 commands,
4259 |topic, handler| msgbus::unsubscribe_defi_swaps(topic.into(), handler),
4260 );
4261 Self::drain_subscriptions(
4262 std::mem::take(&mut self.pool_liquidity_handlers),
4263 commands,
4264 |topic, handler| msgbus::unsubscribe_defi_liquidity(topic.into(), handler),
4265 );
4266 Self::drain_subscriptions(
4267 std::mem::take(&mut self.pool_collect_handlers),
4268 commands,
4269 |topic, handler| msgbus::unsubscribe_defi_collects(topic.into(), handler),
4270 );
4271 Self::drain_subscriptions(
4272 std::mem::take(&mut self.pool_flash_handlers),
4273 commands,
4274 |topic, handler| msgbus::unsubscribe_defi_flash(topic.into(), handler),
4275 );
4276 }
4277
4278 fn drain_subscriptions<K, T>(
4279 subscriptions: AHashMap<K, Subscription<T>>,
4280 commands: &mut Vec<DataCommand>,
4281 mut unsubscribe: impl FnMut(K, &T),
4282 ) where
4283 K: AsRef<str>,
4284 {
4285 let mut subscriptions = subscriptions.into_iter().collect::<Vec<_>>();
4286 subscriptions.sort_unstable_by(|(left, _), (right, _)| left.as_ref().cmp(right.as_ref()));
4287
4288 for (key, subscription) in subscriptions {
4289 unsubscribe(key, &subscription.handler);
4290 commands.extend(subscription.command);
4291 }
4292 }
4293
4294 pub fn new(config: DataActorConfig) -> Self {
4296 let actor_id = config.actor_id.unwrap_or_else(Self::default_actor_id);
4297
4298 Self {
4299 actor_id,
4300 config,
4301 trader_id: None, clock: None, cache: None, state: ComponentState::default(),
4305 topic_handlers: AHashMap::new(),
4306 instrument_handlers: AHashMap::new(),
4307 deltas_handlers: AHashMap::new(),
4308 depth_handlers: AHashMap::new(),
4309 book_handlers: AHashMap::new(),
4310 quote_handlers: AHashMap::new(),
4311 trade_handlers: AHashMap::new(),
4312 bar_handlers: AHashMap::new(),
4313 mark_price_handlers: AHashMap::new(),
4314 index_price_handlers: AHashMap::new(),
4315 funding_rate_handlers: AHashMap::new(),
4316 option_greeks_handlers: AHashMap::new(),
4317 option_chain_handlers: AHashMap::new(),
4318 indicators: Indicators::default(),
4319 warning_events: AHashSet::new(),
4320 requests: RefCell::new(RequestLifecycle::default()),
4321 signal_classes: AHashMap::new(),
4322 #[cfg(feature = "defi")]
4323 block_handlers: AHashMap::new(),
4324 #[cfg(feature = "defi")]
4325 pool_handlers: AHashMap::new(),
4326 #[cfg(feature = "defi")]
4327 pool_swap_handlers: AHashMap::new(),
4328 #[cfg(feature = "defi")]
4329 pool_liquidity_handlers: AHashMap::new(),
4330 #[cfg(feature = "defi")]
4331 pool_collect_handlers: AHashMap::new(),
4332 #[cfg(feature = "defi")]
4333 pool_flash_handlers: AHashMap::new(),
4334 #[cfg(feature = "python")]
4335 message_bus: Rc::default(),
4336 }
4337 }
4338
4339 #[must_use]
4341 pub fn registered_indicators(&self) -> Vec<SharedActorIndicator> {
4342 self.indicators.registered_indicators()
4343 }
4344
4345 pub fn indicators_initialized(&self) -> anyhow::Result<bool> {
4351 self.indicators.initialized()
4352 }
4353
4354 pub fn register_indicator_for_quote_ticks(
4356 &mut self,
4357 instrument_id: InstrumentId,
4358 indicator: SharedActorIndicator,
4359 ) {
4360 self.indicators
4361 .register_indicator_for_quote_ticks(instrument_id, indicator);
4362 }
4363
4364 pub fn register_indicator_for_trade_ticks(
4366 &mut self,
4367 instrument_id: InstrumentId,
4368 indicator: SharedActorIndicator,
4369 ) {
4370 self.indicators
4371 .register_indicator_for_trade_ticks(instrument_id, indicator);
4372 }
4373
4374 pub fn register_indicator_for_bars(
4376 &mut self,
4377 bar_type: BarType,
4378 indicator: SharedActorIndicator,
4379 ) {
4380 self.indicators
4381 .register_indicator_for_bars(bar_type, indicator);
4382 }
4383
4384 pub(crate) fn handle_indicators_for_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
4385 self.indicators.handle_quote(quote)
4386 }
4387
4388 pub(crate) fn handle_indicators_for_quotes(&self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
4389 self.indicators.handle_quotes(quotes)
4390 }
4391
4392 pub(crate) fn handle_indicators_for_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
4393 self.indicators.handle_trade(trade)
4394 }
4395
4396 pub(crate) fn handle_indicators_for_trades(&self, trades: &[TradeTick]) -> anyhow::Result<()> {
4397 self.indicators.handle_trades(trades)
4398 }
4399
4400 pub(crate) fn handle_indicators_for_bar(&self, bar: &Bar) -> anyhow::Result<()> {
4401 self.indicators.handle_bar(bar)
4402 }
4403
4404 pub(crate) fn handle_indicators_for_bars(&self, bars: &[Bar]) -> anyhow::Result<()> {
4405 self.indicators.handle_bars(bars)
4406 }
4407
4408 #[must_use]
4410 pub fn mem_address(&self) -> String {
4411 format!("{self:p}")
4412 }
4413
4414 pub fn state(&self) -> ComponentState {
4416 self.state
4417 }
4418
4419 pub fn trader_id(&self) -> Option<TraderId> {
4421 self.trader_id
4422 }
4423
4424 pub fn actor_id(&self) -> ActorId {
4426 self.actor_id
4427 }
4428
4429 fn default_actor_id() -> ActorId {
4430 ActorId::from(stringify!(DataActor))
4431 }
4432
4433 pub fn timestamp_ns(&self) -> UnixNanos {
4435 self.clock_ref().timestamp_ns()
4436 }
4437
4438 pub(super) fn clock_api(&self) -> ClockApi<'_> {
4439 let clock = self.clock.as_ref().unwrap_or_else(|| {
4440 panic!(
4441 "DataActor {} must be registered before calling `clock()` - trader_id: {:?}",
4442 self.actor_id, self.trader_id
4443 )
4444 });
4445 ClockApi::new(clock.as_ref())
4446 }
4447
4448 fn clock_ref(&self) -> Ref<'_, dyn Clock> {
4449 self.clock
4450 .as_ref()
4451 .unwrap_or_else(|| {
4452 panic!(
4453 "DataActor {} must be registered before calling `clock_ref()` - trader_id: {:?}",
4454 self.actor_id, self.trader_id
4455 )
4456 })
4457 .borrow()
4458 }
4459
4460 fn cache_api(&self) -> CacheApi<'_> {
4461 let cache = self.cache.as_ref().unwrap_or_else(|| {
4462 panic!(
4463 "DataActor {} must be registered before calling `cache()` - trader_id: {:?}",
4464 self.actor_id, self.trader_id
4465 )
4466 });
4467 CacheApi::new(cache.as_ref())
4468 }
4469
4470 pub fn register(
4477 &mut self,
4478 trader_id: TraderId,
4479 clock: Rc<RefCell<dyn Clock>>,
4480 cache: Rc<RefCell<Cache>>,
4481 ) -> anyhow::Result<()> {
4482 if let Some(existing_trader_id) = self.trader_id {
4483 anyhow::bail!(
4484 "DataActor {} already registered with trader {existing_trader_id}",
4485 self.actor_id
4486 );
4487 }
4488
4489 {
4491 let _timestamp = clock.borrow().timestamp_ns();
4492 }
4493
4494 {
4496 let _cache_borrow = cache.borrow();
4497 }
4498
4499 #[cfg(feature = "python")]
4500 self.message_bus.register();
4501
4502 self.trader_id = Some(trader_id);
4503 self.clock = Some(clock);
4504 self.cache = Some(cache);
4505
4506 if !self.is_properly_registered() {
4508 anyhow::bail!(
4509 "DataActor {} registration incomplete - validation failed",
4510 self.actor_id
4511 );
4512 }
4513
4514 log::debug!("Registered {} with trader {trader_id}", self.actor_id);
4515 Ok(())
4516 }
4517
4518 pub fn register_warning_event(&mut self, event_type: &str) {
4520 self.warning_events.insert(event_type.to_string());
4521 log::debug!("Registered event type '{event_type}' for warning logs");
4522 }
4523
4524 pub fn deregister_warning_event(&mut self, event_type: &str) {
4526 self.warning_events.remove(event_type);
4527 log::debug!("Deregistered event type '{event_type}' from warning logs");
4528 }
4529
4530 #[cfg(feature = "python")]
4532 pub fn message_bus(&self) -> Rc<PyMessageBusScope> {
4533 Rc::clone(&self.message_bus)
4534 }
4535
4536 pub fn is_registered(&self) -> bool {
4537 self.trader_id.is_some()
4538 }
4539
4540 pub(crate) fn check_registered(&self) {
4541 assert!(
4542 self.is_registered(),
4543 "Actor has not been registered with a Trader"
4544 );
4545 }
4546
4547 fn is_properly_registered(&self) -> bool {
4549 self.trader_id.is_some() && self.clock.is_some() && self.cache.is_some()
4550 }
4551
4552 pub(crate) fn send_data_cmd(&self, command: DataCommand) {
4553 if self.config.log_commands {
4554 log::info!("{CMD}{SEND} {command:?}");
4555 }
4556
4557 let endpoint = MessagingSwitchboard::data_engine_queue_execute();
4558 msgbus::send_data_command(endpoint, command);
4559 }
4560
4561 pub(crate) fn send_unsubscribe_cmd(
4562 &self,
4563 retained: Option<DataCommand>,
4564 fallback: DataCommand,
4565 ) {
4566 let Some(retained) = retained else {
4567 return;
4568 };
4569 let command = retained
4570 .into_unsubscribe(UUID4::new(), self.timestamp_ns())
4571 .unwrap_or(fallback);
4572 self.send_data_cmd(command);
4573 }
4574
4575 #[allow(dead_code)]
4576 fn send_data_req(&self, request: &RequestCommand) {
4577 if self.config.log_commands {
4578 log::info!("{REQ}{SEND} {request:?}");
4579 }
4580
4581 let endpoint = MessagingSwitchboard::data_engine_queue_execute();
4584 msgbus::send_any(endpoint, request.as_any());
4585 }
4586
4587 pub fn shutdown_system(&self, reason: Option<String>) {
4593 self.check_registered();
4594
4595 let command = ShutdownSystem::new(
4597 self.trader_id().unwrap(),
4598 self.actor_id.inner(),
4599 reason,
4600 UUID4::new(),
4601 self.timestamp_ns(),
4602 None, );
4604
4605 let topic = MessagingSwitchboard::shutdown_system_topic();
4606 msgbus::publish_any(topic, command.as_any());
4607 }
4608
4609 pub fn publish_data(&self, data_type: &DataType, data: &CustomData) {
4619 self.check_registered();
4620
4621 let topic = get_custom_topic(data_type);
4622 msgbus::publish_any(topic, data);
4623 }
4624
4625 pub fn publish_signal(&self, name: &str, value: String, ts_event: UnixNanos) {
4637 self.check_registered();
4638
4639 let now = self.timestamp_ns();
4640 let ts_event = if ts_event.as_u64() == 0 {
4641 now
4642 } else {
4643 ts_event
4644 };
4645 let signal = Signal::new(Ustr::from(name), value, ts_event, now);
4646
4647 let data_type = DataType::new(
4648 &format!(
4649 "Signal{}",
4650 nautilus_core::string::conversions::title_case(name)
4651 ),
4652 None,
4653 None,
4654 );
4655 let data = CustomData::new(Arc::new(signal), data_type);
4656 let topic = get_custom_topic(&data.data_type);
4657 msgbus::publish_any(topic, &data);
4658 }
4659
4660 pub fn add_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
4670 self.check_registered();
4671
4672 let cache = self.cache_rc();
4673 if cache.borrow().synthetic(&synthetic.id).is_some() {
4674 anyhow::bail!("`synthetic` {} already exists", synthetic.id);
4675 }
4676 cache.borrow_mut().add_synthetic(synthetic)
4677 }
4678
4679 pub fn update_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
4689 self.check_registered();
4690
4691 let cache = self.cache_rc();
4692 if cache.borrow().synthetic(&synthetic.id).is_none() {
4693 anyhow::bail!("`synthetic` {} does not exist", synthetic.id);
4694 }
4695 cache.borrow_mut().add_synthetic(synthetic)
4696 }
4697
4698 pub fn subscribe_data(
4704 &mut self,
4705 handler: ShareableMessageHandler,
4706 data_type: DataType,
4707 client_id: Option<ClientId>,
4708 params: Option<Params>,
4709 ) {
4710 assert!(
4711 self.is_properly_registered(),
4712 "DataActor {} is not properly registered - trader_id: {:?}, clock: {}, cache: {}",
4713 self.actor_id,
4714 self.trader_id,
4715 self.clock.is_some(),
4716 self.cache.is_some()
4717 );
4718
4719 let mut topics = get_custom_subscription_topics(&data_type);
4720 let Some(topic) = topics.pop() else {
4721 return;
4722 };
4723
4724 for alias in topics {
4725 self.add_subscription_any(alias, handler.clone(), None, None);
4726 }
4727
4728 if client_id.is_none() {
4730 self.add_subscription_any(topic, handler, None, None);
4731 return;
4732 }
4733
4734 let command = DataCommand::Subscribe(SubscribeCommand::Data(SubscribeCustomData {
4735 data_type,
4736 client_id,
4737 venue: None,
4738 command_id: UUID4::new(),
4739 ts_init: self.timestamp_ns(),
4740 correlation_id: None,
4741 params,
4742 }));
4743
4744 if self.add_subscription_any(topic, handler, None, Some(command.clone())) {
4745 self.send_data_cmd(command);
4746 }
4747 }
4748
4749 pub fn subscribe_signal(
4757 &mut self,
4758 handler: ShareableMessageHandler,
4759 name: &str,
4760 priority: Option<u32>,
4761 ) {
4762 self.check_registered();
4763
4764 let pattern = get_signal_pattern(name);
4765 if self.topic_handlers.contains_key(&pattern) {
4766 log::warn!(
4767 "Actor {} attempted duplicate signal subscription to '{pattern}'",
4768 self.actor_id,
4769 );
4770 return;
4771 }
4772 self.topic_handlers.insert(
4773 pattern,
4774 Subscription {
4775 handler: handler.clone(),
4776 command: None,
4777 },
4778 );
4779 msgbus::subscribe_any(pattern, handler, priority);
4780 }
4781
4782 pub fn subscribe_queue_state(
4788 &mut self,
4789 handler: ShareableMessageHandler,
4790 channel: Option<SystemChannel>,
4791 priority: Option<u32>,
4792 ) {
4793 self.check_registered();
4794
4795 let topic = MessagingSwitchboard::queue_state_changed_pattern(channel);
4796 self.add_subscription_any(topic, handler, priority, None);
4797 }
4798
4799 pub fn subscribe_socket_state(
4805 &mut self,
4806 handler: ShareableMessageHandler,
4807 client_id: Option<ClientId>,
4808 endpoint: Option<&str>,
4809 priority: Option<u32>,
4810 ) {
4811 self.check_registered();
4812
4813 let topic = MessagingSwitchboard::socket_state_changed_pattern(client_id, endpoint);
4814 self.add_subscription_any(topic, handler, priority, None);
4815 }
4816
4817 pub fn subscribe_quotes(
4826 &mut self,
4827 handler: TypedHandler<QuoteTick>,
4828 instrument_ids: impl IntoInstrumentIds,
4829 client_id: Option<ClientId>,
4830 params: Option<Params>,
4831 ) -> anyhow::Result<()> {
4832 self.check_registered();
4833 let instrument_ids = instrument_ids.into_instrument_ids()?;
4834 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
4835 quote_routing(
4836 &instrument_ids,
4837 client_id.as_ref(),
4838 params.as_ref(),
4839 &self.cache_ref(),
4840 )?;
4841 let first_venue = instrument_ids[0].venue;
4842 let venue = instrument_ids
4843 .iter()
4844 .all(|id| id.venue == first_venue)
4845 .then_some(first_venue);
4846
4847 let command = SubscribeQuotes {
4848 instrument_ids,
4849 client_id,
4850 venue,
4851 command_id: UUID4::new(),
4852 ts_init: self.timestamp_ns(),
4853 correlation_id: None,
4854 params,
4855 };
4856
4857 let mut acquired = Vec::with_capacity(command.instrument_ids.len());
4858 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
4859 for (instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
4860 let member = command.with_instrument_ids(*instrument_id);
4861
4862 let topic = get_quotes_topic(*instrument_id);
4863 if self.add_quote_subscription(
4864 topic,
4865 handler,
4866 DataCommand::Subscribe(SubscribeCommand::Quotes(member)),
4867 ) {
4868 acquired.push(*instrument_id);
4869 }
4870 }
4871
4872 if !acquired.is_empty() {
4873 let dispatch = command.with_instrument_ids(acquired);
4874 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Quotes(dispatch)));
4875 }
4876
4877 Ok(())
4878 }
4879
4880 pub fn subscribe_instruments(
4882 &mut self,
4883 pattern: MStr<Pattern>,
4884 handler: TypedHandler<InstrumentAny>,
4885 venue: Venue,
4886 client_id: Option<ClientId>,
4887 params: Option<Params>,
4888 ) {
4889 self.check_registered();
4890
4891 let command = DataCommand::Subscribe(SubscribeCommand::Instruments(SubscribeInstruments {
4892 client_id,
4893 venue,
4894 command_id: UUID4::new(),
4895 ts_init: self.timestamp_ns(),
4896 correlation_id: None,
4897 params,
4898 }));
4899
4900 if self.add_instrument_subscription(pattern, handler, command.clone()) {
4901 self.send_data_cmd(command);
4902 }
4903 }
4904
4905 pub fn subscribe_instrument(
4914 &mut self,
4915 handler: TypedHandler<InstrumentAny>,
4916 instrument_ids: impl IntoInstrumentIds,
4917 client_id: Option<ClientId>,
4918 params: Option<Params>,
4919 ) -> anyhow::Result<()> {
4920 self.check_registered();
4921 let instrument_ids = instrument_ids.into_instrument_ids()?;
4922 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
4923 let venue = instrument_ids
4924 .iter()
4925 .all(|id| id.venue == instrument_ids[0].venue)
4926 .then_some(instrument_ids[0].venue);
4927
4928 let command = SubscribeInstrument {
4929 instrument_ids,
4930 client_id,
4931 venue,
4932 command_id: UUID4::new(),
4933 ts_init: self.timestamp_ns(),
4934 correlation_id: None,
4935 params,
4936 };
4937
4938 let mut acquired = Vec::new();
4939 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
4940 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
4941 let member = command.with_instrument_ids(instrument_id);
4942
4943 let topic = get_instrument_topic(instrument_id);
4944 if self.add_instrument_subscription(
4945 topic.into(),
4946 handler,
4947 DataCommand::Subscribe(SubscribeCommand::Instrument(member)),
4948 ) {
4949 acquired.push(instrument_id);
4950 }
4951 }
4952
4953 if !acquired.is_empty() {
4954 let dispatch = command.with_instrument_ids(acquired);
4955 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Instrument(
4956 dispatch,
4957 )));
4958 }
4959
4960 Ok(())
4961 }
4962
4963 #[expect(clippy::too_many_arguments)]
4972 pub fn subscribe_book_deltas(
4973 &mut self,
4974 handler: TypedHandler<OrderBookDeltas>,
4975 instrument_ids: impl IntoInstrumentIds,
4976 book_type: BookType,
4977 depth: Option<NonZeroUsize>,
4978 client_id: Option<ClientId>,
4979 managed: bool,
4980 params: Option<Params>,
4981 ) -> anyhow::Result<()> {
4982 self.check_registered();
4983 let instrument_ids = instrument_ids.into_instrument_ids()?;
4984 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
4985 let venue = instrument_ids
4986 .iter()
4987 .all(|id| id.venue == instrument_ids[0].venue)
4988 .then_some(instrument_ids[0].venue);
4989
4990 let command = SubscribeBookDeltas {
4991 instrument_ids,
4992 book_type,
4993 client_id,
4994 venue,
4995 command_id: UUID4::new(),
4996 ts_init: self.timestamp_ns(),
4997 depth,
4998 managed,
4999 correlation_id: None,
5000 params,
5001 };
5002
5003 let mut acquired = Vec::new();
5004 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5005 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5006 let member = command.with_instrument_ids(instrument_id);
5007 let topic = if is_parent_subscription(command.params.as_ref()) {
5008 get_book_deltas_pattern(instrument_id)
5009 } else {
5010 get_book_deltas_topic(instrument_id).into()
5011 };
5012
5013 if self.add_deltas_subscription(
5014 topic,
5015 handler,
5016 DataCommand::Subscribe(SubscribeCommand::BookDeltas(member)),
5017 ) {
5018 acquired.push(instrument_id);
5019 }
5020 }
5021
5022 if !acquired.is_empty() {
5023 let dispatch = command.with_instrument_ids(acquired);
5024 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::BookDeltas(
5025 dispatch,
5026 )));
5027 }
5028
5029 Ok(())
5030 }
5031
5032 #[expect(clippy::too_many_arguments)]
5041 pub fn subscribe_book_depth(
5042 &mut self,
5043 handler: TypedHandler<OrderBookDepth>,
5044 instrument_ids: impl IntoInstrumentIds,
5045 book_type: BookType,
5046 depth: Option<NonZeroUsize>,
5047 client_id: Option<ClientId>,
5048 managed: bool,
5049 params: Option<Params>,
5050 ) -> anyhow::Result<()> {
5051 self.check_registered();
5052 let instrument_ids = instrument_ids.into_instrument_ids()?;
5053 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5054 let venue = instrument_ids
5055 .iter()
5056 .all(|id| id.venue == instrument_ids[0].venue)
5057 .then_some(instrument_ids[0].venue);
5058
5059 let command = SubscribeBookDepth {
5060 instrument_ids,
5061 book_type,
5062 client_id,
5063 venue,
5064 command_id: UUID4::new(),
5065 ts_init: self.timestamp_ns(),
5066 depth,
5067 managed,
5068 correlation_id: None,
5069 params,
5070 };
5071
5072 let mut acquired = Vec::new();
5073 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5074 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5075 let member = command.with_instrument_ids(instrument_id);
5076 let topic = if is_parent_subscription(command.params.as_ref()) {
5077 get_book_depth_pattern(instrument_id)
5078 } else {
5079 get_book_depth_topic(instrument_id).into()
5080 };
5081
5082 if self.add_depth_subscription(
5083 topic,
5084 handler,
5085 DataCommand::Subscribe(SubscribeCommand::BookDepth(member)),
5086 ) {
5087 acquired.push(instrument_id);
5088 }
5089 }
5090
5091 if !acquired.is_empty() {
5092 let dispatch = command.with_instrument_ids(acquired);
5093 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::BookDepth(
5094 dispatch,
5095 )));
5096 }
5097
5098 Ok(())
5099 }
5100
5101 #[expect(clippy::too_many_arguments)]
5110 pub fn subscribe_book_at_interval(
5111 &mut self,
5112 handler: TypedHandler<OrderBook>,
5113 instrument_ids: impl IntoInstrumentIds,
5114 book_type: BookType,
5115 depth: Option<NonZeroUsize>,
5116 interval_ms: NonZeroUsize,
5117 client_id: Option<ClientId>,
5118 params: Option<Params>,
5119 ) -> anyhow::Result<()> {
5120 self.check_registered();
5121 let instrument_ids = instrument_ids.into_instrument_ids()?;
5122 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5123 let venue = instrument_ids
5124 .iter()
5125 .all(|id| id.venue == instrument_ids[0].venue)
5126 .then_some(instrument_ids[0].venue);
5127
5128 let command = SubscribeBookSnapshots {
5129 instrument_ids,
5130 book_type,
5131 client_id,
5132 venue,
5133 command_id: UUID4::new(),
5134 ts_init: self.timestamp_ns(),
5135 depth,
5136 interval_ms,
5137 correlation_id: None,
5138 params,
5139 };
5140
5141 let mut acquired = Vec::new();
5142 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5143 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5144 let member = command.with_instrument_ids(instrument_id);
5145
5146 let topic = get_book_snapshots_topic(instrument_id, interval_ms);
5147 if self.add_book_snapshot_subscription(
5148 topic,
5149 handler,
5150 DataCommand::Subscribe(SubscribeCommand::BookSnapshots(member)),
5151 ) {
5152 acquired.push(instrument_id);
5153 }
5154 }
5155
5156 if !acquired.is_empty() {
5157 let dispatch = command.with_instrument_ids(acquired);
5158 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::BookSnapshots(
5159 dispatch,
5160 )));
5161 }
5162
5163 Ok(())
5164 }
5165
5166 pub fn subscribe_trades(
5174 &mut self,
5175 handler: TypedHandler<TradeTick>,
5176 instrument_ids: impl IntoInstrumentIds,
5177 client_id: Option<ClientId>,
5178 params: Option<Params>,
5179 ) -> anyhow::Result<()> {
5180 self.check_registered();
5181 let instrument_ids = instrument_ids.into_instrument_ids()?;
5182 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5183 let mut groups: IndexMap<Option<Venue>, Vec<InstrumentId>> = IndexMap::new();
5184 for id in instrument_ids {
5185 groups
5186 .entry(id.is_synthetic().then_some(id.venue))
5187 .or_default()
5188 .push(id);
5189 }
5190
5191 let contexts = std::iter::repeat_n((handler, params), groups.len());
5192 for ((venue, ids), (handler, params)) in groups.into_iter().zip(contexts) {
5193 let venue = venue.or_else(|| {
5194 ids.iter()
5195 .all(|id| id.venue == ids[0].venue)
5196 .then_some(ids[0].venue)
5197 });
5198
5199 let command = SubscribeTrades::new(
5200 ids,
5201 client_id,
5202 venue,
5203 UUID4::new(),
5204 self.timestamp_ns(),
5205 None,
5206 params,
5207 );
5208 let mut acquired = Vec::new();
5209
5210 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5211 for (id, handler) in command.instrument_ids.iter().zip(handlers) {
5212 let member = command.with_instrument_ids(*id);
5213
5214 let topic = get_trades_topic(*id);
5215 if self.add_trade_subscription(
5216 topic,
5217 handler,
5218 DataCommand::Subscribe(SubscribeCommand::Trades(member)),
5219 ) {
5220 acquired.push(*id);
5221 }
5222 }
5223
5224 if !acquired.is_empty() {
5225 let dispatch = command.with_instrument_ids(acquired);
5226 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Trades(dispatch)));
5227 }
5228 }
5229
5230 Ok(())
5231 }
5232
5233 #[expect(
5239 clippy::needless_pass_by_value,
5240 reason = "preserves the owned bar handler API"
5241 )]
5242 pub fn subscribe_bars(
5243 &mut self,
5244 handler: TypedHandler<Bar>,
5245 bar_types: impl IntoBarTypes,
5246 client_id: Option<ClientId>,
5247 params: Option<Params>,
5248 ) -> anyhow::Result<()> {
5249 self.check_registered();
5250 let bar_types = bar_types.into_bar_types()?;
5251 ensure_routable_bar_types(&bar_types, client_id.is_some())?;
5252 let first_venue = bar_types[0].instrument_id().venue;
5253 let venue = bar_types
5254 .iter()
5255 .all(|bt| bt.instrument_id().venue == first_venue)
5256 .then_some(first_venue);
5257
5258 let command = SubscribeBars::new(
5259 bar_types,
5260 client_id,
5261 venue,
5262 UUID4::new(),
5263 self.timestamp_ns(),
5264 None,
5265 params,
5266 );
5267 let mut acquired = Vec::new();
5268
5269 for bar_type in &command.bar_types {
5270 let member = command.with_bar_types(*bar_type);
5271 let topic = get_bars_topic(bar_type.standard());
5272 if self.add_bar_subscription(
5273 topic,
5274 handler.clone(),
5275 DataCommand::Subscribe(SubscribeCommand::Bars(member)),
5276 ) {
5277 acquired.push(*bar_type);
5278 }
5279 }
5280
5281 if !acquired.is_empty() {
5282 let dispatch = command.with_bar_types(acquired);
5283 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::Bars(dispatch)));
5284 }
5285
5286 Ok(())
5287 }
5288
5289 pub fn subscribe_mark_prices(
5297 &mut self,
5298 handler: TypedHandler<MarkPriceUpdate>,
5299 instrument_ids: impl IntoInstrumentIds,
5300 client_id: Option<ClientId>,
5301 params: Option<Params>,
5302 ) -> anyhow::Result<()> {
5303 self.check_registered();
5304 let instrument_ids = instrument_ids.into_instrument_ids()?;
5305 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5306 let venue = instrument_ids
5307 .iter()
5308 .all(|id| id.venue == instrument_ids[0].venue)
5309 .then_some(instrument_ids[0].venue);
5310
5311 let command = SubscribeMarkPrices {
5312 instrument_ids,
5313 client_id,
5314 venue,
5315 command_id: UUID4::new(),
5316 ts_init: self.timestamp_ns(),
5317 correlation_id: None,
5318 params,
5319 };
5320
5321 let mut acquired = Vec::new();
5322 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5323 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5324 let member = command.with_instrument_ids(instrument_id);
5325
5326 let topic = get_mark_price_topic(instrument_id);
5327 if self.add_mark_price_subscription(
5328 topic,
5329 handler,
5330 DataCommand::Subscribe(SubscribeCommand::MarkPrices(member)),
5331 ) {
5332 acquired.push(instrument_id);
5333 }
5334 }
5335
5336 if !acquired.is_empty() {
5337 let dispatch = command.with_instrument_ids(acquired);
5338 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::MarkPrices(
5339 dispatch,
5340 )));
5341 }
5342
5343 Ok(())
5344 }
5345
5346 pub fn subscribe_index_prices(
5354 &mut self,
5355 handler: TypedHandler<IndexPriceUpdate>,
5356 instrument_ids: impl IntoInstrumentIds,
5357 client_id: Option<ClientId>,
5358 params: Option<Params>,
5359 ) -> anyhow::Result<()> {
5360 self.check_registered();
5361 let instrument_ids = instrument_ids.into_instrument_ids()?;
5362 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5363 let venue = instrument_ids
5364 .iter()
5365 .all(|id| id.venue == instrument_ids[0].venue)
5366 .then_some(instrument_ids[0].venue);
5367
5368 let command = SubscribeIndexPrices {
5369 instrument_ids,
5370 client_id,
5371 venue,
5372 command_id: UUID4::new(),
5373 ts_init: self.timestamp_ns(),
5374 correlation_id: None,
5375 params,
5376 };
5377
5378 let mut acquired = Vec::new();
5379 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5380 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5381 let member = command.with_instrument_ids(instrument_id);
5382
5383 let topic = get_index_price_topic(instrument_id);
5384 if self.add_index_price_subscription(
5385 topic,
5386 handler,
5387 DataCommand::Subscribe(SubscribeCommand::IndexPrices(member)),
5388 ) {
5389 acquired.push(instrument_id);
5390 }
5391 }
5392
5393 if !acquired.is_empty() {
5394 let dispatch = command.with_instrument_ids(acquired);
5395 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::IndexPrices(
5396 dispatch,
5397 )));
5398 }
5399
5400 Ok(())
5401 }
5402
5403 pub fn subscribe_funding_rates(
5411 &mut self,
5412 handler: TypedHandler<FundingRateUpdate>,
5413 instrument_ids: impl IntoInstrumentIds,
5414 client_id: Option<ClientId>,
5415 params: Option<Params>,
5416 ) -> anyhow::Result<()> {
5417 self.check_registered();
5418 let instrument_ids = instrument_ids.into_instrument_ids()?;
5419 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5420 let venue = instrument_ids
5421 .iter()
5422 .all(|id| id.venue == instrument_ids[0].venue)
5423 .then_some(instrument_ids[0].venue);
5424
5425 let command = SubscribeFundingRates {
5426 instrument_ids,
5427 client_id,
5428 venue,
5429 command_id: UUID4::new(),
5430 ts_init: self.timestamp_ns(),
5431 correlation_id: None,
5432 params,
5433 };
5434
5435 let mut acquired = Vec::new();
5436 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5437 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5438 let member = command.with_instrument_ids(instrument_id);
5439
5440 let topic = get_funding_rate_topic(instrument_id);
5441 if self.add_funding_rate_subscription(
5442 topic,
5443 handler,
5444 DataCommand::Subscribe(SubscribeCommand::FundingRates(member)),
5445 ) {
5446 acquired.push(instrument_id);
5447 }
5448 }
5449
5450 if !acquired.is_empty() {
5451 let dispatch = command.with_instrument_ids(acquired);
5452 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::FundingRates(
5453 dispatch,
5454 )));
5455 }
5456
5457 Ok(())
5458 }
5459
5460 pub fn subscribe_option_greeks(
5469 &mut self,
5470 handler: TypedHandler<OptionGreeks>,
5471 instrument_ids: impl IntoInstrumentIds,
5472 client_id: Option<ClientId>,
5473 params: Option<Params>,
5474 ) -> anyhow::Result<()> {
5475 self.check_registered();
5476 let instrument_ids = instrument_ids.into_instrument_ids()?;
5477 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5478 let venue = instrument_ids
5479 .iter()
5480 .all(|id| id.venue == instrument_ids[0].venue)
5481 .then_some(instrument_ids[0].venue);
5482
5483 let command = SubscribeOptionGreeks {
5484 instrument_ids,
5485 client_id,
5486 venue,
5487 command_id: UUID4::new(),
5488 ts_init: self.timestamp_ns(),
5489 correlation_id: None,
5490 params,
5491 };
5492
5493 let mut acquired = Vec::new();
5494 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5495 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5496 let member = command.with_instrument_ids(instrument_id);
5497
5498 let topic = get_option_greeks_topic(instrument_id);
5499 if self.add_option_greeks_subscription(
5500 topic,
5501 handler,
5502 DataCommand::Subscribe(SubscribeCommand::OptionGreeks(member)),
5503 ) {
5504 acquired.push(instrument_id);
5505 }
5506 }
5507
5508 if !acquired.is_empty() {
5509 let dispatch = command.with_instrument_ids(acquired);
5510 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::OptionGreeks(
5511 dispatch,
5512 )));
5513 }
5514
5515 Ok(())
5516 }
5517
5518 pub fn subscribe_instrument_status(
5527 &mut self,
5528 handler: ShareableMessageHandler,
5529 instrument_ids: impl IntoInstrumentIds,
5530 client_id: Option<ClientId>,
5531 params: Option<Params>,
5532 ) -> anyhow::Result<()> {
5533 self.check_registered();
5534 let instrument_ids = instrument_ids.into_instrument_ids()?;
5535 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5536 let venue = instrument_ids
5537 .iter()
5538 .all(|id| id.venue == instrument_ids[0].venue)
5539 .then_some(instrument_ids[0].venue);
5540
5541 let command = SubscribeInstrumentStatus {
5542 instrument_ids,
5543 client_id,
5544 venue,
5545 command_id: UUID4::new(),
5546 ts_init: self.timestamp_ns(),
5547 correlation_id: None,
5548 params,
5549 };
5550
5551 let mut acquired = Vec::new();
5552 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5553 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5554 let member = command.with_instrument_ids(instrument_id);
5555
5556 let topic = get_instrument_status_topic(instrument_id);
5557 if self.add_subscription_any(
5558 topic,
5559 handler,
5560 None,
5561 Some(DataCommand::Subscribe(SubscribeCommand::InstrumentStatus(
5562 member,
5563 ))),
5564 ) {
5565 acquired.push(instrument_id);
5566 }
5567 }
5568
5569 if !acquired.is_empty() {
5570 let dispatch = command.with_instrument_ids(acquired);
5571 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::InstrumentStatus(
5572 dispatch,
5573 )));
5574 }
5575
5576 Ok(())
5577 }
5578
5579 pub fn subscribe_instrument_close(
5588 &mut self,
5589 handler: ShareableMessageHandler,
5590 instrument_ids: impl IntoInstrumentIds,
5591 client_id: Option<ClientId>,
5592 params: Option<Params>,
5593 ) -> anyhow::Result<()> {
5594 self.check_registered();
5595 let instrument_ids = instrument_ids.into_instrument_ids()?;
5596 self.validate_subscription(&instrument_ids, client_id.as_ref())?;
5597 let venue = instrument_ids
5598 .iter()
5599 .all(|id| id.venue == instrument_ids[0].venue)
5600 .then_some(instrument_ids[0].venue);
5601
5602 let command = SubscribeInstrumentClose {
5603 instrument_ids,
5604 client_id,
5605 venue,
5606 command_id: UUID4::new(),
5607 ts_init: self.timestamp_ns(),
5608 correlation_id: None,
5609 params,
5610 };
5611
5612 let mut acquired = Vec::new();
5613 let handlers = std::iter::repeat_n(handler, command.instrument_ids.len());
5614 for (&instrument_id, handler) in command.instrument_ids.iter().zip(handlers) {
5615 let member = command.with_instrument_ids(instrument_id);
5616
5617 let topic = get_instrument_close_topic(instrument_id);
5618 if self.add_instrument_close_subscription(
5619 topic,
5620 handler,
5621 DataCommand::Subscribe(SubscribeCommand::InstrumentClose(member)),
5622 ) {
5623 acquired.push(instrument_id);
5624 }
5625 }
5626
5627 if !acquired.is_empty() {
5628 let dispatch = command.with_instrument_ids(acquired);
5629 self.send_data_cmd(DataCommand::Subscribe(SubscribeCommand::InstrumentClose(
5630 dispatch,
5631 )));
5632 }
5633
5634 Ok(())
5635 }
5636
5637 fn validate_subscription(
5638 &self,
5639 instrument_ids: &[InstrumentId],
5640 client_id: Option<&ClientId>,
5641 ) -> anyhow::Result<()> {
5642 ensure_routable_instrument_ids(instrument_ids, client_id.is_some())?;
5643 ensure_subscription_routes(instrument_ids, client_id, &self.cache_ref())
5644 }
5645
5646 #[expect(
5652 clippy::too_many_arguments,
5653 reason = "subscription command mirrors the option chain request fields"
5654 )]
5655 pub fn subscribe_option_chain(
5656 &mut self,
5657 topic: MStr<Topic>,
5658 handler: TypedHandler<OptionChainSlice>,
5659 series_id: OptionSeriesId,
5660 strike_range: StrikeRange,
5661 snapshot_interval_ms: Option<u64>,
5662 client_id: Option<ClientId>,
5663 params: Option<Params>,
5664 ) -> anyhow::Result<()> {
5665 self.check_registered();
5666
5667 let correlation_id = self
5668 .option_chain_handlers
5669 .get(&topic)
5670 .and_then(|subscription| match subscription.command.as_ref() {
5671 Some(DataCommand::Subscribe(SubscribeCommand::OptionChain(command))) => {
5672 Some(command.correlation_id.unwrap_or(command.command_id))
5673 }
5674 _ => None,
5675 });
5676
5677 let mut subscribe = SubscribeOptionChain::new(
5678 series_id,
5679 strike_range,
5680 snapshot_interval_ms,
5681 UUID4::new(),
5682 self.timestamp_ns(),
5683 client_id,
5684 Some(series_id.venue),
5685 params,
5686 )?;
5687
5688 subscribe.correlation_id = correlation_id;
5689 let command = DataCommand::Subscribe(SubscribeCommand::OptionChain(subscribe));
5690
5691 self.set_option_chain_subscription(topic, handler, command.clone());
5692 self.send_data_cmd(command);
5693 Ok(())
5694 }
5695
5696 pub fn unsubscribe_data(
5698 &mut self,
5699 data_type: DataType,
5700 client_id: Option<ClientId>,
5701 params: Option<Params>,
5702 ) {
5703 self.check_registered();
5704
5705 let mut retained = None;
5706
5707 for topic in get_custom_subscription_topics(&data_type) {
5708 if let Some(command) = self.remove_subscription_any(topic) {
5709 retained = Some(command);
5710 }
5711 }
5712
5713 if client_id.is_none() && retained.is_none() {
5714 return;
5715 }
5716
5717 let command = UnsubscribeCommand::Data(UnsubscribeCustomData {
5718 data_type,
5719 client_id,
5720 venue: None,
5721 command_id: UUID4::new(),
5722 ts_init: self.timestamp_ns(),
5723 correlation_id: None,
5724 params,
5725 });
5726
5727 self.send_unsubscribe_cmd(retained, DataCommand::Unsubscribe(command));
5728 }
5729
5730 pub fn unsubscribe_signal(&mut self, name: &str) {
5736 self.check_registered();
5737
5738 let pattern = get_signal_pattern(name);
5739 if let Some(subscription) = self.topic_handlers.remove(&pattern) {
5740 msgbus::unsubscribe_any(pattern, &subscription.handler);
5741 } else {
5742 log::warn!(
5743 "Actor {} attempted to unsubscribe from signal pattern '{pattern}' when not subscribed",
5744 self.actor_id,
5745 );
5746 }
5747 }
5748
5749 pub fn unsubscribe_queue_state(&mut self, channel: Option<SystemChannel>) {
5755 self.check_registered();
5756
5757 let topic = MessagingSwitchboard::queue_state_changed_pattern(channel);
5758 let _ = self.remove_subscription_any(topic);
5759 }
5760
5761 pub fn unsubscribe_socket_state(
5767 &mut self,
5768 client_id: Option<ClientId>,
5769 endpoint: Option<&str>,
5770 ) {
5771 self.check_registered();
5772
5773 let topic = MessagingSwitchboard::socket_state_changed_pattern(client_id, endpoint);
5774 let _ = self.remove_subscription_any(topic);
5775 }
5776
5777 pub fn unsubscribe_instruments(
5779 &mut self,
5780 venue: Venue,
5781 client_id: Option<ClientId>,
5782 params: Option<Params>,
5783 ) {
5784 self.check_registered();
5785
5786 let pattern = get_instruments_pattern(venue);
5787 let retained = self.remove_instrument_subscription(pattern);
5788
5789 let command = UnsubscribeCommand::Instruments(UnsubscribeInstruments {
5790 client_id,
5791 venue,
5792 command_id: UUID4::new(),
5793 ts_init: self.timestamp_ns(),
5794 correlation_id: None,
5795 params,
5796 });
5797
5798 self.send_unsubscribe_cmd(retained, DataCommand::Unsubscribe(command));
5799 }
5800
5801 pub fn unsubscribe_instrument(
5809 &mut self,
5810 instrument_ids: impl IntoInstrumentIds,
5811 client_id: Option<ClientId>,
5812 _params: Option<Params>,
5813 ) -> anyhow::Result<()> {
5814 self.check_registered();
5815 let instrument_ids = instrument_ids.into_instrument_ids()?;
5816 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5817 let mut retained = Vec::new();
5818
5819 for instrument_id in instrument_ids {
5820 let topic = get_instrument_topic(instrument_id);
5821 if let Some(command) = self.remove_instrument_subscription(topic.into()) {
5822 retained.push(command);
5823 }
5824 }
5825
5826 self.release_subscriptions(retained);
5827 Ok(())
5828 }
5829
5830 #[expect(
5838 clippy::needless_pass_by_value,
5839 reason = "preserves the instrument unsubscribe API"
5840 )]
5841 pub fn unsubscribe_book_deltas(
5842 &mut self,
5843 instrument_ids: impl IntoInstrumentIds,
5844 client_id: Option<ClientId>,
5845 params: Option<Params>,
5846 ) -> anyhow::Result<()> {
5847 self.check_registered();
5848 let instrument_ids = instrument_ids.into_instrument_ids()?;
5849 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5850 let mut retained = Vec::new();
5851
5852 for instrument_id in instrument_ids {
5853 let topic = if is_parent_subscription(params.as_ref()) {
5854 get_book_deltas_pattern(instrument_id)
5855 } else {
5856 get_book_deltas_topic(instrument_id).into()
5857 };
5858
5859 if let Some(command) = self.remove_deltas_subscription(topic) {
5860 retained.push(command);
5861 }
5862 }
5863
5864 self.release_subscriptions(retained);
5865 Ok(())
5866 }
5867
5868 #[expect(
5876 clippy::needless_pass_by_value,
5877 reason = "preserves the instrument unsubscribe API"
5878 )]
5879 pub fn unsubscribe_book_depth(
5880 &mut self,
5881 instrument_ids: impl IntoInstrumentIds,
5882 client_id: Option<ClientId>,
5883 params: Option<Params>,
5884 ) -> anyhow::Result<()> {
5885 self.check_registered();
5886 let instrument_ids = instrument_ids.into_instrument_ids()?;
5887 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5888 let mut retained = Vec::new();
5889
5890 for instrument_id in instrument_ids {
5891 let topic = if is_parent_subscription(params.as_ref()) {
5892 get_book_depth_pattern(instrument_id)
5893 } else {
5894 get_book_depth_topic(instrument_id).into()
5895 };
5896
5897 if let Some(command) = self.remove_depth_subscription(topic) {
5898 retained.push(command);
5899 }
5900 }
5901
5902 self.release_subscriptions(retained);
5903 Ok(())
5904 }
5905
5906 pub fn unsubscribe_book_at_interval(
5914 &mut self,
5915 instrument_ids: impl IntoInstrumentIds,
5916 interval_ms: NonZeroUsize,
5917 client_id: Option<ClientId>,
5918 _params: Option<Params>,
5919 ) -> anyhow::Result<()> {
5920 self.check_registered();
5921 let instrument_ids = instrument_ids.into_instrument_ids()?;
5922 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5923 let mut retained = Vec::new();
5924
5925 for instrument_id in instrument_ids {
5926 let topic = get_book_snapshots_topic(instrument_id, interval_ms);
5927 if let Some(command) = self.remove_book_snapshot_subscription(topic) {
5928 retained.push(command);
5929 }
5930 }
5931
5932 self.release_subscriptions(retained);
5933 Ok(())
5934 }
5935
5936 pub fn unsubscribe_quotes(
5946 &mut self,
5947 instrument_ids: impl IntoInstrumentIds,
5948 client_id: Option<ClientId>,
5949 _params: Option<Params>,
5950 ) -> anyhow::Result<()> {
5951 self.check_registered();
5952 let instrument_ids = instrument_ids.into_instrument_ids()?;
5953 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5954 let mut retained = Vec::new();
5955 for instrument_id in instrument_ids {
5956 retained.extend(self.remove_quote_subscription(get_quotes_topic(instrument_id)));
5957 }
5958
5959 self.release_subscriptions(retained);
5960 Ok(())
5961 }
5962
5963 pub fn unsubscribe_trades(
5971 &mut self,
5972 instrument_ids: impl IntoInstrumentIds,
5973 client_id: Option<ClientId>,
5974 _params: Option<Params>,
5975 ) -> anyhow::Result<()> {
5976 self.check_registered();
5977 let instrument_ids = instrument_ids.into_instrument_ids()?;
5978 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
5979 let mut retained = Vec::new();
5980 for instrument_id in instrument_ids {
5981 retained.extend(self.remove_trade_subscription(get_trades_topic(instrument_id)));
5982 }
5983
5984 self.release_subscriptions(retained);
5985 Ok(())
5986 }
5987
5988 fn release_subscriptions(&self, retained: Vec<DataCommand>) {
5989 let ts_init = self.timestamp_ns();
5990 let mut releases = IndexMap::new();
5991
5992 for retained in retained {
5993 let DataCommand::Subscribe(subscribe) = &retained else {
5994 unreachable!("instrument subscriptions retain a subscribe command");
5995 };
5996
5997 let acquisition_id = subscribe.command_id();
5998
5999 let Some(DataCommand::Unsubscribe(command)) =
6000 retained.into_unsubscribe(acquisition_id, ts_init)
6001 else {
6002 unreachable!("instrument subscriptions retain a subscribe command");
6003 };
6004
6005 releases
6006 .entry(acquisition_id)
6007 .or_insert_with(Vec::new)
6008 .push(command);
6009 }
6010
6011 for group in releases.into_values() {
6012 for command in UnsubscribeCommand::group(group) {
6013 let command_id = UUID4::new();
6014
6015 let command = match command {
6016 UnsubscribeCommand::Instrument(command) => {
6017 UnsubscribeCommand::Instrument(UnsubscribeInstrument {
6018 command_id,
6019 ..command
6020 })
6021 }
6022 UnsubscribeCommand::BookDeltas(command) => {
6023 UnsubscribeCommand::BookDeltas(UnsubscribeBookDeltas {
6024 command_id,
6025 ..command
6026 })
6027 }
6028 UnsubscribeCommand::BookDepth(command) => {
6029 UnsubscribeCommand::BookDepth(UnsubscribeBookDepth {
6030 command_id,
6031 ..command
6032 })
6033 }
6034 UnsubscribeCommand::BookSnapshots(command) => {
6035 UnsubscribeCommand::BookSnapshots(UnsubscribeBookSnapshots {
6036 command_id,
6037 ..command
6038 })
6039 }
6040 UnsubscribeCommand::Quotes(command) => {
6041 UnsubscribeCommand::Quotes(UnsubscribeQuotes {
6042 command_id,
6043 ..command
6044 })
6045 }
6046 UnsubscribeCommand::Trades(command) => {
6047 UnsubscribeCommand::Trades(UnsubscribeTrades {
6048 command_id,
6049 ..command
6050 })
6051 }
6052 UnsubscribeCommand::Bars(command) => {
6053 UnsubscribeCommand::Bars(UnsubscribeBars {
6054 command_id,
6055 ..command
6056 })
6057 }
6058 UnsubscribeCommand::MarkPrices(command) => {
6059 UnsubscribeCommand::MarkPrices(UnsubscribeMarkPrices {
6060 command_id,
6061 ..command
6062 })
6063 }
6064 UnsubscribeCommand::IndexPrices(command) => {
6065 UnsubscribeCommand::IndexPrices(UnsubscribeIndexPrices {
6066 command_id,
6067 ..command
6068 })
6069 }
6070 UnsubscribeCommand::FundingRates(command) => {
6071 UnsubscribeCommand::FundingRates(UnsubscribeFundingRates {
6072 command_id,
6073 ..command
6074 })
6075 }
6076 UnsubscribeCommand::InstrumentStatus(command) => {
6077 UnsubscribeCommand::InstrumentStatus(UnsubscribeInstrumentStatus {
6078 command_id,
6079 ..command
6080 })
6081 }
6082 UnsubscribeCommand::InstrumentClose(command) => {
6083 UnsubscribeCommand::InstrumentClose(UnsubscribeInstrumentClose {
6084 command_id,
6085 ..command
6086 })
6087 }
6088 UnsubscribeCommand::OptionGreeks(command) => {
6089 UnsubscribeCommand::OptionGreeks(UnsubscribeOptionGreeks {
6090 command_id,
6091 ..command
6092 })
6093 }
6094 _ => unreachable!("instrument releases retain instrument commands"),
6095 };
6096
6097 self.send_data_cmd(DataCommand::Unsubscribe(command));
6098 }
6099 }
6100 }
6101
6102 pub fn unsubscribe_bars(
6108 &mut self,
6109 bar_types: impl IntoBarTypes,
6110 client_id: Option<ClientId>,
6111 _params: Option<Params>,
6112 ) -> anyhow::Result<()> {
6113 self.check_registered();
6114 let bar_types = bar_types.into_bar_types()?;
6115 ensure_routable_bar_types(&bar_types, client_id.is_some())?;
6116 let retained = bar_types
6117 .into_iter()
6118 .filter_map(|bar_type| self.remove_bar_subscription(bar_type))
6119 .collect();
6120 self.release_subscriptions(retained);
6121 Ok(())
6122 }
6123
6124 pub fn unsubscribe_mark_prices(
6132 &mut self,
6133 instrument_ids: impl IntoInstrumentIds,
6134 client_id: Option<ClientId>,
6135 _params: Option<Params>,
6136 ) -> anyhow::Result<()> {
6137 self.check_registered();
6138 let instrument_ids = instrument_ids.into_instrument_ids()?;
6139 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6140 let mut retained = Vec::new();
6141
6142 for instrument_id in instrument_ids {
6143 let topic = get_mark_price_topic(instrument_id);
6144 if let Some(command) = self.remove_mark_price_subscription(topic) {
6145 retained.push(command);
6146 }
6147 }
6148
6149 self.release_subscriptions(retained);
6150 Ok(())
6151 }
6152
6153 pub fn unsubscribe_index_prices(
6161 &mut self,
6162 instrument_ids: impl IntoInstrumentIds,
6163 client_id: Option<ClientId>,
6164 _params: Option<Params>,
6165 ) -> anyhow::Result<()> {
6166 self.check_registered();
6167 let instrument_ids = instrument_ids.into_instrument_ids()?;
6168 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6169 let mut retained = Vec::new();
6170
6171 for instrument_id in instrument_ids {
6172 let topic = get_index_price_topic(instrument_id);
6173 if let Some(command) = self.remove_index_price_subscription(topic) {
6174 retained.push(command);
6175 }
6176 }
6177
6178 self.release_subscriptions(retained);
6179 Ok(())
6180 }
6181
6182 pub fn unsubscribe_funding_rates(
6190 &mut self,
6191 instrument_ids: impl IntoInstrumentIds,
6192 client_id: Option<ClientId>,
6193 _params: Option<Params>,
6194 ) -> anyhow::Result<()> {
6195 self.check_registered();
6196 let instrument_ids = instrument_ids.into_instrument_ids()?;
6197 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6198 let mut retained = Vec::new();
6199
6200 for instrument_id in instrument_ids {
6201 let topic = get_funding_rate_topic(instrument_id);
6202 if let Some(command) = self.remove_funding_rate_subscription(topic) {
6203 retained.push(command);
6204 }
6205 }
6206
6207 self.release_subscriptions(retained);
6208 Ok(())
6209 }
6210
6211 pub fn unsubscribe_option_greeks(
6219 &mut self,
6220 instrument_ids: impl IntoInstrumentIds,
6221 client_id: Option<ClientId>,
6222 _params: Option<Params>,
6223 ) -> anyhow::Result<()> {
6224 self.check_registered();
6225 let instrument_ids = instrument_ids.into_instrument_ids()?;
6226 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6227 let mut retained = Vec::new();
6228
6229 for instrument_id in instrument_ids {
6230 let topic = get_option_greeks_topic(instrument_id);
6231 if let Some(command) = self.remove_option_greeks_subscription(topic) {
6232 retained.push(command);
6233 }
6234 }
6235
6236 self.release_subscriptions(retained);
6237 Ok(())
6238 }
6239
6240 pub fn unsubscribe_instrument_status(
6248 &mut self,
6249 instrument_ids: impl IntoInstrumentIds,
6250 client_id: Option<ClientId>,
6251 _params: Option<Params>,
6252 ) -> anyhow::Result<()> {
6253 self.check_registered();
6254 let instrument_ids = instrument_ids.into_instrument_ids()?;
6255 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6256 let mut retained = Vec::new();
6257
6258 for instrument_id in instrument_ids {
6259 let topic = get_instrument_status_topic(instrument_id);
6260 if let Some(command) = self.remove_subscription_any(topic) {
6261 retained.push(command);
6262 }
6263 }
6264
6265 self.release_subscriptions(retained);
6266 Ok(())
6267 }
6268
6269 pub fn unsubscribe_instrument_close(
6277 &mut self,
6278 instrument_ids: impl IntoInstrumentIds,
6279 client_id: Option<ClientId>,
6280 _params: Option<Params>,
6281 ) -> anyhow::Result<()> {
6282 self.check_registered();
6283 let instrument_ids = instrument_ids.into_instrument_ids()?;
6284 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6285 let mut retained = Vec::new();
6286
6287 for instrument_id in instrument_ids {
6288 let topic = get_instrument_close_topic(instrument_id);
6289 if let Some(command) = self.remove_instrument_close_subscription(topic) {
6290 retained.push(command);
6291 }
6292 }
6293
6294 self.release_subscriptions(retained);
6295 Ok(())
6296 }
6297
6298 pub fn unsubscribe_option_chain(
6300 &mut self,
6301 series_id: OptionSeriesId,
6302 client_id: Option<ClientId>,
6303 ) {
6304 self.check_registered();
6305
6306 let topic = get_option_chain_topic(series_id);
6307 let retained = self.remove_option_chain_subscription(topic);
6308
6309 let command = UnsubscribeCommand::OptionChain(UnsubscribeOptionChain::new(
6310 series_id,
6311 UUID4::new(),
6312 self.timestamp_ns(),
6313 client_id,
6314 Some(series_id.venue),
6315 ));
6316
6317 self.send_unsubscribe_cmd(retained, DataCommand::Unsubscribe(command));
6318 }
6319
6320 #[expect(clippy::too_many_arguments)]
6326 pub fn request_data(
6327 &self,
6328 data_type: DataType,
6329 client_id: ClientId,
6330 start: Option<Timestamp>,
6331 end: Option<Timestamp>,
6332 limit: Option<NonZeroUsize>,
6333 params: Option<Params>,
6334 handler: TypedHandler<DataResponseEnvelope>,
6335 ) -> anyhow::Result<UUID4> {
6336 self.check_registered();
6337
6338 let now = self.clock_ref().utc_now();
6339 check_timestamps(now, start, end)?;
6340
6341 let request_id = UUID4::new();
6342 let command = RequestCommand::Data(RequestCustomData {
6343 client_id,
6344 data_type,
6345 start,
6346 end,
6347 limit,
6348 request_id,
6349 ts_init: self.timestamp_ns(),
6350 params,
6351 });
6352
6353 self.register_request_handler(command.request_id(), handler)?;
6354
6355 self.send_data_cmd(DataCommand::Request(command));
6356
6357 Ok(request_id)
6358 }
6359
6360 pub fn request_instrument(
6366 &self,
6367 instrument_ids: impl IntoInstrumentIds,
6368 start: Option<Timestamp>,
6369 end: Option<Timestamp>,
6370 client_id: Option<ClientId>,
6371 params: Option<Params>,
6372 handler: TypedHandler<DataResponseEnvelope>,
6373 ) -> anyhow::Result<UUID4> {
6374 self.check_registered();
6375
6376 let instrument_ids = instrument_ids.into_instrument_ids()?;
6377 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6378
6379 let now = self.clock_ref().utc_now();
6380 check_timestamps(now, start, end)?;
6381
6382 let request_id = UUID4::new();
6383 let command = RequestCommand::Instrument(RequestInstrument {
6384 instrument_ids,
6385 start,
6386 end,
6387 client_id,
6388 request_id,
6389 ts_init: now.into(),
6390 params,
6391 });
6392
6393 self.register_request_handler(command.request_id(), handler)?;
6394
6395 self.send_data_cmd(DataCommand::Request(command));
6396
6397 Ok(request_id)
6398 }
6399
6400 pub fn request_instruments(
6406 &self,
6407 venue: Option<Venue>,
6408 start: Option<Timestamp>,
6409 end: Option<Timestamp>,
6410 client_id: Option<ClientId>,
6411 params: Option<Params>,
6412 handler: TypedHandler<DataResponseEnvelope>,
6413 ) -> anyhow::Result<UUID4> {
6414 self.check_registered();
6415
6416 let now = self.clock_ref().utc_now();
6417 check_timestamps(now, start, end)?;
6418
6419 let request_id = UUID4::new();
6420 let command = RequestCommand::Instruments(RequestInstruments {
6421 venue,
6422 start,
6423 end,
6424 client_id,
6425 request_id,
6426 ts_init: now.into(),
6427 params,
6428 });
6429
6430 self.register_request_handler(command.request_id(), handler)?;
6431
6432 self.send_data_cmd(DataCommand::Request(command));
6433
6434 Ok(request_id)
6435 }
6436
6437 pub fn request_book_snapshot(
6443 &self,
6444 instrument_ids: impl IntoInstrumentIds,
6445 depth: Option<NonZeroUsize>,
6446 client_id: Option<ClientId>,
6447 params: Option<Params>,
6448 handler: TypedHandler<DataResponseEnvelope>,
6449 ) -> anyhow::Result<UUID4> {
6450 self.check_registered();
6451
6452 let instrument_ids = instrument_ids.into_instrument_ids()?;
6453 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6454
6455 let request_id = UUID4::new();
6456 let command = RequestCommand::BookSnapshot(RequestBookSnapshot {
6457 instrument_ids,
6458 depth,
6459 client_id,
6460 request_id,
6461 ts_init: self.timestamp_ns(),
6462 params,
6463 });
6464
6465 self.register_request_handler(command.request_id(), handler)?;
6466
6467 self.send_data_cmd(DataCommand::Request(command));
6468
6469 Ok(request_id)
6470 }
6471
6472 #[expect(clippy::too_many_arguments)]
6478 pub fn request_book_deltas(
6479 &self,
6480 instrument_ids: impl IntoInstrumentIds,
6481 start: Option<Timestamp>,
6482 end: Option<Timestamp>,
6483 limit: Option<NonZeroUsize>,
6484 client_id: Option<ClientId>,
6485 params: Option<Params>,
6486 handler: TypedHandler<DataResponseEnvelope>,
6487 ) -> anyhow::Result<UUID4> {
6488 self.check_registered();
6489
6490 let instrument_ids = instrument_ids.into_instrument_ids()?;
6491 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6492
6493 let now = self.clock_ref().utc_now();
6494 check_timestamps(now, start, end)?;
6495
6496 let request_id = UUID4::new();
6497 let command = RequestCommand::BookDeltas(RequestBookDeltas {
6498 instrument_ids,
6499 start,
6500 end,
6501 limit,
6502 client_id,
6503 request_id,
6504 ts_init: now.into(),
6505 params,
6506 });
6507
6508 self.register_request_handler(command.request_id(), handler)?;
6509
6510 self.send_data_cmd(DataCommand::Request(command));
6511
6512 Ok(request_id)
6513 }
6514
6515 #[expect(clippy::too_many_arguments)]
6521 pub fn request_book_depth(
6522 &self,
6523 instrument_ids: impl IntoInstrumentIds,
6524 start: Option<Timestamp>,
6525 end: Option<Timestamp>,
6526 limit: Option<NonZeroUsize>,
6527 depth: Option<NonZeroUsize>,
6528 client_id: Option<ClientId>,
6529 params: Option<Params>,
6530 handler: TypedHandler<DataResponseEnvelope>,
6531 ) -> anyhow::Result<UUID4> {
6532 self.check_registered();
6533
6534 let instrument_ids = instrument_ids.into_instrument_ids()?;
6535 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6536
6537 let now = self.clock_ref().utc_now();
6538 check_timestamps(now, start, end)?;
6539
6540 let request_id = UUID4::new();
6541 let command = RequestCommand::BookDepth(RequestBookDepth {
6542 instrument_ids,
6543 start,
6544 end,
6545 limit,
6546 depth,
6547 client_id,
6548 request_id,
6549 ts_init: now.into(),
6550 params,
6551 });
6552
6553 self.register_request_handler(command.request_id(), handler)?;
6554
6555 self.send_data_cmd(DataCommand::Request(command));
6556
6557 Ok(request_id)
6558 }
6559
6560 #[expect(clippy::too_many_arguments)]
6566 pub fn request_quotes(
6567 &self,
6568 instrument_ids: impl IntoInstrumentIds,
6569 start: Option<Timestamp>,
6570 end: Option<Timestamp>,
6571 limit: Option<NonZeroUsize>,
6572 client_id: Option<ClientId>,
6573 params: Option<Params>,
6574 handler: TypedHandler<DataResponseEnvelope>,
6575 ) -> anyhow::Result<UUID4> {
6576 self.check_registered();
6577
6578 let instrument_ids = instrument_ids.into_instrument_ids()?;
6579 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6580 anyhow::ensure!(
6581 instrument_ids.len() == 1 || limit.is_none(),
6582 "A record limit is unsupported for multi-instrument quotes requests",
6583 );
6584
6585 let now = self.clock_ref().utc_now();
6586 check_timestamps(now, start, end)?;
6587
6588 let request_id = UUID4::new();
6589 let command = RequestCommand::Quotes(RequestQuotes {
6590 instrument_ids,
6591 start,
6592 end,
6593 limit,
6594 client_id,
6595 request_id,
6596 ts_init: now.into(),
6597 params,
6598 });
6599
6600 self.register_request_handler(command.request_id(), handler)?;
6601
6602 self.send_data_cmd(DataCommand::Request(command));
6603
6604 Ok(request_id)
6605 }
6606
6607 #[expect(clippy::too_many_arguments)]
6613 pub fn request_trades(
6614 &self,
6615 instrument_ids: impl IntoInstrumentIds,
6616 start: Option<Timestamp>,
6617 end: Option<Timestamp>,
6618 limit: Option<NonZeroUsize>,
6619 client_id: Option<ClientId>,
6620 params: Option<Params>,
6621 handler: TypedHandler<DataResponseEnvelope>,
6622 ) -> anyhow::Result<UUID4> {
6623 self.check_registered();
6624
6625 let instrument_ids = instrument_ids.into_instrument_ids()?;
6626 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6627 anyhow::ensure!(
6628 instrument_ids.len() == 1 || limit.is_none(),
6629 "A record limit is unsupported for multi-instrument trades requests",
6630 );
6631
6632 let now = self.clock_ref().utc_now();
6633 check_timestamps(now, start, end)?;
6634
6635 let request_id = UUID4::new();
6636 let command = RequestCommand::Trades(RequestTrades {
6637 instrument_ids,
6638 start,
6639 end,
6640 limit,
6641 client_id,
6642 request_id,
6643 ts_init: now.into(),
6644 params,
6645 });
6646
6647 self.register_request_handler(command.request_id(), handler)?;
6648
6649 self.send_data_cmd(DataCommand::Request(command));
6650
6651 Ok(request_id)
6652 }
6653
6654 #[expect(clippy::too_many_arguments)]
6660 pub fn request_bars(
6661 &self,
6662 bar_types: impl IntoBarTypes,
6663 start: Option<Timestamp>,
6664 end: Option<Timestamp>,
6665 limit: Option<NonZeroUsize>,
6666 client_id: Option<ClientId>,
6667 params: Option<Params>,
6668 handler: TypedHandler<DataResponseEnvelope>,
6669 ) -> anyhow::Result<UUID4> {
6670 self.check_registered();
6671
6672 let bar_types = bar_types.into_bar_types()?;
6673 ensure_routable_bar_types(&bar_types, client_id.is_some())?;
6674
6675 for bar_type in &bar_types {
6676 anyhow::ensure!(
6677 bar_type.is_standard(),
6678 "Composite bar types are not supported for `request_bars`, was {bar_type}; \
6679 request aggregation via the `bar_types` params instead",
6680 );
6681 }
6682
6683 let now = self.clock_ref().utc_now();
6684 check_timestamps(now, start, end)?;
6685
6686 let request_id = UUID4::new();
6687 let command = RequestCommand::Bars(RequestBars {
6688 bar_types,
6689 start,
6690 end,
6691 limit,
6692 client_id,
6693 request_id,
6694 ts_init: now.into(),
6695 params,
6696 });
6697
6698 self.register_request_handler(command.request_id(), handler)?;
6699
6700 self.send_data_cmd(DataCommand::Request(command));
6701
6702 Ok(request_id)
6703 }
6704
6705 #[expect(clippy::too_many_arguments)]
6711 pub fn request_funding_rates(
6712 &self,
6713 instrument_ids: impl IntoInstrumentIds,
6714 start: Option<Timestamp>,
6715 end: Option<Timestamp>,
6716 limit: Option<NonZeroUsize>,
6717 client_id: Option<ClientId>,
6718 params: Option<Params>,
6719 handler: TypedHandler<DataResponseEnvelope>,
6720 ) -> anyhow::Result<UUID4> {
6721 self.check_registered();
6722
6723 let instrument_ids = instrument_ids.into_instrument_ids()?;
6724 ensure_routable_instrument_ids(&instrument_ids, client_id.is_some())?;
6725
6726 let now = self.clock_ref().utc_now();
6727 check_timestamps(now, start, end)?;
6728
6729 let request_id = UUID4::new();
6730 let command = RequestCommand::FundingRates(RequestFundingRates {
6731 instrument_ids,
6732 start,
6733 end,
6734 limit,
6735 client_id,
6736 request_id,
6737 ts_init: now.into(),
6738 params,
6739 });
6740
6741 self.register_request_handler(command.request_id(), handler)?;
6742
6743 self.send_data_cmd(DataCommand::Request(command));
6744
6745 Ok(request_id)
6746 }
6747
6748 fn register_request_handler(
6749 &self,
6750 request_id: &UUID4,
6751 handler: TypedHandler<DataResponseEnvelope>,
6752 ) -> anyhow::Result<()> {
6753 get_message_bus()
6754 .borrow_mut()
6755 .register_response_handler(request_id, handler)?;
6756 self.requests.borrow_mut().register(*request_id);
6757 Ok(())
6758 }
6759
6760 #[must_use]
6762 pub fn is_pending_request(&self, request_id: &UUID4) -> bool {
6763 self.requests.borrow().is_pending(request_id)
6764 }
6765
6766 #[must_use]
6768 pub fn pending_requests(&self) -> Vec<UUID4> {
6769 self.requests.borrow().pending()
6770 }
6771
6772 pub(crate) fn register_request_group(
6773 &self,
6774 request_ids: Vec<UUID4>,
6775 request_id: Option<UUID4>,
6776 handler: Option<TypedHandler<DataResponseEnvelope>>,
6777 ) -> anyhow::Result<UUID4> {
6778 self.requests
6779 .borrow_mut()
6780 .register_group(request_ids, request_id, handler)
6781 }
6782
6783 pub(crate) fn begin_response(&self, request_id: &UUID4) -> Option<ResponseScope> {
6784 self.requests.borrow().begin(request_id)
6785 }
6786
6787 #[cfg(feature = "python")]
6788 pub(crate) fn is_response_current(&self, scope: &ResponseScope) -> bool {
6789 self.requests.borrow().is_current(scope)
6790 }
6791
6792 pub(crate) fn complete_request(
6793 &self,
6794 response: &DataResponseEnvelope,
6795 scope: &ResponseScope,
6796 ) -> bool {
6797 self.requests.borrow_mut().complete(response, scope)
6798 }
6799
6800 pub(crate) fn next_group_response(
6801 &self,
6802 ) -> Option<(
6803 DataResponseEnvelope,
6804 Option<TypedHandler<DataResponseEnvelope>>,
6805 )> {
6806 self.requests.borrow_mut().next_group_response()
6807 }
6808
6809 fn clear_pending_requests(&self) {
6810 let (ids, mut handlers) = self.requests.borrow_mut().clear();
6811 {
6812 let bus = get_message_bus();
6813 let mut bus = bus.borrow_mut();
6814 handlers.extend(ids.iter().filter_map(|id| bus.take_response_handler(id)));
6815 }
6816
6817 drop(handlers);
6818 }
6819
6820 #[cfg(feature = "live")]
6827 pub fn reconnect_socket(&self, client_id: ClientId, endpoint: &str) -> anyhow::Result<()> {
6828 let endpoint = socket_endpoint(endpoint)?;
6829
6830 if !self.is_properly_registered() {
6831 anyhow::bail!(
6832 "Actor {} has not been registered with a Trader",
6833 self.actor_id
6834 );
6835 }
6836
6837 let sender = try_get_system_command_sender()
6838 .ok_or_else(|| anyhow::anyhow!("Live runner system command channel is unavailable"))?;
6839 let trader_id = self
6840 .trader_id
6841 .ok_or_else(|| anyhow::anyhow!("Actor {} has no trader ID", self.actor_id))?;
6842 let command = ReconnectSocket::new(trader_id, client_id, endpoint, self.timestamp_ns());
6843 sender
6844 .send(SystemCommand::ReconnectSocket(command))
6845 .map_err(|_| anyhow::anyhow!("Live runner system command channel is closed"))?;
6846 Ok(())
6847 }
6848
6849 #[cfg(test)]
6850 pub fn quote_handler_count(&self) -> usize {
6851 self.quote_handlers.len()
6852 }
6853
6854 #[cfg(test)]
6855 pub fn trade_handler_count(&self) -> usize {
6856 self.trade_handlers.len()
6857 }
6858
6859 #[cfg(test)]
6860 pub fn bar_handler_count(&self) -> usize {
6861 self.bar_handlers.len()
6862 }
6863
6864 #[cfg(test)]
6865 pub fn deltas_handler_count(&self) -> usize {
6866 self.deltas_handlers.len()
6867 }
6868
6869 #[cfg(test)]
6870 pub fn depth_handler_count(&self) -> usize {
6871 self.depth_handlers.len()
6872 }
6873
6874 #[cfg(test)]
6875 pub fn has_quote_handler(&self, topic: &str) -> bool {
6876 self.quote_handlers
6877 .contains_key(&MStr::<Topic>::from(topic))
6878 }
6879
6880 #[cfg(test)]
6881 pub fn has_trade_handler(&self, topic: &str) -> bool {
6882 self.trade_handlers
6883 .contains_key(&MStr::<Topic>::from(topic))
6884 }
6885
6886 #[cfg(test)]
6887 pub fn has_bar_handler(&self, topic: &str) -> bool {
6888 self.bar_handlers.contains_key(&MStr::<Topic>::from(topic))
6889 }
6890
6891 #[cfg(test)]
6892 pub fn has_deltas_handler(&self, pattern: &str) -> bool {
6893 self.deltas_handlers
6894 .contains_key(&MStr::<Pattern>::from(pattern))
6895 }
6896
6897 #[cfg(test)]
6898 pub fn has_depth_handler(&self, pattern: &str) -> bool {
6899 self.depth_handlers
6900 .contains_key(&MStr::<Pattern>::from(pattern))
6901 }
6902}
6903
6904impl DataActorNative for DataActorCore {
6905 fn core(&self) -> &DataActorCore {
6906 self
6907 }
6908
6909 fn core_mut(&mut self) -> &mut DataActorCore {
6910 self
6911 }
6912}
6913
6914fn check_timestamps(
6915 now: Timestamp,
6916 start: Option<Timestamp>,
6917 end: Option<Timestamp>,
6918) -> anyhow::Result<()> {
6919 if let Some(start) = start {
6920 check_predicate_true(start <= now, "start was > now")?;
6921 }
6922
6923 if let Some(end) = end {
6924 check_predicate_true(end <= now, "end was > now")?;
6925 }
6926
6927 if let (Some(start), Some(end)) = (start, end) {
6928 check_predicate_true(start <= end, "start was > end")?;
6929 }
6930
6931 Ok(())
6932}
6933
6934fn log_error(e: &anyhow::Error) {
6935 log::error!("{e}");
6936}
6937
6938fn log_not_running<T>(msg: &T)
6939where
6940 T: Debug,
6941{
6942 log::trace!("Received message when not running - skipping {msg:?}");
6943}
6944
6945fn log_received<T>(msg: &T)
6946where
6947 T: Debug,
6948{
6949 log::debug!("{RECV} {msg:?}");
6950}
6951
6952fn log_received_bulk(kind: &str, correlation_id: &UUID4, records: usize) {
6953 log::debug!("{RECV} {kind} correlation_id={correlation_id} records={records}");
6954}