Source code for nelson_siegel_svensson.nss

# -*- coding: utf-8 -*-

"""Implementation of a Nelson-Siegel-Svensson interest rate curve model.
See `NelsonSiegelSvenssonCurve` class for details.
"""

from numbers import Real
from dataclasses import dataclass
from typing import Union, Tuple

import numpy as np
from numpy import exp

EPS = np.finfo(float).eps


[docs]@dataclass class NelsonSiegelSvenssonCurve: """Implementation of a Nelson-Siegel-Svensson interest rate curve model. This curve can be interpreted as a factor model with four factors (including a constant). """ beta0: float beta1: float beta2: float beta3: float tau1: float tau2: float
[docs] def factors( self, T: Union[float, np.ndarray] ) -> Union[Tuple[float, float, float], Tuple[np.ndarray, np.ndarray, np.ndarray]]: """Factor loadings for time(s) T, excluding constant.""" tau1 = self.tau1 tau2 = self.tau2 if isinstance(T, Real) and T <= 0: return 1, 0, 0 elif isinstance(T, np.ndarray): zero_idx = T <= 0 T[zero_idx] = EPS # avoid warnings in calculations exp_tt1 = exp(-T / tau1) exp_tt2 = exp(-T / tau2) factor1 = (1 - exp_tt1) / (T / tau1) factor2 = factor1 - exp_tt1 factor3 = (1 - exp_tt2) / (T / tau2) - exp_tt2 if isinstance(T, np.ndarray): T[zero_idx] = 0 factor1[zero_idx] = 1 factor2[zero_idx] = 0 factor3[zero_idx] = 0 return factor1, factor2, factor3
[docs] def factor_matrix(self, T: Union[float, np.ndarray]) -> Union[float, np.ndarray]: """Factor loadings for time(s) T as matrix columns, including constant column (=1.0). """ factor1, factor2, factor3 = self.factors(T) constant: Union[float, np.ndarray] = ( np.ones(T.size) if isinstance(T, np.ndarray) else 1.0 ) return np.stack([constant, factor1, factor2, factor3]).transpose()
[docs] def zero(self, T: Union[float, np.ndarray]) -> Union[float, np.ndarray]: """Zero rate(s) of this curve at time(s) T.""" beta0 = self.beta0 beta1 = self.beta1 beta2 = self.beta2 beta3 = self.beta3 factor1, factor2, factor3 = self.factors(T) res = beta0 + beta1 * factor1 + beta2 * factor2 + beta3 * factor3 return res
def __call__(self, T: Union[float, np.ndarray]) -> Union[float, np.ndarray]: """Zero rate(s) of this curve at time(s) T.""" return self.zero(T)
[docs] def forward(self, T: Union[float, np.ndarray]) -> Union[float, np.ndarray]: """Instantaneous forward rate(s) of this curve at time(s) T.""" exp_tt0 = exp(-T / self.tau1) exp_tt1 = exp(-T / self.tau2) return ( self.beta0 + self.beta1 * exp_tt0 + self.beta2 * exp_tt0 * T / self.tau1 + self.beta3 * exp_tt1 * T / self.tau2 )