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
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:
# 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}%")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.
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:
values = [1000, 1050, 1020]
peak = values[0]
for v in values:
peak = max(peak, v)
print(f"value {v}, peak so far {peak}")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.
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:
# 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}%")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.
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.