use super::*;
fn random_walk(n: usize, start: f64, drift: f64, noise: f64, seed: u64) -> Vec<f64> {
let mut prices = Vec::with_capacity(n);
let mut price = start;
let mut state = seed;
for _ in 0..n {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
let u = (state >> 33) as f64 / (1u64 << 31) as f64; let normal_approx = (u - 0.5) * 2.0 * noise;
price += drift + normal_approx;
prices.push(price);
}
prices
}
fn cointegrated_pair(x: &[f64], beta: f64, alpha: f64, spread_noise: f64, seed: u64) -> Vec<f64> {
let mut y = Vec::with_capacity(x.len());
let mut spread = 0.0;
let mut state = seed;
let mean_reversion_speed = 0.1;
for xi in x {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
let u = (state >> 33) as f64 / (1u64 << 31) as f64;
let shock = (u - 0.5) * 2.0 * spread_noise;
spread = spread * (1.0 - mean_reversion_speed) + shock;
y.push(alpha + beta * xi + spread);
}
y
}
#[test]
fn cointegrated_pair_has_low_p_value() {
let x = random_walk(500, 100.0, 0.01, 1.0, 42);
let y = cointegrated_pair(&x, 1.5, 10.0, 0.5, 99);
let result = engle_granger(&x, &y).expect("should succeed");
assert!(
result.p_value < 0.05,
"cointegrated pair should have p < 0.05, got {}",
result.p_value
);
assert!(
result.half_life.is_some(),
"cointegrated pair should have a finite half-life"
);
let hl = result
.half_life
.expect("cointegrated pair must have a finite half-life");
assert!(
hl > 0.0 && hl < 100.0,
"half-life should be reasonable, got {}",
hl
);
assert!(
result.correlation.abs() > 0.5,
"cointegrated pair should have high correlation, got {}",
result.correlation
);
}
#[test]
fn independent_series_have_high_p_value() {
let x = random_walk(500, 100.0, 0.02, 1.0, 1);
let y = random_walk(500, 200.0, -0.01, 2.0, 99999);
let result = engle_granger(&x, &y).expect("should succeed");
assert!(
result.p_value > 0.10,
"independent series should have p > 0.10, got {}",
result.p_value
);
}
#[test]
fn insufficient_data_returns_error() {
let x = vec![1.0, 2.0, 3.0];
let y = vec![2.0, 4.0, 6.0];
let result = engle_granger(&x, &y);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CointegrationMathError::InsufficientData { .. }
));
}
#[test]
fn length_mismatch_returns_error() {
let x = vec![1.0; 50];
let y = vec![1.0; 40];
let result = engle_granger(&x, &y);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CointegrationMathError::LengthMismatch { .. }
));
}
#[test]
fn ols_regression_recovers_known_relationship() {
let x: Vec<f64> = (0..100).map(|i| i as f64).collect();
let y: Vec<f64> = x.iter().map(|xi| 5.0 + 2.0 * xi).collect();
let (alpha, beta) = ols_regression(&x, &y).expect("should succeed");
assert!(
(alpha - 5.0).abs() < 0.01,
"alpha should be ~5.0, got {}",
alpha
);
assert!(
(beta - 2.0).abs() < 0.01,
"beta should be ~2.0, got {}",
beta
);
}
#[test]
fn perfect_correlation_returns_one() {
let x: Vec<f64> = (0..100).map(|i| i as f64).collect();
let y: Vec<f64> = x.iter().map(|xi| 3.0 * xi + 10.0).collect();
let corr = pearson_correlation(&x, &y).expect("should succeed");
assert!(
(corr - 1.0).abs() < 0.001,
"perfect linear relationship should have corr ≈ 1.0, got {}",
corr
);
}
#[test]
fn spread_stats_to_decimal_converts() {
let (m, s) = spread_stats_to_decimal(1.5, 0.25);
assert!(m > Decimal::ZERO);
assert!(s > Decimal::ZERO);
}