σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academy学习路线
Ctrl K
AO
+1.2k 位学习者
登录加入 ↗
学习路线
AO
+1.2k 位学习者
登录
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python 学习路线›模块 3 · 概率与蒙特卡洛模拟›主题 3.7·~40分钟中级

夏普比率

路线进度模块 3/11 · 主题 7/12
3.7 夏普比率
—实验得分— / 100总 XP—连续天数—

每单位风险的回报

年收益 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 分
资源管理器 · lab-60-1
▾ lab-60-1
…
bash · 浏览器内
learner@quantcraft:~/lab-60-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

lab-60-1 中的所有内容将恢复为初始文件,终端也会被清空。你当前的解答将丢失,且无法撤销。

将被重置
  • main.py → 初始代码
  • 终端历史与测试输出
  • 未提交的更改
将被保留
  • 实验最高得分
  • 你的 XP 与连续天数
  • 讲解进度与笔记

登录后即可运行你的代码。

所有讲解均可免费阅读。创建账户或登录后,即可运行代码、运行测试并提交此实验。

  • 浏览器内 IDE 与 bash 终端,无需安装
  • 即时评分、XP 以及你在学习路线中的位置
创建账户登录
或
使用 GitHub 继续