cultivars.multivariate.structural.stochastic_volatility#

Identification through stochastic volatility: the regimes nobody has to declare.

Rigobon’s scheme buys identification from variance shifts, but it needs the user to say when the shifts happen. This module drops that requirement. Give each structural shock its own latent log-AR(1) variance,

\[u_t = B \varepsilon_t, \qquad \varepsilon_{jt} = e^{h_{jt}/2} \eta_{jt}, \qquad h_{jt} = \phi_j h_{j,t-1} + \sigma_j \xi_{jt},\]

so that the reduced-form covariance \(\Sigma_t = B\, \operatorname{diag}(e^{h_t})\, B'\) moves every period; as long as the variance paths are not proportional to one another, the sequence of covariances pins \(B\) down up to column order and sign (Lewis 2021; Bertsche and Braun 2022). No zero, no sign pattern, no instrument, and no regime dates – the volatility processes are estimated along with the impact matrix. Estimation is Bayesian, by an exact Gibbs sampler on the reduced-form innovations of a fitted closed system: the rows of \(A = B^{-1}\) by Waggoner and Zha’s (2003) construction given the paths, the paths by the Kim-Shephard-Chib mixture step given \(A\), and each path’s persistence and innovation variance from their conditionals. What comes back is a posterior over impact matrices, not a point, and StochasticVolatilitySVARResult holds it: the quantile surfaces of impulse responses summarize a set of complete structural models, none of which traces any one surface.

The module also houses the univariate long-memory relative of the same volatility law, LongMemorySV: \(y_t = c + e^{h_t/2} \epsilon_t\) with \((1 - \phi L)(1 - L)^d (h_t - \mu) = \eta_t\) (Breidt, Crato and de Lima 1998), whose fractional operator has no finite state and is therefore estimated by Whittle in the frequency domain rather than by any filter. It identifies nothing structural; it is here because it is the one-series statement of what a volatility path is and the natural diagnostic for whether an AR(1) log variance is the right law for a shock at all.

Two commitments shape the surface. First, what the data cannot supply is said, not hidden. The structural shocks are statistical objects, labelled by which variable they load on most and signed to a positive diagonal – a convention the summary names as such, with an economic name left as a claim to be argued from outside the model; and the scale is the one the likelihood can separate, since it cannot tell a path’s level from its column’s scale, so every draw is expressed with zero sample-mean log variance per shock and the level folded into \(B\), the impact of a shock at its in-sample average variance. Second, identification is tested, not assumed. With proportional variance paths the likelihood is flat over rotations yet the smooth-AR(1) prior still returns a sharp posterior, chain after chain, so the posterior cannot certify itself; the identifying condition – that the shocks’ variance ratios move over time – is tested on the posterior-median shocks, pair by pair, and the verdict is printed with the result alongside the relabelling rate, the second symptom of the same failure.

Layout. StochasticVolatilitySVAR is a _VolatilityIdentificationModel from _internals, whose _sample runs the three-block Gibbs sampler and relabels the kept draws into a _VolatilityStructuralFit; the prior is VolatilityPrior; the identification test is _variance_ratio_test in _core, the quantile levels pass through _validate_quantiles, and the summary renders through the shared _LABEL_NOTE and _SCALE_NOTE. The posterior median lands on SVARResult through to_point(), and convergence is read through _ConvergenceMixin. LongMemorySV is a _LongMemoryVolatilityModel whose _fit_whittle maximizes the criterion over _fractional_spectrum from _core and emits the truncated AR(inf) law through _long_memory_quasi_state_space in _internals. The declared-regime version of the identification is heteroskedacity.

References

Lewis, D. J. (2021). Identifying shocks via time-varying volatility. Review of Economic Studies, 88(6), 3086-3124.

Bertsche, D., & Braun, R. (2022). Identification of structural vector autoregressions by stochastic volatility. Journal of Business & Economic Statistics, 40(1), 328-341.

Waggoner, D. F., & Zha, T. (2003). A Gibbs sampler for structural vector autoregressions. Journal of Economic Dynamics and Control, 28(2), 349-366.

Kim, S., Shephard, N., & Chib, S. (1998). Stochastic volatility: Likelihood inference and comparison with ARCH models. Review of Economic Studies, 65(3), 361-393.

Breidt, F. J., Crato, N., & de Lima, P. (1998). The detection and estimation of long memory in stochastic volatility. Journal of Econometrics, 83(1-2), 325-348.

Example

The same impact matrix under two volatility laws. With distinct paths the identification test passes and the column directions are recovered; with one path shared by both shocks the chain still returns a sharp matrix, and the test is what says it means nothing:

>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> B = np.array([[1.0, 0.5], [0.3, 1.0]])
>>> def simulate(h):
...     shocks = np.exp(h / 2) * rng.standard_normal((400, 2))
...     y = np.zeros((400, 2))
...     for t in range(1, 400):
...         y[t] = 0.4 * y[t - 1] + B @ shocks[t]
...     return VAR(y, order=1, names=("gdp", "infl")).fit()
>>> distinct = np.zeros((400, 2))
>>> for t in range(1, 400):
...     distinct[t] = [0.97, 0.6] * distinct[t - 1] + [0.2, 0.6] * rng.standard_normal(2)
>>> shared = np.cumsum(0.2 * rng.standard_normal(400)) * 0.3
>>> chain = dict(n_draws=300, n_burn=100, seed=0)
>>> good = StochasticVolatilitySVAR(simulate(distinct)).identify(**chain)
>>> same = simulate(np.column_stack([shared, shared]))
>>> bad = StochasticVolatilitySVAR(same).identify(**chain)
>>> dict(good.to_point().diagnostics)["Identification"]
'variance ratios move over time for every pair'
>>> dict(bad.to_point().diagnostics)["Identification"]
"WEAK: some pair's variance ratio is not detectably time-varying"
>>> unit = lambda v: v / np.linalg.norm(v)
>>> bool(np.abs(unit(good.impact[:, 1]) - unit(B[:, 1])).max() < 0.1)
True

Classes#

LongMemorySV

Long-memory stochastic volatility, Breidt, Crato, and de Lima (1998).

LongMemorySVResult

A Whittle-estimated long-memory stochastic-volatility model.

StochasticVolatilitySVAR

Identification through stochastic volatility, Lewis (2021) / Bertsche-Braun (2022).

StochasticVolatilitySVARResult

The posterior of a stochastic-volatility identification.