Stochastic volatility with fat tails & leverage — in R (stochvol)¶

The R validation of the SV extensions (companion to sv_ext_numpyro.ipynb)¶

sv_ext_numpyro.ipynb fit the SV extensions from a hand-written NumPyro model; here the stochvol package (Kastner, ASIS sampler) fits the same 2×2 grid directly, as an independent check on the two extension parameters that matter:

Gaussian innovations Student-$t$ innovations
no leverage svsample — baseline SV svtsample — adds tail index $\nu$
leverage svlsample — adds $\rho=\mathrm{corr}(\varepsilon_t,\eta_t)$ svtlsample — adds both $\nu$ and $\rho$

The baseline model is $r_t=e^{h_t/2}\varepsilon_t,\ h_t=\mu+\phi(h_{t-1}-\mu)+\sigma_\eta\eta_t$. Two extensions relax the two strongest assumptions:

  • Fat tails — let $\varepsilon_t\sim t_\nu$. A finite $\nu$ says returns have heavier tails than the log-normal SV mixture alone can produce (the SV analogue of GARCH-$t$).
  • Leverage — let $\mathrm{corr}(\varepsilon_t,\eta_t)=\rho<0$, so a negative return shock is contemporaneously paired with an upward volatility shock. This is the SV analogue of the GJR/Heston–Nandi $\gamma$ and the continuous-time Heston $\rho$ — the same asymmetry, a third representation.
In [1]:
suppressMessages(library(stochvol))
d <- read.csv('sp500ret.csv'); r <- d$ret - mean(d$ret)      # de-meaned S&P 500 daily returns, 1987-2009
set.seed(1); D <- 5000; B <- 1500
g  <- svsample  (r, draws=D, burnin=B, quiet=TRUE)           # baseline SV
tt <- svtsample (r, draws=D, burnin=B, quiet=TRUE)           # + Student-t  (nu)
l  <- svlsample (r, draws=D, burnin=B, quiet=TRUE)           # + leverage   (rho)
tl <- svtlsample(r, draws=D, burnin=B, quiet=TRUE)           # + both       (nu, rho)

row <- function(nm, res){
  p <- as.matrix(res$para[[1]]); cn <- colnames(p)
  nu  <- if("nu"  %in% cn) sprintf("%5.2f",  mean(p[,"nu"]))  else "    -"
  rho <- if("rho" %in% cn) sprintf("%+.3f",  mean(p[,"rho"])) else "     -"
  cat(sprintf("%-15s  mu %6.3f   phi %.4f   sigma %.4f   nu %s   rho %s\n",
              nm, mean(p[,"mu"]), mean(p[,"phi"]), mean(p[,"sigma"]), nu, rho))
}
cat("stochvol posterior means (S&P 500):\n")
row("SV Gaussian", g); row("SV-t", tt); row("SV-leverage", l); row("SV-t-leverage", tl)
stochvol posterior means (S&P 500):
SV Gaussian      mu -0.229   phi 0.9872   sigma 0.1506   nu   Inf   rho +0.000
SV-t             mu -0.138   phi 0.9936   sigma 0.1046   nu  8.80   rho +0.000
SV-leverage      mu -0.099   phi 0.9789   sigma 0.1859   nu   Inf   rho -0.516
SV-t-leverage    mu -0.014   phi 0.9876   sigma 0.1353   nu 10.09   rho -0.616

Reading the two extension parameters¶

  • The tail index $\nu$ lands around 9–10 in both $t$ models: returns are fatter-tailed than the baseline SV allows, but not wildly so (a Gaussian would be $\nu\to\infty$).
  • The leverage $\rho$ is firmly negative in both leverage models ($\approx-0.5$ to $-0.6$, entire 95% interval below zero): down-moves and volatility-up-moves are contemporaneously linked. This is the same leverage effect the GARCH notebooks find as $\gamma>0$ and the option notebooks price as the skew.

The figure below shows the two posteriors against the NumPyro fits (sv_ext_numpyro), plus the fitted volatility path of the full SV-$t$-leverage model.

In [2]:
options(repr.plot.width=14, repr.plot.height=4.5)
pt <- as.matrix(tt$para[[1]]); pl <- as.matrix(l$para[[1]]); ptl <- as.matrix(tl$para[[1]])
par(mfrow=c(1,3), mar=c(4.2,3,3.4,1))

# --- leverage rho ---
plot(density(pl[,'rho']), col='firebrick', lwd=2, xlim=c(-0.85,0.05), yaxt='n', ylab='',
     xlab=expression(rho), main='Leverage  rho = corr(eps, eta)')
lines(density(ptl[,'rho']), col='firebrick', lwd=2, lty=2)
abline(v=0, col='grey60'); abline(v=c(-0.61,-0.73), col='navy', lty=3)
legend('topleft', c('SV-leverage','SV-t-leverage','NumPyro -0.61/-0.73'),
       col=c('firebrick','firebrick','navy'), lty=c(1,2,3), lwd=c(2,2,1), bg='white', box.col='grey80', cex=0.85)

# --- tail index nu ---
plot(density(pt[,'nu']), col='steelblue', lwd=2, xlim=c(4,20), yaxt='n', ylab='',
     xlab=expression(nu), main='Tail index  nu  (fat tails)')
lines(density(ptl[,'nu']), col='steelblue', lwd=2, lty=2)
abline(v=9.2, col='navy', lty=3)
legend('topright', c('SV-t','SV-t-leverage','NumPyro ~9'),
       col=c('steelblue','steelblue','navy'), lty=c(1,2,3), lwd=c(2,2,1), bg='white', box.col='grey80', cex=0.85)

# --- fitted volatility (full model) ---
h <- as.matrix(latent(tl)); vol <- exp(colMeans(h)/2)
qv <- exp(apply(h, 2, quantile, c(.05,.95))/2)
plot(abs(r), type='l', col='grey80', ylim=c(0,8.5), xlab='trading day', ylab='daily vol (%)',
     main='SV-t-leverage volatility (S&P 500)')
polygon(c(seq_along(vol), rev(seq_along(vol))), c(qv[1,], rev(qv[2,])),
        col=rgb(0.7,0.1,0.1,0.25), border=NA)
lines(vol, col='firebrick', lwd=1)
legend('topright', c('|return|','posterior vol','90% band'),
       col=c('grey70','firebrick',rgb(0.7,0.1,0.1,0.4)), lwd=c(1,2,6), bg='white', box.col='grey80', cex=0.85)
No description has been provided for this image

Results¶

Two engines, one answer. stochvol (ASIS, C++) reproduces the sv_ext_numpyro (hand-written NUTS) fits on the same data:

parameter stochvol sv_ext_numpyro
$\nu$ (SV-$t$) 8.8 ~9.1
$\nu$ (SV-$t$-leverage) 10.1 ~9.2
$\rho$ (SV-leverage) −0.52 −0.61
$\rho$ (SV-$t$-leverage) −0.62 −0.73
  • Both extensions are real. $\nu\approx 9$–10 (fat tails survive under stochastic volatility) and $\rho\approx-0.5$ to $-0.6$ entirely below zero (leverage). The two samplers agree on sign and broad magnitude (the $\rho$ estimates differ by ~15% — −0.52/−0.62 vs −0.61/−0.73 — reflecting the different leverage parameterisations, as with $\mu$ below).
  • Leverage is the recurring character. $\rho<0$ here is the same asymmetry as GJR/Heston–Nandi $\gamma>0$ (garch_asym_*) and Heston $\rho<0$ (heston1993) — bad news raises volatility more than good news, whether written as an observation-driven recursion, a latent-state correlation, or an option skew.
  • A parameterisation caveat. $\phi$, $\sigma_\eta$, $\nu$ and $\rho$ match across engines; the level $\mu$ drifts as extensions are added (Gaussian −0.23 → t-leverage −0.01) because stochvol does not rescale the $t$ innovations to unit variance while sv_ext_numpyro does — the two conventions differ by $\log\frac{\nu}{\nu-2}$, which $\mu$ absorbs. The comparable quantities agree.

Formal model comparison by the Widely Applicable Information Criterion (WAIC, lower = better) lives in sv_ext_numpyro — WAIC falls monotonically SV > SV-$t$ > SV-leverage > SV-$t$-leverage, so SV-$t$-leverage is preferred; here stochvol independently confirms the parameters those models estimate.

References¶

  • Kastner, G. & Frühwirth-Schnatter, S. (2014). Ancillarity-sufficiency interweaving strategy (ASIS). CSDA — the sampler behind stochvol.
  • Kastner, G. (2016). Dealing with stochastic volatility in time series using the R package stochvol. JSS.
  • Omori, Y., Chib, S., Shephard, N. & Nakajima, J. (2007). Stochastic volatility with leverage. J. Econometrics — the leverage SV model.
  • Harvey, A. C. & Shephard, N. (1996). Estimation of an asymmetric stochastic volatility model. JBES.