#[path = "common/mod.rs"]
mod common;
use adaq_talib::pattern::{
cdl_3blackcrows, cdl_3inside, cdl_3linestrike, cdl_3outside, cdl_3starsinsouth,
cdl_3whitesoldiers, cdl_abandonedbaby, cdl_advanceblock,
};
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_3blackcrows_matches_golden_vector() {
check_cdl("3blackcrows", cdl_3blackcrows);
}
#[test]
fn cdl_3inside_matches_golden_vector() {
check_cdl("3inside", cdl_3inside);
}
#[test]
fn cdl_3linestrike_matches_golden_vector() {
check_cdl("3linestrike", cdl_3linestrike);
}
#[test]
fn cdl_3outside_matches_golden_vector() {
check_cdl("3outside", cdl_3outside);
}
#[test]
fn cdl_3starsinsouth_matches_golden_vector() {
check_cdl("3starsinsouth", cdl_3starsinsouth);
}
#[test]
fn cdl_3whitesoldiers_matches_golden_vector() {
check_cdl("3whitesoldiers", cdl_3whitesoldiers);
}
#[test]
fn cdl_abandonedbaby_matches_golden_vector() {
check_cdl("abandonedbaby", cdl_abandonedbaby);
}
#[test]
fn cdl_advanceblock_matches_golden_vector() {
check_cdl("advanceblock", cdl_advanceblock);
}
fn baseline(n: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
let mut o = Vec::with_capacity(n);
let mut h = Vec::with_capacity(n);
let mut l = Vec::with_capacity(n);
let mut c = Vec::with_capacity(n);
for _ in 0..n {
o.push(100.0);
h.push(103.0);
l.push(98.0);
c.push(101.0);
}
(o, h, l, c)
}
#[test]
fn cdl_3blackcrows_triggers_and_rejects() {
let (mut o, mut h, mut l, mut c) = baseline(20);
o.extend_from_slice(&[100.0, 99.8, 99.5]);
h.extend_from_slice(&[100.5, 99.9, 99.6]);
l.extend_from_slice(&[99.4, 98.9, 98.4]);
c.extend_from_slice(&[99.5, 99.0, 98.5]);
let out = cdl_3blackcrows(&o, &h, &l, &c).unwrap();
assert_eq!(out[22], -100.0, "three black crows should trigger at i=22");
let (mut o2, mut h2, mut l2, mut c2) = baseline(20);
o2.extend_from_slice(&[100.0, 99.8, 99.9]);
h2.extend_from_slice(&[100.5, 99.9, 99.6]);
l2.extend_from_slice(&[99.4, 98.9, 98.4]);
c2.extend_from_slice(&[99.5, 99.0, 98.5]);
let out2 = cdl_3blackcrows(&o2, &h2, &l2, &c2).unwrap();
assert_eq!(out2[22], 0.0, "should not trigger when open[i] not < open[i-1]");
}
#[test]
fn cdl_3inside_triggers_and_rejects() {
let (mut o, mut h, mut l, mut c) = baseline(13);
o.extend_from_slice(&[101.0, 99.0, 100.0]);
h.extend_from_slice(&[101.5, 100.0, 103.0]);
l.extend_from_slice(&[94.5, 98.0, 99.0]);
c.extend_from_slice(&[95.0, 99.5, 102.0]);
let out = cdl_3inside(&o, &h, &l, &c).unwrap();
assert_eq!(out[15], 100.0, "three inside up (1st black) should be +100");
let (mut o2, mut h2, mut l2, mut c2) = baseline(13);
o2.extend_from_slice(&[101.0, 99.0, 100.0]);
h2.extend_from_slice(&[101.5, 100.0, 103.0]);
l2.extend_from_slice(&[94.5, 98.0, 99.0]);
c2.extend_from_slice(&[95.0, 99.5, 100.0]);
let out2 = cdl_3inside(&o2, &h2, &l2, &c2).unwrap();
assert_eq!(out2[15], 0.0, "should not trigger without 3rd closing out");
}
#[test]
fn cdl_3linestrike_triggers_and_rejects() {
let (mut o, mut h, mut l, mut c) = baseline(11);
o.extend_from_slice(&[100.0, 100.5, 101.5, 104.0]);
h.extend_from_slice(&[103.0, 104.0, 105.0, 104.5]);
l.extend_from_slice(&[99.0, 99.5, 100.5, 98.5]);
c.extend_from_slice(&[101.0, 102.0, 103.0, 99.0]);
let out = cdl_3linestrike(&o, &h, &l, &c).unwrap();
assert_eq!(out[14], 100.0, "bullish 3-line strike should be +100");
let (mut o2, mut h2, mut l2, mut c2) = baseline(11);
o2.extend_from_slice(&[100.0, 100.5, 101.5, 104.0]);
h2.extend_from_slice(&[103.0, 104.0, 105.0, 104.5]);
l2.extend_from_slice(&[99.0, 99.5, 100.5, 98.5]);
c2.extend_from_slice(&[101.0, 102.0, 103.0, 101.0]);
let out2 = cdl_3linestrike(&o2, &h2, &l2, &c2).unwrap();
assert_eq!(out2[14], 0.0, "should not trigger when 4th closes inside range");
}
#[test]
fn cdl_3outside_triggers_and_rejects() {
let o = [50.0, 100.0, 90.0, 99.5];
let h = [51.0, 100.5, 101.0, 102.0];
let l = [49.0, 90.0, 89.0, 99.0];
let c = [50.5, 91.0, 101.0, 102.0];
let out = cdl_3outside(&o, &h, &l, &c).unwrap();
assert_eq!(out[3], 100.0, "three outside up should be +100");
let c2 = [50.5, 91.0, 101.0, 100.0];
let out2 = cdl_3outside(&o, &h, &l, &c2).unwrap();
assert_eq!(out2[3], 0.0, "should not trigger without 3rd higher close");
}
#[test]
fn cdl_3starsinsouth_triggers_and_rejects() {
let (mut o, mut h, mut l, mut c) = baseline(13);
o.extend_from_slice(&[100.0, 98.0, 97.0]);
h.extend_from_slice(&[100.5, 99.0, 97.4]);
l.extend_from_slice(&[89.0, 93.0, 96.0]);
c.extend_from_slice(&[95.0, 94.0, 96.5]);
let out = cdl_3starsinsouth(&o, &h, &l, &c).unwrap();
assert_eq!(out[15], 100.0, "three stars in the south should be +100");
let (mut o2, mut h2, mut l2, mut c2) = baseline(13);
o2.extend_from_slice(&[100.0, 98.0, 97.0]);
h2.extend_from_slice(&[100.5, 99.0, 100.0]);
l2.extend_from_slice(&[89.0, 93.0, 96.0]);
c2.extend_from_slice(&[95.0, 94.0, 96.5]);
let out2 = cdl_3starsinsouth(&o2, &h2, &l2, &c2).unwrap();
assert_eq!(out2[15], 0.0, "should not trigger with a long upper shadow on 3rd");
}
#[test]
fn cdl_3whitesoldiers_triggers_and_rejects() {
let (mut o, mut h, mut l, mut c) = baseline(13);
o.extend_from_slice(&[100.0, 102.0, 104.0]);
h.extend_from_slice(&[103.2, 105.2, 106.2]);
l.extend_from_slice(&[99.0, 101.0, 103.0]);
c.extend_from_slice(&[103.0, 105.0, 106.0]);
let out = cdl_3whitesoldiers(&o, &h, &l, &c).unwrap();
assert_eq!(out[15], 100.0, "three white soldiers should be +100");
let (mut o2, mut h2, mut l2, mut c2) = baseline(13);
o2.extend_from_slice(&[100.0, 102.0, 104.0]);
h2.extend_from_slice(&[103.2, 108.0, 106.2]);
l2.extend_from_slice(&[99.0, 101.0, 103.0]);
c2.extend_from_slice(&[103.0, 105.0, 106.0]);
let out2 = cdl_3whitesoldiers(&o2, &h2, &l2, &c2).unwrap();
assert_eq!(out2[15], 0.0, "should not trigger with a long upper shadow");
}
#[test]
fn cdl_abandonedbaby_triggers_and_rejects() {
let (mut o, mut h, mut l, mut c) = baseline(13);
o.extend_from_slice(&[100.0, 107.1, 106.0]);
h.extend_from_slice(&[106.0, 107.5, 105.5]);
l.extend_from_slice(&[99.0, 106.1, 102.0]);
c.extend_from_slice(&[105.0, 107.0, 102.5]);
let out = cdl_abandonedbaby(&o, &h, &l, &c).unwrap();
assert_eq!(out[15], -100.0, "abandoned baby top (bearish) should be -100");
let (mut o2, mut h2, mut l2, mut c2) = baseline(13);
o2.extend_from_slice(&[100.0, 107.1, 106.0]);
h2.extend_from_slice(&[106.0, 107.5, 105.5]);
l2.extend_from_slice(&[99.0, 105.5, 102.0]);
c2.extend_from_slice(&[105.0, 107.0, 102.5]);
let out2 = cdl_abandonedbaby(&o2, &h2, &l2, &c2).unwrap();
assert_eq!(out2[15], 0.0, "should not trigger without a gap up to the doji");
}
#[test]
fn cdl_advanceblock_triggers_and_rejects() {
let (mut o, mut h, mut l, mut c) = baseline(13);
o.extend_from_slice(&[100.0, 104.0, 106.5]);
h.extend_from_slice(&[106.0, 108.0, 112.5]);
l.extend_from_slice(&[99.0, 103.0, 105.0]);
c.extend_from_slice(&[105.0, 107.0, 107.5]);
let out = cdl_advanceblock(&o, &h, &l, &c).unwrap();
assert_eq!(out[15], -100.0, "advance block with 3rd long upper shadow should be -100");
let (mut o2, mut h2, mut l2, mut c2) = baseline(13);
o2.extend_from_slice(&[100.0, 104.0, 105.0]);
h2.extend_from_slice(&[106.0, 108.0, 109.5]);
l2.extend_from_slice(&[99.0, 103.0, 104.0]);
c2.extend_from_slice(&[105.0, 107.0, 109.0]);
let out2 = cdl_advanceblock(&o2, &h2, &l2, &c2).unwrap();
assert_eq!(out2[15], 0.0, "should not trigger for healthy advancing soldiers");
}