quant-metrics 0.7.0

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

use super::*;
use crate::MetricsError;

#[test]
fn var_95_picks_5th_percentile() {
    // 20 sorted returns: VaR(95%) = index (5*20/100) = 1st element
    let returns: Vec<Decimal> = (0..20)
        .map(|i| Decimal::from(i) * dec!(0.01) - dec!(0.10))
        .collect();
    // returns: [-0.10, -0.09, -0.08, ..., 0.09]
    // VaR(95%) = returns[1] = -0.09 (index = (5*20)/100 = 1)
    let v = var(&returns, 95);
    assert_eq!(v, dec!(-0.09), "VaR(95%) = {v}");
}

#[test]
fn cvar_95_is_mean_below_var() {
    // 20 returns: [-0.10, -0.09, ..., 0.09]
    // VaR(95%) cutoff index = (5*20)/100 = 1, so tail = [-0.10]
    // CVaR(95%) = mean([-0.10]) = -0.10
    let returns: Vec<Decimal> = (0..20)
        .map(|i| Decimal::from(i) * dec!(0.01) - dec!(0.10))
        .collect();
    let c = cvar(&returns, 95);
    assert_eq!(c, dec!(-0.10), "CVaR(95%) = {c}");
}

#[test]
fn cvar_always_gte_var_in_magnitude() {
    // Mathematical invariant: CVaR >= VaR (more extreme)
    let returns: Vec<Decimal> = (0..100)
        .map(|i| {
            if i < 5 {
                dec!(-0.10)
            } else if i < 10 {
                dec!(-0.05)
            } else {
                Decimal::from(i) * dec!(0.001)
            }
        })
        .collect();

    let v = var(&returns, 95);
    let c = cvar(&returns, 95);
    assert!(
        c <= v,
        "CVaR({c}) should be <= VaR({v}) (more negative = more extreme)"
    );
}

#[test]
fn try_var_rejects_insufficient_data() {
    let returns = vec![dec!(0.01); 5];
    let result = try_var(&returns, 99);
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(matches!(err, MetricsError::InsufficientData { .. }));
}