use crate::error::DigiFiError;
use crate::utilities::maths_utils::definite_integral;
use crate::statistics::ProbabilityDistribution;
pub fn value_at_risk(alpha: f64, losses_distribution: &impl ProbabilityDistribution) -> Result<f64, DigiFiError> {
if (alpha < 0.0) || (1.0 < alpha) {
return Err(DigiFiError::ParameterConstraint {
title: "Value at Risk".to_owned(),
constraint: "The argument `alpha` must be in the range `[0, 1]`.".to_owned(),
});
}
Ok(losses_distribution.inverse_cdf(alpha)?)
}
pub fn expected_shortfall(alpha: f64, losses_distribution: &impl ProbabilityDistribution) -> Result<f64, DigiFiError> {
if (alpha < 0.0) || (1.0 < alpha) {
return Err(DigiFiError::ParameterConstraint {
title: "Expected Shortfall".to_owned(),
constraint: "The argument `alpha` must be in the range `[0, 1]`.".to_owned(),
});
}
let f = |p| { value_at_risk(p, losses_distribution).unwrap() };
Ok(definite_integral(f, alpha, 1.0, 1_000_000) / (1.0 - alpha))
}
#[cfg(test)]
mod tests {
use crate::statistics::ProbabilityDistribution;
use crate::utilities::TEST_ACCURACY;
use crate::statistics::continuous_distributions::NormalDistribution;
#[test]
fn unit_test_value_at_risk() -> () {
use crate::portfolio_applications::risk_measures::value_at_risk;
let norm_dist: NormalDistribution = NormalDistribution::build(0.0, 1.0).unwrap();
assert!((value_at_risk(0.95, &norm_dist).unwrap() - 1.6447718627498997).abs() < 1_000_000.0 * TEST_ACCURACY);
}
#[test]
fn unit_test_expected_shortfall() -> () {
use crate::portfolio_applications::risk_measures::expected_shortfall;
let norm_dist: NormalDistribution = NormalDistribution::build(0.0, 1.0).unwrap();
let es: f64 = expected_shortfall(0.95, &norm_dist).unwrap();
let theor: f64 = norm_dist.pdf(norm_dist.inverse_cdf(0.95).unwrap()).unwrap() / 0.05;
assert!((es - theor).abs() < 10_000_000.0 * TEST_ACCURACY);
}
}