TimeVaryingNelsonSiegelResult#

class cultivars.multivariate.reduced_form.term_structure.TimeVaryingNelsonSiegelResult(*, panel, maturities, mu, ar, state_innovation_cov, measurement_var, log_decay_mean, decay_ar, decay_sd, factors, factor_cov, log_decay, log_decay_std, llf, filter, nobs, n_params)[source]#

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

The Koopman-Mallee-van der Wel extension: the decay becomes a fourth latent factor with autoregressive dynamics in its log,

\[y_t(\tau) = L_t + S_t\, s(\tau, \lambda_t) + C_t\, c(\tau, \lambda_t) + \varepsilon_t(\tau), \qquad \log\lambda_t - \bar\ell = \phi_\lambda\,(\log\lambda_{t-1} - \bar\ell) + \zeta_t,\]

with the three factors keeping their diagonal autoregressions. The transition stays linear-Gaussian; the measurement is nonlinear only through the loadings’ dependence on \(\lambda_t\), so the system is additive-Gaussian and the extended or unscented filter reads it. The factor and decay paths here are the matching smoother’s output.

Note

The likelihood is an approximation and the record says so wherever it appears. llf is the chosen filter’s Gaussian approximation to the exact likelihood, so information criteria against the constant-decay model – whose likelihood is exact – rank by a heuristic, and the two filters’ values are not comparable with each other either. The decay path is weakly identified by construction: it moves the curve only through the shape of the slope and curvature loadings, so on a sparse maturity grid or a short panel the fit can collapse to a nearly constant decay with the curvature factor absorbing the slack, and a tiny decay_sd with a negative decay_ar is that collapse, not a finding. Many maturities across the hump region are what identify it.

Variables:
  • panel – The observed (nobs, p) yield panel.

  • maturities – The (p,) maturities.

  • mu – (3,) factor means (level, slope, curvature).

  • ar – (3,) diagonal factor persistences.

  • state_innovation_cov – (3, 3) factor innovation covariance.

  • measurement_var – (p,) per-maturity measurement variances.

  • log_decay_mean – Unconditional mean of the log decay.

  • decay_ar – Persistence of the log decay.

  • decay_sd – Innovation standard deviation of the log decay.

  • factors – (nobs, 3) smoothed factor paths.

  • factor_cov – (nobs, 3, 3) smoothed factor covariances.

  • log_decay – (nobs,) smoothed log-decay path.

  • log_decay_std – (nobs,) smoothed log-decay standard deviations.

  • llf – The approximate Gaussian log-likelihood of the chosen filter. Not the exact likelihood: information criteria are comparable against the constant-decay model only as a heuristic, and the summary says so.

  • filter – "extended" or "unscented".

  • nobs – Curve dates.

  • n_params – Free parameters the likelihood was maximized over.

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.

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

Example

Eight maturities across the hump region, sixty dates, and a decay that wanders between 0.44 and 1.5 around 0.6. The unscented fit tracks the level almost exactly and the log-decay path well enough to move the fitted hump with it:

>>> 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, bar = np.array([5.0, -1.5, 1.0]), np.log(0.6)
>>> f, log_lam = np.tile(mu, (60, 1)), np.full(60, bar)
>>> for t in range(1, 60):
...     f[t] = mu + 0.9 * (f[t - 1] - mu) + 0.2 * rng.standard_normal(3)
...     log_lam[t] = bar + 0.95 * (log_lam[t - 1] - bar) + 0.15 * rng.standard_normal()
>>> curves = np.stack(
...     [_nelson_siegel_loadings(taus, np.exp(log_lam[t])) @ f[t] for t in range(60)]
... )
>>> curves = curves + 0.02 * rng.standard_normal((60, 8))
>>> res = TimeVaryingNelsonSiegel(curves, taus).fit()
>>> res.filter, res.nobs, res.n_maturities, res.n_params
('unscented', 60, 8, 23.0)
>>> bool(np.corrcoef(res.level, f[:, 0])[0, 1] > 0.98)
True
>>> bool(np.corrcoef(res.log_decay, log_lam)[0, 1] > 0.7)
True
>>> bool(res.hump_maturity.max() - res.hump_maturity.min() > 1.0)
True

Attributes#

panel

The (nobs, p) yield panel as supplied, whole rows nan where missing.

maturities

The (p,) strictly positive maturities, in the panel's time unit.

mu

The (3,) unconditional factor means, in the order level, slope, curvature.

ar

The (3,) diagonal factor persistences.

state_innovation_cov

The (3, 3) factor innovation covariance.

measurement_var

The (p,) per-maturity measurement variances.

log_decay_mean

The unconditional mean \(\bar\ell\) of \(\log\lambda_t\).

decay_ar

The persistence \(\phi_\lambda\) of the log decay.

decay_sd

The innovation standard deviation of the log decay.

factors

The (nobs, 3) smoothed factor paths.

factor_cov

The (nobs, 3, 3) smoothed factor covariances.

log_decay

The (nobs,) smoothed path of \(\log\lambda_t\).

log_decay_std

The (nobs,) smoothed standard deviations of the log decay.

llf

The chosen filter's Gaussian approximation to the log-likelihood.

filter

the filter that produced llf and the paths.

nobs

Curve dates in the panel.

n_params

the constant-decay count plus the decay mean, persistence and sd.

curvature

The smoothed curvature factor, (nobs,).

decay

The smoothed decay path exp(log lambda_t), (nobs,).

decay_mean

The unconditional decay exp(log_decay_mean).

fitted

Smoothed fitted curves, (nobs, p).

hump_maturity

Where the curvature loading peaks each date, 1.79 / lambda_t.

information_criteria

AIC, BIC, and HQIC derived from the likelihood and parameter count.

level

The smoothed level factor, (nobs,).

n_maturities

Points on the curve.

slope

The smoothed slope factor, (nobs,).

state_space

The fitted system on the nonlinear substrate.

Methods#

compare(*others[, criterion])

Rank this result against others by an information criterion.

likelihood_ratio_test(unrestricted)

Test this result as the restricted model against a nesting one.

loadings(t)

The (p, 3) Nelson-Siegel loadings at date t's smoothed decay.

summary()

The estimation summary, renderable as text, HTML, or a dataframe.