Asymmetric GARCH — leverage effect in R (rugarch)¶

The R corner of the Phase-2 trio (from-scratch → PyMC → R)¶

Companion to garch_asym_python.ipynb (from-scratch, five methods) and garch_asym_pymc.ipynb (PyMC). rugarch fits gjrGARCH and eGARCH directly by ML.

Note: R's Bayesian-GARCH package bayesGARCH is symmetric-only, so the Bayesian asymmetric fits are carried by PyMC and the from-scratch notebook; here R contributes the frequentist gjrGARCH / eGARCH.

In [1]:
suppressMessages({library(rugarch); library(ggplot2)})
d <- read.csv('sp500ret.csv'); r <- d$ret
fit <- function(m) ugarchfit(ugarchspec(variance.model=list(model=m, garchOrder=c(1,1)),
                 mean.model=list(armaOrder=c(0,0), include.mean=TRUE), distribution.model='std'), r, solver='hybrid')
sym <- fit('sGARCH'); gjr <- fit('gjrGARCH'); eg <- fit('eGARCH')
cat(sprintf('symmetric AIC %.0f\n', infocriteria(sym)[1]*length(r)))
cat(sprintf('gjrGARCH  AIC %.0f   gamma1(leverage) = %.3f   alpha1 = %.3f\n',
            infocriteria(gjr)[1]*length(r), coef(gjr)['gamma1'], coef(gjr)['alpha1']))
cat(sprintf('eGARCH    AIC %.0f   alpha1(sign/leverage) = %.3f  gamma1(magnitude) = %.3f\n',
            infocriteria(eg)[1]*length(r), coef(eg)['alpha1'], coef(eg)['gamma1']))
symmetric AIC 14683
gjrGARCH  AIC 14601   gamma1(leverage) = 0.110   alpha1 = 0.009
eGARCH    AIC 14567   alpha1(sign/leverage) = -0.089  gamma1(magnitude) = 0.110

News impact curve¶

The news impact curve (Engle–Ng 1993) plots next-period conditional variance $\sigma_t^2$ as a function of the shock $\varepsilon_{t-1}$, holding $\sigma_{t-1}^2$ at its unconditional level: $$\sigma_t^2 = \omega + \big(\alpha_1 + \gamma_1\,\mathbf{1}[\varepsilon_{t-1}<0]\big)\varepsilon_{t-1}^2 + \beta_1\,\bar\sigma^2 .$$ We evaluate it directly over a $\pm5\%$ shock range (rugarch's built-in newsimpact() uses a very narrow default grid that flattens the curve). Symmetric sGARCH gives a parabola; gjrGARCH gives an asymmetric V — steeper on the bad-news side.

In [2]:
options(repr.plot.width=10, repr.plot.height=4.6)
cs <- coef(sym); cg <- coef(gjr)
uv_s <- cs['omega'] / (1 - cs['alpha1'] - cs['beta1'])                       # unconditional variance
uv_g <- cg['omega'] / (1 - cg['alpha1'] - cg['beta1'] - 0.5*cg['gamma1'])
eps <- seq(-5, 5, length.out = 300)
nic_s <- cs['omega'] + cs['alpha1']*eps^2 + cs['beta1']*uv_s
nic_g <- cg['omega'] + (cg['alpha1'] + cg['gamma1']*(eps < 0))*eps^2 + cg['beta1']*uv_g
df <- rbind(data.frame(eps=eps, sig2=nic_s, model='symmetric sGARCH'),
            data.frame(eps=eps, sig2=nic_g, model='gjrGARCH (leverage)'))
ggplot(df, aes(eps, sig2, colour=model)) +
  geom_line(linewidth=1.1) +
  geom_vline(xintercept=0, colour='grey60', linetype=2) +
  annotate('text', x=-3.4, y=max(nic_g)*0.93, label='bad news\n(steeper)', colour='firebrick', size=3.4) +
  annotate('text', x= 3.4, y=max(nic_g)*0.55, label='good news\n(flatter)', colour='firebrick', size=3.4) +
  scale_colour_manual(values=c('symmetric sGARCH'='steelblue', 'gjrGARCH (leverage)'='firebrick')) +
  labs(title='News impact curve (Engle-Ng): gjrGARCH is steeper after bad news',
       x=expression(epsilon[t-1]~'  (shock, %)'), y=expression(sigma[t]^2~'  (next-period variance)'), colour='') +
  theme_minimal(base_size=13) + theme(legend.position='top')
No description has been provided for this image

Results¶

  • rugarch confirms the leverage effect: a positive gjrGARCH $\gamma_1 \approx 0.11$ (with $\alpha_1$ collapsing to $\approx 0.01$) and the EGARCH sign term, both improving AIC over symmetric sGARCH — matching the arch results.
  • The Engle–Ng news impact curve draws the asymmetry directly: the negative-shock arm is steeper, so bad news raises tomorrow's variance more than good news of the same size. Two frequentist engines agree on the leverage effect.
  • Trio agreement: rugarch gjrGARCH $\gamma_1 > 0$ here, the from-scratch five-method $\gamma \approx 0.13$, and PyMC $P(\gamma > 0) = 1$ — leverage is unanimous across frequentist and Bayesian, Python and R. (Because bayesGARCH has no asymmetric variant, the Bayesian asymmetric fits live in the Python notebooks.)