pub type Bar = (f64, f64, f64, f64);
const SPLICE: usize = 1000;
const PRIME_LEN: usize = 12;
const PRIME: Bar = (100.0, 101.4, 99.6, 101.0);
const PRIME_SHIFTED: Bar = (99.6, 101.0, 99.2, 100.6);
#[inline]
fn put(o: &mut [f64], h: &mut [f64], l: &mut [f64], c: &mut [f64], i: usize, bar: Bar) {
o[i] = bar.0;
h[i] = bar.1;
l[i] = bar.2;
c[i] = bar.3;
}
fn splice_primed(
o: &mut [f64],
h: &mut [f64],
l: &mut [f64],
c: &mut [f64],
prime_cycle: &[Bar],
bars: &[Bar],
) {
if o.len() < SPLICE + PRIME_LEN + bars.len() {
return;
}
for i in 0..PRIME_LEN {
put(o, h, l, c, SPLICE + i, prime_cycle[i % prime_cycle.len()]);
}
for (j, bar) in bars.iter().enumerate() {
put(o, h, l, c, SPLICE + PRIME_LEN + j, *bar);
}
}
macro_rules! fixture {
($(#[$meta:meta])* $name:ident, [$($bar:expr),* $(,)?]) => {
fixture!($(#[$meta])* $name, prime: [PRIME], [$($bar),*]);
};
($(#[$meta:meta])* $name:ident, prime: [$($p:expr),* $(,)?], [$($bar:expr),* $(,)?]) => {
$(#[$meta])*
#[allow(clippy::ptr_arg)]
pub fn $name(
o: &mut Vec<f64>,
h: &mut Vec<f64>,
l: &mut Vec<f64>,
c: &mut Vec<f64>,
) {
splice_primed(o, h, l, c, &[$($p),*], &[$($bar),*]);
}
};
}
fixture!(
counterattack,
[
(101.0, 101.2, 96.8, 97.0), (93.0, 97.2, 92.8, 97.0), ]
);
fixture!(
darkcloudcover,
[
(97.0, 101.2, 96.8, 101.0), (101.5, 101.7, 98.3, 98.5), ]
);
fixture!(
dojistar,
[
(97.0, 101.2, 96.8, 101.0), (102.0, 102.2, 101.8, 102.0), ]
);
fixture!(
harami,
[
(97.0, 101.2, 96.8, 101.0),
(99.5, 99.9, 99.3, 99.7), ]
);
fixture!(
haramicross,
[
(97.0, 101.2, 96.8, 101.0),
(99.6, 99.8, 99.4, 99.6), ]
);
fixture!(
homingpigeon,
[
(101.0, 101.2, 96.8, 97.0),
(99.5, 99.7, 99.1, 99.3), ]
);
fixture!(
inneck,
[
(101.0, 101.2, 96.8, 97.0),
(96.5, 97.2, 96.3, 97.02), ]
);
fixture!(
invertedhammer,
[
(98.5, 99.5, 98.4, 98.7), ]
);
fixture!(
onneck,
[
(101.0, 101.2, 96.8, 97.0),
(96.5, 97.0, 96.4, 96.8), ]
);
fixture!(
kicking,
[
(101.0, 101.0, 98.0, 98.0), (102.0, 105.0, 102.0, 105.0), ]
);
fixture!(
kickingbylength,
[
(101.0, 101.0, 98.0, 98.0),
(102.0, 105.0, 102.0, 105.0),
]
);
fixture!(
piercing,
[
(101.0, 101.2, 96.8, 97.0),
(96.5, 99.7, 96.3, 99.5), ]
);
fixture!(
shootingstar,
[
(101.5, 102.5, 101.45, 101.7), ]
);
fixture!(
thrusting,
[
(101.0, 101.2, 96.8, 97.0),
(96.5, 98.7, 96.3, 98.5), ]
);
fixture!(
two_crows,
[
(97.0, 101.2, 96.8, 101.0), (103.0, 103.2, 101.8, 102.0), (102.5, 102.7, 98.8, 99.0), ]
);
fixture!(
three_black_crows,
[
(101.0, 101.2, 99.0, 99.0),
(100.0, 100.2, 97.5, 97.5),
(99.0, 99.2, 96.0, 96.0),
]
);
fixture!(
three_inside,
[
(97.0, 101.2, 96.8, 101.0),
(99.0, 99.5, 98.8, 99.3), (99.0, 99.2, 95.8, 96.0), ]
);
fixture!(
three_stars_in_south,
[
(101.0, 101.1, 96.0, 99.0), (100.5, 100.6, 95.5, 99.5), (100.55, 100.58, 100.3, 100.4), PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
(101.0, 101.1, 96.0, 99.0), (100.5, 100.6, 95.5, 99.5), (100.3, 100.58, 99.6, 99.6), ]
);
fixture!(
abandonedbaby,
[
(101.0, 101.2, 96.8, 97.0),
(96.0, 96.1, 95.9, 96.0), (96.6, 99.2, 96.5, 99.0), ]
);
fixture!(
eveningdojistar,
[
(97.0, 101.2, 96.8, 101.0),
(102.0, 102.2, 101.8, 102.0),
(101.5, 101.7, 98.8, 99.0),
]
);
fixture!(
eveningstar,
[
(97.0, 101.2, 96.8, 101.0),
(102.0, 102.5, 101.8, 102.3),
(101.5, 101.7, 98.8, 99.0),
]
);
fixture!(
morningdojistar,
[
(101.0, 101.2, 96.8, 97.0),
(96.0, 96.2, 95.8, 96.0),
(96.5, 99.2, 96.3, 99.0),
]
);
fixture!(
morningstar,
[
(101.0, 101.2, 96.8, 97.0),
(96.0, 96.2, 95.5, 95.7),
(96.5, 99.2, 96.3, 99.0),
]
);
fixture!(
gapsidesidewhite,
prime: [PRIME, PRIME_SHIFTED],
[
(98.0, 99.2, 97.8, 99.0), (98.05, 99.25, 97.85, 99.05), PRIME,
PRIME_SHIFTED,
PRIME,
PRIME_SHIFTED,
PRIME,
(102.0, 103.2, 101.8, 103.0), (102.5, 103.7, 102.3, 103.5), PRIME,
PRIME_SHIFTED,
PRIME,
PRIME_SHIFTED,
PRIME,
(102.0, 103.2, 101.8, 103.0), (102.05, 103.3, 101.95, 103.1), ]
);
fixture!(
tasukigap,
[
PRIME,
(102.0, 103.2, 101.8, 103.0),
(102.7, 102.9, 101.6, 101.8),
]
);
fixture!(
tristar,
[
(100.0, 100.2, 99.8, 100.0),
(101.0, 101.2, 100.8, 101.0),
(101.0, 101.2, 100.8, 101.0),
]
);
fixture!(
unique3river,
[
(101.0, 101.2, 96.8, 97.0),
(100.0, 100.2, 96.5, 98.0),
(97.5, 98.0, 97.3, 97.8),
]
);
fixture!(
upsidegap2crows,
[
(97.0, 101.2, 96.8, 101.0),
(102.5, 102.7, 101.8, 102.0),
(103.0, 103.2, 101.3, 101.5),
]
);
fixture!(
xsidegap3methods,
[
(99.0, 101.2, 98.8, 101.0),
(102.0, 103.2, 101.8, 103.0),
(102.5, 102.7, 99.8, 100.0),
]
);
fixture!(
sticksandwich,
[
(101.0, 101.2, 98.8, 99.0),
(99.5, 100.7, 99.3, 100.5),
(100.0, 100.2, 98.8, 99.0),
]
);
fixture!(
concealbabyswall,
[
(101.0, 101.0, 100.0, 100.0), (100.5, 100.5, 99.8, 99.8), (99.5, 100.0, 98.6, 99.0), (100.2, 100.3, 98.3, 98.4), PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
(101.0, 101.05, 99.95, 100.0), (100.5, 100.55, 99.75, 99.8), (99.5, 100.0, 98.95, 99.0), (100.2, 100.3, 98.8, 98.9), ]
);
fixture!(
breakaway,
[
(101.0, 101.2, 96.8, 97.0),
(96.0, 96.2, 95.3, 95.5),
(95.4, 95.5, 94.8, 95.0),
(94.9, 95.0, 94.3, 94.5),
(96.2, 96.5, 96.1, 96.4), PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
PRIME,
(101.0, 101.2, 96.8, 97.0),
(96.0, 96.2, 95.3, 95.5),
(95.4, 96.5, 94.8, 95.0), (94.9, 95.0, 94.3, 94.5),
(94.6, 96.7, 94.4, 96.5),
]
);
fixture!(
ladderbottom,
[
(101.0, 101.2, 99.8, 100.0),
(100.0, 100.2, 98.8, 99.0),
(99.0, 99.2, 97.8, 98.0),
(98.0, 99.0, 97.3, 97.5), (98.5, 100.2, 98.3, 100.0),
]
);
fixture!(
mathold,
[
(97.0, 101.2, 96.8, 101.0),
(102.0, 102.2, 101.3, 101.5),
(101.0, 101.2, 100.3, 100.5),
(100.5, 100.7, 99.8, 100.0),
(100.2, 103.2, 100.0, 103.0),
]
);
fixture!(
risefall3methods,
[
(97.0, 101.5, 96.5, 101.0),
(100.8, 101.0, 100.3, 100.5),
(100.5, 100.7, 100.0, 100.2),
(100.2, 100.4, 99.7, 99.9),
(100.0, 102.7, 99.8, 102.5),
]
);
fixture!(
hikkakemod,
[
(100.0, 105.0, 95.0, 100.5), (102.0, 103.0, 97.0, 97.2), (99.0, 102.0, 98.0, 101.0), (100.0, 101.0, 97.5, 99.0), (102.0, 103.2, 101.8, 103.0), ]
);
#[cfg(test)]
mod self_check {
use super::*;
type OracleFn = fn(&[f64], &[f64], &[f64], &[f64]) -> Result<Vec<i32>, talib_rs::TaError>;
type FixtureFn = fn(&mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>, &mut Vec<f64>);
fn walk(n: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
let (mut o, mut h, mut l, mut c) = (vec![], vec![], vec![], vec![]);
let mut rng = 1337_u32;
let mut price = 50.0;
for _ in 0..n {
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let drift = ((rng >> 16) as f64 / 65536.0) * 4.0 - 2.0;
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let spread = ((rng >> 16) as f64 / 65536.0) * 5.0;
let (open, close) = (price, price + drift);
o.push(open);
h.push(open.max(close) + spread);
l.push(open.min(close) - spread);
c.push(close);
price = close;
}
(o, h, l, c)
}
#[test]
fn every_fixture_makes_the_oracle_fire() {
let cases: &[(&str, FixtureFn, OracleFn)] = &[
(
"counterattack",
counterattack,
talib_rs::pattern::cdl_counterattack,
),
(
"darkcloudcover",
darkcloudcover,
talib_rs::pattern::cdl_darkcloudcover,
),
("dojistar", dojistar, talib_rs::pattern::cdl_dojistar),
("harami", harami, talib_rs::pattern::cdl_harami),
(
"haramicross",
haramicross,
talib_rs::pattern::cdl_haramicross,
),
(
"homingpigeon",
homingpigeon,
talib_rs::pattern::cdl_homingpigeon,
),
("inneck", inneck, talib_rs::pattern::cdl_inneck),
(
"invertedhammer",
invertedhammer,
talib_rs::pattern::cdl_invertedhammer,
),
("onneck", onneck, talib_rs::pattern::cdl_onneck),
("kicking", kicking, talib_rs::pattern::cdl_kicking),
(
"kickingbylength",
kickingbylength,
talib_rs::pattern::cdl_kickingbylength,
),
("piercing", piercing, talib_rs::pattern::cdl_piercing),
(
"shootingstar",
shootingstar,
talib_rs::pattern::cdl_shootingstar,
),
("thrusting", thrusting, talib_rs::pattern::cdl_thrusting),
("two_crows", two_crows, talib_rs::pattern::cdl_2crows),
(
"three_black_crows",
three_black_crows,
talib_rs::pattern::cdl_3blackcrows,
),
("three_inside", three_inside, talib_rs::pattern::cdl_3inside),
(
"three_stars_in_south",
three_stars_in_south,
talib_rs::pattern::cdl_3starsinsouth,
),
(
"abandonedbaby",
abandonedbaby,
talib_rs::pattern::cdl_abandonedbaby,
),
(
"eveningdojistar",
eveningdojistar,
talib_rs::pattern::cdl_eveningdojistar,
),
(
"eveningstar",
eveningstar,
talib_rs::pattern::cdl_eveningstar,
),
(
"morningdojistar",
morningdojistar,
talib_rs::pattern::cdl_morningdojistar,
),
(
"morningstar",
morningstar,
talib_rs::pattern::cdl_morningstar,
),
(
"gapsidesidewhite",
gapsidesidewhite,
talib_rs::pattern::cdl_gapsidesidewhite,
),
("tasukigap", tasukigap, talib_rs::pattern::cdl_tasukigap),
("tristar", tristar, talib_rs::pattern::cdl_tristar),
(
"unique3river",
unique3river,
talib_rs::pattern::cdl_unique3river,
),
(
"upsidegap2crows",
upsidegap2crows,
talib_rs::pattern::cdl_upsidegap2crows,
),
(
"xsidegap3methods",
xsidegap3methods,
talib_rs::pattern::cdl_xsidegap3methods,
),
(
"sticksandwich",
sticksandwich,
talib_rs::pattern::cdl_sticksandwich,
),
(
"concealbabyswall",
concealbabyswall,
talib_rs::pattern::cdl_concealbabyswall,
),
("breakaway", breakaway, talib_rs::pattern::cdl_breakaway),
(
"ladderbottom",
ladderbottom,
talib_rs::pattern::cdl_ladderbottom,
),
("mathold", mathold, talib_rs::pattern::cdl_mathold),
(
"risefall3methods",
risefall3methods,
talib_rs::pattern::cdl_risefall3methods,
),
("hikkakemod", hikkakemod, talib_rs::pattern::cdl_hikkakemod),
];
let mut dead = vec![];
for (name, fixture, oracle) in cases {
let (mut o, mut h, mut l, mut c) = walk(1200);
fixture(&mut o, &mut h, &mut l, &mut c);
let out = oracle(&o, &h, &l, &c).expect("oracle failed");
let hits: Vec<(usize, i32)> = out
.iter()
.enumerate()
.filter(|(i, v)| **v != 0 && (995..1035).contains(i))
.map(|(i, v)| (i, *v))
.collect();
if hits.is_empty() {
dead.push(*name);
} else {
println!("{name}: {hits:?}");
}
}
assert!(dead.is_empty(), "fixtures that never fired: {dead:?}");
}
}