1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
46pub struct FallingThreeMethods {
47 c1: Option<Candle>,
48 c2: Option<Candle>,
49 c3: Option<Candle>,
50 c4: Option<Candle>,
51 has_emitted: bool,
52}
53
54impl FallingThreeMethods {
55 pub const fn new() -> Self {
57 Self {
58 c1: None,
59 c2: None,
60 c3: None,
61 c4: None,
62 has_emitted: false,
63 }
64 }
65}
66
67impl Indicator for FallingThreeMethods {
68 type Input = Candle;
69 type Output = f64;
70
71 #[inline]
72 fn update(&mut self, candle: Candle) -> Option<f64> {
73 let bar1 = self.c1;
74 let bar2 = self.c2;
75 let bar3 = self.c3;
76 let bar4 = self.c4;
77 self.c1 = self.c2;
78 self.c2 = self.c3;
79 self.c3 = self.c4;
80 self.c4 = Some(candle);
81 let (Some(bar1), Some(bar2), Some(bar3), Some(bar4)) = (bar1, bar2, bar3, bar4) else {
82 return None;
83 };
84 self.has_emitted = true;
85 let range1 = bar1.high - bar1.low;
86 if range1 <= 0.0 {
87 return Some(0.0);
88 }
89 let body1 = bar1.open - bar1.close;
90 if body1 < 0.5 * range1 {
91 return Some(0.0); }
93 for mid in [bar2, bar3, bar4] {
95 if (mid.close - mid.open).abs() >= body1 || mid.high > bar1.high || mid.low < bar1.low {
96 return Some(0.0);
97 }
98 }
99 if candle.close < candle.open && candle.close < bar1.close {
101 return Some(-1.0);
102 }
103 Some(0.0)
104 }
105
106 fn reset(&mut self) {
107 self.c1 = None;
108 self.c2 = None;
109 self.c3 = None;
110 self.c4 = None;
111 self.has_emitted = false;
112 }
113
114 #[inline]
115 fn warmup_period(&self) -> usize {
116 5
117 }
118
119 #[inline]
120 fn is_ready(&self) -> bool {
121 self.has_emitted
122 }
123
124 #[inline]
125 fn name(&self) -> &'static str {
126 "FallingThreeMethods"
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::traits::BatchExt;
134
135 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
136 Candle::new(open, high, low, close, 1.0, ts).unwrap()
137 }
138
139 #[test]
140 fn accessors_and_metadata() {
141 let t = FallingThreeMethods::new();
142 assert_eq!(t.name(), "FallingThreeMethods");
143 assert_eq!(t.warmup_period(), 5);
144 assert!(!t.is_ready());
145 }
146
147 #[test]
148 fn falling_three_methods_is_minus_one() {
149 let mut t = FallingThreeMethods::new();
150 assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), None);
151 assert_eq!(t.update(c(11.0, 12.1, 10.9, 12.0, 1)), None);
152 assert_eq!(t.update(c(11.5, 12.6, 11.4, 12.5, 2)), None);
153 assert_eq!(t.update(c(12.0, 13.1, 11.9, 13.0, 3)), None);
154 assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 4)), Some(-1.0));
155 }
156
157 #[test]
158 fn middle_bar_breaks_range_yields_zero() {
159 let mut t = FallingThreeMethods::new();
160 t.update(c(15.0, 15.1, 9.9, 10.0, 0));
161 t.update(c(11.0, 12.1, 10.9, 12.0, 1));
162 t.update(c(11.5, 12.6, 9.0, 12.5, 2));
164 t.update(c(12.0, 13.1, 11.9, 13.0, 3));
165 assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 4)), Some(0.0));
166 }
167
168 #[test]
169 fn bar5_not_new_low_yields_zero() {
170 let mut t = FallingThreeMethods::new();
171 t.update(c(15.0, 15.1, 9.9, 10.0, 0));
172 t.update(c(11.0, 12.1, 10.9, 12.0, 1));
173 t.update(c(11.5, 12.6, 11.4, 12.5, 2));
174 t.update(c(12.0, 13.1, 11.9, 13.0, 3));
175 assert_eq!(t.update(c(12.5, 12.6, 10.4, 10.5, 4)), Some(0.0));
177 }
178
179 #[test]
180 fn first_four_bars_return_zero() {
181 let mut t = FallingThreeMethods::new();
182 assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), None);
183 assert_eq!(t.update(c(11.0, 12.1, 10.9, 12.0, 1)), None);
184 assert_eq!(t.update(c(11.5, 12.6, 11.4, 12.5, 2)), None);
185 assert_eq!(t.update(c(12.0, 13.1, 11.9, 13.0, 3)), None);
186 }
187
188 #[test]
189 fn batch_equals_streaming() {
190 let candles: Vec<Candle> = (0..40)
191 .map(|i| {
192 let base = 200.0 - i as f64;
193 c(base + 5.0, base + 5.1, base - 0.1, base, i)
194 })
195 .collect();
196 let mut a = FallingThreeMethods::new();
197 let mut b = FallingThreeMethods::new();
198 assert_eq!(
199 a.batch(&candles),
200 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
201 );
202 }
203
204 #[test]
205 fn reset_clears_state() {
206 let mut t = FallingThreeMethods::new();
207 t.update(c(15.0, 15.1, 9.9, 10.0, 0));
208 t.update(c(11.0, 12.1, 10.9, 12.0, 1));
209 t.update(c(11.5, 12.6, 11.4, 12.5, 2));
210 t.update(c(12.0, 13.1, 11.9, 13.0, 3));
211 t.update(c(12.5, 12.6, 8.9, 9.0, 4));
212 assert!(t.is_ready());
213 t.reset();
214 assert!(!t.is_ready());
215 assert_eq!(t.update(c(15.0, 15.1, 9.9, 10.0, 0)), None);
216 }
217
218 #[test]
219 fn zero_range_first_bar_yields_zero() {
220 let mut t = FallingThreeMethods::new();
221 t.update(c(10.0, 10.0, 10.0, 10.0, 0));
223 t.update(c(11.0, 12.1, 10.9, 12.0, 1));
224 t.update(c(11.5, 12.6, 11.4, 12.5, 2));
225 t.update(c(12.0, 13.1, 11.9, 13.0, 3));
226 assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 4)), Some(0.0));
227 }
228
229 #[test]
230 fn short_first_body_yields_zero() {
231 let mut t = FallingThreeMethods::new();
232 t.update(c(10.0, 16.0, 9.0, 10.2, 0));
234 t.update(c(11.0, 12.1, 10.9, 12.0, 1));
235 t.update(c(11.5, 12.6, 11.4, 12.5, 2));
236 t.update(c(12.0, 13.1, 11.9, 13.0, 3));
237 assert_eq!(t.update(c(12.5, 12.6, 8.9, 9.0, 4)), Some(0.0));
238 }
239}