egui_charts/studies/builtin/
klinger.rs1use super::smoothing::Smoothing;
2use crate::model::Bar;
3use crate::studies::palette;
4use crate::studies::stepper::Stepper;
5use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
6use egui::Color32;
7
8#[derive(Clone)]
16pub struct KlingerOscillator {
17 short_period: usize,
19 long_period: usize,
21 signal_period: usize,
23 values: IndicatorValues,
24 stepper: Stepper<State>,
25 colors: Vec<Color32>,
27 visible: bool,
28}
29
30#[derive(Clone, Default)]
31struct State {
32 previous: Option<[f64; 3]>,
33 trend: f64,
34 cm: f64,
35 ema: [Smoothing; 3],
36}
37
38impl KlingerOscillator {
39 pub fn new() -> Self {
41 Self {
42 short_period: 34,
43 long_period: 55,
44 signal_period: 13,
45 values: IndicatorValues::new(),
46 stepper: Stepper::default(),
47 colors: vec![
48 palette::BLUE, palette::DARK_GREEN, ],
51 visible: true,
52 }
53 }
54
55 pub fn with_periods(mut self, short: usize, long: usize, signal: usize) -> Self {
57 self.short_period = short.max(1);
58 self.long_period = long.max(1);
59 self.signal_period = signal.max(1);
60 self
61 }
62
63 pub fn with_colors(mut self, colors: Vec<Color32>) -> Self {
65 self.colors = colors;
66 self
67 }
68}
69
70impl Default for KlingerOscillator {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76impl Indicator for KlingerOscillator {
77 crate::studies::inputs::study_inputs!(self;
78 short_period: length("Short period", 1, usize::MAX),
79 long_period: length("Long period", 1, usize::MAX),
80 signal_period: length("Signal period", 1, usize::MAX)
81 ; {});
82
83 fn value_format(&self) -> crate::scales::StudyFormat {
84 crate::scales::StudyFormat::Volume
85 }
86
87 fn name(&self) -> &str {
88 "Klinger"
89 }
90
91 fn desc(&self) -> &str {
92 "Klinger Volume Oscillator - Long-term money flow trend"
93 }
94
95 fn calculate(&mut self, data: &[Bar]) {
96 self.update(data, 0);
97 }
98
99 fn update(&mut self, data: &[Bar], from: usize) {
100 self.stepper
101 .run(data, from, &mut self.values, |state, bar| {
102 if ![bar.high, bar.low, bar.close, bar.volume]
103 .iter()
104 .all(|x| x.is_finite())
105 {
106 return IndicatorValue::None;
107 }
108 let Some(previous) = state.previous.replace([bar.high, bar.low, bar.close]) else {
109 return IndicatorValue::None;
110 };
111 let sum = bar.high + bar.low + bar.close;
112 let previous_sum = previous[0] + previous[1] + previous[2];
113 let trend = if sum > previous_sum {
114 1.0
115 } else if sum < previous_sum {
116 -1.0
117 } else {
118 state.trend
119 };
120 if trend == 0.0 {
121 return IndicatorValue::None;
122 }
123 let dm = bar.high - bar.low;
124 state.cm = if state.trend == 0.0 {
125 dm
126 } else if trend == state.trend {
127 state.cm + dm
128 } else {
129 previous[0] - previous[1] + dm
130 };
131 state.trend = trend;
132 let force = if state.cm == 0.0 {
133 0.0
134 } else {
135 bar.volume * (2.0 * (dm / state.cm - 1.0)).abs() * trend * 100.0
136 };
137 let short = state.ema[0].next(
138 force,
139 self.short_period,
140 2.0 / (self.short_period as f64 + 1.0),
141 );
142 let long = state.ema[1].next(
143 force,
144 self.long_period,
145 2.0 / (self.long_period as f64 + 1.0),
146 );
147 let kvo = short - long;
148 let signal = state.ema[2].next(
149 kvo,
150 self.signal_period,
151 2.0 / (self.signal_period as f64 + 1.0),
152 );
153 if kvo.is_nan() {
154 IndicatorValue::None
155 } else {
156 IndicatorValue::Multiple(vec![kvo, signal])
157 }
158 });
159 }
160
161 fn is_incremental(&self) -> bool {
162 true
163 }
164
165 fn values(&self) -> &IndicatorValues {
166 &self.values
167 }
168
169 fn colors(&self) -> Vec<Color32> {
170 self.colors.clone()
171 }
172
173 fn set_colors(&mut self, colors: Vec<Color32>) {
174 if colors.len() >= 2 {
175 self.colors = colors;
176 }
177 }
178
179 fn is_overlay(&self) -> bool {
180 false
181 }
182
183 fn is_visible(&self) -> bool {
184 self.visible
185 }
186
187 fn set_visible(&mut self, visible: bool) {
188 self.visible = visible;
189 }
190
191 fn clone_box(&self) -> Box<dyn Indicator> {
192 Box::new(self.clone())
193 }
194
195 fn line_count(&self) -> usize {
196 2
197 }
198
199 fn line_names(&self) -> Vec<String> {
200 vec![
201 format!("KVO({},{})", self.short_period, self.long_period),
202 format!("Signal({})", self.signal_period),
203 ]
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use chrono::Utc;
211
212 fn make_bar(high: f64, low: f64, close: f64, volume: f64) -> Bar {
213 Bar {
214 time: Utc::now(),
215 open: (high + low) / 2.0,
216 high,
217 low,
218 close,
219 volume,
220 colors: None,
221 payload: None,
222 }
223 }
224
225 #[test]
229 fn matches_klingers_definition() {
230 let mut data: Vec<Bar> = (0..80)
231 .map(|i| {
232 let close = 100.0 + ((7 * i) % 11) as f64 * 0.5 + 0.25 * i as f64;
233 make_bar(
234 close + 1.0 + (i % 3) as f64 * 0.5,
235 close - 1.0 - (i % 2) as f64 * 0.5,
236 close,
237 1000.0 + 250.0 * (i % 4) as f64,
238 )
239 })
240 .collect();
241 for bar in &mut data[..2] {
244 bar.high = bar.close;
245 bar.low = bar.close;
246 }
247 data[40] = Bar {
248 volume: data[40].volume,
249 ..data[39].clone()
250 };
251
252 let expected = [
254 (-7848.617481565998, f64::NAN),
255 (-4339.64602772666, f64::NAN),
256 (-6205.425115696111, f64::NAN),
257 (-1752.73323152573, f64::NAN),
258 (-4040.127175139147, f64::NAN),
259 (-6200.828960250648, f64::NAN),
260 (-2795.50613638723, f64::NAN),
261 (-5050.879964616739, f64::NAN),
262 (-9170.953041291119, f64::NAN),
263 (-5461.165195491776, f64::NAN),
264 (-6452.141418963016, f64::NAN),
265 (-9929.238900876226, f64::NAN),
266 (-5248.866838205729, -5730.471499056624),
267 (-6125.694142781424, -5786.931876731596),
268 (-1845.4547310008347, -5223.863713055774),
269 (-4086.5283414884398, -5061.387231403298),
270 (-7313.215017025366, -5383.076915063593),
271 (-3092.9035000634976, -5055.9092843492945),
272 (-4119.682994057504, -4922.162671450468),
273 (-6919.806629935592, -5207.540379805487),
274 (-1342.7450352586602, -4655.42675915594),
275 (-2503.1976252211534, -4347.965454308113),
276 (-4561.556968612778, -4378.478527780208),
277 (818.4952172891135, -3636.05370705602),
278 (-1382.6286621873223, -3314.135843503349),
279 ];
280
281 let mut klinger = KlingerOscillator::new();
282 klinger.calculate(&data);
283
284 let values = klinger.values().to_rows();
285 assert_eq!(values.len(), 80);
286 assert!(
287 values[..55]
288 .iter()
289 .all(|v| matches!(v, IndicatorValue::None))
290 );
291 let close = |x: f64, y: f64| (x.is_nan() && y.is_nan()) || (x - y).abs() <= 1e-9 * y.abs();
292 for (offset, &(kvo, signal)) in expected.iter().enumerate() {
293 let bar = 55 + offset;
294 match &values[bar] {
295 IndicatorValue::Multiple(v) => {
296 assert_eq!(v.len(), 2, "bar {bar}");
297 assert!(close(v[0], kvo), "bar {bar} KVO: {} != {kvo}", v[0]);
298 assert!(
299 close(v[1], signal),
300 "bar {bar} signal: {} != {signal}",
301 v[1]
302 );
303 }
304 other => panic!("bar {bar}: expected two lines, got {other:?}"),
305 }
306 }
307 assert_eq!(klinger.line_count(), 2);
308
309 data[60].high = f64::NAN;
311 klinger.calculate(&data);
312 assert!(matches!(
313 klinger.values().to_rows()[60],
314 IndicatorValue::None
315 ));
316 assert!(klinger.values().to_rows()[61..].iter().all(|v| matches!(
317 v,
318 IndicatorValue::Multiple(l) if l[0].is_finite()
319 )));
320
321 klinger.calculate(&data[..55]);
323 assert!(
324 klinger
325 .values()
326 .to_rows()
327 .iter()
328 .all(|v| matches!(v, IndicatorValue::None))
329 );
330 }
331}