use crate::error::StatsResult;
use crate::stationarity::{
AdfRegression, AdfResult as StationarityAdfResult, KpssTrend,
PhillipsPerronResult, ZivotAndrewsBreak, ZivotAndrewsResult,
adf_test as stationarity_adf_test,
kpss_test as stationarity_kpss_test,
phillips_perron_test,
zivot_andrews_test as stationarity_za_test,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdfTrend {
None,
Constant,
ConstantTrend,
}
#[derive(Debug, Clone)]
pub struct AdfResult {
pub statistic: f64,
pub p_value: f64,
pub critical_values: HashMap<String, f64>,
pub lags: usize,
}
pub fn adf_test(x: &[f64], lags: Option<usize>, trend: AdfTrend) -> StatsResult<AdfResult> {
let regression = match trend {
AdfTrend::None => AdfRegression::None,
AdfTrend::Constant => AdfRegression::Constant,
AdfTrend::ConstantTrend => AdfRegression::ConstantTrend,
};
let inner: StationarityAdfResult = stationarity_adf_test(x, lags, regression)?;
let mut critical_values = HashMap::new();
critical_values.insert("1%".to_string(), inner.critical_values.one_pct);
critical_values.insert("5%".to_string(), inner.critical_values.five_pct);
critical_values.insert("10%".to_string(), inner.critical_values.ten_pct);
Ok(AdfResult {
statistic: inner.statistic,
p_value: inner.p_value,
critical_values,
lags: inner.used_lags,
})
}
pub fn kpss_test(x: &[f64], lags: Option<usize>, trend: bool) -> StatsResult<(f64, f64)> {
let kpss_trend = if trend {
KpssTrend::Trend
} else {
KpssTrend::Level
};
let result = stationarity_kpss_test(x, lags, kpss_trend)?;
Ok((result.statistic, result.p_value))
}
pub fn pp_test(x: &[f64], lags: Option<usize>) -> StatsResult<(f64, f64)> {
let result: PhillipsPerronResult = phillips_perron_test(x, lags)?;
Ok((result.z_tau, result.p_value))
}
pub fn za_test(x: &[f64]) -> StatsResult<(f64, f64, usize)> {
let result: ZivotAndrewsResult =
stationarity_za_test(x, None, ZivotAndrewsBreak::Both)?;
Ok((result.statistic, result.p_value, result.break_point))
}
#[cfg(test)]
mod tests {
use super::*;
fn random_walk(n: usize) -> Vec<f64> {
let mut x = vec![0.0f64; n];
let mut s: u64 = 99999;
for i in 1..n {
s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
let e = (s >> 11) as f64 / (1u64 << 53) as f64 - 0.5;
x[i] = x[i - 1] + e;
}
x
}
fn stationary_ar1(n: usize, phi: f64) -> Vec<f64> {
let mut x = vec![0.0f64; n];
let mut s: u64 = 54321;
for i in 1..n {
s = s.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
let e = (s >> 11) as f64 / (1u64 << 53) as f64 - 0.5;
x[i] = phi * x[i - 1] + e;
}
x
}
#[test]
fn test_adf_result_has_critical_values() {
let x = random_walk(80);
let result = adf_test(&x, None, AdfTrend::Constant).unwrap();
assert!(result.critical_values.contains_key("1%"));
assert!(result.critical_values.contains_key("5%"));
assert!(result.critical_values.contains_key("10%"));
}
#[test]
fn test_adf_pvalue_in_range() {
let x = random_walk(80);
let result = adf_test(&x, None, AdfTrend::Constant).unwrap();
assert!(result.p_value >= 0.0 && result.p_value <= 1.0);
}
#[test]
fn test_adf_stationary_low_stat() {
let x = stationary_ar1(100, 0.3);
let result = adf_test(&x, None, AdfTrend::Constant).unwrap();
assert!(result.statistic < 0.0);
}
#[test]
fn test_adf_trend_variants() {
let x = stationary_ar1(60, 0.5);
let _ = adf_test(&x, Some(1), AdfTrend::None).unwrap();
let _ = adf_test(&x, Some(1), AdfTrend::Constant).unwrap();
let _ = adf_test(&x, Some(1), AdfTrend::ConstantTrend).unwrap();
}
#[test]
fn test_kpss_pvalue_range() {
let x = stationary_ar1(80, 0.4);
let (stat, pval) = kpss_test(&x, None, false).unwrap();
assert!(stat >= 0.0);
assert!(pval >= 0.0 && pval <= 1.0);
}
#[test]
fn test_pp_pvalue_range() {
let x = random_walk(80);
let (stat, pval) = pp_test(&x, None).unwrap();
let _ = stat;
assert!(pval >= 0.0 && pval <= 1.0);
}
#[test]
fn test_za_returns_break_index() {
let x = random_walk(60);
let (stat, pval, bp) = za_test(&x).unwrap();
assert!(bp > 0 && bp < x.len());
let _ = stat;
let _ = pval;
}
}