Skip to main content

egui_charts/studies/builtin/
chaikin_volatility.rs

1use super::smoothing::Smoothing;
2use crate::model::Bar;
3use crate::studies::stepper::Stepper;
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7/// Chaikin Volatility
8/// Measures the rate of change of the trading range
9/// High values indicate increased volatility
10#[derive(Clone)]
11pub struct ChaikinVolatility {
12    /// EMA period (typically 10)
13    ema_period: usize,
14    /// ROC period (typically 10)
15    roc_period: usize,
16    values: IndicatorValues,
17    stepper: Stepper<(usize, Smoothing, std::collections::VecDeque<f64>)>,
18    color: Color32,
19    visible: bool,
20}
21
22impl ChaikinVolatility {
23    /// Create with `ema_period`, `roc_period` in bars, each clamped to at least one.
24    pub fn new(ema_period: usize, roc_period: usize) -> Self {
25        Self {
26            ema_period: ema_period.max(1),
27            roc_period: roc_period.max(1),
28            values: IndicatorValues::new(),
29            stepper: Stepper::default(),
30            color: Color32::from_rgb(171, 71, 188),
31            visible: true,
32        }
33    }
34
35    /// Set the plot color.
36    pub fn with_color(mut self, color: Color32) -> Self {
37        self.color = color;
38        self
39    }
40}
41
42impl Default for ChaikinVolatility {
43    fn default() -> Self {
44        Self::new(10, 10)
45    }
46}
47
48impl Indicator for ChaikinVolatility {
49    crate::studies::inputs::study_inputs!(self;
50        ema_period: length("EMA period", 1, usize::MAX),
51        roc_period: length("Roc period", 1, usize::MAX)
52    ; {});
53
54    fn value_format(&self) -> crate::scales::StudyFormat {
55        crate::scales::StudyFormat::Percent
56    }
57
58    fn name(&self) -> &str {
59        "CV"
60    }
61
62    fn desc(&self) -> &str {
63        "Chaikin Volatility - Rate of change of trading range"
64    }
65
66    fn calculate(&mut self, data: &[Bar]) {
67        self.update(data, 0);
68    }
69
70    fn update(&mut self, data: &[Bar], from: usize) {
71        self.stepper
72            .run(data, from, &mut self.values, |state, bar| {
73                if !bar.high.is_finite() || !bar.low.is_finite() {
74                    return IndicatorValue::None;
75                }
76                state.0 += 1;
77                let ema = state
78                    .1
79                    .next(bar.high - bar.low, 1, 2.0 / (self.ema_period as f64 + 1.0));
80                state.2.push_back(ema);
81                let past = if state.2.len() > self.roc_period {
82                    state.2.pop_front().unwrap()
83                } else {
84                    f64::NAN
85                };
86                if state.0 < self.ema_period.saturating_add(self.roc_period) {
87                    IndicatorValue::None
88                } else {
89                    IndicatorValue::Single(if past != 0.0 {
90                        (ema - past) / past * 100.0
91                    } else {
92                        0.0
93                    })
94                }
95            });
96    }
97
98    fn is_incremental(&self) -> bool {
99        true
100    }
101
102    fn values(&self) -> &IndicatorValues {
103        &self.values
104    }
105
106    fn colors(&self) -> Vec<Color32> {
107        vec![self.color]
108    }
109
110    fn set_colors(&mut self, colors: Vec<Color32>) {
111        if !colors.is_empty() {
112            self.color = colors[0];
113        }
114    }
115
116    fn is_overlay(&self) -> bool {
117        false
118    }
119
120    fn is_visible(&self) -> bool {
121        self.visible
122    }
123
124    fn set_visible(&mut self, visible: bool) {
125        self.visible = visible;
126    }
127
128    fn clone_box(&self) -> Box<dyn Indicator> {
129        Box::new(self.clone())
130    }
131
132    fn line_names(&self) -> Vec<String> {
133        vec![format!("CV({},{})", self.ema_period, self.roc_period)]
134    }
135}