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]
Bases:
_SummaryMixin,_ComparisonMixinA 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.
llfis 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 tinydecay_sdwith a negativedecay_aris 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
TimeVaryingNelsonSiegel– the model whosefit()returns this record.DynamicNelsonSiegelResult– the constant-decay model with the exact likelihood, and this fit’s warm start.NonlinearSSM– the type ofstate_space.
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
Private attributes#
The parameter record, rebuilt for the emitter. |
Private methods#
Specification label used when this result appears in a ranking. |
|
|
Assemble the public result from a raw fit and its specification. |
Structured summary rendered by every display path. |