Skip to main content

egui_charts/studies/builtin/
dema.rs

1//! Double Exponential Moving Avg (DEMA) Indicator
2//!
3//! DEMA reduces the lag inherent in EMAs by applying EMA twice and
4//! then subtracting a smoothed EMA.
5//!
6//! # Formula
7//! DEMA = 2 * EMA(price, period) - EMA(EMA(price, period), period)
8//!
9//! # Example
10//! ```
11//! # 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();
12//! use egui_charts::studies::{DEMA, Indicator};
13//!
14//! let mut dema = DEMA::new(14);
15//! dema.calculate(&bars);
16//! ```
17
18use super::smoothing::Smoothing;
19use crate::model::Bar;
20use crate::studies::palette;
21use crate::studies::stepper::Stepper;
22use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
23use egui::Color32;
24
25/// Double Exponential Moving Avg indicator
26#[derive(Clone)]
27pub struct DEMA {
28    period: usize,
29    values: IndicatorValues,
30    stepper: Stepper<[Smoothing; 2]>,
31    color: Color32,
32    visible: bool,
33}
34
35impl DEMA {
36    /// Create a new DEMA indicator
37    ///
38    /// # Arguments
39    /// * `period` - Number of periods for calculation
40    pub fn new(period: usize) -> Self {
41        Self {
42            period: period.max(1),
43            values: IndicatorValues::new(),
44            stepper: Stepper::default(),
45            color: palette::DARK_GREEN,
46            visible: true,
47        }
48    }
49
50    /// Set the line color
51    pub fn with_color(mut self, color: Color32) -> Self {
52        self.color = color;
53        self
54    }
55}
56
57/// Construct with the conventional default parameters.
58impl Default for DEMA {
59    fn default() -> Self {
60        Self::new(9)
61    }
62}
63
64impl Indicator for DEMA {
65    fn source(&self) -> Option<crate::studies::StudySource> {
66        Some(crate::studies::StudySource::Price(
67            crate::model::PriceSource::Close,
68        ))
69    }
70
71    crate::studies::inputs::study_inputs!(self;
72        period: length("Period", 1, usize::MAX)
73    ; {});
74
75    fn name(&self) -> &str {
76        "DEMA"
77    }
78
79    fn desc(&self) -> &str {
80        "Double Exponential Moving Avg - Reduces lag compared to EMA"
81    }
82
83    fn calculate(&mut self, data: &[Bar]) {
84        self.update(data, 0);
85    }
86
87    fn update(&mut self, data: &[Bar], from: usize) {
88        self.stepper
89            .run(data, from, &mut self.values, |state, bar| {
90                if !bar.close.is_finite() {
91                    return IndicatorValue::None;
92                }
93                let alpha = 2.0 / (self.period as f64 + 1.0);
94                let ema1 = state[0].next(bar.close, self.period, alpha);
95                let ema2 = state[1].next(ema1, self.period, alpha);
96                IndicatorValue::Single(2.0 * ema1 - ema2)
97            });
98    }
99
100    fn is_incremental(&self) -> bool {
101        true
102    }
103
104    fn values(&self) -> &IndicatorValues {
105        &self.values
106    }
107
108    fn colors(&self) -> Vec<Color32> {
109        vec![self.color]
110    }
111
112    fn set_colors(&mut self, colors: Vec<Color32>) {
113        if !colors.is_empty() {
114            self.color = colors[0];
115        }
116    }
117
118    fn is_overlay(&self) -> bool {
119        true
120    }
121
122    fn line_count(&self) -> usize {
123        1
124    }
125
126    fn is_visible(&self) -> bool {
127        self.visible
128    }
129
130    fn set_visible(&mut self, visible: bool) {
131        self.visible = visible;
132    }
133
134    fn clone_box(&self) -> Box<dyn Indicator> {
135        Box::new(self.clone())
136    }
137
138    fn line_names(&self) -> Vec<String> {
139        vec![format!("DEMA({})", self.period)]
140    }
141}