DynamicNelsonSiegelResult.forecast#

DynamicNelsonSiegelResult.forecast(steps)[source]#

Curve forecasts with honest standard errors.

Filters the estimation panel to the last factor state, then propagates mean and covariance through the fitted dynamics,

\[\hat f_{T+h} = \mu + \Phi\,(\hat f_{T+h-1} - \mu), \qquad P_{T+h} = \Phi P_{T+h-1} \Phi' + Q,\]

and maps both through the loadings, adding the measurement variance to the yield-level spread.

Parameters:

steps (int) – Horizons ahead, at least 1.

Return type:

tuple[NDArray[double], NDArray[double]]

Returns:

(mean, std) arrays of shape (steps, p) on the estimation maturities; the standard errors include factor uncertainty and measurement noise, but not parameter uncertainty.

Raises:

SpecificationError – If steps is not positive.

Example

>>> import numpy as np
>>> from cultivars.engine._core._polynomials import _nelson_siegel_loadings
>>> rng = np.random.default_rng(0)
>>> taus = np.array([0.5, 2.0, 5.0, 10.0])
>>> basis = _nelson_siegel_loadings(taus, 0.6)
>>> mu = np.array([5.0, -1.5, 0.5])
>>> f = np.tile(mu, (40, 1))
>>> for t in range(1, 40):
...     f[t] = mu + 0.9 * (f[t - 1] - mu) + 0.2 * rng.standard_normal(3)
>>> curves = f @ basis.T + 0.05 * rng.standard_normal((40, 4))
>>> res = DynamicNelsonSiegel(curves, taus, decay=0.6).fit()
>>> mean, std = res.forecast(12)
>>> mean.shape, std.shape, bool(np.all(std > 0.0))
((12, 4), (12, 4), True)
>>> bool(np.all(np.diff(std, axis=0) >= -1e-12))
True
>>> bool(np.abs(mean[-1] - res.loadings @ res.mu).max() < 0.5)
True
>>> res.forecast(0)
Traceback (most recent call last):
cultivars.exceptions.SpecificationError: steps must be at least 1; got 0.