quant-metrics 0.7.0

Pure performance statistics library for trading — Sharpe, Sortino, drawdown, VaR, portfolio composition
Documentation
use super::*;
use rust_decimal_macros::dec;

#[test]
fn rolling_returns_simple() {
    let equity = vec![dec!(100), dec!(110), dec!(121), dec!(133.1)];
    let rolling = RollingWindow::new(2);

    let returns = rolling
        .returns(&equity)
        .expect("rolling returns should succeed with sufficient data");

    assert_eq!(returns.len(), 3);
    assert_eq!(returns[0], dec!(10)); // 100 -> 110 = 10%
    assert_eq!(returns[1], dec!(10)); // 110 -> 121 = 10%
    assert_eq!(returns[2], dec!(10)); // 121 -> 133.1 = 10%
}

#[test]
fn rolling_returns_window_3() {
    let equity = vec![dec!(100), dec!(105), dec!(110), dec!(115)];
    let rolling = RollingWindow::new(3);

    let returns = rolling
        .returns(&equity)
        .expect("rolling returns should succeed with window size 3");

    assert_eq!(returns.len(), 2);
    assert_eq!(returns[0], dec!(10)); // 100 -> 110 = 10%
                                      // 105 -> 115 = 9.52...%
    assert!((returns[1] - dec!(9.52)).abs() < dec!(0.1));
}

#[test]
fn rolling_volatility() {
    // Consistent returns = low volatility
    let stable = vec![dec!(100), dec!(101), dec!(102), dec!(103), dec!(104)];
    // Erratic returns = high volatility
    let erratic = vec![dec!(100), dec!(110), dec!(95), dec!(115), dec!(100)];

    let rolling = RollingWindow::new(3);

    let vol_stable = rolling
        .volatility(&stable)
        .expect("rolling volatility should succeed for stable equity");
    let vol_erratic = rolling
        .volatility(&erratic)
        .expect("rolling volatility should succeed for erratic equity");

    // Erratic should have higher volatility
    let max_stable: Decimal = vol_stable.iter().max().copied().unwrap_or(Decimal::ZERO);
    let max_erratic: Decimal = vol_erratic.iter().max().copied().unwrap_or(Decimal::ZERO);

    assert!(max_erratic > max_stable);
}

#[test]
fn rolling_sharpe() {
    let equity = vec![
        dec!(100),
        dec!(101),
        dec!(102),
        dec!(103),
        dec!(104),
        dec!(105),
    ];
    let rolling = RollingWindow::new(3);

    let sharpe = rolling
        .sharpe(&equity, dec!(0.02), 252)
        .expect("rolling sharpe should succeed with positive returns");

    // All positive returns should give positive Sharpe
    for s in &sharpe {
        assert!(*s > dec!(0));
    }
}

#[test]
fn rolling_max_drawdown() {
    let equity = vec![dec!(100), dec!(110), dec!(90), dec!(100), dec!(105)];
    let rolling = RollingWindow::new(3);

    let dd = rolling
        .max_drawdown(&equity)
        .expect("rolling max_drawdown should succeed with drawdown data");

    // Window [100, 110, 90]: DD from 110 to 90 = -18.18%
    assert!(dd[0] < dec!(-18));
    // Window [110, 90, 100]: DD from 110 to 90 = -18.18%
    assert!(dd[1] < dec!(-18));
}

#[test]
fn rolling_insufficient_data() {
    let equity = vec![dec!(100), dec!(110)];
    let rolling = RollingWindow::new(3);

    assert!(rolling.returns(&equity).is_err());
    assert!(rolling.volatility(&equity).is_err());
}