Skip to main content

egui_charts/studies/builtin/
mcginley_dynamic.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/// McGinley Dynamic: an average that speeds up when price falls away from it and slows down when
8/// price rises above it. `MD = MD[1] + (close - MD[1]) / (N x (close / MD[1])^4)` with N the
9/// period, starting at the first close. While MD is zero, or the divisor is zero, MD keeps its
10/// value.
11#[derive(Clone)]
12pub struct McGinleyDynamic {
13    /// Period (typically 10 or 14)
14    period: usize,
15    values: IndicatorValues,
16    stepper: Stepper<Option<f64>>,
17    color: Color32,
18    visible: bool,
19}
20
21impl McGinleyDynamic {
22    /// Create with a lookback of `period` bars, clamped to at least 1.
23    pub fn new(period: usize) -> Self {
24        Self {
25            period: period.max(1),
26            values: IndicatorValues::new(),
27            stepper: Stepper::default(),
28            color: palette::LIGHT_BLUE,
29            visible: true,
30        }
31    }
32
33    /// Set the plot color.
34    pub fn with_color(mut self, color: Color32) -> Self {
35        self.color = color;
36        self
37    }
38}
39
40impl Default for McGinleyDynamic {
41    fn default() -> Self {
42        Self::new(14)
43    }
44}
45
46impl Indicator for McGinleyDynamic {
47    fn source(&self) -> Option<crate::studies::StudySource> {
48        Some(crate::studies::StudySource::Price(
49            crate::model::PriceSource::Close,
50        ))
51    }
52
53    crate::studies::inputs::study_inputs!(self;
54        period: length("Period", 1, usize::MAX)
55    ; {});
56
57    fn name(&self) -> &str {
58        "MD"
59    }
60
61    fn desc(&self) -> &str {
62        "McGinley Dynamic - Self-adjusting moving avg"
63    }
64
65    fn calculate(&mut self, data: &[Bar]) {
66        self.update(data, 0);
67    }
68
69    fn update(&mut self, data: &[Bar], from: usize) {
70        self.stepper
71            .run(data, from, &mut self.values, |state, bar| {
72                if !bar.close.is_finite() {
73                    return IndicatorValue::None;
74                }
75                let md = state.get_or_insert(bar.close);
76                if *md != 0.0 {
77                    let ratio = bar.close / *md;
78                    let divisor = self.period as f64 * ratio.powi(4);
79                    if divisor != 0.0 {
80                        *md += (bar.close - *md) / divisor;
81                    }
82                }
83                IndicatorValue::Single(*md)
84            });
85    }
86
87    fn is_incremental(&self) -> bool {
88        true
89    }
90
91    fn values(&self) -> &IndicatorValues {
92        &self.values
93    }
94
95    fn colors(&self) -> Vec<Color32> {
96        vec![self.color]
97    }
98
99    fn set_colors(&mut self, colors: Vec<Color32>) {
100        if !colors.is_empty() {
101            self.color = colors[0];
102        }
103    }
104
105    fn is_overlay(&self) -> bool {
106        true // Overlay on price chart
107    }
108
109    fn is_visible(&self) -> bool {
110        self.visible
111    }
112
113    fn set_visible(&mut self, visible: bool) {
114        self.visible = visible;
115    }
116
117    fn clone_box(&self) -> Box<dyn Indicator> {
118        Box::new(self.clone())
119    }
120
121    fn line_names(&self) -> Vec<String> {
122        vec![format!("MD({})", self.period)]
123    }
124}