GARCH(1,1) : prévoir la volatilité crypto
Ouvrez un graphique des rendements journaliers de BTC et vous remarquerez quelque chose que les manuels sur les marches aléatoires ne vous ont jamais préparé à voir : le calme et le chaos arrivent par grappes. Une journée à -6% est rarement isolée. Elle s'inscrit dans une semaine de variations de 4 à 8%, puis le marché souffle et dérive pendant un mois de séances somnolentes à 1% avant la prochaine tempête. Les rendements eux-mêmes semblent proches de l'imprévisible — impossible de dire de façon fiable si demain sera haussier ou baissier — mais leur amplitude est, elle, profondément prévisible. La turbulence d'aujourd'hui en dit long sur celle de demain.
Presque tous les outils de gestion du risque qu'un trader utilise supposent implicitement le contraire. Black-Scholes valorise une option avec un constant unique. Une Value-at-Risk statique multiplie une seule estimation de volatilité par un quantile de la loi normale. Un stop-loss fixe à 3% traite un mardi plat et mort et les heures entourant une publication FOMC ou un dé-pegging d'un exchange majeur comme s'ils portaient le même risque. Chacun de ces outils échoue exactement de la même manière : il réduit une quantité qui varie dans le temps à une constante, puis est pris au dépourvu quand cette constante se met à bouger.
Cet article est la partie 1 d'une série en quatre parties sur la modélisation de la volatilité pour les cryptos. Il pose les fondations : le modèle GARCH(1,1), pourquoi il s'ajuste si bien aux rendements crypto, comment l'estimer honnêtement par maximum de vraisemblance avec la bibliothèque arch, et comment transformer une prévision de variance conditionnelle en deux éléments immédiatement utiles — une taille de position et une largeur de stop qui respirent toutes deux avec le marché. La partie 2 ajoute l'asymétrie et les queues épaisses, la partie 3 passe au multivarié, et la partie 4 assemble le backtest complet de ciblage de volatilité. Nous gardons ici l'application volontairement simple ; la stratégie honnête, validée en walk-forward, fait l'objet de la partie 4.
Les faits stylisés des rendements crypto
Avant de modéliser quoi que ce soit, il vaut la peine d'être précis sur ce que l'on cherche à reproduire. Les rendements financiers empiriques — actions, changes, et cryptos en particulier — partagent un petit ensemble de régularités statistiques robustes documentées depuis des décennies. On les appelle habituellement des faits stylisés, et trois d'entre eux gouvernent tout ce qui suit.
1. Le regroupement de volatilité (clustering). Les grands mouvements ont tendance à être suivis de grands mouvements (de signe quelconque), et les petits mouvements de petits mouvements. Mandelbrot l'a remarqué dans les prix du coton en 1963. Formellement, alors que les rendements sont proches de la non-corrélation sérielle, les rendements au carré (un proxy de la variance réalisée) montrent une autocorrélation positive forte et à décroissance lente.
2. Les queues épaisses (leptokurtose). La distribution non conditionnelle des rendements a bien plus de masse dans les extrêmes qu'une gaussienne. Là où une loi normale a un kurtosis de 3, les log-rendements journaliers de BTC se situent régulièrement au-dessus de 8-10, et les rendements crypto à plus haute fréquence peuvent être pires encore. Les journées à six sigmas, qu'un modèle normal prédit comme devant survenir environ une fois par million d'années, apparaissent plusieurs fois par décennie.
3. Pas d'autocorrélation linéaire dans les rendements, forte autocorrélation dans les rendements au carré. C'est la signature qui distingue un véritable processus de volatilité d'une tendance triviale. Si vous régressez sur ses propres retards, vous n'obtenez rien d'exploitable. Si vous régressez sur ses retards, vous trouvez un signal clair et persistant. C'est précisément la structure qu'un modèle de variance doit capturer — et précisément ce qu'un modèle à constant jette à la poubelle.
On peut vérifier ces trois faits à l'œil en quelques lignes. Rien ici ne nécessite une source de données spéciale ; utilisez ccxt en production, mais pour un extrait reproductible yfinance convient.
import numpy as np
import pandas as pd
import yfinance as yf
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.stats.diagnostic import acorr_ljungbox
from scipy import stats
px = yf.download("BTC-USD", start="2021-01-01", end="2025-12-31")["Close"]
ret = np.log(px / px.shift(1)).dropna() # log returns
ret = ret.rename("btc")
print(f"Observations: {len(ret)}")
print(f"Ann. volatility: {ret.std() * np.sqrt(365):.2%}")
print(f"Skewness: {stats.skew(ret):.2f}")
print(f"Excess kurtosis: {stats.kurtosis(ret):.2f}") # 0 == Gaussian
lb_ret = acorr_ljungbox(ret, lags=[10], return_df=True)
lb_ret2 = acorr_ljungbox(ret ** 2, lags=[10], return_df=True)
print("\nLjung-Box p-value, returns (lag 10):", float(lb_ret["lb_pvalue"].iloc[0]))
print("Ljung-Box p-value, squared (lag 10):", float(lb_ret2["lb_pvalue"].iloc[0]))
La lecture typique (illustrative — votre fenêtre différera) : un excès de kurtosis bien au-dessus de 3, une p-value de Ljung-Box sur les rendements bruts qui échoue à rejeter l'hypothèse « pas d'autocorrélation », et une p-value sur les rendements au carré qui est pratiquement nulle. Ce dernier contraste est tout l'enjeu. Il n'y a rien à trader dans le signe des rendements à l'horizon journalier, mais il y a une structure considérable dans leur variance, et cette structure est prévisible.
Une remarque sur la nature 24/7 des cryptos. Contrairement aux actions, il n'y a pas de gap nocturne ni de clôture le week-end, donc le « jour » est une barre propre de 24 heures et le facteur d'annualisation est , pas . Le regroupement de volatilité survit aussi aux échelles intrajournalières, ce qui compte si vous faites tourner GARCH sur des barres horaires — les basculements de funding rate et les cascades de liquidation injectent des sursauts de variance nets et groupés que le modèle journalier lisse.
D'ARCH à GARCH
Le problème est maintenant posé avec précision : modéliser une variance qui n'est pas constante mais dépend du passé récent. Le premier modèle à faire cela correctement fut l'ARCH d'Engle (Autoregressive Conditional Heteroskedasticity, 1982), qui lui valut le prix Nobel en 2003.
Écrivons le rendement comme une moyenne conditionnelle plus un choc :
Ici est la variance conditionnelle — la variance de sachant tout ce qui est connu jusqu'à l'instant — et est une innovation standardisée (normale standard dans le cas le plus simple). Le mot « conditionnelle » fait tout le travail : la variance non conditionnelle peut être constante, mais conditionnée sur hier, elle bouge.
L'ARCH() d'Engle fait de la variance d'aujourd'hui une somme pondérée des derniers chocs au carré :
avec et pour garder la variance positive. Cela capture directement le clustering : un grand choc pousse à la hausse, ce qui accroît la probabilité d'un autre grand choc, ce qui maintient la variance élevée. Le problème est la décroissance empirique. La persistance de la volatilité sur les marchés réels s'étend sur de nombreux retards, donc pour l'ajuster un modèle ARCH a besoin d'un grand — souvent 8, 10, ou plus — ce qui signifie estimer un vecteur long et instable de qui a tendance au surajustement.
L'intuition de Bollerslev en 1986 fut d'ajouter un terme qui absorbe toute cette persistance avec un seul paramètre. La récursion du GARCH(1,1) — Generalized ARCH — est :
Trois paramètres, trois interprétations claires :
- — le niveau de base ou plancher. Une constante qui ancre le niveau de variance de long terme. La variance ne décroît jamais en dessous de ce que soutient.
- — la réaction aux nouvelles. À quel point la variance répond violemment à la surprise d'hier . Un grand signifie que la variance conditionnelle est nerveuse et sensible aux chocs.
- — la persistance ou mémoire. Quelle part de la variance d'hier se transmet à aujourd'hui. Un grand signifie que la volatilité est lisse et lente à s'estomper — le calme reste calme, les tempêtes restent tempétueuses.
L'élégance réside dans la récursion. Puisque contenait lui-même un terme , développer en remontant dans le temps montre que GARCH(1,1) est un ARCH() avec des poids à décroissance géométrique sur les chocs au carré passés :
Ainsi, le simple vous achète une mémoire infinie et pondérée de façon exponentielle des chocs passés. C'est pourquoi un simple GARCH(1,1) — trois paramètres — bat régulièrement des modèles ARCH à dix paramètres, et pourquoi il est devenu le cheval de bataille de la modélisation appliquée de la volatilité. C'est en fait un proche cousin de l'estimateur de variance EWMA de RiskMetrics, qui est le cas particulier , avec fixé à 0,94. GARCH le généralise en laissant les données choisir , , et un véritable niveau de retour à la moyenne.
Propriétés : stationnarité, variance de long terme et demi-vie
La récursion GARCH(1,1) a quelques propriétés qui valent la peine d'être dérivées, car elles sont ce qui permet de raisonner sur le modèle plutôt que de simplement l'ajuster aveuglément.
Variance non conditionnelle (de long terme). Supposons que le processus soit stationnaire au sens de la covariance, de sorte que la variance non conditionnelle existe et est constante dans le temps. Prenons l'espérance des deux côtés de la récursion. Puisque :
C'est le niveau vers lequel la volatilité retourne en moyenne. Il n'existe — et n'est positif — que lorsque .
Condition de stationnarité. Cette même inégalité,
est la condition de stationnarité au sens de la covariance pour GARCH(1,1). La quantité est la persistance du processus de variance : c'est le coefficient AR(1) qui gouverne comment un choc de variance décroît vers . Si , la variance non conditionnelle est infinie (ou non définie) et les chocs ne s'éteignent jamais complètement.
On peut voir explicitement le retour à la moyenne. Définissons l'écart de variance . Un peu d'algèbre sur la récursion (en substituant ) donne, en espérance :
L'écart entre la variance actuelle et son niveau de long terme se rétrécit d'un facteur à chaque étape. C'est exactement la prévision multi-étapes que nous utilisons plus loin.
Demi-vie de la volatilité. Combien de temps faut-il pour qu'un choc de variance retourne à mi-chemin vers la normale ? Posons et résolvons :
Pour , la demi-vie est d'environ 13,5 jours ; pour , elle est d'environ 34 jours ; pour , elle est d'environ 69 jours. Ce nombre unique est souvent plus intuitif que les paramètres bruts — il vous dit, dans les unités de vos barres, à quel point la volatilité est collante.
Le problème du quasi-IGARCH dans les cryptos. Voici la particularité propre aux cryptos. Quand vous ajustez GARCH(1,1) aux rendements de BTC ou ETH, vous trouvez presque toujours très proche de 1 — des valeurs de 0,98, 0,99, parfois 0,995 sont courantes. C'est le régime quasi-IGARCH (Integrated GARCH). Cela a de vraies conséquences :
- La demi-vie devient énorme (des semaines à des mois), donc le modèle traite la volatilité comme très persistante et à peine mean-reverting.
- L'estimation de devient extrêmement sensible : un petit changement de de 0,99 à 0,995 double la variance de long terme implicite. Ne faites jamais confiance au point estimé de la vol de long terme dans ce régime sans un intervalle de confiance.
- Les prévisions multi-étapes retournent à la moyenne si lentement que, pour des horizons pratiques inférieurs à quelques semaines, GARCH se comporte presque comme une marche aléatoire de la variance (ce que suppose l'EWMA).
Que la quasi-intégration soit authentique ou un artefact de ruptures structurelles (un changement permanent du niveau de volatilité lu par le modèle comme un seul long épisode persistant) est un vrai débat. C'est une raison de plus pour réajuster sur des fenêtres glissantes plutôt que d'ajuster une seule fois sur tout l'historique, un point auquel nous revenons dans les pièges. La structure de régime en particulier est mieux gérée par un modèle de bascule explicite — voir la détection de régime avec les modèles de Markov cachés, qui est complémentaire à GARCH plutôt qu'un remplacement.
Estimation par maximum de vraisemblance
Les paramètres GARCH sont estimés par maximum de vraisemblance. La logique est directe : étant donné , la récursion produit une trajectoire complète de variances conditionnelles , et sous une distribution supposée pour les innovations , on peut écrire à quel point les rendements observés sont vraisemblables. On choisit alors pour maximiser cette vraisemblance.
Supposons des innovations gaussiennes , donc . La densité conditionnelle d'une observation est
Parce que le modèle est écrit conditionnellement, la vraisemblance jointe se factorise en un produit de densités à un pas, et la log-vraisemblance est une simple somme :
Deux faits structurels à noter. D'abord, apparaît à la fois comme une pénalité ( — le modèle est puni pour avoir prétendu une variance élevée) et dans le résidu standardisé ( — le modèle est puni pour avoir été surpris). L'optimum équilibre les deux, ce qui est ce qui fait que la variance suit bien les données. Ensuite, la récursion nécessite une valeur initiale ; le choix habituel est la variance empirique des rendements, et avec quelques milliers d'observations, la valeur initiale importe à peine.
Il n'existe pas de forme close pour le maximiseur, donc on optimise numériquement (arch utilise une méthode quasi-Newton avec des gradients analytiques ou numériques). La surface de vraisemblance est généralement bien comportée pour GARCH(1,1), mais deux choses posent problème en pratique : les contraintes de positivité () et le comportement près de la frontière quand , où l'optimiseur peut avancer lentement. Les deux sont gérés pour vous par une bonne bibliothèque — et vous devriez en utiliser une. Coder soi-même un MLE GARCH est un bon exercice d'apprentissage mais un mauvais choix de production.
La bibliothèque arch
Le paquet arch de Kevin Sheppard est l'outil standard en Python. L'ajustement complet tient en quatre lignes.
from arch import arch_model
r = ret * 100.0
model = arch_model(r, mean="Constant", vol="Garch", p=1, q=1, dist="normal")
res = model.fit(disp="off")
print(res.summary())
Un mot sur les noms d'arguments, car ils sont une source fréquente de confusion. Dans arch, p est le nombre de variances retardées (termes , l'ordre GARCH) et q est le nombre de résidus au carré retardés (termes , l'ordre ARCH). Donc p=1, q=1 est le GARCH(1,1) que nous avons dérivé. (La notation originale de Bollerslev l'écrit GARCH() avec pour l'ordre ARCH — les deux conventions sont transposées. Fiez-vous à la documentation propre de la bibliothèque, pas à votre mémoire.)
En lisant le résumé, le tableau des coefficients ressemble approximativement à ceci (valeurs illustratives pour les rendements journaliers de BTC, pas une expérience réelle) :
Volatility Model
==========================================================
coef std err t P>|t|
----------------------------------------------------------
omega 0.4821 0.201 2.40 0.016
alpha[1] 0.0912 0.021 4.34 0.000
beta[1] 0.8994 0.024 37.5 0.000
==========================================================
Comment le lire :
alpha[1] + beta[1]= 0,0912 + 0,8994 = 0,9906. Persistance juste en dessous de 1 — le régime quasi-IGARCH, exactement comme annoncé. Demi-vie jours.omega= 0,4821, donc la variance de long terme est en unités pourcentage-au-carré, c'est-à-dire une volatilité journalière de long terme de , soit environ annualisée. C'est un chiffre plausible pour BTC.alphaetbetasont tous deux fortement significatifs.alphaétant petit par rapport àbetaest typique : la variance crypto est surtout de la persistance (mémoire), avec une réaction modeste mais réelle aux chocs récents.
Le piège de la mise à l'échelle ×100
C'est la façon la plus courante d'obtenir du n'importe quoi avec arch, elle mérite donc sa propre sous-section. L'optimiseur fonctionne mieux quand les nombres qu'il voit sont de l'ordre à . Les log-rendements journaliers sont de l'ordre , donc leurs carrés sont de l'ordre et doit être autour de — dans une plage où les gradients numériques perdent en précision et où l'ajustement peut échouer silencieusement à converger ou renvoyer des erreurs-types incohérentes.
La solution est d'ajuster sur des rendements mis à l'échelle par 100 (c'est-à-dire en pourcentage), comme ci-dessus. arch émettra même un DataScaleWarning si vous l'oubliez. Tout ce que vous lisez dans le modèle est alors en unités de pourcentage ou pourcentage-au-carré, et vous devez rééchelonner de façon cohérente :
sigma_pct = np.sqrt(res.forecast(horizon=1).variance.iloc[-1, 0])
sigma_decimal = sigma_pct / 100.0
print(f"1-day-ahead conditional vol: {sigma_decimal:.2%}")
Mélanger des quantités mises à l'échelle et non mises à l'échelle — injecter une volatilité en pourcentage dans une formule de dimensionnement de position qui attend des décimales, par exemple — produit des erreurs d'exactement 100x, faciles à manquer car le code s'exécute sans problème. Choisissez une convention (je garde tout en décimal en dehors de l'ajustement et ne mets à l'échelle qu'à la frontière avec arch) et ne la traversez jamais.
Prévoir la variance conditionnelle
Un modèle ajusté n'est utile que s'il prévoit. GARCH donne des prévisions propres et analytiques à n'importe quel horizon.
Un pas en avant. À l'instant (la fin de l'échantillon), nous connaissons et , donc la variance suivante est déterministe :
Aucune espérance nécessaire — tout ce qui est à droite est observé.
Plusieurs pas en avant. Pour , nous ne connaissons pas encore les chocs intermédiaires, donc nous prenons des espérances conditionnelles. En utilisant (car ), la récursion se réduit à un simple AR(1) dans la variance prévue :
Itérer cela à partir de la prévision à un pas donne la forme close, qui est le résultat de retour à la moyenne dérivé plus tôt écrit explicitement :
Lisez ceci attentivement, car c'est la géométrie de chaque prévision GARCH. La structure par terme de la variance commence à la variance conditionnelle d'aujourd'hui et décroît de façon géométrique vers le niveau de long terme . Si aujourd'hui est plus calme que la moyenne, la courbe de prévision remonte vers ; si aujourd'hui est une crise, elle redescend vers celui-ci. La vitesse de cette décroissance est entièrement fixée par — et dans le régime quasi-IGARCH crypto, où , la décroissance est si lente que pour des horizons inférieurs à une couple de semaines, la prévision bouge à peine par rapport au niveau d'aujourd'hui. Cela vaut la peine d'être intériorisé : pour des périodes de détention courtes, la prévision GARCH crypto revient essentiellement à « demain ressemble à aujourd'hui, avec un retour à la moyenne très lent ».
Agrégation vers un horizon de détention. Les traders se soucient rarement de la variance d'un seul jour futur. Si vous détenez une position pendant jours et que les rendements sont conditionnellement non corrélés (le fait stylisé du début), la variance du rendement cumulé sur jours est la somme des variances prévues jour par jour :
C'est le nombre contre lequel vous dimensionnez réellement — la volatilité du P&L sur votre période de détention. Notez que ce n'est absolument pas la mise à l'échelle naïve , qui n'est correcte que si la variance est constante. Quand la variance d'aujourd'hui est au-dessus de , la prévision mean-reverting rend la vraie vol sur jours plus basse que la règle de la racine carrée ; quand aujourd'hui est calme, elle est plus haute. Bien faire cela est la différence entre un stop qui respecte la structure par terme et un stop qui ne la respecte pas.
En code :
H = 10
fc = res.forecast(horizon=H, reindex=False)
var_path_pct2 = fc.variance.iloc[-1].values # [E_T sigma^2_{T+1}, ..., T+H]
var_path = var_path_pct2 / (100.0 ** 2) # back to decimal variance
daily_vol = np.sqrt(var_path)
print("Forecast daily vol path:", np.round(daily_vol * 100, 2), "%")
H_day_var = var_path.sum()
H_day_vol = np.sqrt(H_day_var)
print(f"{H}-day holding-period vol: {H_day_vol:.2%}")
naive = np.sqrt(var_path[0] * H)
print(f"Naive sqrt(H) * sigma_1: {naive:.2%}")
Pour des horizons plus longs, GARCH supporte aussi les prévisions par simulation (method="simulation"), qui propagent la distribution des innovations vers l'avant et donnent la densité complète de prévision, pas seulement sa variance — utile quand les innovations ne sont pas gaussiennes, ce qui sera le cas une fois que nous passerons aux distributions Student-t et asymétriques dans la partie 2. Pour les quantités linéaires en variance ci-dessus, le chemin analytique est exact et gratuit.
Diagnostics : le modèle a-t-il vraiment fonctionné ?
Ajuster un modèle n'est pas la même chose que le valider. Tout l'intérêt de GARCH est d'absorber l'hétéroscédasticité conditionnelle — le regroupement de volatilité — de sorte que ce qui reste soit (proche de) i.i.d. Le bon contrôle consiste donc à examiner les résidus standardisés
et à se demander : le clustering a-t-il disparu ? Si le modèle a capturé la dynamique de la variance, les devraient avoir une variance unitaire et, surtout, leurs carrés ne devraient montrer aucune autocorrélation résiduelle. Nous effectuons trois tests.
1. Ljung-Box sur les résidus standardisés. Vérifie qu'il ne reste pas d'autocorrélation linéaire dans le niveau de (ceci teste en réalité le modèle de moyenne, pas le modèle de variance). Ne devrait pas rejeter.
2. Ljung-Box sur les résidus standardisés au carré. C'est le test important. Si a encore une autocorrélation significative, le modèle de variance a échoué à éliminer le clustering — il reste une structure que GARCH(1,1) n'a pas capturée, et vous pourriez avoir besoin d'un ordre supérieur, d'une variante asymétrique, ou d'une distribution d'innovation différente. Ne devrait pas rejeter.
3. Le test ARCH-LM (test du multiplicateur de Lagrange d'Engle). Régresser sur ses propres retards et tester la significativité jointe. C'est essentiellement une version formelle du test 2 et cela demande directement « reste-t-il un effet ARCH » ? Un résultat non significatif confirme que l'hétéroscédasticité conditionnelle a bien été modélisée.
from statsmodels.stats.diagnostic import acorr_ljungbox
from arch.unitroot.cointegration import engle_granger # (unrelated; shown for import clarity)
z = res.std_resid.dropna() # standardized residuals
z2 = z ** 2
lb_z = acorr_ljungbox(z, lags=[10, 20], return_df=True)
lb_z2 = acorr_ljungbox(z2, lags=[10, 20], return_df=True)
print("Ljung-Box on standardized residuals:\n", lb_z, "\n")
print("Ljung-Box on SQUARED standardized residuals:\n", lb_z2, "\n")
lm = res.arch_lm_test(lags=10, standardized=True)
print(lm)
print(f"\nStd-resid excess kurtosis: {stats.kurtosis(z):.2f}")
À quoi ressemble un bon résultat : les p-values de Ljung-Box sur passent de proches de zéro (sur les rendements bruts au carré) à confortablement au-dessus de 0,05, et le test ARCH-LM échoue à rejeter. C'est votre preuve que le modèle a fait son travail sur le second moment.
À quoi ressemble un résultat imparfait — et ce à quoi vous devez vous attendre avec un GARCH(1,1) gaussien simple sur des cryptos — c'est que les tests de clustering passent mais que le kurtosis des résidus standardisés reste encore élevé (disons 4-6 plutôt que 0). GARCH élimine le clustering mais une seule distribution non conditionnelle à queue épaisse subsiste, parce que des innovations gaussiennes ne peuvent pas reproduire les queues. Cette épaisseur de queue résiduelle n'est pas un bug à corriger ici ; c'est la motivation de la partie 2, GARCH asymétrique et l'effet de levier en crypto, où les innovations Student-t et skewed-t et le terme d'asymétrie GJR/EGARCH traitent exactement ce point.
Application : dimensionnement et stops ajustés à la volatilité
Nous avons maintenant une prévision de la volatilité de demain (et des prochains jours). Qu'en faisons-nous ? Les deux usages les plus simples et les plus utiles sont le dimensionnement de position et le placement de stops. Nous gardons les deux volontairement basiques ici — la stratégie complète de ciblage de volatilité avec toute sa machinerie pratique est la partie 4.
Dimensionnement de position ciblé sur la volatilité
L'idée est de détenir une position dont la contribution au risque est à peu près constante dans le temps, plutôt qu'une position dont le notionnel est constant. Si vous déployez toujours la même taille en dollars, votre risque gonfle dans les régimes de haute volatilité et se ratatine dans les régimes calmes — l'inverse de ce que vous voulez. Le ciblage de volatilité inverse cela : viser une volatilité cible fixe du P&L, et laisser la prévision dicter la taille.
Pour une volatilité annualisée cible (disons 20%) et une volatilité annualisée prévue , le poids de position est
Quand la vol prévue est élevée, vous réduisez ; quand elle est basse, vous augmentez. C'est tout le mécanisme. Parce que est prévue — connue en avant que le rendement en ne soit réalisé — il n'y a pas de look-ahead, à condition d'être discipliné sur le timing (plus de détails dans les pièges).
def vol_target_weight(sigma_forecast_annual, sigma_target_annual=0.20,
w_max=3.0):
"""Volatility-scaled position weight. Inputs/outputs in decimals.
w_max caps leverage so a tiny forecast vol can't demand insane size."""
w = sigma_target_annual / sigma_forecast_annual
return float(np.clip(w, 0.0, w_max))
sigma_1d = np.sqrt(res.forecast(horizon=1).variance.iloc[-1, 0]) / 100.0
sigma_ann = sigma_1d * np.sqrt(365)
w = vol_target_weight(sigma_ann, sigma_target_annual=0.20)
print(f"Forecast annual vol: {sigma_ann:.1%} -> position weight: {w:.2f}x")
C'est un proche cousin des vraies règles d'allocation de capital. Le ciblage de volatilité répond à « combien le risque doit-il varier avec la volatilité », tandis que le critère de Kelly répond à « combien le risque doit-il varier avec l'edge » — et les deux se multiplient dans une pile de dimensionnement complète : taille edge / variance. Notez que le terme de variance de Kelly est exactement la prévision GARCH que vous venez de calculer, ce qui explique pourquoi un modèle de volatilité vivant affine matériellement le dimensionnement de Kelly par rapport à une estimation historique statique. Si votre estimation d'edge porte elle-même une incertitude quantifiée, la prédiction conforme offre un moyen sans hypothèse de distribution d'élargir ou de réduire la taille en conséquence, et elle se compose proprement avec le ciblage de volatilité.
Le plafond w_max n'est pas optionnel. Dans le régime quasi-IGARCH, une période calme peut pousser la vol prévue assez bas, et demandera un levier qui est acceptable sur le papier et ruineux quand le calme se rompt — ce qui, selon le regroupement de volatilité, finit toujours par arriver, souvent brutalement. Plafonner le levier est la reconnaissance grossière mais efficace que votre prévision est une moyenne conditionnelle, pas une garantie, et que le paiement associé à une erreur est asymétrique. Cette asymétrie — un compte explosé n'est pas rattrapable par un gain symétrique — est exactement l'asymétrie perte-profit qui devrait vous rendre systématiquement plus prudent que ce que suggère une règle basée sur la seule variance.
Stops ajustés à la volatilité
Un stop en pourcentage fixe souffre de la même maladie qu'une taille de position fixe : un stop à 3% est un déclencheur ultra-sensible sur un marché calme et une erreur d'arrondi sur un marché violent. Il vous fait sortir de bonnes positions par du bruit ordinaire pendant les régimes de haute volatilité et vous fait rendre bien trop de terrain pendant les transitions. La solution est de fixer la distance du stop en unités de volatilité prévue.
où est la volatilité prévue sur votre horizon de détention attendu (la quantité agrégée de la section prévision) et est un multiple — typiquement 1,5 à 3 — choisi de sorte que le stop se situe en dehors de la fluctuation normale mais à l'intérieur d'un véritable mouvement défavorable.
def vol_scaled_stop(entry_price, side, sigma_H, k=2.0):
"""
entry_price : fill price
side : +1 long, -1 short
sigma_H : forecast volatility over the holding horizon (decimal)
k : stop width in vol units
Returns the stop price.
"""
stop_frac = k * sigma_H
return entry_price * (1.0 - side * stop_frac)
var_path = res.forecast(horizon=10, reindex=False).variance.iloc[-1].values / (100.0 ** 2)
sigma_H = np.sqrt(var_path.sum())
entry = float(px.iloc[-1])
stop = vol_scaled_stop(entry, side=+1, sigma_H=sigma_H, k=2.0)
print(f"Entry {entry:,.0f} | 10-day vol {sigma_H:.2%} | 2-sigma stop {stop:,.0f}")
Parce que utilise la prévision de structure par terme mean-reverting plutôt qu'un chiffre historique plat, le stop s'élargit automatiquement à l'approche des régimes turbulents et se resserre quand la volatilité s'apaise — la structure par terme fait l'adaptation à votre place. C'est la même prévision qui alimente à la fois la taille et le stop, ce qui est une fonctionnalité : dans un régime de haute volatilité, vous détenez simultanément moins et donnez à la position plus de marge, et les deux effets se composent pour un risque de queue matériellement plus bas. Le dimensionnement et les stops sont deux projections d'une seule vue de la volatilité, pas deux boutons indépendants.
C'est aussi loin que nous poussons l'application dans la partie 1. Une vraie stratégie doit gérer les coûts de transaction issus du rééquilibrage constant, le timing entre le moment où la prévision est calculée et celui où le trade est passé, le contrôle du turnover et — par-dessus tout — une évaluation honnête hors échantillon. Tout cela fait l'objet de la partie 4 : la stratégie GARCH de ciblage de volatilité, où nous construisons et testons en walk-forward l'ensemble.
Pièges
GARCH est facile à ajuster et facile de se tromper soi-même avec. Les modes d'échec sont constants.
Mise à l'échelle des rendements. Couvert plus haut, mais c'est le bug numéro un, cela mérite d'être répété : ajustez arch sur des rendements × 100, et rééchelonnez chaque sortie (variance par , volatilité par ). Une erreur silencieuse de 100x ici empoisonne tout calcul de dimensionnement et de stop en aval.
Look-ahead dans l'ajustement. Le tueur subtil. Si vous ajustez le modèle sur tout l'historique puis calculez des « prévisions » sur ce même historique, chaque prévision a secrètement vu le futur — les paramètres ont été estimés en utilisant des données postérieures à la date de prévision. L'ajustement en échantillon aura l'air merveilleux et la performance en direct ne lui ressemblera en rien. Chaque prévision backtestée doit provenir d'un modèle ajusté uniquement sur les données disponibles à ce moment-là : réajuster sur une fenêtre extensible ou glissante, prévoir un pas, avancer. Ceci n'est pas négociable et c'est tout le sujet de l'optimisation walk-forward. L'écart entre un GARCH en échantillon et un GARCH correctement walk-forward est l'écart entre une démo et un système qui survit au contact des marchés en direct — voir aussi la parité backtest-live.
Timing de la prévision. Lié mais distinct. La prévision pour le jour doit être calculée à partir des informations disponibles à la clôture du jour (ou quand votre barre se clôture), et la position doit être exécutable à un prix réellement accessible. Calculer la prévision en utilisant la clôture du jour puis « trader » à l'ouverture du jour est un look-ahead qui gonfle silencieusement chaque résultat.
Surajustement à des ordres élevés. GARCH(1,1) suffit presque toujours. La tentation d'ajuster un GARCH(2,2) ou GARCH(3,1) parce que cela pousse légèrement à la hausse la log-vraisemblance en échantillon est généralement un ajustement au bruit ; les paramètres supplémentaires améliorent rarement les prévisions hors échantillon et rendent souvent l'optimiseur instable près de la frontière. Préférez le modèle parcimonieux, et si vous devez comparer des ordres, comparez-les par la perte de prévision hors échantillon sur un découpage walk-forward, pas par l'AIC en échantillon. Quand les diagnostics résiduels montrent encore un problème, la solution est généralement une meilleure distribution d'innovation ou un terme d'asymétrie (partie 2), pas un ordre supérieur.
Les ruptures structurelles lues comme de la persistance. Comme noté, un changement permanent du niveau de volatilité (un nouveau régime de marché, un changement de microstructure de marché) peut être absorbé par GARCH comme une persistance faussement élevée, poussant vers 1. Si votre estimation de vol de long terme semble instable d'une fenêtre à l'autre, suspectez une rupture plutôt que de faire confiance au point estimé quasi-IGARCH. Les réajustements glissants et, le cas échéant, un modèle de régime explicite protègent contre cela.
Confondre prévisions de volatilité et prévisions de rendement. GARCH prévoit l'amplitude des mouvements, pas leur direction. Il vous dit à quel point le mouvement de demain risque d'être important, pas dans quel sens. C'est précisément pourquoi sa place naturelle est la gestion du risque — dimensionnement, stops, VaR — plutôt que la génération de signal. Ne confondez pas une bonne prévision de variance avec un edge.
Où cela mène ensuite
GARCH(1,1) est la fondation, et il est volontairement incomplet. La série s'appuie dessus dans trois directions :
- Asymétrie et queues épaisses — la vraie volatilité crypto répond plus fortement aux mouvements baissiers qu'aux mouvements haussiers (l'effet de levier), et les innovations gaussiennes ne peuvent pas reproduire les queues. GJR-GARCH, EGARCH, et les innovations Student-t / skewed-t font l'objet de la partie 2.
- Volatilité multivariée — les corrélations entre actifs crypto sont elles-mêmes variables dans le temps et s'envolent lors des krachs. Modéliser dynamiquement toute la matrice de covariance fait l'objet de la partie 3 : DCC-GARCH, qui se connecte directement à Markowitz et la moyenne-variance et à l'allocation basée sur la CVaR une fois la covariance rendue dynamique.
- La stratégie complète — dimensionnement, stops, coûts, turnover, et évaluation honnête en walk-forward se rejoignent dans la partie 4.
Et là où les marginales GARCH alimentent le risque joint : le modèle de variance conditionnelle univarié présenté ici est exactement la première étape du pipeline GARCH-EVT-copule pour la VaR/CVaR de portefeuille. Une fois que vous avez les résidus standardisés d'un ajustement GARCH par actif, vous les transformez et les assemblez avec une copule — les marginales sont GARCH, la dépendance est la copule. Cette construction, y compris la dépendance de queue et le traitement de queue EVT, est couverte en profondeur dans les modèles de copule pour le risque crypto joint ; cet article est le moteur univarié qui se trouve dessous.
Résumé
- Les rendements crypto présentent un regroupement de volatilité, des queues épaisses, et pas d'autocorrélation des rendements mais une forte autocorrélation des rendements au carré. Tout outil qui suppose une volatilité constante — Black-Scholes avec un seul , VaR statique, stops en pourcentage fixe — est mal spécifié au regard de ces faits.
- GARCH(1,1), , modélise une variance conditionnelle variable dans le temps avec trois paramètres : un niveau de base , une réaction au choc , et une persistance . C'est un ARCH() avec une mémoire à décroissance géométrique, ce qui explique pourquoi il bat les ARCH d'ordre élevé.
- La stationnarité requiert ; la variance de long terme est , la persistance est , et la demi-vie de volatilité est . Les cryptos se situent dans le régime quasi-IGARCH () : très persistant, lent à retourner à la moyenne, et avec une estimation fragile de la variance de long terme.
- Estimez par maximum de vraisemblance. La log-vraisemblance gaussienne est une somme de densités à un pas ; ajustez-la avec
arch_model(r*100, vol="Garch", p=1, q=1). Souvenez-vous de la mise à l'échelle ×100 et rééchelonnez chaque sortie de façon cohérente. - Les prévisions retournent à la moyenne de façon géométrique vers la variance de long terme au taux . Agrégez les prévisions de variance journalière pour obtenir la volatilité sur l'horizon de détention — pas la règle naïve .
- Validez avec Ljung-Box sur les résidus standardisés au carré et le test ARCH-LM. Réussir ces tests confirme que le clustering a été correctement modélisé ; les queues épaisses résiduelles qui subsistent motivent la partie 2.
- Appliquez-le au dimensionnement ciblé sur la volatilité (, plafonné) et aux stops ajustés à la volatilité (). Une seule prévision alimente les deux, donc les régimes de haute volatilité obtiennent simultanément une taille plus petite et des stops plus larges.
- Les pièges qui comptent : mise à l'échelle des rendements, look-ahead dans l'ajustement (ajustez uniquement sur des données passées, toujours en walk-forward), timing de la prévision, sur-ordre, et ne jamais confondre une prévision de variance avec une prévision de direction.
Références :
- Engle, R. F. (1982). Autoregressive Conditional Heteroscedasticity with Estimates of the Variance of United Kingdom Inflation. Econometrica, 50(4), 987-1007. DOI
- Bollerslev, T. (1986). Generalized Autoregressive Conditional Heteroskedasticity. Journal of Econometrics, 31(3), 307-327. DOI
- Mandelbrot, B. (1963). The Variation of Certain Speculative Prices. The Journal of Business, 36(4), 394-419. DOI
- Nelson, D. B. (1991). Conditional Heteroskedasticity in Asset Returns: A New Approach. Econometrica, 59(2), 347-370. DOI
- Katsiampa, P. (2017). Volatility estimation for Bitcoin: A comparison of GARCH models. Economics Letters, 158, 3-6. DOI
- Chu, J., Chan, S., Nadarajah, S., & Osterrieder, J. (2017). GARCH Modelling of Cryptocurrencies. Journal of Risk and Financial Management, 10(4), 17. DOI
- Sheppard, K. (2023). arch: Autoregressive Conditional Heteroskedasticity (ARCH) and other tools for financial econometrics in Python. GitHub.
MarketMaker.cc Team
Recherche quantitative et stratégie