Strasmore Research
Formazione Matt ConnorDi Matt Connor · Aggiornato 2026-08-08

Maximum drawdown: profondità e tempi di recupero

Il maximum drawdown misura il calo dal picco al minimo. Scopri quando è profondo e perché il ritorno a un nuovo massimo può richiedere più tempo.

Il maximum drawdown è il calo più ampio, dal picco al minimo, registrato in una serie di valori, espresso come percentuale del picco progressivo. Un conto che sale a 120.000 dollari, scende a 84.000 e poi recupera ha un maximum drawdown del 30%. Il dato resta invariato per sempre, perché registra la fase peggiore dell’intera storia, non la situazione attuale. La maggior parte delle persone cita solo l’entità del calo. Nello stesso dato sono comprese altre due misure: il tempo che intercorre tra il picco e il minimo e il periodo, spesso molto più lungo, che dal minimo porta al raggiungimento di un nuovo massimo.

Come si calcola il maximum drawdown?

Il calcolo richiede un solo passaggio sulla serie. A ogni osservazione si aggiorna il massimo progressivo, si confronta il valore corrente con quel massimo e si conserva la lettura peggiore registrata fino a quel momento.

  1. Imposta il massimo progressivo sul primo valore.
  2. A ogni nuovo valore, aggiorna il massimo se il valore è superiore.
  3. Calcola il drawdown in quel punto: valore diviso per il massimo, meno uno.
  4. Conserva la lettura più negativa. Questo è il maximum drawdown.

Nel calcolo non entrano una serie di rendimenti, una stima della volatilità o un’ipotesi sulla distribuzione. Il ciclo opera su una semplice lista di numeri usando la standard library di Python. Su una macchina Ubuntu appena installata, installa prima l’interprete:

apt-get update && apt-get install -y python3

Salva il ciclo come maxdd.py. I prezzi riportati di seguito costituiscono una serie fittizia di dieci punti, abbastanza breve da poter essere verificata a mano:

prices = [100.0, 104.0, 98.0, 92.0, 95.0, 88.0, 90.0, 101.0, 99.0, 106.0]

peak = prices[0]
max_dd = 0.0

for price in prices:
    if price > peak:
        peak = price
    drawdown = price / peak - 1.0
    if drawdown < max_dd:
        max_dd = drawdown

print(f"maximum drawdown: {max_dd * 100:.1f}%")

Esegui python3 maxdd.py: il programma restituisce maximum drawdown: -15.4%. Verificalo a mano: il picco precedente al minimo è 104, il minimo è 88 e 88 diviso per 104 equivale a 0,846. La serie termina poi a 106, un nuovo massimo storico, ma il 15,4% resta invariato. Il maximum drawdown è la registrazione permanente del momento peggiore osservato in un campione e non dice nulla sul livello al quale si trova oggi la serie. Nello stesso ciclo si possono aggiungere altre due variabili per rilevare la tempistica: l’indice al quale è stato fissato il picco e l’indice al quale si è verificata la lettura peggiore.

Come appare una curva di drawdown

Una curva di drawdown mantiene il passaggio 3 in ogni punto, invece di ridurlo a un unico valore minimo. La linea resta a zero ogni volta che la serie registra un nuovo massimo e rimane sotto lo zero negli intervalli successivi. Per questo il divario viene definito “underwater”. Questa è la curva di SPY, un ETF che replica l’S&P 500, calcolata sulle chiusure di fine mese a partire da gennaio 2016.

QueryCurva underwater di SPY: chiusure di fine mese rispetto al massimo progressivo, 2016–2026
L'esatto SQL dietro ogni numero
WITH monthly AS (
    SELECT toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2016-01-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY month_start
),
runs AS (
    SELECT month_start,
           close_px,
           max(close_px) OVER (ORDER BY month_start
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak
    FROM monthly
)
SELECT formatDateTime(month_start, '%Y-%m') AS month,
       round(100 * (close_px / running_peak - 1), 2) AS drawdown_pct
FROM runs
ORDER BY month_start
Run this yourself

Su 127 rilevazioni di fine mese, la linea resta per lunghi periodi vicina allo zero e scende in alcune profonde depressioni. L’ultimo mese disponibile si attesta a -1.27% rispetto al massimo progressivo. La forma della curva evidenzia due aspetti. I recuperi sono salite graduali, non rimbalzi improvvisi. Inoltre, ogni nuovo massimo riporta la curva a zero e cancella visivamente la memoria di quanto accaduto prima.

Quanto tempo serve per recuperare da un drawdown?

All’interno di un singolo drawdown ci sono tre numeri distinti. Considerarli come un unico dato è l’errore di lettura più comune in una tabella della performance:

  1. Profondità: la distanza percentuale dal picco al minimo.
  2. Tempo per raggiungere il minimo: i giorni di calendario dal picco al minimo.
  3. Tempo di recupero: i giorni di calendario dal minimo al raggiungimento di un nuovo massimo.

Il terzo intervallo è generalmente il più lungo ed è quello che un dato sintetico non mostra mai. Il pannello seguente ricostruisce ogni fase completata in drawdown per SPY dal 2016, sulla base dei prezzi di chiusura giornalieri. Una fase inizia nel giorno in cui viene raggiunto un picco e termina nel giorno in cui il fondo chiude per la prima volta sopra quel livello.

QueryDrawdown completati di SPY dal 2016: profondità, giorni di discesa, giorni di recupero
L'esatto SQL dietro ogni numero
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2016-01-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY session_date
),
runs AS (
    SELECT session_date,
           close_px,
           max(close_px) OVER (ORDER BY session_date
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak
    FROM daily
),
episodes AS (
    SELECT running_peak AS peak_px,
           min(session_date) AS peak_date,
           argMin(session_date, close_px) AS trough_date,
           min(close_px) AS trough_px
    FROM runs
    GROUP BY running_peak
),
sequenced AS (
    SELECT peak_px,
           peak_date,
           trough_date,
           trough_px,
           leadInFrame(peak_date, 1) OVER (ORDER BY peak_date
                                           ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS recovery_date
    FROM episodes
)
SELECT formatDateTime(peak_date, '%b %Y') AS episode_label,
       round(100 * (1 - trough_px / peak_px), 1) AS fall_from_peak_pct,
       dateDiff('day', peak_date, trough_date) AS days_to_trough,
       dateDiff('day', trough_date, recovery_date) AS days_to_new_high,
       dateDiff('day', peak_date, recovery_date) AS days_underwater
FROM sequenced
WHERE recovery_date > trough_date
  AND round(100 * (1 - trough_px / peak_px), 1) >= 5
ORDER BY fall_from_peak_pct DESC
LIMIT 8
Run this yourself

La fase completata più profonda dell’elenco inizia in Feb 2020 e raggiunge 34.2% al di sotto del precedente picco. Il calo è durato 33 giorni di calendario, mentre il recupero ha richiesto 148 giorni, per un totale di 181 giorni sotto il precedente massimo. La voce successiva, del Jan 2022, è meno profonda, pari a 25.4%, ma ha mantenuto il fondo al di sotto del precedente massimo per 746 giorni.

Profondità e durata sono grandezze completamente diverse. Un calo violento seguito da un rapido ritorno e una discesa contenuta che dura anni vengono entrambi compressi in un’unica percentuale. Il crollo di marzo 2020 è l’esempio classico della prima dinamica, mentre come i mercati recuperano dai crolli segue la seconda fase del percorso.

Perché il maximum drawdown peggiora quanto più a lungo lo si misura

Il maximum drawdown è un massimo, quindi l’aggiunta di dati storici può solo lasciarlo invariato o renderlo più profondo. In ogni punto, il picco progressivo osservato su una finestra più lunga è almeno pari a quello osservato su una finestra più breve. Questo determina il risultato aritmetico. La misurazione dello stesso fondo su cinque finestre, tutte con termine il 31 luglio 2026, mostra l’effetto.

QueryStesso fondo, cinque finestre di lookback: maximum drawdown di SPY per durata del campione al 31 luglio 2026
L'esatto SQL dietro ogni numero
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2016-07-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY session_date
),
scoped AS (
    SELECT arrayJoin([1, 2, 3, 5, 10]) AS lookback_years,
           session_date,
           close_px
    FROM daily
),
windowed AS (
    SELECT lookback_years, session_date, close_px
    FROM scoped
    WHERE session_date >= subtractYears(toDate('2026-07-31'), lookback_years)
),
runs AS (
    SELECT lookback_years,
           session_date,
           close_px,
           max(close_px) OVER (PARTITION BY lookback_years ORDER BY session_date
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak
    FROM windowed
)
SELECT concat(toString(lookback_years), if(lookback_years = 1, ' year', ' years')) AS lookback,
       round(100 * max(1 - close_px / running_peak), 1) AS max_drawdown_pct,
       formatDateTime(argMax(session_date, 1 - close_px / running_peak), '%b %Y') AS worst_point
FROM runs
GROUP BY lookback_years
ORDER BY lookback_years
Run this yourself

Sulla finestra mobile di 1 year, la perdita peggiore è stata di 9.1%. Estendendo la stessa serie a 10 years, il dato passa a 34.2%, con il punto peggiore in Mar 2020. Il fondo non è diventato più rischioso. È aumentato il periodo osservato.

La stessa aritmetica distorce i confronti tra strategie. Un backtest di tre anni e uno di vent’anni possono descrivere un comportamento identico e riportare comunque drawdown molto diversi. Per questo il dato ha poco significato se non si indica la finestra di misurazione. Look ahead bias nel backtesting illustra un altro modo in cui un backtest dall’aspetto impeccabile può fornire un’immagine eccessivamente favorevole di sé.

Maximum drawdown e volatilità

La volatilità misura l’ampiezza tipica di un movimento giornaliero in una delle due direzioni. Il maximum drawdown misura uno specifico percorso al ribasso. Sono entrambi indicatori di rischio, ma rispondono a domande diverse. Il riquadro li mette a confronto per otto titoli molto conosciuti nei cinque anni fino al 31 luglio 2026, con la volatilità annualizzata calcolata sulle chiusure giornaliere.

QueryMaximum drawdown rispetto alla volatilità annualizzata: otto large cap, cinque anni al 31 luglio 2026
L'esatto SQL dietro ogni numero
WITH daily AS (
    SELECT ticker,
           toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('SPY', 'AAPL', 'MSFT', 'KO', 'JNJ', 'CVX', 'VZ', 'PG')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2021-08-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, session_date
),
runs AS (
    SELECT ticker,
           session_date,
           close_px,
           max(close_px) OVER (PARTITION BY ticker ORDER BY session_date
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_peak,
           lagInFrame(close_px, 1) OVER (PARTITION BY ticker ORDER BY session_date
                                         ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close
    FROM daily
)
SELECT ticker,
       round(100 * max(1 - close_px / running_peak), 1) AS max_drawdown_pct,
       round(100 * sqrt(252) * stddevSampIf(close_px / prev_close - 1, prev_close > 0), 1) AS annualized_volatility_pct
FROM runs
GROUP BY ticker
HAVING countIf(prev_close > 0) > 20
ORDER BY max_drawdown_pct DESC
Run this yourself

Leggi insieme le due colonne. VZ registra il calo più profondo del gruppo, pari a 45.4%, a fronte di una volatilità annualizzata di 22.6%. All’estremo opposto, KO è sceso del 20.9%, con una volatilità di 16.7%. La volatilità è una media calcolata su ogni giornata della finestra temporale e le medie attenuano l’effetto di un calo lento e prolungato. Il drawdown è il peggior percorso effettivamente seguito dal prezzo. Una posizione poco volatile che scende gradualmente per due anni può registrare un drawdown profondo pur mostrando una volatilità non significativa.

L’anomalia della bassa volatilità analizza il comportamento dei titoli più stabili su orizzonti lunghi, mentre il dimensionamento della posizione secondo il criterio di Kelly mostra come la tolleranza al drawdown si traduca nella dimensione di una posizione.

Cosa non può dirti il numero

Ci sono quattro limiti da tenere presenti:

  1. È un unico percorso realizzato. Lo stesso processo avrebbe potuto produrre un dato molto peggiore con un’estrazione diversa.
  2. Dipende dalla lunghezza del campione. Due drawdown sono confrontabili solo su finestre temporali omogenee.
  3. Dipende dalla frequenza di campionamento. Le chiusure di fine mese nascondono i minimi intraday e producono un numero inferiore rispetto alle chiusure giornaliere sullo stesso periodo.
  4. Non contiene informazioni prospettiche. Essere il 30% sotto un picco non dice nulla sulla possibilità che il 31° punto venga raggiunto domani.

È proprio quest’ultimo limite a rendere necessaria, per un sistema automatizzato, una policy sul drawdown anziché una semplice soglia. «Arresta il sistema al 20%» risponde a una domanda, ma ne lascia senza risposta altre due: cosa succede alle posizioni aperte al momento dell’arresto e quali evidenze consentono di riattivare il sistema. Circuit breaker per i trading bot analizza la struttura di questa policy. Il costo di una regola arbitraria e rigida emerge nella fase di recupero: un sistema che si arresta vicino al minimo e riparte in ritardo resta fuori dal rialzo. Perdere le giornate migliori quantifica questo effetto.

Prima di confrontare due dati, è importante considerare un dettaglio di misurazione: il drawdown calcolato sulla serie dei prezzi di un fondo e quello calcolato su un conto reale differiscono ogni volta che entrano o escono disponibilità liquide e ogni volta che vengono accreditati dividendi. come si misurano i rendimenti mensili tratta questo aspetto in modo completo.

FAQ sul maximum drawdown

Qual è un buon maximum drawdown?

Non esiste un valore universale. Servono un benchmark e un periodo di misurazione. Nei dieci anni fino al 31 luglio 2026, il peggior calo di SPY da un massimo progressivo, misurato sulle chiusure giornaliere, è stato pari a 34.2%. Una strategia che indica un valore inferiore su un campione più breve non è necessariamente più sicura.

Il maximum drawdown è uguale alla volatilità?

No. La volatilità descrive il movimento giornaliero tipico. Il maximum drawdown descrive il peggior percorso da un picco a un minimo. Un investimento a bassa volatilità che scende gradualmente per due anni può registrare un drawdown più profondo di un investimento volatile che continua a segnare nuovi massimi.

Quanto tempo serve per recuperare un maximum drawdown?

Dipende dai casi. Spesso la risalita richiede più tempo della discesa. Per SPY dal 2016, il periodo completato più lungo al di sotto di un precedente massimo è durato 181 giorni di calendario, dal picco al nuovo massimo. Di questi, 33 sono stati trascorsi in calo.

Il maximum drawdown prevede le perdite future?

No. Descrive un percorso già verificatosi. Inoltre, un campione più lungo produce quasi sempre un valore più elevato. Registra ciò che una strategia ha superato finora, senza porre alcun limite a ciò che può accadere in seguito.


Ogni pannello precedente contiene la query SQL sottostante. Aprine uno, sostituisci il ticker o il periodo e applica lo stesso calcolo del drawdown sul terminale Strasmore.

#risk#drawdown#backtesting#performance metrics#python