1use crate::indicators::patterns::candle_settings::{
5 BODY_DOJI, BODY_LONG, BODY_SHORT, CandleWindow, EQUAL, NEAR, RollingCandleAvg,
6 SHADOW_VERY_SHORT, candle_color, lower_shadow, real_body, upper_shadow,
7};
8use crate::traits::Next;
9
10#[inline(always)]
11fn real_body_gap_up(window: &CandleWindow, i: usize, j: usize) -> bool {
12 window.bar(i).open.min(window.bar(i).close) > window.bar(j).open.max(window.bar(j).close)
13}
14
15#[inline(always)]
16fn real_body_gap_down(window: &CandleWindow, i: usize, j: usize) -> bool {
17 window.bar(i).open.max(window.bar(i).close) < window.bar(j).open.min(window.bar(j).close)
18}
19
20const CDLGAPSIDESIDEWHITE_LOOKBACK: usize = 5 + 2; #[derive(Debug, Clone)]
23#[allow(non_camel_case_types)]
24pub struct CDLGAPSIDESIDEWHITE {
25 bars_seen: usize,
26 window: CandleWindow,
27 near_avg: RollingCandleAvg,
28 equal_avg: RollingCandleAvg,
29}
30impl CDLGAPSIDESIDEWHITE {
31 pub fn new() -> Self {
32 Self {
33 bars_seen: 0,
34 window: CandleWindow::new(3),
35 near_avg: RollingCandleAvg::new(NEAR),
36 equal_avg: RollingCandleAvg::new(EQUAL),
37 }
38 }
39}
40impl Default for CDLGAPSIDESIDEWHITE {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45impl Next<(f64, f64, f64, f64)> for CDLGAPSIDESIDEWHITE {
46 type Output = f64;
47 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
48 self.bars_seen += 1;
49 self.window.push(open, high, low, close);
50 self.near_avg.push(open, high, low, close);
51 self.equal_avg.push(open, high, low, close);
52
53 if self.bars_seen <= CDLGAPSIDESIDEWHITE_LOOKBACK {
54 return 0.0;
55 }
56
57 let base = candle_color(self.window.bar(1).open, self.window.bar(1).close) == 1
61 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
62 && (real_body(self.window.bar(1).open, self.window.bar(1).close)
63 - real_body(self.window.bar(0).open, self.window.bar(0).close))
64 .abs()
65 < self.near_avg.val(1)
66 && (self.window.bar(1).open - self.window.bar(0).open).abs() < self.equal_avg.val(1);
67
68 let bull = base && real_body_gap_up(&self.window, 1, 2);
69 let bear = base && real_body_gap_down(&self.window, 1, 2);
70
71 if bull {
72 100.0
73 } else if bear {
74 -100.0
75 } else {
76 0.0
77 }
78 }
79}
80
81const CDLIDENTICAL3CROWS_LOOKBACK: usize = 10 + 2; #[derive(Debug, Clone)]
84#[allow(non_camel_case_types)]
85pub struct CDLIDENTICAL3CROWS {
86 bars_seen: usize,
87 window: CandleWindow,
88 shadow_avg: RollingCandleAvg,
89 equal_avg: RollingCandleAvg,
90}
91impl CDLIDENTICAL3CROWS {
92 pub fn new() -> Self {
93 Self {
94 bars_seen: 0,
95 window: CandleWindow::new(3),
96 shadow_avg: RollingCandleAvg::new(SHADOW_VERY_SHORT),
97 equal_avg: RollingCandleAvg::new(EQUAL),
98 }
99 }
100}
101impl Default for CDLIDENTICAL3CROWS {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106impl Next<(f64, f64, f64, f64)> for CDLIDENTICAL3CROWS {
107 type Output = f64;
108 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
109 self.bars_seen += 1;
110 self.window.push(open, high, low, close);
111 self.shadow_avg.push(open, high, low, close);
112 self.equal_avg.push(open, high, low, close);
113
114 if self.bars_seen <= CDLIDENTICAL3CROWS_LOOKBACK {
115 return 0.0;
116 }
117
118 let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
119 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
120 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
121 && self.window.bar(1).close < self.window.bar(2).close
122 && self.window.bar(0).close < self.window.bar(1).close
123 && lower_shadow(
124 self.window.bar(2).open,
125 self.window.bar(2).low,
126 self.window.bar(2).close,
127 ) < self.shadow_avg.val(2)
128 && lower_shadow(
129 self.window.bar(1).open,
130 self.window.bar(1).low,
131 self.window.bar(1).close,
132 ) < self.shadow_avg.val(1)
133 && lower_shadow(
134 self.window.bar(0).open,
135 self.window.bar(0).low,
136 self.window.bar(0).close,
137 ) < self.shadow_avg.val(0)
138 && (self.window.bar(1).open - self.window.bar(2).close).abs() <= self.equal_avg.val(2)
139 && (self.window.bar(0).open - self.window.bar(1).close).abs() <= self.equal_avg.val(1);
140
141 if cond { -100.0 } else { 0.0 }
142 }
143}
144
145const CDLSTALLEDPATTERN_LOOKBACK: usize = 10 + 2;
146
147#[derive(Debug, Clone)]
148#[allow(non_camel_case_types)]
149pub struct CDLSTALLEDPATTERN {
150 bars_seen: usize,
151 window: CandleWindow,
152 body_long: RollingCandleAvg,
153 body_short: RollingCandleAvg,
154 shadow_vs: RollingCandleAvg,
155 near: RollingCandleAvg,
156}
157impl CDLSTALLEDPATTERN {
158 pub fn new() -> Self {
159 Self {
160 bars_seen: 0,
161 window: CandleWindow::new(3),
162 body_long: RollingCandleAvg::new(BODY_LONG),
163 body_short: RollingCandleAvg::new(BODY_SHORT),
164 shadow_vs: RollingCandleAvg::new(SHADOW_VERY_SHORT),
165 near: RollingCandleAvg::new(NEAR),
166 }
167 }
168}
169impl Default for CDLSTALLEDPATTERN {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174impl Next<(f64, f64, f64, f64)> for CDLSTALLEDPATTERN {
175 type Output = f64;
176 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
177 self.bars_seen += 1;
178 self.window.push(open, high, low, close);
179 self.body_long.push(open, high, low, close);
180 self.body_short.push(open, high, low, close);
181 self.shadow_vs.push(open, high, low, close);
182 self.near.push(open, high, low, close);
183
184 if self.bars_seen <= CDLSTALLEDPATTERN_LOOKBACK {
185 return 0.0;
186 }
187
188 let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
189 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == 1
190 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
191 && self.window.bar(1).close > self.window.bar(2).close
192 && self.window.bar(0).close > self.window.bar(1).close
193 && real_body(self.window.bar(2).open, self.window.bar(2).close) > self.body_long.val(2)
194 && real_body(self.window.bar(1).open, self.window.bar(1).close) > self.body_long.val(1)
195 && upper_shadow(
196 self.window.bar(1).open,
197 self.window.bar(1).high,
198 self.window.bar(1).close,
199 ) < self.shadow_vs.val(1)
200 && self.window.bar(1).open > self.window.bar(2).open
201 && self.window.bar(1).open <= self.window.bar(2).close + self.near.val(2)
202 && real_body(self.window.bar(0).open, self.window.bar(0).close)
203 < self.body_short.val(0)
204 && self.window.bar(0).open
205 >= self.window.bar(1).close
206 - real_body(self.window.bar(0).open, self.window.bar(0).close)
207 - self.near.val(1);
208
209 if cond { -100.0 } else { 0.0 }
210 }
211}
212
213const CDLTASUKIGAP_LOOKBACK: usize = 5 + 2;
214
215#[derive(Debug, Clone)]
216#[allow(non_camel_case_types)]
217pub struct CDLTASUKIGAP {
218 bars_seen: usize,
219 window: CandleWindow,
220 near: RollingCandleAvg,
221}
222impl CDLTASUKIGAP {
223 pub fn new() -> Self {
224 Self {
225 bars_seen: 0,
226 window: CandleWindow::new(3),
227 near: RollingCandleAvg::new(NEAR),
228 }
229 }
230}
231impl Default for CDLTASUKIGAP {
232 fn default() -> Self {
233 Self::new()
234 }
235}
236impl Next<(f64, f64, f64, f64)> for CDLTASUKIGAP {
237 type Output = f64;
238 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
239 self.bars_seen += 1;
240 self.window.push(open, high, low, close);
241 self.near.push(open, high, low, close);
242
243 if self.bars_seen <= CDLTASUKIGAP_LOOKBACK {
244 return 0.0;
245 }
246
247 let c1 = candle_color(self.window.bar(1).open, self.window.bar(1).close);
248 let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
249
250 let near_same = (real_body(self.window.bar(1).open, self.window.bar(1).close)
251 - real_body(self.window.bar(0).open, self.window.bar(0).close))
252 .abs()
253 < self.near.val(1);
254
255 let bull = real_body_gap_up(&self.window, 1, 2)
256 && c1 == 1
257 && c0 == -1
258 && self.window.bar(0).open < self.window.bar(1).close
259 && self.window.bar(0).open > self.window.bar(1).open
260 && self.window.bar(0).close < self.window.bar(1).open
261 && self.window.bar(0).close > self.window.bar(2).open.max(self.window.bar(2).close)
262 && near_same;
263
264 let bear = real_body_gap_down(&self.window, 1, 2)
265 && c1 == -1
266 && c0 == 1
267 && self.window.bar(0).open < self.window.bar(1).open
268 && self.window.bar(0).open > self.window.bar(1).close
269 && self.window.bar(0).close > self.window.bar(1).open
270 && self.window.bar(0).close < self.window.bar(2).open.min(self.window.bar(2).close)
271 && near_same;
272
273 if bull || bear { (c1 * 100) as f64 } else { 0.0 }
274 }
275}
276
277const CDLTRISTAR_LOOKBACK: usize = 10 + 2;
278
279#[derive(Debug, Clone)]
280#[allow(non_camel_case_types)]
281pub struct CDLTRISTAR {
282 bars_seen: usize,
283 window: CandleWindow,
284 body_doji: RollingCandleAvg,
285}
286impl CDLTRISTAR {
287 pub fn new() -> Self {
288 Self {
289 bars_seen: 0,
290 window: CandleWindow::new(3),
291 body_doji: RollingCandleAvg::new(BODY_DOJI),
292 }
293 }
294}
295impl Default for CDLTRISTAR {
296 fn default() -> Self {
297 Self::new()
298 }
299}
300impl Next<(f64, f64, f64, f64)> for CDLTRISTAR {
301 type Output = f64;
302 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
303 self.bars_seen += 1;
304 self.window.push(open, high, low, close);
305 self.body_doji.push(open, high, low, close);
306
307 if self.bars_seen <= CDLTRISTAR_LOOKBACK {
308 return 0.0;
309 }
310
311 let base = real_body(self.window.bar(2).open, self.window.bar(2).close)
312 <= self.body_doji.val(2)
313 && real_body(self.window.bar(1).open, self.window.bar(1).close)
314 <= self.body_doji.val(1)
315 && real_body(self.window.bar(0).open, self.window.bar(0).close)
316 <= self.body_doji.val(0);
317
318 let bear =
319 base && real_body_gap_up(&self.window, 1, 2) && !real_body_gap_up(&self.window, 0, 1);
320 let bull = base
321 && real_body_gap_down(&self.window, 1, 2)
322 && !real_body_gap_down(&self.window, 0, 1);
323
324 (bull as i32 * 100 - bear as i32 * 100) as f64
325 }
326}
327
328const CDLUNIQUE3RIVER_LOOKBACK: usize = 10 + 2;
329
330#[derive(Debug, Clone)]
331#[allow(non_camel_case_types)]
332pub struct CDLUNIQUE3RIVER {
333 bars_seen: usize,
334 window: CandleWindow,
335 body_long: RollingCandleAvg,
336 body_short: RollingCandleAvg,
337}
338impl CDLUNIQUE3RIVER {
339 pub fn new() -> Self {
340 Self {
341 bars_seen: 0,
342 window: CandleWindow::new(3),
343 body_long: RollingCandleAvg::new(BODY_LONG),
344 body_short: RollingCandleAvg::new(BODY_SHORT),
345 }
346 }
347}
348impl Default for CDLUNIQUE3RIVER {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353impl Next<(f64, f64, f64, f64)> for CDLUNIQUE3RIVER {
354 type Output = f64;
355 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
356 self.bars_seen += 1;
357 self.window.push(open, high, low, close);
358 self.body_long.push(open, high, low, close);
359 self.body_short.push(open, high, low, close);
360
361 if self.bars_seen <= CDLUNIQUE3RIVER_LOOKBACK {
362 return 0.0;
363 }
364
365 let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
366 && real_body(self.window.bar(2).open, self.window.bar(2).close) > self.body_long.val(2)
367 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
368 && self.window.bar(1).open.max(self.window.bar(1).close)
369 < self.window.bar(2).open.max(self.window.bar(2).close)
370 && self.window.bar(1).open.min(self.window.bar(1).close)
371 > self.window.bar(2).open.min(self.window.bar(2).close)
372 && self.window.bar(1).low < self.window.bar(2).low
373 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
374 && real_body(self.window.bar(0).open, self.window.bar(0).close)
375 < self.body_short.val(0)
376 && self.window.bar(0).close < self.window.bar(1).close;
377
378 if cond { 100.0 } else { 0.0 }
379 }
380}
381
382const CDLUPSIDEGAP2CROWS_LOOKBACK: usize = 10 + 2;
383
384#[derive(Debug, Clone)]
385#[allow(non_camel_case_types)]
386pub struct CDLUPSIDEGAP2CROWS {
387 bars_seen: usize,
388 window: CandleWindow,
389 body_long: RollingCandleAvg,
390 body_short: RollingCandleAvg,
391}
392impl CDLUPSIDEGAP2CROWS {
393 pub fn new() -> Self {
394 Self {
395 bars_seen: 0,
396 window: CandleWindow::new(3),
397 body_long: RollingCandleAvg::new(BODY_LONG),
398 body_short: RollingCandleAvg::new(BODY_SHORT),
399 }
400 }
401}
402impl Default for CDLUPSIDEGAP2CROWS {
403 fn default() -> Self {
404 Self::new()
405 }
406}
407impl Next<(f64, f64, f64, f64)> for CDLUPSIDEGAP2CROWS {
408 type Output = f64;
409 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
410 self.bars_seen += 1;
411 self.window.push(open, high, low, close);
412 self.body_long.push(open, high, low, close);
413 self.body_short.push(open, high, low, close);
414
415 if self.bars_seen <= CDLUPSIDEGAP2CROWS_LOOKBACK {
416 return 0.0;
417 }
418
419 let cond = candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
420 && real_body(self.window.bar(2).open, self.window.bar(2).close) > self.body_long.val(2)
421 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
422 && real_body(self.window.bar(1).open, self.window.bar(1).close)
423 <= self.body_short.val(1)
424 && real_body_gap_up(&self.window, 1, 2)
425 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
426 && self.window.bar(0).open > self.window.bar(1).open
427 && self.window.bar(0).close < self.window.bar(1).close
428 && self.window.bar(0).close > self.window.bar(2).close;
429
430 if cond { -100.0 } else { 0.0 }
431 }
432}
433
434const CDLXSIDEGAP3METHODS_LOOKBACK: usize = 2;
435
436#[derive(Debug, Clone)]
437#[allow(non_camel_case_types)]
438pub struct CDLXSIDEGAP3METHODS {
439 bars_seen: usize,
440 window: CandleWindow,
441}
442impl CDLXSIDEGAP3METHODS {
443 pub fn new() -> Self {
444 Self {
445 bars_seen: 0,
446 window: CandleWindow::new(3),
447 }
448 }
449}
450impl Default for CDLXSIDEGAP3METHODS {
451 fn default() -> Self {
452 Self::new()
453 }
454}
455impl Next<(f64, f64, f64, f64)> for CDLXSIDEGAP3METHODS {
456 type Output = f64;
457 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
458 self.bars_seen += 1;
459 self.window.push(open, high, low, close);
460
461 if self.bars_seen <= CDLXSIDEGAP3METHODS_LOOKBACK {
462 return 0.0;
463 }
464
465 let c2 = candle_color(self.window.bar(2).open, self.window.bar(2).close);
466 let c1 = candle_color(self.window.bar(1).open, self.window.bar(1).close);
467 let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
468
469 let opens_within = self.window.bar(0).open
470 > self.window.bar(1).open.min(self.window.bar(1).close)
471 && self.window.bar(0).open < self.window.bar(1).open.max(self.window.bar(1).close);
472 let closes_within = self.window.bar(0).close
473 > self.window.bar(2).open.min(self.window.bar(2).close)
474 && self.window.bar(0).close < self.window.bar(2).open.max(self.window.bar(2).close);
475
476 let base = c2 == c1 && c0 != c2 && opens_within && closes_within;
477 let bull = base && c2 == 1 && real_body_gap_up(&self.window, 1, 2);
478 let bear = base && c2 == -1 && real_body_gap_down(&self.window, 1, 2);
479
480 (bull as i32 * 100 - bear as i32 * 100) as f64
481 }
482}
483
484const CDLBREAKAWAY_LOOKBACK: usize = 10 + 4;
485
486#[derive(Debug, Clone)]
487#[allow(non_camel_case_types)]
488pub struct CDLBREAKAWAY {
489 bars_seen: usize,
490 window: CandleWindow,
491 body_long: RollingCandleAvg,
492}
493impl CDLBREAKAWAY {
494 pub fn new() -> Self {
495 Self {
496 bars_seen: 0,
497 window: CandleWindow::new(5),
498 body_long: RollingCandleAvg::new(BODY_LONG),
499 }
500 }
501}
502impl Default for CDLBREAKAWAY {
503 fn default() -> Self {
504 Self::new()
505 }
506}
507impl Next<(f64, f64, f64, f64)> for CDLBREAKAWAY {
508 type Output = f64;
509 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
510 self.bars_seen += 1;
511 self.window.push(open, high, low, close);
512 self.body_long.push(open, high, low, close);
513
514 if self.bars_seen <= CDLBREAKAWAY_LOOKBACK {
515 return 0.0;
516 }
517
518 let c4 = candle_color(self.window.bar(4).open, self.window.bar(4).close);
519 let c3 = candle_color(self.window.bar(3).open, self.window.bar(3).close);
520 let c1 = candle_color(self.window.bar(1).open, self.window.bar(1).close);
521 let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
522
523 let base = real_body(self.window.bar(4).open, self.window.bar(4).close)
527 > self.body_long.val(4)
528 && c4 == c3
529 && c3 == c1
530 && c1 == -c0;
531
532 let bear_first = base
533 && c4 == -1
534 && real_body_gap_down(&self.window, 3, 4)
535 && self.window.bar(2).high < self.window.bar(3).high
536 && self.window.bar(2).low < self.window.bar(3).low
537 && self.window.bar(1).high < self.window.bar(2).high
538 && self.window.bar(1).low < self.window.bar(2).low
539 && self.window.bar(0).close > self.window.bar(3).open
540 && self.window.bar(0).close < self.window.bar(4).close;
541
542 let bull_first = base
543 && c4 == 1
544 && real_body_gap_up(&self.window, 3, 4)
545 && self.window.bar(2).high > self.window.bar(3).high
546 && self.window.bar(2).low > self.window.bar(3).low
547 && self.window.bar(1).high > self.window.bar(2).high
548 && self.window.bar(1).low > self.window.bar(2).low
549 && self.window.bar(0).close < self.window.bar(3).open
550 && self.window.bar(0).close > self.window.bar(4).close;
551
552 if bear_first || bull_first {
553 (c0 * 100) as f64
554 } else {
555 0.0
556 }
557 }
558}
559
560const CDLCONCEALBABYSWALL_LOOKBACK: usize = 10 + 3;
561
562#[derive(Debug, Clone)]
563#[allow(non_camel_case_types)]
564pub struct CDLCONCEALBABYSWALL {
565 bars_seen: usize,
566 window: CandleWindow,
567 shadow_vs: RollingCandleAvg,
568}
569impl CDLCONCEALBABYSWALL {
570 pub fn new() -> Self {
571 Self {
572 bars_seen: 0,
573 window: CandleWindow::new(4),
574 shadow_vs: RollingCandleAvg::new(SHADOW_VERY_SHORT),
575 }
576 }
577}
578impl Default for CDLCONCEALBABYSWALL {
579 fn default() -> Self {
580 Self::new()
581 }
582}
583impl Next<(f64, f64, f64, f64)> for CDLCONCEALBABYSWALL {
584 type Output = f64;
585 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
586 self.bars_seen += 1;
587 self.window.push(open, high, low, close);
588 self.shadow_vs.push(open, high, low, close);
589
590 if self.bars_seen <= CDLCONCEALBABYSWALL_LOOKBACK {
591 return 0.0;
592 }
593
594 let cond = candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
599 && candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
600 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
601 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
602 && upper_shadow(
603 self.window.bar(3).open,
604 self.window.bar(3).high,
605 self.window.bar(3).close,
606 ) < self.shadow_vs.val(3)
607 && lower_shadow(
608 self.window.bar(3).open,
609 self.window.bar(3).low,
610 self.window.bar(3).close,
611 ) < self.shadow_vs.val(3)
612 && upper_shadow(
613 self.window.bar(2).open,
614 self.window.bar(2).high,
615 self.window.bar(2).close,
616 ) < self.shadow_vs.val(2)
617 && lower_shadow(
618 self.window.bar(2).open,
619 self.window.bar(2).low,
620 self.window.bar(2).close,
621 ) < self.shadow_vs.val(2)
622 && real_body_gap_down(&self.window, 1, 2)
623 && self.window.bar(1).high > self.window.bar(2).close
624 && self.window.bar(0).open >= self.window.bar(1).high
625 && self.window.bar(0).close <= self.window.bar(1).low;
626
627 if cond { 100.0 } else { 0.0 }
628 }
629}
630
631const CDLLADDERBOTTOM_LOOKBACK: usize = 10 + 4;
632
633#[derive(Debug, Clone)]
634#[allow(non_camel_case_types)]
635pub struct CDLLADDERBOTTOM {
636 bars_seen: usize,
637 window: CandleWindow,
638 shadow_vs: RollingCandleAvg,
639}
640impl CDLLADDERBOTTOM {
641 pub fn new() -> Self {
642 Self {
643 bars_seen: 0,
644 window: CandleWindow::new(5),
645 shadow_vs: RollingCandleAvg::new(SHADOW_VERY_SHORT),
646 }
647 }
648}
649impl Default for CDLLADDERBOTTOM {
650 fn default() -> Self {
651 Self::new()
652 }
653}
654impl Next<(f64, f64, f64, f64)> for CDLLADDERBOTTOM {
655 type Output = f64;
656 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
657 self.bars_seen += 1;
658 self.window.push(open, high, low, close);
659 self.shadow_vs.push(open, high, low, close);
660
661 if self.bars_seen <= CDLLADDERBOTTOM_LOOKBACK {
662 return 0.0;
663 }
664
665 let cond = candle_color(self.window.bar(4).open, self.window.bar(4).close) == -1
666 && candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
667 && candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
668 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
669 && self.window.bar(3).close < self.window.bar(4).close
670 && self.window.bar(2).close < self.window.bar(3).close
671 && upper_shadow(
672 self.window.bar(1).open,
673 self.window.bar(1).high,
674 self.window.bar(1).close,
675 ) > self.shadow_vs.val(1)
676 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
677 && self.window.bar(0).open > self.window.bar(1).open
678 && self.window.bar(0).close > self.window.bar(1).high;
679
680 if cond { 100.0 } else { 0.0 }
681 }
682}
683
684const CDLMATHOLD_LOOKBACK: usize = 10 + 4;
685
686#[derive(Debug, Clone)]
687#[allow(non_camel_case_types)]
688pub struct CDLMATHOLD {
689 bars_seen: usize,
690 window: CandleWindow,
691 body_long: RollingCandleAvg,
692 body_short: RollingCandleAvg,
693}
694impl CDLMATHOLD {
695 pub fn new() -> Self {
696 Self {
697 bars_seen: 0,
698 window: CandleWindow::new(5),
699 body_long: RollingCandleAvg::new(BODY_LONG),
700 body_short: RollingCandleAvg::new(BODY_SHORT),
701 }
702 }
703}
704impl Default for CDLMATHOLD {
705 fn default() -> Self {
706 Self::new()
707 }
708}
709impl Next<(f64, f64, f64, f64)> for CDLMATHOLD {
710 type Output = f64;
711 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
712 self.bars_seen += 1;
713 self.window.push(open, high, low, close);
714 self.body_long.push(open, high, low, close);
715 self.body_short.push(open, high, low, close);
716
717 if self.bars_seen <= CDLMATHOLD_LOOKBACK {
718 return 0.0;
719 }
720
721 let penetration = 0.5;
722 let cond = real_body(self.window.bar(4).open, self.window.bar(4).close)
723 > self.body_long.val(4)
724 && real_body(self.window.bar(3).open, self.window.bar(3).close)
725 < self.body_short.val(3)
726 && real_body(self.window.bar(2).open, self.window.bar(2).close)
727 < self.body_short.val(2)
728 && real_body(self.window.bar(1).open, self.window.bar(1).close)
729 < self.body_short.val(1)
730 && candle_color(self.window.bar(4).open, self.window.bar(4).close) == 1
731 && candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
732 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
733 && real_body_gap_up(&self.window, 3, 4)
734 && self.window.bar(2).open.min(self.window.bar(2).close) < self.window.bar(4).close
735 && self.window.bar(1).open.min(self.window.bar(1).close) < self.window.bar(4).close
736 && self.window.bar(2).open.min(self.window.bar(2).close)
737 > self.window.bar(4).close
738 - real_body(self.window.bar(4).open, self.window.bar(4).close) * penetration
739 && self.window.bar(1).open.min(self.window.bar(1).close)
740 > self.window.bar(4).close
741 - real_body(self.window.bar(4).open, self.window.bar(4).close) * penetration
742 && self.window.bar(2).open.max(self.window.bar(2).close) < self.window.bar(3).open
743 && self.window.bar(1).open.max(self.window.bar(1).close)
744 < self.window.bar(2).open.max(self.window.bar(2).close)
745 && self.window.bar(0).open > self.window.bar(1).close
746 && self.window.bar(0).close
747 > self
748 .window
749 .bar(3)
750 .high
751 .max(self.window.bar(2).high)
752 .max(self.window.bar(1).high);
753
754 if cond { 100.0 } else { 0.0 }
755 }
756}
757
758const CDLRISEFALL3METHODS_LOOKBACK: usize = 10 + 4;
759
760#[derive(Debug, Clone)]
761#[allow(non_camel_case_types)]
762pub struct CDLRISEFALL3METHODS {
763 bars_seen: usize,
764 window: CandleWindow,
765 body_long: RollingCandleAvg,
766 body_short: RollingCandleAvg,
767}
768impl CDLRISEFALL3METHODS {
769 pub fn new() -> Self {
770 Self {
771 bars_seen: 0,
772 window: CandleWindow::new(5),
773 body_long: RollingCandleAvg::new(BODY_LONG),
774 body_short: RollingCandleAvg::new(BODY_SHORT),
775 }
776 }
777}
778impl Default for CDLRISEFALL3METHODS {
779 fn default() -> Self {
780 Self::new()
781 }
782}
783impl Next<(f64, f64, f64, f64)> for CDLRISEFALL3METHODS {
784 type Output = f64;
785 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
786 self.bars_seen += 1;
787 self.window.push(open, high, low, close);
788 self.body_long.push(open, high, low, close);
789 self.body_short.push(open, high, low, close);
790
791 if self.bars_seen <= CDLRISEFALL3METHODS_LOOKBACK {
792 return 0.0;
793 }
794
795 let c4 = candle_color(self.window.bar(4).open, self.window.bar(4).close);
796 let c0 = candle_color(self.window.bar(0).open, self.window.bar(0).close);
797
798 if real_body(self.window.bar(4).open, self.window.bar(4).close) > self.body_long.val(4)
799 && real_body(self.window.bar(0).open, self.window.bar(0).close) > self.body_long.val(0)
800 {
801 let mid_short = real_body(self.window.bar(3).open, self.window.bar(3).close)
802 < self.body_short.val(3)
803 && real_body(self.window.bar(2).open, self.window.bar(2).close)
804 < self.body_short.val(2)
805 && real_body(self.window.bar(1).open, self.window.bar(1).close)
806 < self.body_short.val(1);
807
808 let bull = c4 == 1
809 && mid_short
810 && candle_color(self.window.bar(3).open, self.window.bar(3).close) == -1
811 && candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
812 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
813 && self.window.bar(3).close < self.window.bar(4).close
814 && self.window.bar(2).close < self.window.bar(3).close
815 && self.window.bar(1).close < self.window.bar(2).close
816 && self.window.bar(3).low > self.window.bar(4).low
817 && self.window.bar(2).low > self.window.bar(4).low
818 && self.window.bar(1).low > self.window.bar(4).low
819 && self.window.bar(3).high < self.window.bar(4).high
820 && self.window.bar(2).high < self.window.bar(4).high
821 && self.window.bar(1).high < self.window.bar(4).high
822 && c0 == 1
823 && self.window.bar(0).open > self.window.bar(1).close
824 && self.window.bar(0).close > self.window.bar(4).close;
825
826 let bear = c4 == -1
827 && mid_short
828 && candle_color(self.window.bar(3).open, self.window.bar(3).close) == 1
829 && candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
830 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == 1
831 && self.window.bar(3).close > self.window.bar(4).close
832 && self.window.bar(2).close > self.window.bar(3).close
833 && self.window.bar(1).close > self.window.bar(2).close
834 && self.window.bar(3).high < self.window.bar(4).high
835 && self.window.bar(2).high < self.window.bar(4).high
836 && self.window.bar(1).high < self.window.bar(4).high
837 && self.window.bar(3).low > self.window.bar(4).low
838 && self.window.bar(2).low > self.window.bar(4).low
839 && self.window.bar(1).low > self.window.bar(4).low
840 && c0 == -1
841 && self.window.bar(0).open < self.window.bar(1).close
842 && self.window.bar(0).close < self.window.bar(4).close;
843
844 (bull as i32 * 100 - bear as i32 * 100) as f64
845 } else {
846 0.0
847 }
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854 use crate::indicators::patterns::fixtures as fx;
855
856 crate::test_pattern_parity!(
857 test_cdlgapsidesidewhite,
858 CDLGAPSIDESIDEWHITE,
859 talib_rs::pattern::cdl_gapsidesidewhite,
860 fx::gapsidesidewhite
861 );
862 crate::test_pattern_parity!(
863 test_cdlidentical3crows,
864 CDLIDENTICAL3CROWS,
865 talib_rs::pattern::cdl_identical3crows
866 );
867 crate::test_pattern_parity!(
868 test_cdlstalledpattern,
869 CDLSTALLEDPATTERN,
870 talib_rs::pattern::cdl_stalledpattern
871 );
872 crate::test_pattern_parity!(
873 test_cdltasukigap,
874 CDLTASUKIGAP,
875 talib_rs::pattern::cdl_tasukigap,
876 fx::tasukigap
877 );
878 crate::test_pattern_parity!(
879 test_cdltristar,
880 CDLTRISTAR,
881 talib_rs::pattern::cdl_tristar,
882 fx::tristar
883 );
884 crate::test_pattern_parity!(
885 test_cdlunique3river,
886 CDLUNIQUE3RIVER,
887 talib_rs::pattern::cdl_unique3river,
888 fx::unique3river
889 );
890 crate::test_pattern_parity!(
891 test_cdlupsidegap2crows,
892 CDLUPSIDEGAP2CROWS,
893 talib_rs::pattern::cdl_upsidegap2crows,
894 fx::upsidegap2crows
895 );
896 crate::test_pattern_parity!(
897 test_cdlxsidegap3methods,
898 CDLXSIDEGAP3METHODS,
899 talib_rs::pattern::cdl_xsidegap3methods,
900 fx::xsidegap3methods
901 );
902 crate::test_pattern_parity!(
903 test_cdlbreakaway,
904 CDLBREAKAWAY,
905 talib_rs::pattern::cdl_breakaway,
906 fx::breakaway
907 );
908 crate::test_pattern_parity!(
909 test_cdlconcealbabyswall,
910 CDLCONCEALBABYSWALL,
911 talib_rs::pattern::cdl_concealbabyswall,
912 fx::concealbabyswall
913 );
914 crate::test_pattern_parity!(
915 test_cdlladderbottom,
916 CDLLADDERBOTTOM,
917 talib_rs::pattern::cdl_ladderbottom,
918 fx::ladderbottom
919 );
920 crate::test_pattern_parity!(
921 test_cdlmathold,
922 CDLMATHOLD,
923 talib_rs::pattern::cdl_mathold,
924 fx::mathold
925 );
926 crate::test_pattern_parity!(
927 test_cdlrisefall3methods,
928 CDLRISEFALL3METHODS,
929 talib_rs::pattern::cdl_risefall3methods,
930 fx::risefall3methods
931 );
932}