egui_charts/studies/builtin/
ohlc_volatility.rs1use super::over_finite_bars;
2use crate::model::Bar;
3use crate::studies::stepper::{update_window, usable_behind};
4use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
5use egui::Color32;
6
7#[derive(Clone)]
11pub struct OhlcVolatility {
12 period: usize,
13 values: IndicatorValues,
14 colors: Vec<Color32>,
15 visible: bool,
16}
17
18impl OhlcVolatility {
19 pub fn new(period: usize) -> Self {
21 Self {
22 period: period.max(1),
23 values: IndicatorValues::new(),
24 colors: vec![Color32::from_rgb(255, 82, 82)], visible: true,
26 }
27 }
28}
29
30impl Default for OhlcVolatility {
31 fn default() -> Self {
32 Self::new(20)
33 }
34}
35
36impl OhlcVolatility {
37 fn compute(&self, data: &[Bar]) -> IndicatorValues {
39 let mut values = IndicatorValues::new();
40 if data.is_empty() {
41 return values;
42 }
43
44 let ln2 = 2.0_f64.ln();
45 let coeff = 2.0 * ln2 - 1.0;
46
47 let mut gk_values = Vec::with_capacity(data.len());
49
50 for bar in data {
51 if bar.low > 0.0 && bar.open > 0.0 {
52 let hl = (bar.high / bar.low).ln();
53 let co = (bar.close / bar.open).ln();
54 let gk = 0.5 * hl * hl - coeff * co * co;
55 gk_values.push(gk);
56 } else {
57 gk_values.push(f64::NAN);
58 }
59 }
60
61 for i in 0..data.len() {
63 if i + 1 < self.period {
64 values.push(IndicatorValue::None);
65 continue;
66 }
67
68 let window = &gk_values[i + 1 - self.period..=i];
69 let valid: Vec<f64> = window.iter().filter(|x| !x.is_nan()).copied().collect();
70
71 if valid.is_empty() {
72 values.push(IndicatorValue::None);
73 continue;
74 }
75
76 let avg = valid.iter().sum::<f64>() / valid.len() as f64;
77 let vol = if avg >= 0.0 {
79 avg.sqrt() * 252.0_f64.sqrt() * 100.0
80 } else {
81 0.0
82 };
83
84 values.push(IndicatorValue::Single(vol));
85 }
86
87 values
88 }
89}
90
91impl Indicator for OhlcVolatility {
92 crate::studies::inputs::study_inputs!(self;
93 period: length("Period", 1, usize::MAX)
94 ; {});
95
96 fn value_format(&self) -> crate::scales::StudyFormat {
97 crate::scales::StudyFormat::Percent
98 }
99
100 fn name(&self) -> &str {
101 "GK Vol"
102 }
103
104 fn desc(&self) -> &str {
105 "Garman-Klass OHLC Volatility - Efficient volatility estimator using OHLC"
106 }
107
108 fn calculate(&mut self, data: &[Bar]) {
109 self.update(data, 0);
110 }
111
112 fn update(&mut self, data: &[Bar], from: usize) {
113 let mut values = std::mem::take(&mut self.values);
114 update_window(
115 &mut values,
116 data,
117 from,
118 usable_behind(self.period - 1, |b| {
119 b.open.is_finite() && b.high.is_finite() && b.low.is_finite() && b.close.is_finite()
120 }),
121 |data| {
122 over_finite_bars(
123 data,
124 |b| {
125 b.open.is_finite()
126 && b.high.is_finite()
127 && b.low.is_finite()
128 && b.close.is_finite()
129 },
130 |data| self.compute(data),
131 )
132 },
133 );
134 self.values = values;
135 }
136
137 fn is_incremental(&self) -> bool {
138 true
139 }
140
141 fn values(&self) -> &IndicatorValues {
142 &self.values
143 }
144
145 fn colors(&self) -> Vec<Color32> {
146 self.colors.clone()
147 }
148
149 fn set_colors(&mut self, colors: Vec<Color32>) {
150 if !colors.is_empty() {
151 self.colors = colors;
152 }
153 }
154
155 fn is_overlay(&self) -> bool {
156 false
157 }
158
159 fn is_visible(&self) -> bool {
160 self.visible
161 }
162
163 fn set_visible(&mut self, visible: bool) {
164 self.visible = visible;
165 }
166
167 fn clone_box(&self) -> Box<dyn Indicator> {
168 Box::new(self.clone())
169 }
170
171 fn line_names(&self) -> Vec<String> {
172 vec![format!("GK Vol({})", self.period)]
173 }
174}