# Source code for smithwilson.model.SmithWilson

"""The main space in the Smith-Wilson model

SmithWilson is the only space in the Smith-Wilson model.

The Smith-Wilson method is used for extrapolating risk-free interest rates under the Solvency II framework.
The method is described in
*"QIS 5 Risk-free interest rates – Extrapolation method"*,
a technical paper issued by CEIOPS (the predecessor of EIOPA).
The technical paper is available on EIOPA's web site_.
Cells in this space are named consistently
with the mathematical symbols in the technical paper_.

.. rubric:: References

Attributes:
log: log <https://docs.python.org/3/library/math.html#math.log>_ function from the standard math library_
exp: exp <https://docs.python.org/3/library/math.html#math.exp>_ function from the standard math library_
np: Numpy_ module
N: Number of durations for which the observed spot rates are available
spot_rates: List of the observed spot rates (annual compound)
UFR: The ultimate forward rate (continuous compound)
alpha: The convergence parameter :math:\\alpha

.. _Numpy : https://numpy.org/

.. _EIOPA's web site: https://eiopa.europa.eu/publications/qis/insurance/insurance-quantitative-impact-study-5/background-documents

.. _the technical paper: https://eiopa.europa.eu/Publications/QIS/ceiops-paper-extrapolation-risk-free-rates_en-20100802.pdf

.. _the standard math library: https://docs.python.org/3/library/math.html
"""

from modelx.serialize.jsonvalues import *

_formula = None

_bases = []

_allow_none = None

_spaces = []

# ---------------------------------------------------------------------------
# Cells

[docs]def u(i):
"""Time (:math:u_i)

:func:u is a series of discrete time points.
i is a 1-based index. By default, :func:u just returns i.

For i = 1, ..., :attr:N, :func:u corresponds to
:math:u_i in the technical paper_,
The time to maturities of the observed zero-coupon bond prices.

:attr:spot_rates, the observed spot rates must exist at each
u(i) from i = 1 to :attr:N. Note that :attr:spot_rates is
0-based, so the sport rate at u(i) is spot_rates[i-1].

Args:
i: The time index (1, 2, ...)

"""
return i

[docs]def m(i):
"""Observed zero-coupon bond prices at time :math:u_i.

:func:m is calculated from :attr:spot_rates as
:math:(1 + spot\_rates[i-1])^{-u_i}

Args:
i(int): Time index (1, 2, ..., :attr:N)
"""
return (1 + spot_rates[i-1]) ** (-u[i])

[docs]def mu(i):
"""Ultimate Forward Rate (UFR) discount factors

:func:mu is defined as :math:e^{-UFR\cdot u_i}.

Args:
i(int): Time index (1, 2, ...)
"""
return exp(-UFR * u[i])

[docs]def W(i, j):
"""The Wilson functions.

:func:W corresponds to formula (2) on page 16 in the technical paper_
defined as:

.. math::

W(t, u_j)=  \\
e^{-UFR\cdot (t+u_j)}\cdot  \\
\left\{   \\
\\alpha\cdot\min(t, u_j) \\
-0.5\cdot e^{-\\alpha\cdot\max(t, u_j)}\cdot(    \\
e^{\\alpha\cdot\min(t, u_j)}     \\
-e^{-\\alpha\cdot\min(t, u_j)}    \\
)   \\
\\right\}

where :math:t = u_i.

Args:
i(int): Time index (1, 2, ..., :attr:N)
j(int): Time index (1, 2, ..., :attr:N)
"""
t = u[i]
uj = u[j]

return exp(-UFR * (t+uj)) * (
alpha * min(t, uj) - 0.5 * exp(-alpha * max(t, uj)) * (
exp(alpha*min(t, uj)) - exp(-alpha*min(t, uj))
))

[docs]def m_vector():
"""The :func:m vector.

:func:m as 1-dimensional :attr:N length numpy array.
"""
return np.array([m(i) for i in range(1, N+1)])

[docs]def mu_vector():
"""The :func:mu vector.

:func:mu as 1-dimensional :attr:N length numpy array.
"""
return np.array([mu(i) for i in range(1, N+1)])

[docs]def W_matrix():
"""The :func:W matrix.

:func:W as a 2-dimensional :attr:N x :attr:N numpy array.
"""
return np.array([[W(i, j) for j in range(1, N+1)] for i in range(1, N+1)])

[docs]def zeta_vector():
"""The :func:zeta vector.

:func:zeta_vector returns :math:\zeta parameters calculated
by formula (5) on page 17 in the technical paper_, which is

.. math::

\\bf \zeta= W^{-1}(p-\mu)

"""
return np.linalg.inv(W_matrix()) @ (m_vector() - mu_vector())

[docs]def zeta(i):
"""The :math:\zeta_i parameters fitted to the observed spot rates.

Args:
i(int): Time index (1, 2, ..., :attr:N)
"""
return zeta_vector()[i-1]

[docs]def P(i):
"""Zero-coupon bond prices extrapolated by the Smith-Wilson method.

:func:P corresponds to formula (1) on page 16 or formula (6)
on page 18 in the technical paper_, defined as:

.. math::

P(t) = e^{-UFR\cdot t}+\sum_{j=1}^{N}\zeta_{j}\cdot W(t, u_j)

substituting :math:t with :math:u_i.

The values of :func:P for i=1, ..., N should be the same
as the values of :func:m, the observed bond prices.

Args:
i(int): Time index (1, 2, ...)
"""
return mu(i) + sum(zeta(j) * W(i, j) for j in range(1, N+1))

[docs]def R(i):
"""The extrapolated annual compound sport rates.

:func:R corresponds to :math:R_t defined as:

.. math::

R_t = \left(\\frac{1}{P(t)}\\right)^\left(\\frac{1}{t}\\right)-1

on page 18 in the technical paper_,
substituting :math:t with :math:u_i.

The values of :func:R for i=1,...,N should be same as the values
of the observed spot rates :attr:spot_rates for 0, ... ,N-1.

Args:
i(int): Time index (1, 2, ...)
"""
return (1 / P(i)) ** (1 / u(i)) - 1

# ---------------------------------------------------------------------------
# References

np = ("Module", "numpy")

log = ("Pickle", "SmithWilson/data/log")

exp = ("Pickle", "SmithWilson/data/exp")

UFR = 0.028587456851912472

alpha = 0.128562

N = 25

spot_rates = ("Pickle", "SmithWilson/data/spot_rates")