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,
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,
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#
Quantile vector autoregression, after Koenker and Bassett, equation by equation. |
|
A fitted quantile vector autoregression, all requested levels together. |