Skip to main content

nautilus_common/actor/
data_actor.rs

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