GARCH(1,1) in R — frequentist (rugarch) and Bayesian (bayesGARCH)¶
The R corner of the trio (from-scratch · PyMC · R)¶
Companion to garch_python.ipynb (from-scratch: MLE + Gauss–Hermite + importance sampling + Metropolis–Hastings + griddy Gibbs) and garch_pymc.ipynb (PyMC/NUTS). Here two standard R packages fit the same GARCH(1,1) on the same S&P 500 returns:
rugarch(Ghalanos) — the reference ML toolkit (ugarchspec/ugarchfit), Gaussian and Student-t.bayesGARCH(Ardia) — MCMC for GARCH(1,1)-t via the Nakatsuma (2000) Metropolis-within-Gibbs scheme — the Bayesian-GARCH workhorse.
Frequentist — rugarch (maximum likelihood)¶
In [1]:
suppressMessages({library(rugarch); library(ggplot2)})
d <- read.csv('sp500ret.csv'); d$date <- as.Date(d$date); r <- d$ret
spec <- function(dist) ugarchspec(variance.model=list(model='sGARCH', garchOrder=c(1,1)),
mean.model=list(armaOrder=c(0,0), include.mean=TRUE), distribution.model=dist)
fitN <- ugarchfit(spec('norm'), r, solver='hybrid')
fitT <- ugarchfit(spec('std'), r, solver='hybrid')
cN <- coef(fitN); cT <- coef(fitT)
cat(sprintf('GARCH-N omega %.4f alpha %.3f beta %.3f persistence %.4f AIC %.0f\n',
cN['omega'], cN['alpha1'], cN['beta1'], cN['alpha1']+cN['beta1'], infocriteria(fitN)[1]*length(r)))
cat(sprintf('GARCH-t omega %.4f alpha %.3f beta %.3f persistence %.4f nu %.1f AIC %.0f\n',
cT['omega'], cT['alpha1'], cT['beta1'], cT['alpha1']+cT['beta1'], cT['shape'], infocriteria(fitT)[1]*length(r)))
GARCH-N omega 0.0138 alpha 0.089 beta 0.903 persistence 0.9925 AIC 15087
GARCH-t omega 0.0061 alpha 0.063 beta 0.934 persistence 0.9970 nu 6.1 AIC 14683
In [2]:
# 1% VaR from GARCH-t (standardized-t quantile) and violation rate + conditional-vol plot
sig <- as.numeric(sigma(fitT)); mu <- cT['mu']; nu <- cT['shape']
VaR <- mu + sig * qdist('std', 0.01, mu=0, sigma=1, shape=nu)
cat(sprintf('1%% VaR violation rate = %.2f%% (target 1.00%%)\n', 100*mean(r < VaR)))
options(repr.plot.width=10, repr.plot.height=4.2)
df <- data.frame(date=d$date, vol=sig*sqrt(252))
ggplot(df, aes(date, vol)) + geom_line(colour='firebrick', linewidth=.3) +
labs(title='rugarch GARCH-t conditional volatility (annualized)', x='year', y='annualized vol %') +
theme_minimal(base_size=13)
1% VaR violation rate = 1.07% (target 1.00%)
Bayesian — bayesGARCH (MCMC)¶
The same GARCH(1,1)-t, now with full posteriors instead of point estimates + asymptotic SEs. Stationarity ($\alpha_1+\beta<1$) is imposed via addPriorConditions.
In [3]:
suppressMessages(library(bayesGARCH))
y <- d$ret - mean(d$ret) # demeaned returns
set.seed(1)
mc <- bayesGARCH(y, control=list(n.chain=2, l.chain=2500, refresh=100000,
addPriorConditions=function(p) p[2]+p[3] < 1))
sm <- window(mc, start=1001) # discard 1000 burn-in
ps <- as.matrix(do.call(rbind, sm)); colnames(ps) <- c('alpha0','alpha1','beta','nu')
per <- ps[,'alpha1'] + ps[,'beta']
cat(sprintf('posterior means: omega %.4f alpha %.3f beta %.3f nu %.1f\n',
mean(ps[,'alpha0']), mean(ps[,'alpha1']), mean(ps[,'beta']), mean(ps[,'nu'])))
cat(sprintf('persistence alpha+beta: mean %.4f 95%% CI [%.4f, %.4f]\n', mean(per), quantile(per,.025), quantile(per,.975)))
cat(sprintf('rugarch ML persistence %.4f, nu %.1f -> frequentist and Bayesian agree\n', cT['alpha1']+cT['beta1'], cT['shape']))
posterior means: omega 0.0107 alpha 0.076 beta 0.919 nu 6.0
persistence alpha+beta: mean 0.9942 95% CI [0.9873, 0.9994]
rugarch ML persistence 0.9970, nu 6.1 -> frequentist and Bayesian agree
In [4]:
options(repr.plot.width=10, repr.plot.height=4)
par(mfrow=c(1,2))
hist(per, breaks=40, col='steelblue', border='white', main='Posterior: persistence alpha+beta', xlab=expression(alpha+beta))
abline(v=mean(per), col='firebrick', lwd=2)
hist(ps[,'nu'], breaks=40, col='seagreen', border='white', main='Posterior: tail dof nu', xlab=expression(nu))
abline(v=mean(ps[,'nu']), col='firebrick', lwd=2)
par(mfrow=c(1,1))
Results¶
rugarch(ML): $\alpha+\beta\approx0.997$, $\nu\approx6$, Student-t strongly preferred by AIC; the 1% VaR violation rate is ≈1% — matching the Pythonarchfit to the decimal.bayesGARCH(MCMC): the same GARCH(1,1)-t structure with full posteriors — persistence posterior just below 1 (near-integrated volatility, pinned by the stationarity prior) and $\nu\approx6$.- Four engines, one story. rugarch and bayesGARCH here, plus the from-scratch five-method notebook and PyMC, all agree on the GARCH(1,1)-t: near-integrated persistence (~0.99) and $\nu\approx6$, frequentist and Bayesian, Python and R. (bayesGARCH's individual $\omega$ and $\alpha/\beta$ split are pulled a little by its stationarity prior — its $\omega$ sits above the ML engines — but persistence and the tail index line up.)