每单位风险的回报
年收益 12%、波动率 20% 的策略并不比年收益 6%、波动率 5% 的更好。夏普比率把它们放在同一把尺子上:高于无风险利率的平均收益除以波动率,(mean - rf) / std,再用 sqrt(期数) 年化。公式你在「均值与方差」一节已经见过。本节讲的是它的用法:如何解读夏普比率、如何改进它,尤其是它有多可信。夏普 0.5 平庸,1 不错,2 很出色但也可疑。
波动率把上涨和下跌一视同仁,可没人会抱怨意外的收益。索提诺比率只保留糟糕的一面:用下行偏差 sqrt(mean(min(r - mar, 0)²)) 取代标准差,其中 mar 是最低可接受收益。高于 mar 的日子贡献为零,但仍计入平均。拥有较大正向跳升和较小亏损的策略,索提诺得分会比夏普高得多。
import math
import numpy as np
import pandas as pd
returns = np.array([0.004, -0.002, 0.001, 0.003, -0.004, 0.002, 0.006, -0.001, -0.010, 0.004, 0.002, -0.003])
sharpe = returns.mean() / returns.std(ddof=1) * math.sqrt(252)
print(round(sharpe, 3)) # 0.598
mar = 0.0 # a day below zero is a bad day
downside = math.sqrt(np.mean(np.minimum(returns - mar, 0) ** 2))
sortino = (returns.mean() - mar) / downside * math.sqrt(252)
print(round(downside, 5), round(sortino, 3)) # 0.00329 0.804 the up days no longer count as risk观察它的变化
整个历史只给一个夏普比率会掩盖过程。滚动夏普比率在最近 window 个收益率上重新计算,让你看到策略的好阶段和坏阶段。用 pandas 就是滚动均值除以滚动标准差再年化。注意短窗口跳动得多么剧烈:一个月的数据远不足以给策略排名。
series = pd.Series(returns)
rolling = series.rolling(5)
rolling_sharpe = rolling.mean() / rolling.std() * math.sqrt(252)
print(rolling_sharpe.round(2).tolist())
# [nan, nan, nan, nan, 1.89, 0.0, 6.96, 4.97, -3.66, 0.51, 0.51, -4.69] a 5-day Sharpe ratio is mostly noise有多少是运气?
夏普比率是估计值,估计就有误差。对基于 n 个收益率测得的每期夏普 sr,标准误约为 sqrt((1 + 0.5 * sr²) / n)。对我们的 12 天,它是每日 0.29,而夏普只有 0.04:误差是估计值的七倍。年化后,同样 12 天给出 0.6 ± 4.6。对于在 years 年内观察到的年化夏普 S,t 统计量是 S * sqrt(years),所以需要 (1.96 / S)² 年才能有 95% 的把握说它不为零:夏普 0.5 要 15 年,夏普 1 要 4 年。对平庸策略做短期回测什么也证明不了。
per_period = returns.mean() / returns.std(ddof=1) # the per-day Sharpe ratio
std_error = math.sqrt((1 + 0.5 * per_period ** 2) / len(returns))
print(round(per_period, 4), round(std_error, 4)) # 0.0376 0.2888 the error is 7 times the estimate
print(round(std_error * math.sqrt(252), 3)) # 4.584 annualised: 0.6 +/- 4.6
# Years needed for an annual Sharpe ratio to reach t = 1.96
for annual in (0.5, 1.0, 2.0):
print(annual, round((1.96 / annual) ** 2, 1))
# 0.5 15.4
# 1.0 3.8
# 2.0 1.0# A yearly Sharpe of 1.0 observed over 4 years (1,008 days): how precise is it?
per_day = 1.0 / math.sqrt(252)
error_per_day = math.sqrt((1 + 0.5 * per_day ** 2) / 1008)
print(round(per_day, 4), round(error_per_day, 4), round(error_per_day * math.sqrt(252), 3)) # 0.063 0.0315 0.5 so 1.0 +/- 0.5表现最好的回测最不可靠如果你试了一个策略的 100 个变体,并保留夏普比率最高的那个,这个胜出者的数字就被选择夸大了:即使毫无优势的策略,靠运气也能得到 1 以上的夏普。请把你的热情除以所试变体的数量,在选择过程从未见过的数据上检验,并优先选择有经济逻辑的想法。
- 夏普 =
(mean - rf) / std * sqrt(期数);索提诺用下行偏差sqrt(mean(min(r - mar, 0)²))取代标准差。 - 滚动夏普(
rolling.mean() / rolling.std())展示比率的变化;短窗口多半是噪声。 - 其标准误为每期
sqrt((1 + 0.5 * sr²) / n);年化夏普的 t 统计量是S * sqrt(years):夏普为 1 约需 4 年。
下一节:回撤,投资者真正感受到的从高点下来的亏损。
实验:评分内容
- 用
sortino_ratio只惩罚糟糕的日子25 分 - 用
rolling_sharpe观察比率随时间的变化25 分 - 用
sharpe_standard_error衡量夏普比率中的运气成分25 分 - 用
sharpe_t_stat判断它是否真实25 分