use wickra_embed_core::{Atr, Candle, Ema, Indicator, Roc, Rsi, Sma};
const N: usize = 500;
fn price(i: usize) -> f64 {
100.0 + 12.0 * (i as f64 / 5.0).sin() + 0.03 * i as f64
}
fn candle(i: usize) -> (f64, f64, f64, f64) {
let open = price(i);
let close = price(i + 1);
(open, open.max(close) + 1.0, open.min(close) - 1.0, close)
}
fn assert_scalar_parity<E, W>(label: &str, mut ours: E, mut theirs: W)
where
E: FnMut(f64) -> Option<f64>,
W: FnMut(f64) -> Option<f64>,
{
for i in 0..N {
let x = price(i);
assert_eq!(
ours(x).map(f64::to_bits),
theirs(x).map(f64::to_bits),
"{label}: index {i} differs"
);
}
}
#[test]
fn sma_matches_live_wickra_core() {
let mut e = Sma::<20>::new();
let mut w = wickra_core::Sma::new(20).unwrap();
assert_scalar_parity(
"sma20",
|x| e.update(x),
|x| <wickra_core::Sma as wickra_core::Indicator>::update(&mut w, x),
);
}
#[test]
fn ema_matches_live_wickra_core() {
let mut e = Ema::new(20);
let mut w = wickra_core::Ema::new(20).unwrap();
assert_scalar_parity(
"ema20",
|x| e.update(x),
|x| <wickra_core::Ema as wickra_core::Indicator>::update(&mut w, x),
);
}
#[test]
fn rsi_matches_live_wickra_core() {
let mut e = Rsi::<14>::new();
let mut w = wickra_core::Rsi::new(14).unwrap();
assert_scalar_parity(
"rsi14",
|x| e.update(x),
|x| <wickra_core::Rsi as wickra_core::Indicator>::update(&mut w, x),
);
}
#[test]
fn roc_matches_live_wickra_core() {
let mut e = Roc::<10>::new();
let mut w = wickra_core::Roc::new(10).unwrap();
assert_scalar_parity(
"roc10",
|x| e.update(x),
|x| <wickra_core::Roc as wickra_core::Indicator>::update(&mut w, x),
);
}
#[test]
fn atr_matches_live_wickra_core() {
let mut e = Atr::<14>::new();
let mut w = wickra_core::Atr::new(14).unwrap();
for i in 0..N {
let (open, high, low, close) = candle(i);
let ec = Candle::new(open, high, low, close, 1000.0, i64::try_from(i).unwrap());
let wc =
wickra_core::Candle::new(open, high, low, close, 1000.0, i64::try_from(i).unwrap())
.unwrap();
let a = e.update(ec);
let b = <wickra_core::Atr as wickra_core::Indicator>::update(&mut w, wc);
assert_eq!(a.map(f64::to_bits), b.map(f64::to_bits), "atr14: index {i}");
}
}
fn math_probe_values() -> Vec<f64> {
let mut v = vec![
0.0,
-0.0,
1.0,
-1.0,
0.5,
-0.5,
f64::MIN_POSITIVE,
1e-300,
1e300,
core::f64::consts::PI,
-core::f64::consts::E,
];
for i in 0..N {
v.push(price(i));
v.push(-price(i));
}
v
}
#[test]
fn libm_abs_equals_std_abs() {
for &x in &math_probe_values() {
assert_eq!(
f64::abs(x).to_bits(),
libm::fabs(x).to_bits(),
"abs({x}) diverges"
);
}
}
#[test]
fn libm_max_equals_std_max() {
let is_zero = |v: f64| (v.to_bits() << 1) == 0;
let vals = math_probe_values();
for &a in &vals {
for &b in &[1.0, -1.0, 0.0, a * 0.5, 100.0] {
let std_max = f64::max(a, b);
let libm_max = libm::fmax(a, b);
if is_zero(std_max) {
assert!(is_zero(libm_max), "max({a}, {b}) not both zero");
} else {
assert_eq!(
std_max.to_bits(),
libm_max.to_bits(),
"max({a}, {b}) diverges"
);
}
}
}
}
#[test]
fn libm_fma_equals_std_mul_add() {
let vals = math_probe_values();
for &a in &vals {
let (b, c) = (0.9, a * 0.1);
assert_eq!(
f64::mul_add(a, b, c).to_bits(),
libm::fma(a, b, c).to_bits(),
"mul_add({a}, {b}, {c}) diverges"
);
}
}