1use crate::ohlcv::Candle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
42pub struct AdvanceBlock {
43 prev: Option<Candle>,
44 prev_prev: Option<Candle>,
45 has_emitted: bool,
46}
47
48impl AdvanceBlock {
49 pub const fn new() -> Self {
51 Self {
52 prev: None,
53 prev_prev: None,
54 has_emitted: false,
55 }
56 }
57}
58
59impl Indicator for AdvanceBlock {
60 type Input = Candle;
61 type Output = f64;
62
63 #[inline]
64 fn update(&mut self, candle: Candle) -> Option<f64> {
65 let pp = self.prev_prev;
66 let p = self.prev;
67 self.prev_prev = self.prev;
68 self.prev = Some(candle);
69 let (Some(bar1), Some(bar2)) = (pp, p) else {
70 return None;
71 };
72 self.has_emitted = true;
73 let body1 = bar1.close - bar1.open;
74 let body2 = bar2.close - bar2.open;
75 let body3 = candle.close - candle.open;
76 let upper2 = bar2.high - bar2.close;
77 let upper3 = candle.high - candle.close;
78 if bar1.close > bar1.open
79 && bar2.close > bar2.open
80 && candle.close > candle.open
81 && bar2.close > bar1.close
82 && candle.close > bar2.close
83 && bar2.open >= bar1.open
84 && bar2.open <= bar1.close
85 && candle.open >= bar2.open
86 && candle.open <= bar2.close
87 && body2 < body1
88 && body3 < body2
89 && upper3 >= upper2
90 && upper3 > 0.0
91 {
92 return Some(-1.0);
93 }
94 Some(0.0)
95 }
96
97 fn reset(&mut self) {
98 self.prev = None;
99 self.prev_prev = None;
100 self.has_emitted = false;
101 }
102
103 #[inline]
104 fn warmup_period(&self) -> usize {
105 3
106 }
107
108 #[inline]
109 fn is_ready(&self) -> bool {
110 self.has_emitted
111 }
112
113 #[inline]
114 fn name(&self) -> &'static str {
115 "AdvanceBlock"
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::traits::BatchExt;
123
124 fn c(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
125 Candle::new(open, high, low, close, 1.0, ts).unwrap()
126 }
127
128 #[test]
129 fn accessors_and_metadata() {
130 let t = AdvanceBlock::new();
131 assert_eq!(t.name(), "AdvanceBlock");
132 assert_eq!(t.warmup_period(), 3);
133 assert!(!t.is_ready());
134 }
135
136 #[test]
137 fn advance_block_is_minus_one() {
138 let mut t = AdvanceBlock::new();
139 assert_eq!(t.update(c(10.0, 13.1, 9.9, 13.0, 0)), None);
140 assert_eq!(t.update(c(12.0, 14.3, 11.9, 14.0, 1)), None);
141 assert_eq!(t.update(c(13.5, 15.0, 13.4, 14.5, 2)), Some(-1.0));
142 }
143
144 #[test]
145 fn strong_advance_yields_zero() {
146 let mut t = AdvanceBlock::new();
147 assert_eq!(t.update(c(10.0, 11.1, 9.9, 11.0, 0)), None);
149 assert_eq!(t.update(c(10.5, 12.6, 10.4, 12.5, 1)), None);
150 assert_eq!(t.update(c(11.5, 14.1, 11.4, 14.0, 2)), Some(0.0));
151 }
152
153 #[test]
154 fn no_upper_shadow_growth_yields_zero() {
155 let mut t = AdvanceBlock::new();
156 t.update(c(10.0, 13.1, 9.9, 13.0, 0));
157 t.update(c(12.0, 14.3, 11.9, 14.0, 1));
158 assert_eq!(t.update(c(13.5, 14.5, 13.4, 14.5, 2)), Some(0.0));
160 }
161
162 #[test]
163 fn first_two_bars_return_zero() {
164 let mut t = AdvanceBlock::new();
165 assert_eq!(t.update(c(10.0, 13.1, 9.9, 13.0, 0)), None);
166 assert_eq!(t.update(c(12.0, 14.3, 11.9, 14.0, 1)), None);
167 }
168
169 #[test]
170 fn batch_equals_streaming() {
171 let candles: Vec<Candle> = (0..40)
172 .map(|i| {
173 let base = 100.0 + i as f64;
174 c(base, base + 2.0, base - 0.2, base + 1.5, i)
175 })
176 .collect();
177 let mut a = AdvanceBlock::new();
178 let mut b = AdvanceBlock::new();
179 assert_eq!(
180 a.batch(&candles),
181 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
182 );
183 }
184
185 #[test]
186 fn reset_clears_state() {
187 let mut t = AdvanceBlock::new();
188 t.update(c(10.0, 13.1, 9.9, 13.0, 0));
189 t.update(c(12.0, 14.3, 11.9, 14.0, 1));
190 t.update(c(13.5, 15.0, 13.4, 14.5, 2));
191 assert!(t.is_ready());
192 t.reset();
193 assert!(!t.is_ready());
194 assert_eq!(t.update(c(10.0, 13.1, 9.9, 13.0, 0)), None);
195 }
196}