Skip to main content

egui_charts/studies/builtin/
aroon.rs

1//! Aroon Indicator
2//!
3//! Aroon identifies trend changes and trend strength by measuring the time
4//! since the highest high and lowest low within a lookback period.
5//!
6//! # Components
7//! - Aroon Up: ((period - days since highest high) / period) * 100
8//! - Aroon Down: ((period - days since lowest low) / period) * 100
9//! - Aroon Oscillator: Aroon Up - Aroon Down (optional)
10//!
11//! # Interpretation
12//! - Aroon Up > 70 and Aroon Down < 30: Strong uptrend
13//! - Aroon Down > 70 and Aroon Up < 30: Strong downtrend
14//! - Crossovers indicate potential trend changes
15//!
16//! # Example
17//! ```
18//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
19//! use egui_charts::studies::{Aroon, Indicator};
20//!
21//! let mut aroon = Aroon::new(25);
22//! aroon.calculate(&bars);
23//! ```
24
25use super::over_finite_bars;
26use crate::model::Bar;
27use crate::studies::palette;
28use crate::studies::stepper::{update_window, usable_behind};
29use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
30use egui::Color32;
31
32/// Aroon indicator
33#[derive(Clone)]
34pub struct Aroon {
35    period: usize,
36    values: IndicatorValues,
37    up_color: Color32,
38    down_color: Color32,
39    visible: bool,
40}
41
42impl Aroon {
43    /// Create a new Aroon indicator
44    ///
45    /// # Arguments
46    /// * `period` - Lookback period (typically 25)
47    pub fn new(period: usize) -> Self {
48        Self {
49            period: period.max(1),
50            values: IndicatorValues::new(),
51            up_color: palette::DARK_ORANGE,
52            down_color: palette::BLUE,
53            visible: true,
54        }
55    }
56
57    /// Create with default param (25)
58    pub fn default_params() -> Self {
59        Self::new(25)
60    }
61
62    /// Set colors for up and down lines
63    pub fn with_colors(mut self, up: Color32, down: Color32) -> Self {
64        self.up_color = up;
65        self.down_color = down;
66        self
67    }
68}
69
70/// Construct with the conventional default parameters.
71impl Default for Aroon {
72    fn default() -> Self {
73        Self::new(14)
74    }
75}
76
77impl Aroon {
78    /// The values over `data`, every bar of which has finite inputs.
79    fn compute(&self, data: &[Bar]) -> IndicatorValues {
80        let mut values = IndicatorValues::new();
81        if data.is_empty() {
82            return values;
83        }
84
85        for i in 0..data.len() {
86            if i < self.period {
87                values.push(IndicatorValue::None);
88                continue;
89            }
90
91            // Find highest high and lowest low positions in lookback
92            let start = i - self.period;
93            let mut highest_idx = start;
94            let mut lowest_idx = start;
95            let mut highest = data[start].high;
96            let mut lowest = data[start].low;
97
98            for j in (start + 1)..=i {
99                if data[j].high >= highest {
100                    highest = data[j].high;
101                    highest_idx = j;
102                }
103                if data[j].low <= lowest {
104                    lowest = data[j].low;
105                    lowest_idx = j;
106                }
107            }
108
109            // Days since highest/lowest
110            let days_since_high = i - highest_idx;
111            let days_since_low = i - lowest_idx;
112
113            // Calculate Aroon Up and Down (0-100 scale)
114            let aroon_up = ((self.period - days_since_high) as f64 / self.period as f64) * 100.0;
115            let aroon_down = ((self.period - days_since_low) as f64 / self.period as f64) * 100.0;
116
117            values.push_lines(&[aroon_up, aroon_down]);
118        }
119
120        values
121    }
122}
123
124impl Indicator for Aroon {
125    crate::studies::inputs::study_inputs!(self;
126        period: length("Period", 1, usize::MAX)
127    ; {});
128
129    fn name(&self) -> &str {
130        "Aroon"
131    }
132
133    fn desc(&self) -> &str {
134        "Aroon - Trend identification indicator"
135    }
136
137    fn calculate(&mut self, data: &[Bar]) {
138        self.update(data, 0);
139    }
140
141    fn update(&mut self, data: &[Bar], from: usize) {
142        let mut values = std::mem::take(&mut self.values);
143        update_window(
144            &mut values,
145            data,
146            from,
147            usable_behind(self.period, |b| b.high.is_finite() && b.low.is_finite()),
148            |data| {
149                over_finite_bars(
150                    data,
151                    |b| b.high.is_finite() && b.low.is_finite(),
152                    |data| self.compute(data),
153                )
154            },
155        );
156        self.values = values;
157    }
158
159    fn is_incremental(&self) -> bool {
160        true
161    }
162
163    fn values(&self) -> &IndicatorValues {
164        &self.values
165    }
166
167    fn colors(&self) -> Vec<Color32> {
168        vec![self.up_color, self.down_color]
169    }
170
171    fn set_colors(&mut self, colors: Vec<Color32>) {
172        if !colors.is_empty() {
173            self.up_color = colors[0];
174        }
175        if colors.len() > 1 {
176            self.down_color = colors[1];
177        }
178    }
179
180    fn is_overlay(&self) -> bool {
181        false // Aroon is a separate oscillator pane
182    }
183
184    fn line_count(&self) -> usize {
185        2 // Aroon Up and Aroon Down
186    }
187
188    fn is_visible(&self) -> bool {
189        self.visible
190    }
191
192    fn set_visible(&mut self, visible: bool) {
193        self.visible = visible;
194    }
195
196    fn clone_box(&self) -> Box<dyn Indicator> {
197        Box::new(self.clone())
198    }
199
200    fn line_names(&self) -> Vec<String> {
201        vec![
202            format!("Aroon Up({})", self.period),
203            format!("Aroon Down({})", self.period),
204        ]
205    }
206}