Maximum drawdown : ampleur et durée de récupération
Découvrez comment mesurer le maximum drawdown, distinguer la profondeur de la baisse et comprendre pourquoi le retour à un nouveau plus haut peut prendre plus de temps.
Le maximum drawdown est la plus forte baisse entre un point haut et un point bas d’une série de valeurs, mesurée en pourcentage du plus haut atteint jusque-là. Un compte qui monte à 120 000 dollars, baisse à 84 000 dollars, puis se redresse affiche un maximum drawdown de 30 %. Ce chiffre reste inchangé, car la statistique enregistre la pire période de toute l’historique, et non la situation actuelle. La plupart des investisseurs citent uniquement l’ampleur de la baisse. Deux autres mesures sont intégrées à cette même statistique : le temps écoulé entre le point haut et le point bas, puis la période, bien plus longue, nécessaire pour revenir de ce point bas à un nouveau plus haut.
Comment calculer le drawdown maximal ?
Le calcul se fait en un seul passage sur la série. À chaque observation, vous mettez à jour le plus-haut courant, mesurez la valeur actuelle par rapport à ce niveau, puis conservez la pire valeur observée jusque-là.
- Définissez le pic courant comme étant la première valeur.
- À chaque nouvelle valeur, relevez le pic si cette valeur est supérieure.
- Calculez le drawdown à ce point : valeur divisée par le pic, moins un.
- Conservez la valeur la plus négative. Il s’agit du drawdown maximal.
Aucune série de rendements, aucune estimation de la volatilité et aucune hypothèse de distribution n’interviennent. La boucle s’exécute sur une simple liste de nombres avec la bibliothèque standard Python. Sur une nouvelle machine Ubuntu, installez d’abord l’interpréteur :
apt-get update && apt-get install -y python3
Enregistrez la boucle sous maxdd.py. Les prix ci-dessous constituent une série fictive de dix points, suffisamment courte pour être vérifiée manuellement :
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}%")
Exécutez python3 maxdd.py : le programme affiche maximum drawdown: -15.4%. Vérifiez le résultat manuellement : le pic précédant le point bas est de 104, le point bas est de 88, et 88 divisé par 104 donne 0,846. La série se termine ensuite à 106, un plus-haut historique, mais le drawdown de 15,4 % reste inchangé. Le drawdown maximal constitue un relevé définitif du pire moment observé dans un échantillon. Il ne dit rien de la position actuelle de la série. Deux variables supplémentaires dans la même boucle permettent d’enregistrer le moment où le pic a été atteint et celui où la pire valeur a été observée.
À quoi ressemble une courbe de drawdown
Une courbe de drawdown conserve l’étape 3 à chaque point, au lieu de la réduire à une seule valeur correspondant au pire niveau. La ligne reste à zéro chaque fois que la série inscrit un nouveau plus-haut. Elle passe sous zéro entre deux plus-hauts. C’est pourquoi l’écart est qualifié d’« underwater ». Voici cette courbe pour SPY, un ETF répliquant le S&P 500, calculée à partir des clôtures de fin de mois depuis janvier 2016.
Le SQL exact derrière chaque chiffre
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_startSur 127 observations de fin de mois, la ligne reste longtemps proche de zéro et plonge à quelques reprises dans des creux marqués. Le dernier mois disponible se situe à -1.27% du plus-haut cumulé. Deux caractéristiques de la forme sont particulièrement instructives. Les reprises sont lentes et progressives, plutôt que de prendre la forme de rebonds rapides. Chaque nouveau plus-haut ramène la courbe à zéro et efface visuellement la trace de ce qui l’a précédé.
Combien de temps faut-il pour se remettre d’un drawdown ?
Un même drawdown recouvre trois chiffres distincts. Les confondre est l’erreur d’interprétation la plus courante dans un tableau de performance :
- Amplitude : la baisse de la valeur par rapport à son plus-haut, en pourcentage.
- Durée jusqu’au point bas : le nombre de jours calendaires entre le sommet et le creux.
- Durée de récupération : le nombre de jours calendaires entre ce creux et un nouveau plus-haut.
La troisième mesure est généralement la plus longue. C’est aussi celle qu’un chiffre affiché en titre ne montre jamais. Le panneau ci-dessous reconstitue chaque phase complète passée sous l’eau pour le SPY depuis 2016, à partir des cours de clôture quotidiens. Une phase commence le jour où un sommet est atteint et se termine le jour où le fonds clôture pour la première fois au-dessus de ce niveau.
Le SQL exact derrière chaque chiffre
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 8La phase complète la plus profonde de la liste commence en Feb 2020 et atteint 34.2% sous le sommet précédent. La baisse a duré 33 jours calendaires, puis le retour au sommet a pris 148 jours, soit 181 jours au total sous l’eau. L’entrée suivante, à partir de Jan 2022, est moins marquée, à 25.4%, mais le fonds est resté sous son ancien plus-haut pendant 746 jours.
Amplitude et durée sont deux choses totalement différentes. Une chute brutale suivie d’un aller-retour rapide et une baisse modérée qui s’étale sur plusieurs années se résument toutes deux à un seul pourcentage. Le krach de mars 2020 illustre parfaitement le premier cas, tandis que la manière dont les marchés se remettent des krachs décrit la deuxième phase du mouvement.
Pourquoi le maximum drawdown s’aggrave avec la durée de mesure
Le maximum drawdown est un maximum. Ajouter de l’historique peut donc seulement le laisser inchangé ou l’accentuer. Dans une fenêtre plus longue, le plus haut historique à chaque date est au moins aussi élevé que dans une fenêtre plus courte. Le calcul est donc mécanique. Mesurer le même fonds sur cinq fenêtres se terminant toutes le 31 juillet 2026 permet d’observer cet effet.
Le SQL exact derrière chaque chiffre
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_yearsSur la fenêtre glissante de 1 year, la baisse maximale mesurée atteint 9.1%. En étendant la même série à 10 years, le chiffre passe à 34.2%, le point bas étant atteint en Mar 2020. Le fonds n’est pas devenu plus risqué. L’échantillon est simplement plus long.
Le même calcul fausse les comparaisons entre stratégies. Un backtest de trois ans et un backtest de vingt ans peuvent décrire un comportement identique tout en affichant des drawdowns très différents. Ce chiffre n’a donc guère de sens sans indication de la fenêtre de mesure. Le look-ahead bias dans les backtests présente une autre façon dont un backtest apparemment propre peut se valoriser à tort.
Drawdown maximal et volatilité
La volatilité mesure l’ampleur habituelle d’un mouvement quotidien, à la hausse comme à la baisse. Le drawdown maximal mesure une trajectoire baissière précise. Ce sont deux indicateurs de risque, qui répondent à des questions différentes. Le tableau les met côte à côte pour huit valeurs bien connues sur les cinq années allant jusqu’au 31 juillet 2026, avec une volatilité annualisée calculée à partir des cours de clôture quotidiens.
Le SQL exact derrière chaque chiffre
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 DESCLisez les deux colonnes ensemble. VZ affiche la baisse la plus profonde du groupe, à 45.4%, contre une volatilité annualisée de 22.6%. À l’autre extrémité, KO a reculé de 20.9%, avec une volatilité de 16.7%. La volatilité est une moyenne calculée sur chaque journée de la période, et les moyennes atténuent l’effet d’une longue baisse progressive. Le drawdown correspond à la pire trajectoire effectivement suivie par le cours. Une position peu volatile qui dérive à la baisse pendant deux ans peut afficher un drawdown profond malgré une volatilité peu remarquable.
L’anomalie de faible volatilité examine le comportement des valeurs les plus calmes sur de longues périodes, tandis que le dimensionnement des positions selon le critère de Kelly montre comment une tolérance au drawdown se traduit en taille de position.
Ce que le chiffre ne permet pas de savoir
Quatre limites méritent d’être gardées à l’esprit :
- Il s’agit d’une trajectoire réalisée. Le même processus aurait pu produire un résultat bien plus mauvais avec un autre tirage.
- Il dépend de la longueur de l’échantillon. Deux drawdowns ne se comparent que sur des fenêtres de durée comparable.
- Il dépend de la fréquence d’échantillonnage. Les clôtures de fin de mois masquent les points bas intraday et produisent un chiffre inférieur à celui obtenu avec des clôtures quotidiennes sur la même période.
- Il ne contient aucune information prospective. Se situer 30 % sous un sommet ne dit rien sur la possibilité que le 31e point survienne demain.
Cette dernière limite explique pourquoi un système automatisé a besoin d’une politique de drawdown plutôt que d’un seuil de drawdown. « Suspendre à 20 % » répond à une question et en laisse deux sans réponse : que faire des positions ouvertes au moment de la suspension, et quels éléments permettent de réactiver le système ? Disjoncteurs pour les bots de trading analyse la structure de cette politique. Le coût d’une règle uniforme et arbitraire se manifeste pendant la phase de reprise : un système qui s’arrête près du point bas et redémarre tard reste à l’écart de la remontée. Rater les meilleures séances chiffre ce mécanisme.
Un détail de mesure doit être pris en compte avant de comparer deux chiffres : le drawdown calculé sur la série de cours d’un fonds et celui calculé sur un compte réel diffèrent dès lors que des liquidités entrent ou sortent, ou que des dividendes sont versés. comment les rendements mensuels sont mesurés traite ce point en détail.
FAQ sur le maximum drawdown
Qu’est-ce qu’un bon maximum drawdown ?
Il n’existe pas de valeur universelle. Il faut un indice de référence et une période de mesure. Au cours des dix années allant jusqu’au 31 juillet 2026, la pire baisse de SPY depuis un plus-haut glissant, mesurée sur les clôtures quotidiennes, a atteint 34.2%. Une stratégie affichant un chiffre inférieur sur un échantillon plus court n’a pas nécessairement été moins risquée.
Le maximum drawdown est-il la même chose que la volatilité ?
Non. La volatilité décrit l’amplitude habituelle des mouvements quotidiens. Le maximum drawdown décrit le pire parcours entre un plus-haut et un plus-bas. Une position peu volatile qui baisse régulièrement pendant deux ans peut afficher un drawdown plus profond qu’une position très irrégulière qui continue d’inscrire de nouveaux plus-hauts.
Combien de temps faut-il pour récupérer après un maximum drawdown ?
Cela varie. La remontée constitue souvent la moitié la plus longue de l’épisode. Pour SPY depuis 2016, la plus longue période achevée sous un précédent plus-haut a duré 181 jours calendaires entre le sommet et le nouveau plus-haut. 33 de ces jours ont été consacrés à la baisse.
Le maximum drawdown permet-il de prévoir les pertes futures ?
Non. Il décrit un parcours déjà réalisé. Une période d’observation plus longue produit presque toujours un chiffre plus élevé. Il indique ce qu’une stratégie a supporté jusqu’à présent, sans fixer de limite à ce qui peut suivre.
Chaque panneau ci-dessus contient la requête SQL correspondante. Ouvrez-en un, remplacez le ticker ou la période, puis appliquez le même calcul du drawdown sur le terminal Strasmore.