Skip to main content

egui_charts/studies/builtin/
majority_rule.rs

1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::stepper::{update_window, usable_behind};
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7/// Majority Rule - counts up vs down closes over period.
8/// Outputs +1 if majority up, -1 if majority down, 0 if tie.
9#[derive(Clone)]
10pub struct MajorityRule {
11    period: usize,
12    values: IndicatorValues,
13    colors: Vec<Color32>,
14    visible: bool,
15}
16
17impl MajorityRule {
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            colors: vec![Color32::from_rgb(255, 82, 82)],
24            visible: true,
25        }
26    }
27}
28
29impl Default for MajorityRule {
30    fn default() -> Self {
31        Self::new(10)
32    }
33}
34
35impl MajorityRule {
36    /// The values over `data`, every bar of which has finite inputs.
37    fn compute(&self, data: &[Bar]) -> IndicatorValues {
38        let mut values = IndicatorValues::new();
39        if data.is_empty() {
40            return values;
41        }
42
43        // First bar has no previous close to compare
44        values.push(IndicatorValue::None);
45
46        for i in 1..data.len() {
47            if i < self.period {
48                values.push(IndicatorValue::None);
49                continue;
50            }
51
52            let start = i + 1 - self.period;
53            let mut up_count: i32 = 0;
54            let mut down_count: i32 = 0;
55
56            for idx in start..=i {
57                if idx == 0 {
58                    continue;
59                }
60                if data[idx].close > data[idx - 1].close {
61                    up_count += 1;
62                } else if data[idx].close < data[idx - 1].close {
63                    down_count += 1;
64                }
65            }
66
67            let result = if up_count > down_count {
68                1.0
69            } else if down_count > up_count {
70                -1.0
71            } else {
72                0.0
73            };
74
75            values.push(IndicatorValue::Single(result));
76        }
77
78        values
79    }
80}
81
82impl Indicator for MajorityRule {
83    fn source(&self) -> Option<crate::studies::StudySource> {
84        Some(crate::studies::StudySource::Price(
85            crate::model::PriceSource::Close,
86        ))
87    }
88
89    crate::studies::inputs::study_inputs!(self;
90        period: length("Period", 1, usize::MAX)
91    ; {});
92
93    fn name(&self) -> &str {
94        "MajRule"
95    }
96
97    fn desc(&self) -> &str {
98        "Majority Rule - +1 if majority up closes, -1 if majority down, 0 if tie"
99    }
100
101    fn calculate(&mut self, data: &[Bar]) {
102        self.update(data, 0);
103    }
104
105    fn update(&mut self, data: &[Bar], from: usize) {
106        let mut values = std::mem::take(&mut self.values);
107        update_window(
108            &mut values,
109            data,
110            from,
111            usable_behind(self.period, |b| b.close.is_finite()),
112            |data| over_finite_bars(data, |b| b.close.is_finite(), |data| self.compute(data)),
113        );
114        self.values = values;
115    }
116
117    fn is_incremental(&self) -> bool {
118        true
119    }
120
121    fn values(&self) -> &IndicatorValues {
122        &self.values
123    }
124
125    fn colors(&self) -> Vec<Color32> {
126        self.colors.clone()
127    }
128
129    fn set_colors(&mut self, colors: Vec<Color32>) {
130        if !colors.is_empty() {
131            self.colors = colors;
132        }
133    }
134
135    fn is_overlay(&self) -> bool {
136        false
137    }
138
139    fn is_visible(&self) -> bool {
140        self.visible
141    }
142
143    fn set_visible(&mut self, visible: bool) {
144        self.visible = visible;
145    }
146
147    fn clone_box(&self) -> Box<dyn Indicator> {
148        Box::new(self.clone())
149    }
150
151    fn line_names(&self) -> Vec<String> {
152        vec![format!("MajRule({})", self.period)]
153    }
154}