stock-trek 0.8.0

Stock Trek time-series analysis
Documentation
use crate::statistics::filter;

#[derive(Clone, Default)]
pub struct Filter;

impl Filter {
    pub fn hodrick_prescott_filter(
        &self,
        time_series_values: &[f64],
        lambda: f64,
    ) -> (Vec<f64>, Vec<f64>) {
        filter::hodrick_prescott_filter(time_series_values, lambda)
    }
    pub fn wiener_filter(&self, time_series_values: &[f64], window_size: usize) -> Vec<f64> {
        filter::wiener_filter(time_series_values, window_size)
    }
}

pub fn hodrick_prescott_filter(time_series_values: &[f64], lambda: f64) -> (Vec<f64>, Vec<f64>) {
    let n = time_series_values.len();
    let mut trend = time_series_values.to_vec();
    if n < 3 {
        return (trend.clone(), vec![0.0; n]);
    }
    for _ in 0..100 {
        for t in 1..n - 1 {
            trend[t] = (time_series_values[t] + lambda * (trend[t - 1] + trend[t + 1]))
                / (1.0 + 2.0 * lambda);
        }
    }
    let cyclical: Vec<f64> = time_series_values
        .iter()
        .zip(trend.iter())
        .map(|(x, t)| x - t)
        .collect();
    (trend, cyclical)
}

pub fn wiener_filter(time_series_values: &[f64], window_size: usize) -> Vec<f64> {
    let n = time_series_values.len();
    let mut result = Vec::with_capacity(n);
    if n == 0 || window_size == 0 {
        return result;
    }
    for i in 0..n {
        let start = i.saturating_sub(window_size / 2);
        let end = usize::min(n, i + window_size / 2 + 1);
        let window = &time_series_values[start..end];
        let mean = window.iter().sum::<f64>() / window.len() as f64;
        let var = window
            .iter()
            .map(|x| {
                let d = x - mean;
                d * d
            })
            .sum::<f64>()
            / window.len() as f64;
        let noise = var;
        let signal = (var - noise).max(0.0);
        let value = mean + (signal / (signal + noise + 1e-12)) * (time_series_values[i] - mean);
        result.push(value);
    }
    result
}