cultivars.forecast.conditional#
Conditional forecasts: the system’s future given stated paths for part of it.
A policy scenario is a conditional forecast: hold the policy rate on a stated path, or oil prices, or a fiscal variable, and ask what the rest of the system does. Waggoner and Zha (1999) make the question exact for any closed linear system. Write the future in moving-average form,
with \(P\) the lower Cholesky factor of the innovation covariance; each conditioned cell is then one linear restriction on the stacked future shocks, \(R \varepsilon = r\), and the forecast is the law of the system given those restrictions – a Gaussian centered on the minimum-norm shock path \(\varepsilon^\ast = R^\top (R R^\top)^{-1} r\) that delivers the conditions, spread over the shock directions the conditions leave free with covariance \(I - R^\top (R R^\top)^{-1} R\). Under a sampled result every retained \((B, \Sigma)\) draw contributes its own conditional paths, so parameter uncertainty is kept.
Two commitments shape the surface. First, the conditions are hard: each conditioned cell is met exactly on every path. Interval (soft) conditions, which would let a cell wander inside a band, are not implemented, and the record says so in its notes rather than leaving a degenerate band to be misread. Second, the shocks that deliver the scenario are reported, together with a modesty reading (Leeper & Zha, 2003): \(\|\varepsilon^\ast\|^2\) is \(\chi^2\) on the number of conditions under the model, so a scenario that needs shocks far larger than the model deems typical is one whose forecast the model itself does not believe, and the summary says so rather than printing the bands as though they meant the same thing as an unconditional forecast’s.
Layout. conditional_forecast() is the producer and
ConditionalForecastResult the record, which carries the paths,
the implied shocks, and the modesty reading, and satisfies
PredictiveResult so that a fan chart or a
density score reads it like any other density forecaster. The numerics
live in _core: _validate_conditions turns the name-to-path
mapping into a (steps, k) grid with nan for free cells,
_moving_average_from_stack recovers \(\Psi_j\) from a lag stack,
_conditional_restrictions builds \(R\) and \(r\),
_draw_conditional_shocks returns \(\varepsilon^\ast\) and the
restricted Gaussian draws, and _moving_average_paths propagates them;
_simulate_vector_autoregression in _internals supplies the
per-draw mean path under a sampled result.
References
Waggoner, D. F., & Zha, T. (1999). Conditional forecasts in dynamic multivariate models. Review of Economics and Statistics, 81(4), 639-651.
Leeper, E. M., & Zha, T. (2003). Modest policy interventions. Journal of Monetary Economics, 50(8), 1673-1700.
Doan, T., Litterman, R., & Sims, C. (1984). Forecasting and conditional projection using realistic prior distributions. Econometric Reviews, 3(1), 1-100.
Example
A two-variable system with the rate held at one for two quarters, and the modesty reading that says the model finds that ordinary:
>>> import numpy as np
>>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR
>>> rng = np.random.default_rng(0)
>>> y = np.zeros((200, 2))
>>> for t in range(1, 200):
... y[t] = np.array([[0.5, 0.2], [0.1, 0.6]]) @ y[t - 1] + rng.standard_normal(2)
>>> res = VAR(y, order=1, names=("gdp", "rate")).fit()
>>> scenario = conditional_forecast(res, 4, {"rate": [1.0, 1.0, None, None]}, seed=0)
>>> bool(np.allclose(scenario.paths[:, :2, 1], 1.0))
True
>>> scenario.n_conditions, scenario.paths.shape
(2, (1000, 4, 2))
>>> round(scenario.modesty, 2), bool(scenario.modesty_pvalue > 0.05)
(1.38, True)
Classes#
A forecast of the whole system given stated future paths of some of it. |
Functions#
|
Forecast a closed system given stated future values of some of its variables. |