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.
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.
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)
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
stochvoldoes not rescale the $t$ innovations to unit variance whilesv_ext_numpyrodoes — 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.