cultivars.multivariate.reduced_form.term_structure#

The dynamic Nelson-Siegel model: the yield curve as three moving factors.

Nelson and Siegel (1987) fit one date’s curve with three loadings in a single decay parameter,

\[y(\tau) = L + S\,\frac{1 - e^{-\lambda\tau}}{\lambda\tau} + C\left(\frac{1 - e^{-\lambda\tau}}{\lambda\tau} - e^{-\lambda\tau}\right),\]

whose coefficients read as level, slope and curvature. Diebold and Li (2006) turned the per-date fit into a time-series model by letting the three coefficients evolve, and Diebold, Rudebusch and Aruoba (2006) wrote that as one state space: diagonal autoregressions for the factors, curves observing them through the loadings with per-maturity measurement noise. That is the form estimated here, by exact maximum likelihood through the Kalman filter in a single step, with the Diebold-Li two-step regression surviving as the optimizer’s warm start. Koopman, Mallee and van der Wel (2010) then let the decay move as well, \(\log\lambda_t\) a fourth autoregressive state, so the loadings themselves become time-varying and the measurement nonlinear; that model is estimated through the extended or unscented filter on the nonlinear substrate.

Two commitments shape the surface. First, exactness is tracked, not assumed. The constant-decay likelihood is exact and one number, so nested specifications – decay estimated against decay fixed at a published value – compare by the usual criteria; the time-varying model’s likelihood is whichever filter’s Gaussian approximation was maximized, and its result says so in its summary, in its comparison label and in the note that travels with the criteria, because a reader comparing the two by AIC is comparing an exact number with an approximate one. Second, the measurement is per-maturity and tolerant of gaps. Each point on the curve carries its own fitted noise variance, so an illiquid tenor is down-weighted by its own scatter instead of contaminating the factors, and the linear model’s filter handles element-wise missingness, so ragged panels – maturities that enter and leave the sample – estimate without imputation. The nonlinear model inherits only whole-row missingness from its substrate and refuses partial rows rather than imputing them.

Layout. DynamicNelsonSiegel validates the panel and maturities inline, builds _NelsonSiegelObjective from _internals – whose starts() is the Diebold-Li two-step warm start – maximizes it with _maximize_likelihood, and reads the smoother through LinearGaussianSSM. TimeVaryingNelsonSiegel is a thin fit() over _DecayNelsonSiegelModel in _internals, which runs the constant-decay fit first, appends the three decay parameters, maximizes _DecayNelsonSiegelObjective through the chosen filter and smooths with the matching smoother; _decay_nelson_siegel_state_space emits the fitted NonlinearSSM. Both results carry the summary and comparison mixins; the loadings are _nelson_siegel_loadings in _core, and the hump maturity \(1.79 / \lambda\) is where the curvature loading peaks. Each result rebuilds its parameter record for the emitter from the public fields, so a result constructed by hand is as usable as a fitted one.

References

Nelson, C. R., & Siegel, A. F. (1987). Parsimonious modeling of yield curves. Journal of Business, 60(4), 473-489.

Diebold, F. X., & Li, C. (2006). Forecasting the term structure of government bond yields. Journal of Econometrics, 130(2), 337-364.

Diebold, F. X., Rudebusch, G. D., & Aruoba, S. B. (2006). The macroeconomy and the yield curve: A dynamic latent factor approach. Journal of Econometrics, 131(1-2), 309-338.

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

Sixty curves on eight maturities from a constant-decay model. The one-step fit recovers the decay, and the time-varying model, asked whether the decay moves, reports a smoothed path that barely does and loses the comparison on its two extra parameters – the honest answer on data where the decay is constant:

>>> import numpy as np
>>> from cultivars.engine._core._polynomials import _nelson_siegel_loadings
>>> rng = np.random.default_rng(0)
>>> taus = np.array([0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 7.0, 10.0])
>>> mu = np.array([5.0, -1.5, 1.0])
>>> f = np.tile(mu, (60, 1))
>>> for t in range(1, 60):
...     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.02 * rng.standard_normal((60, 8))
>>> dns = DynamicNelsonSiegel(curves, taus).fit()
>>> round(dns.decay, 2), dns.n_params
(0.62, 21.0)
>>> tv = TimeVaryingNelsonSiegel(curves, taus).fit(filter="extended")
>>> bool(tv.decay.max() - tv.decay.min() < 0.1), tv.n_params
(True, 23.0)
>>> table = dns.compare(tv)
>>> table.rows[0][0], table.rows[1][0]
('DNS(lambda estimated)', 'DNS-TV[extended]')

Classes#

DynamicNelsonSiegel

One-step maximum-likelihood dynamic Nelson-Siegel estimation.

DynamicNelsonSiegelResult

A fitted dynamic Nelson-Siegel model: factors, decay, and noise.

TimeVaryingNelsonSiegel

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

TimeVaryingNelsonSiegelResult

A fitted Nelson-Siegel model with a time-varying loading decay.