TimeVaryingNelsonSiegel#

class cultivars.multivariate.reduced_form.term_structure.TimeVaryingNelsonSiegel(panel, maturities)[source]

Bases: _DecayNelsonSiegelModel[TimeVaryingNelsonSiegelResult]

The dynamic Nelson-Siegel model with a time-varying loading decay.

The state is (level, slope, curvature, log lambda), each a diagonal AR(1); the measurement is the Nelson-Siegel curve at the current \(\lambda_t\). Estimated by maximizing the unscented (default) or extended filter’s likelihood from the constant-decay exact fit, which is run first as the warm start; the three decay parameters are appended to that fit’s vector and the whole vector is re-optimized through the chosen filter. The unscented filter is the default because the loadings are smooth and bounded in \(\lambda\), so its sigma points capture the nonlinearity that a first-order Jacobian flattens; it costs two to three times the extended filter’s running time.

Variables:
  • _panel – The validated (nobs, p) yield panel, whole rows nan where missing.

  • _maturities – The validated (p,) maturities.

See also

References

Koopman, S. J., Mallee, M. I. P., & van der Wel, M. (2010). Analyzing the term structure of interest rates using the dynamic Nelson-Siegel model with time-varying parameters. Journal of Business & Economic Statistics, 28(3), 329-343.

Example

>>> import numpy as np
>>> from cultivars.engine._core._polynomials import _nelson_siegel_loadings
>>> rng = np.random.default_rng(0)
>>> taus = np.array([0.25, 1.0, 2.0, 5.0, 10.0])
>>> f = np.zeros((120, 3))
>>> mu = np.array([5.0, -1.5, 0.5])
>>> f[0] = mu
>>> for t in range(1, 120):
...     f[t] = mu + 0.9 * (f[t - 1] - mu) + 0.2 * rng.standard_normal(3)
>>> curves = f @ _nelson_siegel_loadings(taus, 0.6).T
>>> curves = curves + 0.05 * rng.standard_normal((120, 5))
>>> res = TimeVaryingNelsonSiegel(curves, taus).fit()
>>> bool(np.corrcoef(res.level, f[:, 0])[0, 1] > 0.95)
True