egui_charts/studies/builtin/
keltner.rs1use super::clamp_param;
30use super::price_oscillator::{Mean, OscillatorAverage};
31use super::smoothing::Smoothing;
32use crate::model::{Bar, PriceSource};
33use crate::studies::palette;
34use crate::studies::stepper::Stepper;
35use crate::studies::{ATR, Indicator, IndicatorValue, IndicatorValues};
36use egui::Color32;
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum KeltnerBand {
42 AverageTrueRange,
44 TrueRange,
46 Range,
48}
49
50#[derive(Clone)]
52pub struct KeltnerChannels {
53 ema_period: usize,
54 atr_period: usize,
55 multiplier: f64,
56 source: PriceSource,
57 average: OscillatorAverage,
58 band: KeltnerBand,
59 values: IndicatorValues,
60 stepper: Stepper<State>,
61 upper_color: Color32,
62 middle_color: Color32,
63 lower_color: Color32,
64 fill_color: Color32,
65 visible: bool,
66}
67
68#[derive(Clone, Default)]
69struct State {
70 previous: Option<f64>,
71 middle: Mean,
72 band: Smoothing,
73}
74
75impl KeltnerChannels {
76 pub fn new(ema_period: usize, atr_period: usize, multiplier: f64) -> Self {
83 Self {
84 ema_period: ema_period.max(1),
85 atr_period: atr_period.max(1),
86 multiplier: clamp_param(multiplier, 0.0, f64::MAX),
87 source: PriceSource::Close,
88 average: OscillatorAverage::Exponential,
89 band: KeltnerBand::AverageTrueRange,
90 values: IndicatorValues::new(),
91 stepper: Stepper::default(),
92 upper_color: palette::BLUE,
93 middle_color: palette::BLUE,
94 lower_color: palette::BLUE,
95 fill_color: palette::LIGHT_BLUE.gamma_multiply_u8(13),
96 visible: true,
97 }
98 }
99
100 pub fn default_params() -> Self {
102 Self::new(20, 10, 2.0)
103 }
104
105 pub fn with_colors(mut self, upper: Color32, middle: Color32, lower: Color32) -> Self {
107 self.upper_color = upper;
108 self.middle_color = middle;
109 self.lower_color = lower;
110 self.fill_color = Color32::from_rgba_unmultiplied(upper.r(), upper.g(), upper.b(), 16);
111 self
112 }
113
114 pub fn with_source(mut self, source: PriceSource) -> Self {
116 self.source = source;
117 self
118 }
119
120 pub fn with_average(mut self, average: OscillatorAverage) -> Self {
122 self.average = average;
123 self
124 }
125
126 pub fn with_band(mut self, band: KeltnerBand) -> Self {
128 self.band = band;
129 self
130 }
131}
132
133impl Default for KeltnerChannels {
135 fn default() -> Self {
136 Self::new(20, 10, 2.0)
137 }
138}
139
140impl Indicator for KeltnerChannels {
141 crate::studies::inputs::study_inputs!(self;
142 ema_period: length("Length", 1, usize::MAX),
143 multiplier: number("Multiplier", 0.0, f64::MAX),
144 source: choice("Source", PriceSource::all()),
145 average: choice("Average", [OscillatorAverage::Exponential, OscillatorAverage::Simple]),
146 band: choice("Band style", [KeltnerBand::AverageTrueRange, KeltnerBand::TrueRange, KeltnerBand::Range]),
147 atr_period: length("ATR length", 1, usize::MAX)
148 ; {});
149
150 fn fills(&self) -> Vec<crate::studies::StudyFill> {
151 vec![crate::studies::StudyFill {
152 first: crate::studies::FillBoundary::Plot(1),
153 second: crate::studies::FillBoundary::Plot(2),
154 color: self.fill_color,
155 inverted_color: None,
156 }]
157 }
158
159 fn name(&self) -> &str {
160 "Keltner"
161 }
162
163 fn desc(&self) -> &str {
164 "Keltner Channels - Volatility-based envelope"
165 }
166
167 fn calculate(&mut self, data: &[Bar]) {
168 self.update(data, 0);
169 }
170
171 fn update(&mut self, data: &[Bar], from: usize) {
172 self.stepper
173 .run(data, from, &mut self.values, |state, bar| {
174 let price = self.source.compute(bar.open, bar.high, bar.low, bar.close);
175 if ![bar.high, bar.low, bar.close, price]
176 .iter()
177 .all(|v| v.is_finite())
178 {
179 return IndicatorValue::None;
180 }
181 let tr = state
182 .previous
183 .map_or(bar.high - bar.low, |close| ATR::true_range(bar, close));
184 state.previous = Some(bar.close);
185 let middle = state.middle.next(self.average, price, self.ema_period);
186 let band = match self.band {
187 KeltnerBand::AverageTrueRange => {
188 state
189 .band
190 .next(tr, self.atr_period, 1.0 / self.atr_period as f64)
191 }
192 KeltnerBand::TrueRange => tr,
193 KeltnerBand::Range => state.band.next(
194 bar.high - bar.low,
195 self.ema_period,
196 1.0 / self.ema_period as f64,
197 ),
198 };
199 if middle.is_nan() || band.is_nan() {
201 return IndicatorValue::None;
202 }
203 let width = self.multiplier * band;
204 IndicatorValue::Multiple(vec![middle, middle + width, middle - width])
205 });
206 }
207
208 fn is_incremental(&self) -> bool {
209 true
210 }
211
212 fn values(&self) -> &IndicatorValues {
213 &self.values
214 }
215
216 fn colors(&self) -> Vec<Color32> {
217 vec![
218 self.middle_color,
219 self.upper_color,
220 self.lower_color,
221 self.fill_color,
222 ]
223 }
224
225 fn set_colors(&mut self, colors: Vec<Color32>) {
226 if !colors.is_empty() {
227 self.middle_color = colors[0];
228 }
229 if colors.len() > 1 {
230 self.upper_color = colors[1];
231 }
232 if colors.len() > 2 {
233 self.lower_color = colors[2];
234 }
235 if let Some(&color) = colors.get(3) {
236 self.fill_color = color;
237 } else if colors.len() > 1 {
238 let color = self.upper_color;
239 self.fill_color = Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), 16);
240 }
241 }
242
243 fn is_overlay(&self) -> bool {
244 true }
246
247 fn line_count(&self) -> usize {
248 3 }
250
251 fn is_visible(&self) -> bool {
252 self.visible
253 }
254
255 fn set_visible(&mut self, visible: bool) {
256 self.visible = visible;
257 }
258
259 fn clone_box(&self) -> Box<dyn Indicator> {
260 Box::new(self.clone())
261 }
262
263 fn line_names(&self) -> Vec<String> {
264 let average = match self.average {
265 OscillatorAverage::Exponential => "EMA",
266 OscillatorAverage::Simple => "SMA",
267 };
268 let band = match self.band {
269 KeltnerBand::AverageTrueRange => "ATR",
270 KeltnerBand::TrueRange => "TR",
271 KeltnerBand::Range => "Range",
272 };
273 vec![
274 format!("{average}({})", self.ema_period),
275 format!("Upper({}x {band})", self.multiplier),
276 format!("Lower({}x {band})", self.multiplier),
277 ]
278 }
279}