Skip to main content

egui_charts/studies/builtin/
guppy_ma.rs

1use crate::model::Bar;
2use crate::studies::stepper::Stepper;
3use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
4use egui::Color32;
5
6/// Short group periods: 3, 5, 8, 10, 12, 15
7/// Long group periods: 30, 35, 40, 45, 50, 60
8const SHORT_PERIODS: [usize; 6] = [3, 5, 8, 10, 12, 15];
9const LONG_PERIODS: [usize; 6] = [30, 35, 40, 45, 50, 60];
10
11/// Guppy Multiple Moving Average (GMMA)
12#[derive(Clone)]
13pub struct GuppyMA {
14    values: IndicatorValues,
15    stepper: Stepper<Option<[f64; 12]>>,
16    colors: Vec<Color32>,
17    visible: bool,
18}
19
20impl GuppyMA {
21    /// Create the six short and six long exponential moving averages.
22    pub fn new() -> Self {
23        Self {
24            values: IndicatorValues::new(),
25            stepper: Stepper::default(),
26            colors: [217, 224, 232, 240, 247, 255]
27                .into_iter()
28                .map(|alpha| Color32::from_rgba_unmultiplied(0, 255, 255, alpha))
29                .chain(
30                    [217, 224, 232, 240, 247, 255]
31                        .into_iter()
32                        .map(|alpha| Color32::from_rgba_unmultiplied(255, 0, 0, alpha)),
33                )
34                .collect(),
35            visible: true,
36        }
37    }
38}
39
40impl Default for GuppyMA {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl Indicator for GuppyMA {
47    fn source(&self) -> Option<crate::studies::StudySource> {
48        Some(crate::studies::StudySource::Price(
49            crate::model::PriceSource::Close,
50        ))
51    }
52
53    fn name(&self) -> &str {
54        "GMMA"
55    }
56
57    fn desc(&self) -> &str {
58        "Guppy Multiple Moving Average - 12 EMAs in short and long groups"
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                let averages = state.get_or_insert([bar.close; 12]);
72                for (average, period) in averages
73                    .iter_mut()
74                    .zip(SHORT_PERIODS.into_iter().chain(LONG_PERIODS))
75                {
76                    *average = (bar.close - *average) * (2.0 / (period as f64 + 1.0)) + *average;
77                }
78                IndicatorValue::Multiple(averages.to_vec())
79            });
80    }
81
82    fn is_incremental(&self) -> bool {
83        true
84    }
85
86    fn values(&self) -> &IndicatorValues {
87        &self.values
88    }
89
90    fn colors(&self) -> Vec<Color32> {
91        self.colors.clone()
92    }
93
94    fn set_colors(&mut self, colors: Vec<Color32>) {
95        if colors.len() >= 12 {
96            self.colors = colors;
97        }
98    }
99
100    fn is_overlay(&self) -> bool {
101        true
102    }
103
104    fn line_count(&self) -> usize {
105        12
106    }
107
108    fn is_visible(&self) -> bool {
109        self.visible
110    }
111
112    fn set_visible(&mut self, visible: bool) {
113        self.visible = visible;
114    }
115
116    fn clone_box(&self) -> Box<dyn Indicator> {
117        Box::new(self.clone())
118    }
119
120    fn line_names(&self) -> Vec<String> {
121        let mut names = Vec::with_capacity(12);
122        for &p in &SHORT_PERIODS {
123            names.push(format!("EMA({p})"));
124        }
125        for &p in &LONG_PERIODS {
126            names.push(format!("EMA({p})"));
127        }
128        names
129    }
130}