Skip to main content

egui_charts/studies/builtin/
elder_ray.rs

1use super::smoothing::Smoothing;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::Stepper;
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8/// Elder Ray Index (Bull Power / Bear Power)
9/// Measures buying and selling pressure
10/// Developed by Alexander Elder
11#[derive(Clone)]
12pub struct ElderRay {
13    /// EMA period (typically 13)
14    period: usize,
15    values: IndicatorValues,
16    stepper: Stepper<Smoothing>,
17    /// Colors: [Bull Power (green), Bear Power (red)]
18    colors: Vec<Color32>,
19    visible: bool,
20}
21
22impl ElderRay {
23    /// Create with a lookback of `period` bars, clamped to at least 1.
24    pub fn new(period: usize) -> Self {
25        Self {
26            period: period.max(1),
27            values: IndicatorValues::new(),
28            stepper: Stepper::default(),
29            colors: vec![
30                palette::TEAL, // Bull Power
31                palette::RED,  // Bear Power
32            ],
33            visible: true,
34        }
35    }
36
37    /// Set plot colors in the order returned by [`Indicator::line_names`].
38    pub fn with_colors(mut self, colors: Vec<Color32>) -> Self {
39        self.colors = colors;
40        self
41    }
42}
43
44/// Construct with the conventional default parameters.
45impl Default for ElderRay {
46    fn default() -> Self {
47        Self::new(13)
48    }
49}
50
51impl Indicator for ElderRay {
52    fn plot_style(&self, line: usize) -> crate::studies::PlotStyle {
53        crate::studies::PlotStyle {
54            color: self.colors.get(line).copied().unwrap_or(Color32::WHITE),
55            plot_type: crate::studies::PlotType::Columns,
56            width: 1.0,
57            pattern: crate::model::LineStyle::Solid,
58            visible: true,
59        }
60    }
61
62    crate::studies::inputs::study_inputs!(self;
63        period: length("Period", 1, usize::MAX)
64    ; {});
65
66    fn value_format(&self) -> crate::scales::StudyFormat {
67        crate::scales::StudyFormat::Price
68    }
69
70    fn name(&self) -> &str {
71        "Elder Ray"
72    }
73
74    fn desc(&self) -> &str {
75        "Elder Ray - Bull and Bear Power indicators"
76    }
77
78    fn calculate(&mut self, data: &[Bar]) {
79        self.update(data, 0);
80    }
81
82    fn update(&mut self, data: &[Bar], from: usize) {
83        self.stepper
84            .run(data, from, &mut self.values, |state, bar| {
85                if ![bar.high, bar.low, bar.close].iter().all(|x| x.is_finite()) {
86                    return IndicatorValue::None;
87                }
88                let ema = state.next(bar.close, self.period, 2.0 / (self.period as f64 + 1.0));
89                if ema.is_nan() {
90                    IndicatorValue::None
91                } else {
92                    IndicatorValue::Multiple(vec![bar.high - ema, bar.low - ema])
93                }
94            });
95    }
96
97    fn is_incremental(&self) -> bool {
98        true
99    }
100
101    fn values(&self) -> &IndicatorValues {
102        &self.values
103    }
104
105    fn colors(&self) -> Vec<Color32> {
106        self.colors.clone()
107    }
108
109    fn set_colors(&mut self, colors: Vec<Color32>) {
110        if colors.len() >= 2 {
111            self.colors = colors;
112        }
113    }
114
115    fn is_overlay(&self) -> bool {
116        false // Separate pane
117    }
118
119    fn is_visible(&self) -> bool {
120        self.visible
121    }
122
123    fn set_visible(&mut self, visible: bool) {
124        self.visible = visible;
125    }
126
127    fn clone_box(&self) -> Box<dyn Indicator> {
128        Box::new(self.clone())
129    }
130
131    fn line_names(&self) -> Vec<String> {
132        vec![
133            format!("Bull({})", self.period),
134            format!("Bear({})", self.period),
135        ]
136    }
137}