pub fn percentile_of(value: f64, population: &[f64]) -> f64 {
if population.is_empty() {
return 0.0;
}
let n_le = population.iter().filter(|&&p| value >= p).count();
100.0 * n_le as f64 / population.len() as f64
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BaselineBand {
Below,
Within,
Above,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HumanBaseline {
pub floor_sharpe: f64,
pub median_sharpe: f64,
pub ceiling_sharpe: f64,
}
impl HumanBaseline {
pub fn skilled_trader() -> Self {
let per_period = |annual: f64| annual / 252.0_f64.sqrt();
Self {
floor_sharpe: per_period(0.5),
median_sharpe: per_period(1.0),
ceiling_sharpe: per_period(2.0),
}
}
pub fn reference_dsr_population(
&self,
track_len: usize,
n_trials: u32,
trials_sr_std: f64,
) -> Vec<f64> {
[self.floor_sharpe, self.median_sharpe, self.ceiling_sharpe]
.iter()
.map(|&sr| dsr_from_sharpe(sr, track_len, n_trials, trials_sr_std))
.collect()
}
pub fn classify_dsr(
&self,
dsr: f64,
track_len: usize,
n_trials: u32,
trials_sr_std: f64,
) -> BaselineBand {
let floor = dsr_from_sharpe(self.floor_sharpe, track_len, n_trials, trials_sr_std);
let ceiling = dsr_from_sharpe(self.ceiling_sharpe, track_len, n_trials, trials_sr_std);
if dsr < floor {
BaselineBand::Below
} else if dsr > ceiling {
BaselineBand::Above
} else {
BaselineBand::Within
}
}
}
fn dsr_from_sharpe(sr: f64, track_len: usize, n_trials: u32, trials_sr_std: f64) -> f64 {
if track_len < 2 {
return 0.0;
}
let sr_star = crate::deflated_sharpe::expected_max_sharpe(trials_sr_std, n_trials);
let denom = (1.0 + 0.5 * sr * sr).max(1e-12).sqrt();
let z = (sr - sr_star) * (track_len as f64 - 1.0).sqrt() / denom;
crate::stats::norm_cdf(z)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9
}
#[test]
fn ranks_within_population() {
let pop = [0.2, 0.5, 0.8, 1.1, 1.4];
assert!(approx(percentile_of(0.9, &pop), 60.0)); assert!(approx(percentile_of(0.1, &pop), 0.0)); assert!(approx(percentile_of(2.0, &pop), 100.0)); assert!(approx(percentile_of(0.5, &pop), 40.0)); }
#[test]
fn empty_population_is_zero() {
assert_eq!(percentile_of(1.0, &[]), 0.0);
}
#[test]
fn skilled_trader_band_is_ordered_and_per_period() {
let b = HumanBaseline::skilled_trader();
assert!(b.floor_sharpe < b.median_sharpe && b.median_sharpe < b.ceiling_sharpe);
assert!(approx(b.median_sharpe, 1.0 / 252.0_f64.sqrt()));
}
#[test]
fn reference_population_is_ordered_and_scores_a_dsr() {
let b = HumanBaseline::skilled_trader();
let pop = b.reference_dsr_population(500, 50, 0.5);
assert_eq!(pop.len(), 3);
assert!(
pop[0] <= pop[1] && pop[1] <= pop[2],
"floor≤median≤ceiling DSR"
);
let pct = percentile_of(pop[1], &pop);
assert!((0.0..=100.0).contains(&pct));
}
#[test]
fn classify_dsr_brackets_the_band() {
let b = HumanBaseline::skilled_trader();
let (len, nt, disp) = (500, 50, 0.5);
let pop = b.reference_dsr_population(len, nt, disp);
assert_eq!(
b.classify_dsr(pop[0] - 0.1, len, nt, disp),
BaselineBand::Below
);
assert_eq!(
b.classify_dsr((pop[0] + pop[2]) / 2.0, len, nt, disp),
BaselineBand::Within
);
assert_eq!(
b.classify_dsr(pop[2] + 1e-6, len, nt, disp),
BaselineBand::Above
);
}
}