σμ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.8·~40分钟中级

回撤

路线进度模块 3/11 · 主题 8/12
3.8 回撤
—实验得分— / 100总 XP—连续天数—

你真正感受到的亏损

波动率把上涨和下跌一视同仁,但投资者面对的是另一个数字:账户比它曾达到的最高值低多少。这就是回撤。如果一个组合涨到 110,现在是 98,那么无论每日波动率怎么说,它都比高点低 10.9%。最大回撤是历史上最深的坑,水下时间是爬出来所用的时长。客户是在回撤时离场,风险经理是在回撤时压缩限额,而不是在标准差上。

import pandas as pd

prices = pd.Series(
    [100, 105, 103, 98, 104, 110, 108, 107, 111, 95], dtype=float,
    index=pd.bdate_range("2026-03-02", periods=10),
)

peak = prices.cummax()                 # the highest price so far, day by day
drawdown = prices / peak - 1           # 0 on a new high, negative below it

print(peak.tolist())                   # [100.0, 105.0, 105.0, 105.0, 105.0, 110.0, 110.0, 110.0, 111.0, 111.0]
print(drawdown.round(4).tolist())      # [0.0, 0.0, -0.019, -0.0667, -0.0095, 0.0, -0.0182, -0.0273, 0.0, -0.1441]

最深的坑

cummax() 一次调用就给出滚动高点。用价格除以它再减 1,就得到每天的回撤,永远为零或负。最大回撤就是这个序列的最小值,idxmin() 告诉你它发生在何时。回撤有一种重要的不对称性:亏 20% 需要赚 25% 才能回本,即 1 / (1 - 0.20) - 1,亏 50% 需要赚 100%。这就是风险经理拼命让回撤保持浅的原因。

print(round(drawdown.min(), 4), drawdown.idxmin().date())   # -0.1441 2026-03-13   111 down to 95

# The climb back is steeper than the fall
for loss in (0.10, 0.20, 0.50):
    print(loss, round(1 / (1 - loss) - 1, 3))
# 0.1 0.111
# 0.2 0.25
# 0.5 1.0

水下时间

深度只是故事的一半,持续时间是另一半。一个策略可能只亏 8%,却在旧高点下方待上三年,这会考验任何投资者的耐心。回撤为负的日子就是水下,创出新高则结束这一段。用 pandas 找最长的一段:先用比较标记水下的日子,再用 (flag != flag.shift()).cumsum() 给每段相同值编号,然后在每段内计数。一个计数并重置的简单循环同样有效,而且易读。

underwater = drawdown < 0                                     # True while below the running peak
run_id = (underwater != underwater.shift()).cumsum()           # a new id each time the flag changes
streak = underwater.astype(int).groupby(run_id).cumsum()       # 1, 2, 3, ... inside each underwater run

print(streak.tolist())                    # [0, 0, 1, 2, 3, 0, 1, 2, 0, 1]
print(int(streak.max()))                  # 3   the longest wait for a new high, in days
# Return divided by the depth of the worst hole: the Calmar ratio
annual_return = 0.12
print(round(annual_return / abs(drawdown.min()), 2))     # 0.83
过去的回撤不是最坏的回测的最大回撤只是该样本中发生过的最坏情况,而不是可能发生的最坏情况:更长的历史几乎总会包含更深的坑。把它当作下限,与同期市场的最大回撤比较,并在确定仓位时留出余地。
  • peak = prices.cummax() 与 drawdown = prices / peak - 1:创新高时为 0,之下为负。
  • 最大回撤是 drawdown.min();亏损 x 需要 1 / (1 - x) - 1 的收益才能回本。
  • 水下时间是回撤为负的最长连续天数;回测的最大回撤是下限,而非保证。

下一节:随机数生成器与种子,所有模拟的基础。

实验:评分内容

  • 用 running_peak 跟踪迄今为止的最高价25 分
  • 用 drawdown_series 衡量与高点的距离25 分
  • 用 max_drawdown 找出最大跌幅25 分
  • 用 longest_underwater 统计等待新高的最长时间25 分
资源管理器 · lab-61-1
▾ lab-61-1
…
bash · 浏览器内
learner@quantcraft:~/lab-61-1$
Python 尚未加载PythonLn 1, Col 1Spaces: 4UTF-8

要重置此主题吗?

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

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

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

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

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