Skip to main content

egui_charts/studies/builtin/
chaikin_oscillator.rs

1//! Chaikin Oscillator
2//!
3//! Marc Chaikin's oscillator is the momentum of the Accumulation/Distribution line: the
4//! difference between a fast and a slow exponential average of it.
5//!
6//! # Formula
7//! ```text
8//! money flow multiplier = ((close - low) - (high - close)) / (high - low), 0 when high == low
9//! ADL = previous ADL + money flow multiplier * volume
10//! Chaikin Oscillator = EMA(ADL, fast) - EMA(ADL, slow)
11//! ```
12//! with conventional lengths 3 and 10. Each EMA has alpha 2 / (length + 1) and is seeded with
13//! the first ADL value, as [`EMA`](super::EMA) and the Chaikin Volatility average are, so the
14//! oscillator starts at 0 on the first bar.
15//!
16//! # Example
17//! ```
18//! # let bars: Vec<egui_charts::model::Bar> = (0..60).map(|i| { let p = 100.0 + (i as f64 * 0.3).sin() * 5.0; egui_charts::model::Bar::new(chrono::Utc::now(), p, p + 1.0, p - 1.0, p + 0.5, 1_000.0) }).collect();
19//! use egui_charts::studies::{ChaikinOscillator, Indicator};
20//!
21//! let mut oscillator = ChaikinOscillator::new(3, 10);
22//! oscillator.calculate(&bars);
23//! assert_eq!(oscillator.values().value(0, 0), Some(0.0));
24//! ```
25
26use crate::model::Bar;
27use crate::studies::palette;
28use crate::studies::stepper::Stepper;
29use crate::studies::{Indicator, IndicatorValue, IndicatorValues};
30use egui::Color32;
31
32/// Fast minus slow EMA of the Accumulation/Distribution line.
33#[derive(Clone)]
34pub struct ChaikinOscillator {
35    fast_length: usize,
36    slow_length: usize,
37    values: IndicatorValues,
38    stepper: Stepper<(f64, Option<[f64; 2]>)>,
39    color: Color32,
40    visible: bool,
41}
42
43impl ChaikinOscillator {
44    /// Create a Chaikin Oscillator from its fast and slow EMA lengths (conventionally 3 and 10).
45    pub fn new(fast_length: usize, slow_length: usize) -> Self {
46        Self {
47            fast_length: fast_length.max(1),
48            slow_length: slow_length.max(1),
49            values: IndicatorValues::new(),
50            stepper: Stepper::default(),
51            color: Color32::from_rgb(236, 64, 122),
52            visible: true,
53        }
54    }
55}
56
57impl Default for ChaikinOscillator {
58    fn default() -> Self {
59        Self::new(3, 10)
60    }
61}
62
63impl Indicator for ChaikinOscillator {
64    crate::studies::inputs::study_inputs!(self;
65        fast_length: length("Fast Length", 1, usize::MAX),
66        slow_length: length("Slow Length", 1, usize::MAX)
67    ; {});
68
69    fn value_format(&self) -> crate::scales::StudyFormat {
70        crate::scales::StudyFormat::Volume
71    }
72
73    fn name(&self) -> &str {
74        "Chaikin Oscillator"
75    }
76
77    fn desc(&self) -> &str {
78        "Chaikin Oscillator - Momentum of the Accumulation/Distribution line"
79    }
80
81    fn calculate(&mut self, data: &[Bar]) {
82        self.update(data, 0);
83    }
84
85    fn update(&mut self, data: &[Bar], from: usize) {
86        self.stepper
87            .run(data, from, &mut self.values, |state, bar| {
88                if ![bar.high, bar.low, bar.close, bar.volume]
89                    .iter()
90                    .all(|x| x.is_finite())
91                {
92                    return IndicatorValue::None;
93                }
94                let range = bar.high - bar.low;
95                if range > 0.0 {
96                    let multiplier = (2.0 * bar.close - bar.high - bar.low) / range;
97                    state.0 += multiplier * bar.volume;
98                }
99                let adl = if state.0.is_finite() {
100                    state.0
101                } else {
102                    f64::NAN
103                };
104                let ema = state.1.get_or_insert([adl; 2]);
105                ema[0] += (adl - ema[0]) * (2.0 / (self.fast_length as f64 + 1.0));
106                ema[1] += (adl - ema[1]) * (2.0 / (self.slow_length as f64 + 1.0));
107                IndicatorValue::Single(ema[0] - ema[1])
108            });
109    }
110
111    fn is_incremental(&self) -> bool {
112        true
113    }
114
115    fn values(&self) -> &IndicatorValues {
116        &self.values
117    }
118
119    fn colors(&self) -> Vec<Color32> {
120        vec![self.color]
121    }
122
123    fn set_colors(&mut self, colors: Vec<Color32>) {
124        if let Some(&color) = colors.first() {
125            self.color = color;
126        }
127    }
128
129    fn pane(&self) -> crate::studies::StudyPane {
130        crate::studies::StudyPane {
131            levels: vec![(0.0, palette::GRAY, "Zero")],
132            ..Default::default()
133        }
134    }
135
136    fn is_overlay(&self) -> bool {
137        false
138    }
139
140    fn is_visible(&self) -> bool {
141        self.visible
142    }
143
144    fn set_visible(&mut self, visible: bool) {
145        self.visible = visible;
146    }
147
148    fn clone_box(&self) -> Box<dyn Indicator> {
149        Box::new(self.clone())
150    }
151
152    fn line_names(&self) -> Vec<String> {
153        vec![format!(
154            "Chaikin Osc({}, {})",
155            self.fast_length, self.slow_length
156        )]
157    }
158}