cultivars.multivariate.nonlinear.quantile#

The quantile VAR: one autoregression per quantile level, no error term at all.

A quantile VAR models the conditional quantiles of each variable as linear in the system’s lags,

\[Q_\tau(y_{i,t} \mid y_{t-1}, \ldots, y_{t-p}) = c_i(\tau)' d_t + \sum_{l=1}^{p} a_{i,l}(\tau)' y_{t-l}, \qquad \tau \in (0, 1),\]

one full coefficient stack per requested level, so the tails get their own dynamics rather than the mean’s dynamics plus a symmetric error. This is the natural language for macro-financial tail risk: Adrian, Boyarchenko and Giannone’s growth-at-risk finding is exactly that the lower quantiles of GDP growth respond to financial conditions in a way the median does not, which a conditional-mean VAR cannot express at any lag length. Estimation is the check-loss program of Koenker and Bassett,

\[\hat\beta_i(\tau) = \arg\min_\beta \sum_t \rho_\tau(y_{i,t} - x_t'\beta), \qquad \rho_\tau(e) = e\,(\tau - \mathbf{1}\{e < 0\}),\]

solved exactly as a linear program, equation by equation and level by level, since the loss is additive across both.

Two commitments shape the surface. First, three consequences of minimizing a check loss rather than a likelihood are enforced rather than papered over. There is no llf, no parameter-count comparison, and no information criteria – check loss is not a likelihood and numbers shaped like those would rank nothing meaningful; the Koenker-Machado \(R^1\), local to its level, is the goodness of fit that does exist and is reported. There is no orthogonalized impulse response or variance decomposition – a quantile system has no innovation covariance to factor, so QVARResult.irf() returns the reduced-form propagation of one level’s coefficients and says what linearization that is; for the same reason the result does not satisfy the closed-system contract and cannot be passed to the structural layer or to the spillover view. And the forecast is one step only – iterating a quantile equation compounds quantiles of quantiles, which is not the multi-step quantile of anything; the honest multi-horizon object is a direct regression at each horizon, a different model the caller must state. Second, separately fitted levels can cross – the estimated 90th percentile path can dip below the estimated median in finite samples – and the result measures this (QVARResult.crossing_share) instead of silently rearranging the fits. Standard errors are not yet offered; the summary says so.

Layout. QVAR validates on _QuantileVectorAutoRegressionModel in _internals, which re-specifies the linear VAR base without its prior surface and holds the engine: _quantile_regression solves one equation at one level through scipy.optimize.linprog with the HiGHS backend on a sparse constraint matrix, _check_loss scores a residual block, and _fit_quantile validates and sorts the levels, runs the \(k \times Q\) programs over the shared design, computes the unconditional-quantile benchmark losses, and packs a _VectorQuantileFit. QVARResult takes its summary from _SummaryMixin and builds each level’s companion through companion_matrix(). The conditional-mean system on the same design is vector_autoregression; the other members of this package model the mean’s coefficients as functions of a state or of time.

References

Koenker, R., & Bassett, G. (1978). Regression quantiles. Econometrica, 46(1), 33-50.

Koenker, R., & Machado, J. A. F. (1999). Goodness of fit and related inference processes for quantile regression. Journal of the American Statistical Association, 94(448), 1296-1310.

White, H., Kim, T.-H., & Manganelli, S. (2015). VAR for VaR: Measuring tail dependence using multivariate regression quantiles. Journal of Econometrics, 187(1), 169-188.

Adrian, T., Boyarchenko, N., & Giannone, D. (2019). Vulnerable growth. American Economic Review, 109(4), 1263-1289.

Example

Growth at risk in two lines. Financial conditions scale the downside shocks to growth, so the lower-tail coefficient on lagged conditions is far larger than the median’s while a conditional-mean VAR sees one number:

>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> y = np.zeros((400, 2))
>>> for t in range(1, 400):
...     fci = 0.6 * y[t - 1, 1] + rng.standard_normal()
...     shock = rng.standard_normal()
...     if shock < 0.0:
...         shock *= np.exp(0.4 * y[t - 1, 1])
...     y[t] = [0.3 * y[t - 1, 0] - 0.2 * y[t - 1, 1] + shock, fci]
>>> res = QVAR(y, order=1, names=("growth", "fci")).fit(quantiles=(0.1, 0.5, 0.9))
>>> on_fci = {q: round(float(res.coefficients(q)[0, 0, 1]), 2) for q in res.quantiles}
>>> bool(on_fci[0.1] < -0.5), bool(-0.3 < on_fci[0.5] < 0.0), bool(-0.3 < on_fci[0.9] < 0.0)
(True, True, True)
>>> mean = VAR(y, order=1).fit()
>>> bool(-0.4 < mean.coefficients[0, 0, 1] < -0.1)
True
>>> bool(res.crossing_share < 0.01), bool(res.pseudo_r_squared(0.1)["growth"] > 0.1)
(True, True)

Classes#

QVAR

Quantile vector autoregression, after Koenker and Bassett, equation by equation.

QVARResult

A fitted quantile vector autoregression, all requested levels together.