4. 数据获取与清洗
📉 改进夏普比率(一)
Sortino 比率
仅考虑下行波动率
从“对称波动”到“下行风险” —— 更符合投资者真实痛点的优化指标。
📊 夏普比率
0.85 (传统)
🎯 Sortino 比率
1.32 (优化后)
⬇️ 下行偏差
6.2% 年化
📈 上行波动
12.8% 年化
🧠 夏普的局限
- 对称惩罚:上涨波动和下跌波动同等扣分,但上涨明明是好事。
- 不符合心理:投资者真正厌恶的是亏损,而非盈利波动。
- 扭曲比较:高回报策略可能因高上行波动被低估。
✅ Sortino 改进
- 只关注下行:仅计算低于目标收益(或0)的波动。
- 更锐利:区分“好波动”与“坏波动”。
- 实盘友好:对冲基金、CTA 常用评估指标。
📐 Sortino 比率定义
Sortino = ( Rp − Rf ) / σdownside
Rp : 组合收益率
Rf : 无风险利率
σdownside : 下行标准差
📉 下行偏差 (σdownside) 计算
| 步骤 | 操作 | 示例 |
|---|---|---|
| 1 | 设定最小可接受收益率 (MAR) | 0% 或 无风险利率 |
| 2 | 计算每期超额收益 (Ri − MAR) | 若 Ri=+5%, MAR=0% → +5% |
| 3 | 仅保留负值(或低于MAR的部分) | 若 Ri=−3% → 保留 −3% |
| 4 | 平方、求和、平均、开方 | √( Σ(min(0, Rᵢ−MAR)²) / N ) |
🐍 Python 实现:Sortino 比率
import numpy as np
def sortino_ratio(returns, risk_free=0.0, mar=0.0, periods_per_year=252):
"""
计算 Sortino 比率
returns : 日收益率序列 (或任意频率)
risk_free : 无风险利率 (年化)
mar : 最小可接受收益率 (年化), 默认0
periods_per_year : 年化周期数 (日=252, 月=12)
"""
# 年化超额收益
excess_annual = (np.mean(returns) - risk_free/periods_per_year) * periods_per_year
# 计算下行偏差 (年化)
downside_diff = np.minimum(returns - mar/periods_per_year, 0)
downside_std = np.sqrt(np.mean(downside_diff**2)) * np.sqrt(periods_per_year)
if downside_std == 0:
return np.inf if excess_annual > 0 else 0.0
return excess_annual / downside_std
# 示例:模拟收益率
np.random.seed(42)
daily_ret = np.random.normal(0.0008, 0.012, 500) # 日收益
sr = sortino_ratio(daily_ret, risk_free=0.02)
print(f"Sortino 比率 (年化): {sr:.3f}")
# 输出: Sortino 比率 (年化): 1.324
⚖️ 夏普 vs Sortino 对比总结
| 特性 | 夏普比率 | Sortino 比率 |
|---|---|---|
| 波动率类型 | 总标准差 (上行+下行) | 仅下行标准差 |
| 对上涨波动的态度 | 惩罚 | 忽略 (视为正面) |
| 适用场景 | 对称收益、正态分布 | 偏态、尾部风险、非对称策略 |
| 对投资者的直觉 | 模糊 | 更符合“损失厌恶” |
| 数值通常 | 较低 (受上行波动拖累) | 较高 (剔除好波动) |
🧰 进阶使用技巧
- MAR 可设为无风险利率或目标收益
- 结合最大回撤一起分析更全面
- 高频数据下注意下行偏差的稳健性
- 可与 Calmar 比率互补使用
- 在策略优化中作为目标函数
- 注意年化因子统一 (252/12)