σμ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 AcademySkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
Skills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python skill›Module 3 · Probability & Monte Carlo simulation›Topic 3.8·~40 minIntermediate

Drawdowns

Skill positionModule 3 of 11 · Topic 8 of 12
3.8 Drawdowns
—Lab score— / 100Total XP—Streak—

The loss you actually feel

Volatility treats up and down alike, but an investor lives with a different number: how far the account is below the best value it ever had. That is the drawdown. If a portfolio rose to 110 and now stands at 98, it is 10.9% below its peak, whatever the day-to-day volatility says. The maximum drawdown is the deepest such hole in the history, and the time underwater is how long it took to climb out. Clients quit and risk managers cut limits at drawdowns, not at standard deviations.

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]

The deepest hole

cummax() gives the running peak in one call. Dividing the price by it and subtracting 1 gives the drawdown on every day, always zero or negative. The maximum drawdown is simply the minimum of that series, and idxmin() tells you when it happened. Drawdowns are asymmetric in a way that matters: a 20% loss needs a 25% gain to get back, 1 / (1 - 0.20) - 1, and a 50% loss needs 100%. This is why risk managers fight to keep drawdowns shallow.

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

Time underwater

Depth is half the story; duration is the other half. A strategy can lose only 8% and still spend three years below its old high, which tests any investor's patience. A day is underwater while its drawdown is negative, and a new high ends the stretch. To find the longest stretch with pandas, mark the underwater days with a comparison, label each run of equal values with (flag != flag.shift()).cumsum(), then count within each run. A plain loop that counts and resets works just as well and is easy to read.

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
A past drawdown is not the worst oneThe maximum drawdown of a backtest is the worst one that happened in that sample, not the worst that can happen: a longer history will almost always contain a deeper hole. Treat it as a lower bound, compare it with the largest drawdown of the market over the same period, and leave a margin when you set position sizes.
  • peak = prices.cummax() and drawdown = prices / peak - 1: zero on a new high, negative below it.
  • The maximum drawdown is drawdown.min(); a loss of x needs a gain of 1 / (1 - x) - 1 to recover.
  • Time underwater is the longest run of days with a negative drawdown; a backtest's worst drawdown is a lower bound, not a guarantee.

Next up: random number generators and seeds, the foundation of every simulation.

Lab · what's graded

  • Track the highest price so far with running_peak25 pts
  • Measure the distance from the peak with drawdown_series25 pts
  • Find the worst fall with max_drawdown25 pts
  • Count the longest wait for a new high with longest_underwater25 pts
Explorer · lab-61-1
▾ lab-61-1
…
bash · in-browser
learner@quantcraft:~/lab-61-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-61-1 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes

Log in to run your code.

Every lecture is free to read. Create an account or log in to run code, run tests and submit this lab.

  • In-browser IDE with a bash terminal — nothing to install
  • Instant grading, XP and your place on the skill
Create an accountLog in
OR
Continue with GitHub