Skip to main content

nautilus_common/actor/
data_actor.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use 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, OrderBookDepth10, 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    indicators::{Indicators, SharedActorIndicator},
52    registry::try_get_actor_unchecked,
53};
54#[cfg(feature = "defi")]
55use crate::defi;
56#[cfg(feature = "defi")]
57#[allow(unused_imports)]
58use crate::defi::data_actor as _; // Brings DeFi impl blocks into scope
59use crate::{
60    cache::{Cache, CacheApi},
61    clock::{Clock, ClockApi},
62    component::Component,
63    enums::{ComponentState, ComponentTrigger},
64    logging::{CMD, RECV, REQ, SEND},
65    messages::{
66        data::{
67            BarsResponse, BookDeltasResponse, BookDepthResponse, BookResponse, CustomDataResponse,
68            DataCommand, FundingRatesResponse, InstrumentResponse, InstrumentsResponse,
69            QuotesResponse, RequestBars, RequestBookDeltas, RequestBookDepth, RequestBookSnapshot,
70            RequestCommand, RequestCustomData, RequestFundingRates, RequestInstrument,
71            RequestInstruments, RequestQuotes, RequestTrades, SubscribeBars, SubscribeBookDeltas,
72            SubscribeBookDepth10, SubscribeBookSnapshots, SubscribeCommand, SubscribeCustomData,
73            SubscribeFundingRates, SubscribeIndexPrices, SubscribeInstrument,
74            SubscribeInstrumentClose, SubscribeInstrumentStatus, SubscribeInstruments,
75            SubscribeMarkPrices, SubscribeOptionChain, SubscribeOptionGreeks, SubscribeQuotes,
76            SubscribeTrades, TradesResponse, UnsubscribeBars, UnsubscribeBookDeltas,
77            UnsubscribeBookDepth10, UnsubscribeBookSnapshots, UnsubscribeCommand,
78            UnsubscribeCustomData, UnsubscribeFundingRates, UnsubscribeIndexPrices,
79            UnsubscribeInstrument, UnsubscribeInstrumentClose, UnsubscribeInstrumentStatus,
80            UnsubscribeInstruments, UnsubscribeMarkPrices, UnsubscribeOptionChain,
81            UnsubscribeOptionGreeks, UnsubscribeQuotes, UnsubscribeTrades, is_parent_subscription,
82        },
83        system::{QueueStateChanged, ShutdownSystem, SocketStateChanged},
84    },
85    msgbus::{
86        self, MStr, Pattern, ShareableMessageHandler, Topic, TypedHandler, get_message_bus,
87        switchboard::{
88            MessagingSwitchboard, get_bars_topic, get_book_deltas_pattern, get_book_deltas_topic,
89            get_book_depth10_pattern, get_book_depth10_topic, get_book_snapshots_topic,
90            get_custom_topic, get_funding_rate_topic, get_index_price_topic,
91            get_instrument_close_topic, get_instrument_status_topic, get_instrument_topic,
92            get_instruments_pattern, get_mark_price_topic, get_option_chain_topic,
93            get_option_greeks_topic, get_quotes_topic, get_signal_pattern, get_trades_topic,
94        },
95    },
96    signal::Signal,
97    timer::{TimeEvent, TimeEventCallback},
98};
99#[cfg(feature = "live")]
100use crate::{
101    live::try_get_system_command_sender,
102    messages::{
103        SystemCommand,
104        system::{ReconnectSocket, socket_endpoint},
105    },
106};
107
108/// Common configuration for [`DataActor`] based components.
109#[derive(Debug, Clone, Deserialize, Serialize)]
110#[serde(default, deny_unknown_fields)]
111#[cfg_attr(
112    feature = "python",
113    pyo3::pyclass(module = "nautilus_trader.common", subclass, from_py_object)
114)]
115#[cfg_attr(
116    feature = "python",
117    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")
118)]
119pub struct DataActorConfig {
120    /// The custom identifier for the Actor.
121    pub actor_id: Option<ActorId>,
122    /// If events should be logged.
123    pub log_events: bool,
124    /// If commands should be logged.
125    pub log_commands: bool,
126}
127
128impl Default for DataActorConfig {
129    fn default() -> Self {
130        Self {
131            actor_id: None,
132            log_events: true,
133            log_commands: true,
134        }
135    }
136}
137
138/// Configuration for creating actors from importable paths.
139#[derive(Debug, Clone, Deserialize, Serialize)]
140#[serde(deny_unknown_fields)]
141#[cfg_attr(
142    feature = "python",
143    pyo3::pyclass(module = "nautilus_trader.common", from_py_object)
144)]
145#[cfg_attr(
146    feature = "python",
147    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.common")
148)]
149pub struct ImportableActorConfig {
150    /// The fully qualified name of the Actor class.
151    pub actor_path: String,
152    /// The fully qualified name of the Actor config class.
153    pub config_path: String,
154    /// The actor configuration as a dictionary.
155    pub config: HashMap<String, serde_json::Value>,
156}
157
158type RequestCallback = Arc<dyn Fn(UUID4) + Send + Sync>;
159
160/// Explicit native-only access for data actor runtime state.
161///
162/// Normal actor and strategy code should use facade methods such as
163/// [`DataActor::clock`] and [`DataActor::cache`]. Import this trait only from
164/// Rust code compiled into the same native binary as the engine, when a
165/// performance-sensitive path or host integration needs access below the facade
166/// API.
167///
168/// Do not import this trait in strategy code intended to run through Python or
169/// the plug-in authoring surface. Native borrows, `Rc<RefCell<_>>`, and core
170/// references do not cross those boundaries.
171pub trait DataActorNative {
172    /// Returns the actor core.
173    fn core(&self) -> &DataActorCore;
174
175    /// Returns the mutable actor core.
176    fn core_mut(&mut self) -> &mut DataActorCore;
177
178    /// Returns the mutable clock borrow for the actor.
179    ///
180    /// # Panics
181    ///
182    /// Panics if the actor has not been registered with a trader.
183    fn clock_mut(&mut self) -> RefMut<'_, dyn Clock> {
184        let core = self.core_mut();
185        core.clock
186            .as_ref()
187            .unwrap_or_else(|| {
188                panic!(
189                    "DataActor {} must be registered before calling `clock_mut()` - trader_id: {:?}",
190                    core.actor_id, core.trader_id
191                )
192            })
193            .borrow_mut()
194    }
195
196    /// Returns a clone of the reference-counted clock.
197    ///
198    /// # Panics
199    ///
200    /// Panics if the actor has not yet been registered.
201    fn clock_rc(&self) -> Rc<RefCell<dyn Clock>> {
202        self.core()
203            .clock
204            .as_ref()
205            .expect("DataActor must be registered before accessing clock")
206            .clone()
207    }
208
209    /// Returns a read-only cache borrow.
210    ///
211    /// # Panics
212    ///
213    /// Panics if the actor has not yet been registered.
214    fn cache_ref(&self) -> Ref<'_, Cache> {
215        self.core()
216            .cache
217            .as_ref()
218            .expect("DataActor must be registered before accessing cache")
219            .borrow()
220    }
221
222    /// Returns a clone of the reference-counted cache.
223    ///
224    /// # Panics
225    ///
226    /// Panics if the actor has not yet been registered.
227    fn cache_rc(&self) -> Rc<RefCell<Cache>> {
228        self.core()
229            .cache
230            .as_ref()
231            .expect("DataActor must be registered before accessing cache")
232            .clone()
233    }
234}
235
236/// Defines lifecycle callbacks, data handlers, and subscription/request
237/// methods for data actors.
238///
239/// Default methods that read or mutate native runtime state carry explicit
240/// [`DataActorNative`] bounds. Data actor implementations that only need
241/// core-free callbacks can implement this trait with their own [`Component`]
242/// implementation, while runtime-registered actors keep using native wiring.
243pub trait DataActor: Component {
244    /// Returns the actor ID.
245    fn actor_id(&self) -> ActorId
246    where
247        Self: DataActorNative,
248    {
249        self.core().actor_id()
250    }
251
252    /// Returns the trader ID this actor is registered to.
253    fn trader_id(&self) -> Option<TraderId>
254    where
255        Self: DataActorNative,
256    {
257        self.core().trader_id()
258    }
259
260    /// Returns whether the actor is registered with a trader.
261    fn is_registered(&self) -> bool
262    where
263        Self: DataActorNative,
264    {
265        self.core().is_registered()
266    }
267
268    /// Returns the actor configuration.
269    fn config(&self) -> &DataActorConfig
270    where
271        Self: DataActorNative,
272    {
273        &self.core().config
274    }
275
276    /// Actions to be performed when the actor state is saved.
277    ///
278    /// # Errors
279    ///
280    /// Returns an error if saving the actor state fails.
281    fn on_save(&self) -> anyhow::Result<IndexMap<String, Vec<u8>>> {
282        Ok(IndexMap::new())
283    }
284
285    /// Actions to be performed when the actor state is loaded.
286    ///
287    /// # Errors
288    ///
289    /// Returns an error if loading the actor state fails.
290    #[allow(unused_variables)]
291    fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {
292        Ok(())
293    }
294
295    /// Actions to be performed on start.
296    ///
297    /// # Errors
298    ///
299    /// Returns an error if starting the actor fails.
300    fn on_start(&mut self) -> anyhow::Result<()> {
301        log::warn!(
302            "The `on_start` handler was called when not overridden, \
303            it's expected that any actions required when starting the actor \
304            occur here, such as subscribing/requesting data"
305        );
306        Ok(())
307    }
308
309    /// Actions to be performed on stop.
310    ///
311    /// # Errors
312    ///
313    /// Returns an error if stopping the actor fails.
314    fn on_stop(&mut self) -> anyhow::Result<()> {
315        log::warn!(
316            "The `on_stop` handler was called when not overridden, \
317            it's expected that any actions required when stopping the actor \
318            occur here, such as unsubscribing from data",
319        );
320        Ok(())
321    }
322
323    /// Actions to be performed on resume.
324    ///
325    /// # Errors
326    ///
327    /// Returns an error if resuming the actor fails.
328    fn on_resume(&mut self) -> anyhow::Result<()> {
329        log::warn!(
330            "The `on_resume` handler was called when not overridden, \
331            it's expected that any actions required when resuming the actor \
332            following a stop occur here"
333        );
334        Ok(())
335    }
336
337    /// Actions to be performed on reset.
338    ///
339    /// # Errors
340    ///
341    /// Returns an error if resetting the actor fails.
342    fn on_reset(&mut self) -> anyhow::Result<()> {
343        log::warn!(
344            "The `on_reset` handler was called when not overridden, \
345            it's expected that any actions required when resetting the actor \
346            occur here, such as resetting indicators and other state"
347        );
348        Ok(())
349    }
350
351    /// Actions to be performed on dispose.
352    ///
353    /// # Errors
354    ///
355    /// Returns an error if disposing the actor fails.
356    fn on_dispose(&mut self) -> anyhow::Result<()> {
357        Ok(())
358    }
359
360    /// Actions to be performed on degrade.
361    ///
362    /// # Errors
363    ///
364    /// Returns an error if degrading the actor fails.
365    fn on_degrade(&mut self) -> anyhow::Result<()> {
366        Ok(())
367    }
368
369    /// Actions to be performed on fault.
370    ///
371    /// # Errors
372    ///
373    /// Returns an error if faulting the actor fails.
374    fn on_fault(&mut self) -> anyhow::Result<()> {
375        Ok(())
376    }
377
378    /// Actions to be performed when receiving a time event.
379    ///
380    /// # Errors
381    ///
382    /// Returns an error if handling the time event fails.
383    #[allow(unused_variables)]
384    fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
385        Ok(())
386    }
387
388    /// Actions to be performed when receiving custom data.
389    ///
390    /// # Errors
391    ///
392    /// Returns an error if handling the data fails.
393    #[allow(unused_variables)]
394    fn on_data(&mut self, data: &CustomData) -> anyhow::Result<()> {
395        Ok(())
396    }
397
398    /// Actions to be performed when receiving a signal.
399    ///
400    /// # Errors
401    ///
402    /// Returns an error if handling the signal fails.
403    #[allow(unused_variables)]
404    fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
405        Ok(())
406    }
407
408    /// Actions to be performed when receiving a queue state change.
409    ///
410    /// # Errors
411    ///
412    /// Returns an error if handling the queue state change fails.
413    #[allow(unused_variables)]
414    fn on_queue_state(&mut self, event: &QueueStateChanged) -> anyhow::Result<()> {
415        Ok(())
416    }
417
418    /// Actions to be performed when receiving a socket state change.
419    ///
420    /// # Errors
421    ///
422    /// Returns an error if handling the socket state change fails.
423    #[allow(unused_variables)]
424    fn on_socket_state(&mut self, event: &SocketStateChanged) -> anyhow::Result<()> {
425        Ok(())
426    }
427
428    /// Actions to be performed when receiving an instrument.
429    ///
430    /// # Errors
431    ///
432    /// Returns an error if handling the instrument fails.
433    #[allow(unused_variables)]
434    fn on_instrument(&mut self, instrument: &InstrumentAny) -> anyhow::Result<()> {
435        Ok(())
436    }
437
438    /// Actions to be performed when receiving order book deltas.
439    ///
440    /// # Errors
441    ///
442    /// Returns an error if handling the book deltas fails.
443    #[allow(unused_variables)]
444    fn on_book_deltas(&mut self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
445        Ok(())
446    }
447
448    /// Actions to be performed when receiving an order book depth10 snapshot.
449    ///
450    /// # Errors
451    ///
452    /// Returns an error if handling the book depth fails.
453    #[allow(unused_variables)]
454    fn on_book_depth(&mut self, depth: &OrderBookDepth10) -> anyhow::Result<()> {
455        Ok(())
456    }
457
458    /// Actions to be performed when receiving an order book.
459    ///
460    /// # Errors
461    ///
462    /// Returns an error if handling the book fails.
463    #[allow(unused_variables)]
464    fn on_book(&mut self, order_book: &OrderBook) -> anyhow::Result<()> {
465        Ok(())
466    }
467
468    /// Actions to be performed when receiving a quote.
469    ///
470    /// # Errors
471    ///
472    /// Returns an error if handling the quote fails.
473    #[allow(unused_variables)]
474    fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
475        Ok(())
476    }
477
478    /// Actions to be performed when receiving a trade.
479    ///
480    /// # Errors
481    ///
482    /// Returns an error if handling the trade fails.
483    #[allow(unused_variables)]
484    fn on_trade(&mut self, tick: &TradeTick) -> anyhow::Result<()> {
485        Ok(())
486    }
487
488    /// Actions to be performed when receiving a bar.
489    ///
490    /// # Errors
491    ///
492    /// Returns an error if handling the bar fails.
493    #[allow(unused_variables)]
494    fn on_bar(&mut self, bar: &Bar) -> anyhow::Result<()> {
495        Ok(())
496    }
497
498    /// Actions to be performed when receiving a mark price update.
499    ///
500    /// # Errors
501    ///
502    /// Returns an error if handling the mark price update fails.
503    #[allow(unused_variables)]
504    fn on_mark_price(&mut self, mark_price: &MarkPriceUpdate) -> anyhow::Result<()> {
505        Ok(())
506    }
507
508    /// Actions to be performed when receiving an index price update.
509    ///
510    /// # Errors
511    ///
512    /// Returns an error if handling the index price update fails.
513    #[allow(unused_variables)]
514    fn on_index_price(&mut self, index_price: &IndexPriceUpdate) -> anyhow::Result<()> {
515        Ok(())
516    }
517
518    /// Actions to be performed when receiving a funding rate update.
519    ///
520    /// # Errors
521    ///
522    /// Returns an error if handling the funding rate update fails.
523    #[allow(unused_variables)]
524    fn on_funding_rate(&mut self, funding_rate: &FundingRateUpdate) -> anyhow::Result<()> {
525        Ok(())
526    }
527
528    /// Actions to be performed when receiving exchange-provided option greeks.
529    ///
530    /// # Errors
531    ///
532    /// Returns an error if handling the option greeks fails.
533    #[allow(unused_variables)]
534    fn on_option_greeks(&mut self, greeks: &OptionGreeks) -> anyhow::Result<()> {
535        Ok(())
536    }
537
538    /// Actions to be performed when receiving an option chain slice snapshot.
539    ///
540    /// # Errors
541    ///
542    /// Returns an error if handling the option chain slice fails.
543    #[allow(unused_variables)]
544    fn on_option_chain(&mut self, slice: &OptionChainSlice) -> anyhow::Result<()> {
545        Ok(())
546    }
547
548    /// Actions to be performed when receiving an instrument status update.
549    ///
550    /// # Errors
551    ///
552    /// Returns an error if handling the instrument status update fails.
553    #[allow(unused_variables)]
554    fn on_instrument_status(&mut self, data: &InstrumentStatus) -> anyhow::Result<()> {
555        Ok(())
556    }
557
558    /// Actions to be performed when receiving an instrument close update.
559    ///
560    /// # Errors
561    ///
562    /// Returns an error if handling the instrument close update fails.
563    #[allow(unused_variables)]
564    fn on_instrument_close(&mut self, update: &InstrumentClose) -> anyhow::Result<()> {
565        Ok(())
566    }
567
568    #[cfg(feature = "defi")]
569    /// Actions to be performed when receiving a block.
570    ///
571    /// # Errors
572    ///
573    /// Returns an error if handling the block fails.
574    #[allow(unused_variables)]
575    fn on_block(&mut self, block: &Block) -> anyhow::Result<()> {
576        Ok(())
577    }
578
579    #[cfg(feature = "defi")]
580    /// Actions to be performed when receiving a pool.
581    ///
582    /// # Errors
583    ///
584    /// Returns an error if handling the pool fails.
585    #[allow(unused_variables)]
586    fn on_pool(&mut self, pool: &Pool) -> anyhow::Result<()> {
587        Ok(())
588    }
589
590    #[cfg(feature = "defi")]
591    /// Actions to be performed when receiving a pool swap.
592    ///
593    /// # Errors
594    ///
595    /// Returns an error if handling the pool swap fails.
596    #[allow(unused_variables)]
597    fn on_pool_swap(&mut self, swap: &PoolSwap) -> anyhow::Result<()> {
598        Ok(())
599    }
600
601    #[cfg(feature = "defi")]
602    /// Actions to be performed when receiving a pool liquidity update.
603    ///
604    /// # Errors
605    ///
606    /// Returns an error if handling the pool liquidity update fails.
607    #[allow(unused_variables)]
608    fn on_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate) -> anyhow::Result<()> {
609        Ok(())
610    }
611
612    #[cfg(feature = "defi")]
613    /// Actions to be performed when receiving a pool fee collect event.
614    ///
615    /// # Errors
616    ///
617    /// Returns an error if handling the pool fee collect fails.
618    #[allow(unused_variables)]
619    fn on_pool_fee_collect(&mut self, collect: &PoolFeeCollect) -> anyhow::Result<()> {
620        Ok(())
621    }
622
623    #[cfg(feature = "defi")]
624    /// Actions to be performed when receiving a pool flash event.
625    ///
626    /// # Errors
627    ///
628    /// Returns an error if handling the pool flash fails.
629    #[allow(unused_variables)]
630    fn on_pool_flash(&mut self, flash: &PoolFlash) -> anyhow::Result<()> {
631        Ok(())
632    }
633
634    /// Actions to be performed when receiving historical custom data.
635    ///
636    /// The callback runs once per response. A scalar [`CustomData`] remains scalar, while a
637    /// `Vec<CustomData>` batch remains intact, including when empty.
638    ///
639    /// # Errors
640    ///
641    /// Returns an error if handling the historical data fails.
642    #[allow(unused_variables)]
643    fn on_historical_data(&mut self, data: &dyn Any) -> anyhow::Result<()> {
644        Ok(())
645    }
646
647    /// Actions to be performed when receiving historical book deltas.
648    ///
649    /// # Errors
650    ///
651    /// Returns an error if handling the historical book deltas fails.
652    #[allow(unused_variables)]
653    fn on_historical_book_deltas(&mut self, deltas: &[OrderBookDelta]) -> anyhow::Result<()> {
654        Ok(())
655    }
656
657    /// Actions to be performed when receiving historical book depth.
658    ///
659    /// # Errors
660    ///
661    /// Returns an error if handling the historical book depth fails.
662    #[allow(unused_variables)]
663    fn on_historical_book_depth(&mut self, depths: &[OrderBookDepth10]) -> anyhow::Result<()> {
664        Ok(())
665    }
666
667    /// Actions to be performed when receiving historical quotes.
668    ///
669    /// # Errors
670    ///
671    /// Returns an error if handling the historical quotes fails.
672    #[allow(unused_variables)]
673    fn on_historical_quotes(&mut self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
674        Ok(())
675    }
676
677    /// Actions to be performed when receiving historical trades.
678    ///
679    /// # Errors
680    ///
681    /// Returns an error if handling the historical trades fails.
682    #[allow(unused_variables)]
683    fn on_historical_trades(&mut self, trades: &[TradeTick]) -> anyhow::Result<()> {
684        Ok(())
685    }
686
687    /// Actions to be performed when receiving historical bars.
688    ///
689    /// # Errors
690    ///
691    /// Returns an error if handling the historical bars fails.
692    #[allow(unused_variables)]
693    fn on_historical_bars(&mut self, bars: &[Bar]) -> anyhow::Result<()> {
694        Ok(())
695    }
696
697    /// Actions to be performed when receiving historical mark prices.
698    ///
699    /// # Errors
700    ///
701    /// Returns an error if handling the historical mark prices fails.
702    #[allow(unused_variables)]
703    fn on_historical_mark_prices(&mut self, mark_prices: &[MarkPriceUpdate]) -> anyhow::Result<()> {
704        Ok(())
705    }
706
707    /// Actions to be performed when receiving historical index prices.
708    ///
709    /// # Errors
710    ///
711    /// Returns an error if handling the historical index prices fails.
712    #[allow(unused_variables)]
713    fn on_historical_index_prices(
714        &mut self,
715        index_prices: &[IndexPriceUpdate],
716    ) -> anyhow::Result<()> {
717        Ok(())
718    }
719
720    /// Actions to be performed when receiving historical funding rates.
721    ///
722    /// # Errors
723    ///
724    /// Returns an error if handling the historical funding rates fails.
725    #[allow(unused_variables)]
726    fn on_historical_funding_rates(
727        &mut self,
728        funding_rates: &[FundingRateUpdate],
729    ) -> anyhow::Result<()> {
730        Ok(())
731    }
732
733    /// Returns the user-facing clock API.
734    fn clock(&self) -> ClockApi<'_>
735    where
736        Self: DataActorNative,
737    {
738        self.core().clock_api()
739    }
740
741    /// Returns the user-facing cache API.
742    fn cache(&self) -> CacheApi<'_>
743    where
744        Self: DataActorNative,
745    {
746        self.core().cache_api()
747    }
748
749    /// Sends a shutdown command to the system with an optional reason.
750    ///
751    /// # Panics
752    ///
753    /// Panics if the actor is not registered or has no trader ID.
754    fn shutdown_system(&self, reason: Option<String>)
755    where
756        Self: DataActorNative,
757    {
758        self.core().shutdown_system(reason);
759    }
760
761    /// Publishes `data` on the message bus under the topic derived from `data_type`.
762    ///
763    /// `data_type` is kept as an explicit parameter to allow callers to override the
764    /// routing topic from the payload's intrinsic type.
765    ///
766    /// # Panics
767    ///
768    /// Panics if the actor is not registered with a trader.
769    fn publish_data(&self, data_type: &DataType, data: &CustomData)
770    where
771        Self: DataActorNative,
772    {
773        self.core().publish_data(data_type, data);
774    }
775
776    /// Publishes a [`Signal`] constructed from `name` and `value`.
777    ///
778    /// # Panics
779    ///
780    /// Panics if the actor is not registered with a trader.
781    fn publish_signal(&self, name: &str, value: String, ts_event: UnixNanos)
782    where
783        Self: DataActorNative,
784    {
785        self.core().publish_signal(name, value, ts_event);
786    }
787
788    // panics-doc-ok
789    /// Adds the `synthetic` instrument to the cache.
790    ///
791    /// # Errors
792    ///
793    /// Returns an error if a synthetic with the same ID already exists, or if the
794    /// backing cache fails to persist it.
795    ///
796    /// # Panics
797    ///
798    /// Panics if the actor is not registered with a trader.
799    fn add_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()>
800    where
801        Self: DataActorNative,
802    {
803        self.core().add_synthetic(synthetic)
804    }
805
806    // panics-doc-ok
807    /// Updates the `synthetic` instrument in the cache, replacing the existing entry.
808    ///
809    /// # Errors
810    ///
811    /// Returns an error if no synthetic with the same ID already exists, or if the
812    /// backing cache fails to persist the replacement.
813    ///
814    /// # Panics
815    ///
816    /// Panics if the actor is not registered with a trader.
817    fn update_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()>
818    where
819        Self: DataActorNative,
820    {
821        self.core().update_synthetic(synthetic)
822    }
823
824    /// Handles a received time event.
825    fn handle_time_event(&mut self, event: &TimeEvent) {
826        log_received(&event);
827
828        if self.not_running() {
829            log_not_running(&event);
830            return;
831        }
832
833        if let Err(e) = DataActor::on_time_event(self, event) {
834            log_error(&e);
835        }
836    }
837
838    /// Handles a received custom data point.
839    fn handle_data(&mut self, data: &CustomData) {
840        log_received(&data);
841
842        if self.not_running() {
843            log_not_running(&data);
844            return;
845        }
846
847        if let Err(e) = self.on_data(data) {
848            log_error(&e);
849        }
850    }
851
852    /// Handles a received signal.
853    fn handle_signal(&mut self, signal: &Signal) {
854        log_received(&signal);
855
856        if self.not_running() {
857            log_not_running(&signal);
858            return;
859        }
860
861        if let Err(e) = self.on_signal(signal) {
862            log_error(&e);
863        }
864    }
865
866    /// Handles a received queue state change.
867    fn handle_queue_state(&mut self, event: &QueueStateChanged) {
868        log_received(&event);
869
870        if self.not_running() {
871            log_not_running(&event);
872            return;
873        }
874
875        if let Err(e) = self.on_queue_state(event) {
876            log_error(&e);
877        }
878    }
879
880    /// Handles a received socket state change.
881    fn handle_socket_state(&mut self, event: &SocketStateChanged) {
882        log_received(&event);
883
884        if self.not_running() {
885            log_not_running(&event);
886            return;
887        }
888
889        if let Err(e) = self.on_socket_state(event) {
890            log_error(&e);
891        }
892    }
893
894    /// Handles a received instrument.
895    fn handle_instrument(&mut self, instrument: &InstrumentAny) {
896        log_received(&instrument);
897
898        if self.not_running() {
899            log_not_running(&instrument);
900            return;
901        }
902
903        if let Err(e) = self.on_instrument(instrument) {
904            log_error(&e);
905        }
906    }
907
908    /// Handles received order book deltas.
909    fn handle_book_deltas(&mut self, deltas: &OrderBookDeltas) {
910        log_received(&deltas);
911
912        if self.not_running() {
913            log_not_running(&deltas);
914            return;
915        }
916
917        if let Err(e) = self.on_book_deltas(deltas) {
918            log_error(&e);
919        }
920    }
921
922    /// Handles a received order book depth10 snapshot.
923    fn handle_book_depth(&mut self, depth: &OrderBookDepth10) {
924        log_received(&depth);
925
926        if self.not_running() {
927            log_not_running(&depth);
928            return;
929        }
930
931        if let Err(e) = self.on_book_depth(depth) {
932            log_error(&e);
933        }
934    }
935
936    /// Handles a received order book reference.
937    fn handle_book(&mut self, book: &OrderBook) {
938        log_received(&book);
939
940        if self.not_running() {
941            log_not_running(&book);
942            return;
943        }
944
945        if let Err(e) = self.on_book(book) {
946            log_error(&e);
947        }
948    }
949
950    /// Handles a received quote.
951    fn handle_quote(&mut self, quote: &QuoteTick)
952    where
953        Self: DataActorNative,
954    {
955        log_received(&quote);
956
957        if let Err(e) = self.core().handle_indicators_for_quote(quote) {
958            log_error(&e);
959            return;
960        }
961
962        if self.not_running() {
963            log_not_running(&quote);
964            return;
965        }
966
967        if let Err(e) = self.on_quote(quote) {
968            log_error(&e);
969        }
970    }
971
972    /// Handles a received trade.
973    fn handle_trade(&mut self, trade: &TradeTick)
974    where
975        Self: DataActorNative,
976    {
977        log_received(&trade);
978
979        if let Err(e) = self.core().handle_indicators_for_trade(trade) {
980            log_error(&e);
981            return;
982        }
983
984        if self.not_running() {
985            log_not_running(&trade);
986            return;
987        }
988
989        if let Err(e) = self.on_trade(trade) {
990            log_error(&e);
991        }
992    }
993
994    /// Handles a receiving bar.
995    fn handle_bar(&mut self, bar: &Bar)
996    where
997        Self: DataActorNative,
998    {
999        log_received(&bar);
1000
1001        if let Err(e) = self.core().handle_indicators_for_bar(bar) {
1002            log_error(&e);
1003            return;
1004        }
1005
1006        if self.not_running() {
1007            log_not_running(&bar);
1008            return;
1009        }
1010
1011        if let Err(e) = self.on_bar(bar) {
1012            log_error(&e);
1013        }
1014    }
1015
1016    /// Handles a received mark price update.
1017    fn handle_mark_price(&mut self, mark_price: &MarkPriceUpdate) {
1018        log_received(&mark_price);
1019
1020        if self.not_running() {
1021            log_not_running(&mark_price);
1022            return;
1023        }
1024
1025        if let Err(e) = self.on_mark_price(mark_price) {
1026            log_error(&e);
1027        }
1028    }
1029
1030    /// Handles a received index price update.
1031    fn handle_index_price(&mut self, index_price: &IndexPriceUpdate) {
1032        log_received(&index_price);
1033
1034        if self.not_running() {
1035            log_not_running(&index_price);
1036            return;
1037        }
1038
1039        if let Err(e) = self.on_index_price(index_price) {
1040            log_error(&e);
1041        }
1042    }
1043
1044    /// Handles a received funding rate update.
1045    fn handle_funding_rate(&mut self, funding_rate: &FundingRateUpdate) {
1046        log_received(&funding_rate);
1047
1048        if self.not_running() {
1049            log_not_running(&funding_rate);
1050            return;
1051        }
1052
1053        if let Err(e) = self.on_funding_rate(funding_rate) {
1054            log_error(&e);
1055        }
1056    }
1057
1058    /// Handles a received option greeks update.
1059    fn handle_option_greeks(&mut self, greeks: &OptionGreeks) {
1060        log_received(&greeks);
1061
1062        if self.not_running() {
1063            log_not_running(&greeks);
1064            return;
1065        }
1066
1067        if let Err(e) = self.on_option_greeks(greeks) {
1068            log_error(&e);
1069        }
1070    }
1071
1072    /// Handles a received option chain slice snapshot.
1073    fn handle_option_chain(&mut self, slice: &OptionChainSlice) {
1074        log_received(&slice);
1075
1076        if self.not_running() {
1077            log_not_running(&slice);
1078            return;
1079        }
1080
1081        if let Err(e) = self.on_option_chain(slice) {
1082            log_error(&e);
1083        }
1084    }
1085
1086    /// Handles a received instrument status.
1087    fn handle_instrument_status(&mut self, status: &InstrumentStatus) {
1088        log_received(&status);
1089
1090        if self.not_running() {
1091            log_not_running(&status);
1092            return;
1093        }
1094
1095        if let Err(e) = self.on_instrument_status(status) {
1096            log_error(&e);
1097        }
1098    }
1099
1100    /// Handles a received instrument close.
1101    fn handle_instrument_close(&mut self, close: &InstrumentClose) {
1102        log_received(&close);
1103
1104        if self.not_running() {
1105            log_not_running(&close);
1106            return;
1107        }
1108
1109        if let Err(e) = self.on_instrument_close(close) {
1110            log_error(&e);
1111        }
1112    }
1113
1114    #[cfg(feature = "defi")]
1115    /// Handles a received block.
1116    fn handle_block(&mut self, block: &Block) {
1117        log_received(&block);
1118
1119        if self.not_running() {
1120            log_not_running(&block);
1121            return;
1122        }
1123
1124        if let Err(e) = self.on_block(block) {
1125            log_error(&e);
1126        }
1127    }
1128
1129    #[cfg(feature = "defi")]
1130    /// Handles a received pool definition update.
1131    fn handle_pool(&mut self, pool: &Pool) {
1132        log_received(&pool);
1133
1134        if self.not_running() {
1135            log_not_running(&pool);
1136            return;
1137        }
1138
1139        if let Err(e) = self.on_pool(pool) {
1140            log_error(&e);
1141        }
1142    }
1143
1144    #[cfg(feature = "defi")]
1145    /// Handles a received pool swap.
1146    fn handle_pool_swap(&mut self, swap: &PoolSwap) {
1147        log_received(&swap);
1148
1149        if self.not_running() {
1150            log_not_running(&swap);
1151            return;
1152        }
1153
1154        if let Err(e) = self.on_pool_swap(swap) {
1155            log_error(&e);
1156        }
1157    }
1158
1159    #[cfg(feature = "defi")]
1160    /// Handles a received pool liquidity update.
1161    fn handle_pool_liquidity_update(&mut self, update: &PoolLiquidityUpdate) {
1162        log_received(&update);
1163
1164        if self.not_running() {
1165            log_not_running(&update);
1166            return;
1167        }
1168
1169        if let Err(e) = self.on_pool_liquidity_update(update) {
1170            log_error(&e);
1171        }
1172    }
1173
1174    #[cfg(feature = "defi")]
1175    /// Handles a received pool fee collect.
1176    fn handle_pool_fee_collect(&mut self, collect: &PoolFeeCollect) {
1177        log_received(&collect);
1178
1179        if self.not_running() {
1180            log_not_running(&collect);
1181            return;
1182        }
1183
1184        if let Err(e) = self.on_pool_fee_collect(collect) {
1185            log_error(&e);
1186        }
1187    }
1188
1189    #[cfg(feature = "defi")]
1190    /// Handles a received pool flash event.
1191    fn handle_pool_flash(&mut self, flash: &PoolFlash) {
1192        log_received(&flash);
1193
1194        if self.not_running() {
1195            log_not_running(&flash);
1196            return;
1197        }
1198
1199        if let Err(e) = self.on_pool_flash(flash) {
1200            log_error(&e);
1201        }
1202    }
1203
1204    /// Handles received historical data.
1205    fn handle_historical_data(&mut self, data: &dyn Any) {
1206        log_received(&data);
1207
1208        if let Err(e) = self.on_historical_data(data) {
1209            log_error(&e);
1210        }
1211    }
1212
1213    /// Handles a data response.
1214    fn handle_data_response(&mut self, resp: &CustomDataResponse) {
1215        if let Some(data) = resp.data.as_ref().downcast_ref::<Vec<CustomData>>() {
1216            log_received_bulk("CustomDataResponse", &resp.correlation_id, data.len());
1217            log::trace!("{RECV} {resp:?}");
1218        } else {
1219            log_received(&resp);
1220        }
1221
1222        if let Err(e) = self.on_historical_data(resp.data.as_ref()) {
1223            log_error(&e);
1224        }
1225    }
1226
1227    /// Handles an instrument response.
1228    fn handle_instrument_response(&mut self, resp: &InstrumentResponse) {
1229        log_received(&resp);
1230
1231        if let Err(e) = self.on_instrument(&resp.data) {
1232            log_error(&e);
1233        }
1234    }
1235
1236    /// Handles an instruments response.
1237    fn handle_instruments_response(&mut self, resp: &InstrumentsResponse) {
1238        log_received_bulk("InstrumentsResponse", &resp.correlation_id, resp.data.len());
1239        log::trace!("{RECV} {resp:?}");
1240
1241        for inst in &resp.data {
1242            if let Err(e) = self.on_instrument(inst) {
1243                log_error(&e);
1244            }
1245        }
1246    }
1247
1248    /// Handles a book response.
1249    fn handle_book_response(&mut self, resp: &BookResponse) {
1250        log_received(&resp);
1251
1252        if let Err(e) = self.on_book(&resp.data) {
1253            log_error(&e);
1254        }
1255    }
1256
1257    /// Handles a book deltas response.
1258    fn handle_book_deltas_response(&mut self, resp: &BookDeltasResponse) {
1259        log_received_bulk("BookDeltasResponse", &resp.correlation_id, resp.data.len());
1260        log::trace!("{RECV} {resp:?}");
1261
1262        if let Err(e) = self.on_historical_book_deltas(&resp.data) {
1263            log_error(&e);
1264        }
1265    }
1266
1267    /// Handles a book depth response.
1268    fn handle_book_depth_response(&mut self, resp: &BookDepthResponse) {
1269        log_received_bulk("BookDepthResponse", &resp.correlation_id, resp.data.len());
1270        log::trace!("{RECV} {resp:?}");
1271
1272        if let Err(e) = self.on_historical_book_depth(&resp.data) {
1273            log_error(&e);
1274        }
1275    }
1276
1277    /// Handles a quotes response.
1278    fn handle_quotes_response(&mut self, resp: &QuotesResponse)
1279    where
1280        Self: DataActorNative,
1281    {
1282        log_received_bulk("QuotesResponse", &resp.correlation_id, resp.data.len());
1283        log::trace!("{RECV} {resp:?}");
1284
1285        if let Err(e) = self.core().handle_indicators_for_quotes(&resp.data) {
1286            log_error(&e);
1287            return;
1288        }
1289
1290        if let Err(e) = self.on_historical_quotes(&resp.data) {
1291            log_error(&e);
1292        }
1293    }
1294
1295    /// Handles a trades response.
1296    fn handle_trades_response(&mut self, resp: &TradesResponse)
1297    where
1298        Self: DataActorNative,
1299    {
1300        log_received_bulk("TradesResponse", &resp.correlation_id, resp.data.len());
1301        log::trace!("{RECV} {resp:?}");
1302
1303        if let Err(e) = self.core().handle_indicators_for_trades(&resp.data) {
1304            log_error(&e);
1305            return;
1306        }
1307
1308        if let Err(e) = self.on_historical_trades(&resp.data) {
1309            log_error(&e);
1310        }
1311    }
1312
1313    /// Handles a bars response.
1314    fn handle_bars_response(&mut self, resp: &BarsResponse)
1315    where
1316        Self: DataActorNative,
1317    {
1318        log_received_bulk("BarsResponse", &resp.correlation_id, resp.data.len());
1319        log::trace!("{RECV} {resp:?}");
1320
1321        if let Err(e) = self.core().handle_indicators_for_bars(&resp.data) {
1322            log_error(&e);
1323            return;
1324        }
1325
1326        if let Err(e) = self.on_historical_bars(&resp.data) {
1327            log_error(&e);
1328        }
1329    }
1330
1331    /// Handles a funding rates response.
1332    fn handle_funding_rates_response(&mut self, resp: &FundingRatesResponse) {
1333        log_received_bulk(
1334            "FundingRatesResponse",
1335            &resp.correlation_id,
1336            resp.data.len(),
1337        );
1338        log::trace!("{RECV} {resp:?}");
1339
1340        if let Err(e) = self.on_historical_funding_rates(&resp.data) {
1341            log_error(&e);
1342        }
1343    }
1344
1345    /// Subscribe to streaming `data_type` data.
1346    fn subscribe_data(
1347        &mut self,
1348        data_type: DataType,
1349        client_id: Option<ClientId>,
1350        params: Option<Params>,
1351    ) where
1352        Self: DataActorNative,
1353        Self: 'static + Debug + Sized,
1354    {
1355        let actor_id = self.core().actor_id().inner();
1356        let handler = ShareableMessageHandler::from_typed(move |data: &CustomData| {
1357            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1358                actor.handle_data(data);
1359            } else {
1360                log::error!("Actor {actor_id} not found for data handling");
1361            }
1362        });
1363
1364        DataActorCore::subscribe_data(self.core_mut(), handler, data_type, client_id, params);
1365    }
1366
1367    /// Subscribe to [`Signal`] data by `name`.
1368    ///
1369    /// An empty `name` subscribes to every signal.
1370    ///
1371    /// # Parameters
1372    ///
1373    /// - `name`: signal name to subscribe to.
1374    /// - `priority`: optional dispatch priority. Pass `None` for default
1375    ///   ordering (by pattern then handler ID). Pass `Some(p)` when actors
1376    ///   sharing a signal need deterministic ordering: higher-priority
1377    ///   handlers receive the message before lower-priority handlers.
1378    ///
1379    /// Re-subscribing does not update an existing priority; call
1380    /// [`unsubscribe_signal`](Self::unsubscribe_signal) first.
1381    fn subscribe_signal(&mut self, name: &str, priority: Option<u32>)
1382    where
1383        Self: DataActorNative,
1384        Self: 'static + Debug + Sized,
1385    {
1386        let actor_id = self.core().actor_id().inner();
1387        // Signals are published as `CustomData` wrapping a `Signal`; downcast
1388        // the inner value so subscribers receive the typed `Signal` in `on_signal`.
1389        let handler = ShareableMessageHandler::from_typed(move |data: &CustomData| {
1390            if let Some(signal) = data.data.as_any().downcast_ref::<Signal>() {
1391                if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1392                    actor.handle_signal(signal);
1393                } else {
1394                    log::error!("Actor {actor_id} not found for signal handling");
1395                }
1396            }
1397        });
1398
1399        DataActorCore::subscribe_signal(self.core_mut(), handler, name, priority);
1400    }
1401
1402    /// Subscribes to [`QueueStateChanged`] events.
1403    ///
1404    /// `priority` controls dispatch order when multiple actors subscribe to the event. Higher
1405    /// values receive the event first. Re-subscribing does not update an existing priority; call
1406    /// [`unsubscribe_queue_state`](Self::unsubscribe_queue_state) first.
1407    fn subscribe_queue_state(&mut self, priority: Option<u32>)
1408    where
1409        Self: DataActorNative,
1410        Self: 'static + Debug + Sized,
1411    {
1412        let actor_id = self.core().actor_id().inner();
1413        let handler = ShareableMessageHandler::from_typed(move |event: &QueueStateChanged| {
1414            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1415                actor.handle_queue_state(event);
1416            } else {
1417                log::error!("Actor {actor_id} not found for queue state change handling");
1418            }
1419        });
1420
1421        DataActorCore::subscribe_queue_state(self.core_mut(), handler, priority);
1422    }
1423
1424    /// Subscribes to [`SocketStateChanged`] events.
1425    ///
1426    /// `priority` controls dispatch order when multiple actors subscribe to the event. Higher
1427    /// values receive the event first. Re-subscribing does not update an existing priority; call
1428    /// [`unsubscribe_socket_state`](Self::unsubscribe_socket_state) first.
1429    fn subscribe_socket_state(&mut self, priority: Option<u32>)
1430    where
1431        Self: DataActorNative,
1432        Self: 'static + Debug + Sized,
1433    {
1434        let actor_id = self.core().actor_id().inner();
1435        let handler = ShareableMessageHandler::from_typed(move |event: &SocketStateChanged| {
1436            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1437                actor.handle_socket_state(event);
1438            } else {
1439                log::error!("Actor {actor_id} not found for socket state change handling");
1440            }
1441        });
1442
1443        DataActorCore::subscribe_socket_state(self.core_mut(), handler, priority);
1444    }
1445
1446    /// Subscribe to streaming [`QuoteTick`] data for the `instrument_id`.
1447    fn subscribe_quotes(
1448        &mut self,
1449        instrument_id: InstrumentId,
1450        client_id: Option<ClientId>,
1451        params: Option<Params>,
1452    ) where
1453        Self: DataActorNative,
1454        Self: 'static + Debug + Sized,
1455    {
1456        let actor_id = self.core().actor_id().inner();
1457        let topic = get_quotes_topic(instrument_id);
1458
1459        let handler = TypedHandler::from(move |quote: &QuoteTick| {
1460            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1461                actor.handle_quote(quote);
1462            } else {
1463                log::error!("Actor {actor_id} not found for quote handling");
1464            }
1465        });
1466
1467        DataActorCore::subscribe_quotes(
1468            self.core_mut(),
1469            topic,
1470            handler,
1471            instrument_id,
1472            client_id,
1473            params,
1474        );
1475    }
1476
1477    /// Subscribe to streaming [`InstrumentAny`] data for the `venue`.
1478    fn subscribe_instruments(
1479        &mut self,
1480        venue: Venue,
1481        client_id: Option<ClientId>,
1482        params: Option<Params>,
1483    ) where
1484        Self: DataActorNative,
1485        Self: 'static + Debug + Sized,
1486    {
1487        let actor_id = self.core().actor_id().inner();
1488        let pattern = get_instruments_pattern(venue);
1489
1490        let handler = TypedHandler::from(move |instrument: &InstrumentAny| {
1491            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1492                actor.handle_instrument(instrument);
1493            } else {
1494                log::error!("Actor {actor_id} not found for instruments handling");
1495            }
1496        });
1497
1498        DataActorCore::subscribe_instruments(
1499            self.core_mut(),
1500            pattern,
1501            handler,
1502            venue,
1503            client_id,
1504            params,
1505        );
1506    }
1507
1508    /// Subscribe to streaming [`InstrumentAny`] data for the `instrument_id`.
1509    fn subscribe_instrument(
1510        &mut self,
1511        instrument_id: InstrumentId,
1512        client_id: Option<ClientId>,
1513        params: Option<Params>,
1514    ) where
1515        Self: DataActorNative,
1516        Self: 'static + Debug + Sized,
1517    {
1518        let actor_id = self.core().actor_id().inner();
1519        let topic = get_instrument_topic(instrument_id);
1520
1521        let handler = TypedHandler::from(move |instrument: &InstrumentAny| {
1522            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1523                actor.handle_instrument(instrument);
1524            } else {
1525                log::error!("Actor {actor_id} not found for instrument handling");
1526            }
1527        });
1528
1529        DataActorCore::subscribe_instrument(
1530            self.core_mut(),
1531            topic,
1532            handler,
1533            instrument_id,
1534            client_id,
1535            params,
1536        );
1537    }
1538
1539    /// Subscribe to streaming [`OrderBookDeltas`] data for the `instrument_id`.
1540    ///
1541    /// When `managed` is true, the data engine maintains an [`OrderBook`] in the cache for each
1542    /// instrument the subscription resolves to, applying each batch of deltas as it arrives.
1543    /// A parent subscription resolves to every matching underlying instrument.
1544    fn subscribe_book_deltas(
1545        &mut self,
1546        instrument_id: InstrumentId,
1547        book_type: BookType,
1548        depth: Option<NonZeroUsize>,
1549        client_id: Option<ClientId>,
1550        managed: bool,
1551        params: Option<Params>,
1552    ) where
1553        Self: DataActorNative,
1554        Self: 'static + Debug + Sized,
1555    {
1556        let actor_id = self.core().actor_id().inner();
1557        let is_parent = is_parent_subscription(params.as_ref());
1558        let pattern = if is_parent {
1559            get_book_deltas_pattern(instrument_id)
1560        } else {
1561            get_book_deltas_topic(instrument_id).into()
1562        };
1563
1564        let handler = TypedHandler::from(move |deltas: &OrderBookDeltas| {
1565            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1566                actor.handle_book_deltas(deltas);
1567            } else {
1568                log::error!("Actor {actor_id} not found for book deltas handling");
1569            }
1570        });
1571
1572        DataActorCore::subscribe_book_deltas(
1573            self.core_mut(),
1574            pattern,
1575            handler,
1576            instrument_id,
1577            book_type,
1578            depth,
1579            client_id,
1580            managed,
1581            params,
1582        );
1583    }
1584
1585    /// Subscribe to streaming [`OrderBookDepth10`] data for the `instrument_id`.
1586    ///
1587    /// When `managed` is true, the data engine maintains an [`OrderBook`] in the cache for each
1588    /// instrument the subscription resolves to, applying each update as it arrives.
1589    /// A parent subscription resolves to every matching underlying instrument.
1590    fn subscribe_book_depth10(
1591        &mut self,
1592        instrument_id: InstrumentId,
1593        book_type: BookType,
1594        client_id: Option<ClientId>,
1595        managed: bool,
1596        params: Option<Params>,
1597    ) where
1598        Self: DataActorNative,
1599        Self: 'static + Debug + Sized,
1600    {
1601        let actor_id = self.core().actor_id().inner();
1602        let pattern = if is_parent_subscription(params.as_ref()) {
1603            get_book_depth10_pattern(instrument_id)
1604        } else {
1605            get_book_depth10_topic(instrument_id).into()
1606        };
1607
1608        let handler = TypedHandler::from(move |depth: &OrderBookDepth10| {
1609            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1610                actor.handle_book_depth(depth);
1611            } else {
1612                log::error!("Actor {actor_id} not found for book depth handling");
1613            }
1614        });
1615
1616        DataActorCore::subscribe_book_depth10(
1617            self.core_mut(),
1618            pattern,
1619            handler,
1620            instrument_id,
1621            book_type,
1622            client_id,
1623            managed,
1624            params,
1625        );
1626    }
1627
1628    /// Subscribe to [`OrderBook`] snapshots at a specified interval for the `instrument_id`.
1629    fn subscribe_book_at_interval(
1630        &mut self,
1631        instrument_id: InstrumentId,
1632        book_type: BookType,
1633        depth: Option<NonZeroUsize>,
1634        interval_ms: NonZeroUsize,
1635        client_id: Option<ClientId>,
1636        params: Option<Params>,
1637    ) where
1638        Self: DataActorNative,
1639        Self: 'static + Debug + Sized,
1640    {
1641        let actor_id = self.core().actor_id().inner();
1642        let topic = get_book_snapshots_topic(instrument_id, interval_ms);
1643
1644        let handler = TypedHandler::from(move |book: &OrderBook| {
1645            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1646                actor.handle_book(book);
1647            } else {
1648                log::error!("Actor {actor_id} not found for book handling");
1649            }
1650        });
1651
1652        DataActorCore::subscribe_book_at_interval(
1653            self.core_mut(),
1654            topic,
1655            handler,
1656            instrument_id,
1657            book_type,
1658            depth,
1659            interval_ms,
1660            client_id,
1661            params,
1662        );
1663    }
1664
1665    /// Subscribe to streaming [`TradeTick`] data for the `instrument_id`.
1666    fn subscribe_trades(
1667        &mut self,
1668        instrument_id: InstrumentId,
1669        client_id: Option<ClientId>,
1670        params: Option<Params>,
1671    ) where
1672        Self: DataActorNative,
1673        Self: 'static + Debug + Sized,
1674    {
1675        let actor_id = self.core().actor_id().inner();
1676        let topic = get_trades_topic(instrument_id);
1677
1678        let handler = TypedHandler::from(move |trade: &TradeTick| {
1679            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1680                actor.handle_trade(trade);
1681            } else {
1682                log::error!("Actor {actor_id} not found for trade handling");
1683            }
1684        });
1685
1686        DataActorCore::subscribe_trades(
1687            self.core_mut(),
1688            topic,
1689            handler,
1690            instrument_id,
1691            client_id,
1692            params,
1693        );
1694    }
1695
1696    /// Subscribe to streaming [`Bar`] data for the `bar_type`.
1697    fn subscribe_bars(
1698        &mut self,
1699        bar_type: BarType,
1700        client_id: Option<ClientId>,
1701        params: Option<Params>,
1702    ) where
1703        Self: DataActorNative,
1704        Self: 'static + Debug + Sized,
1705    {
1706        let actor_id = self.core().actor_id().inner();
1707        // Aggregators publish emitted bars under the standard type, so subscribe on that topic
1708        let topic = get_bars_topic(bar_type.standard());
1709
1710        let handler = TypedHandler::from(move |bar: &Bar| {
1711            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1712                actor.handle_bar(bar);
1713            } else {
1714                log::error!("Actor {actor_id} not found for bar handling");
1715            }
1716        });
1717
1718        DataActorCore::subscribe_bars(self.core_mut(), topic, handler, bar_type, client_id, params);
1719    }
1720
1721    /// Subscribe to streaming [`MarkPriceUpdate`] data for the `instrument_id`.
1722    fn subscribe_mark_prices(
1723        &mut self,
1724        instrument_id: InstrumentId,
1725        client_id: Option<ClientId>,
1726        params: Option<Params>,
1727    ) where
1728        Self: DataActorNative,
1729        Self: 'static + Debug + Sized,
1730    {
1731        let actor_id = self.core().actor_id().inner();
1732        let topic = get_mark_price_topic(instrument_id);
1733
1734        let handler = TypedHandler::from(move |mark_price: &MarkPriceUpdate| {
1735            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1736                actor.handle_mark_price(mark_price);
1737            } else {
1738                log::error!("Actor {actor_id} not found for mark price handling");
1739            }
1740        });
1741
1742        DataActorCore::subscribe_mark_prices(
1743            self.core_mut(),
1744            topic,
1745            handler,
1746            instrument_id,
1747            client_id,
1748            params,
1749        );
1750    }
1751
1752    /// Subscribe to streaming [`IndexPriceUpdate`] data for the `instrument_id`.
1753    fn subscribe_index_prices(
1754        &mut self,
1755        instrument_id: InstrumentId,
1756        client_id: Option<ClientId>,
1757        params: Option<Params>,
1758    ) where
1759        Self: DataActorNative,
1760        Self: 'static + Debug + Sized,
1761    {
1762        let actor_id = self.core().actor_id().inner();
1763        let topic = get_index_price_topic(instrument_id);
1764
1765        let handler = TypedHandler::from(move |index_price: &IndexPriceUpdate| {
1766            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1767                actor.handle_index_price(index_price);
1768            } else {
1769                log::error!("Actor {actor_id} not found for index price handling");
1770            }
1771        });
1772
1773        DataActorCore::subscribe_index_prices(
1774            self.core_mut(),
1775            topic,
1776            handler,
1777            instrument_id,
1778            client_id,
1779            params,
1780        );
1781    }
1782
1783    /// Subscribe to streaming [`FundingRateUpdate`] data for the `instrument_id`.
1784    fn subscribe_funding_rates(
1785        &mut self,
1786        instrument_id: InstrumentId,
1787        client_id: Option<ClientId>,
1788        params: Option<Params>,
1789    ) where
1790        Self: DataActorNative,
1791        Self: 'static + Debug + Sized,
1792    {
1793        let actor_id = self.core().actor_id().inner();
1794        let topic = get_funding_rate_topic(instrument_id);
1795
1796        let handler = TypedHandler::from(move |funding_rate: &FundingRateUpdate| {
1797            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1798                actor.handle_funding_rate(funding_rate);
1799            } else {
1800                log::error!("Actor {actor_id} not found for funding rate handling");
1801            }
1802        });
1803
1804        DataActorCore::subscribe_funding_rates(
1805            self.core_mut(),
1806            topic,
1807            handler,
1808            instrument_id,
1809            client_id,
1810            params,
1811        );
1812    }
1813
1814    /// Subscribe to streaming [`OptionGreeks`] data for the `instrument_id`.
1815    fn subscribe_option_greeks(
1816        &mut self,
1817        instrument_id: InstrumentId,
1818        client_id: Option<ClientId>,
1819        params: Option<Params>,
1820    ) where
1821        Self: DataActorNative,
1822        Self: 'static + Debug + Sized,
1823    {
1824        let actor_id = self.core().actor_id().inner();
1825        let topic = get_option_greeks_topic(instrument_id);
1826
1827        let handler = TypedHandler::from(move |option_greeks: &OptionGreeks| {
1828            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1829                actor.handle_option_greeks(option_greeks);
1830            } else {
1831                log::error!("Actor {actor_id} not found for option greeks handling");
1832            }
1833        });
1834
1835        DataActorCore::subscribe_option_greeks(
1836            self.core_mut(),
1837            topic,
1838            handler,
1839            instrument_id,
1840            client_id,
1841            params,
1842        );
1843    }
1844
1845    /// Subscribe to streaming [`InstrumentStatus`] data for the `instrument_id`.
1846    fn subscribe_instrument_status(
1847        &mut self,
1848        instrument_id: InstrumentId,
1849        client_id: Option<ClientId>,
1850        params: Option<Params>,
1851    ) where
1852        Self: DataActorNative,
1853        Self: 'static + Debug + Sized,
1854    {
1855        let actor_id = self.core().actor_id().inner();
1856        let topic = get_instrument_status_topic(instrument_id);
1857
1858        let handler = ShareableMessageHandler::from_typed(move |status: &InstrumentStatus| {
1859            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1860                actor.handle_instrument_status(status);
1861            } else {
1862                log::error!("Actor {actor_id} not found for instrument status handling");
1863            }
1864        });
1865
1866        DataActorCore::subscribe_instrument_status(
1867            self.core_mut(),
1868            topic,
1869            handler,
1870            instrument_id,
1871            client_id,
1872            params,
1873        );
1874    }
1875
1876    /// Subscribe to streaming [`InstrumentClose`] data for the `instrument_id`.
1877    fn subscribe_instrument_close(
1878        &mut self,
1879        instrument_id: InstrumentId,
1880        client_id: Option<ClientId>,
1881        params: Option<Params>,
1882    ) where
1883        Self: DataActorNative,
1884        Self: 'static + Debug + Sized,
1885    {
1886        let actor_id = self.core().actor_id().inner();
1887        let topic = get_instrument_close_topic(instrument_id);
1888
1889        let handler = ShareableMessageHandler::from_typed(move |close: &InstrumentClose| {
1890            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1891                actor.handle_instrument_close(close);
1892            } else {
1893                log::error!("Actor {actor_id} not found for instrument close handling");
1894            }
1895        });
1896
1897        DataActorCore::subscribe_instrument_close(
1898            self.core_mut(),
1899            topic,
1900            handler,
1901            instrument_id,
1902            client_id,
1903            params,
1904        );
1905    }
1906
1907    /// Subscribe to streaming [`OptionChainSlice`] snapshots for the option `series_id`.
1908    ///
1909    /// The ATM price is always derived from the exchange-provided forward price
1910    /// embedded in each option greeks/ticker update.
1911    fn subscribe_option_chain(
1912        &mut self,
1913        series_id: OptionSeriesId,
1914        strike_range: StrikeRange,
1915        snapshot_interval_ms: Option<u64>,
1916        client_id: Option<ClientId>,
1917        params: Option<Params>,
1918    ) where
1919        Self: DataActorNative,
1920        Self: 'static + Debug + Sized,
1921    {
1922        let actor_id = self.core().actor_id().inner();
1923        let topic = get_option_chain_topic(series_id);
1924
1925        let handler = TypedHandler::from(move |slice: &OptionChainSlice| {
1926            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1927                actor.handle_option_chain(slice);
1928            } else {
1929                log::error!("Actor {actor_id} not found for option chain handling");
1930            }
1931        });
1932
1933        DataActorCore::subscribe_option_chain(
1934            self.core_mut(),
1935            topic,
1936            handler,
1937            series_id,
1938            strike_range,
1939            snapshot_interval_ms,
1940            client_id,
1941            params,
1942        );
1943    }
1944
1945    #[cfg(feature = "defi")]
1946    /// Subscribe to streaming [`Block`] data for the `chain`.
1947    fn subscribe_blocks(
1948        &mut self,
1949        chain: Blockchain,
1950        client_id: Option<ClientId>,
1951        params: Option<Params>,
1952    ) where
1953        Self: DataActorNative,
1954        Self: 'static + Debug + Sized,
1955    {
1956        let actor_id = self.core().actor_id().inner();
1957        let topic = defi::switchboard::get_defi_blocks_topic(chain);
1958
1959        let handler = TypedHandler::from(move |block: &Block| {
1960            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1961                actor.handle_block(block);
1962            } else {
1963                log::error!("Actor {actor_id} not found for block handling");
1964            }
1965        });
1966
1967        DataActorCore::subscribe_blocks(self.core_mut(), topic, handler, chain, client_id, params);
1968    }
1969
1970    #[cfg(feature = "defi")]
1971    /// Subscribe to streaming [`Pool`] definition updates for the AMM pool at the `instrument_id`.
1972    fn subscribe_pool(
1973        &mut self,
1974        instrument_id: InstrumentId,
1975        client_id: Option<ClientId>,
1976        params: Option<Params>,
1977    ) where
1978        Self: DataActorNative,
1979        Self: 'static + Debug + Sized,
1980    {
1981        let actor_id = self.core().actor_id().inner();
1982        let topic = defi::switchboard::get_defi_pool_topic(instrument_id);
1983
1984        let handler = TypedHandler::from(move |pool: &Pool| {
1985            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
1986                actor.handle_pool(pool);
1987            } else {
1988                log::error!("Actor {actor_id} not found for pool handling");
1989            }
1990        });
1991
1992        DataActorCore::subscribe_pool(
1993            self.core_mut(),
1994            topic,
1995            handler,
1996            instrument_id,
1997            client_id,
1998            params,
1999        );
2000    }
2001
2002    #[cfg(feature = "defi")]
2003    /// Subscribe to streaming [`PoolSwap`] data for the `instrument_id`.
2004    fn subscribe_pool_swaps(
2005        &mut self,
2006        instrument_id: InstrumentId,
2007        client_id: Option<ClientId>,
2008        params: Option<Params>,
2009    ) where
2010        Self: DataActorNative,
2011        Self: 'static + Debug + Sized,
2012    {
2013        let actor_id = self.core().actor_id().inner();
2014        let topic = defi::switchboard::get_defi_pool_swaps_topic(instrument_id);
2015
2016        let handler = TypedHandler::from(move |swap: &PoolSwap| {
2017            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2018                actor.handle_pool_swap(swap);
2019            } else {
2020                log::error!("Actor {actor_id} not found for pool swap handling");
2021            }
2022        });
2023
2024        DataActorCore::subscribe_pool_swaps(
2025            self.core_mut(),
2026            topic,
2027            handler,
2028            instrument_id,
2029            client_id,
2030            params,
2031        );
2032    }
2033
2034    #[cfg(feature = "defi")]
2035    /// Subscribe to streaming [`PoolLiquidityUpdate`] data for the `instrument_id`.
2036    fn subscribe_pool_liquidity_updates(
2037        &mut self,
2038        instrument_id: InstrumentId,
2039        client_id: Option<ClientId>,
2040        params: Option<Params>,
2041    ) where
2042        Self: DataActorNative,
2043        Self: 'static + Debug + Sized,
2044    {
2045        let actor_id = self.core().actor_id().inner();
2046        let topic = defi::switchboard::get_defi_liquidity_topic(instrument_id);
2047
2048        let handler = TypedHandler::from(move |update: &PoolLiquidityUpdate| {
2049            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2050                actor.handle_pool_liquidity_update(update);
2051            } else {
2052                log::error!("Actor {actor_id} not found for pool liquidity update handling");
2053            }
2054        });
2055
2056        DataActorCore::subscribe_pool_liquidity_updates(
2057            self.core_mut(),
2058            topic,
2059            handler,
2060            instrument_id,
2061            client_id,
2062            params,
2063        );
2064    }
2065
2066    #[cfg(feature = "defi")]
2067    /// Subscribe to streaming [`PoolFeeCollect`] data for the `instrument_id`.
2068    fn subscribe_pool_fee_collects(
2069        &mut self,
2070        instrument_id: InstrumentId,
2071        client_id: Option<ClientId>,
2072        params: Option<Params>,
2073    ) where
2074        Self: DataActorNative,
2075        Self: 'static + Debug + Sized,
2076    {
2077        let actor_id = self.core().actor_id().inner();
2078        let topic = defi::switchboard::get_defi_collect_topic(instrument_id);
2079
2080        let handler = TypedHandler::from(move |collect: &PoolFeeCollect| {
2081            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2082                actor.handle_pool_fee_collect(collect);
2083            } else {
2084                log::error!("Actor {actor_id} not found for pool fee collect handling");
2085            }
2086        });
2087
2088        DataActorCore::subscribe_pool_fee_collects(
2089            self.core_mut(),
2090            topic,
2091            handler,
2092            instrument_id,
2093            client_id,
2094            params,
2095        );
2096    }
2097
2098    #[cfg(feature = "defi")]
2099    /// Subscribe to streaming [`PoolFlash`] events for the given `instrument_id`.
2100    fn subscribe_pool_flash_events(
2101        &mut self,
2102        instrument_id: InstrumentId,
2103        client_id: Option<ClientId>,
2104        params: Option<Params>,
2105    ) where
2106        Self: DataActorNative,
2107        Self: 'static + Debug + Sized,
2108    {
2109        let actor_id = self.core().actor_id().inner();
2110        let topic = defi::switchboard::get_defi_flash_topic(instrument_id);
2111
2112        let handler = TypedHandler::from(move |flash: &PoolFlash| {
2113            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2114                actor.handle_pool_flash(flash);
2115            } else {
2116                log::error!("Actor {actor_id} not found for pool flash handling");
2117            }
2118        });
2119
2120        DataActorCore::subscribe_pool_flash_events(
2121            self.core_mut(),
2122            topic,
2123            handler,
2124            instrument_id,
2125            client_id,
2126            params,
2127        );
2128    }
2129
2130    /// Unsubscribe from streaming `data_type` data.
2131    fn unsubscribe_data(
2132        &mut self,
2133        data_type: DataType,
2134        client_id: Option<ClientId>,
2135        params: Option<Params>,
2136    ) where
2137        Self: DataActorNative,
2138        Self: 'static + Debug + Sized,
2139    {
2140        DataActorCore::unsubscribe_data(self.core_mut(), data_type, client_id, params);
2141    }
2142
2143    /// Unsubscribe from [`Signal`] data by `name`.
2144    fn unsubscribe_signal(&mut self, name: &str)
2145    where
2146        Self: DataActorNative,
2147        Self: 'static + Debug + Sized,
2148    {
2149        DataActorCore::unsubscribe_signal(self.core_mut(), name);
2150    }
2151
2152    /// Unsubscribes from [`QueueStateChanged`] events.
2153    fn unsubscribe_queue_state(&mut self)
2154    where
2155        Self: DataActorNative,
2156        Self: 'static + Debug + Sized,
2157    {
2158        DataActorCore::unsubscribe_queue_state(self.core_mut());
2159    }
2160
2161    /// Unsubscribes from [`SocketStateChanged`] events.
2162    fn unsubscribe_socket_state(&mut self)
2163    where
2164        Self: DataActorNative,
2165        Self: 'static + Debug + Sized,
2166    {
2167        DataActorCore::unsubscribe_socket_state(self.core_mut());
2168    }
2169
2170    /// Unsubscribe from streaming [`InstrumentAny`] data for the `venue`.
2171    fn unsubscribe_instruments(
2172        &mut self,
2173        venue: Venue,
2174        client_id: Option<ClientId>,
2175        params: Option<Params>,
2176    ) where
2177        Self: DataActorNative,
2178        Self: 'static + Debug + Sized,
2179    {
2180        DataActorCore::unsubscribe_instruments(self.core_mut(), venue, client_id, params);
2181    }
2182
2183    /// Unsubscribe from streaming [`InstrumentAny`] data for the `instrument_id`.
2184    fn unsubscribe_instrument(
2185        &mut self,
2186        instrument_id: InstrumentId,
2187        client_id: Option<ClientId>,
2188        params: Option<Params>,
2189    ) where
2190        Self: DataActorNative,
2191        Self: 'static + Debug + Sized,
2192    {
2193        DataActorCore::unsubscribe_instrument(self.core_mut(), instrument_id, client_id, params);
2194    }
2195
2196    /// Unsubscribe from streaming [`OrderBookDeltas`] data for the `instrument_id`.
2197    fn unsubscribe_book_deltas(
2198        &mut self,
2199        instrument_id: InstrumentId,
2200        client_id: Option<ClientId>,
2201        params: Option<Params>,
2202    ) where
2203        Self: DataActorNative,
2204        Self: 'static + Debug + Sized,
2205    {
2206        DataActorCore::unsubscribe_book_deltas(self.core_mut(), instrument_id, client_id, params);
2207    }
2208
2209    /// Unsubscribe from streaming [`OrderBookDepth10`] data for the `instrument_id`.
2210    fn unsubscribe_book_depth10(
2211        &mut self,
2212        instrument_id: InstrumentId,
2213        client_id: Option<ClientId>,
2214        params: Option<Params>,
2215    ) where
2216        Self: DataActorNative,
2217        Self: 'static + Debug + Sized,
2218    {
2219        DataActorCore::unsubscribe_book_depth10(self.core_mut(), instrument_id, client_id, params);
2220    }
2221
2222    /// Unsubscribe from [`OrderBook`] snapshots at a specified interval for the `instrument_id`.
2223    fn unsubscribe_book_at_interval(
2224        &mut self,
2225        instrument_id: InstrumentId,
2226        interval_ms: NonZeroUsize,
2227        client_id: Option<ClientId>,
2228        params: Option<Params>,
2229    ) where
2230        Self: DataActorNative,
2231        Self: 'static + Debug + Sized,
2232    {
2233        DataActorCore::unsubscribe_book_at_interval(
2234            self.core_mut(),
2235            instrument_id,
2236            interval_ms,
2237            client_id,
2238            params,
2239        );
2240    }
2241
2242    /// Unsubscribe from streaming [`QuoteTick`] data for the `instrument_id`.
2243    fn unsubscribe_quotes(
2244        &mut self,
2245        instrument_id: InstrumentId,
2246        client_id: Option<ClientId>,
2247        params: Option<Params>,
2248    ) where
2249        Self: DataActorNative,
2250        Self: 'static + Debug + Sized,
2251    {
2252        DataActorCore::unsubscribe_quotes(self.core_mut(), instrument_id, client_id, params);
2253    }
2254
2255    /// Unsubscribe from streaming [`TradeTick`] data for the `instrument_id`.
2256    fn unsubscribe_trades(
2257        &mut self,
2258        instrument_id: InstrumentId,
2259        client_id: Option<ClientId>,
2260        params: Option<Params>,
2261    ) where
2262        Self: DataActorNative,
2263        Self: 'static + Debug + Sized,
2264    {
2265        DataActorCore::unsubscribe_trades(self.core_mut(), instrument_id, client_id, params);
2266    }
2267
2268    /// Unsubscribe from streaming [`Bar`] data for the `bar_type`.
2269    fn unsubscribe_bars(
2270        &mut self,
2271        bar_type: BarType,
2272        client_id: Option<ClientId>,
2273        params: Option<Params>,
2274    ) where
2275        Self: DataActorNative,
2276        Self: 'static + Debug + Sized,
2277    {
2278        DataActorCore::unsubscribe_bars(self.core_mut(), bar_type, client_id, params);
2279    }
2280
2281    /// Unsubscribe from streaming [`MarkPriceUpdate`] data for the `instrument_id`.
2282    fn unsubscribe_mark_prices(
2283        &mut self,
2284        instrument_id: InstrumentId,
2285        client_id: Option<ClientId>,
2286        params: Option<Params>,
2287    ) where
2288        Self: DataActorNative,
2289        Self: 'static + Debug + Sized,
2290    {
2291        DataActorCore::unsubscribe_mark_prices(self.core_mut(), instrument_id, client_id, params);
2292    }
2293
2294    /// Unsubscribe from streaming [`IndexPriceUpdate`] data for the `instrument_id`.
2295    fn unsubscribe_index_prices(
2296        &mut self,
2297        instrument_id: InstrumentId,
2298        client_id: Option<ClientId>,
2299        params: Option<Params>,
2300    ) where
2301        Self: DataActorNative,
2302        Self: 'static + Debug + Sized,
2303    {
2304        DataActorCore::unsubscribe_index_prices(self.core_mut(), instrument_id, client_id, params);
2305    }
2306
2307    /// Unsubscribe from streaming [`FundingRateUpdate`] data for the `instrument_id`.
2308    fn unsubscribe_funding_rates(
2309        &mut self,
2310        instrument_id: InstrumentId,
2311        client_id: Option<ClientId>,
2312        params: Option<Params>,
2313    ) where
2314        Self: DataActorNative,
2315        Self: 'static + Debug + Sized,
2316    {
2317        DataActorCore::unsubscribe_funding_rates(self.core_mut(), instrument_id, client_id, params);
2318    }
2319
2320    /// Unsubscribe from streaming [`OptionGreeks`] data for the `instrument_id`.
2321    fn unsubscribe_option_greeks(
2322        &mut self,
2323        instrument_id: InstrumentId,
2324        client_id: Option<ClientId>,
2325        params: Option<Params>,
2326    ) where
2327        Self: DataActorNative,
2328        Self: 'static + Debug + Sized,
2329    {
2330        DataActorCore::unsubscribe_option_greeks(self.core_mut(), instrument_id, client_id, params);
2331    }
2332
2333    /// Unsubscribe from streaming [`InstrumentStatus`] data for the `instrument_id`.
2334    fn unsubscribe_instrument_status(
2335        &mut self,
2336        instrument_id: InstrumentId,
2337        client_id: Option<ClientId>,
2338        params: Option<Params>,
2339    ) where
2340        Self: DataActorNative,
2341        Self: 'static + Debug + Sized,
2342    {
2343        DataActorCore::unsubscribe_instrument_status(
2344            self.core_mut(),
2345            instrument_id,
2346            client_id,
2347            params,
2348        );
2349    }
2350
2351    /// Unsubscribe from streaming [`InstrumentClose`] data for the `instrument_id`.
2352    fn unsubscribe_instrument_close(
2353        &mut self,
2354        instrument_id: InstrumentId,
2355        client_id: Option<ClientId>,
2356        params: Option<Params>,
2357    ) where
2358        Self: DataActorNative,
2359        Self: 'static + Debug + Sized,
2360    {
2361        DataActorCore::unsubscribe_instrument_close(
2362            self.core_mut(),
2363            instrument_id,
2364            client_id,
2365            params,
2366        );
2367    }
2368
2369    /// Unsubscribe from streaming [`OptionChainSlice`] snapshots for the option `series_id`.
2370    fn unsubscribe_option_chain(&mut self, series_id: OptionSeriesId, client_id: Option<ClientId>)
2371    where
2372        Self: DataActorNative,
2373        Self: 'static + Debug + Sized,
2374    {
2375        DataActorCore::unsubscribe_option_chain(self.core_mut(), series_id, client_id);
2376    }
2377
2378    #[cfg(feature = "defi")]
2379    /// Unsubscribe from streaming [`Block`] data for the `chain`.
2380    fn unsubscribe_blocks(
2381        &mut self,
2382        chain: Blockchain,
2383        client_id: Option<ClientId>,
2384        params: Option<Params>,
2385    ) where
2386        Self: DataActorNative,
2387        Self: 'static + Debug + Sized,
2388    {
2389        DataActorCore::unsubscribe_blocks(self.core_mut(), chain, client_id, params);
2390    }
2391
2392    #[cfg(feature = "defi")]
2393    /// Unsubscribe from streaming [`Pool`] definition updates for the AMM pool at the `instrument_id`.
2394    fn unsubscribe_pool(
2395        &mut self,
2396        instrument_id: InstrumentId,
2397        client_id: Option<ClientId>,
2398        params: Option<Params>,
2399    ) where
2400        Self: DataActorNative,
2401        Self: 'static + Debug + Sized,
2402    {
2403        DataActorCore::unsubscribe_pool(self.core_mut(), instrument_id, client_id, params);
2404    }
2405
2406    #[cfg(feature = "defi")]
2407    /// Unsubscribe from streaming [`PoolSwap`] data for the `instrument_id`.
2408    fn unsubscribe_pool_swaps(
2409        &mut self,
2410        instrument_id: InstrumentId,
2411        client_id: Option<ClientId>,
2412        params: Option<Params>,
2413    ) where
2414        Self: DataActorNative,
2415        Self: 'static + Debug + Sized,
2416    {
2417        DataActorCore::unsubscribe_pool_swaps(self.core_mut(), instrument_id, client_id, params);
2418    }
2419
2420    #[cfg(feature = "defi")]
2421    /// Unsubscribe from streaming [`PoolLiquidityUpdate`] data for the `instrument_id`.
2422    fn unsubscribe_pool_liquidity_updates(
2423        &mut self,
2424        instrument_id: InstrumentId,
2425        client_id: Option<ClientId>,
2426        params: Option<Params>,
2427    ) where
2428        Self: DataActorNative,
2429        Self: 'static + Debug + Sized,
2430    {
2431        DataActorCore::unsubscribe_pool_liquidity_updates(
2432            self.core_mut(),
2433            instrument_id,
2434            client_id,
2435            params,
2436        );
2437    }
2438
2439    #[cfg(feature = "defi")]
2440    /// Unsubscribe from streaming [`PoolFeeCollect`] data for the `instrument_id`.
2441    fn unsubscribe_pool_fee_collects(
2442        &mut self,
2443        instrument_id: InstrumentId,
2444        client_id: Option<ClientId>,
2445        params: Option<Params>,
2446    ) where
2447        Self: DataActorNative,
2448        Self: 'static + Debug + Sized,
2449    {
2450        DataActorCore::unsubscribe_pool_fee_collects(
2451            self.core_mut(),
2452            instrument_id,
2453            client_id,
2454            params,
2455        );
2456    }
2457
2458    #[cfg(feature = "defi")]
2459    /// Unsubscribe from streaming [`PoolFlash`] events for the given `instrument_id`.
2460    fn unsubscribe_pool_flash_events(
2461        &mut self,
2462        instrument_id: InstrumentId,
2463        client_id: Option<ClientId>,
2464        params: Option<Params>,
2465    ) where
2466        Self: DataActorNative,
2467        Self: 'static + Debug + Sized,
2468    {
2469        DataActorCore::unsubscribe_pool_flash_events(
2470            self.core_mut(),
2471            instrument_id,
2472            client_id,
2473            params,
2474        );
2475    }
2476
2477    /// Request historical custom data of the given `data_type`.
2478    ///
2479    /// # Errors
2480    ///
2481    /// Returns an error if input parameters are invalid.
2482    fn request_data(
2483        &mut self,
2484        data_type: DataType,
2485        client_id: ClientId,
2486        start: Option<Timestamp>,
2487        end: Option<Timestamp>,
2488        limit: Option<NonZeroUsize>,
2489        params: Option<Params>,
2490    ) -> anyhow::Result<UUID4>
2491    where
2492        Self: DataActorNative,
2493        Self: 'static + Debug + Sized,
2494    {
2495        let actor_id = self.core().actor_id().inner();
2496        let handler = ShareableMessageHandler::from_typed(move |resp: &CustomDataResponse| {
2497            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2498                actor.handle_data_response(resp);
2499            } else {
2500                log::error!("Actor {actor_id} not found for data response handling");
2501            }
2502        });
2503
2504        DataActorCore::request_data(
2505            self.core_mut(),
2506            data_type,
2507            client_id,
2508            start,
2509            end,
2510            limit,
2511            params,
2512            handler,
2513        )
2514    }
2515
2516    /// Request historical [`InstrumentResponse`] data for the given `instrument_id`.
2517    ///
2518    /// # Errors
2519    ///
2520    /// Returns an error if input parameters are invalid.
2521    fn request_instrument(
2522        &mut self,
2523        instrument_id: InstrumentId,
2524        start: Option<Timestamp>,
2525        end: Option<Timestamp>,
2526        client_id: Option<ClientId>,
2527        params: Option<Params>,
2528    ) -> anyhow::Result<UUID4>
2529    where
2530        Self: DataActorNative,
2531        Self: 'static + Debug + Sized,
2532    {
2533        let actor_id = self.core().actor_id().inner();
2534        let handler = ShareableMessageHandler::from_typed(move |resp: &InstrumentResponse| {
2535            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2536                actor.handle_instrument_response(resp);
2537            } else {
2538                log::error!("Actor {actor_id} not found for instrument response handling");
2539            }
2540        });
2541
2542        DataActorCore::request_instrument(
2543            self.core_mut(),
2544            instrument_id,
2545            start,
2546            end,
2547            client_id,
2548            params,
2549            handler,
2550        )
2551    }
2552
2553    /// Request historical [`InstrumentsResponse`] definitions for the optional `venue`.
2554    ///
2555    /// # Errors
2556    ///
2557    /// Returns an error if input parameters are invalid.
2558    fn request_instruments(
2559        &mut self,
2560        venue: Option<Venue>,
2561        start: Option<Timestamp>,
2562        end: Option<Timestamp>,
2563        client_id: Option<ClientId>,
2564        params: Option<Params>,
2565    ) -> anyhow::Result<UUID4>
2566    where
2567        Self: DataActorNative,
2568        Self: 'static + Debug + Sized,
2569    {
2570        let actor_id = self.core().actor_id().inner();
2571        let handler = ShareableMessageHandler::from_typed(move |resp: &InstrumentsResponse| {
2572            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2573                actor.handle_instruments_response(resp);
2574            } else {
2575                log::error!("Actor {actor_id} not found for instruments response handling");
2576            }
2577        });
2578
2579        DataActorCore::request_instruments(
2580            self.core_mut(),
2581            venue,
2582            start,
2583            end,
2584            client_id,
2585            params,
2586            handler,
2587        )
2588    }
2589
2590    /// Request an [`OrderBook`] snapshot for the given `instrument_id`.
2591    ///
2592    /// # Errors
2593    ///
2594    /// Returns an error if input parameters are invalid.
2595    fn request_book_snapshot(
2596        &mut self,
2597        instrument_id: InstrumentId,
2598        depth: Option<NonZeroUsize>,
2599        client_id: Option<ClientId>,
2600        params: Option<Params>,
2601    ) -> anyhow::Result<UUID4>
2602    where
2603        Self: DataActorNative,
2604        Self: 'static + Debug + Sized,
2605    {
2606        let actor_id = self.core().actor_id().inner();
2607        let handler = ShareableMessageHandler::from_typed(move |resp: &BookResponse| {
2608            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2609                actor.handle_book_response(resp);
2610            } else {
2611                log::error!("Actor {actor_id} not found for book response handling");
2612            }
2613        });
2614
2615        DataActorCore::request_book_snapshot(
2616            self.core_mut(),
2617            instrument_id,
2618            depth,
2619            client_id,
2620            params,
2621            handler,
2622        )
2623    }
2624
2625    /// Request historical [`OrderBookDelta`] data for the given `instrument_id`.
2626    ///
2627    /// # Errors
2628    ///
2629    /// Returns an error if input parameters are invalid.
2630    fn request_book_deltas(
2631        &mut self,
2632        instrument_id: InstrumentId,
2633        start: Option<Timestamp>,
2634        end: Option<Timestamp>,
2635        limit: Option<NonZeroUsize>,
2636        client_id: Option<ClientId>,
2637        params: Option<Params>,
2638    ) -> anyhow::Result<UUID4>
2639    where
2640        Self: DataActorNative,
2641        Self: 'static + Debug + Sized,
2642    {
2643        let actor_id = self.core().actor_id().inner();
2644        let handler = ShareableMessageHandler::from_typed(move |resp: &BookDeltasResponse| {
2645            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2646                actor.handle_book_deltas_response(resp);
2647            } else {
2648                log::error!("Actor {actor_id} not found for book deltas response handling");
2649            }
2650        });
2651
2652        DataActorCore::request_book_deltas(
2653            self.core_mut(),
2654            instrument_id,
2655            start,
2656            end,
2657            limit,
2658            client_id,
2659            params,
2660            handler,
2661        )
2662    }
2663
2664    /// Request historical [`OrderBookDepth10`] data for the given `instrument_id`.
2665    ///
2666    /// # Errors
2667    ///
2668    /// Returns an error if input parameters are invalid.
2669    #[expect(clippy::too_many_arguments)]
2670    fn request_book_depth(
2671        &mut self,
2672        instrument_id: InstrumentId,
2673        start: Option<Timestamp>,
2674        end: Option<Timestamp>,
2675        limit: Option<NonZeroUsize>,
2676        depth: Option<NonZeroUsize>,
2677        client_id: Option<ClientId>,
2678        params: Option<Params>,
2679    ) -> anyhow::Result<UUID4>
2680    where
2681        Self: DataActorNative,
2682        Self: 'static + Debug + Sized,
2683    {
2684        let actor_id = self.core().actor_id().inner();
2685        let handler = ShareableMessageHandler::from_typed(move |resp: &BookDepthResponse| {
2686            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2687                actor.handle_book_depth_response(resp);
2688            } else {
2689                log::error!("Actor {actor_id} not found for book depth response handling");
2690            }
2691        });
2692
2693        DataActorCore::request_book_depth(
2694            self.core_mut(),
2695            instrument_id,
2696            start,
2697            end,
2698            limit,
2699            depth,
2700            client_id,
2701            params,
2702            handler,
2703        )
2704    }
2705
2706    /// Request historical [`QuoteTick`] data for the given `instrument_id`.
2707    ///
2708    /// # Errors
2709    ///
2710    /// Returns an error if input parameters are invalid.
2711    fn request_quotes(
2712        &mut self,
2713        instrument_id: InstrumentId,
2714        start: Option<Timestamp>,
2715        end: Option<Timestamp>,
2716        limit: Option<NonZeroUsize>,
2717        client_id: Option<ClientId>,
2718        params: Option<Params>,
2719    ) -> anyhow::Result<UUID4>
2720    where
2721        Self: DataActorNative,
2722        Self: 'static + Debug + Sized,
2723    {
2724        let actor_id = self.core().actor_id().inner();
2725        let handler = ShareableMessageHandler::from_typed(move |resp: &QuotesResponse| {
2726            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2727                actor.handle_quotes_response(resp);
2728            } else {
2729                log::error!("Actor {actor_id} not found for quotes response handling");
2730            }
2731        });
2732
2733        DataActorCore::request_quotes(
2734            self.core_mut(),
2735            instrument_id,
2736            start,
2737            end,
2738            limit,
2739            client_id,
2740            params,
2741            handler,
2742        )
2743    }
2744
2745    /// Request historical [`TradeTick`] data for the given `instrument_id`.
2746    ///
2747    /// # Errors
2748    ///
2749    /// Returns an error if input parameters are invalid.
2750    fn request_trades(
2751        &mut self,
2752        instrument_id: InstrumentId,
2753        start: Option<Timestamp>,
2754        end: Option<Timestamp>,
2755        limit: Option<NonZeroUsize>,
2756        client_id: Option<ClientId>,
2757        params: Option<Params>,
2758    ) -> anyhow::Result<UUID4>
2759    where
2760        Self: DataActorNative,
2761        Self: 'static + Debug + Sized,
2762    {
2763        let actor_id = self.core().actor_id().inner();
2764        let handler = ShareableMessageHandler::from_typed(move |resp: &TradesResponse| {
2765            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2766                actor.handle_trades_response(resp);
2767            } else {
2768                log::error!("Actor {actor_id} not found for trades response handling");
2769            }
2770        });
2771
2772        DataActorCore::request_trades(
2773            self.core_mut(),
2774            instrument_id,
2775            start,
2776            end,
2777            limit,
2778            client_id,
2779            params,
2780            handler,
2781        )
2782    }
2783
2784    /// Request historical [`Bar`] data for the given `bar_type`.
2785    ///
2786    /// # Errors
2787    ///
2788    /// Returns an error if input parameters are invalid.
2789    fn request_bars(
2790        &mut self,
2791        bar_type: BarType,
2792        start: Option<Timestamp>,
2793        end: Option<Timestamp>,
2794        limit: Option<NonZeroUsize>,
2795        client_id: Option<ClientId>,
2796        params: Option<Params>,
2797    ) -> anyhow::Result<UUID4>
2798    where
2799        Self: DataActorNative,
2800        Self: 'static + Debug + Sized,
2801    {
2802        let actor_id = self.core().actor_id().inner();
2803        let handler = ShareableMessageHandler::from_typed(move |resp: &BarsResponse| {
2804            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2805                actor.handle_bars_response(resp);
2806            } else {
2807                log::error!("Actor {actor_id} not found for bars response handling");
2808            }
2809        });
2810
2811        DataActorCore::request_bars(
2812            self.core_mut(),
2813            bar_type,
2814            start,
2815            end,
2816            limit,
2817            client_id,
2818            params,
2819            handler,
2820        )
2821    }
2822
2823    /// Request historical [`FundingRateUpdate`] data for the given `instrument_id`.
2824    ///
2825    /// # Errors
2826    ///
2827    /// Returns an error if input parameters are invalid.
2828    fn request_funding_rates(
2829        &mut self,
2830        instrument_id: InstrumentId,
2831        start: Option<Timestamp>,
2832        end: Option<Timestamp>,
2833        limit: Option<NonZeroUsize>,
2834        client_id: Option<ClientId>,
2835        params: Option<Params>,
2836    ) -> anyhow::Result<UUID4>
2837    where
2838        Self: DataActorNative,
2839        Self: 'static + Debug + Sized,
2840    {
2841        let actor_id = self.core().actor_id().inner();
2842        let handler = ShareableMessageHandler::from_typed(move |resp: &FundingRatesResponse| {
2843            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2844                actor.handle_funding_rates_response(resp);
2845            } else {
2846                log::error!("Actor {actor_id} not found for funding rates response handling");
2847            }
2848        });
2849
2850        DataActorCore::request_funding_rates(
2851            self.core_mut(),
2852            instrument_id,
2853            start,
2854            end,
2855            limit,
2856            client_id,
2857            params,
2858            handler,
2859        )
2860    }
2861
2862    /// Requests reconnect of one socket endpoint owned by `client_id`.
2863    ///
2864    /// This is a fire-and-observe command. A successful return means the live runner queued the
2865    /// request. [`SocketStateChanged`] events for the same endpoint report whether the transport
2866    /// enters reconnect mode and later recovers.
2867    ///
2868    /// # Errors
2869    ///
2870    /// Returns an error if the actor is not registered, the endpoint label is invalid, the live
2871    /// runner is unavailable, or the runner command channel is closed.
2872    #[cfg(feature = "live")]
2873    fn reconnect_socket(&self, client_id: ClientId, endpoint: &str) -> anyhow::Result<()>
2874    where
2875        Self: DataActorNative,
2876    {
2877        DataActorCore::reconnect_socket(self.core(), client_id, endpoint)
2878    }
2879}
2880
2881// Blanket implementation: any DataActor automatically implements Actor
2882impl<T> Actor for T
2883where
2884    T: DataActor + DataActorNative + Debug + 'static,
2885{
2886    fn id(&self) -> Ustr {
2887        self.core().actor_id.inner()
2888    }
2889
2890    #[allow(unused_variables)]
2891    fn handle(&mut self, msg: &dyn Any) {
2892        // Default empty implementation - concrete actors can override if needed
2893    }
2894
2895    fn as_any(&self) -> &dyn Any {
2896        self
2897    }
2898}
2899
2900// Blanket implementation: any DataActor automatically implements Component
2901impl<T> Component for T
2902where
2903    T: DataActor + DataActorNative + Debug + 'static,
2904{
2905    fn component_id(&self) -> ComponentId {
2906        ComponentId::from(self.core().actor_id)
2907    }
2908
2909    fn release_subscriptions(&mut self) {
2910        self.core_mut().unsubscribe_all();
2911    }
2912
2913    fn state(&self) -> ComponentState {
2914        self.core().state
2915    }
2916
2917    fn transition_state(&mut self, trigger: ComponentTrigger) -> anyhow::Result<()> {
2918        let core = self.core_mut();
2919        core.state = core.state.transition(&trigger)?;
2920        log::info!(
2921            component = core.actor_id.inner().as_str();
2922            "{}",
2923            core.state.variant_name()
2924        );
2925        Ok(())
2926    }
2927
2928    fn register(
2929        &mut self,
2930        trader_id: TraderId,
2931        clock: Rc<RefCell<dyn Clock>>,
2932        cache: Rc<RefCell<Cache>>,
2933    ) -> anyhow::Result<()> {
2934        DataActorCore::register(self.core_mut(), trader_id, clock.clone(), cache)?;
2935
2936        // Register default time event handler for this actor
2937        let actor_id = self.core().actor_id().inner();
2938        let callback = TimeEventCallback::from(move |event: TimeEvent| {
2939            if let Some(mut actor) = try_get_actor_unchecked::<Self>(&actor_id) {
2940                actor.handle_time_event(&event);
2941            } else {
2942                log::error!("Actor {actor_id} not found for time event handling");
2943            }
2944        });
2945
2946        clock.borrow_mut().register_default_handler(callback);
2947
2948        self.initialize()
2949    }
2950
2951    fn on_start(&mut self) -> anyhow::Result<()> {
2952        DataActor::on_start(self)
2953    }
2954
2955    fn on_stop(&mut self) -> anyhow::Result<()> {
2956        DataActor::on_stop(self)
2957    }
2958
2959    fn on_resume(&mut self) -> anyhow::Result<()> {
2960        DataActor::on_resume(self)
2961    }
2962
2963    fn on_degrade(&mut self) -> anyhow::Result<()> {
2964        DataActor::on_degrade(self)
2965    }
2966
2967    fn on_fault(&mut self) -> anyhow::Result<()> {
2968        DataActor::on_fault(self)
2969    }
2970
2971    fn on_reset(&mut self) -> anyhow::Result<()> {
2972        DataActor::on_reset(self)
2973    }
2974
2975    fn on_dispose(&mut self) -> anyhow::Result<()> {
2976        DataActor::on_dispose(self)
2977    }
2978}
2979
2980/// Core functionality for all actors.
2981#[derive(Clone)]
2982#[allow(
2983    dead_code,
2984    reason = "TODO: Under development (pending_requests, signal_classes)"
2985)]
2986pub struct DataActorCore {
2987    /// The actor identifier.
2988    pub actor_id: ActorId,
2989    /// The actors configuration.
2990    pub config: DataActorConfig,
2991    trader_id: Option<TraderId>,
2992    clock: Option<Rc<RefCell<dyn Clock>>>, // Wired up on registration
2993    cache: Option<Rc<RefCell<Cache>>>,     // Wired up on registration
2994    state: ComponentState,
2995    topic_handlers: AHashMap<MStr<Pattern>, ShareableMessageHandler>,
2996    instrument_handlers: AHashMap<MStr<Pattern>, TypedHandler<InstrumentAny>>,
2997    deltas_handlers: AHashMap<MStr<Pattern>, TypedHandler<OrderBookDeltas>>,
2998    depth10_handlers: AHashMap<MStr<Pattern>, TypedHandler<OrderBookDepth10>>,
2999    book_handlers: AHashMap<MStr<Topic>, TypedHandler<OrderBook>>,
3000    quote_handlers: AHashMap<MStr<Topic>, TypedHandler<QuoteTick>>,
3001    trade_handlers: AHashMap<MStr<Topic>, TypedHandler<TradeTick>>,
3002    bar_handlers: AHashMap<MStr<Topic>, TypedHandler<Bar>>,
3003    mark_price_handlers: AHashMap<MStr<Topic>, TypedHandler<MarkPriceUpdate>>,
3004    index_price_handlers: AHashMap<MStr<Topic>, TypedHandler<IndexPriceUpdate>>,
3005    funding_rate_handlers: AHashMap<MStr<Topic>, TypedHandler<FundingRateUpdate>>,
3006    option_greeks_handlers: AHashMap<MStr<Topic>, TypedHandler<OptionGreeks>>,
3007    option_chain_handlers: AHashMap<MStr<Topic>, TypedHandler<OptionChainSlice>>,
3008    #[cfg(feature = "defi")]
3009    block_handlers: AHashMap<MStr<Topic>, TypedHandler<Block>>,
3010    #[cfg(feature = "defi")]
3011    pool_handlers: AHashMap<MStr<Topic>, TypedHandler<Pool>>,
3012    #[cfg(feature = "defi")]
3013    pool_swap_handlers: AHashMap<MStr<Topic>, TypedHandler<PoolSwap>>,
3014    #[cfg(feature = "defi")]
3015    pool_liquidity_handlers: AHashMap<MStr<Topic>, TypedHandler<PoolLiquidityUpdate>>,
3016    #[cfg(feature = "defi")]
3017    pool_collect_handlers: AHashMap<MStr<Topic>, TypedHandler<PoolFeeCollect>>,
3018    #[cfg(feature = "defi")]
3019    pool_flash_handlers: AHashMap<MStr<Topic>, TypedHandler<PoolFlash>>,
3020    warning_events: AHashSet<String>, // TODO: TBD
3021    pending_requests: AHashMap<UUID4, Option<RequestCallback>>,
3022    signal_classes: AHashMap<String, String>,
3023    indicators: Indicators,
3024}
3025
3026impl Debug for DataActorCore {
3027    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3028        f.debug_struct(stringify!(DataActorCore))
3029            .field("actor_id", &self.actor_id)
3030            .field("config", &self.config)
3031            .field("state", &self.state)
3032            .field("trader_id", &self.trader_id)
3033            .finish()
3034    }
3035}
3036
3037impl DataActorCore {
3038    /// Adds a subscription handler for the `topic`.
3039    ///
3040    //// Logs a warning if the actor is already subscribed to the topic.
3041    pub(crate) fn add_subscription_any(
3042        &mut self,
3043        topic: MStr<Topic>,
3044        handler: ShareableMessageHandler,
3045        priority: Option<u32>,
3046    ) {
3047        let pattern: MStr<Pattern> = topic.into();
3048        if self.topic_handlers.contains_key(&pattern) {
3049            log::warn!(
3050                "Actor {} attempted duplicate subscription to topic '{topic}'",
3051                self.actor_id,
3052            );
3053            return;
3054        }
3055
3056        self.topic_handlers.insert(pattern, handler.clone());
3057        msgbus::subscribe_any(pattern, handler, priority);
3058    }
3059
3060    /// Removes a subscription handler for the `topic` if present.
3061    ///
3062    /// Logs a warning if the actor is not currently subscribed to the topic.
3063    pub(crate) fn remove_subscription_any(&mut self, topic: MStr<Topic>) {
3064        let pattern: MStr<Pattern> = topic.into();
3065        if let Some(handler) = self.topic_handlers.remove(&pattern) {
3066            msgbus::unsubscribe_any(pattern, &handler);
3067        } else {
3068            log::warn!(
3069                "Actor {} attempted to unsubscribe from topic '{topic}' when not subscribed",
3070                self.actor_id,
3071            );
3072        }
3073    }
3074
3075    pub(crate) fn add_quote_subscription(
3076        &mut self,
3077        topic: MStr<Topic>,
3078        handler: TypedHandler<QuoteTick>,
3079    ) {
3080        if self.quote_handlers.contains_key(&topic) {
3081            log::warn!(
3082                "Actor {} attempted duplicate quote subscription to '{topic}'",
3083                self.actor_id
3084            );
3085            return;
3086        }
3087        self.quote_handlers.insert(topic, handler.clone());
3088        msgbus::subscribe_quotes(topic.into(), handler, None);
3089    }
3090
3091    #[allow(dead_code)]
3092    pub(crate) fn remove_quote_subscription(&mut self, topic: MStr<Topic>) {
3093        if let Some(handler) = self.quote_handlers.remove(&topic) {
3094            msgbus::unsubscribe_quotes(topic.into(), &handler);
3095        }
3096    }
3097
3098    pub(crate) fn add_trade_subscription(
3099        &mut self,
3100        topic: MStr<Topic>,
3101        handler: TypedHandler<TradeTick>,
3102    ) {
3103        if self.trade_handlers.contains_key(&topic) {
3104            log::warn!(
3105                "Actor {} attempted duplicate trade subscription to '{topic}'",
3106                self.actor_id
3107            );
3108            return;
3109        }
3110        self.trade_handlers.insert(topic, handler.clone());
3111        msgbus::subscribe_trades(topic.into(), handler, None);
3112    }
3113
3114    #[allow(dead_code)]
3115    pub(crate) fn remove_trade_subscription(&mut self, topic: MStr<Topic>) {
3116        if let Some(handler) = self.trade_handlers.remove(&topic) {
3117            msgbus::unsubscribe_trades(topic.into(), &handler);
3118        }
3119    }
3120
3121    pub(crate) fn add_bar_subscription(&mut self, topic: MStr<Topic>, handler: TypedHandler<Bar>) {
3122        if self.bar_handlers.contains_key(&topic) {
3123            log::warn!(
3124                "Actor {} attempted duplicate bar subscription to '{topic}'",
3125                self.actor_id
3126            );
3127            return;
3128        }
3129        self.bar_handlers.insert(topic, handler.clone());
3130        msgbus::subscribe_bars(topic.into(), handler, None);
3131    }
3132
3133    #[allow(dead_code)]
3134    pub(crate) fn remove_bar_subscription(&mut self, topic: MStr<Topic>) {
3135        if let Some(handler) = self.bar_handlers.remove(&topic) {
3136            msgbus::unsubscribe_bars(topic.into(), &handler);
3137        }
3138    }
3139
3140    pub(crate) fn add_deltas_subscription(
3141        &mut self,
3142        pattern: MStr<Pattern>,
3143        handler: TypedHandler<OrderBookDeltas>,
3144    ) {
3145        if self.deltas_handlers.contains_key(&pattern) {
3146            log::warn!(
3147                "Actor {} attempted duplicate deltas subscription to '{pattern}'",
3148                self.actor_id
3149            );
3150            return;
3151        }
3152        self.deltas_handlers.insert(pattern, handler.clone());
3153        msgbus::subscribe_book_deltas(pattern, handler, None);
3154    }
3155
3156    #[allow(dead_code)]
3157    pub(crate) fn remove_deltas_subscription(&mut self, pattern: MStr<Pattern>) {
3158        if let Some(handler) = self.deltas_handlers.remove(&pattern) {
3159            msgbus::unsubscribe_book_deltas(pattern, &handler);
3160        }
3161    }
3162
3163    pub(crate) fn add_depth10_subscription(
3164        &mut self,
3165        pattern: MStr<Pattern>,
3166        handler: TypedHandler<OrderBookDepth10>,
3167    ) {
3168        if self.depth10_handlers.contains_key(&pattern) {
3169            log::warn!(
3170                "Actor {} attempted duplicate depth10 subscription to '{pattern}'",
3171                self.actor_id
3172            );
3173            return;
3174        }
3175        self.depth10_handlers.insert(pattern, handler.clone());
3176        msgbus::subscribe_book_depth10(pattern, handler, None);
3177    }
3178
3179    pub(crate) fn remove_depth10_subscription(&mut self, pattern: MStr<Pattern>) {
3180        if let Some(handler) = self.depth10_handlers.remove(&pattern) {
3181            msgbus::unsubscribe_book_depth10(pattern, &handler);
3182        }
3183    }
3184
3185    pub(crate) fn add_instrument_subscription(
3186        &mut self,
3187        pattern: MStr<Pattern>,
3188        handler: TypedHandler<InstrumentAny>,
3189    ) {
3190        if self.instrument_handlers.contains_key(&pattern) {
3191            log::warn!(
3192                "Actor {} attempted duplicate instrument subscription to '{pattern}'",
3193                self.actor_id
3194            );
3195            return;
3196        }
3197        self.instrument_handlers.insert(pattern, handler.clone());
3198        msgbus::subscribe_instruments(pattern, handler, None);
3199    }
3200
3201    #[allow(dead_code)]
3202    pub(crate) fn remove_instrument_subscription(&mut self, pattern: MStr<Pattern>) {
3203        if let Some(handler) = self.instrument_handlers.remove(&pattern) {
3204            msgbus::unsubscribe_instruments(pattern, &handler);
3205        }
3206    }
3207
3208    pub(crate) fn add_instrument_close_subscription(
3209        &mut self,
3210        topic: MStr<Topic>,
3211        handler: ShareableMessageHandler,
3212    ) {
3213        let pattern: MStr<Pattern> = topic.into();
3214        if self.topic_handlers.contains_key(&pattern) {
3215            log::warn!(
3216                "Actor {} attempted duplicate instrument close subscription to '{topic}'",
3217                self.actor_id
3218            );
3219            return;
3220        }
3221        self.topic_handlers.insert(pattern, handler.clone());
3222        msgbus::subscribe_any(pattern, handler, None);
3223    }
3224
3225    #[allow(dead_code)]
3226    pub(crate) fn remove_instrument_close_subscription(&mut self, topic: MStr<Topic>) {
3227        let pattern: MStr<Pattern> = topic.into();
3228        if let Some(handler) = self.topic_handlers.remove(&pattern) {
3229            msgbus::unsubscribe_any(pattern, &handler);
3230        }
3231    }
3232
3233    pub(crate) fn add_book_snapshot_subscription(
3234        &mut self,
3235        topic: MStr<Topic>,
3236        handler: TypedHandler<OrderBook>,
3237    ) {
3238        if self.book_handlers.contains_key(&topic) {
3239            log::warn!(
3240                "Actor {} attempted duplicate book snapshot subscription to '{topic}'",
3241                self.actor_id
3242            );
3243            return;
3244        }
3245        self.book_handlers.insert(topic, handler.clone());
3246        msgbus::subscribe_book_snapshots(topic.into(), handler, None);
3247    }
3248
3249    #[allow(dead_code)]
3250    pub(crate) fn remove_book_snapshot_subscription(&mut self, topic: MStr<Topic>) {
3251        if let Some(handler) = self.book_handlers.remove(&topic) {
3252            msgbus::unsubscribe_book_snapshots(topic.into(), &handler);
3253        }
3254    }
3255
3256    pub(crate) fn add_mark_price_subscription(
3257        &mut self,
3258        topic: MStr<Topic>,
3259        handler: TypedHandler<MarkPriceUpdate>,
3260    ) {
3261        if self.mark_price_handlers.contains_key(&topic) {
3262            log::warn!(
3263                "Actor {} attempted duplicate mark price subscription to '{topic}'",
3264                self.actor_id
3265            );
3266            return;
3267        }
3268        self.mark_price_handlers.insert(topic, handler.clone());
3269        msgbus::subscribe_mark_prices(topic.into(), handler, None);
3270    }
3271
3272    #[allow(dead_code)]
3273    pub(crate) fn remove_mark_price_subscription(&mut self, topic: MStr<Topic>) {
3274        if let Some(handler) = self.mark_price_handlers.remove(&topic) {
3275            msgbus::unsubscribe_mark_prices(topic.into(), &handler);
3276        }
3277    }
3278
3279    pub(crate) fn add_index_price_subscription(
3280        &mut self,
3281        topic: MStr<Topic>,
3282        handler: TypedHandler<IndexPriceUpdate>,
3283    ) {
3284        if self.index_price_handlers.contains_key(&topic) {
3285            log::warn!(
3286                "Actor {} attempted duplicate index price subscription to '{topic}'",
3287                self.actor_id
3288            );
3289            return;
3290        }
3291        self.index_price_handlers.insert(topic, handler.clone());
3292        msgbus::subscribe_index_prices(topic.into(), handler, None);
3293    }
3294
3295    #[allow(dead_code)]
3296    pub(crate) fn remove_index_price_subscription(&mut self, topic: MStr<Topic>) {
3297        if let Some(handler) = self.index_price_handlers.remove(&topic) {
3298            msgbus::unsubscribe_index_prices(topic.into(), &handler);
3299        }
3300    }
3301
3302    pub(crate) fn add_funding_rate_subscription(
3303        &mut self,
3304        topic: MStr<Topic>,
3305        handler: TypedHandler<FundingRateUpdate>,
3306    ) {
3307        if self.funding_rate_handlers.contains_key(&topic) {
3308            log::warn!(
3309                "Actor {} attempted duplicate funding rate subscription to '{topic}'",
3310                self.actor_id
3311            );
3312            return;
3313        }
3314        self.funding_rate_handlers.insert(topic, handler.clone());
3315        msgbus::subscribe_funding_rates(topic.into(), handler, None);
3316    }
3317
3318    #[allow(dead_code)]
3319    pub(crate) fn remove_funding_rate_subscription(&mut self, topic: MStr<Topic>) {
3320        if let Some(handler) = self.funding_rate_handlers.remove(&topic) {
3321            msgbus::unsubscribe_funding_rates(topic.into(), &handler);
3322        }
3323    }
3324
3325    pub(crate) fn add_option_greeks_subscription(
3326        &mut self,
3327        topic: MStr<Topic>,
3328        handler: TypedHandler<OptionGreeks>,
3329    ) {
3330        if self.option_greeks_handlers.contains_key(&topic) {
3331            log::warn!(
3332                "Actor {} attempted duplicate option greeks subscription to '{topic}'",
3333                self.actor_id
3334            );
3335            return;
3336        }
3337        self.option_greeks_handlers.insert(topic, handler.clone());
3338        msgbus::subscribe_option_greeks(topic.into(), handler, None);
3339    }
3340
3341    #[allow(dead_code)]
3342    pub(crate) fn remove_option_greeks_subscription(&mut self, topic: MStr<Topic>) {
3343        if let Some(handler) = self.option_greeks_handlers.remove(&topic) {
3344            msgbus::unsubscribe_option_greeks(topic.into(), &handler);
3345        }
3346    }
3347
3348    pub(crate) fn add_option_chain_subscription(
3349        &mut self,
3350        topic: MStr<Topic>,
3351        handler: TypedHandler<OptionChainSlice>,
3352    ) {
3353        if self.option_chain_handlers.contains_key(&topic) {
3354            log::warn!(
3355                "Actor {} attempted duplicate option chain subscription to '{topic}'",
3356                self.actor_id
3357            );
3358            return;
3359        }
3360        self.option_chain_handlers.insert(topic, handler.clone());
3361        msgbus::subscribe_option_chain(topic.into(), handler, None);
3362    }
3363
3364    pub(crate) fn remove_option_chain_subscription(&mut self, topic: MStr<Topic>) {
3365        if let Some(handler) = self.option_chain_handlers.remove(&topic) {
3366            msgbus::unsubscribe_option_chain(topic.into(), &handler);
3367        }
3368    }
3369
3370    #[cfg(feature = "defi")]
3371    pub(crate) fn add_block_subscription(
3372        &mut self,
3373        topic: MStr<Topic>,
3374        handler: TypedHandler<Block>,
3375    ) {
3376        if self.block_handlers.contains_key(&topic) {
3377            log::warn!(
3378                "Actor {} attempted duplicate block subscription to '{topic}'",
3379                self.actor_id
3380            );
3381            return;
3382        }
3383        self.block_handlers.insert(topic, handler.clone());
3384        msgbus::subscribe_defi_blocks(topic.into(), handler, None);
3385    }
3386
3387    #[cfg(feature = "defi")]
3388    #[allow(dead_code)]
3389    pub(crate) fn remove_block_subscription(&mut self, topic: MStr<Topic>) {
3390        if let Some(handler) = self.block_handlers.remove(&topic) {
3391            msgbus::unsubscribe_defi_blocks(topic.into(), &handler);
3392        }
3393    }
3394
3395    #[cfg(feature = "defi")]
3396    pub(crate) fn add_pool_subscription(
3397        &mut self,
3398        topic: MStr<Topic>,
3399        handler: TypedHandler<Pool>,
3400    ) {
3401        if self.pool_handlers.contains_key(&topic) {
3402            log::warn!(
3403                "Actor {} attempted duplicate pool subscription to '{topic}'",
3404                self.actor_id
3405            );
3406            return;
3407        }
3408        self.pool_handlers.insert(topic, handler.clone());
3409        msgbus::subscribe_defi_pools(topic.into(), handler, None);
3410    }
3411
3412    #[cfg(feature = "defi")]
3413    #[allow(dead_code)]
3414    pub(crate) fn remove_pool_subscription(&mut self, topic: MStr<Topic>) {
3415        if let Some(handler) = self.pool_handlers.remove(&topic) {
3416            msgbus::unsubscribe_defi_pools(topic.into(), &handler);
3417        }
3418    }
3419
3420    #[cfg(feature = "defi")]
3421    pub(crate) fn add_pool_swap_subscription(
3422        &mut self,
3423        topic: MStr<Topic>,
3424        handler: TypedHandler<PoolSwap>,
3425    ) {
3426        if self.pool_swap_handlers.contains_key(&topic) {
3427            log::warn!(
3428                "Actor {} attempted duplicate pool swap subscription to '{topic}'",
3429                self.actor_id
3430            );
3431            return;
3432        }
3433        self.pool_swap_handlers.insert(topic, handler.clone());
3434        msgbus::subscribe_defi_swaps(topic.into(), handler, None);
3435    }
3436
3437    #[cfg(feature = "defi")]
3438    #[allow(dead_code)]
3439    pub(crate) fn remove_pool_swap_subscription(&mut self, topic: MStr<Topic>) {
3440        if let Some(handler) = self.pool_swap_handlers.remove(&topic) {
3441            msgbus::unsubscribe_defi_swaps(topic.into(), &handler);
3442        }
3443    }
3444
3445    #[cfg(feature = "defi")]
3446    pub(crate) fn add_pool_liquidity_subscription(
3447        &mut self,
3448        topic: MStr<Topic>,
3449        handler: TypedHandler<PoolLiquidityUpdate>,
3450    ) {
3451        if self.pool_liquidity_handlers.contains_key(&topic) {
3452            log::warn!(
3453                "Actor {} attempted duplicate pool liquidity subscription to '{topic}'",
3454                self.actor_id
3455            );
3456            return;
3457        }
3458        self.pool_liquidity_handlers.insert(topic, handler.clone());
3459        msgbus::subscribe_defi_liquidity(topic.into(), handler, None);
3460    }
3461
3462    #[cfg(feature = "defi")]
3463    #[allow(dead_code)]
3464    pub(crate) fn remove_pool_liquidity_subscription(&mut self, topic: MStr<Topic>) {
3465        if let Some(handler) = self.pool_liquidity_handlers.remove(&topic) {
3466            msgbus::unsubscribe_defi_liquidity(topic.into(), &handler);
3467        }
3468    }
3469
3470    #[cfg(feature = "defi")]
3471    pub(crate) fn add_pool_collect_subscription(
3472        &mut self,
3473        topic: MStr<Topic>,
3474        handler: TypedHandler<PoolFeeCollect>,
3475    ) {
3476        if self.pool_collect_handlers.contains_key(&topic) {
3477            log::warn!(
3478                "Actor {} attempted duplicate pool collect subscription to '{topic}'",
3479                self.actor_id
3480            );
3481            return;
3482        }
3483        self.pool_collect_handlers.insert(topic, handler.clone());
3484        msgbus::subscribe_defi_collects(topic.into(), handler, None);
3485    }
3486
3487    #[cfg(feature = "defi")]
3488    #[allow(dead_code)]
3489    pub(crate) fn remove_pool_collect_subscription(&mut self, topic: MStr<Topic>) {
3490        if let Some(handler) = self.pool_collect_handlers.remove(&topic) {
3491            msgbus::unsubscribe_defi_collects(topic.into(), &handler);
3492        }
3493    }
3494
3495    #[cfg(feature = "defi")]
3496    pub(crate) fn add_pool_flash_subscription(
3497        &mut self,
3498        topic: MStr<Topic>,
3499        handler: TypedHandler<PoolFlash>,
3500    ) {
3501        if self.pool_flash_handlers.contains_key(&topic) {
3502            log::warn!(
3503                "Actor {} attempted duplicate pool flash subscription to '{topic}'",
3504                self.actor_id
3505            );
3506            return;
3507        }
3508        self.pool_flash_handlers.insert(topic, handler.clone());
3509        msgbus::subscribe_defi_flash(topic.into(), handler, None);
3510    }
3511
3512    #[cfg(feature = "defi")]
3513    #[allow(dead_code)]
3514    pub(crate) fn remove_pool_flash_subscription(&mut self, topic: MStr<Topic>) {
3515        if let Some(handler) = self.pool_flash_handlers.remove(&topic) {
3516            msgbus::unsubscribe_defi_flash(topic.into(), &handler);
3517        }
3518    }
3519
3520    /// Removes every message bus subscription this actor installed.
3521    ///
3522    /// Called on disposal so retirement leaves no handler which would resolve an actor that
3523    /// deregistration has already removed.
3524    pub(crate) fn unsubscribe_all(&mut self) {
3525        for (pattern, handler) in std::mem::take(&mut self.topic_handlers) {
3526            msgbus::unsubscribe_any(pattern, &handler);
3527        }
3528
3529        for (pattern, handler) in std::mem::take(&mut self.instrument_handlers) {
3530            msgbus::unsubscribe_instruments(pattern, &handler);
3531        }
3532
3533        for (pattern, handler) in std::mem::take(&mut self.deltas_handlers) {
3534            msgbus::unsubscribe_book_deltas(pattern, &handler);
3535        }
3536
3537        for (pattern, handler) in std::mem::take(&mut self.depth10_handlers) {
3538            msgbus::unsubscribe_book_depth10(pattern, &handler);
3539        }
3540
3541        for (topic, handler) in std::mem::take(&mut self.book_handlers) {
3542            msgbus::unsubscribe_book_snapshots(topic.into(), &handler);
3543        }
3544
3545        for (topic, handler) in std::mem::take(&mut self.quote_handlers) {
3546            msgbus::unsubscribe_quotes(topic.into(), &handler);
3547        }
3548
3549        for (topic, handler) in std::mem::take(&mut self.trade_handlers) {
3550            msgbus::unsubscribe_trades(topic.into(), &handler);
3551        }
3552
3553        for (topic, handler) in std::mem::take(&mut self.bar_handlers) {
3554            msgbus::unsubscribe_bars(topic.into(), &handler);
3555        }
3556
3557        for (topic, handler) in std::mem::take(&mut self.mark_price_handlers) {
3558            msgbus::unsubscribe_mark_prices(topic.into(), &handler);
3559        }
3560
3561        for (topic, handler) in std::mem::take(&mut self.index_price_handlers) {
3562            msgbus::unsubscribe_index_prices(topic.into(), &handler);
3563        }
3564
3565        for (topic, handler) in std::mem::take(&mut self.funding_rate_handlers) {
3566            msgbus::unsubscribe_funding_rates(topic.into(), &handler);
3567        }
3568
3569        for (topic, handler) in std::mem::take(&mut self.option_greeks_handlers) {
3570            msgbus::unsubscribe_option_greeks(topic.into(), &handler);
3571        }
3572
3573        for (topic, handler) in std::mem::take(&mut self.option_chain_handlers) {
3574            msgbus::unsubscribe_option_chain(topic.into(), &handler);
3575        }
3576
3577        #[cfg(feature = "defi")]
3578        self.unsubscribe_all_defi();
3579    }
3580
3581    #[cfg(feature = "defi")]
3582    fn unsubscribe_all_defi(&mut self) {
3583        for (topic, handler) in std::mem::take(&mut self.block_handlers) {
3584            msgbus::unsubscribe_defi_blocks(topic.into(), &handler);
3585        }
3586
3587        for (topic, handler) in std::mem::take(&mut self.pool_handlers) {
3588            msgbus::unsubscribe_defi_pools(topic.into(), &handler);
3589        }
3590
3591        for (topic, handler) in std::mem::take(&mut self.pool_swap_handlers) {
3592            msgbus::unsubscribe_defi_swaps(topic.into(), &handler);
3593        }
3594
3595        for (topic, handler) in std::mem::take(&mut self.pool_liquidity_handlers) {
3596            msgbus::unsubscribe_defi_liquidity(topic.into(), &handler);
3597        }
3598
3599        for (topic, handler) in std::mem::take(&mut self.pool_collect_handlers) {
3600            msgbus::unsubscribe_defi_collects(topic.into(), &handler);
3601        }
3602
3603        for (topic, handler) in std::mem::take(&mut self.pool_flash_handlers) {
3604            msgbus::unsubscribe_defi_flash(topic.into(), &handler);
3605        }
3606    }
3607
3608    /// Creates a new [`DataActorCore`] instance.
3609    pub fn new(config: DataActorConfig) -> Self {
3610        let actor_id = config.actor_id.unwrap_or_else(Self::default_actor_id);
3611
3612        Self {
3613            actor_id,
3614            config,
3615            trader_id: None, // None until registered
3616            clock: None,     // None until registered
3617            cache: None,     // None until registered
3618            state: ComponentState::default(),
3619            topic_handlers: AHashMap::new(),
3620            instrument_handlers: AHashMap::new(),
3621            deltas_handlers: AHashMap::new(),
3622            depth10_handlers: AHashMap::new(),
3623            book_handlers: AHashMap::new(),
3624            quote_handlers: AHashMap::new(),
3625            trade_handlers: AHashMap::new(),
3626            bar_handlers: AHashMap::new(),
3627            mark_price_handlers: AHashMap::new(),
3628            index_price_handlers: AHashMap::new(),
3629            funding_rate_handlers: AHashMap::new(),
3630            option_greeks_handlers: AHashMap::new(),
3631            option_chain_handlers: AHashMap::new(),
3632            #[cfg(feature = "defi")]
3633            block_handlers: AHashMap::new(),
3634            #[cfg(feature = "defi")]
3635            pool_handlers: AHashMap::new(),
3636            #[cfg(feature = "defi")]
3637            pool_swap_handlers: AHashMap::new(),
3638            #[cfg(feature = "defi")]
3639            pool_liquidity_handlers: AHashMap::new(),
3640            #[cfg(feature = "defi")]
3641            pool_collect_handlers: AHashMap::new(),
3642            #[cfg(feature = "defi")]
3643            pool_flash_handlers: AHashMap::new(),
3644            warning_events: AHashSet::new(),
3645            pending_requests: AHashMap::new(),
3646            signal_classes: AHashMap::new(),
3647            indicators: Indicators::default(),
3648        }
3649    }
3650
3651    /// Returns the registered indicators.
3652    #[must_use]
3653    pub fn registered_indicators(&self) -> Vec<SharedActorIndicator> {
3654        self.indicators.registered_indicators()
3655    }
3656
3657    /// Returns whether all registered indicators are initialized.
3658    ///
3659    /// # Errors
3660    ///
3661    /// Returns an error if a registered indicator cannot report readiness.
3662    pub fn indicators_initialized(&self) -> anyhow::Result<bool> {
3663        self.indicators.initialized()
3664    }
3665
3666    /// Registers an indicator to receive quote ticks for an instrument.
3667    pub fn register_indicator_for_quote_ticks(
3668        &mut self,
3669        instrument_id: InstrumentId,
3670        indicator: SharedActorIndicator,
3671    ) {
3672        self.indicators
3673            .register_indicator_for_quote_ticks(instrument_id, indicator);
3674    }
3675
3676    /// Registers an indicator to receive trade ticks for an instrument.
3677    pub fn register_indicator_for_trade_ticks(
3678        &mut self,
3679        instrument_id: InstrumentId,
3680        indicator: SharedActorIndicator,
3681    ) {
3682        self.indicators
3683            .register_indicator_for_trade_ticks(instrument_id, indicator);
3684    }
3685
3686    /// Registers an indicator to receive bars for a bar type.
3687    pub fn register_indicator_for_bars(
3688        &mut self,
3689        bar_type: BarType,
3690        indicator: SharedActorIndicator,
3691    ) {
3692        self.indicators
3693            .register_indicator_for_bars(bar_type, indicator);
3694    }
3695
3696    pub(crate) fn handle_indicators_for_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
3697        self.indicators.handle_quote(quote)
3698    }
3699
3700    pub(crate) fn handle_indicators_for_quotes(&self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
3701        self.indicators.handle_quotes(quotes)
3702    }
3703
3704    pub(crate) fn handle_indicators_for_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
3705        self.indicators.handle_trade(trade)
3706    }
3707
3708    pub(crate) fn handle_indicators_for_trades(&self, trades: &[TradeTick]) -> anyhow::Result<()> {
3709        self.indicators.handle_trades(trades)
3710    }
3711
3712    pub(crate) fn handle_indicators_for_bar(&self, bar: &Bar) -> anyhow::Result<()> {
3713        self.indicators.handle_bar(bar)
3714    }
3715
3716    pub(crate) fn handle_indicators_for_bars(&self, bars: &[Bar]) -> anyhow::Result<()> {
3717        self.indicators.handle_bars(bars)
3718    }
3719
3720    /// Returns the memory address of this instance as a hexadecimal string.
3721    #[must_use]
3722    pub fn mem_address(&self) -> String {
3723        format!("{self:p}")
3724    }
3725
3726    /// Returns the actors state.
3727    pub fn state(&self) -> ComponentState {
3728        self.state
3729    }
3730
3731    /// Returns the trader ID this actor is registered to.
3732    pub fn trader_id(&self) -> Option<TraderId> {
3733        self.trader_id
3734    }
3735
3736    /// Returns the actors ID.
3737    pub fn actor_id(&self) -> ActorId {
3738        self.actor_id
3739    }
3740
3741    fn default_actor_id() -> ActorId {
3742        ActorId::from(stringify!(DataActor))
3743    }
3744
3745    /// Returns a UNIX nanoseconds timestamp from the actor's internal clock.
3746    pub fn timestamp_ns(&self) -> UnixNanos {
3747        self.clock_ref().timestamp_ns()
3748    }
3749
3750    fn clock_api(&self) -> ClockApi<'_> {
3751        let clock = self.clock.as_ref().unwrap_or_else(|| {
3752            panic!(
3753                "DataActor {} must be registered before calling `clock()` - trader_id: {:?}",
3754                self.actor_id, self.trader_id
3755            )
3756        });
3757        ClockApi::new(clock.as_ref())
3758    }
3759
3760    fn clock_ref(&self) -> Ref<'_, dyn Clock> {
3761        self.clock
3762            .as_ref()
3763            .unwrap_or_else(|| {
3764                panic!(
3765                    "DataActor {} must be registered before calling `clock_ref()` - trader_id: {:?}",
3766                    self.actor_id, self.trader_id
3767                )
3768            })
3769            .borrow()
3770    }
3771
3772    fn cache_api(&self) -> CacheApi<'_> {
3773        let cache = self.cache.as_ref().unwrap_or_else(|| {
3774            panic!(
3775                "DataActor {} must be registered before calling `cache()` - trader_id: {:?}",
3776                self.actor_id, self.trader_id
3777            )
3778        });
3779        CacheApi::new(cache.as_ref())
3780    }
3781
3782    /// Register the data actor with a trader.
3783    ///
3784    /// # Errors
3785    ///
3786    /// Returns an error if the actor has already been registered with a trader
3787    /// or if the provided dependencies are invalid.
3788    pub fn register(
3789        &mut self,
3790        trader_id: TraderId,
3791        clock: Rc<RefCell<dyn Clock>>,
3792        cache: Rc<RefCell<Cache>>,
3793    ) -> anyhow::Result<()> {
3794        if let Some(existing_trader_id) = self.trader_id {
3795            anyhow::bail!(
3796                "DataActor {} already registered with trader {existing_trader_id}",
3797                self.actor_id
3798            );
3799        }
3800
3801        // Validate clock by attempting to access it
3802        {
3803            let _timestamp = clock.borrow().timestamp_ns();
3804        }
3805
3806        // Validate cache by attempting to access it
3807        {
3808            let _cache_borrow = cache.borrow();
3809        }
3810
3811        self.trader_id = Some(trader_id);
3812        self.clock = Some(clock);
3813        self.cache = Some(cache);
3814
3815        // Verify complete registration
3816        if !self.is_properly_registered() {
3817            anyhow::bail!(
3818                "DataActor {} registration incomplete - validation failed",
3819                self.actor_id
3820            );
3821        }
3822
3823        log::debug!("Registered {} with trader {trader_id}", self.actor_id);
3824        Ok(())
3825    }
3826
3827    /// Register an event type for warning log levels.
3828    pub fn register_warning_event(&mut self, event_type: &str) {
3829        self.warning_events.insert(event_type.to_string());
3830        log::debug!("Registered event type '{event_type}' for warning logs");
3831    }
3832
3833    /// Deregister an event type from warning log levels.
3834    pub fn deregister_warning_event(&mut self, event_type: &str) {
3835        self.warning_events.remove(event_type);
3836        log::debug!("Deregistered event type '{event_type}' from warning logs");
3837    }
3838
3839    pub fn is_registered(&self) -> bool {
3840        self.trader_id.is_some()
3841    }
3842
3843    pub(crate) fn check_registered(&self) {
3844        assert!(
3845            self.is_registered(),
3846            "Actor has not been registered with a Trader"
3847        );
3848    }
3849
3850    /// Validates registration state without panicking.
3851    fn is_properly_registered(&self) -> bool {
3852        self.trader_id.is_some() && self.clock.is_some() && self.cache.is_some()
3853    }
3854
3855    pub(crate) fn send_data_cmd(&self, command: DataCommand) {
3856        if self.config.log_commands {
3857            log::info!("{CMD}{SEND} {command:?}");
3858        }
3859
3860        let endpoint = MessagingSwitchboard::data_engine_queue_execute();
3861        msgbus::send_data_command(endpoint, command);
3862    }
3863
3864    #[allow(dead_code)]
3865    fn send_data_req(&self, request: &RequestCommand) {
3866        if self.config.log_commands {
3867            log::info!("{REQ}{SEND} {request:?}");
3868        }
3869
3870        // For now, simplified approach - data requests without dynamic handlers
3871        // TODO: Implement proper dynamic dispatch for response handlers
3872        let endpoint = MessagingSwitchboard::data_engine_queue_execute();
3873        msgbus::send_any(endpoint, request.as_any());
3874    }
3875
3876    /// Sends a shutdown command to the system with an optional reason.
3877    ///
3878    /// # Panics
3879    ///
3880    /// Panics if the actor is not registered or has no trader ID.
3881    pub fn shutdown_system(&self, reason: Option<String>) {
3882        self.check_registered();
3883
3884        // Checked registered before unwrapping trader ID
3885        let command = ShutdownSystem::new(
3886            self.trader_id().unwrap(),
3887            self.actor_id.inner(),
3888            reason,
3889            UUID4::new(),
3890            self.timestamp_ns(),
3891            None, // correlation_id
3892        );
3893
3894        let topic = MessagingSwitchboard::shutdown_system_topic();
3895        msgbus::publish_any(topic, command.as_any());
3896    }
3897
3898    /// Publishes `data` on the message bus under the topic derived from `data_type`.
3899    ///
3900    /// `data_type` is kept as an explicit parameter (rather than deriving it from
3901    /// `data.data_type`) to mirror the v1 Python `publish_data(data_type, data)` API and
3902    /// to allow callers to override the routing topic from the payload's intrinsic type.
3903    ///
3904    /// # Panics
3905    ///
3906    /// Panics if the actor is not registered with a trader.
3907    pub fn publish_data(&self, data_type: &DataType, data: &CustomData) {
3908        self.check_registered();
3909
3910        let topic = get_custom_topic(data_type);
3911        msgbus::publish_any(topic, data);
3912    }
3913
3914    /// Publishes a [`Signal`] constructed from `name` and `value`, wrapped in [`CustomData`]
3915    /// so it is consumed by signal subscribers and by any `CustomData`-aware pipeline
3916    /// (for example the feather persistence writer).
3917    ///
3918    /// The topic mirrors the v1 Python scheme `data.Signal<TitleName>` so subscribers
3919    /// using either a specific name or the global wildcard are both notified.
3920    /// If `ts_event` is zero then the current clock timestamp is used.
3921    ///
3922    /// # Panics
3923    ///
3924    /// Panics if the actor is not registered with a trader.
3925    pub fn publish_signal(&self, name: &str, value: String, ts_event: UnixNanos) {
3926        self.check_registered();
3927
3928        let now = self.timestamp_ns();
3929        let ts_event = if ts_event.as_u64() == 0 {
3930            now
3931        } else {
3932            ts_event
3933        };
3934        let signal = Signal::new(Ustr::from(name), value, ts_event, now);
3935
3936        let data_type = DataType::new(
3937            &format!(
3938                "Signal{}",
3939                nautilus_core::string::conversions::title_case(name)
3940            ),
3941            None,
3942            None,
3943        );
3944        let data = CustomData::new(Arc::new(signal), data_type);
3945        let topic = get_custom_topic(&data.data_type);
3946        msgbus::publish_any(topic, &data);
3947    }
3948
3949    /// Adds the `synthetic` instrument to the cache.
3950    ///
3951    /// # Errors
3952    ///
3953    /// Returns an error if a synthetic with the same ID already exists, or if the
3954    /// backing cache fails to persist it. Panics if the actor is not registered
3955    /// with a trader. // panics-doc-ok
3956    pub fn add_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
3957        self.check_registered();
3958
3959        let cache = self.cache_rc();
3960        if cache.borrow().synthetic(&synthetic.id).is_some() {
3961            anyhow::bail!("`synthetic` {} already exists", synthetic.id);
3962        }
3963        cache.borrow_mut().add_synthetic(synthetic)
3964    }
3965
3966    /// Updates the `synthetic` instrument in the cache, replacing the existing entry.
3967    ///
3968    /// # Errors
3969    ///
3970    /// Returns an error if no synthetic with the same ID already exists, or if the
3971    /// backing cache fails to persist the replacement. Panics if the actor is not
3972    /// registered with a trader. // panics-doc-ok
3973    pub fn update_synthetic(&self, synthetic: SyntheticInstrument) -> anyhow::Result<()> {
3974        self.check_registered();
3975
3976        let cache = self.cache_rc();
3977        if cache.borrow().synthetic(&synthetic.id).is_none() {
3978            anyhow::bail!("`synthetic` {} does not exist", synthetic.id);
3979        }
3980        cache.borrow_mut().add_synthetic(synthetic)
3981    }
3982
3983    /// Helper method for registering data subscriptions from the trait.
3984    ///
3985    /// # Panics
3986    ///
3987    /// Panics if the actor is not properly registered.
3988    pub fn subscribe_data(
3989        &mut self,
3990        handler: ShareableMessageHandler,
3991        data_type: DataType,
3992        client_id: Option<ClientId>,
3993        params: Option<Params>,
3994    ) {
3995        assert!(
3996            self.is_properly_registered(),
3997            "DataActor {} is not properly registered - trader_id: {:?}, clock: {}, cache: {}",
3998            self.actor_id,
3999            self.trader_id,
4000            self.clock.is_some(),
4001            self.cache.is_some()
4002        );
4003
4004        let topic = get_custom_topic(&data_type);
4005        self.add_subscription_any(topic, handler, None);
4006
4007        // If no client ID specified, just subscribe to the topic
4008        if client_id.is_none() {
4009            return;
4010        }
4011
4012        let command = SubscribeCommand::Data(SubscribeCustomData {
4013            data_type,
4014            client_id,
4015            venue: None,
4016            command_id: UUID4::new(),
4017            ts_init: self.timestamp_ns(),
4018            correlation_id: None,
4019            params,
4020        });
4021
4022        self.send_data_cmd(DataCommand::Subscribe(command));
4023    }
4024
4025    /// Helper method for registering signal subscriptions from the trait.
4026    ///
4027    /// An empty `name` subscribes to every signal via the `data.Signal*` wildcard pattern.
4028    ///
4029    /// # Panics
4030    ///
4031    /// Panics if the actor is not registered with a trader.
4032    pub fn subscribe_signal(
4033        &mut self,
4034        handler: ShareableMessageHandler,
4035        name: &str,
4036        priority: Option<u32>,
4037    ) {
4038        self.check_registered();
4039
4040        let pattern = get_signal_pattern(name);
4041        if self.topic_handlers.contains_key(&pattern) {
4042            log::warn!(
4043                "Actor {} attempted duplicate signal subscription to '{pattern}'",
4044                self.actor_id,
4045            );
4046            return;
4047        }
4048        self.topic_handlers.insert(pattern, handler.clone());
4049        msgbus::subscribe_any(pattern, handler, priority);
4050    }
4051
4052    /// Registers a queue state change subscription from the trait.
4053    ///
4054    /// # Panics
4055    ///
4056    /// Panics if the actor is not registered with a trader.
4057    pub fn subscribe_queue_state(
4058        &mut self,
4059        handler: ShareableMessageHandler,
4060        priority: Option<u32>,
4061    ) {
4062        self.check_registered();
4063
4064        let topic = MessagingSwitchboard::queue_state_changed_topic();
4065        self.add_subscription_any(topic, handler, priority);
4066    }
4067
4068    /// Registers a socket state change subscription from the trait.
4069    ///
4070    /// # Panics
4071    ///
4072    /// Panics if the actor is not registered with a trader.
4073    pub fn subscribe_socket_state(
4074        &mut self,
4075        handler: ShareableMessageHandler,
4076        priority: Option<u32>,
4077    ) {
4078        self.check_registered();
4079
4080        let topic = MessagingSwitchboard::socket_state_changed_topic();
4081        self.add_subscription_any(topic, handler, priority);
4082    }
4083
4084    /// Helper method for registering quotes subscriptions from the trait.
4085    pub fn subscribe_quotes(
4086        &mut self,
4087        topic: MStr<Topic>,
4088        handler: TypedHandler<QuoteTick>,
4089        instrument_id: InstrumentId,
4090        client_id: Option<ClientId>,
4091        params: Option<Params>,
4092    ) {
4093        self.check_registered();
4094
4095        self.add_quote_subscription(topic, handler);
4096
4097        let command = SubscribeCommand::Quotes(SubscribeQuotes {
4098            instrument_id,
4099            client_id,
4100            venue: Some(instrument_id.venue),
4101            command_id: UUID4::new(),
4102            ts_init: self.timestamp_ns(),
4103            correlation_id: None,
4104            params,
4105        });
4106
4107        self.send_data_cmd(DataCommand::Subscribe(command));
4108    }
4109
4110    /// Helper method for registering instruments subscriptions from the trait.
4111    pub fn subscribe_instruments(
4112        &mut self,
4113        pattern: MStr<Pattern>,
4114        handler: TypedHandler<InstrumentAny>,
4115        venue: Venue,
4116        client_id: Option<ClientId>,
4117        params: Option<Params>,
4118    ) {
4119        self.check_registered();
4120
4121        self.add_instrument_subscription(pattern, handler);
4122
4123        let command = SubscribeCommand::Instruments(SubscribeInstruments {
4124            client_id,
4125            venue,
4126            command_id: UUID4::new(),
4127            ts_init: self.timestamp_ns(),
4128            correlation_id: None,
4129            params,
4130        });
4131
4132        self.send_data_cmd(DataCommand::Subscribe(command));
4133    }
4134
4135    /// Helper method for registering instrument subscriptions from the trait.
4136    pub fn subscribe_instrument(
4137        &mut self,
4138        topic: MStr<Topic>,
4139        handler: TypedHandler<InstrumentAny>,
4140        instrument_id: InstrumentId,
4141        client_id: Option<ClientId>,
4142        params: Option<Params>,
4143    ) {
4144        self.check_registered();
4145
4146        self.add_instrument_subscription(topic.into(), handler);
4147
4148        let command = SubscribeCommand::Instrument(SubscribeInstrument {
4149            instrument_id,
4150            client_id,
4151            venue: Some(instrument_id.venue),
4152            command_id: UUID4::new(),
4153            ts_init: self.timestamp_ns(),
4154            correlation_id: None,
4155            params,
4156        });
4157
4158        self.send_data_cmd(DataCommand::Subscribe(command));
4159    }
4160
4161    /// Helper method for registering book deltas subscriptions from the trait.
4162    #[expect(clippy::too_many_arguments)]
4163    pub fn subscribe_book_deltas(
4164        &mut self,
4165        pattern: MStr<Pattern>,
4166        handler: TypedHandler<OrderBookDeltas>,
4167        instrument_id: InstrumentId,
4168        book_type: BookType,
4169        depth: Option<NonZeroUsize>,
4170        client_id: Option<ClientId>,
4171        managed: bool,
4172        params: Option<Params>,
4173    ) {
4174        self.check_registered();
4175
4176        self.add_deltas_subscription(pattern, handler);
4177
4178        let command = SubscribeCommand::BookDeltas(SubscribeBookDeltas {
4179            instrument_id,
4180            book_type,
4181            client_id,
4182            venue: Some(instrument_id.venue),
4183            command_id: UUID4::new(),
4184            ts_init: self.timestamp_ns(),
4185            depth,
4186            managed,
4187            correlation_id: None,
4188            params,
4189        });
4190
4191        self.send_data_cmd(DataCommand::Subscribe(command));
4192    }
4193
4194    /// Helper method for registering book depth10 subscriptions from the trait.
4195    #[expect(clippy::too_many_arguments)]
4196    pub fn subscribe_book_depth10(
4197        &mut self,
4198        pattern: MStr<Pattern>,
4199        handler: TypedHandler<OrderBookDepth10>,
4200        instrument_id: InstrumentId,
4201        book_type: BookType,
4202        client_id: Option<ClientId>,
4203        managed: bool,
4204        params: Option<Params>,
4205    ) {
4206        self.check_registered();
4207
4208        self.add_depth10_subscription(pattern, handler);
4209
4210        let command = SubscribeCommand::BookDepth10(SubscribeBookDepth10 {
4211            instrument_id,
4212            book_type,
4213            client_id,
4214            venue: Some(instrument_id.venue),
4215            command_id: UUID4::new(),
4216            ts_init: self.timestamp_ns(),
4217            depth: NonZeroUsize::new(10),
4218            managed,
4219            correlation_id: None,
4220            params,
4221        });
4222
4223        self.send_data_cmd(DataCommand::Subscribe(command));
4224    }
4225
4226    /// Helper method for registering book snapshots subscriptions from the trait.
4227    #[expect(clippy::too_many_arguments)]
4228    pub fn subscribe_book_at_interval(
4229        &mut self,
4230        topic: MStr<Topic>,
4231        handler: TypedHandler<OrderBook>,
4232        instrument_id: InstrumentId,
4233        book_type: BookType,
4234        depth: Option<NonZeroUsize>,
4235        interval_ms: NonZeroUsize,
4236        client_id: Option<ClientId>,
4237        params: Option<Params>,
4238    ) {
4239        self.check_registered();
4240
4241        self.add_book_snapshot_subscription(topic, handler);
4242
4243        let command = SubscribeCommand::BookSnapshots(SubscribeBookSnapshots {
4244            instrument_id,
4245            book_type,
4246            client_id,
4247            venue: Some(instrument_id.venue),
4248            command_id: UUID4::new(),
4249            ts_init: self.timestamp_ns(),
4250            depth,
4251            interval_ms,
4252            correlation_id: None,
4253            params,
4254        });
4255
4256        self.send_data_cmd(DataCommand::Subscribe(command));
4257    }
4258
4259    /// Helper method for registering trades subscriptions from the trait.
4260    pub fn subscribe_trades(
4261        &mut self,
4262        topic: MStr<Topic>,
4263        handler: TypedHandler<TradeTick>,
4264        instrument_id: InstrumentId,
4265        client_id: Option<ClientId>,
4266        params: Option<Params>,
4267    ) {
4268        self.check_registered();
4269
4270        self.add_trade_subscription(topic, handler);
4271
4272        let command = SubscribeCommand::Trades(SubscribeTrades {
4273            instrument_id,
4274            client_id,
4275            venue: Some(instrument_id.venue),
4276            command_id: UUID4::new(),
4277            ts_init: self.timestamp_ns(),
4278            correlation_id: None,
4279            params,
4280        });
4281
4282        self.send_data_cmd(DataCommand::Subscribe(command));
4283    }
4284
4285    /// Helper method for registering bars subscriptions from the trait.
4286    pub fn subscribe_bars(
4287        &mut self,
4288        topic: MStr<Topic>,
4289        handler: TypedHandler<Bar>,
4290        bar_type: BarType,
4291        client_id: Option<ClientId>,
4292        params: Option<Params>,
4293    ) {
4294        self.check_registered();
4295
4296        self.add_bar_subscription(topic, handler);
4297
4298        let command = SubscribeCommand::Bars(SubscribeBars {
4299            bar_type,
4300            client_id,
4301            venue: Some(bar_type.instrument_id().venue),
4302            command_id: UUID4::new(),
4303            ts_init: self.timestamp_ns(),
4304            correlation_id: None,
4305            params,
4306        });
4307
4308        self.send_data_cmd(DataCommand::Subscribe(command));
4309    }
4310
4311    /// Helper method for registering mark prices subscriptions from the trait.
4312    pub fn subscribe_mark_prices(
4313        &mut self,
4314        topic: MStr<Topic>,
4315        handler: TypedHandler<MarkPriceUpdate>,
4316        instrument_id: InstrumentId,
4317        client_id: Option<ClientId>,
4318        params: Option<Params>,
4319    ) {
4320        self.check_registered();
4321
4322        self.add_mark_price_subscription(topic, handler);
4323
4324        let command = SubscribeCommand::MarkPrices(SubscribeMarkPrices {
4325            instrument_id,
4326            client_id,
4327            venue: Some(instrument_id.venue),
4328            command_id: UUID4::new(),
4329            ts_init: self.timestamp_ns(),
4330            correlation_id: None,
4331            params,
4332        });
4333
4334        self.send_data_cmd(DataCommand::Subscribe(command));
4335    }
4336
4337    /// Helper method for registering index prices subscriptions from the trait.
4338    pub fn subscribe_index_prices(
4339        &mut self,
4340        topic: MStr<Topic>,
4341        handler: TypedHandler<IndexPriceUpdate>,
4342        instrument_id: InstrumentId,
4343        client_id: Option<ClientId>,
4344        params: Option<Params>,
4345    ) {
4346        self.check_registered();
4347
4348        self.add_index_price_subscription(topic, handler);
4349
4350        let command = SubscribeCommand::IndexPrices(SubscribeIndexPrices {
4351            instrument_id,
4352            client_id,
4353            venue: Some(instrument_id.venue),
4354            command_id: UUID4::new(),
4355            ts_init: self.timestamp_ns(),
4356            correlation_id: None,
4357            params,
4358        });
4359
4360        self.send_data_cmd(DataCommand::Subscribe(command));
4361    }
4362
4363    /// Helper method for registering funding rates subscriptions from the trait.
4364    pub fn subscribe_funding_rates(
4365        &mut self,
4366        topic: MStr<Topic>,
4367        handler: TypedHandler<FundingRateUpdate>,
4368        instrument_id: InstrumentId,
4369        client_id: Option<ClientId>,
4370        params: Option<Params>,
4371    ) {
4372        self.check_registered();
4373
4374        self.add_funding_rate_subscription(topic, handler);
4375
4376        let command = SubscribeCommand::FundingRates(SubscribeFundingRates {
4377            instrument_id,
4378            client_id,
4379            venue: Some(instrument_id.venue),
4380            command_id: UUID4::new(),
4381            ts_init: self.timestamp_ns(),
4382            correlation_id: None,
4383            params,
4384        });
4385
4386        self.send_data_cmd(DataCommand::Subscribe(command));
4387    }
4388
4389    /// Helper method for registering option greeks subscriptions from the trait.
4390    pub fn subscribe_option_greeks(
4391        &mut self,
4392        topic: MStr<Topic>,
4393        handler: TypedHandler<OptionGreeks>,
4394        instrument_id: InstrumentId,
4395        client_id: Option<ClientId>,
4396        params: Option<Params>,
4397    ) {
4398        self.check_registered();
4399
4400        self.add_option_greeks_subscription(topic, handler);
4401
4402        let command = SubscribeCommand::OptionGreeks(SubscribeOptionGreeks {
4403            instrument_id,
4404            client_id,
4405            venue: Some(instrument_id.venue),
4406            command_id: UUID4::new(),
4407            ts_init: self.timestamp_ns(),
4408            correlation_id: None,
4409            params,
4410        });
4411
4412        self.send_data_cmd(DataCommand::Subscribe(command));
4413    }
4414
4415    /// Helper method for registering instrument status subscriptions from the trait.
4416    pub fn subscribe_instrument_status(
4417        &mut self,
4418        topic: MStr<Topic>,
4419        handler: ShareableMessageHandler,
4420        instrument_id: InstrumentId,
4421        client_id: Option<ClientId>,
4422        params: Option<Params>,
4423    ) {
4424        self.check_registered();
4425
4426        self.add_subscription_any(topic, handler, None);
4427
4428        let command = SubscribeCommand::InstrumentStatus(SubscribeInstrumentStatus {
4429            instrument_id,
4430            client_id,
4431            venue: Some(instrument_id.venue),
4432            command_id: UUID4::new(),
4433            ts_init: self.timestamp_ns(),
4434            correlation_id: None,
4435            params,
4436        });
4437
4438        self.send_data_cmd(DataCommand::Subscribe(command));
4439    }
4440
4441    /// Helper method for registering instrument close subscriptions from the trait.
4442    pub fn subscribe_instrument_close(
4443        &mut self,
4444        topic: MStr<Topic>,
4445        handler: ShareableMessageHandler,
4446        instrument_id: InstrumentId,
4447        client_id: Option<ClientId>,
4448        params: Option<Params>,
4449    ) {
4450        self.check_registered();
4451
4452        self.add_instrument_close_subscription(topic, handler);
4453
4454        let command = SubscribeCommand::InstrumentClose(SubscribeInstrumentClose {
4455            instrument_id,
4456            client_id,
4457            venue: Some(instrument_id.venue),
4458            command_id: UUID4::new(),
4459            ts_init: self.timestamp_ns(),
4460            correlation_id: None,
4461            params,
4462        });
4463
4464        self.send_data_cmd(DataCommand::Subscribe(command));
4465    }
4466
4467    /// Helper method for subscribing to option chain snapshots from the trait.
4468    #[expect(
4469        clippy::too_many_arguments,
4470        reason = "subscription command mirrors the option chain request fields"
4471    )]
4472    pub fn subscribe_option_chain(
4473        &mut self,
4474        topic: MStr<Topic>,
4475        handler: TypedHandler<OptionChainSlice>,
4476        series_id: OptionSeriesId,
4477        strike_range: StrikeRange,
4478        snapshot_interval_ms: Option<u64>,
4479        client_id: Option<ClientId>,
4480        params: Option<Params>,
4481    ) {
4482        self.check_registered();
4483
4484        self.add_option_chain_subscription(topic, handler);
4485
4486        let command = SubscribeCommand::OptionChain(SubscribeOptionChain::new(
4487            series_id,
4488            strike_range,
4489            snapshot_interval_ms,
4490            UUID4::new(),
4491            self.timestamp_ns(),
4492            client_id,
4493            Some(series_id.venue),
4494            params,
4495        ));
4496
4497        self.send_data_cmd(DataCommand::Subscribe(command));
4498    }
4499
4500    /// Helper method for unsubscribing from data.
4501    pub fn unsubscribe_data(
4502        &mut self,
4503        data_type: DataType,
4504        client_id: Option<ClientId>,
4505        params: Option<Params>,
4506    ) {
4507        self.check_registered();
4508
4509        let topic = get_custom_topic(&data_type);
4510        self.remove_subscription_any(topic);
4511
4512        if client_id.is_none() {
4513            return;
4514        }
4515
4516        let command = UnsubscribeCommand::Data(UnsubscribeCustomData {
4517            data_type,
4518            client_id,
4519            venue: None,
4520            command_id: UUID4::new(),
4521            ts_init: self.timestamp_ns(),
4522            correlation_id: None,
4523            params,
4524        });
4525
4526        self.send_data_cmd(DataCommand::Unsubscribe(command));
4527    }
4528
4529    /// Helper method for unsubscribing from signals.
4530    ///
4531    /// # Panics
4532    ///
4533    /// Panics if the actor is not registered with a trader.
4534    pub fn unsubscribe_signal(&mut self, name: &str) {
4535        self.check_registered();
4536
4537        let pattern = get_signal_pattern(name);
4538        if let Some(handler) = self.topic_handlers.remove(&pattern) {
4539            msgbus::unsubscribe_any(pattern, &handler);
4540        } else {
4541            log::warn!(
4542                "Actor {} attempted to unsubscribe from signal pattern '{pattern}' when not subscribed",
4543                self.actor_id,
4544            );
4545        }
4546    }
4547
4548    /// Unsubscribes from queue state changes.
4549    ///
4550    /// # Panics
4551    ///
4552    /// Panics if the actor is not registered with a trader.
4553    pub fn unsubscribe_queue_state(&mut self) {
4554        self.check_registered();
4555
4556        let topic = MessagingSwitchboard::queue_state_changed_topic();
4557        self.remove_subscription_any(topic);
4558    }
4559
4560    /// Unsubscribes from socket state changes.
4561    ///
4562    /// # Panics
4563    ///
4564    /// Panics if the actor is not registered with a trader.
4565    pub fn unsubscribe_socket_state(&mut self) {
4566        self.check_registered();
4567
4568        let topic = MessagingSwitchboard::socket_state_changed_topic();
4569        self.remove_subscription_any(topic);
4570    }
4571
4572    /// Helper method for unsubscribing from instruments.
4573    pub fn unsubscribe_instruments(
4574        &mut self,
4575        venue: Venue,
4576        client_id: Option<ClientId>,
4577        params: Option<Params>,
4578    ) {
4579        self.check_registered();
4580
4581        let pattern = get_instruments_pattern(venue);
4582        self.remove_instrument_subscription(pattern);
4583
4584        let command = UnsubscribeCommand::Instruments(UnsubscribeInstruments {
4585            client_id,
4586            venue,
4587            command_id: UUID4::new(),
4588            ts_init: self.timestamp_ns(),
4589            correlation_id: None,
4590            params,
4591        });
4592
4593        self.send_data_cmd(DataCommand::Unsubscribe(command));
4594    }
4595
4596    /// Helper method for unsubscribing from instrument.
4597    pub fn unsubscribe_instrument(
4598        &mut self,
4599        instrument_id: InstrumentId,
4600        client_id: Option<ClientId>,
4601        params: Option<Params>,
4602    ) {
4603        self.check_registered();
4604
4605        let topic = get_instrument_topic(instrument_id);
4606        self.remove_instrument_subscription(topic.into());
4607
4608        let command = UnsubscribeCommand::Instrument(UnsubscribeInstrument {
4609            instrument_id,
4610            client_id,
4611            venue: Some(instrument_id.venue),
4612            command_id: UUID4::new(),
4613            ts_init: self.timestamp_ns(),
4614            correlation_id: None,
4615            params,
4616        });
4617
4618        self.send_data_cmd(DataCommand::Unsubscribe(command));
4619    }
4620
4621    /// Helper method for unsubscribing from book deltas.
4622    pub fn unsubscribe_book_deltas(
4623        &mut self,
4624        instrument_id: InstrumentId,
4625        client_id: Option<ClientId>,
4626        params: Option<Params>,
4627    ) {
4628        self.check_registered();
4629
4630        let pattern = if is_parent_subscription(params.as_ref()) {
4631            get_book_deltas_pattern(instrument_id)
4632        } else {
4633            get_book_deltas_topic(instrument_id).into()
4634        };
4635        self.remove_deltas_subscription(pattern);
4636
4637        let command = UnsubscribeCommand::BookDeltas(UnsubscribeBookDeltas {
4638            instrument_id,
4639            client_id,
4640            venue: Some(instrument_id.venue),
4641            command_id: UUID4::new(),
4642            ts_init: self.timestamp_ns(),
4643            correlation_id: None,
4644            params,
4645        });
4646
4647        self.send_data_cmd(DataCommand::Unsubscribe(command));
4648    }
4649
4650    /// Helper method for unsubscribing from book depth10 snapshots.
4651    pub fn unsubscribe_book_depth10(
4652        &mut self,
4653        instrument_id: InstrumentId,
4654        client_id: Option<ClientId>,
4655        params: Option<Params>,
4656    ) {
4657        self.check_registered();
4658
4659        let pattern = if is_parent_subscription(params.as_ref()) {
4660            get_book_depth10_pattern(instrument_id)
4661        } else {
4662            get_book_depth10_topic(instrument_id).into()
4663        };
4664        self.remove_depth10_subscription(pattern);
4665
4666        let command = UnsubscribeCommand::BookDepth10(UnsubscribeBookDepth10 {
4667            instrument_id,
4668            client_id,
4669            venue: Some(instrument_id.venue),
4670            command_id: UUID4::new(),
4671            ts_init: self.timestamp_ns(),
4672            correlation_id: None,
4673            params,
4674        });
4675
4676        self.send_data_cmd(DataCommand::Unsubscribe(command));
4677    }
4678
4679    /// Helper method for unsubscribing from book snapshots at interval.
4680    pub fn unsubscribe_book_at_interval(
4681        &mut self,
4682        instrument_id: InstrumentId,
4683        interval_ms: NonZeroUsize,
4684        client_id: Option<ClientId>,
4685        params: Option<Params>,
4686    ) {
4687        self.check_registered();
4688
4689        let topic = get_book_snapshots_topic(instrument_id, interval_ms);
4690        self.remove_book_snapshot_subscription(topic);
4691
4692        let command = UnsubscribeCommand::BookSnapshots(UnsubscribeBookSnapshots {
4693            instrument_id,
4694            interval_ms,
4695            client_id,
4696            venue: Some(instrument_id.venue),
4697            command_id: UUID4::new(),
4698            ts_init: self.timestamp_ns(),
4699            correlation_id: None,
4700            params,
4701        });
4702
4703        self.send_data_cmd(DataCommand::Unsubscribe(command));
4704    }
4705
4706    /// Helper method for unsubscribing from quotes.
4707    pub fn unsubscribe_quotes(
4708        &mut self,
4709        instrument_id: InstrumentId,
4710        client_id: Option<ClientId>,
4711        params: Option<Params>,
4712    ) {
4713        self.check_registered();
4714
4715        let topic = get_quotes_topic(instrument_id);
4716        self.remove_quote_subscription(topic);
4717
4718        let command = UnsubscribeCommand::Quotes(UnsubscribeQuotes {
4719            instrument_id,
4720            client_id,
4721            venue: Some(instrument_id.venue),
4722            command_id: UUID4::new(),
4723            ts_init: self.timestamp_ns(),
4724            correlation_id: None,
4725            params,
4726        });
4727
4728        self.send_data_cmd(DataCommand::Unsubscribe(command));
4729    }
4730
4731    /// Helper method for unsubscribing from trades.
4732    pub fn unsubscribe_trades(
4733        &mut self,
4734        instrument_id: InstrumentId,
4735        client_id: Option<ClientId>,
4736        params: Option<Params>,
4737    ) {
4738        self.check_registered();
4739
4740        let topic = get_trades_topic(instrument_id);
4741        self.remove_trade_subscription(topic);
4742
4743        let command = UnsubscribeCommand::Trades(UnsubscribeTrades {
4744            instrument_id,
4745            client_id,
4746            venue: Some(instrument_id.venue),
4747            command_id: UUID4::new(),
4748            ts_init: self.timestamp_ns(),
4749            correlation_id: None,
4750            params,
4751        });
4752
4753        self.send_data_cmd(DataCommand::Unsubscribe(command));
4754    }
4755
4756    /// Helper method for unsubscribing from bars.
4757    pub fn unsubscribe_bars(
4758        &mut self,
4759        bar_type: BarType,
4760        client_id: Option<ClientId>,
4761        params: Option<Params>,
4762    ) {
4763        self.check_registered();
4764
4765        // Match the standard topic used at subscribe time (see `subscribe_bars`)
4766        let topic = get_bars_topic(bar_type.standard());
4767        self.remove_bar_subscription(topic);
4768
4769        let command = UnsubscribeCommand::Bars(UnsubscribeBars {
4770            bar_type,
4771            client_id,
4772            venue: Some(bar_type.instrument_id().venue),
4773            command_id: UUID4::new(),
4774            ts_init: self.timestamp_ns(),
4775            correlation_id: None,
4776            params,
4777        });
4778
4779        self.send_data_cmd(DataCommand::Unsubscribe(command));
4780    }
4781
4782    /// Helper method for unsubscribing from mark prices.
4783    pub fn unsubscribe_mark_prices(
4784        &mut self,
4785        instrument_id: InstrumentId,
4786        client_id: Option<ClientId>,
4787        params: Option<Params>,
4788    ) {
4789        self.check_registered();
4790
4791        let topic = get_mark_price_topic(instrument_id);
4792        self.remove_mark_price_subscription(topic);
4793
4794        let command = UnsubscribeCommand::MarkPrices(UnsubscribeMarkPrices {
4795            instrument_id,
4796            client_id,
4797            venue: Some(instrument_id.venue),
4798            command_id: UUID4::new(),
4799            ts_init: self.timestamp_ns(),
4800            correlation_id: None,
4801            params,
4802        });
4803
4804        self.send_data_cmd(DataCommand::Unsubscribe(command));
4805    }
4806
4807    /// Helper method for unsubscribing from index prices.
4808    pub fn unsubscribe_index_prices(
4809        &mut self,
4810        instrument_id: InstrumentId,
4811        client_id: Option<ClientId>,
4812        params: Option<Params>,
4813    ) {
4814        self.check_registered();
4815
4816        let topic = get_index_price_topic(instrument_id);
4817        self.remove_index_price_subscription(topic);
4818
4819        let command = UnsubscribeCommand::IndexPrices(UnsubscribeIndexPrices {
4820            instrument_id,
4821            client_id,
4822            venue: Some(instrument_id.venue),
4823            command_id: UUID4::new(),
4824            ts_init: self.timestamp_ns(),
4825            correlation_id: None,
4826            params,
4827        });
4828
4829        self.send_data_cmd(DataCommand::Unsubscribe(command));
4830    }
4831
4832    /// Helper method for unsubscribing from funding rates.
4833    pub fn unsubscribe_funding_rates(
4834        &mut self,
4835        instrument_id: InstrumentId,
4836        client_id: Option<ClientId>,
4837        params: Option<Params>,
4838    ) {
4839        self.check_registered();
4840
4841        let topic = get_funding_rate_topic(instrument_id);
4842        self.remove_funding_rate_subscription(topic);
4843
4844        let command = UnsubscribeCommand::FundingRates(UnsubscribeFundingRates {
4845            instrument_id,
4846            client_id,
4847            venue: Some(instrument_id.venue),
4848            command_id: UUID4::new(),
4849            ts_init: self.timestamp_ns(),
4850            correlation_id: None,
4851            params,
4852        });
4853
4854        self.send_data_cmd(DataCommand::Unsubscribe(command));
4855    }
4856
4857    /// Helper method for unsubscribing from option greeks.
4858    pub fn unsubscribe_option_greeks(
4859        &mut self,
4860        instrument_id: InstrumentId,
4861        client_id: Option<ClientId>,
4862        params: Option<Params>,
4863    ) {
4864        self.check_registered();
4865
4866        let topic = get_option_greeks_topic(instrument_id);
4867        self.remove_option_greeks_subscription(topic);
4868
4869        let command = UnsubscribeCommand::OptionGreeks(UnsubscribeOptionGreeks {
4870            instrument_id,
4871            client_id,
4872            venue: Some(instrument_id.venue),
4873            command_id: UUID4::new(),
4874            ts_init: self.timestamp_ns(),
4875            correlation_id: None,
4876            params,
4877        });
4878
4879        self.send_data_cmd(DataCommand::Unsubscribe(command));
4880    }
4881
4882    /// Helper method for unsubscribing from instrument status.
4883    pub fn unsubscribe_instrument_status(
4884        &mut self,
4885        instrument_id: InstrumentId,
4886        client_id: Option<ClientId>,
4887        params: Option<Params>,
4888    ) {
4889        self.check_registered();
4890
4891        let topic = get_instrument_status_topic(instrument_id);
4892        self.remove_subscription_any(topic);
4893
4894        let command = UnsubscribeCommand::InstrumentStatus(UnsubscribeInstrumentStatus {
4895            instrument_id,
4896            client_id,
4897            venue: Some(instrument_id.venue),
4898            command_id: UUID4::new(),
4899            ts_init: self.timestamp_ns(),
4900            correlation_id: None,
4901            params,
4902        });
4903
4904        self.send_data_cmd(DataCommand::Unsubscribe(command));
4905    }
4906
4907    /// Helper method for unsubscribing from instrument close.
4908    pub fn unsubscribe_instrument_close(
4909        &mut self,
4910        instrument_id: InstrumentId,
4911        client_id: Option<ClientId>,
4912        params: Option<Params>,
4913    ) {
4914        self.check_registered();
4915
4916        let topic = get_instrument_close_topic(instrument_id);
4917        self.remove_instrument_close_subscription(topic);
4918
4919        let command = UnsubscribeCommand::InstrumentClose(UnsubscribeInstrumentClose {
4920            instrument_id,
4921            client_id,
4922            venue: Some(instrument_id.venue),
4923            command_id: UUID4::new(),
4924            ts_init: self.timestamp_ns(),
4925            correlation_id: None,
4926            params,
4927        });
4928
4929        self.send_data_cmd(DataCommand::Unsubscribe(command));
4930    }
4931
4932    /// Helper method for unsubscribing from option chain snapshots.
4933    pub fn unsubscribe_option_chain(
4934        &mut self,
4935        series_id: OptionSeriesId,
4936        client_id: Option<ClientId>,
4937    ) {
4938        self.check_registered();
4939
4940        let topic = get_option_chain_topic(series_id);
4941        self.remove_option_chain_subscription(topic);
4942
4943        let command = UnsubscribeCommand::OptionChain(UnsubscribeOptionChain::new(
4944            series_id,
4945            UUID4::new(),
4946            self.timestamp_ns(),
4947            client_id,
4948            Some(series_id.venue),
4949        ));
4950
4951        self.send_data_cmd(DataCommand::Unsubscribe(command));
4952    }
4953
4954    /// Helper method for requesting data.
4955    ///
4956    /// # Errors
4957    ///
4958    /// Returns an error if input parameters are invalid.
4959    #[expect(clippy::too_many_arguments)]
4960    pub fn request_data(
4961        &self,
4962        data_type: DataType,
4963        client_id: ClientId,
4964        start: Option<Timestamp>,
4965        end: Option<Timestamp>,
4966        limit: Option<NonZeroUsize>,
4967        params: Option<Params>,
4968        handler: ShareableMessageHandler,
4969    ) -> anyhow::Result<UUID4> {
4970        self.check_registered();
4971
4972        let now = self.clock_ref().utc_now();
4973        check_timestamps(now, start, end)?;
4974
4975        let request_id = UUID4::new();
4976        let command = RequestCommand::Data(RequestCustomData {
4977            client_id,
4978            data_type,
4979            start,
4980            end,
4981            limit,
4982            request_id,
4983            ts_init: self.timestamp_ns(),
4984            params,
4985        });
4986
4987        get_message_bus()
4988            .borrow_mut()
4989            .register_response_handler(command.request_id(), handler)?;
4990
4991        self.send_data_cmd(DataCommand::Request(command));
4992
4993        Ok(request_id)
4994    }
4995
4996    /// Helper method for requesting instrument.
4997    ///
4998    /// # Errors
4999    ///
5000    /// Returns an error if input parameters are invalid.
5001    pub fn request_instrument(
5002        &self,
5003        instrument_id: InstrumentId,
5004        start: Option<Timestamp>,
5005        end: Option<Timestamp>,
5006        client_id: Option<ClientId>,
5007        params: Option<Params>,
5008        handler: ShareableMessageHandler,
5009    ) -> anyhow::Result<UUID4> {
5010        self.check_registered();
5011
5012        let now = self.clock_ref().utc_now();
5013        check_timestamps(now, start, end)?;
5014
5015        let request_id = UUID4::new();
5016        let command = RequestCommand::Instrument(RequestInstrument {
5017            instrument_id,
5018            start,
5019            end,
5020            client_id,
5021            request_id,
5022            ts_init: now.into(),
5023            params,
5024        });
5025
5026        get_message_bus()
5027            .borrow_mut()
5028            .register_response_handler(command.request_id(), handler)?;
5029
5030        self.send_data_cmd(DataCommand::Request(command));
5031
5032        Ok(request_id)
5033    }
5034
5035    /// Helper method for requesting instruments.
5036    ///
5037    /// # Errors
5038    ///
5039    /// Returns an error if input parameters are invalid.
5040    pub fn request_instruments(
5041        &self,
5042        venue: Option<Venue>,
5043        start: Option<Timestamp>,
5044        end: Option<Timestamp>,
5045        client_id: Option<ClientId>,
5046        params: Option<Params>,
5047        handler: ShareableMessageHandler,
5048    ) -> anyhow::Result<UUID4> {
5049        self.check_registered();
5050
5051        let now = self.clock_ref().utc_now();
5052        check_timestamps(now, start, end)?;
5053
5054        let request_id = UUID4::new();
5055        let command = RequestCommand::Instruments(RequestInstruments {
5056            venue,
5057            start,
5058            end,
5059            client_id,
5060            request_id,
5061            ts_init: now.into(),
5062            params,
5063        });
5064
5065        get_message_bus()
5066            .borrow_mut()
5067            .register_response_handler(command.request_id(), handler)?;
5068
5069        self.send_data_cmd(DataCommand::Request(command));
5070
5071        Ok(request_id)
5072    }
5073
5074    /// Helper method for requesting book snapshot.
5075    ///
5076    /// # Errors
5077    ///
5078    /// Returns an error if input parameters are invalid.
5079    pub fn request_book_snapshot(
5080        &self,
5081        instrument_id: InstrumentId,
5082        depth: Option<NonZeroUsize>,
5083        client_id: Option<ClientId>,
5084        params: Option<Params>,
5085        handler: ShareableMessageHandler,
5086    ) -> anyhow::Result<UUID4> {
5087        self.check_registered();
5088
5089        let request_id = UUID4::new();
5090        let command = RequestCommand::BookSnapshot(RequestBookSnapshot {
5091            instrument_id,
5092            depth,
5093            client_id,
5094            request_id,
5095            ts_init: self.timestamp_ns(),
5096            params,
5097        });
5098
5099        get_message_bus()
5100            .borrow_mut()
5101            .register_response_handler(command.request_id(), handler)?;
5102
5103        self.send_data_cmd(DataCommand::Request(command));
5104
5105        Ok(request_id)
5106    }
5107
5108    /// Helper method for requesting book deltas.
5109    ///
5110    /// # Errors
5111    ///
5112    /// Returns an error if input parameters are invalid.
5113    #[expect(clippy::too_many_arguments)]
5114    pub fn request_book_deltas(
5115        &self,
5116        instrument_id: InstrumentId,
5117        start: Option<Timestamp>,
5118        end: Option<Timestamp>,
5119        limit: Option<NonZeroUsize>,
5120        client_id: Option<ClientId>,
5121        params: Option<Params>,
5122        handler: ShareableMessageHandler,
5123    ) -> anyhow::Result<UUID4> {
5124        self.check_registered();
5125
5126        let now = self.clock_ref().utc_now();
5127        check_timestamps(now, start, end)?;
5128
5129        let request_id = UUID4::new();
5130        let command = RequestCommand::BookDeltas(RequestBookDeltas {
5131            instrument_id,
5132            start,
5133            end,
5134            limit,
5135            client_id,
5136            request_id,
5137            ts_init: now.into(),
5138            params,
5139        });
5140
5141        get_message_bus()
5142            .borrow_mut()
5143            .register_response_handler(command.request_id(), handler)?;
5144
5145        self.send_data_cmd(DataCommand::Request(command));
5146
5147        Ok(request_id)
5148    }
5149
5150    /// Sends a request for historical book depth.
5151    ///
5152    /// # Errors
5153    ///
5154    /// Returns an error if input parameters are invalid.
5155    #[expect(clippy::too_many_arguments)]
5156    pub fn request_book_depth(
5157        &self,
5158        instrument_id: InstrumentId,
5159        start: Option<Timestamp>,
5160        end: Option<Timestamp>,
5161        limit: Option<NonZeroUsize>,
5162        depth: Option<NonZeroUsize>,
5163        client_id: Option<ClientId>,
5164        params: Option<Params>,
5165        handler: ShareableMessageHandler,
5166    ) -> anyhow::Result<UUID4> {
5167        self.check_registered();
5168
5169        let now = self.clock_ref().utc_now();
5170        check_timestamps(now, start, end)?;
5171
5172        let request_id = UUID4::new();
5173        let command = RequestCommand::BookDepth(RequestBookDepth {
5174            instrument_id,
5175            start,
5176            end,
5177            limit,
5178            depth,
5179            client_id,
5180            request_id,
5181            ts_init: now.into(),
5182            params,
5183        });
5184
5185        get_message_bus()
5186            .borrow_mut()
5187            .register_response_handler(command.request_id(), handler)?;
5188
5189        self.send_data_cmd(DataCommand::Request(command));
5190
5191        Ok(request_id)
5192    }
5193
5194    /// Helper method for requesting quotes.
5195    ///
5196    /// # Errors
5197    ///
5198    /// Returns an error if input parameters are invalid.
5199    #[expect(clippy::too_many_arguments)]
5200    pub fn request_quotes(
5201        &self,
5202        instrument_id: InstrumentId,
5203        start: Option<Timestamp>,
5204        end: Option<Timestamp>,
5205        limit: Option<NonZeroUsize>,
5206        client_id: Option<ClientId>,
5207        params: Option<Params>,
5208        handler: ShareableMessageHandler,
5209    ) -> anyhow::Result<UUID4> {
5210        self.check_registered();
5211
5212        let now = self.clock_ref().utc_now();
5213        check_timestamps(now, start, end)?;
5214
5215        let request_id = UUID4::new();
5216        let command = RequestCommand::Quotes(RequestQuotes {
5217            instrument_id,
5218            start,
5219            end,
5220            limit,
5221            client_id,
5222            request_id,
5223            ts_init: now.into(),
5224            params,
5225        });
5226
5227        get_message_bus()
5228            .borrow_mut()
5229            .register_response_handler(command.request_id(), handler)?;
5230
5231        self.send_data_cmd(DataCommand::Request(command));
5232
5233        Ok(request_id)
5234    }
5235
5236    /// Helper method for requesting trades.
5237    ///
5238    /// # Errors
5239    ///
5240    /// Returns an error if input parameters are invalid.
5241    #[expect(clippy::too_many_arguments)]
5242    pub fn request_trades(
5243        &self,
5244        instrument_id: InstrumentId,
5245        start: Option<Timestamp>,
5246        end: Option<Timestamp>,
5247        limit: Option<NonZeroUsize>,
5248        client_id: Option<ClientId>,
5249        params: Option<Params>,
5250        handler: ShareableMessageHandler,
5251    ) -> anyhow::Result<UUID4> {
5252        self.check_registered();
5253
5254        let now = self.clock_ref().utc_now();
5255        check_timestamps(now, start, end)?;
5256
5257        let request_id = UUID4::new();
5258        let command = RequestCommand::Trades(RequestTrades {
5259            instrument_id,
5260            start,
5261            end,
5262            limit,
5263            client_id,
5264            request_id,
5265            ts_init: now.into(),
5266            params,
5267        });
5268
5269        get_message_bus()
5270            .borrow_mut()
5271            .register_response_handler(command.request_id(), handler)?;
5272
5273        self.send_data_cmd(DataCommand::Request(command));
5274
5275        Ok(request_id)
5276    }
5277
5278    /// Helper method for requesting bars.
5279    ///
5280    /// # Errors
5281    ///
5282    /// Returns an error if input parameters are invalid.
5283    #[expect(clippy::too_many_arguments)]
5284    pub fn request_bars(
5285        &self,
5286        bar_type: BarType,
5287        start: Option<Timestamp>,
5288        end: Option<Timestamp>,
5289        limit: Option<NonZeroUsize>,
5290        client_id: Option<ClientId>,
5291        params: Option<Params>,
5292        handler: ShareableMessageHandler,
5293    ) -> anyhow::Result<UUID4> {
5294        self.check_registered();
5295
5296        anyhow::ensure!(
5297            bar_type.is_standard(),
5298            "Composite bar types are not supported for `request_bars`, was {bar_type}; \
5299             request aggregation via the `bar_types` params instead",
5300        );
5301
5302        let now = self.clock_ref().utc_now();
5303        check_timestamps(now, start, end)?;
5304
5305        let request_id = UUID4::new();
5306        let command = RequestCommand::Bars(RequestBars {
5307            bar_type,
5308            start,
5309            end,
5310            limit,
5311            client_id,
5312            request_id,
5313            ts_init: now.into(),
5314            params,
5315        });
5316
5317        get_message_bus()
5318            .borrow_mut()
5319            .register_response_handler(command.request_id(), handler)?;
5320
5321        self.send_data_cmd(DataCommand::Request(command));
5322
5323        Ok(request_id)
5324    }
5325
5326    /// Helper method for requesting funding rates.
5327    ///
5328    /// # Errors
5329    ///
5330    /// Returns an error if input parameters are invalid.
5331    #[expect(clippy::too_many_arguments)]
5332    pub fn request_funding_rates(
5333        &self,
5334        instrument_id: InstrumentId,
5335        start: Option<Timestamp>,
5336        end: Option<Timestamp>,
5337        limit: Option<NonZeroUsize>,
5338        client_id: Option<ClientId>,
5339        params: Option<Params>,
5340        handler: ShareableMessageHandler,
5341    ) -> anyhow::Result<UUID4> {
5342        self.check_registered();
5343
5344        let now = self.clock_ref().utc_now();
5345        check_timestamps(now, start, end)?;
5346
5347        let request_id = UUID4::new();
5348        let command = RequestCommand::FundingRates(RequestFundingRates {
5349            instrument_id,
5350            start,
5351            end,
5352            limit,
5353            client_id,
5354            request_id,
5355            ts_init: now.into(),
5356            params,
5357        });
5358
5359        get_message_bus()
5360            .borrow_mut()
5361            .register_response_handler(command.request_id(), handler)?;
5362
5363        self.send_data_cmd(DataCommand::Request(command));
5364
5365        Ok(request_id)
5366    }
5367
5368    /// Sends a fire-and-observe reconnect command.
5369    ///
5370    /// # Errors
5371    ///
5372    /// Returns an error if the actor is not registered, the endpoint label is invalid, the live
5373    /// runner is unavailable, or the command channel is closed.
5374    #[cfg(feature = "live")]
5375    pub fn reconnect_socket(&self, client_id: ClientId, endpoint: &str) -> anyhow::Result<()> {
5376        let endpoint = socket_endpoint(endpoint)?;
5377
5378        if !self.is_properly_registered() {
5379            anyhow::bail!(
5380                "Actor {} has not been registered with a Trader",
5381                self.actor_id
5382            );
5383        }
5384
5385        let sender = try_get_system_command_sender()
5386            .ok_or_else(|| anyhow::anyhow!("Live runner system command channel is unavailable"))?;
5387        let trader_id = self
5388            .trader_id
5389            .ok_or_else(|| anyhow::anyhow!("Actor {} has no trader ID", self.actor_id))?;
5390        let command = ReconnectSocket::new(trader_id, client_id, endpoint, self.timestamp_ns());
5391        sender
5392            .send(SystemCommand::ReconnectSocket(command))
5393            .map_err(|_| anyhow::anyhow!("Live runner system command channel is closed"))?;
5394        Ok(())
5395    }
5396
5397    #[cfg(test)]
5398    pub fn quote_handler_count(&self) -> usize {
5399        self.quote_handlers.len()
5400    }
5401
5402    #[cfg(test)]
5403    pub fn trade_handler_count(&self) -> usize {
5404        self.trade_handlers.len()
5405    }
5406
5407    #[cfg(test)]
5408    pub fn bar_handler_count(&self) -> usize {
5409        self.bar_handlers.len()
5410    }
5411
5412    #[cfg(test)]
5413    pub fn deltas_handler_count(&self) -> usize {
5414        self.deltas_handlers.len()
5415    }
5416
5417    #[cfg(test)]
5418    pub fn depth10_handler_count(&self) -> usize {
5419        self.depth10_handlers.len()
5420    }
5421
5422    #[cfg(test)]
5423    pub fn has_quote_handler(&self, topic: &str) -> bool {
5424        self.quote_handlers
5425            .contains_key(&MStr::<Topic>::from(topic))
5426    }
5427
5428    #[cfg(test)]
5429    pub fn has_trade_handler(&self, topic: &str) -> bool {
5430        self.trade_handlers
5431            .contains_key(&MStr::<Topic>::from(topic))
5432    }
5433
5434    #[cfg(test)]
5435    pub fn has_bar_handler(&self, topic: &str) -> bool {
5436        self.bar_handlers.contains_key(&MStr::<Topic>::from(topic))
5437    }
5438
5439    #[cfg(test)]
5440    pub fn has_deltas_handler(&self, pattern: &str) -> bool {
5441        self.deltas_handlers
5442            .contains_key(&MStr::<Pattern>::from(pattern))
5443    }
5444
5445    #[cfg(test)]
5446    pub fn has_depth10_handler(&self, pattern: &str) -> bool {
5447        self.depth10_handlers
5448            .contains_key(&MStr::<Pattern>::from(pattern))
5449    }
5450}
5451
5452impl DataActorNative for DataActorCore {
5453    fn core(&self) -> &DataActorCore {
5454        self
5455    }
5456
5457    fn core_mut(&mut self) -> &mut DataActorCore {
5458        self
5459    }
5460}
5461
5462fn check_timestamps(
5463    now: Timestamp,
5464    start: Option<Timestamp>,
5465    end: Option<Timestamp>,
5466) -> anyhow::Result<()> {
5467    if let Some(start) = start {
5468        check_predicate_true(start <= now, "start was > now")?;
5469    }
5470
5471    if let Some(end) = end {
5472        check_predicate_true(end <= now, "end was > now")?;
5473    }
5474
5475    if let (Some(start), Some(end)) = (start, end) {
5476        check_predicate_true(start <= end, "start was > end")?;
5477    }
5478
5479    Ok(())
5480}
5481
5482fn log_error(e: &anyhow::Error) {
5483    log::error!("{e}");
5484}
5485
5486fn log_not_running<T>(msg: &T)
5487where
5488    T: Debug,
5489{
5490    log::trace!("Received message when not running - skipping {msg:?}");
5491}
5492
5493fn log_received<T>(msg: &T)
5494where
5495    T: Debug,
5496{
5497    log::debug!("{RECV} {msg:?}");
5498}
5499
5500fn log_received_bulk(kind: &str, correlation_id: &UUID4, records: usize) {
5501    log::debug!("{RECV} {kind} correlation_id={correlation_id} records={records}");
5502}