Skip to main content

egui_charts/studies/
mod.rs

1//! Technical indicators, calculation helpers and study presentation.
2//!
3//! Implement [`Indicator`] or use [`CustomIndicator`] for a host-defined study;
4//! [`IndicatorRegistry`] calculates a group of studies without a widget. Enable
5//! `studies` for the built-in catalogue and `IndicatorFactory`. A chart owns the
6//! calculation and rendering lifecycle of studies added through `Chart::add_indicator`.
7//!
8//! # Calculating a study
9//!
10//! Outputs contain a contiguous column per line. Missing and warm-up results are
11//! NaN; [`IndicatorValues::value`] returns finite results as `Option<f64>`.
12//!
13//! ```
14//! # #[cfg(feature = "studies")] {
15//! use egui_charts::{model::Bar, studies::{Indicator, SMA}};
16//!
17//! let bars = vec![
18//!     Bar::new("2024-01-01T00:00:00Z".parse().unwrap(), 100.0, 102.0, 99.0, 101.0, 10.0),
19//!     Bar::new("2024-01-01T00:01:00Z".parse().unwrap(), 101.0, 103.0, 100.0, 103.0, 20.0),
20//! ];
21//! let mut sma = SMA::new(2);
22//! sma.calculate(&bars);
23//! assert_eq!(sma.values().value(0, 1), Some(102.0));
24//! # }
25//! ```
26//!
27//! [`helpers`] provides scalar calculations for custom studies, and [`Stepper`]
28//! provides incremental calculation state. [`StudyData`] carries auxiliary data
29//! requested from the host's feed.
30
31#[cfg(feature = "studies")]
32#[cfg_attr(docsrs, doc(cfg(feature = "studies")))]
33mod builtin;
34mod custom;
35mod error;
36pub use crate::data::{StudyData, StudyDataRequest};
37pub use error::{RefusedStudy, StudyError};
38#[cfg(feature = "studies")]
39#[cfg_attr(docsrs, doc(cfg(feature = "studies")))]
40pub mod factory;
41mod format;
42pub mod helpers;
43mod indicator_trait;
44pub mod palette;
45pub use format::StudyScale;
46mod inputs;
47mod metric;
48pub use inputs::{InputKind, InputValue, StudyInput, StudySource};
49pub use metric::MetricStudy;
50pub(crate) mod plots;
51pub(crate) mod projection;
52mod settings;
53pub(crate) mod table;
54pub use plots::{FillBoundary, LabelPlacement, StudyCandle, StudyFill, StudyLabel, StudyZone};
55pub use projection::StudyProjection;
56pub use settings::{
57    ConfiguredStudy, LevelStyle, PlotStyle, PlotType, StudyIntervalRange, StudySettings,
58    StudySettingsSnapshot,
59};
60pub use table::{StudyCell, StudyTable};
61mod values;
62pub use values::IndicatorValues;
63#[cfg(all(test, feature = "studies"))]
64mod reference;
65mod stepper;
66#[cfg(all(test, feature = "studies"))]
67mod test_support;
68pub use stepper::Stepper;
69
70#[cfg(feature = "studies")]
71#[cfg_attr(docsrs, doc(cfg(feature = "studies")))]
72pub use builtin::*;
73pub use custom::CustomIndicator;
74#[cfg(feature = "studies")]
75#[cfg_attr(docsrs, doc(cfg(feature = "studies")))]
76pub use factory::IndicatorFactory;
77pub use indicator_trait::{
78    CrossDirection, Indicator, IndicatorFill, IndicatorLineStyle, IndicatorMarker, IndicatorValue,
79    StudyPane,
80};
81
82mod chart_state;
83pub use chart_state::{ChartState, ZoomState};
84
85#[cfg(feature = "studies")]
86mod template;
87#[cfg(feature = "studies")]
88#[cfg_attr(docsrs, doc(cfg(feature = "studies")))]
89pub use template::{StudyTemplate, StudyTemplateSet};
90
91use crate::model::Bar;
92
93/// A registry that owns a collection of [`Indicator`] trait objects and
94/// provides batch operations (calculate all, clear, etc.).
95///
96/// Use the registry when you want to manage multiple indicators as a group
97/// and recalculate them together whenever the underlying bar data changes.
98///
99/// # Example
100///
101/// ```rust
102/// # #[cfg(feature = "studies")] {
103/// # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
104/// use egui_charts::studies::{IndicatorRegistry, SMA, EMA};
105///
106/// let mut registry = IndicatorRegistry::new();
107/// registry.add(Box::new(SMA::new(20)));
108/// registry.add(Box::new(EMA::new(50)));
109/// registry.calculate_all(&bars);
110///
111/// for indicator in registry.indicators() {
112///     println!("{}: {} values", indicator.name(), indicator.values().len());
113/// }
114/// # }
115/// ```
116pub struct IndicatorRegistry {
117    indicators: Vec<Box<dyn Indicator>>,
118}
119
120impl IndicatorRegistry {
121    /// Create an empty registry with no indicators.
122    pub fn new() -> Self {
123        Self {
124            indicators: Vec::new(),
125        }
126    }
127
128    /// Append a boxed indicator to the registry.
129    pub fn add_indicator(&mut self, indicator: Box<dyn Indicator>) {
130        self.indicators.push(indicator);
131    }
132
133    /// Alias for [`add_indicator`](Self::add_indicator) for brevity.
134    pub fn add(&mut self, indicator: Box<dyn Indicator>) {
135        self.add_indicator(indicator);
136    }
137
138    /// Remove and return the indicator at `index`, or `None` if out of bounds.
139    pub fn remove_indicator(&mut self, index: usize) -> Option<Box<dyn Indicator>> {
140        if index < self.indicators.len() {
141            Some(self.indicators.remove(index))
142        } else {
143            None
144        }
145    }
146
147    /// Return a shared slice of all registered indicators.
148    pub fn indicators(&self) -> &[Box<dyn Indicator>] {
149        &self.indicators
150    }
151
152    /// Return a mutable slice of all registered indicators.
153    pub fn indicators_mut(&mut self) -> &mut [Box<dyn Indicator>] {
154        &mut self.indicators
155    }
156
157    /// Recalculate every indicator against the provided bar data.
158    ///
159    /// This iterates through all registered indicators and calls
160    /// [`Indicator::calculate`] on each one.
161    pub fn calculate_all(&mut self, data: &[Bar]) {
162        for indicator in &mut self.indicators {
163            indicator.calculate(data);
164        }
165    }
166
167    /// Bring every indicator up to date after `data[from..]` changed or grew,
168    /// given that `data[..from]` is unchanged since the previous call.
169    ///
170    /// Calls [`Indicator::update`] on each one; studies that are not
171    /// incremental recompute in full.
172    pub fn update_all(&mut self, data: &[Bar], from: usize) {
173        for indicator in &mut self.indicators {
174            indicator.update(data, from);
175        }
176    }
177
178    /// Remove all indicators from the registry.
179    pub fn clear(&mut self) {
180        self.indicators.clear();
181    }
182}
183
184impl Default for IndicatorRegistry {
185    fn default() -> Self {
186        Self::new()
187    }
188}