Skip to content

Yield Curve

quantflow.rates.yield_curve.YieldCurve pydantic-model

Bases: BaseModel, ABC

Abstract base class for yield curves

instanteous_forward_rate abstractmethod

instanteous_forward_rate(ttm)

Calculate the instantaneous forward rate for a given time to maturity

The instantaneous forward rate is related to discount factor by the following formula:

\[\begin{equation} f(\tau) = -\frac{\partial \ln D(\tau)}{\partial \tau} \end{equation}\]

where \(D(\tau)\) is the discount factor for a given time to maturity \(\tau\).

Source code in quantflow/rates/yield_curve.py
@abstractmethod
def instanteous_forward_rate(self, ttm: float) -> Decimal:
    r"""Calculate the instantaneous forward rate for a given time to maturity

    The instantaneous forward rate is related to discount factor
    by the following formula:

    \begin{equation}
        f(\tau) = -\frac{\partial \ln D(\tau)}{\partial \tau}
    \end{equation}

    where $D(\tau)$ is the discount factor for a given time to maturity $\tau$.
    """

discount_factor abstractmethod

discount_factor(ttm)

Calculate the discount factor for a given time to maturity

The discount factor is related to the instantaneous forward rate by the following formula:

\[\begin{equation} D(\tau) = \exp{\left(-\int_0^\tau f(s) ds\right)} \end{equation}\]

where \(f(\tau)\) is the instantaneous forward rate for a given time to maturity \(\tau\).

Source code in quantflow/rates/yield_curve.py
@abstractmethod
def discount_factor(self, ttm: float) -> Decimal:
    r"""Calculate the discount factor for a given time to maturity

    The discount factor is related to the instantaneous forward rate
    by the following formula:

    \begin{equation}
        D(\tau) = \exp{\left(-\int_0^\tau f(s) ds\right)}
    \end{equation}

    where $f(\tau)$ is the instantaneous forward rate for a given time to
    maturity $\tau$.
    """

quantflow.rates.nelson_siegel.NelsonSiegel pydantic-model

Bases: YieldCurve

Class representing a Nelson-Siegel yield curve

The Nelson-Siegel model is a popular parametric model for fitting the term structure of interest rates. It is defined by the following formula for the instantaneous forward rate:

\[\begin{equation} f(\tau) = \beta_1 + \beta_2 e^{-\lambda \tau} + \beta_3 \lambda \tau e^{-\lambda \tau} \end{equation}\]

where \(\tau\) is the time to maturity, \(\beta_1\) is the level parameter, \(\beta_2\) is the slope parameter, \(\beta_3\) is the curvature parameter and \(\lambda\) is the decay factor.

Fields:

beta1 pydantic-field

beta1

Level parameter

beta2 pydantic-field

beta2

Slope parameter

beta3 pydantic-field

beta3

Curvature parameter

lambda_ pydantic-field

lambda_

Decay factor

instanteous_forward_rate

instanteous_forward_rate(ttm)
Source code in quantflow/rates/nelson_siegel.py
def instanteous_forward_rate(self, ttm: Number) -> Decimal:
    ttmd = to_decimal(ttm)
    if ttmd <= 0:
        return self.beta1 + self.beta2
    else:
        tt = ttmd * self.lambda_
        et = (-tt).exp()
        return self.beta1 + self.beta2 * et + self.beta3 * tt * et

discount_factor

discount_factor(ttm)

Calculate the discount factor for a given time to maturity.

The discount factor is calculated using the formula:

\[\begin{align*} D(\tau) &= e^{-r(\tau) \tau} \\ r(\tau) &= \beta_1 + \beta_2 \frac{1 - e^{-\lambda \tau}} {\lambda \tau} + \beta_3 \left(\frac{1 - e^{-\lambda \tau}}{\lambda \tau} - e^{-\lambda \tau}\right) \end{align*}\]
Source code in quantflow/rates/nelson_siegel.py
def discount_factor(self, ttm: Number) -> Decimal:
    r"""Calculate the discount factor for a given time to maturity.

    The discount factor is calculated using the formula:

    \begin{align*}
        D(\tau) &= e^{-r(\tau) \tau} \\
        r(\tau) &= \beta_1 + \beta_2 \frac{1 - e^{-\lambda \tau}}
        {\lambda \tau} + \beta_3
        \left(\frac{1 - e^{-\lambda \tau}}{\lambda \tau}
          - e^{-\lambda \tau}\right)
    \end{align*}
    """
    ttmd = to_decimal(ttm)
    if ttmd <= 0:
        return ONE
    else:
        tt = ttmd * self.lambda_
        et = (-tt).exp()
        ett = (1 - et) / tt
        zero_coupon_rate = self.beta1 + self.beta2 * ett + self.beta3 * (ett - et)
        return (-zero_coupon_rate * ttmd).exp()

fit classmethod

fit(ttm, rates, lambda_bounds=(0.01, 10.0))

Fit a Nelson-Siegel curve to observed zero-coupon rates.

Uses a profile OLS approach: for each candidate \(\lambda\) the betas are solved exactly via least squares, so only a 1-D scalar minimisation over \(\lambda\) is needed.

PARAMETER DESCRIPTION
ttm

times to maturity in years (1-D, length >= 3)

TYPE: ArrayLike

rates

observed zero-coupon rates, same length as ttm

TYPE: ArrayLike

lambda_bounds

search bounds for the decay parameter \(\lambda\)

TYPE: tuple[float, float] DEFAULT: (0.01, 10.0)

Source code in quantflow/rates/nelson_siegel.py
@classmethod
def fit(
    cls,
    ttm: Annotated[
        ArrayLike,
        Doc("times to maturity in years (1-D, length >= 3)"),
    ],
    rates: Annotated[
        ArrayLike, Doc("observed zero-coupon rates, same length as ttm")
    ],
    lambda_bounds: Annotated[
        tuple[float, float],
        Doc("search bounds for the decay parameter $\\lambda$"),
    ] = (0.01, 10.0),
) -> NelsonSiegel:
    r"""Fit a Nelson-Siegel curve to observed zero-coupon rates.

    Uses a profile OLS approach: for each candidate $\lambda$ the betas are
    solved exactly via least squares, so only a 1-D scalar minimisation over
    $\lambda$ is needed.
    """
    ttm_arr = np.asarray(ttm, dtype=float)
    rates_arr = np.asarray(rates, dtype=float)
    result = minimize_scalar(
        _rss,
        bounds=lambda_bounds,
        method="bounded",
        args=(ttm_arr, rates_arr),
    )
    lam: float = result.x
    b1, b2, b3 = _ols_betas(ttm_arr, rates_arr, lam)
    return cls(
        beta1=Decimal(str(round(b1, 10))),
        beta2=Decimal(str(round(b2, 10))),
        beta3=Decimal(str(round(b3, 10))),
        lambda_=Decimal(str(round(lam, 10))),
    )