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