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)]
9pub struct ChaikinOscillatorEngine {
10 fast_len: usize,
11 slow_len: usize,
12 adl: AccDistEngine,
13 fast_ema: Ema,
14 slow_ema: Ema,
15 count: usize,
16}
17
18impl ChaikinOscillatorEngine {
19 pub fn new(fast_len: usize, slow_len: usize) -> Self {
20 Self {
21 fast_len: fast_len.max(1),
22 slow_len: slow_len.max(1),
23 adl: AccDistEngine::new(),
24 fast_ema: Ema::new(fast_len),
25 slow_ema: Ema::new(slow_len),
26 count: 0,
27 }
28 }
29
30 pub fn with_defaults() -> Self {
31 Self::new(3, 10)
32 }
33
34 pub fn fast_len(&self) -> usize {
35 self.fast_len
36 }
37
38 pub fn slow_len(&self) -> usize {
39 self.slow_len
40 }
41}
42
43impl Indicator for ChaikinOscillatorEngine {
44 fn name(&self) -> &str {
45 "chaikin_oscillator"
46 }
47
48 fn warmup_period(&self) -> usize {
49 self.slow_len
50 }
51
52 fn reset(&mut self) {
53 self.adl.reset();
54 self.fast_ema.reset();
55 self.slow_ema.reset();
56 self.count = 0;
57 }
58
59 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
60 self.count += 1;
61 let adl_val = self.adl.on_bar(bar)?.value;
62
63 let fast = self.fast_ema.update(adl_val);
64 let slow = self.slow_ema.update(adl_val);
65
66 if self.count < self.slow_len {
67 return None;
68 }
69
70 let cho_val = fast - slow;
71 Some(IndicatorOutput::new(cho_val))
72 }
73
74 fn alerts(&self) -> Vec<IndicatorAlert> {
75 Vec::new()
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_chaikin_oscillator() {
85 let mut cho = ChaikinOscillatorEngine::with_defaults();
86 let mut out = None;
87 for i in 0..20 {
88 let b = Bar::new(i, 100.0, 105.0, 95.0, 100.0 + i as f64, 1000.0);
89 out = cho.on_bar(&b);
90 }
91 assert!(out.is_some());
92 }
93}