Skip to main content

kestrel_chartkit/indicator/
chaikin_osc.rs

1use super::smoothing::Ema;
2use super::volume_indicators::AccDistEngine;
3use super::{Indicator, IndicatorAlert, IndicatorOutput};
4use crate::model::Bar;
5
6/// Chaikin Oscillator: `Ema(fast_len) - Ema(slow_len)` of the Accumulation/Distribution line.
7///
8/// The A/D line is [`super::volume_indicators::AccDistEngine`]'s; both averages are the shared
9/// [`Ema`] with its first-sample seed and run from the first bar; the value is published from the
10/// `slow_len`-th bar on. Where every close sits mid-range the A/D line stays at zero, and so does
11/// this.
12///
13/// First output: with the `slow_len`-th bar. [`Indicator::reset`] clears the line and both
14/// averages.
15#[derive(Debug, Clone)]
16pub struct ChaikinOscillatorEngine {
17    fast_len: usize,
18    slow_len: usize,
19    adl: AccDistEngine,
20    fast_ema: Ema,
21    slow_ema: Ema,
22    count: usize,
23}
24
25impl ChaikinOscillatorEngine {
26    pub fn new(fast_len: usize, slow_len: usize) -> Self {
27        Self {
28            fast_len: fast_len.max(1),
29            slow_len: slow_len.max(1),
30            adl: AccDistEngine::new(),
31            fast_ema: Ema::new(fast_len),
32            slow_ema: Ema::new(slow_len),
33            count: 0,
34        }
35    }
36
37    pub fn with_defaults() -> Self {
38        Self::new(3, 10)
39    }
40
41    pub fn fast_len(&self) -> usize {
42        self.fast_len
43    }
44
45    pub fn slow_len(&self) -> usize {
46        self.slow_len
47    }
48}
49
50impl Indicator for ChaikinOscillatorEngine {
51    fn name(&self) -> &str {
52        "chaikin_oscillator"
53    }
54
55    fn warmup_period(&self) -> usize {
56        self.slow_len
57    }
58
59    fn reset(&mut self) {
60        self.adl.reset();
61        self.fast_ema.reset();
62        self.slow_ema.reset();
63        self.count = 0;
64    }
65
66    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
67        self.count += 1;
68        let adl_val = self.adl.on_bar(bar)?.value;
69
70        let fast = self.fast_ema.update(adl_val)?;
71        let slow = self.slow_ema.update(adl_val)?;
72
73        if self.count < self.slow_len {
74            return None;
75        }
76
77        let cho_val = fast - slow;
78        Some(IndicatorOutput::new(cho_val))
79    }
80
81    fn alerts(&self) -> Vec<IndicatorAlert> {
82        Vec::new()
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_chaikin_oscillator() {
92        let mut cho = ChaikinOscillatorEngine::with_defaults();
93        let mut out = None;
94        for i in 0..20 {
95            let b = Bar::new(i, 100.0, 105.0, 95.0, 100.0 + i as f64, 1000.0);
96            out = cho.on_bar(&b);
97        }
98        assert!(out.is_some());
99    }
100}