quantwave_core/indicators/incremental/
cdl_doji.rs1use crate::indicators::incremental::utils::RingBuffer;
4use crate::traits::Next;
5
6const LOOKBACK: usize = 10;
8const FACTOR_DIV: f64 = 0.1 / LOOKBACK as f64; #[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}