giacomini_white#

cultivars.forecast.comparison.giacomini_white(losses_a, losses_b, *, horizon=1, lags=1, instruments=None)[source]#

Giacomini-White (2006) test that a loss gap was predictable at the origin.

The conditional null \(\mathbb{E}[d_t \mid \mathcal{F}_{t-h}] = 0\) is tested through the instruments \(h_t\): a constant, lags lagged differentials, and whatever the caller adds, via the Wald statistic

\[\mathrm{GW} = T\, \bar z^\top \hat\Omega^{-1} \bar z, \qquad z_t = h_t\, d_t, \qquad \mathrm{GW} \sim \chi^2_{\dim h}\]

under the null, with \(\hat\Omega\) the uncentered sample covariance of \(z_t\) at one step and Bartlett HAC beyond. Because a loss indexed by its origin is only realized horizon steps later, the lagged differentials start at \(d_{t - h}\) – the most recent one actually known – rather than \(d_{t-1}\), and the sample drops its first horizon + lags - 1 origins. lags=0 with no instruments is the unconditional test, a chi-squared cousin of Diebold-Mariano.

The test is built for rolling-window estimation, where parameter estimates never converge and the forecasting method – model, window, estimator – is what the null is about; under a recursive scheme the null drifts as the window grows. Use ForecastComparison for the unconditional question with the small-sample correction. At one step the size is close to nominal from 100 origins on; at multi-step horizons the truncated Bartlett kernel understates the moment covariance and the test over-rejects mildly (about 7-9% at nominal 5% for a four-step horizon), which the record’s notes say.

Parameters:
  • losses_a (ArrayLike) – (T,) losses of the first forecaster, one per evaluation origin, negatively oriented.

  • losses_b (ArrayLike) – (T,) losses of the second, aligned origin by origin.

  • horizon (int) – The forecast horizon behind the losses; sets both the instrument lag and the HAC window.

  • lags (int) – Lagged differentials among the instruments.

  • instruments (ArrayLike | None) – Optional (T,) or (T, m) further instruments, each row known at its origin – a regime indicator, a volatility proxy, the sign of the last error. The caller vouches for measurability; the function cannot.

Return type:

GiacominiWhiteTest

Returns:

The GiacominiWhiteTest.

Raises:
  • DimensionError – If the series or instruments do not align.

  • SpecificationError – If there are too few origins, the horizon or lags is invalid, or the series are numerically identical.

  • NumericalError – If a series is not finite or the instruments are collinear.

Note

Caller instruments are aligned to the full (T,) series and trimmed with it, so instruments[t] must be known at origin t, before the loss at t is realized; an instrument that peeks at the outcome makes the gap predictable by construction. A constant caller instrument duplicates the built-in constant and is refused as collinear. The retained sample must hold at least eight origins, 2 * horizon + 1, and twice the instrument count, whichever is largest.

See also

References

Giacomini, R., & White, H. (2006). Tests of conditional predictive ability. Econometrica, 74(6), 1545-1578.

Example

Two forecasters whose loss gap reverses with a regime; the regime as instrument makes the reversal predictable, and the constant-only test, which is the unconditional one, sees only the near-zero average:

>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> regime = np.repeat([1.0, -1.0], 100)
>>> a = rng.standard_normal(200) ** 2 + 0.6 * regime
>>> b = rng.standard_normal(200) ** 2 - 0.6 * regime
>>> verdict = giacomini_white(a, b, instruments=regime)
>>> bool(verdict.pvalue < 0.01), verdict.instruments, verdict.df, verdict.nobs
(True, 'constant, d[t-1], z1', 3, 199)
>>> unconditional = giacomini_white(a, b, lags=0)
>>> bool(unconditional.pvalue > 0.1), unconditional.instruments, unconditional.nobs
(True, 'constant', 200)
>>> giacomini_white(a, b, horizon=2, lags=2).instruments
'constant, d[t-2], d[t-3]'