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 Ok(())
88 }
89
90 fn handle_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
91 self.borrow_mut().handle_trade(trade);
92 Ok(())
93 }
94
95 fn handle_bar(&self, bar: &Bar) -> anyhow::Result<()> {
96 self.borrow_mut().handle_bar(bar);
97 Ok(())
98 }
99}
100
101#[derive(Clone, Default)]
103#[allow(
104 clippy::struct_field_names,
105 reason = "indicator-prefixed fields denote distinct indicator collections"
106)]
107pub struct Indicators {
108 indicators: Vec<SharedActorIndicator>,
109 indicators_for_quotes: AHashMap<InstrumentId, Vec<SharedActorIndicator>>,
110 indicators_for_trades: AHashMap<InstrumentId, Vec<SharedActorIndicator>>,
111 indicators_for_bars: AHashMap<(InstrumentId, BarSpecification), Vec<SharedActorIndicator>>,
112}
113
114impl Debug for Indicators {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct(stringify!(Indicators))
117 .field("indicators", &self.indicators.len())
118 .field("indicators_for_quotes", &self.indicators_for_quotes.len())
119 .field("indicators_for_trades", &self.indicators_for_trades.len())
120 .field("indicators_for_bars", &self.indicators_for_bars.len())
121 .finish()
122 }
123}
124
125impl Indicators {
126 #[must_use]
128 pub fn registered_indicators(&self) -> Vec<SharedActorIndicator> {
129 self.indicators.clone()
130 }
131
132 pub fn initialized(&self) -> anyhow::Result<bool> {
138 if self.indicators.is_empty() {
139 return Ok(false);
140 }
141
142 for indicator in &self.indicators {
143 if !indicator.initialized()? {
144 return Ok(false);
145 }
146 }
147
148 Ok(true)
149 }
150
151 pub fn register_indicator_for_quote_ticks(
153 &mut self,
154 instrument_id: InstrumentId,
155 indicator: SharedActorIndicator,
156 ) {
157 self.register_indicator(indicator.clone());
158 self.register_by_key(instrument_id, indicator, IndicatorKind::Quote);
159 }
160
161 pub fn register_indicator_for_trade_ticks(
163 &mut self,
164 instrument_id: InstrumentId,
165 indicator: SharedActorIndicator,
166 ) {
167 self.register_indicator(indicator.clone());
168 self.register_by_key(instrument_id, indicator, IndicatorKind::Trade);
169 }
170
171 pub fn register_indicator_for_bars(
173 &mut self,
174 bar_type: BarType,
175 indicator: SharedActorIndicator,
176 ) {
177 self.register_indicator(indicator.clone());
178 self.register_bar(bar_type.id_spec_key(), indicator);
179 }
180
181 pub fn handle_quote(&self, quote: &QuoteTick) -> anyhow::Result<()> {
187 if let Some(indicators) = self.indicators_for_quotes.get("e.instrument_id) {
188 for indicator in indicators {
189 indicator.handle_quote(quote)?;
190 }
191 }
192
193 Ok(())
194 }
195
196 pub fn handle_quotes(&self, quotes: &[QuoteTick]) -> anyhow::Result<()> {
202 for quote in quotes {
203 self.handle_quote(quote)?;
204 }
205
206 Ok(())
207 }
208
209 pub fn handle_trade(&self, trade: &TradeTick) -> anyhow::Result<()> {
215 if let Some(indicators) = self.indicators_for_trades.get(&trade.instrument_id) {
216 for indicator in indicators {
217 indicator.handle_trade(trade)?;
218 }
219 }
220
221 Ok(())
222 }
223
224 pub fn handle_trades(&self, trades: &[TradeTick]) -> anyhow::Result<()> {
230 for trade in trades {
231 self.handle_trade(trade)?;
232 }
233
234 Ok(())
235 }
236
237 pub fn handle_bar(&self, bar: &Bar) -> anyhow::Result<()> {
243 if let Some(indicators) = self.indicators_for_bars.get(&bar.bar_type.id_spec_key()) {
244 for indicator in indicators {
245 indicator.handle_bar(bar)?;
246 }
247 }
248
249 Ok(())
250 }
251
252 pub fn handle_bars(&self, bars: &[Bar]) -> anyhow::Result<()> {
258 for bar in bars {
259 self.handle_bar(bar)?;
260 }
261
262 Ok(())
263 }
264
265 fn register_indicator(&mut self, indicator: SharedActorIndicator) {
266 if !contains_indicator(&self.indicators, &indicator) {
267 self.indicators.push(indicator);
268 }
269 }
270
271 fn register_by_key(
272 &mut self,
273 instrument_id: InstrumentId,
274 indicator: SharedActorIndicator,
275 kind: IndicatorKind,
276 ) {
277 let indicators = match kind {
278 IndicatorKind::Quote => self.indicators_for_quotes.entry(instrument_id).or_default(),
279 IndicatorKind::Trade => self.indicators_for_trades.entry(instrument_id).or_default(),
280 };
281
282 if !contains_indicator(indicators, &indicator) {
283 indicators.push(indicator);
284 }
285 }
286
287 fn register_bar(
288 &mut self,
289 bar_key: (InstrumentId, BarSpecification),
290 indicator: SharedActorIndicator,
291 ) {
292 let indicators = self.indicators_for_bars.entry(bar_key).or_default();
293
294 if !contains_indicator(indicators, &indicator) {
295 indicators.push(indicator);
296 }
297 }
298}
299
300#[derive(Clone, Copy)]
301enum IndicatorKind {
302 Quote,
303 Trade,
304}
305
306fn contains_indicator(
307 indicators: &[SharedActorIndicator],
308 indicator: &SharedActorIndicator,
309) -> bool {
310 let indicator_key = indicator.key();
311 indicators
312 .iter()
313 .any(|registered| registered.key() == indicator_key)
314}
315
316#[cfg(test)]
317mod tests {
318 use std::{
319 any::Any,
320 cell::Cell,
321 rc::Rc,
322 str::FromStr,
323 sync::atomic::{AtomicUsize, Ordering},
324 };
325
326 use nautilus_model::data::{Bar, BarType, QuoteTick, TradeTick};
327 use rstest::rstest;
328
329 use super::{ActorIndicator, Indicators, SharedActorIndicator};
330
331 static NEXT_KEY: AtomicUsize = AtomicUsize::new(1);
332
333 #[derive(Debug)]
334 struct TrackingIndicator {
335 key: usize,
336 initialized: Cell<bool>,
337 quotes: Cell<usize>,
338 trades: Cell<usize>,
339 bars: Cell<usize>,
340 }
341
342 impl TrackingIndicator {
343 fn new() -> Self {
344 Self {
345 key: NEXT_KEY.fetch_add(1, Ordering::Relaxed),
346 initialized: Cell::new(false),
347 quotes: Cell::new(0),
348 trades: Cell::new(0),
349 bars: Cell::new(0),
350 }
351 }
352
353 fn set_initialized(&self, initialized: bool) {
354 self.initialized.set(initialized);
355 }
356 }
357
358 impl ActorIndicator for TrackingIndicator {
359 fn key(&self) -> usize {
360 self.key
361 }
362
363 fn as_any(&self) -> &dyn Any {
364 self
365 }
366
367 fn initialized(&self) -> anyhow::Result<bool> {
368 Ok(self.initialized.get())
369 }
370
371 fn handle_quote(&self, _quote: &QuoteTick) -> anyhow::Result<()> {
372 self.quotes.set(self.quotes.get() + 1);
373 Ok(())
374 }
375
376 fn handle_trade(&self, _trade: &TradeTick) -> anyhow::Result<()> {
377 self.trades.set(self.trades.get() + 1);
378 Ok(())
379 }
380
381 fn handle_bar(&self, _bar: &Bar) -> anyhow::Result<()> {
382 self.bars.set(self.bars.get() + 1);
383 Ok(())
384 }
385 }
386
387 #[derive(Debug)]
388 struct ErrorIndicator {
389 key: usize,
390 }
391
392 impl ErrorIndicator {
393 fn new() -> Self {
394 Self {
395 key: NEXT_KEY.fetch_add(1, Ordering::Relaxed),
396 }
397 }
398 }
399
400 impl ActorIndicator for ErrorIndicator {
401 fn key(&self) -> usize {
402 self.key
403 }
404
405 fn as_any(&self) -> &dyn Any {
406 self
407 }
408
409 fn initialized(&self) -> anyhow::Result<bool> {
410 Ok(true)
411 }
412
413 fn handle_quote(&self, _quote: &QuoteTick) -> anyhow::Result<()> {
414 anyhow::bail!("indicator failed");
415 }
416
417 fn handle_trade(&self, _trade: &TradeTick) -> anyhow::Result<()> {
418 Ok(())
419 }
420
421 fn handle_bar(&self, _bar: &Bar) -> anyhow::Result<()> {
422 Ok(())
423 }
424 }
425
426 #[rstest]
427 fn test_register_indicator_routes_quote_trade_and_bar_with_deduplication() {
428 let mut indicators = Indicators::default();
429 let indicator = Rc::new(TrackingIndicator::new());
430 let registered: SharedActorIndicator = indicator.clone();
431 let quote = QuoteTick::default();
432 let trade = TradeTick::default();
433 let bar = Bar::default();
434 let external_bar_type = BarType::from_str(&format!(
435 "{}-1-MINUTE-LAST-EXTERNAL",
436 bar.bar_type.instrument_id()
437 ))
438 .unwrap();
439
440 indicators.register_indicator_for_quote_ticks(quote.instrument_id, registered.clone());
441 indicators.register_indicator_for_quote_ticks(quote.instrument_id, registered.clone());
442 indicators.register_indicator_for_trade_ticks(trade.instrument_id, registered.clone());
443 indicators.register_indicator_for_trade_ticks(trade.instrument_id, registered.clone());
444 indicators.register_indicator_for_bars(external_bar_type, registered.clone());
445 indicators.register_indicator_for_bars(external_bar_type, registered);
446
447 indicators.handle_quote("e).unwrap();
448 indicators.handle_trade(&trade).unwrap();
449 indicators.handle_bar(&bar).unwrap();
450
451 assert_eq!(indicators.registered_indicators().len(), 1);
452 assert_eq!(indicator.quotes.get(), 1);
453 assert_eq!(indicator.trades.get(), 1);
454 assert_eq!(indicator.bars.get(), 1);
455 }
456
457 #[rstest]
458 fn test_initialized_requires_all_registered_indicators() {
459 let mut indicators = Indicators::default();
460 let first = Rc::new(TrackingIndicator::new());
461 let second = Rc::new(TrackingIndicator::new());
462 let quote = QuoteTick::default();
463
464 indicators.register_indicator_for_quote_ticks(quote.instrument_id, first.clone());
465 indicators.register_indicator_for_quote_ticks(quote.instrument_id, second.clone());
466
467 first.set_initialized(true);
468
469 assert!(!indicators.initialized().unwrap());
470
471 second.set_initialized(true);
472
473 assert!(indicators.initialized().unwrap());
474 }
475
476 #[rstest]
477 fn test_handle_quote_propagates_indicator_error() {
478 let mut indicators = Indicators::default();
479 let indicator = Rc::new(ErrorIndicator::new());
480 let quote = QuoteTick::default();
481
482 indicators.register_indicator_for_quote_ticks(quote.instrument_id, indicator);
483
484 let err = indicators.handle_quote("e).unwrap_err();
485
486 assert_eq!(err.to_string(), "indicator failed");
487 }
488}