# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# #  Smith-Wilson Model Overview
#
# This Jupyter notebook shows you how to load the **smithwilson** model included in the **smithwilson** project. It also walks you through steps to create the same model from scratch.
#
# The Smith-Wilson model calculates extraporated interest rates using the Smith-Wilson method.
#
# The Smith-Wilson method is used for extraporating risk-free interest rates under the Solvency II framework. The method is described in details in *QIS 5 Risk-free interest rates – Extrapolation method*, [a technical paper](https://eiopa.europa.eu/Publications/QIS/ceiops-paper-extrapolation-risk-free-rates_en-20100802.pdf) issued by CEIOPS(the predecessor of EIOPA). The technical paper is available on [EIOPA's web site](https://eiopa.europa.eu/publications/qis/insurance/insurance-quantitative-impact-study-5/background-documents).
# Formulas and variables in this notebook are named consistently with the mathmatical symbols in the technical paper.
#
# This project is inspired by a pure Python implementation of Smith-Wilson
# yield curve fitting algorithm created by <NAME>.
# His original work can be found [on his github page](https://github.com/simicd/smith-wilson-py).
#
# [the technical paper]: https://eiopa.europa.eu/Publications/QIS/ceiops-paper-extrapolation-risk-free-rates_en-20100802.pdf

# ## About this notebook
#
# This notebook is included in **lifelib** package as part of the **smithwilson** project.
#
# If you're viewing this page as a static HTML page on https://lifelib.io, the same contents are also available [here on binder] as Jupyter notebook executable online (it may take a while to load). To run this notebook and get all the outputs below, Go to the **Cell** menu above, and then click **Run All**.
#
# [here on binder]: https://mybinder.org/v2/gh/fumitoh/lifelib/binder?filepath=lifelib%2Fprojects%2Fsmithwilson%2Fsmithwilson.ipynb

# ## Reading in the complete model
#
# The complete model is included under the **smithwilson** project in lifelib package. Load a model from `model` folder in your
# project folder by `modelx.read_model` function.
# The example blow shows how to do it.
# Note that you need two backslashes to separate folders:

import modelx as mx
m = mx.read_model("model")   #  Need 2 backslashes as a separator on Windows e.g. "C:\\Users\\fumito\\model"

# The model has only one space, named `SmithWilson`.
# The space contains a few cells:

s = m.SmithWilson
s.cells

# It also contains references (refs),
# such as `spot_rates`, `N`, `UFR` and `alpha`.
# By default, these values are set equal to the values used in Dejan's
# reference model.
# The original source of the input data is Switzerland EIOPA spot rates with LLP 25 years available from the following URL.
#
# Source: https://eiopa.europa.eu/Publications/Standards/EIOPA_RFR_20190531.zip; EIOPA_RFR_20190531_Term_Structures.xlsx; Tab: RFR_spot_no_VA
#          
#

s.N

s.UFR

s.alpha

s.spot_rates

# `R` calculates the extrapoted spot rate for a give time index $i$.

[s.R[i] for i in range(10, 151, 10)]

# For $i = 1,\dots,N$, `R[i]` is the same as `spot_rates[i-1]`. 

[s.spot_rates[i-1] for i in range(10, 26, 5)]

# ## Building the Smith-Wilson model from scratch
#
# We now try to create the **smithwilson** model from scratch. 
# The model we create is essentially the same as the model included in the **smithwilson** project, excpt for docstrings.
#
# Below are the steps to create the model.
# 1. Create a model and space.
# 2. Input values to as *references*.
# 3. Define cells.
# 4. Get the results.
# 5. Save the model.

# ### 1. Create a model and space
#
# First, we create an empty model named `smithwilson2`, and also an empty space named `SmithWilson` in the model. 
# The following statement creates the model and space, and assign the space to a name `s2`.

s2 = mx.new_model(name="smithwilson2").new_space(name="SmithWilson")

# ### 2. Input values to as *references*
#
# In this step, we create *references* in the *SmithWilson* space, and assign input values to the *references*.
# We will create cells and define their formulas in the sapce in the next step, and those *references* are referred by the formulas of the cells. 
#
# The values are taken from https://github.com/simicd/smith-wilson-py/blob/master/main.py

# +
# Annual compound spot rates for time to maturities from 1 to 25 years
s2.spot_rates = [
    -0.00803, -0.00814, -0.00778, -0.00725, -0.00652,
    -0.00565, -0.0048, -0.00391, -0.00313, -0.00214,
    -0.0014, -0.00067, -0.00008, 0.00051, 0.00108,
    0.00157, 0.00197, 0.00228, 0.0025, 0.00264,
    0.00271, 0.00274, 0.0028, 0.00291, 0.00309]

s2.N = 25   # Number of time to maturities.

s2.alpha = 0.128562  # Alpha parameter in the Smith-Wilson functions

ufr = 0.029    # Annual compound

from math import log
s2.UFR = log(1 + ufr) # Continuous compound UFR, 0.028587456851912472
# -

# You also nee to import `log` and `exp` from `math` module for later use. 
# We also use numpy later, so import `numpy` as `np`. 
# These functions and module need to be accessible from cells in `SmithWilson` space,
# so assign them to refs.

# +
from math import log, exp
import numpy as np

s2.log = log
s2.exp = exp
s2.np = np
# -

# ### 3. Define cells
#
# In the previous step, we have assigned all the necessary inputs in the *SmithWilson* space. In this step we move on to defining cells.
#
# We use `defcells` decorator to define cells from Python functions. `defcells` decorator creates cells in the *current* space, so confirm the *SmithWilson* space we just created is set to the *current* space by the following code.

mx.cur_space()


# The names of the cells below are set consistent with the mathmatical symbols in the technical paper.
#
# * `u(i)`: Time at each `i` in years. Time steps can be uneven. For the maturities of the zero coupon bonds with known prices $u_i$
# * `m(i)`: The market prices of the zero coupon bonds, $m_i$
# * `mu(i)`: Ultimate Forward Rate (UFR) discount factors, $\mu_i$
# * `W(i, j)`: The Wilson functions, $W(t_i, u_j)$

@mx.defcells
def u(i):
    """Time to maturities"""
    return i


# +
@mx.defcells
def m(i):
    """Observed zero-coupon bond prices"""
    return (1 + spot_rates[i-1]) ** (-u[i])


@mx.defcells
def mu(i):
    """Ultimate Forward Rate (UFR) discount factors"""
    return exp(-UFR * u[i])


# -

@mx.defcells
def W(i, j):
    """The Wilson functions"""

    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))))


# We want to use Numpy's vector and matrix operations to solve for $\zeta$,
# so we create a vector or matrix version of cells for each of `m`, `mu`, `W`.
# These cells have no parameter and return numpy arrays.

# +
@mx.defcells
def m_vector():
    return np.array([m(i) for i in range(1, N+1)])

@mx.defcells
def mu_vector():
    return np.array([mu(i) for i in range(1, N+1)])

@mx.defcells
def W_matrix():
    return np.array(
        [[W(i, j) for j in range(1, N+1)] for i in range(1, N+1)]
    )


# -

# `zeta_vector` cells carries out the matrix-vector calcuculation: $\zeta = \bf W^{-1}(\bf m - {\mu})$.
#
# `zeta` extracts from an element from `zeta_vector` for each `i` 

# +
@mx.defcells
def zeta_vector():
    return np.linalg.inv(W_matrix()) @ (m_vector() - mu_vector())

@mx.defcells
def zeta(i):
    return zeta_vector()[i-1]


# -

# `P(i)` cells calculates bond prices from `mu`, `zeta` and `W`. The values of `P(i)` should be the same as those of `m(i)` for `i=1,...,N` .
#
# `R(i)` are the extaporated annual compound rates. The values of `R(i)` should be the same as those of `spot_rates[i-1]` for `i=1,...,N`.

# +
@mx.defcells
def P(i):
    """Zero-coupon bond prices calculated by Smith-Wilson method."""
    return mu(i) + sum(zeta(j) * W(i, j) for j in range(1, N+1))


@mx.defcells
def R(i):
    """Extrapolated rates"""
    return (1 / P(i)) ** (1 / u(i)) - 1


# -

# ### 4. Get the results

# You can check that the cells you define above exists in the `SmithWilson` space by getting the space's `cells` attribute.

s2.cells

# `R` cells calculates or holds the extraporated spot rates. You can see that for `i=1,...,25`, the values are the same ase the `sport_rates`.
#
# The code below outputs `R(i)` for `i=10, 15, 20, ..., 100`

[s2.R[i] for i in range(10, 101, 5)]

# ### 5. Save the model

# You can write the model by `write_model`. The model is written to files under the folder you specify as the second paramter. Later you can read the model by `read_model`.
#
# ```python
# mx.write_model(mx.cur_model(), "your_folder")
#
# ```

StarcoderdataJupyterScriptsDedupFiltered