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 total_return_positive() {
    let equity = vec![dec!(10000), dec!(10500)];
    assert_eq!(
        total_return(&equity).expect("positive total return"),
        dec!(5)
    );
}

#[test]
fn total_return_negative() {
    let equity = vec![dec!(10000), dec!(9000)];
    assert_eq!(
        total_return(&equity).expect("negative total return"),
        dec!(-10)
    );
}

#[test]
fn total_return_multi_period() {
    let equity = vec![dec!(10000), dec!(10500), dec!(10200), dec!(11000)];
    assert_eq!(
        total_return(&equity).expect("multi-period total return"),
        dec!(10)
    );
}

#[test]
fn total_return_insufficient_data() {
    let equity = vec![dec!(10000)];
    assert_eq!(
        total_return(&equity),
        Err(MetricsError::InsufficientData {
            required: 2,
            actual: 1
        })
    );
}

#[test]
fn total_return_zero_start() {
    let equity = vec![dec!(0), dec!(10000)];
    assert_eq!(
        total_return(&equity),
        Err(MetricsError::DivisionByZero {
            context: "starting equity is zero"
        })
    );
}

#[test]
fn cagr_one_year_10_percent() {
    // Start: 10000, End: 11000, 1 year = 10% CAGR
    let equity = vec![dec!(10000), dec!(11000)];
    let result = cagr(&equity, dec!(1)).expect("CAGR for 1-year 10% growth");
    // Allow small approximation error
    assert!((result - dec!(10)).abs() < dec!(0.1));
}

#[test]
fn cagr_two_years() {
    // Start: 10000, End: 12100, 2 years
    // CAGR = (12100/10000)^(1/2) - 1 = 1.1 - 1 = 10%
    let equity = vec![dec!(10000), dec!(12100)];
    let result = cagr(&equity, dec!(2)).expect("CAGR for 2-year growth");
    assert!((result - dec!(10)).abs() < dec!(0.5));
}

#[test]
fn cagr_invalid_years() {
    let equity = vec![dec!(10000), dec!(11000)];
    assert!(matches!(
        cagr(&equity, dec!(0)),
        Err(MetricsError::InvalidParameter(_))
    ));
}

#[test]
fn annualized_return_daily() {
    // 252 trading days, 5% total return
    let mut equity = vec![dec!(10000)];
    for _ in 0..251 {
        equity.push(dec!(10000)); // flat
    }
    equity.push(dec!(10500)); // 5% at end

    let result = annualized_return(&equity, 252).expect("annualized return for daily equity");
    // Should be approximately 5% annualized (since we have 1 year of data)
    assert!((result - dec!(5)).abs() < dec!(1));
}

#[test]
fn ln_known_values() {
    // ln(1) = 0
    assert_eq!(ln_approx(dec!(1)), dec!(0));

    // ln(e) ≈ 1 (approximation has larger error for values far from 1)
    let e = dec!(2.71828);
    let ln_e = ln_approx(e);
    // Taylor series around 1 loses accuracy for x far from 1
    // Just verify it's in the right ballpark
    assert!(ln_e > dec!(0.5) && ln_e < dec!(1.5));
}

#[test]
fn exp_known_values() {
    // e^0 = 1
    assert_eq!(exp_approx(dec!(0)), dec!(1));

    // e^1 ≈ 2.718
    assert!((exp_approx(dec!(1)) - dec!(2.718)).abs() < dec!(0.01));
}