use ndarray::ArrayView1;
use statrs::distribution::Continuous;
use statrs::distribution::ContinuousCDF;
use statrs::distribution::Normal;
use super::var::PnlOrLoss;
use super::var::VarMethod;
use super::var::assert_confidence;
use super::var::gaussian_var;
use super::var::losses_from_samples;
use super::var::sample_quantile;
use crate::traits::FloatExt;
pub fn expected_shortfall<T: FloatExt>(
samples: ArrayView1<T>,
confidence: T,
orientation: PnlOrLoss,
method: VarMethod,
) -> T {
assert_confidence(confidence);
match method {
VarMethod::Gaussian => gaussian_es(samples, confidence, orientation),
VarMethod::Historical | VarMethod::MonteCarlo => {
historical_es(samples, confidence, orientation)
}
}
}
pub fn gaussian_es<T: FloatExt>(
samples: ArrayView1<T>,
confidence: T,
orientation: PnlOrLoss,
) -> T {
assert_confidence(confidence);
let losses = losses_from_samples(samples, orientation);
let n = losses.len();
assert!(n >= 2, "need at least two observations for Gaussian ES");
let mean = losses.iter().fold(T::zero(), |acc, &v| acc + v) / T::from_usize_(n);
let var = losses
.iter()
.fold(T::zero(), |acc, &v| acc + (v - mean).powi(2))
/ T::from_usize_(n - 1);
let sigma = var.sqrt();
let c = confidence.to_f64().unwrap();
let normal = Normal::new(0.0, 1.0).expect("standard normal");
let q = normal.inverse_cdf(c);
let phi_q = normal.pdf(q);
let factor = T::from_f64_fast(phi_q / (1.0 - c));
mean + sigma * factor
}
pub fn historical_es<T: FloatExt>(
samples: ArrayView1<T>,
confidence: T,
orientation: PnlOrLoss,
) -> T {
assert_confidence(confidence);
let losses = losses_from_samples(samples, orientation);
let n = losses.len();
assert!(n >= 1, "need at least one observation for historical ES");
let mut sorted = losses.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let var = sample_quantile(&sorted, confidence);
let tail: Vec<T> = sorted.iter().copied().filter(|&v| v >= var).collect();
if tail.is_empty() {
return var;
}
tail.iter().fold(T::zero(), |acc, &v| acc + v) / T::from_usize_(tail.len())
}
pub fn monte_carlo_es<T: FloatExt>(
simulated_samples: ArrayView1<T>,
confidence: T,
orientation: PnlOrLoss,
) -> T {
historical_es(simulated_samples, confidence, orientation)
}
pub fn gaussian_var_es<T: FloatExt>(
samples: ArrayView1<T>,
confidence: T,
orientation: PnlOrLoss,
) -> (T, T) {
(
gaussian_var(samples, confidence, orientation),
gaussian_es(samples, confidence, orientation),
)
}