Skip to main content

quantwave_core/indicators/incremental/
cdl_doji.rs

1//! Native O(1) streaming CDLDOJI (matches `talib_rs::pattern::cdl_doji`).
2
3use crate::indicators::incremental::utils::RingBuffer;
4use crate::traits::Next;
5
6/// Candle setting equivalent to TA-Lib `BODY_DOJI`: HighLow range, avg period 10, factor 0.1.
7const LOOKBACK: usize = 10;
8const FACTOR_DIV: f64 = 0.1 / LOOKBACK as f64; // 0.01
9
10#[derive(Debug, Clone)]
11#[allow(non_camel_case_types)]
12pub struct CDLDOJI {
13    bar_index: usize,
14    hl_sum: f64,
15    hl_window: RingBuffer<f64>,
16}
17
18impl CDLDOJI {
19    pub fn new() -> Self {
20        Self {
21            bar_index: 0,
22            hl_sum: 0.0,
23            hl_window: RingBuffer::with_capacity(LOOKBACK),
24        }
25    }
26}
27
28impl Default for CDLDOJI {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl Next<(f64, f64, f64, f64)> for CDLDOJI {
35    type Output = f64;
36
37    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
38        let idx = self.bar_index;
39        self.bar_index += 1;
40        let hl = high - low;
41
42        if idx < LOOKBACK {
43            self.hl_window.push_back(hl);
44            self.hl_sum += hl;
45            return 0.0;
46        }
47
48        let body = (close - open).abs();
49        let thresh = self.hl_sum * FACTOR_DIV;
50        let out = 100.0_f64.copysign(thresh - body).max(0.0) as f64;
51
52        if let Some(old) = self.hl_window.pop_front() {
53            self.hl_sum += hl - old;
54        }
55        self.hl_window.push_back(hl);
56
57        out
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use proptest::prelude::*;
65
66    proptest! {
67        #[test]
68        fn test_cdl_doji_parity(
69            o in prop::collection::vec(10.0..100.0, 1..100),
70            h in prop::collection::vec(10.0..100.0, 1..100),
71            l in prop::collection::vec(10.0..100.0, 1..100),
72            c in prop::collection::vec(10.0..100.0, 1..100)
73        ) {
74            let len = o.len().min(h.len()).min(l.len()).min(c.len());
75            if len == 0 { return Ok(()); }
76
77            let mut doji = CDLDOJI::new();
78            let streaming: Vec<f64> = (0..len)
79                .map(|i| doji.next((o[i], h[i], l[i], c[i])))
80                .collect();
81            let batch = talib_rs::pattern::cdl_doji(&o[..len], &h[..len], &l[..len], &c[..len])
82                .unwrap_or_else(|_| vec![0; len]);
83
84            for (s, b) in streaming.iter().zip(batch.iter()) {
85                assert_eq!(*s as i32, *b);
86            }
87        }
88    }
89}