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