1#[derive(Clone, Copy)]
20enum RangeType {
21 RealBody,
22 HighLow,
23 Shadows,
24}
25
26#[derive(Clone, Copy)]
27struct CandleSetting {
28 range: RangeType,
29 avg_period: usize,
30 factor: f64,
31}
32
33const BODY_LONG: CandleSetting = CandleSetting {
35 range: RangeType::RealBody,
36 avg_period: 10,
37 factor: 1.0,
38};
39const BODY_SHORT: CandleSetting = CandleSetting {
40 range: RangeType::RealBody,
41 avg_period: 10,
42 factor: 1.0,
43};
44const BODY_DOJI: CandleSetting = CandleSetting {
45 range: RangeType::HighLow,
46 avg_period: 10,
47 factor: 0.1,
48};
49const SHADOW_LONG: CandleSetting = CandleSetting {
50 range: RangeType::RealBody,
51 avg_period: 0,
52 factor: 1.0,
53};
54const SHADOW_VERY_LONG: CandleSetting = CandleSetting {
55 range: RangeType::RealBody,
56 avg_period: 0,
57 factor: 2.0,
58};
59const SHADOW_SHORT: CandleSetting = CandleSetting {
60 range: RangeType::Shadows,
61 avg_period: 10,
62 factor: 1.0,
63};
64const SHADOW_VERY_SHORT: CandleSetting = CandleSetting {
65 range: RangeType::HighLow,
66 avg_period: 10,
67 factor: 0.1,
68};
69const NEAR: CandleSetting = CandleSetting {
70 range: RangeType::HighLow,
71 avg_period: 5,
72 factor: 0.2,
73};
74const FAR: CandleSetting = CandleSetting {
75 range: RangeType::HighLow,
76 avg_period: 5,
77 factor: 0.6,
78};
79const EQUAL: CandleSetting = CandleSetting {
80 range: RangeType::HighLow,
81 avg_period: 5,
82 factor: 0.05,
83};
84
85struct Candles<'a> {
87 open: &'a [f64],
88 high: &'a [f64],
89 low: &'a [f64],
90 close: &'a [f64],
91 len: usize,
92}
93
94impl<'a> Candles<'a> {
95 fn new(open: &'a [f64], high: &'a [f64], low: &'a [f64], close: &'a [f64]) -> Self {
96 let len = open.len().min(high.len()).min(low.len()).min(close.len());
97 Self {
98 open,
99 high,
100 low,
101 close,
102 len,
103 }
104 }
105
106 #[inline]
107 fn real_body(&self, i: usize) -> f64 {
108 (self.close[i] - self.open[i]).abs()
109 }
110
111 #[inline]
112 fn upper_shadow(&self, i: usize) -> f64 {
113 self.high[i] - self.open[i].max(self.close[i])
114 }
115
116 #[inline]
117 fn lower_shadow(&self, i: usize) -> f64 {
118 self.open[i].min(self.close[i]) - self.low[i]
119 }
120
121 #[inline]
122 fn high_low(&self, i: usize) -> f64 {
123 self.high[i] - self.low[i]
124 }
125
126 #[inline]
128 fn color(&self, i: usize) -> i32 {
129 if self.close[i] >= self.open[i] { 1 } else { -1 }
130 }
131
132 #[inline]
134 fn body_top(&self, i: usize) -> f64 {
135 self.open[i].max(self.close[i])
136 }
137
138 #[inline]
140 fn body_bottom(&self, i: usize) -> f64 {
141 self.open[i].min(self.close[i])
142 }
143
144 #[inline]
146 fn real_body_gap_up(&self, i2: usize, i1: usize) -> bool {
147 self.body_bottom(i2) > self.body_top(i1)
148 }
149
150 #[inline]
152 fn real_body_gap_down(&self, i2: usize, i1: usize) -> bool {
153 self.body_top(i2) < self.body_bottom(i1)
154 }
155
156 #[inline]
158 fn candle_gap_up(&self, i2: usize, i1: usize) -> bool {
159 self.low[i2] > self.high[i1]
160 }
161
162 #[inline]
164 fn candle_gap_down(&self, i2: usize, i1: usize) -> bool {
165 self.high[i2] < self.low[i1]
166 }
167
168 fn range(&self, setting: &CandleSetting, i: usize) -> f64 {
169 match setting.range {
170 RangeType::RealBody => self.real_body(i),
171 RangeType::HighLow => self.high_low(i),
172 RangeType::Shadows => self.upper_shadow(i) + self.lower_shadow(i),
173 }
174 }
175
176 fn average(&self, setting: &CandleSetting, i: usize) -> f64 {
180 let base = if setting.avg_period == 0 {
181 self.range(setting, i)
182 } else {
183 let mut sum = 0.0;
184 for k in (i - setting.avg_period)..i {
185 sum += self.range(setting, k);
186 }
187 sum / setting.avg_period as f64
188 };
189 let divisor = if matches!(setting.range, RangeType::Shadows) {
190 2.0
191 } else {
192 1.0
193 };
194 setting.factor * base / divisor
195 }
196}
197
198fn scan(len: usize, lookback: usize, mut test: impl FnMut(usize) -> i32) -> Vec<i32> {
201 let mut out = vec![0i32; len];
202 if lookback < len {
203 for (i, slot) in out.iter_mut().enumerate().skip(lookback) {
204 *slot = test(i);
205 }
206 }
207 out
208}
209
210pub fn cdl_doji(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
212 let k = Candles::new(open, high, low, close);
213 scan(k.len, BODY_DOJI.avg_period, |i| {
214 if k.real_body(i) <= k.average(&BODY_DOJI, i) {
215 100
216 } else {
217 0
218 }
219 })
220}
221
222pub fn cdl_dragonfly_doji(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
224 let k = Candles::new(open, high, low, close);
225 scan(
226 k.len,
227 BODY_DOJI.avg_period.max(SHADOW_VERY_SHORT.avg_period),
228 |i| {
229 let svs = k.average(&SHADOW_VERY_SHORT, i);
230 if k.real_body(i) <= k.average(&BODY_DOJI, i)
231 && k.upper_shadow(i) < svs
232 && k.lower_shadow(i) > svs
233 {
234 100
235 } else {
236 0
237 }
238 },
239 )
240}
241
242pub fn cdl_gravestone_doji(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
244 let k = Candles::new(open, high, low, close);
245 scan(
246 k.len,
247 BODY_DOJI.avg_period.max(SHADOW_VERY_SHORT.avg_period),
248 |i| {
249 let svs = k.average(&SHADOW_VERY_SHORT, i);
250 if k.real_body(i) <= k.average(&BODY_DOJI, i)
251 && k.lower_shadow(i) < svs
252 && k.upper_shadow(i) > svs
253 {
254 100
255 } else {
256 0
257 }
258 },
259 )
260}
261
262pub fn cdl_long_legged_doji(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
264 let k = Candles::new(open, high, low, close);
265 scan(
266 k.len,
267 BODY_DOJI.avg_period.max(SHADOW_LONG.avg_period),
268 |i| {
269 let shadow_long = k.average(&SHADOW_LONG, i);
270 if k.real_body(i) <= k.average(&BODY_DOJI, i)
271 && (k.upper_shadow(i) > shadow_long || k.lower_shadow(i) > shadow_long)
272 {
273 100
274 } else {
275 0
276 }
277 },
278 )
279}
280
281pub fn cdl_marubozu(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
283 let k = Candles::new(open, high, low, close);
284 scan(
285 k.len,
286 BODY_LONG.avg_period.max(SHADOW_VERY_SHORT.avg_period),
287 |i| {
288 let svs = k.average(&SHADOW_VERY_SHORT, i);
289 if k.real_body(i) > k.average(&BODY_LONG, i)
290 && k.upper_shadow(i) < svs
291 && k.lower_shadow(i) < svs
292 {
293 100 * k.color(i)
294 } else {
295 0
296 }
297 },
298 )
299}
300
301pub fn cdl_closing_marubozu(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
303 let k = Candles::new(open, high, low, close);
304 scan(
305 k.len,
306 BODY_LONG.avg_period.max(SHADOW_VERY_SHORT.avg_period),
307 |i| {
308 let svs = k.average(&SHADOW_VERY_SHORT, i);
309 if k.real_body(i) > k.average(&BODY_LONG, i)
310 && ((k.color(i) == 1 && k.upper_shadow(i) < svs)
311 || (k.color(i) == -1 && k.lower_shadow(i) < svs))
312 {
313 100 * k.color(i)
314 } else {
315 0
316 }
317 },
318 )
319}
320
321pub fn cdl_long_line(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
323 let k = Candles::new(open, high, low, close);
324 scan(
325 k.len,
326 BODY_LONG.avg_period.max(SHADOW_SHORT.avg_period),
327 |i| {
328 let short = k.average(&SHADOW_SHORT, i);
329 if k.real_body(i) > k.average(&BODY_LONG, i)
330 && k.upper_shadow(i) < short
331 && k.lower_shadow(i) < short
332 {
333 100 * k.color(i)
334 } else {
335 0
336 }
337 },
338 )
339}
340
341pub fn cdl_short_line(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
343 let k = Candles::new(open, high, low, close);
344 scan(
345 k.len,
346 BODY_SHORT.avg_period.max(SHADOW_SHORT.avg_period),
347 |i| {
348 let short = k.average(&SHADOW_SHORT, i);
349 if k.real_body(i) < k.average(&BODY_SHORT, i)
350 && k.upper_shadow(i) < short
351 && k.lower_shadow(i) < short
352 {
353 100 * k.color(i)
354 } else {
355 0
356 }
357 },
358 )
359}
360
361pub fn cdl_spinning_top(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
363 let k = Candles::new(open, high, low, close);
364 scan(k.len, BODY_SHORT.avg_period, |i| {
365 let body = k.real_body(i);
366 if body < k.average(&BODY_SHORT, i) && k.upper_shadow(i) > body && k.lower_shadow(i) > body
367 {
368 100 * k.color(i)
369 } else {
370 0
371 }
372 })
373}
374
375pub fn cdl_high_wave(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
377 let k = Candles::new(open, high, low, close);
378 scan(
379 k.len,
380 BODY_SHORT.avg_period.max(SHADOW_VERY_LONG.avg_period),
381 |i| {
382 let very_long = k.average(&SHADOW_VERY_LONG, i);
383 if k.real_body(i) < k.average(&BODY_SHORT, i)
384 && k.upper_shadow(i) > very_long
385 && k.lower_shadow(i) > very_long
386 {
387 100 * k.color(i)
388 } else {
389 0
390 }
391 },
392 )
393}
394
395pub fn cdl_2crows(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
404 let k = Candles::new(open, high, low, close);
405 scan(k.len, BODY_LONG.avg_period + 2, |i| {
406 if k.color(i - 2) == 1
407 && k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
408 && k.color(i - 1) == -1
409 && k.real_body_gap_up(i - 1, i - 2)
410 && k.color(i) == -1
411 && k.open[i] < k.open[i - 1]
412 && k.open[i] > k.close[i - 1]
413 && k.close[i] > k.open[i - 2]
414 && k.close[i] < k.close[i - 2]
415 {
416 -100
417 } else {
418 0
419 }
420 })
421}
422
423pub fn cdl_3blackcrows(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
426 let k = Candles::new(open, high, low, close);
427 scan(k.len, SHADOW_VERY_SHORT.avg_period + 3, |i| {
428 if k.color(i - 3) == 1
429 && k.color(i - 2) == -1
430 && k.lower_shadow(i - 2) < k.average(&SHADOW_VERY_SHORT, i - 2)
431 && k.color(i - 1) == -1
432 && k.lower_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
433 && k.color(i) == -1
434 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
435 && k.open[i - 1] < k.open[i - 2]
436 && k.open[i - 1] > k.close[i - 2]
437 && k.open[i] < k.open[i - 1]
438 && k.open[i] > k.close[i - 1]
439 && k.high[i - 3] > k.close[i - 2]
440 && k.close[i - 2] > k.close[i - 1]
441 && k.close[i - 1] > k.close[i]
442 {
443 -100
444 } else {
445 0
446 }
447 })
448}
449
450pub fn cdl_3inside(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
453 let k = Candles::new(open, high, low, close);
454 scan(
455 k.len,
456 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 2,
457 |i| {
458 if k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
459 && k.real_body(i - 1) <= k.average(&BODY_SHORT, i - 1)
460 && k.body_top(i - 1) < k.body_top(i - 2)
461 && k.body_bottom(i - 1) > k.body_bottom(i - 2)
462 && ((k.color(i - 2) == 1 && k.color(i) == -1 && k.close[i] < k.open[i - 2])
463 || (k.color(i - 2) == -1 && k.color(i) == 1 && k.close[i] > k.open[i - 2]))
464 {
465 -k.color(i - 2) * 100
466 } else {
467 0
468 }
469 },
470 )
471}
472
473pub fn cdl_3linestrike(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
476 let k = Candles::new(open, high, low, close);
477 scan(k.len, NEAR.avg_period + 3, |i| {
478 if k.color(i - 3) == k.color(i - 2)
479 && k.color(i - 2) == k.color(i - 1)
480 && k.color(i) == -k.color(i - 1)
481 && k.open[i - 2] >= k.body_bottom(i - 3) - k.average(&NEAR, i - 3)
482 && k.open[i - 2] <= k.body_top(i - 3) + k.average(&NEAR, i - 3)
483 && k.open[i - 1] >= k.body_bottom(i - 2) - k.average(&NEAR, i - 2)
484 && k.open[i - 1] <= k.body_top(i - 2) + k.average(&NEAR, i - 2)
485 && ((k.color(i - 1) == 1
486 && k.close[i - 1] > k.close[i - 2]
487 && k.close[i - 2] > k.close[i - 3]
488 && k.open[i] > k.close[i - 1]
489 && k.close[i] < k.open[i - 3])
490 || (k.color(i - 1) == -1
491 && k.close[i - 1] < k.close[i - 2]
492 && k.close[i - 2] < k.close[i - 3]
493 && k.open[i] < k.close[i - 1]
494 && k.close[i] > k.open[i - 3]))
495 {
496 k.color(i - 1) * 100
497 } else {
498 0
499 }
500 })
501}
502
503pub fn cdl_3outside(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
506 let k = Candles::new(open, high, low, close);
507 scan(k.len, 3, |i| {
508 if (k.color(i - 1) == 1
509 && k.color(i - 2) == -1
510 && k.close[i - 1] > k.open[i - 2]
511 && k.open[i - 1] < k.close[i - 2]
512 && k.close[i] > k.close[i - 1])
513 || (k.color(i - 1) == -1
514 && k.color(i - 2) == 1
515 && k.open[i - 1] > k.close[i - 2]
516 && k.close[i - 1] < k.open[i - 2]
517 && k.close[i] < k.close[i - 1])
518 {
519 k.color(i - 1) * 100
520 } else {
521 0
522 }
523 })
524}
525
526pub fn cdl_3starsinsouth(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
528 let k = Candles::new(open, high, low, close);
529 let lookback = SHADOW_VERY_SHORT
530 .avg_period
531 .max(SHADOW_LONG.avg_period)
532 .max(BODY_LONG.avg_period)
533 .max(BODY_SHORT.avg_period)
534 + 2;
535 scan(k.len, lookback, |i| {
536 if k.color(i - 2) == -1
537 && k.color(i - 1) == -1
538 && k.color(i) == -1
539 && k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
540 && k.lower_shadow(i - 2) > k.average(&SHADOW_LONG, i - 2)
541 && k.real_body(i - 1) < k.real_body(i - 2)
542 && k.open[i - 1] > k.close[i - 2]
543 && k.open[i - 1] <= k.high[i - 2]
544 && k.low[i - 1] < k.close[i - 2]
545 && k.low[i - 1] >= k.low[i - 2]
546 && k.lower_shadow(i - 1) > k.average(&SHADOW_VERY_SHORT, i - 1)
547 && k.real_body(i) < k.average(&BODY_SHORT, i)
548 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
549 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
550 && k.low[i] > k.low[i - 1]
551 && k.high[i] < k.high[i - 1]
552 {
553 100
554 } else {
555 0
556 }
557 })
558}
559
560pub fn cdl_3whitesoldiers(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
563 let k = Candles::new(open, high, low, close);
564 let lookback = SHADOW_VERY_SHORT
565 .avg_period
566 .max(BODY_SHORT.avg_period)
567 .max(FAR.avg_period)
568 .max(NEAR.avg_period)
569 + 2;
570 scan(k.len, lookback, |i| {
571 if k.color(i - 2) == 1
572 && k.upper_shadow(i - 2) < k.average(&SHADOW_VERY_SHORT, i - 2)
573 && k.color(i - 1) == 1
574 && k.upper_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
575 && k.color(i) == 1
576 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
577 && k.close[i] > k.close[i - 1]
578 && k.close[i - 1] > k.close[i - 2]
579 && k.open[i - 1] > k.open[i - 2]
580 && k.open[i - 1] <= k.close[i - 2] + k.average(&NEAR, i - 2)
581 && k.open[i] > k.open[i - 1]
582 && k.open[i] <= k.close[i - 1] + k.average(&NEAR, i - 1)
583 && k.real_body(i - 1) > k.real_body(i - 2) - k.average(&FAR, i - 2)
584 && k.real_body(i) > k.real_body(i - 1) - k.average(&FAR, i - 1)
585 && k.real_body(i) > k.average(&BODY_SHORT, i)
586 {
587 100
588 } else {
589 0
590 }
591 })
592}
593
594pub fn cdl_abandonedbaby(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
597 let k = Candles::new(open, high, low, close);
598 let penetration = 0.3;
599 let lookback = BODY_DOJI
600 .avg_period
601 .max(BODY_LONG.avg_period)
602 .max(BODY_SHORT.avg_period)
603 + 2;
604 scan(k.len, lookback, |i| {
605 if k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
606 && k.real_body(i - 1) <= k.average(&BODY_DOJI, i - 1)
607 && k.real_body(i) > k.average(&BODY_SHORT, i)
608 && ((k.color(i - 2) == 1
609 && k.color(i) == -1
610 && k.close[i] < k.close[i - 2] - k.real_body(i - 2) * penetration
611 && k.candle_gap_up(i - 1, i - 2)
612 && k.candle_gap_down(i, i - 1))
613 || (k.color(i - 2) == -1
614 && k.color(i) == 1
615 && k.close[i] > k.close[i - 2] + k.real_body(i - 2) * penetration
616 && k.candle_gap_down(i - 1, i - 2)
617 && k.candle_gap_up(i, i - 1)))
618 {
619 k.color(i) * 100
620 } else {
621 0
622 }
623 })
624}
625
626pub fn cdl_advanceblock(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
629 let k = Candles::new(open, high, low, close);
630 let lookback = SHADOW_LONG
631 .avg_period
632 .max(SHADOW_SHORT.avg_period)
633 .max(FAR.avg_period)
634 .max(NEAR.avg_period)
635 .max(BODY_LONG.avg_period)
636 + 2;
637 scan(k.len, lookback, |i| {
638 if k.color(i - 2) == 1
639 && k.color(i - 1) == 1
640 && k.color(i) == 1
641 && k.close[i] > k.close[i - 1]
642 && k.close[i - 1] > k.close[i - 2]
643 && k.open[i - 1] > k.open[i - 2]
644 && k.open[i - 1] <= k.close[i - 2] + k.average(&NEAR, i - 2)
645 && k.open[i] > k.open[i - 1]
646 && k.open[i] <= k.close[i - 1] + k.average(&NEAR, i - 1)
647 && k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
648 && k.upper_shadow(i - 2) < k.average(&SHADOW_SHORT, i - 2)
649 && ((k.real_body(i - 1) < k.real_body(i - 2) - k.average(&FAR, i - 2)
650 && k.real_body(i) < k.real_body(i - 1) + k.average(&NEAR, i - 1))
651 || (k.real_body(i) < k.real_body(i - 1) - k.average(&FAR, i - 1))
652 || (k.real_body(i) < k.real_body(i - 1)
653 && k.real_body(i - 1) < k.real_body(i - 2)
654 && (k.upper_shadow(i) > k.average(&SHADOW_SHORT, i)
655 || k.upper_shadow(i - 1) > k.average(&SHADOW_SHORT, i - 1)))
656 || (k.real_body(i) < k.real_body(i - 1)
657 && k.upper_shadow(i) > k.average(&SHADOW_LONG, i)))
658 {
659 -100
660 } else {
661 0
662 }
663 })
664}
665
666pub fn cdl_belthold(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
669 let k = Candles::new(open, high, low, close);
670 scan(
671 k.len,
672 BODY_LONG.avg_period.max(SHADOW_VERY_SHORT.avg_period),
673 |i| {
674 if k.real_body(i) > k.average(&BODY_LONG, i)
675 && ((k.color(i) == 1 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i))
676 || (k.color(i) == -1 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)))
677 {
678 k.color(i) * 100
679 } else {
680 0
681 }
682 },
683 )
684}
685
686pub fn cdl_breakaway(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
688 let k = Candles::new(open, high, low, close);
689 scan(k.len, BODY_LONG.avg_period + 4, |i| {
690 if k.real_body(i - 4) > k.average(&BODY_LONG, i - 4)
691 && k.color(i - 4) == k.color(i - 3)
692 && k.color(i - 3) == k.color(i - 1)
693 && k.color(i - 1) == -k.color(i)
694 && ((k.color(i - 4) == -1
695 && k.real_body_gap_down(i - 3, i - 4)
696 && k.high[i - 2] < k.high[i - 3]
697 && k.low[i - 2] < k.low[i - 3]
698 && k.high[i - 1] < k.high[i - 2]
699 && k.low[i - 1] < k.low[i - 2]
700 && k.close[i] > k.open[i - 3]
701 && k.close[i] < k.close[i - 4])
702 || (k.color(i - 4) == 1
703 && k.real_body_gap_up(i - 3, i - 4)
704 && k.high[i - 2] > k.high[i - 3]
705 && k.low[i - 2] > k.low[i - 3]
706 && k.high[i - 1] > k.high[i - 2]
707 && k.low[i - 1] > k.low[i - 2]
708 && k.close[i] < k.open[i - 3]
709 && k.close[i] > k.close[i - 4]))
710 {
711 k.color(i) * 100
712 } else {
713 0
714 }
715 })
716}
717
718pub fn cdl_concealbabyswall(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
721 let k = Candles::new(open, high, low, close);
722 scan(k.len, SHADOW_VERY_SHORT.avg_period + 3, |i| {
723 if k.color(i - 3) == -1
724 && k.color(i - 2) == -1
725 && k.color(i - 1) == -1
726 && k.color(i) == -1
727 && k.lower_shadow(i - 3) < k.average(&SHADOW_VERY_SHORT, i - 3)
728 && k.upper_shadow(i - 3) < k.average(&SHADOW_VERY_SHORT, i - 3)
729 && k.lower_shadow(i - 2) < k.average(&SHADOW_VERY_SHORT, i - 2)
730 && k.upper_shadow(i - 2) < k.average(&SHADOW_VERY_SHORT, i - 2)
731 && k.real_body_gap_down(i - 1, i - 2)
732 && k.upper_shadow(i - 1) > k.average(&SHADOW_VERY_SHORT, i - 1)
733 && k.high[i - 1] > k.close[i - 2]
734 && k.high[i] > k.high[i - 1]
735 && k.low[i] < k.low[i - 1]
736 {
737 100
738 } else {
739 0
740 }
741 })
742}
743
744pub fn cdl_counterattack(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
747 let k = Candles::new(open, high, low, close);
748 scan(k.len, EQUAL.avg_period.max(BODY_LONG.avg_period) + 1, |i| {
749 if k.color(i - 1) == -k.color(i)
750 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
751 && k.real_body(i) > k.average(&BODY_LONG, i)
752 && k.close[i] <= k.close[i - 1] + k.average(&EQUAL, i - 1)
753 && k.close[i] >= k.close[i - 1] - k.average(&EQUAL, i - 1)
754 {
755 k.color(i) * 100
756 } else {
757 0
758 }
759 })
760}
761
762pub fn cdl_darkcloudcover(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
765 let k = Candles::new(open, high, low, close);
766 let penetration = 0.5;
767 scan(k.len, BODY_LONG.avg_period + 1, |i| {
768 if k.color(i - 1) == 1
769 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
770 && k.color(i) == -1
771 && k.open[i] > k.high[i - 1]
772 && k.close[i] > k.open[i - 1]
773 && k.close[i] < k.close[i - 1] - k.real_body(i - 1) * penetration
774 {
775 -100
776 } else {
777 0
778 }
779 })
780}
781
782pub fn cdl_dojistar(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
784 let k = Candles::new(open, high, low, close);
785 scan(
786 k.len,
787 BODY_DOJI.avg_period.max(BODY_LONG.avg_period) + 1,
788 |i| {
789 if k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
790 && k.real_body(i) <= k.average(&BODY_DOJI, i)
791 && ((k.color(i - 1) == 1 && k.real_body_gap_up(i, i - 1))
792 || (k.color(i - 1) == -1 && k.real_body_gap_down(i, i - 1)))
793 {
794 -k.color(i - 1) * 100
795 } else {
796 0
797 }
798 },
799 )
800}
801
802pub fn cdl_engulfing(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
805 let k = Candles::new(open, high, low, close);
806 scan(k.len, 2, |i| {
807 let engulfs = (k.color(i) == 1
808 && k.color(i - 1) == -1
809 && ((k.close[i] >= k.open[i - 1] && k.open[i] < k.close[i - 1])
810 || (k.close[i] > k.open[i - 1] && k.open[i] <= k.close[i - 1])))
811 || (k.color(i) == -1
812 && k.color(i - 1) == 1
813 && ((k.open[i] >= k.close[i - 1] && k.close[i] < k.open[i - 1])
814 || (k.open[i] > k.close[i - 1] && k.close[i] <= k.open[i - 1])));
815 if engulfs {
816 if k.open[i] != k.close[i - 1] && k.close[i] != k.open[i - 1] {
817 k.color(i) * 100
818 } else {
819 k.color(i) * 80
820 }
821 } else {
822 0
823 }
824 })
825}
826
827pub fn cdl_eveningdojistar(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
830 let k = Candles::new(open, high, low, close);
831 let penetration = 0.3;
832 let lookback = BODY_DOJI
833 .avg_period
834 .max(BODY_LONG.avg_period)
835 .max(BODY_SHORT.avg_period)
836 + 2;
837 scan(k.len, lookback, |i| {
838 if k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
839 && k.color(i - 2) == 1
840 && k.real_body(i - 1) <= k.average(&BODY_DOJI, i - 1)
841 && k.real_body_gap_up(i - 1, i - 2)
842 && k.real_body(i) > k.average(&BODY_SHORT, i)
843 && k.color(i) == -1
844 && k.close[i] < k.close[i - 2] - k.real_body(i - 2) * penetration
845 {
846 -100
847 } else {
848 0
849 }
850 })
851}
852
853pub fn cdl_eveningstar(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
856 let k = Candles::new(open, high, low, close);
857 let penetration = 0.3;
858 scan(
859 k.len,
860 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 2,
861 |i| {
862 if k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
863 && k.color(i - 2) == 1
864 && k.real_body(i - 1) <= k.average(&BODY_SHORT, i - 1)
865 && k.real_body_gap_up(i - 1, i - 2)
866 && k.real_body(i) > k.average(&BODY_SHORT, i)
867 && k.color(i) == -1
868 && k.close[i] < k.close[i - 2] - k.real_body(i - 2) * penetration
869 {
870 -100
871 } else {
872 0
873 }
874 },
875 )
876}
877
878pub fn cdl_gapsidesidewhite(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
881 let k = Candles::new(open, high, low, close);
882 scan(k.len, NEAR.avg_period.max(EQUAL.avg_period) + 2, |i| {
883 let gap_up = k.real_body_gap_up(i - 1, i - 2) && k.real_body_gap_up(i, i - 2);
884 let gap_down = k.real_body_gap_down(i - 1, i - 2) && k.real_body_gap_down(i, i - 2);
885 if (gap_up || gap_down)
886 && k.color(i - 1) == 1
887 && k.color(i) == 1
888 && k.real_body(i) >= k.real_body(i - 1) - k.average(&NEAR, i - 1)
889 && k.real_body(i) <= k.real_body(i - 1) + k.average(&NEAR, i - 1)
890 && k.open[i] >= k.open[i - 1] - k.average(&EQUAL, i - 1)
891 && k.open[i] <= k.open[i - 1] + k.average(&EQUAL, i - 1)
892 {
893 if gap_up { 100 } else { -100 }
894 } else {
895 0
896 }
897 })
898}
899
900pub fn cdl_hammer(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
902 let k = Candles::new(open, high, low, close);
903 let lookback = BODY_SHORT
904 .avg_period
905 .max(SHADOW_LONG.avg_period)
906 .max(SHADOW_VERY_SHORT.avg_period)
907 .max(NEAR.avg_period)
908 + 1;
909 scan(k.len, lookback, |i| {
910 if k.real_body(i) < k.average(&BODY_SHORT, i)
911 && k.lower_shadow(i) > k.average(&SHADOW_LONG, i)
912 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
913 && k.body_bottom(i) <= k.low[i - 1] + k.average(&NEAR, i - 1)
914 {
915 100
916 } else {
917 0
918 }
919 })
920}
921
922pub fn cdl_hangingman(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
925 let k = Candles::new(open, high, low, close);
926 let lookback = BODY_SHORT
927 .avg_period
928 .max(SHADOW_LONG.avg_period)
929 .max(SHADOW_VERY_SHORT.avg_period)
930 .max(NEAR.avg_period)
931 + 1;
932 scan(k.len, lookback, |i| {
933 if k.real_body(i) < k.average(&BODY_SHORT, i)
934 && k.lower_shadow(i) > k.average(&SHADOW_LONG, i)
935 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
936 && k.body_bottom(i) >= k.high[i - 1] - k.average(&NEAR, i - 1)
937 {
938 -100
939 } else {
940 0
941 }
942 })
943}
944
945pub fn cdl_harami(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
948 let k = Candles::new(open, high, low, close);
949 scan(
950 k.len,
951 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 1,
952 |i| {
953 if k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
954 && k.real_body(i) <= k.average(&BODY_SHORT, i)
955 {
956 if k.body_top(i) < k.body_top(i - 1) && k.body_bottom(i) > k.body_bottom(i - 1) {
957 -k.color(i - 1) * 100
958 } else if k.body_top(i) <= k.body_top(i - 1)
959 && k.body_bottom(i) >= k.body_bottom(i - 1)
960 {
961 -k.color(i - 1) * 80
962 } else {
963 0
964 }
965 } else {
966 0
967 }
968 },
969 )
970}
971
972pub fn cdl_haramicross(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
974 let k = Candles::new(open, high, low, close);
975 scan(
976 k.len,
977 BODY_DOJI.avg_period.max(BODY_LONG.avg_period) + 1,
978 |i| {
979 if k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
980 && k.real_body(i) <= k.average(&BODY_DOJI, i)
981 {
982 if k.body_top(i) < k.body_top(i - 1) && k.body_bottom(i) > k.body_bottom(i - 1) {
983 -k.color(i - 1) * 100
984 } else if k.body_top(i) <= k.body_top(i - 1)
985 && k.body_bottom(i) >= k.body_bottom(i - 1)
986 {
987 -k.color(i - 1) * 80
988 } else {
989 0
990 }
991 } else {
992 0
993 }
994 },
995 )
996}
997
998pub fn cdl_hikkake(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1001 let k = Candles::new(open, high, low, close);
1002 let lookback = 5usize;
1003 let mut out = vec![0i32; k.len];
1004 if lookback >= k.len {
1005 return out;
1006 }
1007 let detect = |i: usize| -> bool {
1008 k.high[i - 1] < k.high[i - 2]
1009 && k.low[i - 1] > k.low[i - 2]
1010 && ((k.high[i] < k.high[i - 1] && k.low[i] < k.low[i - 1])
1011 || (k.high[i] > k.high[i - 1] && k.low[i] > k.low[i - 1]))
1012 };
1013 let mut pattern_result = 0i32;
1014 let mut pattern_idx = 0usize;
1015 let mut i = lookback - 3;
1016 while i < k.len {
1017 if detect(i) {
1018 pattern_result = 100 * if k.high[i] < k.high[i - 1] { 1 } else { -1 };
1019 pattern_idx = i;
1020 if i >= lookback {
1021 out[i] = pattern_result;
1022 }
1023 } else if i <= pattern_idx + 3
1024 && ((pattern_result > 0 && k.close[i] > k.high[pattern_idx - 1])
1025 || (pattern_result < 0 && k.close[i] < k.low[pattern_idx - 1]))
1026 {
1027 if i >= lookback {
1028 out[i] = pattern_result + 100 * if pattern_result > 0 { 1 } else { -1 };
1029 }
1030 pattern_idx = 0;
1031 }
1032 i += 1;
1033 }
1034 out
1035}
1036
1037pub fn cdl_hikkakemod(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1040 let k = Candles::new(open, high, low, close);
1041 let lookback = 1usize.max(NEAR.avg_period) + 5;
1042 let mut out = vec![0i32; k.len];
1043 if lookback >= k.len {
1044 return out;
1045 }
1046 let detect = |i: usize| -> bool {
1047 let near = k.average(&NEAR, i - 2);
1048 k.high[i - 2] < k.high[i - 3]
1049 && k.low[i - 2] > k.low[i - 3]
1050 && k.high[i - 1] < k.high[i - 2]
1051 && k.low[i - 1] > k.low[i - 2]
1052 && ((k.high[i] < k.high[i - 1]
1053 && k.low[i] < k.low[i - 1]
1054 && k.close[i - 2] <= k.low[i - 2] + near)
1055 || (k.high[i] > k.high[i - 1]
1056 && k.low[i] > k.low[i - 1]
1057 && k.close[i - 2] >= k.high[i - 2] - near))
1058 };
1059 let mut pattern_result = 0i32;
1060 let mut pattern_idx = 0usize;
1061 let mut i = lookback - 3;
1062 while i < k.len {
1063 if detect(i) {
1064 pattern_result = 100 * if k.high[i] < k.high[i - 1] { 1 } else { -1 };
1065 pattern_idx = i;
1066 if i >= lookback {
1067 out[i] = pattern_result;
1068 }
1069 } else if i <= pattern_idx + 3
1070 && ((pattern_result > 0 && k.close[i] > k.high[pattern_idx - 1])
1071 || (pattern_result < 0 && k.close[i] < k.low[pattern_idx - 1]))
1072 {
1073 if i >= lookback {
1074 out[i] = pattern_result + 100 * if pattern_result > 0 { 1 } else { -1 };
1075 }
1076 pattern_idx = 0;
1077 }
1078 i += 1;
1079 }
1080 out
1081}
1082
1083pub fn cdl_homingpigeon(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1085 let k = Candles::new(open, high, low, close);
1086 scan(
1087 k.len,
1088 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 1,
1089 |i| {
1090 if k.color(i - 1) == -1
1091 && k.color(i) == -1
1092 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1093 && k.real_body(i) <= k.average(&BODY_SHORT, i)
1094 && k.open[i] < k.open[i - 1]
1095 && k.close[i] > k.close[i - 1]
1096 {
1097 100
1098 } else {
1099 0
1100 }
1101 },
1102 )
1103}
1104
1105pub fn cdl_identical3crows(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1108 let k = Candles::new(open, high, low, close);
1109 scan(
1110 k.len,
1111 SHADOW_VERY_SHORT.avg_period.max(EQUAL.avg_period) + 2,
1112 |i| {
1113 if k.color(i - 2) == -1
1114 && k.lower_shadow(i - 2) < k.average(&SHADOW_VERY_SHORT, i - 2)
1115 && k.color(i - 1) == -1
1116 && k.lower_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
1117 && k.color(i) == -1
1118 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1119 && k.close[i - 2] > k.close[i - 1]
1120 && k.close[i - 1] > k.close[i]
1121 && k.open[i - 1] <= k.close[i - 2] + k.average(&EQUAL, i - 2)
1122 && k.open[i - 1] >= k.close[i - 2] - k.average(&EQUAL, i - 2)
1123 && k.open[i] <= k.close[i - 1] + k.average(&EQUAL, i - 1)
1124 && k.open[i] >= k.close[i - 1] - k.average(&EQUAL, i - 1)
1125 {
1126 -100
1127 } else {
1128 0
1129 }
1130 },
1131 )
1132}
1133
1134pub fn cdl_inneck(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1137 let k = Candles::new(open, high, low, close);
1138 scan(k.len, EQUAL.avg_period.max(BODY_LONG.avg_period) + 1, |i| {
1139 if k.color(i - 1) == -1
1140 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1141 && k.color(i) == 1
1142 && k.open[i] < k.low[i - 1]
1143 && k.close[i] <= k.close[i - 1] + k.average(&EQUAL, i - 1)
1144 && k.close[i] >= k.close[i - 1]
1145 {
1146 -100
1147 } else {
1148 0
1149 }
1150 })
1151}
1152
1153pub fn cdl_invertedhammer(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1155 let k = Candles::new(open, high, low, close);
1156 let lookback = BODY_SHORT
1157 .avg_period
1158 .max(SHADOW_LONG.avg_period)
1159 .max(SHADOW_VERY_SHORT.avg_period)
1160 + 1;
1161 scan(k.len, lookback, |i| {
1162 if k.real_body(i) < k.average(&BODY_SHORT, i)
1163 && k.upper_shadow(i) > k.average(&SHADOW_LONG, i)
1164 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1165 && k.real_body_gap_down(i, i - 1)
1166 {
1167 100
1168 } else {
1169 0
1170 }
1171 })
1172}
1173
1174pub fn cdl_kicking(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1176 let k = Candles::new(open, high, low, close);
1177 scan(
1178 k.len,
1179 SHADOW_VERY_SHORT.avg_period.max(BODY_LONG.avg_period) + 1,
1180 |i| {
1181 if k.color(i - 1) == -k.color(i)
1182 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1183 && k.upper_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
1184 && k.lower_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
1185 && k.real_body(i) > k.average(&BODY_LONG, i)
1186 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1187 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1188 && ((k.color(i - 1) == -1 && k.candle_gap_up(i, i - 1))
1189 || (k.color(i - 1) == 1 && k.candle_gap_down(i, i - 1)))
1190 {
1191 k.color(i) * 100
1192 } else {
1193 0
1194 }
1195 },
1196 )
1197}
1198
1199pub fn cdl_kickingbylength(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1201 let k = Candles::new(open, high, low, close);
1202 scan(
1203 k.len,
1204 SHADOW_VERY_SHORT.avg_period.max(BODY_LONG.avg_period) + 1,
1205 |i| {
1206 if k.color(i - 1) == -k.color(i)
1207 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1208 && k.upper_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
1209 && k.lower_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
1210 && k.real_body(i) > k.average(&BODY_LONG, i)
1211 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1212 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1213 && ((k.color(i - 1) == -1 && k.candle_gap_up(i, i - 1))
1214 || (k.color(i - 1) == 1 && k.candle_gap_down(i, i - 1)))
1215 {
1216 let longer = if k.real_body(i) > k.real_body(i - 1) {
1217 i
1218 } else {
1219 i - 1
1220 };
1221 k.color(longer) * 100
1222 } else {
1223 0
1224 }
1225 },
1226 )
1227}
1228
1229pub fn cdl_ladderbottom(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1232 let k = Candles::new(open, high, low, close);
1233 scan(k.len, SHADOW_VERY_SHORT.avg_period + 4, |i| {
1234 if k.color(i - 4) == -1
1235 && k.color(i - 3) == -1
1236 && k.color(i - 2) == -1
1237 && k.open[i - 4] > k.open[i - 3]
1238 && k.open[i - 3] > k.open[i - 2]
1239 && k.close[i - 4] > k.close[i - 3]
1240 && k.close[i - 3] > k.close[i - 2]
1241 && k.color(i - 1) == -1
1242 && k.upper_shadow(i - 1) > k.average(&SHADOW_VERY_SHORT, i - 1)
1243 && k.color(i) == 1
1244 && k.open[i] > k.open[i - 1]
1245 && k.close[i] > k.high[i - 1]
1246 {
1247 100
1248 } else {
1249 0
1250 }
1251 })
1252}
1253
1254pub fn cdl_matchinglow(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1256 let k = Candles::new(open, high, low, close);
1257 scan(k.len, EQUAL.avg_period + 1, |i| {
1258 if k.color(i - 1) == -1
1259 && k.color(i) == -1
1260 && k.close[i] <= k.close[i - 1] + k.average(&EQUAL, i - 1)
1261 && k.close[i] >= k.close[i - 1] - k.average(&EQUAL, i - 1)
1262 {
1263 100
1264 } else {
1265 0
1266 }
1267 })
1268}
1269
1270pub fn cdl_mathold(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1273 let k = Candles::new(open, high, low, close);
1274 let penetration = 0.5;
1275 scan(
1276 k.len,
1277 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 4,
1278 |i| {
1279 if k.real_body(i - 4) > k.average(&BODY_LONG, i - 4)
1280 && k.real_body(i - 3) < k.average(&BODY_SHORT, i - 3)
1281 && k.real_body(i - 2) < k.average(&BODY_SHORT, i - 2)
1282 && k.real_body(i - 1) < k.average(&BODY_SHORT, i - 1)
1283 && k.color(i - 4) == 1
1284 && k.color(i - 3) == -1
1285 && k.color(i) == 1
1286 && k.real_body_gap_up(i - 3, i - 4)
1287 && k.body_bottom(i - 2) < k.close[i - 4]
1288 && k.body_bottom(i - 1) < k.close[i - 4]
1289 && k.body_bottom(i - 2) > k.close[i - 4] - k.real_body(i - 4) * penetration
1290 && k.body_bottom(i - 1) > k.close[i - 4] - k.real_body(i - 4) * penetration
1291 && k.body_top(i - 2) < k.open[i - 3]
1292 && k.body_top(i - 1) < k.body_top(i - 2)
1293 && k.open[i] > k.close[i - 1]
1294 && k.close[i] > k.high[i - 3].max(k.high[i - 2]).max(k.high[i - 1])
1295 {
1296 100
1297 } else {
1298 0
1299 }
1300 },
1301 )
1302}
1303
1304pub fn cdl_morningdojistar(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1307 let k = Candles::new(open, high, low, close);
1308 let penetration = 0.3;
1309 let lookback = BODY_DOJI
1310 .avg_period
1311 .max(BODY_LONG.avg_period)
1312 .max(BODY_SHORT.avg_period)
1313 + 2;
1314 scan(k.len, lookback, |i| {
1315 if k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
1316 && k.color(i - 2) == -1
1317 && k.real_body(i - 1) <= k.average(&BODY_DOJI, i - 1)
1318 && k.real_body_gap_down(i - 1, i - 2)
1319 && k.real_body(i) > k.average(&BODY_SHORT, i)
1320 && k.color(i) == 1
1321 && k.close[i] > k.close[i - 2] + k.real_body(i - 2) * penetration
1322 {
1323 100
1324 } else {
1325 0
1326 }
1327 })
1328}
1329
1330pub fn cdl_morningstar(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1333 let k = Candles::new(open, high, low, close);
1334 let penetration = 0.3;
1335 scan(
1336 k.len,
1337 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 2,
1338 |i| {
1339 if k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
1340 && k.color(i - 2) == -1
1341 && k.real_body(i - 1) <= k.average(&BODY_SHORT, i - 1)
1342 && k.real_body_gap_down(i - 1, i - 2)
1343 && k.real_body(i) > k.average(&BODY_SHORT, i)
1344 && k.color(i) == 1
1345 && k.close[i] > k.close[i - 2] + k.real_body(i - 2) * penetration
1346 {
1347 100
1348 } else {
1349 0
1350 }
1351 },
1352 )
1353}
1354
1355pub fn cdl_onneck(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1357 let k = Candles::new(open, high, low, close);
1358 scan(k.len, EQUAL.avg_period.max(BODY_LONG.avg_period) + 1, |i| {
1359 if k.color(i - 1) == -1
1360 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1361 && k.color(i) == 1
1362 && k.open[i] < k.low[i - 1]
1363 && k.close[i] <= k.low[i - 1] + k.average(&EQUAL, i - 1)
1364 && k.close[i] >= k.low[i - 1] - k.average(&EQUAL, i - 1)
1365 {
1366 -100
1367 } else {
1368 0
1369 }
1370 })
1371}
1372
1373pub fn cdl_piercing(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1376 let k = Candles::new(open, high, low, close);
1377 scan(k.len, BODY_LONG.avg_period + 1, |i| {
1378 if k.color(i - 1) == -1
1379 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1380 && k.color(i) == 1
1381 && k.real_body(i) > k.average(&BODY_LONG, i)
1382 && k.open[i] < k.low[i - 1]
1383 && k.close[i] < k.open[i - 1]
1384 && k.close[i] > k.close[i - 1] + k.real_body(i - 1) * 0.5
1385 {
1386 100
1387 } else {
1388 0
1389 }
1390 })
1391}
1392
1393pub fn cdl_rickshawman(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1395 let k = Candles::new(open, high, low, close);
1396 let lookback = BODY_DOJI
1397 .avg_period
1398 .max(SHADOW_LONG.avg_period)
1399 .max(NEAR.avg_period);
1400 scan(k.len, lookback, |i| {
1401 let near = k.average(&NEAR, i);
1402 let mid = k.low[i] + k.high_low(i) / 2.0;
1403 if k.real_body(i) <= k.average(&BODY_DOJI, i)
1404 && k.lower_shadow(i) > k.average(&SHADOW_LONG, i)
1405 && k.upper_shadow(i) > k.average(&SHADOW_LONG, i)
1406 && k.body_bottom(i) <= mid + near
1407 && k.body_top(i) >= mid - near
1408 {
1409 100
1410 } else {
1411 0
1412 }
1413 })
1414}
1415
1416pub fn cdl_risefall3methods(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1419 let k = Candles::new(open, high, low, close);
1420 scan(
1421 k.len,
1422 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 4,
1423 |i| {
1424 let dir = k.color(i - 4) as f64;
1425 if k.real_body(i - 4) > k.average(&BODY_LONG, i - 4)
1426 && k.real_body(i - 3) < k.average(&BODY_SHORT, i - 3)
1427 && k.real_body(i - 2) < k.average(&BODY_SHORT, i - 2)
1428 && k.real_body(i - 1) < k.average(&BODY_SHORT, i - 1)
1429 && k.real_body(i) > k.average(&BODY_LONG, i)
1430 && k.color(i - 4) == -k.color(i - 3)
1431 && k.color(i - 3) == k.color(i - 2)
1432 && k.color(i - 2) == k.color(i - 1)
1433 && k.color(i - 1) == -k.color(i)
1434 && k.body_bottom(i - 3) < k.high[i - 4]
1435 && k.body_top(i - 3) > k.low[i - 4]
1436 && k.body_bottom(i - 2) < k.high[i - 4]
1437 && k.body_top(i - 2) > k.low[i - 4]
1438 && k.body_bottom(i - 1) < k.high[i - 4]
1439 && k.body_top(i - 1) > k.low[i - 4]
1440 && k.close[i - 2] * dir < k.close[i - 3] * dir
1441 && k.close[i - 1] * dir < k.close[i - 2] * dir
1442 && k.open[i] * dir > k.close[i - 1] * dir
1443 && k.close[i] * dir > k.close[i - 4] * dir
1444 {
1445 100 * k.color(i - 4)
1446 } else {
1447 0
1448 }
1449 },
1450 )
1451}
1452
1453pub fn cdl_separatinglines(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1456 let k = Candles::new(open, high, low, close);
1457 let lookback = SHADOW_VERY_SHORT
1458 .avg_period
1459 .max(BODY_LONG.avg_period)
1460 .max(EQUAL.avg_period)
1461 + 1;
1462 scan(k.len, lookback, |i| {
1463 if k.color(i - 1) == -k.color(i)
1464 && k.open[i] <= k.open[i - 1] + k.average(&EQUAL, i - 1)
1465 && k.open[i] >= k.open[i - 1] - k.average(&EQUAL, i - 1)
1466 && k.real_body(i) > k.average(&BODY_LONG, i)
1467 && ((k.color(i) == 1 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i))
1468 || (k.color(i) == -1 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)))
1469 {
1470 k.color(i) * 100
1471 } else {
1472 0
1473 }
1474 })
1475}
1476
1477pub fn cdl_shootingstar(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1479 let k = Candles::new(open, high, low, close);
1480 let lookback = BODY_SHORT
1481 .avg_period
1482 .max(SHADOW_LONG.avg_period)
1483 .max(SHADOW_VERY_SHORT.avg_period)
1484 + 1;
1485 scan(k.len, lookback, |i| {
1486 if k.real_body(i) < k.average(&BODY_SHORT, i)
1487 && k.upper_shadow(i) > k.average(&SHADOW_LONG, i)
1488 && k.lower_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1489 && k.real_body_gap_up(i, i - 1)
1490 {
1491 -100
1492 } else {
1493 0
1494 }
1495 })
1496}
1497
1498pub fn cdl_stalledpattern(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1501 let k = Candles::new(open, high, low, close);
1502 let lookback = BODY_LONG
1503 .avg_period
1504 .max(BODY_SHORT.avg_period)
1505 .max(SHADOW_VERY_SHORT.avg_period)
1506 .max(NEAR.avg_period)
1507 + 2;
1508 scan(k.len, lookback, |i| {
1509 if k.color(i - 2) == 1
1510 && k.color(i - 1) == 1
1511 && k.color(i) == 1
1512 && k.close[i] > k.close[i - 1]
1513 && k.close[i - 1] > k.close[i - 2]
1514 && k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
1515 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1516 && k.upper_shadow(i - 1) < k.average(&SHADOW_VERY_SHORT, i - 1)
1517 && k.open[i - 1] > k.open[i - 2]
1518 && k.open[i - 1] <= k.close[i - 2] + k.average(&NEAR, i - 2)
1519 && k.real_body(i) < k.average(&BODY_SHORT, i)
1520 && k.open[i] >= k.close[i - 1] - k.real_body(i) - k.average(&NEAR, i - 1)
1521 {
1522 -100
1523 } else {
1524 0
1525 }
1526 })
1527}
1528
1529pub fn cdl_sticksandwich(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1532 let k = Candles::new(open, high, low, close);
1533 scan(k.len, EQUAL.avg_period + 2, |i| {
1534 if k.color(i - 2) == -1
1535 && k.color(i - 1) == 1
1536 && k.color(i) == -1
1537 && k.low[i - 1] > k.close[i - 2]
1538 && k.close[i] <= k.close[i - 2] + k.average(&EQUAL, i - 2)
1539 && k.close[i] >= k.close[i - 2] - k.average(&EQUAL, i - 2)
1540 {
1541 100
1542 } else {
1543 0
1544 }
1545 })
1546}
1547
1548pub fn cdl_takuri(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1551 let k = Candles::new(open, high, low, close);
1552 let lookback = BODY_DOJI
1553 .avg_period
1554 .max(SHADOW_VERY_SHORT.avg_period)
1555 .max(SHADOW_VERY_LONG.avg_period);
1556 scan(k.len, lookback, |i| {
1557 if k.real_body(i) <= k.average(&BODY_DOJI, i)
1558 && k.upper_shadow(i) < k.average(&SHADOW_VERY_SHORT, i)
1559 && k.lower_shadow(i) > k.average(&SHADOW_VERY_LONG, i)
1560 {
1561 100
1562 } else {
1563 0
1564 }
1565 })
1566}
1567
1568pub fn cdl_tasukigap(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1571 let k = Candles::new(open, high, low, close);
1572 scan(k.len, NEAR.avg_period + 2, |i| {
1573 let near = k.average(&NEAR, i - 1);
1574 if (k.real_body_gap_up(i - 1, i - 2)
1575 && k.color(i - 1) == 1
1576 && k.color(i) == -1
1577 && k.open[i] < k.close[i - 1]
1578 && k.open[i] > k.open[i - 1]
1579 && k.close[i] < k.open[i - 1]
1580 && k.close[i] > k.body_top(i - 2)
1581 && (k.real_body(i - 1) - k.real_body(i)).abs() < near)
1582 || (k.real_body_gap_down(i - 1, i - 2)
1583 && k.color(i - 1) == -1
1584 && k.color(i) == 1
1585 && k.open[i] < k.open[i - 1]
1586 && k.open[i] > k.close[i - 1]
1587 && k.close[i] > k.open[i - 1]
1588 && k.close[i] < k.body_bottom(i - 2)
1589 && (k.real_body(i - 1) - k.real_body(i)).abs() < near)
1590 {
1591 k.color(i - 1) * 100
1592 } else {
1593 0
1594 }
1595 })
1596}
1597
1598pub fn cdl_thrusting(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1601 let k = Candles::new(open, high, low, close);
1602 scan(k.len, EQUAL.avg_period.max(BODY_LONG.avg_period) + 1, |i| {
1603 if k.color(i - 1) == -1
1604 && k.real_body(i - 1) > k.average(&BODY_LONG, i - 1)
1605 && k.color(i) == 1
1606 && k.open[i] < k.low[i - 1]
1607 && k.close[i] > k.close[i - 1] + k.average(&EQUAL, i - 1)
1608 && k.close[i] <= k.close[i - 1] + k.real_body(i - 1) * 0.5
1609 {
1610 -100
1611 } else {
1612 0
1613 }
1614 })
1615}
1616
1617pub fn cdl_tristar(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1619 let k = Candles::new(open, high, low, close);
1620 scan(k.len, BODY_DOJI.avg_period + 2, |i| {
1621 let doji = k.average(&BODY_DOJI, i - 2);
1622 if k.real_body(i - 2) <= doji && k.real_body(i - 1) <= doji && k.real_body(i) <= doji {
1623 let mut result = 0;
1624 if k.real_body_gap_up(i - 1, i - 2) && k.body_top(i) < k.body_top(i - 1) {
1625 result = -100;
1626 }
1627 if k.real_body_gap_down(i - 1, i - 2) && k.body_bottom(i) > k.body_bottom(i - 1) {
1628 result = 100;
1629 }
1630 result
1631 } else {
1632 0
1633 }
1634 })
1635}
1636
1637pub fn cdl_unique3river(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1640 let k = Candles::new(open, high, low, close);
1641 scan(
1642 k.len,
1643 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 2,
1644 |i| {
1645 if k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
1646 && k.color(i - 2) == -1
1647 && k.color(i - 1) == -1
1648 && k.close[i - 1] > k.close[i - 2]
1649 && k.open[i - 1] <= k.open[i - 2]
1650 && k.low[i - 1] < k.low[i - 2]
1651 && k.real_body(i) < k.average(&BODY_SHORT, i)
1652 && k.color(i) == 1
1653 && k.open[i] > k.low[i - 1]
1654 {
1655 100
1656 } else {
1657 0
1658 }
1659 },
1660 )
1661}
1662
1663pub fn cdl_upsidegap2crows(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1666 let k = Candles::new(open, high, low, close);
1667 scan(
1668 k.len,
1669 BODY_SHORT.avg_period.max(BODY_LONG.avg_period) + 2,
1670 |i| {
1671 if k.color(i - 2) == 1
1672 && k.real_body(i - 2) > k.average(&BODY_LONG, i - 2)
1673 && k.color(i - 1) == -1
1674 && k.real_body(i - 1) <= k.average(&BODY_SHORT, i - 1)
1675 && k.real_body_gap_up(i - 1, i - 2)
1676 && k.color(i) == -1
1677 && k.open[i] > k.open[i - 1]
1678 && k.close[i] < k.close[i - 1]
1679 && k.close[i] > k.close[i - 2]
1680 {
1681 -100
1682 } else {
1683 0
1684 }
1685 },
1686 )
1687}
1688
1689pub fn cdl_xsidegap3methods(open: &[f64], high: &[f64], low: &[f64], close: &[f64]) -> Vec<i32> {
1692 let k = Candles::new(open, high, low, close);
1693 scan(k.len, 2, |i| {
1694 if k.color(i - 2) == k.color(i - 1)
1695 && k.color(i - 1) == -k.color(i)
1696 && k.open[i] < k.body_top(i - 1)
1697 && k.open[i] > k.body_bottom(i - 1)
1698 && k.close[i] < k.body_top(i - 2)
1699 && k.close[i] > k.body_bottom(i - 2)
1700 && ((k.color(i - 2) == 1 && k.real_body_gap_up(i - 1, i - 2))
1701 || (k.color(i - 2) == -1 && k.real_body_gap_down(i - 1, i - 2)))
1702 {
1703 k.color(i - 2) * 100
1704 } else {
1705 0
1706 }
1707 })
1708}