pub fn historical_var(returns: &[f64], confidence: f64) -> Option<f64> {
if returns.is_empty() {
return None;
}
let mut sorted = returns.to_vec();
sorted.sort_by(|a, b| a.total_cmp(b));
let idx = ((1.0 - confidence) * sorted.len() as f64) as usize;
let idx = idx.min(sorted.len() - 1);
Some(-sorted[idx])
}
pub fn parametric_var(returns: &[f64], confidence: f64) -> Option<f64> {
if returns.len() < 2 {
return None;
}
let mean = returns.iter().sum::<f64>() / returns.len() as f64;
let variance =
returns.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / (returns.len() - 1) as f64;
let std_dev = variance.sqrt();
let z = normal_quantile(confidence);
Some(-(mean - z * std_dev))
}
fn normal_quantile(p: f64) -> f64 {
match p {
p if p >= 0.999 => 3.090,
p if p >= 0.995 => 2.576,
p if p >= 0.990 => 2.326,
p if p >= 0.975 => 1.960,
p if p >= 0.950 => 1.645,
p if p >= 0.900 => 1.282,
_ => 1.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_historical_var_empty() {
assert!(historical_var(&[], 0.95).is_none());
}
#[test]
fn test_historical_var_simple() {
let returns = [-0.05_f64, 0.0, 0.05, 0.10, -0.10];
let var = historical_var(&returns, 0.95).unwrap();
assert!((var - 0.10).abs() < 1e-9, "got {var}");
}
#[test]
fn test_parametric_var_reasonable() {
let returns: Vec<f64> = (0..100)
.map(|i| if i % 2 == 0 { 0.01 } else { -0.01 })
.collect();
let var = parametric_var(&returns, 0.95).unwrap();
assert!(var > 0.0, "VaR must be positive");
}
}