A perda que você realmente sente
A volatilidade trata igualmente a alta e a queda, mas o investidor vive com outro número: o quanto sua conta está abaixo do melhor valor que já teve. Esse é o drawdown. Se uma carteira subiu a 110 e agora vale 98, está 10,9% abaixo do pico, diga o que disser a volatilidade diária. O drawdown máximo é o buraco mais fundo do histórico, e o tempo submerso é quanto demorou para sair dele. Clientes saem e gestores de risco cortam limites em drawdowns, não em desvios padrão.
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]O buraco mais fundo
cummax() dá o pico corrente em uma chamada. Dividir o preço por ele e subtrair 1 dá o drawdown de cada dia, sempre zero ou negativo. O drawdown máximo é simplesmente o mínimo dessa série, e idxmin() diz quando ocorreu. Os drawdowns são assimétricos de um jeito que importa: uma perda de 20% exige um ganho de 25% para voltar, 1 / (1 - 0,20) - 1, e uma perda de 50% exige 100%. Por isso os gestores de risco lutam para manter os drawdowns rasos.
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.0O tempo submerso
A profundidade é metade da história; a duração é a outra metade. Uma estratégia pode perder só 8% e ainda passar três anos abaixo da sua antiga máxima, o que testa a paciência de qualquer investidor. Um dia está submerso enquanto seu drawdown é negativo, e uma nova máxima encerra o trecho. Para achar o trecho mais longo com pandas, marque os dias submersos com uma comparação, rotule cada sequência de valores iguais com (flag != flag.shift()).cumsum() e conte dentro de cada sequência. Um laço simples que conta e zera funciona igualmente bem e é fácil de ler.
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.83peak = prices.cummax()edrawdown = prices / peak - 1: zero em nova máxima, negativo abaixo dela.- O drawdown máximo é
drawdown.min(); uma perda dexexige um ganho de1 / (1 - x) - 1para se recuperar. - O tempo submerso é a mais longa sequência de dias com drawdown negativo; o pior drawdown de um backtest é um limite inferior, não uma garantia.
A seguir: os geradores de números aleatórios e as sementes, a base de toda simulação.
Lab: o que é avaliado
- Acompanhar a máxima atingida com
running_peak25 pts - Medir a distância do pico com
drawdown_series25 pts - Encontrar a pior queda com
max_drawdown25 pts - Contar a mais longa espera por uma nova máxima com
longest_underwater25 pts