Skip to main content

egui_charts/studies/builtin/
smoothed_ma.rs

1use crate::model::Bar;
2use crate::studies::palette;
3use crate::studies::stepper::Stepper;
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7/// Smoothed Moving Average (Wilder's Smoothing)
8#[derive(Clone)]
9pub struct SmoothedMA {
10    period: usize,
11    values: IndicatorValues,
12    stepper: Stepper<(usize, f64)>,
13    color: Color32,
14    visible: bool,
15}
16
17impl SmoothedMA {
18    /// Create with a lookback of `period` bars, clamped to at least 1.
19    pub fn new(period: usize) -> Self {
20        Self {
21            period: period.max(1),
22            values: IndicatorValues::new(),
23            stepper: Stepper::default(),
24            color: palette::DEEP_PURPLE,
25            visible: true,
26        }
27    }
28
29    /// Set the plot color.
30    pub fn with_color(mut self, color: Color32) -> Self {
31        self.color = color;
32        self
33    }
34}
35
36impl Default for SmoothedMA {
37    fn default() -> Self {
38        Self::new(7)
39    }
40}
41
42impl Indicator for SmoothedMA {
43    fn source(&self) -> Option<crate::studies::StudySource> {
44        Some(crate::studies::StudySource::Price(
45            crate::model::PriceSource::Close,
46        ))
47    }
48
49    crate::studies::inputs::study_inputs!(self;
50        period: length("Period", 1, usize::MAX)
51    ; {});
52
53    fn name(&self) -> &str {
54        "SMMA"
55    }
56
57    fn desc(&self) -> &str {
58        "Smoothed Moving Average - Wilder's smoothing method"
59    }
60
61    fn calculate(&mut self, data: &[Bar]) {
62        self.update(data, 0);
63    }
64
65    fn update(&mut self, data: &[Bar], from: usize) {
66        self.stepper
67            .run(data, from, &mut self.values, |state, bar| {
68                if !bar.close.is_finite() {
69                    return IndicatorValue::None;
70                }
71                if state.0 == 0 {
72                    state.1 = -0.0;
73                }
74                if state.0 < self.period {
75                    state.0 += 1;
76                    state.1 += bar.close;
77                    if state.0 < self.period {
78                        return IndicatorValue::None;
79                    }
80                    state.1 /= self.period as f64;
81                } else {
82                    state.1 =
83                        (state.1 * (self.period as f64 - 1.0) + bar.close) / self.period as f64;
84                }
85                IndicatorValue::Single(state.1)
86            });
87    }
88
89    fn is_incremental(&self) -> bool {
90        true
91    }
92
93    fn values(&self) -> &IndicatorValues {
94        &self.values
95    }
96
97    fn colors(&self) -> Vec<Color32> {
98        vec![self.color]
99    }
100
101    fn set_colors(&mut self, colors: Vec<Color32>) {
102        if !colors.is_empty() {
103            self.color = colors[0];
104        }
105    }
106
107    fn is_overlay(&self) -> bool {
108        true
109    }
110
111    fn is_visible(&self) -> bool {
112        self.visible
113    }
114
115    fn set_visible(&mut self, visible: bool) {
116        self.visible = visible;
117    }
118
119    fn clone_box(&self) -> Box<dyn Indicator> {
120        Box::new(self.clone())
121    }
122
123    fn line_names(&self) -> Vec<String> {
124        vec![format!("SMMA({})", self.period)]
125    }
126}