egui_charts/studies/builtin/
chop_zone.rs1use super::smoothing::Smoothing;
39use crate::model::Bar;
40use crate::studies::palette;
41use crate::studies::stepper::Stepper;
42use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
43use egui::Color32;
44
45const EMA_LENGTH: usize = 34;
46const RANGE_LENGTH: usize = 30;
47
48#[derive(Clone)]
50pub struct ChopZone {
51 values: IndicatorValues,
52 stepper: Stepper<State>,
53 angles: IndicatorValues,
55 colors: [Color32; 9],
57 visible: bool,
58}
59
60#[derive(Clone, Default)]
61struct State {
62 count: usize,
63 prices: std::collections::VecDeque<[f64; 2]>,
64 ema: Smoothing,
65 previous: f64,
66}
67
68impl ChopZone {
69 pub fn new() -> Self {
71 Self {
72 values: IndicatorValues::new(),
73 stepper: Stepper::default(),
74 angles: IndicatorValues::new(),
75 colors: [
76 palette::CYAN,
77 palette::DARK_GREEN,
78 palette::LIGHT_GREEN,
79 palette::DEEP_TEAL,
80 palette::YELLOW,
81 palette::LIGHT_ORANGE,
82 palette::ORANGE,
83 palette::PINK,
84 palette::DEEP_RED,
85 ],
86 visible: true,
87 }
88 }
89}
90
91impl Default for ChopZone {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl Indicator for ChopZone {
99 fn plot_style(&self, _line: usize) -> crate::studies::PlotStyle {
100 crate::studies::PlotStyle {
101 color: self.colors[0],
102 width: 1.0,
103 pattern: crate::model::LineStyle::Solid,
104 plot_type: crate::studies::PlotType::Columns,
105 visible: true,
106 }
107 }
108
109 fn plot_color(&self, _line: usize, bar: usize) -> Option<Color32> {
110 let angle = self.angles.value(0, bar)?;
111 let band = match angle {
112 a if a >= 5.0 => 0,
113 a if a >= 3.57 => 1,
114 a if a >= 2.14 => 2,
115 a if a >= 0.71 => 3,
116 a if a <= -5.0 => 8,
117 a if a <= -3.57 => 7,
118 a if a <= -2.14 => 6,
119 a if a <= -0.71 => 5,
120 _ => 4,
121 };
122 Some(self.colors[band])
123 }
124
125 fn value_format(&self) -> crate::scales::StudyFormat {
126 crate::scales::StudyFormat::Fixed(0)
127 }
128
129 fn name(&self) -> &str {
130 "Chop Zone"
131 }
132
133 fn desc(&self) -> &str {
134 "Chop Zone - Trend and chop by the angle of an EMA"
135 }
136
137 fn calculate(&mut self, data: &[Bar]) {
138 self.update(data, 0);
139 }
140
141 fn update(&mut self, data: &[Bar], from: usize) {
142 self.stepper
143 .run(data, from, &mut self.angles, |state, bar| {
144 if ![bar.high, bar.low, bar.close].iter().all(|x| x.is_finite()) {
145 return IndicatorValue::None;
146 }
147 state.count += 1;
148 state.prices.push_back([bar.high, bar.low]);
149 if state.prices.len() > RANGE_LENGTH {
150 state.prices.pop_front();
151 }
152 let ema = state
153 .ema
154 .next(bar.close, EMA_LENGTH, 2.0 / (EMA_LENGTH as f64 + 1.0));
155 let previous = std::mem::replace(&mut state.previous, ema);
156 if state.count <= RANGE_LENGTH.max(EMA_LENGTH) {
157 return IndicatorValue::None;
158 }
159 let high = state
160 .prices
161 .iter()
162 .map(|v| v[0])
163 .fold(f64::NEG_INFINITY, f64::max);
164 let low = state
165 .prices
166 .iter()
167 .map(|v| v[1])
168 .fold(f64::INFINITY, f64::min);
169 let range = high - low;
170 if range == 0.0 {
171 return IndicatorValue::None;
172 }
173 let span = 25.0 / range * low;
174 let hlc3 = (bar.high + bar.low + bar.close) / 3.0;
175 let y = (previous - ema) / hlc3 * span;
176 let angle = y.abs().atan().to_degrees().round();
177 IndicatorValue::Single(if y > 0.0 { -angle } else { angle })
178 });
179 let start = from.min(self.values.len()).min(data.len());
180 self.values.truncate(start);
181 for i in start..data.len() {
182 self.values.push(if self.angles.value(0, i).is_some() {
183 IndicatorValue::Single(1.0)
184 } else {
185 IndicatorValue::None
186 });
187 }
188 }
189
190 fn is_incremental(&self) -> bool {
191 true
192 }
193
194 fn values(&self) -> &IndicatorValues {
195 &self.values
196 }
197
198 fn colors(&self) -> Vec<Color32> {
199 self.colors.to_vec()
200 }
201
202 fn set_colors(&mut self, colors: Vec<Color32>) {
203 for (old, new) in self.colors.iter_mut().zip(colors) {
204 *old = new;
205 }
206 }
207
208 fn is_overlay(&self) -> bool {
209 false
210 }
211
212 fn is_visible(&self) -> bool {
213 self.visible
214 }
215
216 fn set_visible(&mut self, visible: bool) {
217 self.visible = visible;
218 }
219
220 fn clone_box(&self) -> Box<dyn Indicator> {
221 Box::new(self.clone())
222 }
223
224 fn line_names(&self) -> Vec<String> {
225 vec!["Chop Zone".into()]
226 }
227}