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 win_rate_mixed() {
    let trades = vec![dec!(100), dec!(-50), dec!(75), dec!(-25), dec!(80)];
    assert_eq!(
        win_rate(&trades).expect("win_rate with mixed trades"),
        dec!(60)
    ); // 3/5 = 60%
}

#[test]
fn win_rate_all_wins() {
    let trades = vec![dec!(100), dec!(50), dec!(75)];
    assert_eq!(
        win_rate(&trades).expect("win_rate with all wins"),
        dec!(100)
    );
}

#[test]
fn win_rate_all_losses() {
    let trades = vec![dec!(-100), dec!(-50), dec!(-75)];
    assert_eq!(
        win_rate(&trades).expect("win_rate with all losses"),
        dec!(0)
    );
}

#[test]
fn win_rate_empty() {
    let trades: Vec<Decimal> = vec![];
    assert_eq!(
        win_rate(&trades),
        Err(MetricsError::InsufficientData {
            required: 1,
            actual: 0
        })
    );
}

#[test]
fn win_rate_with_breakeven() {
    // Zero P&L trades are neither wins nor losses
    let trades = vec![dec!(100), dec!(0), dec!(-50)];
    // 1 win, 1 breakeven, 1 loss = 1/3 = 33.33%
    let rate = win_rate(&trades).expect("win_rate with breakeven trades");
    assert!((rate - dec!(33.333333)).abs() < dec!(0.001));
}

#[test]
fn profit_factor_profitable() {
    // Wins: 100 + 75 = 175
    // Losses: 50 + 25 = 75
    // PF = 175/75 = 2.33...
    let trades = vec![dec!(100), dec!(-50), dec!(75), dec!(-25)];
    let pf = profit_factor(&trades).expect("profit_factor for profitable trades");
    assert!((pf - dec!(2.333333)).abs() < dec!(0.001));
}

#[test]
fn profit_factor_losing() {
    // Wins: 50
    // Losses: 100 + 75 = 175
    // PF = 50/175 = 0.285...
    let trades = vec![dec!(50), dec!(-100), dec!(-75)];
    let pf = profit_factor(&trades).expect("profit_factor for losing trades");
    assert!(pf < dec!(1));
}

#[test]
fn profit_factor_no_losses() {
    let trades = vec![dec!(100), dec!(50)];
    assert_eq!(
        profit_factor(&trades),
        Err(MetricsError::DivisionByZero {
            context: "no losing trades"
        })
    );
}

#[test]
fn avg_win_calculation() {
    let trades = vec![dec!(100), dec!(-50), dec!(200), dec!(-30)];
    // Wins: 100, 200 -> avg = 150
    assert_eq!(
        avg_win(&trades).expect("avg_win with winning trades"),
        dec!(150)
    );
}

#[test]
fn avg_loss_calculation() {
    let trades = vec![dec!(100), dec!(-50), dec!(200), dec!(-30)];
    // Losses: -50, -30 -> avg = -40
    assert_eq!(
        avg_loss(&trades).expect("avg_loss with losing trades"),
        dec!(-40)
    );
}

#[test]
fn expectancy_positive() {
    // 60% win rate, $100 avg win, -$50 avg loss
    // E = 0.6 * 100 + 0.4 * (-50) = 60 - 20 = 40
    let trades = vec![dec!(100), dec!(100), dec!(100), dec!(-50), dec!(-50)];
    let exp = expectancy(&trades).expect("positive expectancy");
    assert_eq!(exp, dec!(40));
}

#[test]
fn expectancy_negative() {
    // 40% win rate, $50 avg win, -$100 avg loss
    // E = 0.4 * 50 + 0.6 * (-100) = 20 - 60 = -40
    let trades = vec![dec!(50), dec!(50), dec!(-100), dec!(-100), dec!(-100)];
    let exp = expectancy(&trades).expect("negative expectancy");
    assert_eq!(exp, dec!(-40));
}

#[test]
fn expectancy_breakeven() {
    // 50% win rate, $100 avg win, -$100 avg loss
    // E = 0.5 * 100 + 0.5 * (-100) = 0
    let trades = vec![dec!(100), dec!(-100)];
    let exp = expectancy(&trades).expect("breakeven expectancy");
    assert_eq!(exp, dec!(0));
}