#[path = "common/mod.rs"]
mod common;
use adaq_talib::pattern::*;
use adaq_talib::utils::approx_eq_slice;
fn check_cdl(
name: &str,
f: fn(&[f64], &[f64], &[f64], &[f64]) -> Result<Vec<f64>, adaq_talib::error::TaError>,
) {
let fixture = format!("cdl_{name}.json");
let json = common::load_json(&fixture).expect("load fixture");
let open = common::load_f64_array(&json, "open").expect("open");
let high = common::load_f64_array(&json, "high").expect("high");
let low = common::load_f64_array(&json, "low").expect("low");
let close = common::load_f64_array(&json, "close").expect("close");
let expected = common::load_f64_array(&json, "expected").expect("expected");
let out = f(&open, &high, &low, &close).expect("pattern fn");
assert_eq!(out.len(), expected.len(), "length mismatch for cdl_{name}");
assert!(
approx_eq_slice(&out, &expected),
"cdl_{name} output deviates from golden vector beyond ADR 0005 tolerance"
);
}
#[test]
fn cdl_belthold_matches_golden_vector() {
check_cdl("belthold", cdl_belthold);
}
#[test]
fn cdl_breakaway_matches_golden_vector() {
check_cdl("breakaway", cdl_breakaway);
}
#[test]
fn cdl_closingmarubozu_matches_golden_vector() {
check_cdl("closingmarubozu", cdl_closingmarubozu);
}
#[test]
fn cdl_concealbabyswall_matches_golden_vector() {
check_cdl("concealbabyswall", cdl_concealbabyswall);
}
#[test]
fn cdl_counterattack_matches_golden_vector() {
check_cdl("counterattack", cdl_counterattack);
}
#[test]
fn cdl_darkcloudcover_matches_golden_vector() {
check_cdl("darkcloudcover", cdl_darkcloudcover);
}
#[test]
fn cdl_dojistar_matches_golden_vector() {
check_cdl("dojistar", cdl_dojistar);
}
#[test]
fn cdl_dragonflydoji_matches_golden_vector() {
check_cdl("dragonflydoji", cdl_dragonflydoji);
}
fn baseline_open() -> Vec<f64> {
vec![10.0; 10]
}
fn baseline_high() -> Vec<f64> {
vec![10.2; 10]
}
fn baseline_low() -> Vec<f64> {
vec![9.8; 10]
}
fn baseline_close() -> Vec<f64> {
vec![10.1; 10]
}
#[test]
fn cdl_belthold_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.1);
l.push(10.0); c.push(20.0);
let out = cdl_belthold(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 100.0, "bullish belt-hold should be +100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.1);
l.push(5.0); c.push(20.0);
let out = cdl_belthold(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 0.0, "belt-hold with long lower shadow should be 0");
}
#[test]
fn cdl_closingmarubozu_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.03); l.push(9.9);
c.push(20.0);
let out = cdl_closingmarubozu(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 100.0, "bullish closing marubozu should be +100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.5); l.push(9.9);
c.push(20.0);
let out = cdl_closingmarubozu(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 0.0, "closing marubozu with long upper shadow should be 0");
}
#[test]
fn cdl_dragonflydoji_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(10.03); l.push(5.0); c.push(10.0);
let out = cdl_dragonflydoji(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 100.0, "dragonfly doji should be +100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(10.03);
l.push(9.97); c.push(10.0);
let out = cdl_dragonflydoji(&o, &h, &l, &c).unwrap();
assert_eq!(out[10], 0.0, "doji without long lower shadow should be 0");
}
#[test]
fn cdl_darkcloudcover_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.1);
l.push(9.9);
c.push(20.0); o.push(21.0);
h.push(21.1);
l.push(11.9);
c.push(12.0); let out = cdl_darkcloudcover(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], -100.0, "dark cloud cover should be -100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.1);
l.push(9.9);
c.push(20.0);
o.push(21.0);
h.push(21.1);
l.push(17.9);
c.push(18.0); let out = cdl_darkcloudcover(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], 0.0, "dark cloud cover without penetration should be 0");
}
#[test]
fn cdl_dojistar_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.1);
l.push(9.9);
c.push(20.0); o.push(25.0);
h.push(25.1);
l.push(24.9);
c.push(25.05); let out = cdl_dojistar(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], -100.0, "bearish doji star should be -100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(20.1);
l.push(9.9);
c.push(20.0);
o.push(15.0);
h.push(15.1);
l.push(14.9);
c.push(15.05); let out = cdl_dojistar(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], 0.0, "doji star without gap should be 0");
}
#[test]
fn cdl_counterattack_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(12.0);
h.push(12.1);
l.push(9.9);
c.push(10.0); o.push(8.0);
h.push(10.1);
l.push(7.9);
c.push(10.0); let out = cdl_counterattack(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], 100.0, "bullish counterattack should be +100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(12.0);
h.push(12.1);
l.push(9.9);
c.push(10.0);
o.push(8.0);
h.push(20.1);
l.push(7.9);
c.push(20.0); let out = cdl_counterattack(&o, &h, &l, &c).unwrap();
assert_eq!(out[11], 0.0, "counterattack with unequal closes should be 0");
}
#[test]
fn cdl_concealbabyswall_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(10.03);
l.push(7.97);
c.push(8.0); o.push(8.0);
h.push(8.03);
l.push(5.97);
c.push(6.0); o.push(5.5);
h.push(6.1); l.push(4.9);
c.push(5.0); o.push(5.2);
h.push(6.5);
l.push(4.5);
c.push(4.8); let out = cdl_concealbabyswall(&o, &h, &l, &c).unwrap();
assert_eq!(out[13], 100.0, "concealing baby swallow should be +100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(10.03);
l.push(7.97);
c.push(8.0);
o.push(8.0);
h.push(8.03);
l.push(5.97);
c.push(6.0);
o.push(5.5);
h.push(6.1);
l.push(4.9);
c.push(5.0);
o.push(5.2);
h.push(6.0); l.push(4.5);
c.push(4.8);
let out = cdl_concealbabyswall(&o, &h, &l, &c).unwrap();
assert_eq!(out[13], 0.0, "concealing baby swallow without engulf should be 0");
}
#[test]
fn cdl_breakaway_trigger_and_reject() {
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(12.1);
l.push(9.9);
c.push(12.0); o.push(13.0);
h.push(14.1);
l.push(12.9);
c.push(14.0); o.push(13.5);
h.push(15.1);
l.push(13.4);
c.push(15.0); o.push(14.5);
h.push(16.1);
l.push(14.4);
c.push(16.0); o.push(15.0);
h.push(15.1);
l.push(12.4);
c.push(12.5); let out = cdl_breakaway(&o, &h, &l, &c).unwrap();
assert_eq!(out[14], -100.0, "bearish breakaway should be -100");
let mut o = baseline_open();
let mut h = baseline_high();
let mut l = baseline_low();
let mut c = baseline_close();
o.push(10.0);
h.push(12.1);
l.push(9.9);
c.push(12.0);
o.push(13.0);
h.push(14.1);
l.push(12.9);
c.push(14.0);
o.push(13.5);
h.push(15.1);
l.push(13.4);
c.push(15.0);
o.push(14.5);
h.push(16.1);
l.push(14.4);
c.push(16.0);
o.push(15.0);
h.push(15.1);
l.push(11.4);
c.push(11.0); let out = cdl_breakaway(&o, &h, &l, &c).unwrap();
assert_eq!(out[14], 0.0, "breakaway without gap fill should be 0");
}