Skip to main content

egui_charts/studies/builtin/
zero_trend_vol.rs

1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::{update_window, usable_behind};
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8/// Zero-mean close-to-close volatility, without subtracting the mean log return.
9///
10/// `sqrt(mean(ln(close / previous_close)^2)) * sqrt(252) * 100`.
11/// Annualisation assumes daily bars and 252 trading days per year (see #672).
12/// A full `period` of returns from positive closes is required before a value exists.
13#[derive(Clone)]
14pub struct ZeroTrendVol {
15    period: usize,
16    values: IndicatorValues,
17    colors: Vec<Color32>,
18    visible: bool,
19}
20
21impl ZeroTrendVol {
22    /// Create a zero-trend study over `period` log returns (at least 1).
23    pub fn new(period: usize) -> Self {
24        Self {
25            period: period.max(1),
26            values: IndicatorValues::new(),
27            colors: vec![palette::LIGHT_BLUE],
28            visible: true,
29        }
30    }
31}
32
33impl Default for ZeroTrendVol {
34    fn default() -> Self {
35        Self::new(20)
36    }
37}
38
39impl ZeroTrendVol {
40    /// The values over `data`, every bar of which has finite inputs.
41    fn compute(&self, data: &[Bar]) -> IndicatorValues {
42        let returns: Vec<_> = data
43            .windows(2)
44            .map(|bars| {
45                if bars[0].close > 0.0 && bars[1].close > 0.0 {
46                    (bars[1].close / bars[0].close).ln()
47                } else {
48                    f64::NAN
49                }
50            })
51            .collect();
52        (0..data.len())
53            .map(|i| {
54                if i < self.period {
55                    return IndicatorValue::None;
56                }
57                let window = &returns[i - self.period..i];
58                if window.iter().any(|r| !r.is_finite()) {
59                    return IndicatorValue::None;
60                }
61                let mean_squared = window.iter().map(|r| r * r).sum::<f64>() / self.period as f64;
62                IndicatorValue::Single(mean_squared.sqrt() * 252.0_f64.sqrt() * 100.0)
63            })
64            .collect()
65    }
66}
67
68impl Indicator for ZeroTrendVol {
69    fn source(&self) -> Option<crate::studies::StudySource> {
70        Some(crate::studies::StudySource::Price(
71            crate::model::PriceSource::Close,
72        ))
73    }
74
75    crate::studies::inputs::study_inputs!(self;
76        period: length("Period", 1, usize::MAX)
77    ; {});
78
79    fn value_format(&self) -> crate::scales::StudyFormat {
80        crate::scales::StudyFormat::Percent
81    }
82
83    fn name(&self) -> &str {
84        "Zero-Trend Vol"
85    }
86
87    fn desc(&self) -> &str {
88        "Zero-Trend Close-to-Close Volatility - Annualised percentage assuming daily bars"
89    }
90
91    fn calculate(&mut self, data: &[Bar]) {
92        self.update(data, 0);
93    }
94
95    fn update(&mut self, data: &[Bar], from: usize) {
96        let mut values = std::mem::take(&mut self.values);
97        update_window(
98            &mut values,
99            data,
100            from,
101            usable_behind(self.period, |b| b.close.is_finite()),
102            |data| over_finite_bars(data, |b| b.close.is_finite(), |data| self.compute(data)),
103        );
104        self.values = values;
105    }
106
107    fn is_incremental(&self) -> bool {
108        true
109    }
110
111    fn values(&self) -> &IndicatorValues {
112        &self.values
113    }
114
115    fn colors(&self) -> Vec<Color32> {
116        self.colors.clone()
117    }
118
119    fn set_colors(&mut self, colors: Vec<Color32>) {
120        if !colors.is_empty() {
121            self.colors = colors;
122        }
123    }
124
125    fn is_overlay(&self) -> bool {
126        false
127    }
128
129    fn is_visible(&self) -> bool {
130        self.visible
131    }
132
133    fn set_visible(&mut self, visible: bool) {
134        self.visible = visible;
135    }
136
137    fn clone_box(&self) -> Box<dyn Indicator> {
138        Box::new(self.clone())
139    }
140
141    fn line_names(&self) -> Vec<String> {
142        vec![format!("Zero-Trend Vol({})", self.period)]
143    }
144}