# 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 issued by CEIOPS(the predecessor of EIOPA). The technical paper is available on EIOPA’s web
site. 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 Dejan Simic. His original work can be found on his github page.

## 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**.

## 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:

```
[1]:
```

```
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:

```
[2]:
```

```
s = m.SmithWilson
s.cells
```

```
[2]:
```

```
{P,
R,
W,
W_matrix,
m,
m_vector,
mu,
mu_vector,
u,
zeta,
zeta_vector}
```

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

```
[3]:
```

```
s.N
```

```
[3]:
```

```
25
```

```
[4]:
```

```
s.UFR
```

```
[4]:
```

```
0.028587456851912472
```

```
[5]:
```

```
s.alpha
```

```
[5]:
```

```
0.128562
```

```
[6]:
```

```
s.spot_rates
```

```
[6]:
```

```
[-0.00803,
-0.00814,
-0.00778,
-0.00725,
-0.00652,
-0.00565,
-0.0048,
-0.00391,
-0.00313,
-0.00214,
-0.0014,
-0.00067,
-8e-05,
0.00051,
0.00108,
0.00157,
0.00197,
0.00228,
0.0025,
0.00264,
0.00271,
0.00274,
0.0028,
0.00291,
0.00309]
```

`R`

calculates the extrapoted spot rate for a give time index \(i\).

```
[7]:
```

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

```
[7]:
```

```
[-0.0021400000002478325,
0.0026399999998294454,
0.004987777012509076,
0.009589281258343796,
0.013152667277319896,
0.0157106404653784,
0.01758288328289881,
0.018999270994646267,
0.020104712394634294,
0.020990537324858893,
0.021716028320261982,
0.02232103673631003,
0.02283325665510394,
0.023272509104879324,
0.023653347800582036]
```

For \(i = 1,\dots,N\), `R[i]`

is the same as `spot_rates[i-1]`

.

```
[8]:
```

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

```
[8]:
```

```
[-0.00214, 0.00108, 0.00264, 0.00309]
```

## 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`

.

```
[9]:
```

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

```
[10]:
```

```
# 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.

```
[11]:
```

```
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.

```
[12]:
```

```
mx.cur_space()
```

```
[12]:
```

```
<UserSpace SmithWilson in smithwilson2>
```

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

```
[13]:
```

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

```
[14]:
```

```
@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])
```

```
[15]:
```

```
@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.

```
[16]:
```

```
@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`

```
[17]:
```

```
@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`

.

```
[18]:
```

```
@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.

```
[19]:
```

```
s2.cells
```

```
[19]:
```

```
{P,
R,
W,
W_matrix,
m,
m_vector,
mu,
mu_vector,
u,
zeta,
zeta_vector}
```

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

```
[20]:
```

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

```
[20]:
```

```
[-0.0021400000002478325,
0.001079999999791026,
0.0026399999998294454,
0.0030899999998661443,
0.004987777012509076,
0.007366600230549469,
0.009589281258343796,
0.011517021559910967,
0.013152667277319896,
0.014535885669793025,
0.0157106404653784,
0.016715719536043006,
0.01758288328289881,
0.018337416110107085,
0.018999270994646267,
0.019584201846686966,
0.020104712394634294,
0.020570802184906256,
0.020990537324858893]
```

### 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`

.

```
mx.write_model(mx.cur_model(), "your_folder")
```