use ndarray::ArrayView1;
use super::drawdown::equity_from_returns;
use super::drawdown::max_drawdown;
use crate::traits::FloatExt;
fn mean<T: FloatExt>(xs: ArrayView1<T>) -> T {
let n = xs.len();
assert!(n > 0, "empty series");
xs.iter().fold(T::zero(), |a, &v| a + v) / T::from_usize_(n)
}
fn stdev<T: FloatExt>(xs: ArrayView1<T>) -> T {
let n = xs.len();
assert!(n >= 2, "need at least two observations for stdev");
let m = mean(xs);
let var = xs.iter().fold(T::zero(), |a, &v| a + (v - m).powi(2)) / T::from_usize_(n - 1);
var.sqrt()
}
pub fn sharpe_ratio<T: FloatExt>(
returns: ArrayView1<T>,
risk_free_per_period: T,
periods_per_year: T,
) -> T {
let m = mean(returns);
let s = stdev(returns);
if s <= T::min_positive_val() {
return T::zero();
}
(m - risk_free_per_period) / s * periods_per_year.sqrt()
}
pub fn sortino_ratio<T: FloatExt>(
returns: ArrayView1<T>,
mar_per_period: T,
periods_per_year: T,
) -> T {
let n = returns.len();
assert!(n >= 2, "need at least two observations for Sortino");
let m = mean(returns);
let mut sum_sq = T::zero();
for &r in returns.iter() {
let d = r - mar_per_period;
if d < T::zero() {
sum_sq += d.powi(2);
}
}
let downside = (sum_sq / T::from_usize_(n)).sqrt();
if downside <= T::min_positive_val() {
return T::zero();
}
(m - mar_per_period) / downside * periods_per_year.sqrt()
}
pub fn information_ratio<T: FloatExt>(
returns: ArrayView1<T>,
benchmark: ArrayView1<T>,
periods_per_year: T,
) -> T {
assert_eq!(
returns.len(),
benchmark.len(),
"returns and benchmark must have matching length"
);
let diff = &returns.to_owned() - &benchmark.to_owned();
let m = mean(diff.view());
let s = stdev(diff.view());
if s <= T::min_positive_val() {
return T::zero();
}
m / s * periods_per_year.sqrt()
}
pub fn calmar_ratio<T: FloatExt>(returns: ArrayView1<T>, periods_per_year: T) -> T {
let annualised_return = mean(returns) * periods_per_year;
let equity = equity_from_returns(returns, T::one());
let mdd = max_drawdown(equity.view());
let mdd_abs = mdd.abs();
if mdd_abs <= T::min_positive_val() {
return T::zero();
}
annualised_return / mdd_abs
}