1#[cfg(feature = "indicators")]
17use std::cell::RefCell;
18use std::{any::Any, fmt::Debug, rc::Rc};
19
20use ahash::AHashMap;
21#[cfg(feature = "indicators")]
22use nautilus_indicators::indicator::Indicator;
23use nautilus_model::{
24 data::{Bar, BarSpecification, BarType, QuoteTick, TradeTick},
25 identifiers::InstrumentId,
26};
27
28pub type SharedActorIndicator = Rc<dyn ActorIndicator>;
30
31pub trait ActorIndicator: Any {
33 fn key(&self) -> usize;
35
36 fn as_any(&self) -> &dyn Any;
38
39 fn initialized(&self) -> anyhow::Result<bool>;
45
46 fn handle_quote(&self, quote: &QuoteTick) -> anyhow::Result<()>;
52
53 fn handle_trade(&self, trade: &TradeTick) -> anyhow::Result<()>;
59
60 fn handle_bar(&self, bar: &Bar) -> anyhow::Result<()>;
66}
67
68#[cfg(feature = "indicators")]
69impl<T> ActorIndicator for RefCell<T>
70where
71 T: Indicator + 'static,
72{
73 fn key(&self) -> usize {
74 std::ptr::from_ref(self).cast::<()>() as usize
75 }
76
77 fn as_any(&self) -> &dyn Any {
78 self
79 }
80
81 fn initialized(&self) -> anyhow::Result<bool> {
82 Ok(self.borrow().initialized())
83 }
84
85 fn handle_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
86 self.borrow_mut().handle_quote(quote)
87 }
88
89 fn handle_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
90 self.borrow_mut().handle_trade(trade);
91 Ok(())
92 }
93
94 fn handle_bar(&self, bar: &Bar) -> anyhow::Result<()> {
95 self.borrow_mut().handle_bar(bar);
96 Ok(())
97 }
98}
99
100#[derive(Clone, Default)]
102#[allow(
103 clippy::struct_field_names,
104 reason = "indicator-prefixed fields denote distinct indicator collections"
105)]
106pub struct Indicators {
107 indicators: Vec<SharedActorIndicator>,
108 indicators_for_quotes: AHashMap<InstrumentId, Vec<SharedActorIndicator>>,
109 indicators_for_trades: AHashMap<InstrumentId, Vec<SharedActorIndicator>>,
110 indicators_for_bars: AHashMap<(InstrumentId, BarSpecification), Vec<SharedActorIndicator>>,
111}
112
113impl Debug for Indicators {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.debug_struct(stringify!(Indicators))
116 .field("indicators", &self.indicators.len())
117 .field("indicators_for_quotes", &self.indicators_for_quotes.len())
118 .field("indicators_for_trades", &self.indicators_for_trades.len())
119 .field("indicators_for_bars", &self.indicators_for_bars.len())
120 .finish()
121 }
122}
123
124impl Indicators {
125 #[must_use]
127 pub fn registered_indicators(&self) -> Vec<SharedActorIndicator> {
128 self.indicators.clone()
129 }
130
131 pub fn initialized(&self) -> anyhow::Result<bool> {
137 if self.indicators.is_empty() {
138 return Ok(false);
139 }
140
141 for indicator in &self.indicators {
142 if !indicator.initialized()? {
143 return Ok(false);
144 }
145 }
146
147 Ok(true)
148 }
149
150 pub fn register_indicator_for_quote_ticks(
152 &mut self,
153 instrument_id: InstrumentId,
154 indicator: SharedActorIndicator,
155 ) {
156 self.register_indicator(indicator.clone());
157 self.register_by_key(instrument_id, indicator, IndicatorKind::Quote);
158 }
159
160 pub fn register_indicator_for_trade_ticks(
162 &mut self,
163 instrument_id: InstrumentId,
164 indicator: SharedActorIndicator,
165 ) {
166 self.register_indicator(indicator.clone());
167 self.register_by_key(instrument_id, indicator, IndicatorKind::Trade);
168 }
169
170 pub fn register_indicator_for_bars(
172 &mut self,
173 bar_type: BarType,
174 indicator: SharedActorIndicator,
175 ) {
176 self.register_indicator(indicator.clone());
177 self.register_bar(bar_type.id_spec_key(), indicator);
178 }
179
180 pub fn handle_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
186 if let Some(indicators) = self.indicators_for_quotes.get("e.instrument_id) {
187 for indicator in indicators {
188 indicator.handle_quote(quote)?;
189 }
190 }
191
192 Ok(())
193 }
194
195 pub fn handle_quotes(&self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
201 for quote in quotes {
202 self.handle_quote(quote)?;
203 }
204
205 Ok(())
206 }
207
208 pub fn handle_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
214 if let Some(indicators) = self.indicators_for_trades.get(&trade.instrument_id) {
215 for indicator in indicators {
216 indicator.handle_trade(trade)?;
217 }
218 }
219
220 Ok(())
221 }
222
223 pub fn handle_trades(&self, trades: &[TradeTick]) -> anyhow::Result<()> {
229 for trade in trades {
230 self.handle_trade(trade)?;
231 }
232
233 Ok(())
234 }
235
236 pub fn handle_bar(&self, bar: &Bar) -> anyhow::Result<()> {
242 if let Some(indicators) = self.indicators_for_bars.get(&bar.bar_type.id_spec_key()) {
243 for indicator in indicators {
244 indicator.handle_bar(bar)?;
245 }
246 }
247
248 Ok(())
249 }
250
251 pub fn handle_bars(&self, bars: &[Bar]) -> anyhow::Result<()> {
257 for bar in bars {
258 self.handle_bar(bar)?;
259 }
260
261 Ok(())
262 }
263
264 fn register_indicator(&mut self, indicator: SharedActorIndicator) {
265 if !contains_indicator(&self.indicators, &indicator) {
266 self.indicators.push(indicator);
267 }
268 }
269
270 fn register_by_key(
271 &mut self,
272 instrument_id: InstrumentId,
273 indicator: SharedActorIndicator,
274 kind: IndicatorKind,
275 ) {
276 let indicators = match kind {
277 IndicatorKind::Quote => self.indicators_for_quotes.entry(instrument_id).or_default(),
278 IndicatorKind::Trade => self.indicators_for_trades.entry(instrument_id).or_default(),
279 };
280
281 if !contains_indicator(indicators, &indicator) {
282 indicators.push(indicator);
283 }
284 }
285
286 fn register_bar(
287 &mut self,
288 bar_key: (InstrumentId, BarSpecification),
289 indicator: SharedActorIndicator,
290 ) {
291 let indicators = self.indicators_for_bars.entry(bar_key).or_default();
292
293 if !contains_indicator(indicators, &indicator) {
294 indicators.push(indicator);
295 }
296 }
297}
298
299#[derive(Clone, Copy)]
300enum IndicatorKind {
301 Quote,
302 Trade,
303}
304
305fn contains_indicator(
306 indicators: &[SharedActorIndicator],
307 indicator: &SharedActorIndicator,
308) -> bool {
309 let indicator_key = indicator.key();
310 indicators
311 .iter()
312 .any(|registered| registered.key() == indicator_key)
313}
314
315#[cfg(test)]
316mod tests {
317 #[cfg(feature = "indicators")]
318 use std::cell::RefCell;
319 use std::{
320 any::Any,
321 cell::Cell,
322 rc::Rc,
323 str::FromStr,
324 sync::atomic::{AtomicUsize, Ordering},
325 };
326
327 #[cfg(feature = "indicators")]
328 use nautilus_indicators::indicator::Indicator;
329 use nautilus_model::data::{Bar, BarType, QuoteTick, TradeTick};
330 use rstest::rstest;
331
332 use super::{ActorIndicator, Indicators, SharedActorIndicator};
333
334 static NEXT_KEY: AtomicUsize = AtomicUsize::new(1);
335
336 #[derive(Debug)]
337 struct TrackingIndicator {
338 key: usize,
339 initialized: Cell<bool>,
340 quotes: Cell<usize>,
341 trades: Cell<usize>,
342 bars: Cell<usize>,
343 }
344
345 impl TrackingIndicator {
346 fn new() -> Self {
347 Self {
348 key: NEXT_KEY.fetch_add(1, Ordering::Relaxed),
349 initialized: Cell::new(false),
350 quotes: Cell::new(0),
351 trades: Cell::new(0),
352 bars: Cell::new(0),
353 }
354 }
355
356 fn set_initialized(&self, initialized: bool) {
357 self.initialized.set(initialized);
358 }
359 }
360
361 impl ActorIndicator for TrackingIndicator {
362 fn key(&self) -> usize {
363 self.key
364 }
365
366 fn as_any(&self) -> &dyn Any {
367 self
368 }
369
370 fn initialized(&self) -> anyhow::Result<bool> {
371 Ok(self.initialized.get())
372 }
373
374 fn handle_quote(&self, _quote: &QuoteTick) -> anyhow::Result<()> {
375 self.quotes.set(self.quotes.get() + 1);
376 Ok(())
377 }
378
379 fn handle_trade(&self, _trade: &TradeTick) -> anyhow::Result<()> {
380 self.trades.set(self.trades.get() + 1);
381 Ok(())
382 }
383
384 fn handle_bar(&self, _bar: &Bar) -> anyhow::Result<()> {
385 self.bars.set(self.bars.get() + 1);
386 Ok(())
387 }
388 }
389
390 #[derive(Debug)]
391 struct ErrorIndicator {
392 key: usize,
393 }
394
395 impl ErrorIndicator {
396 fn new() -> Self {
397 Self {
398 key: NEXT_KEY.fetch_add(1, Ordering::Relaxed),
399 }
400 }
401 }
402
403 impl ActorIndicator for ErrorIndicator {
404 fn key(&self) -> usize {
405 self.key
406 }
407
408 fn as_any(&self) -> &dyn Any {
409 self
410 }
411
412 fn initialized(&self) -> anyhow::Result<bool> {
413 Ok(true)
414 }
415
416 fn handle_quote(&self, _quote: &QuoteTick) -> anyhow::Result<()> {
417 anyhow::bail!("indicator failed");
418 }
419
420 fn handle_trade(&self, _trade: &TradeTick) -> anyhow::Result<()> {
421 anyhow::bail!("trade indicator failed");
422 }
423
424 fn handle_bar(&self, _bar: &Bar) -> anyhow::Result<()> {
425 anyhow::bail!("bar indicator failed");
426 }
427 }
428
429 #[cfg(feature = "indicators")]
430 #[derive(Debug, Default)]
431 struct NativeIndicator {
432 initialized: bool,
433 quotes: Vec<QuoteTick>,
434 trades: Vec<TradeTick>,
435 bars: Vec<Bar>,
436 }
437
438 #[cfg(feature = "indicators")]
439 impl Indicator for NativeIndicator {
440 fn name(&self) -> String {
441 stringify!(NativeIndicator).to_string()
442 }
443
444 fn has_inputs(&self) -> bool {
445 !(self.quotes.is_empty() && self.trades.is_empty() && self.bars.is_empty())
446 }
447
448 fn initialized(&self) -> bool {
449 self.initialized
450 }
451
452 fn handle_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
453 self.quotes.push(*quote);
454 Ok(())
455 }
456
457 fn handle_trade(&mut self, trade: &TradeTick) {
458 self.trades.push(*trade);
459 }
460
461 fn handle_bar(&mut self, bar: &Bar) {
462 self.bars.push(*bar);
463 }
464
465 fn reset(&mut self) {
466 self.initialized = false;
467 self.quotes.clear();
468 self.trades.clear();
469 self.bars.clear();
470 }
471 }
472
473 #[rstest]
474 fn test_register_indicator_routes_quote_trade_and_bar_with_deduplication() {
475 let mut indicators = Indicators::default();
476 let indicator = Rc::new(TrackingIndicator::new());
477 let registered: SharedActorIndicator = indicator.clone();
478 let quote = QuoteTick::default();
479 let trade = TradeTick::default();
480 let bar = Bar::default();
481 let external_bar_type = BarType::from_str(&format!(
482 "{}-1-MINUTE-LAST-EXTERNAL",
483 bar.bar_type.instrument_id()
484 ))
485 .unwrap();
486
487 indicators.register_indicator_for_quote_ticks(quote.instrument_id, registered.clone());
488 indicators.register_indicator_for_quote_ticks(quote.instrument_id, registered.clone());
489 indicators.register_indicator_for_trade_ticks(trade.instrument_id, registered.clone());
490 indicators.register_indicator_for_trade_ticks(trade.instrument_id, registered.clone());
491 indicators.register_indicator_for_bars(external_bar_type, registered.clone());
492 indicators.register_indicator_for_bars(external_bar_type, registered);
493
494 indicators.handle_quote("e).unwrap();
495 indicators.handle_trade(&trade).unwrap();
496 indicators.handle_bar(&bar).unwrap();
497
498 assert_eq!(indicators.registered_indicators().len(), 1);
499 assert_eq!(indicator.quotes.get(), 1);
500 assert_eq!(indicator.trades.get(), 1);
501 assert_eq!(indicator.bars.get(), 1);
502 }
503
504 #[rstest]
505 fn test_initialized_requires_all_registered_indicators() {
506 let mut indicators = Indicators::default();
507 let first = Rc::new(TrackingIndicator::new());
508 let second = Rc::new(TrackingIndicator::new());
509 let quote = QuoteTick::default();
510
511 indicators.register_indicator_for_quote_ticks(quote.instrument_id, first.clone());
512 indicators.register_indicator_for_quote_ticks(quote.instrument_id, second.clone());
513
514 first.set_initialized(true);
515
516 assert!(!indicators.initialized().unwrap());
517
518 second.set_initialized(true);
519
520 assert!(indicators.initialized().unwrap());
521 }
522
523 #[rstest]
524 fn test_handle_quote_propagates_indicator_error() {
525 let mut indicators = Indicators::default();
526 let indicator = Rc::new(ErrorIndicator::new());
527 let quote = QuoteTick::default();
528
529 indicators.register_indicator_for_quote_ticks(quote.instrument_id, indicator);
530
531 let err = indicators.handle_quote("e).unwrap_err();
532
533 assert_eq!(err.to_string(), "indicator failed");
534 }
535
536 #[rstest]
537 fn test_handle_trade_and_bar_propagate_indicator_errors() {
538 let trade = TradeTick::default();
539 let bar = Bar::default();
540 let trade_indicator = Rc::new(ErrorIndicator::new());
541 let bar_indicator = Rc::new(ErrorIndicator::new());
542 let mut indicators = Indicators::default();
543
544 indicators.register_indicator_for_trade_ticks(trade.instrument_id, trade_indicator);
545 indicators.register_indicator_for_bars(bar.bar_type, bar_indicator);
546
547 let trade_error = indicators.handle_trade(&trade).unwrap_err();
548 let bar_error = indicators.handle_bar(&bar).unwrap_err();
549
550 assert_eq!(trade_error.to_string(), "trade indicator failed");
551 assert_eq!(bar_error.to_string(), "bar indicator failed");
552 }
553
554 #[rstest]
555 fn test_handle_quotes_stops_after_first_indicator_error() {
556 let quote = QuoteTick::default();
557 let error_indicator = Rc::new(ErrorIndicator::new());
558 let tracking_indicator = Rc::new(TrackingIndicator::new());
559 let mut indicators = Indicators::default();
560
561 indicators
562 .register_indicator_for_quote_ticks(quote.instrument_id, tracking_indicator.clone());
563 indicators.register_indicator_for_quote_ticks(quote.instrument_id, error_indicator);
564
565 let error = indicators.handle_quotes(&[quote, quote]).unwrap_err();
566
567 assert_eq!(error.to_string(), "indicator failed");
568 assert_eq!(tracking_indicator.quotes.get(), 1);
569 }
570
571 #[cfg(feature = "indicators")]
572 #[rstest]
573 fn test_native_indicator_bridge_forwards_readiness_and_market_data() {
574 let quote = QuoteTick::default();
575 let trade = TradeTick::default();
576 let bar = Bar::default();
577 let indicator = Rc::new(RefCell::new(NativeIndicator::default()));
578 let registered: SharedActorIndicator = indicator.clone();
579 let mut indicators = Indicators::default();
580
581 indicators.register_indicator_for_quote_ticks(quote.instrument_id, registered.clone());
582 indicators.register_indicator_for_trade_ticks(trade.instrument_id, registered.clone());
583 indicators.register_indicator_for_bars(bar.bar_type, registered);
584
585 assert!(!indicators.initialized().unwrap());
586
587 indicator.borrow_mut().initialized = true;
588 indicators.handle_quote("e).unwrap();
589 indicators.handle_trade(&trade).unwrap();
590 indicators.handle_bar(&bar).unwrap();
591
592 let registered = indicators.registered_indicators();
593 let indicator = indicator.borrow();
594 assert_eq!(registered.len(), 1);
595 assert!(registered[0].as_any().is::<RefCell<NativeIndicator>>());
596 assert!(indicators.initialized().unwrap());
597 assert_eq!(indicator.quotes, vec![quote]);
598 assert_eq!(indicator.trades, vec![trade]);
599 assert_eq!(indicator.bars, vec![bar]);
600 }
601}