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 #[inline]
97 fn update(&mut self, candle: Candle) -> Option<f64> {
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 None;
104 };
105 self.has_emitted = true;
106 let tol2 = self.tolerance * bar2.open.abs().max(bar1.close.abs());
107 let tol3 = self.tolerance * candle.open.abs().max(bar2.close.abs());
108 if bar1.close < bar1.open
109 && bar2.close < bar2.open
110 && candle.close < candle.open
111 && bar2.close < bar1.close
112 && candle.close < bar2.close
113 && (bar2.open - bar1.close).abs() <= tol2
114 && (candle.open - bar2.close).abs() <= tol3
115 {
116 return Some(-1.0);
117 }
118 Some(0.0)
119 }
120
121 fn reset(&mut self) {
122 self.prev = None;
123 self.prev_prev = None;
124 self.has_emitted = false;
125 }
126
127 #[inline]
128 fn warmup_period(&self) -> usize {
129 3
130 }
131
132 #[inline]
133 fn is_ready(&self) -> bool {
134 self.has_emitted
135 }
136
137 #[inline]
138 fn name(&self) -> &'static str {
139 "IdenticalThreeCrows"
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::traits::BatchExt;
147
148 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
149 Candle::new(open, high, low, close, 1.0, ts).unwrap()
150 }
151
152 #[test]
153 fn rejects_invalid_tolerance() {
154 assert!(IdenticalThreeCrows::with_tolerance(-0.01).is_err());
155 assert!(IdenticalThreeCrows::with_tolerance(1.0).is_err());
156 }
157
158 #[test]
159 fn accepts_valid_tolerance() {
160 let t = IdenticalThreeCrows::with_tolerance(0.0).unwrap();
161 assert!((t.tolerance() - 0.0).abs() < 1e-12);
162 }
163
164 #[test]
165 fn accessors_and_metadata() {
166 let t = IdenticalThreeCrows::default();
167 assert_eq!(t.name(), "IdenticalThreeCrows");
168 assert_eq!(t.warmup_period(), 3);
169 assert!(!t.is_ready());
170 assert!((t.tolerance() - 0.001).abs() < 1e-12);
171 }
172
173 #[test]
174 fn identical_three_crows_is_minus_one() {
175 let mut t = IdenticalThreeCrows::new();
176 assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), None);
178 assert_eq!(t.update(c(12.0, 12.1, 10.9, 11.0, 1)), None);
179 assert_eq!(t.update(c(11.0, 11.1, 9.9, 10.0, 2)), Some(-1.0));
180 }
181
182 #[test]
183 fn non_identical_opens_yield_zero() {
184 let mut t = IdenticalThreeCrows::new();
185 t.update(c(13.0, 13.1, 11.9, 12.0, 0));
186 t.update(c(12.0, 12.1, 10.9, 11.0, 1));
187 assert_eq!(t.update(c(10.0, 10.1, 8.9, 9.0, 2)), Some(0.0));
189 }
190
191 #[test]
192 fn rising_close_yields_zero() {
193 let mut t = IdenticalThreeCrows::new();
194 t.update(c(13.0, 13.1, 11.9, 12.0, 0));
195 t.update(c(12.0, 12.1, 10.9, 11.0, 1));
196 assert_eq!(t.update(c(11.0, 12.2, 10.9, 12.0, 2)), Some(0.0));
198 }
199
200 #[test]
201 fn first_two_bars_return_zero() {
202 let mut t = IdenticalThreeCrows::new();
203 assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), None);
204 assert_eq!(t.update(c(12.0, 12.1, 10.9, 11.0, 1)), None);
205 }
206
207 #[test]
208 fn batch_equals_streaming() {
209 let candles: Vec<Candle> = (0..40)
210 .map(|i| {
211 let base = 100.0 - i as f64;
212 c(base, base + 0.1, base - 1.1, base - 1.0, i)
213 })
214 .collect();
215 let mut a = IdenticalThreeCrows::new();
216 let mut b = IdenticalThreeCrows::new();
217 assert_eq!(
218 a.batch(&candles),
219 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
220 );
221 }
222
223 #[test]
224 fn reset_clears_state() {
225 let mut t = IdenticalThreeCrows::new();
226 t.update(c(13.0, 13.1, 11.9, 12.0, 0));
227 t.update(c(12.0, 12.1, 10.9, 11.0, 1));
228 t.update(c(11.0, 11.1, 9.9, 10.0, 2));
229 assert!(t.is_ready());
230 t.reset();
231 assert!(!t.is_ready());
232 assert_eq!(t.update(c(13.0, 13.1, 11.9, 12.0, 0)), None);
233 }
234}