补齐权威下行风险指标
This commit is contained in:
@@ -50,6 +50,7 @@ pub struct BacktestMetrics {
|
|||||||
pub max_drawdown_duration_days: usize,
|
pub max_drawdown_duration_days: usize,
|
||||||
pub total_trade_days: usize,
|
pub total_trade_days: usize,
|
||||||
pub sortino: f64,
|
pub sortino: f64,
|
||||||
|
pub downside_risk: f64,
|
||||||
pub information_ratio: f64,
|
pub information_ratio: f64,
|
||||||
pub tracking_error: f64,
|
pub tracking_error: f64,
|
||||||
pub volatility: f64,
|
pub volatility: f64,
|
||||||
@@ -174,6 +175,8 @@ pub fn compute_backtest_metrics(
|
|||||||
effective_annual_risk_free_rate(&daily_risk_free_rates, TRADING_DAYS_PER_YEAR);
|
effective_annual_risk_free_rate(&daily_risk_free_rates, TRADING_DAYS_PER_YEAR);
|
||||||
let sharpe = annualized_sharpe(&returns, &daily_risk_free_rates, TRADING_DAYS_PER_YEAR);
|
let sharpe = annualized_sharpe(&returns, &daily_risk_free_rates, TRADING_DAYS_PER_YEAR);
|
||||||
let sortino = annualized_sortino(&returns, &daily_risk_free_rates, TRADING_DAYS_PER_YEAR);
|
let sortino = annualized_sortino(&returns, &daily_risk_free_rates, TRADING_DAYS_PER_YEAR);
|
||||||
|
let downside_risk =
|
||||||
|
annualized_downside_risk(&returns, &daily_risk_free_rates, TRADING_DAYS_PER_YEAR);
|
||||||
let information_ratio = annualized_sharpe(
|
let information_ratio = annualized_sharpe(
|
||||||
&excess_returns,
|
&excess_returns,
|
||||||
&zero_risk_free_rates,
|
&zero_risk_free_rates,
|
||||||
@@ -296,6 +299,7 @@ pub fn compute_backtest_metrics(
|
|||||||
max_drawdown_duration_days,
|
max_drawdown_duration_days,
|
||||||
total_trade_days,
|
total_trade_days,
|
||||||
sortino,
|
sortino,
|
||||||
|
downside_risk,
|
||||||
information_ratio,
|
information_ratio,
|
||||||
tracking_error,
|
tracking_error,
|
||||||
volatility,
|
volatility,
|
||||||
@@ -498,6 +502,23 @@ fn annualized_sortino(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn annualized_downside_risk(
|
||||||
|
returns: &[f64],
|
||||||
|
daily_risk_free_rates: &[f64],
|
||||||
|
periods_per_year: f64,
|
||||||
|
) -> f64 {
|
||||||
|
if returns.is_empty() || returns.len() != daily_risk_free_rates.len() {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
let downside_mean_square = returns
|
||||||
|
.iter()
|
||||||
|
.zip(daily_risk_free_rates)
|
||||||
|
.map(|(value, risk_free)| (value - risk_free).min(0.0).powi(2))
|
||||||
|
.sum::<f64>()
|
||||||
|
/ returns.len() as f64;
|
||||||
|
downside_mean_square.sqrt() * periods_per_year.sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
fn annualized_std(values: &[f64], periods_per_year: f64) -> f64 {
|
fn annualized_std(values: &[f64], periods_per_year: f64) -> f64 {
|
||||||
std_dev(values) * periods_per_year.sqrt()
|
std_dev(values) * periods_per_year.sqrt()
|
||||||
}
|
}
|
||||||
@@ -863,6 +884,7 @@ mod tests {
|
|||||||
let expected_sortino = mean(&adjusted) / downside * TRADING_DAYS_PER_YEAR.sqrt();
|
let expected_sortino = mean(&adjusted) / downside * TRADING_DAYS_PER_YEAR.sqrt();
|
||||||
assert!((metrics.sharpe - expected_sharpe).abs() < 1e-12);
|
assert!((metrics.sharpe - expected_sharpe).abs() < 1e-12);
|
||||||
assert!((metrics.sortino - expected_sortino).abs() < 1e-12);
|
assert!((metrics.sortino - expected_sortino).abs() < 1e-12);
|
||||||
|
assert!((metrics.downside_risk - downside * TRADING_DAYS_PER_YEAR.sqrt()).abs() < 1e-12);
|
||||||
assert_eq!(metrics.risk_free_rate_source, "test");
|
assert_eq!(metrics.risk_free_rate_source, "test");
|
||||||
assert_eq!(metrics.risk_free_rate_tenor, "3M");
|
assert_eq!(metrics.risk_free_rate_tenor, "3M");
|
||||||
assert_eq!(metrics.risk_free_rate_observation_count, 4);
|
assert_eq!(metrics.risk_free_rate_observation_count, 4);
|
||||||
|
|||||||
Reference in New Issue
Block a user