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

Maximum drawdown: profondità e tempo di recupero

Scopri come si misura il maximum drawdown, cosa indica un calo profondo e perché il recupero fino a un nuovo massimo può richiedere più tempo.

Il maximum drawdown è il calo più ampio dal picco al minimo di una serie di valori, misurato come percentuale rispetto al picco progressivo. Un conto che sale a 120.000 dollari, scende a 84.000 e poi recupera registra un maximum drawdown del 30% e conserva questo valore per sempre, perché la statistica misura la fase peggiore dell’intera storia, non la situazione attuale. La maggior parte delle persone indica solo l’entità del calo. Nella stessa statistica rientrano però altri due valori: il tempo dal picco al minimo e il periodo, spesso molto più lungo, che va dal minimo 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 il peggior dato rilevato fino a quel momento.

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

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

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

Salva il ciclo come maxdd.py. I prezzi riportati di seguito formano una serie fittizia di dieci punti, abbastanza contenuta 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 risultato è 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 registra in modo permanente il momento peggiore del campione e non dice nulla sul livello a cui si trova oggi la serie. Nello stesso ciclo, altre due variabili consentono di registrare il momento in cui si è formato il picco e quello in cui è stato rilevato il dato peggiore.

Come appare una curva di drawdown

Una curva di drawdown conserva 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 tra un massimo e l’altro. Per questo il divario viene definito «underwater». Di seguito è riportata la curva di SPY, un ETF che replica l’indice S&P 500, calcolata sui prezzi di chiusura di fine mese a partire da gennaio 2016.

QueryCurva underwater di SPY: chiusura 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 rientranze. L’ultimo mese disponibile si colloca a -1.27% rispetto al massimo progressivo. La forma evidenzia due aspetti. I recuperi sono salite lente, non rimbalzi improvvisi. Inoltre, ogni nuovo massimo riporta la curva a zero e cancella dalla rappresentazione visiva ciò che è accaduto prima.

Quanto tempo serve per recuperare da un drawdown?

In un singolo drawdown si celano tre numeri distinti. Trattarli come un unico dato è l’errore di lettura più comune di una tabella delle performance:

  1. Profondità: la distanza percentuale tra il picco e il 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 è di norma il più lungo. È anche quello che un dato sintetico non mostra mai. Il pannello seguente ricostruisce ogni fase completa in territorio negativo di SPY dal 2016, sulla base delle chiusure giornaliere. Una fase inizia nel giorno in cui viene fissato 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 completa più profonda dell’elenco inizia in Feb 2020 e raggiunge un livello 34.2% inferiore al picco precedente. Il calo dura 33 giorni di calendario, mentre il recupero richiede 148 giorni. Il fondo resta quindi sotto il precedente massimo per 181 giorni complessivi. La voce successiva, del Jan 2022, è meno profonda, con un calo del 25.4%, e mantiene il fondo al di sotto del vecchio massimo per 746 giorni.

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

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

Il maximum drawdown è un valore massimo. Aggiungere dati storici può quindi lasciarlo invariato o renderlo più profondo. In una finestra più lunga, il massimo progressivo è almeno pari, in ogni momento, a quello osservato in una finestra più breve. Questo determina il risultato. Misurare lo stesso fondo su cinque finestre che terminano tutte il 31 luglio 2026 mostra l’effetto.

QueryStesso fondo, cinque finestre di lookback: maximum drawdown di SPY per lunghezza 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

Nella finestra trailing di 1 year, la perdita peggiore è stata pari a 9.1%. Estendendo la stessa serie a 10 years, il dato diventa 34.2%, con il punto di minimo in Mar 2020. Il fondo non è diventato più rischioso. Il campione è semplicemente più lungo.

La stessa dinamica distorce il confronto tra strategie. Un backtest di three years e uno di twenty years possono descrivere un comportamento identico e riportare comunque drawdown molto diversi. Senza indicare la finestra di osservazione, il dato ha quindi un significato limitato. Il look-ahead bias nel backtesting illustra un altro modo in cui un backtest dall’aspetto impeccabile può risultare più favorevole di quanto dovrebbe.

Maximum drawdown e volatilità

La volatilità misura l’ampiezza tipica di un movimento giornaliero, in entrambe le direzioni. Il maximum drawdown misura invece uno specifico percorso al ribasso. Sono entrambi indicatori di rischio, ma rispondono a domande diverse. Il pannello li mette a confronto per otto titoli molto noti, nell’arco dei cinque anni terminati il 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 del 22.6%. All’estremo opposto, KO ha perso il 20.9%, con una volatilità del 16.7%. La volatilità è una media di tutte le sedute dell’intervallo considerato, e le medie attenuano l’effetto di un ribasso lungo e graduale. Il drawdown rappresenta invece il peggior percorso effettivamente seguito dal prezzo. Un investimento poco volatile che scende lentamente per due anni può registrare un drawdown profondo con una volatilità apparentemente ordinaria.

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. Rappresenta un solo percorso realizzato. Lo stesso processo avrebbe potuto produrre un dato molto peggiore con un’altra estrazione.
  2. Dipende dalla durata del campione. Per questo due drawdown sono confrontabili solo su finestre temporali omogenee.
  3. Dipende dalla frequenza di campionamento. Le chiusure di fine mese nascondono i minimi infragiornalieri e producono un dato più contenuto rispetto alle chiusure giornaliere sullo stesso periodo.
  4. Non contiene informazioni prospettiche. Trovarsi il 30% sotto un picco non dice nulla sulla possibilità che il 31° punto venga raggiunto domani.

È quest’ultimo limite a spiegare perché un sistema automatizzato ha bisogno di una policy sul drawdown, non di una semplice soglia. “Arresto al 20%” risponde a una domanda e ne lascia due senza risposta: 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 ferma 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 intervallo temporale di riferimento. Nei dieci anni fino al 31 luglio 2026, il peggior calo di SPY da un picco progressivo, misurato sulle chiusure giornaliere, è stato di 34.2%. Una strategia che indica un valore inferiore su un campione più breve non è necessariamente più sicura.

Il maximum drawdown coincide con la volatilità?

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

Quanto tempo serve per recuperare un maximum drawdown?

Dipende dal caso. 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 permette di prevedere le perdite future?

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


Ogni pannello qui sopra conserva la query SQL sottostante. Aprine uno, sostituisci il ticker o l’intervallo temporale ed esegui lo stesso calcolo del drawdown sul terminale Strasmore.

#risk#drawdown#backtesting#performance metrics#python