Skip to main content

nautilus_common/actor/
data_actor.rs

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