1use crate::indicators::patterns::candle_settings::*;
4use crate::traits::Next;
5
6#[derive(Debug, Clone)]
8#[allow(non_camel_case_types)]
9pub struct CDL3BLACKCROWS {
10 bars_seen: usize,
11 window: CandleWindow,
12 shadow_very_short_avg: RollingCandleAvg,
13}
14
15impl CDL3BLACKCROWS {
16 pub fn new() -> Self {
17 Self {
18 bars_seen: 0,
19 window: CandleWindow::new(4),
20 shadow_very_short_avg: RollingCandleAvg::new(SHADOW_VERY_SHORT),
21 }
22 }
23}
24
25impl Default for CDL3BLACKCROWS {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl Next<(f64, f64, f64, f64)> for CDL3BLACKCROWS {
32 type Output = f64;
33
34 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
35 self.bars_seen += 1;
36 self.window.push(open, high, low, close);
37 self.shadow_very_short_avg.push(open, high, low, close);
38
39 if self.bars_seen <= SHADOW_VERY_SHORT.avg_period + 3 {
40 return 0.0;
41 }
42
43 if candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
44 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
45 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
46 && self.window.bar(1).close < self.window.bar(2).close
47 && self.window.bar(0).close < self.window.bar(1).close
48 && self.window.bar(2).open <= self.window.bar(3).open.max(self.window.bar(3).close)
49 && self.window.bar(1).open <= self.window.bar(2).open
50 && self.window.bar(1).open >= self.window.bar(2).close
51 && self.window.bar(0).open <= self.window.bar(1).open
52 && self.window.bar(0).open >= self.window.bar(1).close
53 && lower_shadow(
54 self.window.bar(2).open,
55 self.window.bar(2).low,
56 self.window.bar(2).close,
57 ) < self.shadow_very_short_avg.val(2)
58 && lower_shadow(
59 self.window.bar(1).open,
60 self.window.bar(1).low,
61 self.window.bar(1).close,
62 ) < self.shadow_very_short_avg.val(1)
63 && lower_shadow(
64 self.window.bar(0).open,
65 self.window.bar(0).low,
66 self.window.bar(0).close,
67 ) < self.shadow_very_short_avg.val(0)
68 {
69 return -100.0;
70 }
71 0.0
72 }
73}
74
75#[derive(Debug, Clone)]
77#[allow(non_camel_case_types)]
78pub struct CDL3INSIDE {
79 bars_seen: usize,
80 window: CandleWindow,
81 body_long_avg: RollingCandleAvg,
82 body_short_avg: RollingCandleAvg,
83}
84
85impl CDL3INSIDE {
86 pub fn new() -> Self {
87 Self {
88 bars_seen: 0,
89 window: CandleWindow::new(3),
90 body_long_avg: RollingCandleAvg::new(BODY_LONG),
91 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
92 }
93 }
94}
95
96impl Default for CDL3INSIDE {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102impl Next<(f64, f64, f64, f64)> for CDL3INSIDE {
103 type Output = f64;
104
105 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
106 self.bars_seen += 1;
107 self.window.push(open, high, low, close);
108 self.body_long_avg.push(open, high, low, close);
109 self.body_short_avg.push(open, high, low, close);
110
111 if self.bars_seen <= BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 2 {
112 return 0.0;
113 }
114
115 if real_body(self.window.bar(2).open, self.window.bar(2).close) > self.body_long_avg.val(2)
116 && real_body(self.window.bar(1).open, self.window.bar(1).close)
117 <= self.body_short_avg.val(1)
118 && self.window.bar(1).open.max(self.window.bar(1).close)
119 < self.window.bar(2).open.max(self.window.bar(2).close)
120 && self.window.bar(1).open.min(self.window.bar(1).close)
121 > self.window.bar(2).open.min(self.window.bar(2).close)
122 {
123 if candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
124 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
125 && self.window.bar(0).close < self.window.bar(2).open
126 {
127 return -100.0;
128 } else if candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
129 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
130 && self.window.bar(0).close > self.window.bar(2).open
131 {
132 return 100.0;
133 }
134 }
135 0.0
136 }
137}
138
139#[derive(Debug, Clone)]
141#[allow(non_camel_case_types)]
142pub struct CDL3OUTSIDE {
143 bars_seen: usize,
144 window: CandleWindow,
145}
146
147impl CDL3OUTSIDE {
148 pub fn new() -> Self {
149 Self {
150 bars_seen: 0,
151 window: CandleWindow::new(3),
152 }
153 }
154}
155
156impl Default for CDL3OUTSIDE {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162impl Next<(f64, f64, f64, f64)> for CDL3OUTSIDE {
163 type Output = f64;
164
165 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
166 self.bars_seen += 1;
167 self.window.push(open, high, low, close);
168
169 if self.bars_seen <= 2 {
170 return 0.0;
171 }
172
173 let bull = candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
174 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == 1
175 && self.window.bar(1).close >= self.window.bar(2).open
176 && self.window.bar(1).open <= self.window.bar(2).close
177 && self.window.bar(0).close > self.window.bar(1).close;
178 let bear = candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
179 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
180 && self.window.bar(1).open >= self.window.bar(2).close
181 && self.window.bar(1).close <= self.window.bar(2).open
182 && self.window.bar(0).close < self.window.bar(1).close;
183 if bull {
184 return 100.0;
185 }
186 if bear {
187 return -100.0;
188 }
189 0.0
190 }
191}
192
193#[derive(Debug, Clone)]
195#[allow(non_camel_case_types)]
196pub struct CDL3STARSINSOUTH {
197 bars_seen: usize,
198 window: CandleWindow,
199 body_long_avg: RollingCandleAvg,
200 shadow_long_avg: RollingCandleAvg,
201 body_short_avg: RollingCandleAvg,
202 shadow_vs_avg: RollingCandleAvg,
203}
204
205impl CDL3STARSINSOUTH {
206 pub fn new() -> Self {
207 Self {
208 bars_seen: 0,
209 window: CandleWindow::new(3),
210 body_long_avg: RollingCandleAvg::new(BODY_LONG),
211 shadow_long_avg: RollingCandleAvg::new(SHADOW_LONG),
212 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
213 shadow_vs_avg: RollingCandleAvg::new(SHADOW_VERY_SHORT),
214 }
215 }
216}
217
218impl Default for CDL3STARSINSOUTH {
219 fn default() -> Self {
220 Self::new()
221 }
222}
223
224impl Next<(f64, f64, f64, f64)> for CDL3STARSINSOUTH {
225 type Output = f64;
226
227 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
228 self.bars_seen += 1;
229 self.window.push(open, high, low, close);
230 self.body_long_avg.push(open, high, low, close);
231 self.shadow_long_avg.push(open, high, low, close);
232 self.body_short_avg.push(open, high, low, close);
233 self.shadow_vs_avg.push(open, high, low, close);
234
235 if self.bars_seen <= BODY_LONG.avg_period + 2 {
238 return 0.0;
239 }
240
241 if candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
242 && candle_color(self.window.bar(1).open, self.window.bar(1).close) == -1
243 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
244 && real_body(self.window.bar(2).open, self.window.bar(2).close)
245 > self.body_long_avg.val(2)
246 && lower_shadow(
247 self.window.bar(2).open,
248 self.window.bar(2).low,
249 self.window.bar(2).close,
250 ) > self.shadow_long_avg.val(2)
251 && self.window.bar(1).open.min(self.window.bar(1).close)
252 > self.window.bar(2).open.min(self.window.bar(2).close)
253 && self.window.bar(1).open.max(self.window.bar(1).close)
254 < self.window.bar(2).open.max(self.window.bar(2).close)
255 && self.window.bar(1).low < self.window.bar(2).low
256 && real_body(self.window.bar(0).open, self.window.bar(0).close)
258 < self.body_short_avg.val(0)
259 && upper_shadow(
260 self.window.bar(0).open,
261 self.window.bar(0).high,
262 self.window.bar(0).close,
263 ) < self.shadow_vs_avg.val(0)
264 && lower_shadow(
265 self.window.bar(0).open,
266 self.window.bar(0).low,
267 self.window.bar(0).close,
268 ) < self.shadow_vs_avg.val(0)
269 && self.window.bar(0).low > self.window.bar(1).low
270 && self.window.bar(0).high < self.window.bar(1).high
271 {
272 return 100.0;
273 }
274 0.0
275 }
276}
277
278#[derive(Debug, Clone)]
280#[allow(non_camel_case_types)]
281pub struct CDL3WHITESOLDIERS {
282 bars_seen: usize,
283 window: CandleWindow,
284 near_avg: RollingCandleAvg,
285 far_avg: RollingCandleAvg,
286 body_short_avg: RollingCandleAvg,
287 shadow_very_short_avg: RollingCandleAvg,
288}
289
290impl CDL3WHITESOLDIERS {
291 pub fn new() -> Self {
292 Self {
293 bars_seen: 0,
294 window: CandleWindow::new(4),
295 near_avg: RollingCandleAvg::new(NEAR),
296 far_avg: RollingCandleAvg::new(FAR),
297 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
298 shadow_very_short_avg: RollingCandleAvg::new(SHADOW_VERY_SHORT),
299 }
300 }
301}
302
303impl Default for CDL3WHITESOLDIERS {
304 fn default() -> Self {
305 Self::new()
306 }
307}
308
309impl Next<(f64, f64, f64, f64)> for CDL3WHITESOLDIERS {
310 type Output = f64;
311
312 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
313 self.bars_seen += 1;
314 self.window.push(open, high, low, close);
315 self.near_avg.push(open, high, low, close);
316 self.far_avg.push(open, high, low, close);
317 self.body_short_avg.push(open, high, low, close);
318 self.shadow_very_short_avg.push(open, high, low, close);
319
320 let lookback = SHADOW_VERY_SHORT
322 .avg_period
323 .max(BODY_SHORT.avg_period)
324 .max(FAR.avg_period)
325 .max(NEAR.avg_period)
326 + 2;
327 if self.bars_seen <= lookback {
328 return 0.0;
329 }
330
331 let b2 = self.window.bar(2);
332 let b1 = self.window.bar(1);
333 let b0 = self.window.bar(0);
334
335 if candle_color(b2.open, b2.close) == 1
336 && candle_color(b1.open, b1.close) == 1
337 && candle_color(b0.open, b0.close) == 1
338 && b1.close > b2.close
339 && b0.close > b1.close
340 && upper_shadow(b2.open, b2.high, b2.close) < self.shadow_very_short_avg.val(2)
342 && upper_shadow(b1.open, b1.high, b1.close) < self.shadow_very_short_avg.val(1)
343 && upper_shadow(b0.open, b0.high, b0.close) < self.shadow_very_short_avg.val(0)
344 && b1.open > b2.open
346 && b1.open <= b2.close + self.near_avg.val(1)
347 && b0.open > b1.open
348 && b0.open <= b1.close + self.near_avg.val(0)
349 && real_body(b1.open, b1.close) > real_body(b2.open, b2.close) - self.far_avg.val(1)
351 && real_body(b0.open, b0.close) > real_body(b1.open, b1.close) - self.far_avg.val(0)
352 && real_body(b0.open, b0.close) > self.body_short_avg.val(0)
354 {
355 return 100.0;
356 }
357 0.0
358 }
359}
360
361#[derive(Debug, Clone)]
363#[allow(non_camel_case_types)]
364pub struct CDLABANDONEDBABY {
365 bars_seen: usize,
366 window: CandleWindow,
367 body_doji_avg: RollingCandleAvg,
368 body_long_avg: RollingCandleAvg,
369 body_short_avg: RollingCandleAvg,
370}
371
372impl CDLABANDONEDBABY {
373 pub fn new() -> Self {
374 Self {
375 bars_seen: 0,
376 window: CandleWindow::new(3),
377 body_doji_avg: RollingCandleAvg::new(BODY_DOJI),
378 body_long_avg: RollingCandleAvg::new(BODY_LONG),
379 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
380 }
381 }
382}
383
384impl Default for CDLABANDONEDBABY {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390impl Next<(f64, f64, f64, f64)> for CDLABANDONEDBABY {
391 type Output = f64;
392
393 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
394 self.bars_seen += 1;
395 self.window.push(open, high, low, close);
396 self.body_doji_avg.push(open, high, low, close);
397 self.body_long_avg.push(open, high, low, close);
398 self.body_short_avg.push(open, high, low, close);
399
400 if self.bars_seen
401 <= BODY_DOJI
402 .avg_period
403 .max(BODY_LONG.avg_period)
404 .max(BODY_SHORT.avg_period)
405 + 2
406 {
407 return 0.0;
408 }
409
410 let penetration = 0.3;
411 let base = real_body(self.window.bar(2).open, self.window.bar(2).close)
412 > self.body_long_avg.val(2)
413 && real_body(self.window.bar(1).open, self.window.bar(1).close)
414 <= self.body_doji_avg.val(1)
415 && real_body(self.window.bar(0).open, self.window.bar(0).close)
416 > self.body_short_avg.val(0);
417 let bull = base
418 && candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
419 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
420 && self.window.bar(1).high < self.window.bar(2).low
421 && self.window.bar(0).low > self.window.bar(1).high
422 && self.window.bar(0).close
423 > self.window.bar(2).close
424 + real_body(self.window.bar(2).open, self.window.bar(2).close) * penetration;
425 let bear = base
426 && candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
427 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
428 && self.window.bar(1).low > self.window.bar(2).high
429 && self.window.bar(0).high < self.window.bar(1).low
430 && self.window.bar(0).close
431 < self.window.bar(2).close
432 - real_body(self.window.bar(2).open, self.window.bar(2).close) * penetration;
433 if bull {
434 return 100.0;
435 }
436 if bear {
437 return -100.0;
438 }
439 0.0
440 }
441}
442
443#[derive(Debug, Clone)]
445#[allow(non_camel_case_types)]
446pub struct CDLADVANCEBLOCK {
447 bars_seen: usize,
448 window: CandleWindow,
449 far_avg: RollingCandleAvg,
450 near_avg: RollingCandleAvg,
451 shadow_long_avg: RollingCandleAvg,
452 shadow_short_avg: RollingCandleAvg,
453 body_long_avg: RollingCandleAvg,
454}
455
456impl CDLADVANCEBLOCK {
457 pub fn new() -> Self {
458 Self {
459 bars_seen: 0,
460 window: CandleWindow::new(3),
461 far_avg: RollingCandleAvg::new(FAR),
462 near_avg: RollingCandleAvg::new(NEAR),
463 shadow_long_avg: RollingCandleAvg::new(SHADOW_LONG),
464 shadow_short_avg: RollingCandleAvg::new(SHADOW_SHORT),
465 body_long_avg: RollingCandleAvg::new(BODY_LONG),
466 }
467 }
468}
469
470impl Default for CDLADVANCEBLOCK {
471 fn default() -> Self {
472 Self::new()
473 }
474}
475
476impl Next<(f64, f64, f64, f64)> for CDLADVANCEBLOCK {
477 type Output = f64;
478
479 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
480 self.bars_seen += 1;
481 self.window.push(open, high, low, close);
482 self.far_avg.push(open, high, low, close);
483 self.near_avg.push(open, high, low, close);
484 self.shadow_long_avg.push(open, high, low, close);
485 self.shadow_short_avg.push(open, high, low, close);
486 self.body_long_avg.push(open, high, low, close);
487
488 let lookback = SHADOW_SHORT
491 .avg_period
492 .max(FAR.avg_period)
493 .max(NEAR.avg_period)
494 .max(BODY_LONG.avg_period)
495 + 2;
496 if self.bars_seen <= lookback {
497 return 0.0;
498 }
499
500 let b2 = self.window.bar(2);
501 let b1 = self.window.bar(1);
502 let b0 = self.window.bar(0);
503
504 let rb2 = real_body(b2.open, b2.close);
505 let rb1 = real_body(b1.open, b1.close);
506 let rb0 = real_body(b0.open, b0.close);
507
508 let base = candle_color(b2.open, b2.close) == 1
511 && candle_color(b1.open, b1.close) == 1
512 && candle_color(b0.open, b0.close) == 1
513 && b1.close > b2.close
514 && b0.close > b1.close
515 && b1.open > b2.open
516 && b1.open <= b2.close + self.near_avg.val(1)
517 && b0.open > b1.open
518 && b0.open <= b1.close + self.near_avg.val(0)
519 && rb2 > self.body_long_avg.val(2)
520 && upper_shadow(b2.open, b2.high, b2.close) < self.shadow_short_avg.val(2);
521
522 let weakness = base
524 && ((rb1 < rb2 - self.far_avg.val(1) && rb0 < rb1 + self.near_avg.val(0))
525 || (rb0 < rb1
526 && rb1 < rb2
527 && (upper_shadow(b0.open, b0.high, b0.close) > self.shadow_long_avg.val(0)
528 || upper_shadow(b1.open, b1.high, b1.close)
529 > self.shadow_long_avg.val(1)))
530 || (rb0 < rb1 - self.far_avg.val(0)));
531
532 if weakness { -100.0 } else { 0.0 }
533 }
534}
535
536#[derive(Debug, Clone)]
538#[allow(non_camel_case_types)]
539pub struct CDLEVENINGDOJISTAR {
540 bars_seen: usize,
541 window: CandleWindow,
542 body_doji_avg: RollingCandleAvg,
543 body_long_avg: RollingCandleAvg,
544 body_short_avg: RollingCandleAvg,
545}
546
547impl CDLEVENINGDOJISTAR {
548 pub fn new() -> Self {
549 Self {
550 bars_seen: 0,
551 window: CandleWindow::new(3),
552 body_doji_avg: RollingCandleAvg::new(BODY_DOJI),
553 body_long_avg: RollingCandleAvg::new(BODY_LONG),
554 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
555 }
556 }
557}
558
559impl Default for CDLEVENINGDOJISTAR {
560 fn default() -> Self {
561 Self::new()
562 }
563}
564
565impl Next<(f64, f64, f64, f64)> for CDLEVENINGDOJISTAR {
566 type Output = f64;
567
568 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
569 self.bars_seen += 1;
570 self.window.push(open, high, low, close);
571 self.body_doji_avg.push(open, high, low, close);
572 self.body_long_avg.push(open, high, low, close);
573 self.body_short_avg.push(open, high, low, close);
574
575 if self.bars_seen
576 <= BODY_DOJI
577 .avg_period
578 .max(BODY_LONG.avg_period)
579 .max(BODY_SHORT.avg_period)
580 + 2
581 {
582 return 0.0;
583 }
584
585 let penetration = 0.3;
586 if candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
587 && real_body(self.window.bar(2).open, self.window.bar(2).close)
588 > self.body_long_avg.val(2)
589 && real_body(self.window.bar(1).open, self.window.bar(1).close)
590 <= self.body_doji_avg.val(1)
591 && (self.window.bar(1).open.min(self.window.bar(1).close)
592 > self.window.bar(2).open.max(self.window.bar(2).close))
593 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
594 && real_body(self.window.bar(0).open, self.window.bar(0).close)
595 > self.body_short_avg.val(0)
596 && self.window.bar(0).close
597 < self.window.bar(2).close
598 - real_body(self.window.bar(2).open, self.window.bar(2).close) * penetration
599 {
600 return -100.0;
601 }
602 0.0
603 }
604}
605
606#[derive(Debug, Clone)]
608#[allow(non_camel_case_types)]
609pub struct CDLEVENINGSTAR {
610 bars_seen: usize,
611 window: CandleWindow,
612 body_long_avg: RollingCandleAvg,
613 body_short_avg: RollingCandleAvg,
614}
615
616impl CDLEVENINGSTAR {
617 pub fn new() -> Self {
618 Self {
619 bars_seen: 0,
620 window: CandleWindow::new(3),
621 body_long_avg: RollingCandleAvg::new(BODY_LONG),
622 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
623 }
624 }
625}
626
627impl Default for CDLEVENINGSTAR {
628 fn default() -> Self {
629 Self::new()
630 }
631}
632
633impl Next<(f64, f64, f64, f64)> for CDLEVENINGSTAR {
634 type Output = f64;
635
636 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
637 self.bars_seen += 1;
638 self.window.push(open, high, low, close);
639 self.body_long_avg.push(open, high, low, close);
640 self.body_short_avg.push(open, high, low, close);
641
642 if self.bars_seen <= BODY_LONG.avg_period.max(BODY_SHORT.avg_period) + 2 {
643 return 0.0;
644 }
645
646 let penetration = 0.3;
647 if candle_color(self.window.bar(2).open, self.window.bar(2).close) == 1
648 && real_body(self.window.bar(2).open, self.window.bar(2).close)
649 > self.body_long_avg.val(2)
650 && real_body(self.window.bar(1).open, self.window.bar(1).close)
651 <= self.body_short_avg.val(1)
652 && (self.window.bar(1).open.min(self.window.bar(1).close)
653 > self.window.bar(2).open.max(self.window.bar(2).close))
654 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == -1
655 && real_body(self.window.bar(0).open, self.window.bar(0).close)
656 > self.body_short_avg.val(0)
657 && self.window.bar(0).close
658 < self.window.bar(2).close
659 - real_body(self.window.bar(2).open, self.window.bar(2).close) * penetration
660 {
661 return -100.0;
662 }
663 0.0
664 }
665}
666
667#[derive(Debug, Clone)]
669#[allow(non_camel_case_types)]
670pub struct CDLMORNINGDOJISTAR {
671 bars_seen: usize,
672 window: CandleWindow,
673 body_doji_avg: RollingCandleAvg,
674 body_long_avg: RollingCandleAvg,
675 body_short_avg: RollingCandleAvg,
676}
677
678impl CDLMORNINGDOJISTAR {
679 pub fn new() -> Self {
680 Self {
681 bars_seen: 0,
682 window: CandleWindow::new(3),
683 body_doji_avg: RollingCandleAvg::new(BODY_DOJI),
684 body_long_avg: RollingCandleAvg::new(BODY_LONG),
685 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
686 }
687 }
688}
689
690impl Default for CDLMORNINGDOJISTAR {
691 fn default() -> Self {
692 Self::new()
693 }
694}
695
696impl Next<(f64, f64, f64, f64)> for CDLMORNINGDOJISTAR {
697 type Output = f64;
698
699 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
700 self.bars_seen += 1;
701 self.window.push(open, high, low, close);
702 self.body_doji_avg.push(open, high, low, close);
703 self.body_long_avg.push(open, high, low, close);
704 self.body_short_avg.push(open, high, low, close);
705
706 if self.bars_seen
707 <= BODY_DOJI
708 .avg_period
709 .max(BODY_LONG.avg_period)
710 .max(BODY_SHORT.avg_period)
711 + 2
712 {
713 return 0.0;
714 }
715
716 let penetration = 0.3;
717 if candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
718 && real_body(self.window.bar(2).open, self.window.bar(2).close)
719 > self.body_long_avg.val(2)
720 && real_body(self.window.bar(1).open, self.window.bar(1).close)
721 <= self.body_doji_avg.val(1)
722 && (self.window.bar(1).open.max(self.window.bar(1).close)
723 < self.window.bar(2).open.min(self.window.bar(2).close))
724 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
725 && real_body(self.window.bar(0).open, self.window.bar(0).close)
726 > self.body_short_avg.val(0)
727 && self.window.bar(0).close
728 > self.window.bar(2).close
729 + real_body(self.window.bar(2).open, self.window.bar(2).close) * penetration
730 {
731 return 100.0;
732 }
733 0.0
734 }
735}
736
737#[derive(Debug, Clone)]
739#[allow(non_camel_case_types)]
740pub struct CDLMORNINGSTAR {
741 bars_seen: usize,
742 window: CandleWindow,
743 body_long_avg: RollingCandleAvg,
744 body_short_avg: RollingCandleAvg,
745}
746
747impl CDLMORNINGSTAR {
748 pub fn new() -> Self {
749 Self {
750 bars_seen: 0,
751 window: CandleWindow::new(3),
752 body_long_avg: RollingCandleAvg::new(BODY_LONG),
753 body_short_avg: RollingCandleAvg::new(BODY_SHORT),
754 }
755 }
756}
757
758impl Default for CDLMORNINGSTAR {
759 fn default() -> Self {
760 Self::new()
761 }
762}
763
764impl Next<(f64, f64, f64, f64)> for CDLMORNINGSTAR {
765 type Output = f64;
766
767 fn next(&mut self, (open, high, low, close): (f64, f64, f64, f64)) -> Self::Output {
768 self.bars_seen += 1;
769 self.window.push(open, high, low, close);
770 self.body_long_avg.push(open, high, low, close);
771 self.body_short_avg.push(open, high, low, close);
772
773 if self.bars_seen <= BODY_LONG.avg_period.max(BODY_SHORT.avg_period) + 2 {
774 return 0.0;
775 }
776
777 let penetration = 0.3;
778 if candle_color(self.window.bar(2).open, self.window.bar(2).close) == -1
779 && real_body(self.window.bar(2).open, self.window.bar(2).close)
780 > self.body_long_avg.val(2)
781 && real_body(self.window.bar(1).open, self.window.bar(1).close)
782 <= self.body_short_avg.val(1)
783 && (self.window.bar(1).open.max(self.window.bar(1).close)
784 < self.window.bar(2).open.min(self.window.bar(2).close))
785 && candle_color(self.window.bar(0).open, self.window.bar(0).close) == 1
786 && real_body(self.window.bar(0).open, self.window.bar(0).close)
787 > self.body_short_avg.val(0)
788 && self.window.bar(0).close
789 > self.window.bar(2).close
790 + real_body(self.window.bar(2).open, self.window.bar(2).close) * penetration
791 {
792 return 100.0;
793 }
794 0.0
795 }
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::indicators::patterns::fixtures as fx;
802 crate::test_pattern_parity!(
818 test_cdl3blackcrows_parity,
819 CDL3BLACKCROWS,
820 talib_rs::pattern::cdl_3blackcrows,
821 fx::three_black_crows,
822 oracle_exempt = "talib-rs's cdl_3blackcrows seeds shadow_sum[k] at bar `start-3+k` but \
823 uses it for bar `i-2+k`, then rolls that skewed set, producing a \
824 non-contiguous window matching no principled rolling average. A \
825 from-scratch C-TA-Lib-semantics model and the native implementation \
826 produce the identical 8 mismatches vs the oracle over 200,000 bars; a \
827 shifted model produces 11. Native == C TA-Lib; the oracle is wrong.",
828 max_mismatches = 12,
829 reference_mismatches = 8
830 );
831 crate::test_pattern_parity!(
832 test_cdl3inside_parity,
833 CDL3INSIDE,
834 talib_rs::pattern::cdl_3inside,
835 fx::three_inside
836 );
837 crate::test_pattern_parity!(
838 test_cdl3outside_parity,
839 CDL3OUTSIDE,
840 talib_rs::pattern::cdl_3outside
841 );
842 crate::test_pattern_parity!(
843 test_cdl3starsinsouth_parity,
844 CDL3STARSINSOUTH,
845 talib_rs::pattern::cdl_3starsinsouth,
846 fx::three_stars_in_south
847 );
848 crate::test_pattern_parity!(
849 test_cdl3whitesoldiers_parity,
850 CDL3WHITESOLDIERS,
851 talib_rs::pattern::cdl_3whitesoldiers
852 );
853 crate::test_pattern_parity!(
854 test_cdlabandonedbaby_parity,
855 CDLABANDONEDBABY,
856 talib_rs::pattern::cdl_abandonedbaby,
857 fx::abandonedbaby
858 );
859 crate::test_pattern_parity!(
860 test_cdladvanceblock_parity,
861 CDLADVANCEBLOCK,
862 talib_rs::pattern::cdl_advanceblock
863 );
864 crate::test_pattern_parity!(
865 test_cdleveningdojistar_parity,
866 CDLEVENINGDOJISTAR,
867 talib_rs::pattern::cdl_eveningdojistar,
868 fx::eveningdojistar
869 );
870 crate::test_pattern_parity!(
871 test_cdleveningstar_parity,
872 CDLEVENINGSTAR,
873 talib_rs::pattern::cdl_eveningstar,
874 fx::eveningstar
875 );
876 crate::test_pattern_parity!(
877 test_cdlmorningdojistar_parity,
878 CDLMORNINGDOJISTAR,
879 talib_rs::pattern::cdl_morningdojistar,
880 fx::morningdojistar
881 );
882 crate::test_pattern_parity!(
883 test_cdlmorningstar_parity,
884 CDLMORNINGSTAR,
885 talib_rs::pattern::cdl_morningstar,
886 fx::morningstar
887 );
888}