6.5 — Volatility I: ARCH, GARCH(1,1), Forecasts and VaR

Chapter 6 · Time Series Models for Trading and Risk

Prof. Xuhu Wan

Section 6.5 · Chapter 6 · Learning Statistics with Python

Volatility I: ARCH, GARCH(1,1), Forecasts and VaR

Time Series Models for Trading and Risk

Prof. Xuhu Wan

ISOM, HKUST Business School · 2026 Edition

Volatility I: ARCH, GARCH(1,1), Forecasts and VaR

Returns look like white noise — until you square them. You will predict which ACF is flat and which is loud, turn the picture into a \(p\)-value, write a GARCH(1,1) estimator in 15 lines, forecast tomorrow’s variance and back-test a Value-at-Risk through March 2020.

Which is easier to forecast: the sign of tomorrow’s return, or its size?

For daily Dow Jones returns, which is more predictable from yesterday?

  • The sign (up or down)
  • The size (how big the move is)
  • Both equally
  • Neither — returns are i.i.d.

The daily standard deviation was 0.417 % in 2017 and 6.381 % in March 2020 — a 15× swing in scale, a 234× swing in variance. The worst day, −13.84 % on 2020-03-16, sits inside a cluster of other huge days. Quiet begets quiet; storms beget storms.

Predict: the ACF of \(r_t\) versus the ACF of \(r_t^2\)

The white-noise band is \(\pm 2/\sqrt{1479} = \pm 0.052\).

Which pattern will acf(r) and acf(r**2) show at lags 1–5?

  • Both inside the ±0.052 band — returns are white noise, so are their squares
  • Both far outside the band — returns are strongly autocorrelated
  • \(r_t\) mostly inside the band; \(r_t^2\) far outside at every lag
  • \(r_t\) far outside; \(r_t^2\) inside

Squared returns: ACF 0.442, 0.551, 0.325, 0.324, 0.297 — every lag far outside ±0.052, and \(Q(10) = 1929\). Raw returns: −0.195, 0.157, −0.015, −0.080, 0.079 — small, though \(Q(10)=321\) is not zero either (the March 2020 reversals).

Engle’s ARCH and Bollerslev’s GARCH

\[\varepsilon_t = \sigma_t z_t,\ z_t \sim N(0,1),\qquad \text{ARCH}(q):\ \sigma_t^2 = \omega + \sum_{i=1}^{q}\alpha_i \varepsilon_{t-i}^2,\qquad \text{GARCH}(1,1):\ \sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2 .\]

\(\varepsilon_t\) stays white noise (\(\mathbb E[\varepsilon_t\mid\text{past}] = 0\)) while \(\varepsilon_t^2\) follows an AR(\(q\)). Unconditional variance \(\omega/(1-\alpha-\beta)\); persistence \(\alpha+\beta\).

Compared with ARCH(\(q\)), the \(\beta\) term makes the ACF of \(\varepsilon_t^2\)

  • Cut off sharply after lag 1
  • Decay geometrically at rate \(\alpha+\beta\), never cutting off — GARCH(1,1) is ARCH(\(\infty\))
  • Zero at every lag
  • Constant at every lag

The ARCH-LM test: one OLS you already know

Engle’s ARCH-LM test for order \(q\) regresses:

  • \(r_t\) on \(r_{t-1},\dots,r_{t-q}\) and reports the F-test
  • \(|r_t|\) on time and reports the slope
  • \(\hat\varepsilon_t^2\) on \(\hat\varepsilon_{t-1}^2,\dots,\hat\varepsilon_{t-q}^2\) and reports \(LM = nR^2 \sim \chi^2_q\)
  • \(\hat\varepsilon_t\) on \(\hat\varepsilon_t^2\) and reports \(R^2\)

\(R^2 = 0.362\) on a regression that “should” have \(R^2 = 0\): \(LM = 533.2\) against a \(\chi^2_5\) whose 1 % critical value is 15.1. het_arch reports the same 533.2 — you just re-implemented it. (On 1 479 i.i.d. normals the same code gives \(LM \approx 5\), \(p = 0.42\).)

Your turn: does Apple have ARCH effects too?

Load appl.csv, build percent log returns for 2018–2020 into ra, and run arch_lm(ra.values, q=5). The starter leaves LM_a as a placeholder; replace the marked line so that LM_a is Apple’s ARCH-LM statistic.

Expected once fixed: \(LM = 132.6\), \(p < 10^{-4}\) on 756 days, with squared-return ACF 0.319, 0.302, 0.159. Apple clusters like the index.

In Colab: the notebook fits GARCH in one line — read the output carefully

In Colab

from arch import arch_model
re = dji["Return"]                       # decimal log returns, 1985 onward
GARCH11 = arch_model(re.iloc[0:252*5], p=1, q=1).fit(disp="off")
print(GARCH11.summary())
#   mu        1.0176e-03
#   omega     3.5235e-06   std err 4.250e-12
#   alpha[1]  0.1000       std err 5.500e-02
#   beta[1]   0.8800       std err 3.678e-02

\(\hat\alpha = 0.1000\) and \(\hat\beta = 0.8800\) to four decimals. What is the most likely explanation?

  • The Dow really has \(\alpha=0.10\), \(\beta=0.88\) exactly
  • arch rounds to two decimals
  • These are the optimiser’s starting values — it never moved because the data were in decimals
  • Only 1 260 observations, so the estimate is coarse

The estimator in under 20 lines

\(r_t = \mu + \varepsilon_t\). Gaussian log-likelihood of one observation: \(-\tfrac12\left[\ln(2\pi\sigma_t^2) + \varepsilon_t^2/\sigma_t^2\right]\). Sum, negate, minimise — in percent, so \(\omega\) is of order 0.01, not \(10^{-6}\).

Fit it to the Dow, 2015–2020

Equity indices typically show \(\alpha \approx 0.05\)\(0.2\) and \(\alpha+\beta \approx 0.95\)\(0.99\). Will alpha + beta land above or below 0.95?

print(round(alpha + beta, 3))

0.975 — above

\(\hat\mu=0.082\), \(\hat\omega=0.037\), \(\hat\alpha=0.214\), \(\hat\beta=0.761\). Persistence 0.975 — a shock to variance halves in 27 days. Long-run sd 1.217 % vs sample 1.240 %. Well under a second.

The conditional volatility path

From 0.43 % on 2017-11-09 to 8.88 % on 2020-03-17 — 141 % annualised. Volatility is not a number; it is a time series, and the model tracks it one day at a time.

Forecast: one step, then many

Tomorrow is exact: \(\hat\sigma^2_{T+1} = \omega + \alpha\varepsilon_T^2 + \beta\sigma_T^2\). Beyond that, \(\mathbb{E}[\varepsilon^2_{T+h}] = \sigma^2_{T+h}\), so \(\hat\sigma^2_{T+h} = \omega + (\alpha+\beta)\,\hat\sigma^2_{T+h-1}\).

On 2020-11-13, \(\varepsilon_T = 1.283\) and \(\sigma_T^2 = 1.913\). What is \(\hat\sigma^2_{T+1}\) to 3 decimals?

0.0374 + 0.214*1.283**2 + 0.761*1.913

1.845

1.845 → 1.810 → 1.704 → 1.560, sliding toward the long-run 1.48. A GARCH forecast is a decay path, not a level: the further out, the less today matters.

From variance to Value-at-Risk: normal or Student-\(t\)?

\(\text{VaR}_p = -\left(\hat\mu + \hat\sigma_{T+1}\, q_p\right)\), a positive loss. With \(q_{0.01} = -2.326\), \(\hat\mu = 0.082\), \(\hat\sigma_{T+1} = \sqrt{1.845} = 1.358\):

print(round(VaR_1pct, 3)) prints?

-(0.082 + 1.358 * (-2.326))

3.077

Standardised residuals still have excess kurtosis 2.43; the fitted \(\nu = 12.7\). Tomorrow’s 1 % VaR: 3.08 % (normal) vs 3.23 % (\(t\)). In-sample the normal 1 % VaR is breached 2.5 % of days, the \(t\) 2.2 % — both too often. At 5 % both are close (5.8 %, 6.1 %). Fat tails survive GARCH; they are just thinner.

The honest test is out of sample: back-test with Kupiec

Fit on 2015–2018 (1 006 days), freeze the parameters, run the recursion forward through 2019–2020 (473 days), count breaches of the 1 % VaR. Compare with an unconditional VaR from the training mean and sd.

Over 2019–2020 the 1 % VaR should be breached about 5 times (473 × 0.01). Which is closer to 5?

  • Unconditional VaR — a fixed number is more robust
  • Both hit exactly 5
  • GARCH — it re-scales within days; unconditional VaR is breached far more, mostly in March 2020
  • Neither breaches at all

1 % VaR: GARCH 18 breaches (3.8 %, Kupiec \(p<0.001\)), unconditional 31 (6.6 %). 5 % VaR: GARCH 32 (6.8 %, \(p = 0.09\) — not rejected), unconditional 47 (9.9 %). GARCH-normal is still too thin at 1 %; the fixed-sd model is simply wrong. The unconditional line is crossed ten times in March 2020 alone; GARCH’s is crossed once — it was already at \(\sigma \approx 6\) %.

What you discovered

  • Daily returns: unpredictable sign, predictable size. 2017 sd 0.42 %, March 2020 sd 6.38 %. ACF of \(r_t^2\): 0.44, 0.55, 0.33 …; ARCH-LM 533 on the Dow, 133 on Apple, 5 on white noise.
  • GARCH(1,1) by MLE is 15 lines: a variance recursion, a Gaussian log-likelihood, minimize with bounds. Dow 2015–2020: \(\alpha=0.214\), \(\beta=0.761\), half-life 27 days. Work in percent or the optimiser stalls at its starting values.
  • Conditional \(\sigma_t\) ranged 0.43 % → 8.88 %. Multi-step forecasts decay geometrically toward \(\omega/(1-\alpha-\beta)\).
  • \(\text{VaR}_p = -(\hat\mu + \hat\sigma_{T+1} q_p)\). Standardised residuals keep excess kurtosis 2.4; the 1 % normal VaR is breached 3.8 % of days out of sample — the unconditional VaR 6.6 %.

Next: §6.6 — does bad news raise volatility more than good news? And what happens when you size positions by \(\hat\sigma_t\)?