egui_charts/studies/builtin/
close_to_close_vol.rs1use 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#[derive(Clone)]
11pub struct CloseToCloseVol {
12 period: usize,
13 values: IndicatorValues,
14 colors: Vec<Color32>,
15 visible: bool,
16}
17
18impl CloseToCloseVol {
19 pub fn new(period: usize) -> Self {
21 Self {
22 period: period.max(1),
23 values: IndicatorValues::new(),
24 colors: vec![palette::LIGHT_BLUE], visible: true,
26 }
27 }
28}
29
30impl Default for CloseToCloseVol {
31 fn default() -> Self {
32 Self::new(20)
33 }
34}
35
36impl CloseToCloseVol {
37 fn compute(&self, data: &[Bar]) -> IndicatorValues {
39 let mut values = IndicatorValues::new();
40 if data.len() < 2 {
41 for _ in data {
42 values.push(IndicatorValue::None);
43 }
44 return values;
45 }
46
47 let mut log_returns = Vec::with_capacity(data.len());
49 log_returns.push(f64::NAN); for i in 1..data.len() {
52 if data[i - 1].close > 0.0 && data[i].close > 0.0 {
53 log_returns.push((data[i].close / data[i - 1].close).ln());
54 } else {
55 log_returns.push(f64::NAN);
56 }
57 }
58
59 for i in 0..data.len() {
61 if i < self.period {
62 values.push(IndicatorValue::None);
63 continue;
64 }
65
66 let window = &log_returns[i + 1 - self.period..=i];
67 let valid: Vec<f64> = window.iter().filter(|x| !x.is_nan()).copied().collect();
68
69 if valid.len() < 2 {
70 values.push(IndicatorValue::None);
71 continue;
72 }
73
74 let mean = valid.iter().sum::<f64>() / valid.len() as f64;
75 let variance =
76 valid.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (valid.len() - 1) as f64;
77 let std_dev = variance.sqrt();
78
79 let annualized = std_dev * 252.0_f64.sqrt() * 100.0;
81 values.push(IndicatorValue::Single(annualized));
82 }
83
84 values
85 }
86}
87
88impl Indicator for CloseToCloseVol {
89 fn source(&self) -> Option<crate::studies::StudySource> {
90 Some(crate::studies::StudySource::Price(
91 crate::model::PriceSource::Close,
92 ))
93 }
94
95 crate::studies::inputs::study_inputs!(self;
96 period: length("Period", 1, usize::MAX)
97 ; {});
98
99 fn value_format(&self) -> crate::scales::StudyFormat {
100 crate::scales::StudyFormat::Percent
101 }
102
103 fn name(&self) -> &str {
104 "C2C Vol"
105 }
106
107 fn desc(&self) -> &str {
108 "Close-to-Close Volatility - Annualized standard deviation of log returns"
109 }
110
111 fn calculate(&mut self, data: &[Bar]) {
112 self.update(data, 0);
113 }
114
115 fn update(&mut self, data: &[Bar], from: usize) {
116 let mut values = std::mem::take(&mut self.values);
117 update_window(
118 &mut values,
119 data,
120 from,
121 usable_behind(self.period, |b| b.close.is_finite()),
122 |data| over_finite_bars(data, |b| b.close.is_finite(), |data| self.compute(data)),
123 );
124 self.values = values;
125 }
126
127 fn is_incremental(&self) -> bool {
128 true
129 }
130
131 fn values(&self) -> &IndicatorValues {
132 &self.values
133 }
134
135 fn colors(&self) -> Vec<Color32> {
136 self.colors.clone()
137 }
138
139 fn set_colors(&mut self, colors: Vec<Color32>) {
140 if !colors.is_empty() {
141 self.colors = colors;
142 }
143 }
144
145 fn is_overlay(&self) -> bool {
146 false
147 }
148
149 fn is_visible(&self) -> bool {
150 self.visible
151 }
152
153 fn set_visible(&mut self, visible: bool) {
154 self.visible = visible;
155 }
156
157 fn clone_box(&self) -> Box<dyn Indicator> {
158 Box::new(self.clone())
159 }
160
161 fn line_names(&self) -> Vec<String> {
162 vec![format!("C2C Vol({})", self.period)]
163 }
164}