Skip to main content

quantwave_core/indicators/incremental/
volume_ta.rs

1//! Native O(1) volume indicators: AD, ADOSC.
2
3use crate::indicators::incremental::talib_ema::TalibEma;
4use crate::traits::Next;
5
6/// Accumulation/Distribution Line.
7#[derive(Debug, Clone)]
8#[allow(non_camel_case_types)]
9pub struct AD {
10    cumulative: f64,
11}
12
13impl AD {
14    pub fn new() -> Self {
15        Self { cumulative: 0.0 }
16    }
17}
18
19impl Next<(f64, f64, f64, f64)> for AD {
20    type Output = f64;
21
22    fn next(&mut self, (high, low, close, volume): (f64, f64, f64, f64)) -> Self::Output {
23        let hl = high - low;
24        let mfm = if hl > 0.0 {
25            ((close - low) - (high - close)) / hl
26        } else {
27            0.0
28        };
29        self.cumulative += mfm * volume;
30        self.cumulative
31    }
32}
33
34/// Chaikin A/D Oscillator.
35#[derive(Debug, Clone)]
36#[allow(non_camel_case_types)]
37pub struct ADOSC {
38    pub fastperiod: usize,
39    pub slowperiod: usize,
40    ad: AD,
41    fast_ema: TalibEma,
42    slow_ema: TalibEma,
43}
44
45impl ADOSC {
46    pub fn new(fastperiod: usize, slowperiod: usize) -> Self {
47        Self {
48            fastperiod,
49            slowperiod,
50            ad: AD::new(),
51            fast_ema: TalibEma::new(fastperiod),
52            slow_ema: TalibEma::new(slowperiod),
53        }
54    }
55}
56
57impl Next<(f64, f64, f64, f64)> for ADOSC {
58    type Output = f64;
59
60    fn next(&mut self, ohlcv: (f64, f64, f64, f64)) -> Self::Output {
61        let ad_val = self.ad.next(ohlcv);
62        let fast = self.fast_ema.next(ad_val);
63        let slow = self.slow_ema.next(ad_val);
64        if fast.is_nan() || slow.is_nan() {
65            f64::NAN
66        } else {
67            fast - slow
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use proptest::prelude::*;
76
77    proptest! {
78        #[test]
79        fn test_ad_parity(
80            h in prop::collection::vec(10.0..100.0, 1..100),
81            l in prop::collection::vec(10.0..100.0, 1..100),
82            c in prop::collection::vec(10.0..100.0, 1..100),
83            v in prop::collection::vec(1.0..1000.0, 1..100)
84        ) {
85            let len = h.len().min(l.len()).min(c.len()).min(v.len());
86            let mut ad = AD::new();
87            let mut high = Vec::with_capacity(len);
88            let mut low = Vec::with_capacity(len);
89            let mut close = Vec::with_capacity(len);
90            let mut vol = Vec::with_capacity(len);
91            for i in 0..len {
92                let v_h: f64 = h[i];
93                let v_l: f64 = l[i];
94                let v_c: f64 = c[i];
95                let v_v: f64 = v[i];
96                high.push(v_h.max(v_l).max(v_c));
97                low.push(v_h.min(v_l).min(v_c));
98                close.push(v_c);
99                vol.push(v_v);
100            }
101            let streaming: Vec<f64> = (0..len)
102                .map(|i| ad.next((high[i], low[i], close[i], vol[i])))
103                .collect();
104            let batch = talib_rs::volume::ad(&high, &low, &close, &vol)
105                .unwrap_or_else(|_| vec![f64::NAN; len]);
106            for (s, b) in streaming.iter().zip(batch.iter()) {
107                if s.is_nan() { assert!(b.is_nan()); }
108                else { approx::assert_relative_eq!(s, b, epsilon = 1e-6); }
109            }
110        }
111    }
112}