Skip to main content

egui_charts/studies/builtin/
roc.rs

1//! Rate of Change (ROC) Indicator
2//!
3//! ROC is a momentum oscillator that measures the percentage change in price
4//! between the current price and the price N periods ago.
5//!
6//! # Interpretation
7//! - Positive ROC: Price is higher than N periods ago (bullish)
8//! - Negative ROC: Price is lower than N periods ago (bearish)
9//! - Zero crossings can signal trend changes
10//! - Extreme readings may indicate overbought/oversold conditions
11//!
12//! # Calculation
13//! ROC = ((Close - Close[n]) / Close[n]) * 100
14//! A previous close of exactly zero has no defined rate of change.
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::{RateOfChange, Indicator};
20//!
21//! let mut roc = RateOfChange::new(12);
22//! roc.calculate(&bars);
23//! ```
24
25use crate::model::Bar;
26use crate::studies::palette;
27use crate::studies::stepper::update_window;
28use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
29use egui::Color32;
30
31/// Rate of Change indicator
32#[derive(Clone)]
33pub struct RateOfChange {
34    period: usize,
35    values: IndicatorValues,
36    color: Color32,
37    visible: bool,
38}
39
40impl RateOfChange {
41    /// Create a new ROC indicator
42    ///
43    /// # Arguments
44    /// * `period` - Lookback period (typically 12 or 25)
45    pub fn new(period: usize) -> Self {
46        Self {
47            period: period.max(1),
48            values: IndicatorValues::new(),
49            color: palette::BLUE,
50            visible: true,
51        }
52    }
53
54    /// Set the indicator color
55    pub fn with_color(mut self, color: Color32) -> Self {
56        self.color = color;
57        self
58    }
59}
60
61/// Construct with the conventional default parameters.
62impl Default for RateOfChange {
63    fn default() -> Self {
64        Self::new(9)
65    }
66}
67
68impl RateOfChange {
69    fn compute(&self, data: &[Bar]) -> IndicatorValues {
70        let mut values = IndicatorValues::new();
71
72        if data.len() <= self.period {
73            for _ in 0..data.len() {
74                values.push(IndicatorValue::None);
75            }
76            return values;
77        }
78
79        for i in 0..data.len() {
80            if i < self.period {
81                values.push(IndicatorValue::None);
82            } else {
83                let prev_close = data[i - self.period].close;
84                if prev_close == 0.0 {
85                    values.push(IndicatorValue::None);
86                } else {
87                    let roc = ((data[i].close - prev_close) / prev_close) * 100.0;
88                    values.push(IndicatorValue::Single(roc));
89                }
90            }
91        }
92        values
93    }
94}
95
96impl Indicator for RateOfChange {
97    fn source(&self) -> Option<crate::studies::StudySource> {
98        Some(crate::studies::StudySource::Price(
99            crate::model::PriceSource::Close,
100        ))
101    }
102
103    crate::studies::inputs::study_inputs!(self;
104        period: length("Period", 1, usize::MAX)
105    ; {});
106
107    fn value_format(&self) -> crate::scales::StudyFormat {
108        crate::scales::StudyFormat::Percent
109    }
110
111    fn name(&self) -> &str {
112        "ROC"
113    }
114
115    fn desc(&self) -> &str {
116        "Rate of Change - Momentum oscillator (percentage)"
117    }
118
119    fn calculate(&mut self, data: &[Bar]) {
120        self.update(data, 0);
121    }
122
123    fn update(&mut self, data: &[Bar], from: usize) {
124        let mut values = std::mem::take(&mut self.values);
125        update_window(
126            &mut values,
127            data,
128            from,
129            |_data, from| from.saturating_sub(self.period),
130            |data| self.compute(data),
131        );
132        self.values = values;
133    }
134
135    fn is_incremental(&self) -> bool {
136        true
137    }
138
139    fn values(&self) -> &IndicatorValues {
140        &self.values
141    }
142
143    fn colors(&self) -> Vec<Color32> {
144        vec![self.color]
145    }
146
147    fn set_colors(&mut self, colors: Vec<Color32>) {
148        if !colors.is_empty() {
149            self.color = colors[0];
150        }
151    }
152
153    fn is_overlay(&self) -> bool {
154        false // ROC is drawn in separate pane
155    }
156
157    fn is_visible(&self) -> bool {
158        self.visible
159    }
160
161    fn set_visible(&mut self, visible: bool) {
162        self.visible = visible;
163    }
164
165    fn clone_box(&self) -> Box<dyn Indicator> {
166        Box::new(self.clone())
167    }
168
169    fn line_names(&self) -> Vec<String> {
170        vec![format!("ROC({})", self.period)]
171    }
172}