1use crate::error::{Error, Result};
4use crate::ohlcv::Candle;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone)]
45pub struct IdenticalThreeCrows {
46 tolerance: f64,
47 prev: Option<Candle>,
48 prev_prev: Option<Candle>,
49 has_emitted: bool,
50}
51
52impl Default for IdenticalThreeCrows {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl IdenticalThreeCrows {
59 pub const fn new() -> Self {
61 Self {
62 tolerance: 0.001,
63 prev: None,
64 prev_prev: None,
65 has_emitted: false,
66 }
67 }
68
69 pub fn with_tolerance(tolerance: f64) -> Result<Self> {
73 if !(0.0..1.0).contains(&tolerance) {
74 return Err(Error::InvalidPeriod {
75 message: "identical three crows tolerance must lie in [0, 1)",
76 });
77 }
78 Ok(Self {
79 tolerance,
80 prev: None,
81 prev_prev: None,
82 has_emitted: false,
83 })
84 }
85
86 pub fn tolerance(&self) -> f64 {
88 self.tolerance
89 }
90}
91
92impl Indicator for IdenticalThreeCrows {
93 type Input = Candle;
94 type Output = f64;
95
96 fn update(&mut self, candle: Candle) -> Option<f64> {
97 self.has_emitted = true;
98 let pp = self.prev_prev;
99 let p = self.prev;
100 self.prev_prev = self.prev;
101 self.prev = Some(candle);
102 let (Some(bar1), Some(bar2)) = (pp, p) else {
103 return Some(0.0);
104 };
105 let tol2 = self.tolerance * bar2.open.abs().max(bar1.close.abs());
106 let tol3 = self.tolerance * candle.open.abs().max(bar2.close.abs());
107 if bar1.close < bar1.open
108 && bar2.close < bar2.open
109 && candle.close < candle.open
110 && bar2.close < bar1.close
111 && candle.close < bar2.close
112 && (bar2.open - bar1.close).abs() <= tol2
113 && (candle.open - bar2.close).abs() <= tol3
114 {
115 return Some(-1.0);
116 }
117 Some(0.0)
118 }
119
120 fn reset(&mut self) {
121 self.prev = None;
122 self.prev_prev = None;
123 self.has_emitted = false;
124 }
125
126 fn warmup_period(&self) -> usize {
127 3
128 }
129
130 fn is_ready(&self) -> bool {
131 self.has_emitted
132 }
133
134 fn name(&self) -> &'static str {
135 "IdenticalThreeCrows"
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::traits::BatchExt;
143
144 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
145 Candle::new(open, high, low, close, 1.0, ts).unwrap()
146 }
147
148 #[test]
149 fn rejects_invalid_tolerance() {
150 assert!(IdenticalThreeCrows::with_tolerance(-0.01).is_err());
151 assert!(IdenticalThreeCrows::with_tolerance(1.0).is_err());
152 }
153
154 #[test]
155 fn accepts_valid_tolerance() {
156 let t = IdenticalThreeCrows::with_tolerance(0.0).unwrap();
157 assert!((t.tolerance() - 0.0).abs() < 1e-12);
158 }
159
160 #[test]
161 fn accessors_and_metadata() {
162 let t = IdenticalThreeCrows::default();
163 assert_eq!(t.name(), "IdenticalThreeCrows");
164 assert_eq!(t.warmup_period(), 3);
165 assert!(!t.is_ready());
166 assert!((t.tolerance() - 0.001).abs() < 1e-12);
167 }
168
169 #[test]
170 fn identical_three_crows_is_minus_one() {
171 let mut t = IdenticalThreeCrows::new();
172 assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), Some(0.0));
174 assert_eq!(t.update(c(12.0, 12.1, 10.9, 11.0, 1)), Some(0.0));
175 assert_eq!(t.update(c(11.0, 11.1, 9.9, 10.0, 2)), Some(-1.0));
176 }
177
178 #[test]
179 fn non_identical_opens_yield_zero() {
180 let mut t = IdenticalThreeCrows::new();
181 t.update(c(13.0, 13.1, 11.9, 12.0, 0));
182 t.update(c(12.0, 12.1, 10.9, 11.0, 1));
183 assert_eq!(t.update(c(10.0, 10.1, 8.9, 9.0, 2)), Some(0.0));
185 }
186
187 #[test]
188 fn rising_close_yields_zero() {
189 let mut t = IdenticalThreeCrows::new();
190 t.update(c(13.0, 13.1, 11.9, 12.0, 0));
191 t.update(c(12.0, 12.1, 10.9, 11.0, 1));
192 assert_eq!(t.update(c(11.0, 12.2, 10.9, 12.0, 2)), Some(0.0));
194 }
195
196 #[test]
197 fn first_two_bars_return_zero() {
198 let mut t = IdenticalThreeCrows::new();
199 assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), Some(0.0));
200 assert_eq!(t.update(c(12.0, 12.1, 10.9, 11.0, 1)), Some(0.0));
201 }
202
203 #[test]
204 fn batch_equals_streaming() {
205 let candles: Vec<Candle> = (0..40)
206 .map(|i| {
207 let base = 100.0 - i as f64;
208 c(base, base + 0.1, base - 1.1, base - 1.0, i)
209 })
210 .collect();
211 let mut a = IdenticalThreeCrows::new();
212 let mut b = IdenticalThreeCrows::new();
213 assert_eq!(
214 a.batch(&candles),
215 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
216 );
217 }
218
219 #[test]
220 fn reset_clears_state() {
221 let mut t = IdenticalThreeCrows::new();
222 t.update(c(13.0, 13.1, 11.9, 12.0, 0));
223 t.update(c(12.0, 12.1, 10.9, 11.0, 1));
224 t.update(c(11.0, 11.1, 9.9, 10.0, 2));
225 assert!(t.is_ready());
226 t.reset();
227 assert!(!t.is_ready());
228 assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), Some(0.0));
229 }
230}