Skip to main content

quantwave_core/indicators/patterns/
cdl3linestrike.rs

1//! Native O(1) streaming CDL3LINESTRIKE (matches `talib_rs::pattern::cdl_3linestrike`).
2
3use crate::indicators::patterns::candle_settings::{
4    CandleWindow, NEAR, RollingCandleAvg, candle_color,
5};
6use crate::traits::Next;
7
8const LOOKBACK: usize = 5 + 3; // NEAR.avg_period (5) + 3
9
10#[derive(Debug, Clone)]
11#[allow(non_camel_case_types)]
12pub struct CDL3LINESTRIKE {
13    bars_seen: usize,
14    window: CandleWindow,
15    near_avg: RollingCandleAvg,
16}
17
18impl CDL3LINESTRIKE {
19    pub fn new() -> Self {
20        Self {
21            bars_seen: 0,
22            window: CandleWindow::new(4), // Needs i, i-1, i-2, i-3 => capacity 4
23            near_avg: RollingCandleAvg::new(NEAR),
24        }
25    }
26}
27
28impl Default for CDL3LINESTRIKE {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl Next<(f64, f64, f64, f64)> for CDL3LINESTRIKE {
35    type Output = f64;
36
37    fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
38        self.bars_seen += 1;
39
40        self.window.push(open, high, low, close);
41        self.near_avg.push(open, high, low, close);
42
43        if self.bars_seen <= LOOKBACK {
44            return 0.0;
45        }
46
47        let c3 = candle_color(self.window.bar(3).open, self.window.bar(3).close);
48        let c2 = candle_color(self.window.bar(2).open, self.window.bar(2).close);
49        let c1 = candle_color(self.window.bar(1).open, self.window.bar(1).close);
50        let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
51
52        if c3 == c2 && c2 == c1 && c0 != c1 {
53            let progressive = if c3 == 1 {
54                self.window.bar(2).close > self.window.bar(3).close
55                    && self.window.bar(1).close > self.window.bar(2).close
56            } else {
57                self.window.bar(2).close < self.window.bar(3).close
58                    && self.window.bar(1).close < self.window.bar(2).close
59            };
60
61            let opens_near = if c3 == 1 {
62                self.window.bar(2).open >= self.window.bar(3).open.min(self.window.bar(3).close)
63                    && self.window.bar(2).open <= self.window.bar(3).close + self.near_avg.val(3)
64                    && self.window.bar(1).open
65                        >= self.window.bar(2).open.min(self.window.bar(2).close)
66                    && self.window.bar(1).open <= self.window.bar(2).close + self.near_avg.val(2)
67            } else {
68                self.window.bar(2).open <= self.window.bar(3).open.max(self.window.bar(3).close)
69                    && self.window.bar(2).open >= self.window.bar(3).close - self.near_avg.val(3)
70                    && self.window.bar(1).open
71                        <= self.window.bar(2).open.max(self.window.bar(2).close)
72                    && self.window.bar(1).open >= self.window.bar(2).close - self.near_avg.val(2)
73            };
74
75            let strike = if c3 == 1 {
76                self.window.bar(0).open >= self.window.bar(1).close
77                    && self.window.bar(0).close <= self.window.bar(3).open
78            } else {
79                self.window.bar(0).open <= self.window.bar(1).close
80                    && self.window.bar(0).close >= self.window.bar(3).open
81            };
82
83            if progressive && opens_near && strike {
84                return (c3 * 100) as f64;
85            }
86        }
87
88        0.0
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    crate::test_pattern_parity!(
96        test_cdl3linestrike_parity,
97        CDL3LINESTRIKE,
98        talib_rs::pattern::cdl_3linestrike
99    );
100}