use crate::stats::{kurtosis, mean, norm_cdf, norm_ppf, skewness, std_dev};
pub fn sharpe_ratio(returns: &[f64]) -> f64 {
let s = std_dev(returns);
if s == 0.0 {
return 0.0;
}
mean(returns) / s
}
pub fn probabilistic_sharpe_ratio(returns: &[f64], sr_benchmark: f64) -> f64 {
let n = returns.len();
if n < 2 {
return 0.0;
}
let sr = sharpe_ratio(returns);
let g3 = skewness(returns);
let g4 = kurtosis(returns); let denom = (1.0 - g3 * sr + ((g4 - 1.0) / 4.0) * sr * sr)
.max(1e-12)
.sqrt();
let z = (sr - sr_benchmark) * (n as f64 - 1.0).sqrt() / denom;
norm_cdf(z)
}
pub fn expected_max_sharpe(trials_sr_std: f64, n_trials: u32) -> f64 {
let n = n_trials.max(1) as f64;
if n <= 1.0 || trials_sr_std <= 0.0 {
return 0.0;
}
const GAMMA: f64 = 0.577_215_664_901_532_9; let e = std::f64::consts::E;
let z1 = norm_ppf(1.0 - 1.0 / n);
let z2 = norm_ppf(1.0 - 1.0 / (n * e));
trials_sr_std * ((1.0 - GAMMA) * z1 + GAMMA * z2)
}
pub fn deflated_sharpe_ratio(returns: &[f64], n_trials: u32, trials_sr_std: f64) -> f64 {
let sr_star = expected_max_sharpe(trials_sr_std, n_trials);
probabilistic_sharpe_ratio(returns, sr_star)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn psr_high_for_consistent_edge() {
let r: Vec<f64> = (0..250)
.map(|i| 0.001 + 0.0001 * ((i % 5) as f64 - 2.0))
.collect();
assert!(probabilistic_sharpe_ratio(&r, 0.0) > 0.99);
}
#[test]
fn deflation_penalizes_many_trials() {
let r: Vec<f64> = (0..120)
.map(|i| 0.02 + 0.1 * (i as f64 * 0.9).sin())
.collect();
let few = deflated_sharpe_ratio(&r, 2, 0.5);
let many = deflated_sharpe_ratio(&r, 500, 0.5);
assert!(
many < few,
"many-trial DSR {many} should be < few-trial DSR {few}"
);
}
}