Learning LibraryQuant LibraryTeens

Risk basics: measuring the downside

Returns get the attention, but risk is what ends the game. A key measure is drawdown — how far you fall from your best point — and it's a short loop to compute. (Educational only — not advice.)

The big idea

Drawdown measures the largest drop from a peak value, capturing how much pain a strategy put you through along the way.

See it in code

1The basics

Drawdown is one subtraction: how far you fell from a high point, as a percent of that high. Say the account peaked at 1050 and slid to 900:

python
# Drawdown: how far you fall from a high point - risk, not advice.
peak = 1050
low = 900
drawdown = (peak - low) / peak * 100
print(f"Drawdown: {drawdown:.1f}%")
Run it — the drop from that peak, in percent:
Drawdown: 14.3%

That's the whole idea for a single peak and low. But an account has many highs — so we need the peak to update itself as new records get set.

2A step further

The peak is a running maximum: as values arrive, max() keeps the best seen so far, and it doesn't drop back when the account dips:

python
values = [1000, 1050, 1020]

peak = values[0]
for v in values:
    peak = max(peak, v)
    print(f"value {v}, peak so far {peak}")
Run it — the peak climbs to 1050 and then holds:
value 1000, peak so far 1000
value 1050, peak so far 1050
value 1020, peak so far 1050

See how the peak stays at 1050 even as the value falls to 1020? That held-high is what we measure the drop against — combine it with the drawdown formula and a loop finds the worst fall automatically.

3In our world

Now put both pieces together over a full account curve: track the running peak, measure how far below it each step falls, and keep the deepest of those falls as the maximum drawdown:

python
# Drawdown: how far an account falls from its peak - risk, not advice.
values = [1000, 1050, 1020, 900, 950, 1100]

peak = values[0]
max_drawdown = 0.0
for v in values:
    peak = max(peak, v)
    drawdown = (peak - v) / peak * 100
    max_drawdown = max(max_drawdown, drawdown)

print(f"Worst drawdown: {max_drawdown:.1f}%")
Run it — the deepest drop from a high point:
Worst drawdown: 14.3%

The loop found the very drop we computed by hand at the start — peak 1050 down to 900, a 14.3% fall. Even though the account ended higher than it started, it took that hit along the way. That number matters: a strategy with great returns but a huge drawdown is one most people couldn't stomach holding.

The same idea, everywhere

Tracking a running maximum and comparing against it is a broadly useful pattern: peak memory usage in a program, a high-water mark in a game, the worst latency spike in a service. 'Distance from the best-so-far' is a general way to measure downside.

Try it yourself

Print the drawdown at each step to see where the worst moment was. Then compare two value series — one smoother, one jumpier — and notice how return alone hides very different risk.

The common mistake

Judging a strategy on return alone. Two strategies can end at the same value while one barely dipped and the other nearly collapsed. Ignoring drawdown and other risk measures is how people get blindsided — which is the whole reason risk is a first-class concept.

What it unlocks

Risk measurement builds on operators and f-strings, and completes the picture of a backtest.