kestrel_chartkit/indicator/
chaikin_osc.rs1use super::smoothing::Ema;
2use super::volume_indicators::AccDistEngine;
3use super::{Indicator, IndicatorAlert, IndicatorOutput};
4use crate::model::Bar;
5
6#[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}