ARCH and GARCH models are about one specific thing: predicting how much a value will wiggle, not predicting the value itself.
Imagine you’re tracking the daily return of a stock. Some weeks it barely moves. Other weeks it swings wildly — and importantly, wild weeks tend to cluster together. A big move today makes a big move tomorrow more likely. Calm periods stay calm for a while too.
This is called volatility clustering, and regular models (which assume constant variance) completely miss it.
Breaking down the name:
The simple idea: today’s variance depends on yesterday’s squared shock (error).
\[\sigma_t^2 = \omega + \alpha \cdot \epsilon_{t-1}^2\]
In plain English: “If yesterday had a big surprise (positive or negative), expect today to be more volatile too.”
Toy example. Say \(\omega = 0.1\) and \(\alpha = 0.6\).
One big shock spikes the predicted volatility.
Here are the most common and effective alternatives to model the conditional mean \(\mu_t\), ordered from lowest to highest complexity: 1. Constant Mean (The Financial Standard):If you are working with daily or weekly returns of highly liquid assets (such as S&P 500 stocks or major currency pairs), the Efficient Market Hypothesis suggests that past returns cannot predict future returns. In this case, the mean is simply modeled as a historical constant:\[\mu_t = \mu\]When to use it: In the vast majority of liquid financial assets at daily or lower frequencies. This is the default setting in most statistical libraries, such as arch in Python or rugarch in R. 2. ARMA Models (Autoregressive Moving Average): If you detect that today’s returns share a correlation with previous days’ returns (autocorrelation), you need an autoregressive structure. For example, an AR(1) model assumes that today’s return depends partly on yesterday’s return:\[\mu_t = c + \phi_1 y_{t-1}\]If there is also persistence in recent shocks, you can use a full ARMA(p, q) model:\[\mu_t = c + \sum_{i=1}^p \phi_i y_{t-i} + \sum_{j=1}^q \theta_j \epsilon_{t-j}\]When to use it: Highly common in high-frequency data (minutes or hours), less liquid markets (such as emerging markets or low-cap cryptocurrencies) where market frictions exist, or whenever a Ljung-Box test on the raw returns detects significant autocorrelation.
ARCH only looks one step back and forgets fast. GARCH adds memory of recent volatility itself, not just shocks:
\[\sigma_t^2 = \omega + \alpha \cdot \epsilon_{t-1}^2 + \beta \cdot \sigma_{t-1}^2\]
The extra \(\beta \sigma_{t-1}^2\) term means: “Also factor in how volatile things already were yesterday, not just yesterday’s surprise.” This makes GARCH smoother and better at modeling realistic, slowly-decaying volatility — a shock causes a spike, then volatility gradually settles back down over days or weeks, not instantly.
Analogy. ARCH is like reacting only to the last earthquake’s magnitude. GARCH is like reacting to the last earthquake and remembering we’re still in an aftershock period.
| Model | Remembers | Use when |
|---|---|---|
| ARCH | Just the last shock | Simple, short-memory volatility |
| GARCH | Last shock + last volatility level | Most real-world data (smoother, more realistic decay) |
“GARCH(1,1)” simply means it looks back 1 period for the shock term and 1 period for the volatility term — the most common, often “good enough” specification.
The 5-Step Statistical Roadmap
1.Preparation Phase: Is y our data ready? Before modeling volatility, your data (usually financial returns) must be stationary.Stationarity Test (ADF): Checks if the statistical properties (mean, variance) change over time. GARCH requires the series to be stationary.Decision: Run the Augmented Dickey-Fuller (ADF) test. You want a low p-value (less than 0.05) to reject the null hypothesis of a unit root.Tip: If you have stock prices, you must convert them to log returns (\(r_t = \ln(P_t / P_{t-1})\)) to achieve stationarity.
2. Mean Modeling Phase (ARMA/ARIMA) GARCH models the variance of the residuals (errors) from a mean equation. First, you need to define that mean.Autocorrelation Analysis (ACF/PACF): Helps you see if today’s return depends on past returns to set up an ARMA(\(p, q\)) model.Ljung-Box Test (on mean residuals): Checks if there is any remaining serial correlation in the returns. You want a high p-value (greater than 0.05), proving the residuals are “white noise” in terms of their level.
3. The Decisive Test: Do you actually need a GARCH model? There is no point in using GARCH if your volatility is constant over time. You must prove that ARCH effects (conditional heteroskedasticity or “volatility clustering”) exist.Engle’s ARCH-LM Test: Tests whether the squared residuals of your mean model are autocorrelated (i.e., if periods of high variance are followed by periods of high variance).Decision: You look for a very low p-value (less than 0.05) to reject the null hypothesis of “no ARCH effects.” If rejected, you have the green light to use GARCH.
4. Selecting the Error Distribution Financial: markets are notorious for “fat tails” (extreme events happen more often than a normal distribution predicts).Jarque-Bera Test: Tests if your residuals are normally distributed by looking at skewness and kurtosis.Decision: If it rejects normality (which happens 99% of the time with financial data), you should configure your GARCH using a Student-t or Generalized Error Distribution (GED) instead of a standard Gaussian distribution. This drastically improves model accuracy.
5. Post-Estimation Diagnostics: Did it work?Once you fit your model (e.g., a GARCH(1,1)), you must ensure it successfully captured the volatility.Ljung-Box Test on Squared Standardized Residuals:Decision: This time, you want a high p-value (greater than 0.05). This proves that the GARCH model successfully removed the volatility clustering, leaving no ARCH effects behind.
## Warning: package 'tseries' was built under R version 4.4.2
## Registered S3 method overwritten by 'quantmod':
## method from
## as.zoo.data.frame zoo
## Warning: package 'FinTS' was built under R version 4.4.3
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.4.2
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
## Warning: package 'rugarch' was built under R version 4.4.3
## Loading required package: parallel
## Warning in adf.test(returns_pct): p-value smaller than printed p-value
## ADF Test p-value: 0.01
## Ljung-Box (Mean) p-value: 0.2987683
## Engle ARCH-LM Test p-value: 0.03208996
##
## *---------------------------------*
## * GARCH Model Fit *
## *---------------------------------*
##
## Conditional Variance Dynamics
## -----------------------------------
## GARCH Model : sGARCH(1,1)
## Mean Model : ARFIMA(0,0,0)
## Distribution : std
##
## Optimal Parameters
## ------------------------------------
## Estimate Std. Error t value Pr(>|t|)
## mu -0.027148 0.031695 -0.85653 0.39170
## omega 0.001063 0.003747 0.28378 0.77658
## alpha1 0.000000 0.003881 0.00000 1.00000
## beta1 0.999000 0.000036 27594.86420 0.00000
## shape 46.442391 27.714631 1.67574 0.09379
##
## Robust Standard Errors:
## Estimate Std. Error t value Pr(>|t|)
## mu -0.027148 0.030359 -0.89422 0.371204
## omega 0.001063 0.003588 0.29638 0.766938
## alpha1 0.000000 0.003707 0.00000 1.000000
## beta1 0.999000 0.000033 30380.11020 0.000000
## shape 46.442391 17.459844 2.65995 0.007815
##
## LogLikelihood : -1418.563
##
## Information Criteria
## ------------------------------------
##
## Akaike 2.8500
## Bayes 2.8745
## Shibata 2.8499
## Hannan-Quinn 2.8593
##
## Weighted Ljung-Box Test on Standardized Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 0.01112 0.9160
## Lag[2*(p+q)+(p+q)-1][2] 0.32211 0.7835
## Lag[4*(p+q)+(p+q)-1][5] 1.84163 0.6563
## d.o.f=0
## H0 : No serial correlation
##
## Weighted Ljung-Box Test on Standardized Squared Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 0.6714 0.4126
## Lag[2*(p+q)+(p+q)-1][5] 2.7529 0.4540
## Lag[4*(p+q)+(p+q)-1][9] 7.5320 0.1586
## d.o.f=2
##
## Weighted ARCH LM Tests
## ------------------------------------
## Statistic Shape Scale P-Value
## ARCH Lag[3] 0.245 0.500 2.000 0.6206
## ARCH Lag[5] 1.363 1.440 1.667 0.6292
## ARCH Lag[7] 5.381 2.315 1.543 0.1887
##
## Nyblom stability test
## ------------------------------------
## Joint Statistic: 3.3197
## Individual Statistics:
## mu 0.08194
## omega 0.33825
## alpha1 0.35937
## beta1 0.33364
## shape 0.05701
##
## Asymptotic Critical Values (10% 5% 1%)
## Joint Statistic: 1.28 1.47 1.88
## Individual Statistic: 0.35 0.47 0.75
##
## Sign Bias Test
## ------------------------------------
## t-value prob sig
## Sign Bias 1.0788 0.2809
## Negative Sign Bias 0.5927 0.5535
## Positive Sign Bias 1.2862 0.1987
## Joint Effect 2.0087 0.5706
##
##
## Adjusted Pearson Goodness-of-Fit Test:
## ------------------------------------
## group statistic p-value(g-1)
## 1 20 20.76 0.3502
## 2 30 29.20 0.4548
## 3 40 34.31 0.6834
## 4 50 43.79 0.6836
##
##
## Elapsed time : 0.2958212
## Ljung-Box (Post-GARCH on squared residuals) p-value: 0.06636732
The code below simulates a simple GARCH(1,1) process from scratch (no external packages needed) so you can see volatility clustering appear.
Notice how the volatility panel spikes right after large moves in the returns panel, then decays gradually — that gradual decay is the “G” (generalized) memory effect in GARCH.
In practice you’d fit a GARCH model rather than simulate one. There
are several R packages for this — here we use fGarch, which
is lightweight and easy to install (no compilation required), as an
alternative to the more feature-heavy but harder-to-install
rugarch:
## Warning: package 'fGarch' was built under R version 4.4.3
## NOTE: Packages 'fBasics', 'timeDate', and 'timeSeries' are no longer
## attached to the search() path when 'fGarch' is attached.
##
## If needed attach them yourself in your R script by e.g.,
## require("timeSeries")
## Warning: package 'quantmod' was built under R version 4.4.2
## Loading required package: xts
## Warning: package 'xts' was built under R version 4.4.2
## Loading required package: TTR
## Warning: package 'TTR' was built under R version 4.4.2
##
## Attaching package: 'TTR'
## The following object is masked from 'package:fGarch':
##
## volatility
## [1] "AAPL"
##
## Title:
## GARCH Modelling
##
## Call:
## garchFit(formula = ~garch(1, 1), data = returns, trace = FALSE)
##
## Mean and Variance Equation:
## data ~ garch(1, 1)
## <environment: 0x00000183ded66000>
## [data = returns]
##
## Conditional Distribution:
## norm
##
## Coefficient(s):
## mu omega alpha1 beta1
## 1.2898e-03 1.3829e-05 9.2035e-02 8.7057e-01
##
## Std. Errors:
## based on Hessian
##
## Error Analysis:
## Estimate Std. Error t value Pr(>|t|)
## mu 1.290e-03 4.037e-04 3.195 0.001398 **
## omega 1.383e-05 3.703e-06 3.734 0.000188 ***
## alpha1 9.204e-02 1.459e-02 6.309 2.8e-10 ***
## beta1 8.706e-01 2.063e-02 42.199 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Log Likelihood:
## 4295.532 normalized: 2.616037
##
## Description:
## Mon Jul 20 20:21:30 2026 by user: cgsal
##
##
##
## Standardised Residuals Tests:
## Statistic p-Value
## Jarque-Bera Test R Chi^2 453.9178779 0.0000000
## Shapiro-Wilk Test R W 0.9749407 0.0000000
## Ljung-Box Test R Q(10) 4.4253756 0.9261244
## Ljung-Box Test R Q(15) 5.4871300 0.9871368
## Ljung-Box Test R Q(20) 11.1379345 0.9425667
## Ljung-Box Test R^2 Q(10) 8.3330834 0.5963370
## Ljung-Box Test R^2 Q(15) 8.7639864 0.8895309
## Ljung-Box Test R^2 Q(20) 13.3698722 0.8609565
## LM Arch Test R TR^2 8.3298878 0.7588487
##
## Information Criterion Statistics:
## AIC BIC SIC HQIC
## -5.227201 -5.214038 -5.227213 -5.222320
returns_xts keeps the date index (it’s an
xts object), so you can plot it directly and get real dates
on the x-axis:
If you’d rather use ggplot2 (more control over styling,
easier to customize):
## Warning: package 'ggplot2' was built under R version 4.4.3
If you’re using the CSV route instead of quantmod, just
make sure your date column is an actual Date
object (e.g. df$date <- as.Date(df$date)) before
building returns_df, so ggplot knows to treat the x-axis as
a time axis.
If you’d rather not re-download data from Yahoo Finance every time
you knit this document, you can save what quantmod already
pulled into a local CSV, then read from that instead:
Once you run summary(fit) above, you’ll get a
coefficient table roughly like this:
| Coefficient | Meaning |
|---|---|
mu |
The mean of the returns series. Usually close to 0 for daily stock returns and often not statistically significant — that’s normal, since GARCH is about variance, not the mean. |
omega (\(\omega\)) |
The baseline variance level. Combined with alpha1 and beta1, it gives the long-run (unconditional) variance: \(\omega / (1 - \alpha_1 - \beta_1)\). |
alpha1 (\(\alpha\)) |
How much yesterday’s shock (squared residual) feeds into today’s variance. Higher = more reactive, jumpy volatility. |
beta1 (\(\beta\)) |
How much of yesterday’s variance itself carries over. Higher = more persistent volatility that decays slowly. |
Three things worth checking in your results:
alpha1 and beta1 to be significant. If
beta1 isn’t significant, a plain ARCH model (no volatility
memory term) might already be enough.Standard GARCH only looks at the series’ own past (its shocks and its own past variance). But sometimes you have another variable you believe drives volatility — trading volume, the VIX, a macro indicator, or even another asset’s returns (volatility spillover). GARCH-X extends the variance equation to include this extra term:
\[\sigma_t^2 = \omega + \alpha \cdot \epsilon_{t-1}^2 + \beta \cdot \sigma_{t-1}^2 + \gamma \cdot X_{t-1}\]
The new piece is \(\gamma \cdot X_{t-1}\): how much yesterday’s exogenous variable \(X\) adds to (or subtracts from) today’s variance, on top of what the shock and persistence terms already explain.
Note on packages: fGarch does not
support exogenous regressors in the variance equation. For GARCH-X you
need rugarch, which supports this via
external.regressors inside variance.model.
## [1] "AAPL"
##
## *---------------------------------*
## * GARCH Model Fit *
## *---------------------------------*
##
## Conditional Variance Dynamics
## -----------------------------------
## GARCH Model : sGARCH(1,1)
## Mean Model : ARFIMA(0,0,0)
## Distribution : norm
##
## Optimal Parameters
## ------------------------------------
## Estimate Std. Error t value Pr(>|t|)
## mu 0.000868 0.000381 2.277110 0.02278
## omega 0.000000 0.000003 0.134151 0.89328
## alpha1 0.049999 0.005981 8.359617 0.00000
## beta1 0.876105 0.003498 250.468151 0.00000
## vxreg1 0.000000 0.000000 0.000003 1.00000
##
## Robust Standard Errors:
## Estimate Std. Error t value Pr(>|t|)
## mu 0.000868 0.000444 1.954747 0.050613
## omega 0.000000 0.000010 0.037406 0.970161
## alpha1 0.049999 0.016356 3.056942 0.002236
## beta1 0.876105 0.039194 22.353021 0.000000
## vxreg1 0.000000 0.000000 0.000004 0.999997
##
## LogLikelihood : 4309.035
##
## Information Criteria
## ------------------------------------
##
## Akaike -5.2424
## Bayes -5.2260
## Shibata -5.2424
## Hannan-Quinn -5.2363
##
## Weighted Ljung-Box Test on Standardized Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 0.1454 0.7030
## Lag[2*(p+q)+(p+q)-1][2] 0.2066 0.8502
## Lag[4*(p+q)+(p+q)-1][5] 0.6188 0.9379
## d.o.f=0
## H0 : No serial correlation
##
## Weighted Ljung-Box Test on Standardized Squared Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 3.415 0.064616
## Lag[2*(p+q)+(p+q)-1][5] 9.205 0.014688
## Lag[4*(p+q)+(p+q)-1][9] 14.138 0.005761
## d.o.f=2
##
## Weighted ARCH LM Tests
## ------------------------------------
## Statistic Shape Scale P-Value
## ARCH Lag[3] 0.02623 0.500 2.000 0.871335
## ARCH Lag[5] 11.56836 1.440 1.667 0.002713
## ARCH Lag[7] 13.63026 2.315 1.543 0.002504
##
## Nyblom stability test
## ------------------------------------
## Joint Statistic: 445.4237
## Individual Statistics:
## mu 0.2243
## omega 0.2221
## alpha1 1.4588
## beta1 0.9819
## vxreg1 442.5298
##
## Asymptotic Critical Values (10% 5% 1%)
## Joint Statistic: 1.28 1.47 1.88
## Individual Statistic: 0.35 0.47 0.75
##
## Sign Bias Test
## ------------------------------------
## t-value prob sig
## Sign Bias 0.09032 0.92804
## Negative Sign Bias 2.15573 0.03125 **
## Positive Sign Bias 0.19817 0.84294
## Joint Effect 6.79174 0.07884 *
##
##
## Adjusted Pearson Goodness-of-Fit Test:
## ------------------------------------
## group statistic p-value(g-1)
## 1 20 42.70 0.001424
## 2 30 57.40 0.001285
## 3 40 71.67 0.001104
## 4 50 83.88 0.001411
##
##
## Elapsed time : 0.08587694
The coefficient table will now include an extra row, typically
labeled vxreg1 (or vxreg1,
vxreg2, … if you added more than one exogenous variable),
alongside omega, alpha1, and
beta1. That coefficient is \(\gamma\):
vxreg1 is statistically significant
(small p-value), it means your exogenous variable adds explanatory power
for volatility beyond what the shock/persistence terms already
capture.Practical note: exogenous regressors must be aligned in time with the returns series — same number of observations, no NAs, no lookahead bias (make sure you’re only using past values of \(X\), not same-day or future values, unless that’s intentional).
There are two common explanations, and it’s worth ruling out the second one before concluding “volume doesn’t matter”:
To confirm your code and interpretation are correct, it helps to
simulate data where you know the exogenous variable truly
drives volatility, and check that ugarchfit recovers a
large, significant \(\gamma\):
##
## *---------------------------------*
## * GARCH Model Fit *
## *---------------------------------*
##
## Conditional Variance Dynamics
## -----------------------------------
## GARCH Model : sGARCH(1,1)
## Mean Model : ARFIMA(0,0,0)
## Distribution : norm
##
## Optimal Parameters
## ------------------------------------
## Estimate Std. Error t value Pr(>|t|)
## mu -0.018495 0.082581 -0.22396 0.82279
## omega 1.438006 2.363899 0.60832 0.54297
## alpha1 0.020561 0.014967 1.37382 0.16950
## beta1 0.879480 0.139928 6.28524 0.00000
## vxreg1 0.624650 0.618685 1.00964 0.31267
##
## Robust Standard Errors:
## Estimate Std. Error t value Pr(>|t|)
## mu -0.018495 0.078135 -0.23670 0.812887
## omega 1.438006 3.861510 0.37239 0.709599
## alpha1 0.020561 0.021646 0.94988 0.342175
## beta1 0.879480 0.235346 3.73696 0.000186
## vxreg1 0.624650 0.793919 0.78679 0.431403
##
## LogLikelihood : -8792.482
##
## Information Criteria
## ------------------------------------
##
## Akaike 5.8650
## Bayes 5.8750
## Shibata 5.8650
## Hannan-Quinn 5.8686
##
## Weighted Ljung-Box Test on Standardized Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 1.069 0.3012
## Lag[2*(p+q)+(p+q)-1][2] 1.794 0.2994
## Lag[4*(p+q)+(p+q)-1][5] 3.128 0.3842
## d.o.f=0
## H0 : No serial correlation
##
## Weighted Ljung-Box Test on Standardized Squared Residuals
## ------------------------------------
## statistic p-value
## Lag[1] 0.9462 0.3307
## Lag[2*(p+q)+(p+q)-1][5] 2.6279 0.4792
## Lag[4*(p+q)+(p+q)-1][9] 4.6707 0.4793
## d.o.f=2
##
## Weighted ARCH LM Tests
## ------------------------------------
## Statistic Shape Scale P-Value
## ARCH Lag[3] 0.03442 0.500 2.000 0.8528
## ARCH Lag[5] 2.07855 1.440 1.667 0.4541
## ARCH Lag[7] 3.43130 2.315 1.543 0.4360
##
## Nyblom stability test
## ------------------------------------
## Joint Statistic: 0.7355
## Individual Statistics:
## mu 0.2449
## omega 0.2547
## alpha1 0.2550
## beta1 0.2604
## vxreg1 0.2279
##
## Asymptotic Critical Values (10% 5% 1%)
## Joint Statistic: 1.28 1.47 1.88
## Individual Statistic: 0.35 0.47 0.75
##
## Sign Bias Test
## ------------------------------------
## t-value prob sig
## Sign Bias 0.6402 0.5221
## Negative Sign Bias 0.1341 0.8933
## Positive Sign Bias 0.4203 0.6743
## Joint Effect 0.8202 0.8446
##
##
## Adjusted Pearson Goodness-of-Fit Test:
## ------------------------------------
## group statistic p-value(g-1)
## 1 20 19.65 0.41570
## 2 30 41.66 0.06023
## 3 40 37.65 0.53128
## 4 50 55.40 0.24603
##
##
## Elapsed time : 0.5237029
Because gamma_true is deliberately large (2.0) relative
to the other terms, vxreg1 in the output should come back
clearly nonzero and highly significant. If you run this and it does,
that confirms your code is set up correctly — so if vxreg1
is near zero on your real AAPL data, it’s most likely explanation 1
above (a genuine null effect), especially after you’ve already tried
standardizing the regressor.
show(fit) from rugarch prints several
diagnostic tests beyond the coefficient table. Here’s what each one is
checking:
Coefficient table (Optimal Parameters)
Each row (mu, omega, alpha1,
beta1, vxreg1, …) shows the estimate, standard
error, t-value, and p-value — a p-value below 0.05 means that term is
statistically significant at the 5% level. Rugarch also prints a “Robust
Standard Errors” version of the same table, which is generally more
trustworthy since it doesn’t assume the errors are perfectly normal.
Weighted Ljung-Box Test on Standardized Residuals
Checks whether there’s leftover autocorrelation in the mean
— i.e. whether the model missed some predictable pattern in the returns
themselves. You want a high p-value (fail to reject)
here, meaning no significant leftover structure. For a simple GARCH(1,1)
with armaOrder = c(0,0), this is really testing whether the
mean was truly unpredictable, which is usually the case for daily stock
returns.
Weighted Ljung-Box Test on Standardized Squared Residuals
The most important test for a GARCH model. It checks whether there’s leftover autocorrelation in the squared residuals — i.e. whether volatility clustering is still present after the model. You want a high p-value here too. A low p-value means your GARCH specification didn’t fully capture the volatility clustering, and you may need a higher-order model (e.g. GARCH(2,1)) or a different variance specification.
Weighted ARCH LM Tests
Similar purpose to the squared-residuals Ljung-Box test: it’s a formal Lagrange Multiplier test for remaining ARCH effects at different lags. Again, you want high p-values, confirming no leftover ARCH structure your model failed to capture.
Sign Bias Test (Engle-Ng)
Tests for asymmetry — whether positive and negative shocks affect future volatility differently (the “leverage effect” common in stocks, where bad news tends to increase volatility more than good news of the same size). It reports four rows:
A significant result (p < 0.05) here suggests a symmetric model
like plain GARCH is missing something, and an asymmetric model
(e.g. EGARCH or GJR-GARCH, both available in rugarch) might
fit better.
Adjusted Pearson Goodness-of-Fit Test
Compares the empirical distribution of the standardized residuals
against the distribution you assumed when fitting (normal, by default).
A low p-value suggests the assumed distribution doesn’t match well — a
common reason to switch to a fatter-tailed distribution like the
Student-t (distribution.model = "std" in
ugarchspec), since financial returns usually have more
extreme values than a normal distribution predicts.
Information Criteria (Akaike, Bayes, Shibata, Hannan-Quinn)
These aren’t hypothesis tests — they’re used to compare different model specifications (e.g. GARCH(1,1) vs GARCH-X vs EGARCH) fit to the same data. Lower is better. They penalize models with more parameters, so they help you avoid overfitting when deciding whether an added term (like your exogenous variable) is worth the extra complexity.
A model that fits well in-sample isn’t automatically useful — the real test is whether its volatility forecasts hold up on data it never saw. The standard approach: fit on an earlier chunk of the series (training), forecast forward, and compare against what actually happened in the remaining chunk (validation).
There’s no way to observe “true” daily volatility directly, so the usual approach is to use a proxy: the absolute value (or square) of the actual return on that day. Bigger realized moves are treated as evidence of higher realized volatility.
Caveat with a static forecast: a GARCH model forecast beyond a few days converges toward the long-run unconditional variance (\(\omega / (1 - \alpha - \beta)\)) and essentially flattens out. That’s expected behavior, not a bug — GARCH is much better at short-horizon forecasts than at predicting exactly how volatile day 150 of the validation set will be.
A more rigorous way to validate is a rolling one-step-ahead
forecast: refit (or just re-forecast) at every point in the
validation window, each time only using information available up to that
day. rugarch has a built-in function for this,
ugarchroll:
This tends to track realized volatility much more closely than the static forecast, since the model is continually re-anchored with fresh information — it’s a better reflection of how the model would actually perform if you used it day by day going forward.