egui_charts/studies/builtin/
choppiness.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)]
12pub struct ChoppinessIndex {
13 period: usize,
15 values: IndicatorValues,
16 color: Color32,
17 visible: bool,
18}
19
20impl ChoppinessIndex {
21 pub fn new(period: usize) -> Self {
23 Self {
24 period: period.max(1),
25 values: IndicatorValues::new(),
26 color: palette::BLUE,
27 visible: true,
28 }
29 }
30
31 pub fn with_color(mut self, color: Color32) -> Self {
33 self.color = color;
34 self
35 }
36}
37
38impl Default for ChoppinessIndex {
40 fn default() -> Self {
41 Self::new(14)
42 }
43}
44
45impl ChoppinessIndex {
46 fn compute(&self, data: &[Bar]) -> IndicatorValues {
48 let mut values = IndicatorValues::new();
49 if data.len() < self.period.saturating_add(1) {
50 for _ in 0..data.len() {
51 values.push(IndicatorValue::None);
52 }
53 return values;
54 }
55
56 let mut tr_values = Vec::with_capacity(data.len());
58 tr_values.push(data[0].high - data[0].low); for i in 1..data.len() {
61 let current = &data[i];
62 let prev = &data[i - 1];
63
64 let tr = (current.high - current.low)
65 .max((current.high - prev.close).abs())
66 .max((current.low - prev.close).abs());
67
68 tr_values.push(tr);
69 }
70
71 for i in 0..data.len() {
73 if i < self.period {
74 values.push(IndicatorValue::None);
75 } else {
76 let atr_sum: f64 = tr_values[i + 1 - self.period..=i].iter().sum();
78
79 let window = &data[i + 1 - self.period..=i];
81 let highest = window
82 .iter()
83 .map(|b| b.high)
84 .fold(f64::NEG_INFINITY, f64::max);
85 let lowest = window.iter().map(|b| b.low).fold(f64::INFINITY, f64::min);
86
87 let range = highest - lowest;
88
89 if range == 0.0 {
90 values.push(IndicatorValue::None);
91 } else {
92 let chop = 100.0 * (atr_sum / range).log10() / (self.period as f64).log10();
94 values.push(IndicatorValue::Single(chop));
95 }
96 }
97 }
98
99 values
100 }
101}
102
103impl Indicator for ChoppinessIndex {
104 crate::studies::inputs::study_inputs!(self;
105 period: length("Period", 1, usize::MAX)
106 ; {});
107
108 fn name(&self) -> &str {
109 "CHOP"
110 }
111
112 fn desc(&self) -> &str {
113 "Choppiness Index - Measures market trendiness"
114 }
115
116 fn calculate(&mut self, data: &[Bar]) {
117 self.update(data, 0);
118 }
119
120 fn update(&mut self, data: &[Bar], from: usize) {
121 let mut values = std::mem::take(&mut self.values);
122 update_window(
123 &mut values,
124 data,
125 from,
126 usable_behind(self.period, |b| {
127 b.high.is_finite() && b.low.is_finite() && b.close.is_finite()
128 }),
129 |data| {
130 over_finite_bars(
131 data,
132 |b| b.high.is_finite() && b.low.is_finite() && b.close.is_finite(),
133 |data| self.compute(data),
134 )
135 },
136 );
137 self.values = values;
138 }
139
140 fn is_incremental(&self) -> bool {
141 true
142 }
143
144 fn values(&self) -> &IndicatorValues {
145 &self.values
146 }
147
148 fn colors(&self) -> Vec<Color32> {
149 vec![self.color]
150 }
151
152 fn set_colors(&mut self, colors: Vec<Color32>) {
153 if !colors.is_empty() {
154 self.color = colors[0];
155 }
156 }
157
158 fn is_overlay(&self) -> bool {
159 false
160 }
161
162 fn is_visible(&self) -> bool {
163 self.visible
164 }
165
166 fn set_visible(&mut self, visible: bool) {
167 self.visible = visible;
168 }
169
170 fn clone_box(&self) -> Box<dyn Indicator> {
171 Box::new(self.clone())
172 }
173
174 fn line_names(&self) -> Vec<String> {
175 vec![format!("CHOP({})", self.period)]
176 }
177}