cultivars.multivariate.large_dim.volatility#

The Student-t Bayesian VAR: fat tails as a model, not a casualty.

Macroeconomic samples carry a handful of dates – 2008, 2020 – that a Gaussian VAR can only accommodate by inflating the covariance for every other date, which is how a few observations end up owning the error bands. Student-t innovations (Chiu, Mumtaz and Pinter, 2017) treat those dates as what they are: draws from the same system’s fatter tail. The model is

\[y_t = c + \sum_{i=1}^{p} A_i y_{t-i} + u_t, \qquad u_t \sim t_\nu(0, \Sigma), \qquad u_t \mid w_t \sim N(0, \Sigma / w_t),\quad w_t \sim \mathrm{Gamma}(\nu/2, \nu/2),\]

and the second line is how it is estimated (Geweke, 1993): each date carries a latent precision weight, and conditional on the weights the model is exactly the conjugate Normal-inverse-Wishart VAR on rows rescaled by \(\sqrt{w_t}\). The Gibbs sampler therefore alternates three exact conditionals – \((B, \Sigma)\) from the weighted conjugate update, the weights from \(\mathrm{Gamma}\bigl((\nu + k)/2, (\nu + u_t' \Sigma^{-1} u_t)/2\bigr)\), and \(\nu\) from its conditional on a fixed grid – and inherits the conjugate model’s prior machinery unchanged, dummy observations included. The reported \(\Sigma\) is the t scale; the innovation covariance is \(\Sigma\,\nu/(\nu - 2)\).

Two commitments shape the surface. First, the tail index can be stated or learned, and when learned it is learned exactly: nu is drawn from its conditional evaluated on the grid 2 .. 29, 30, 35, 40, 50, 60, 80, 100 under a prior uniform over grid points, a step with no tuning parameter and no acceptance rate. The grid is coarse above thirty because the t and the Gaussian are indistinguishable there; on data whose innovations are Gaussian the posterior settles in the teens and twenties at macro sample sizes, which is the model’s way of saying the tails were not needed, and only a long Gaussian sample pushes it to the grid’s top. Second, the latent weights are reported, as StudentBVARResult.weight_mean – a per-date outlier map, and often the most readable diagnostic the fit produces: the dates the t distribution absorbed are exactly the dates with small weights, and StudentBVARResult.outlier_dates() lists them.

Deliberately absent: the marginal likelihood. The t likelihood breaks the conjugacy that made the Gaussian model’s evidence a closed form, and a simulated stand-in would not deserve the name; rank tail specifications by predictive performance instead.

Layout. StudentBVAR defaults the prior to NormalInverseWishartPrior and validates on _StudentBayesianVectorAutoRegressionModel in _internals, which extends the conjugate _BayesianVectorAutoRegressionModel; its _fit_student runs the three-block sampler, drawing (B, Sigma) through _draw_conjugate on the reweighted rows and nu through _draw_degrees on _STUDENT_DF_GRID from _core, and packs a _VectorStudentFit. StudentBVARResult extends _VectorPosteriorDrawsResult – credible intervals, impulse responses with posterior bands, the predictive, the stability share, the chain diagnostics – and overrides its three noise hooks so that the predictive, simulate() and posterior_replications() all draw t innovations. The Gaussian conjugate model is bayesian; the other account of fat marginal tails, through time-varying volatility, is volatility.

References

Chiu, C.-W. J., Mumtaz, H., & Pinter, G. (2017). Forecasting with VAR models: Fat tails and stochastic volatility. International Journal of Forecasting, 33(4), 1124-1143.

Geweke, J. (1993). Bayesian treatment of the independent Student-t linear model. Journal of Applied Econometrics, 8(S1), S19-S40.

Example

A bivariate VAR(1) with \(t_4\) innovations next to one with Gaussian innovations: the tail index separates them, and the weight map on the fat-tailed sample points at its largest residuals:

>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> fat, normal = np.zeros((200, 2)), np.zeros((200, 2))
>>> for t in range(1, 200):
...     w = rng.gamma(2.0, 0.5)
...     fat[t] = 0.5 * fat[t - 1] + rng.standard_normal(2) / np.sqrt(w)
...     normal[t] = 0.5 * normal[t - 1] + rng.standard_normal(2)
>>> heavy = StudentBVAR(fat, order=1).fit(n_draws=400, n_burn=200, seed=0)
>>> light = StudentBVAR(normal, order=1).fit(n_draws=400, n_burn=200, seed=0)
>>> bool(heavy.df < 10.0 < light.df)
True
>>> bool(heavy.outlier_dates().size > light.outlier_dates().size)
True
>>> worst = int(np.argmax(np.abs(heavy.resid).max(axis=1)))
>>> bool(worst in heavy.outlier_dates())
True
>>> ratio = heavy.innovation_covariance / heavy.sigma_u
>>> bool(np.allclose(ratio, heavy.df / (heavy.df - 2.0)))
True

Classes#

BVARSV

Bayesian VAR with stochastic volatility, Carriero-Clark-Marcellino.

BVARSVResult

A fitted stochastic-volatility BVAR: one system, a path of covariances.