Skip to content

Commit 0f01ed6

Browse files
authored
docs: Write how-to-use section
Add how to use section with step-by-step descriptions. Closes PR #2
2 parents a21b773 + 95b2a03 commit 0f01ed6

File tree

4 files changed

+107
-37
lines changed

4 files changed

+107
-37
lines changed

README.md

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,87 @@
11
# README
22
## Overview
3-
This Python package provides an implementation of the Smith-Wilson yield curve fitting algorithm for interpolations and extrapolations of zero-coupon bond rates. This algorithm is used for the extrapolation of [EIOPA risk-free term structures](https://eiopa.europa.eu/Publications/Standards/Technical%20Documentation%20(31%20Jan%202018).pdf) in the Solvency II framework. Details are available in the Technical Paper [QIS 5 Risk-free interest rates](https://eiopa.europa.eu/Publications/QIS/ceiops-paper-extrapolation-risk-free-rates_en-20100802.pdf). Examples of extrapolated yield curves including the parameters applied can be found [here](https://eiopa.europa.eu/Publications/Standards/EIOPA_RFR_20190531.zip).
3+
This Python package implements the Smith-Wilson yield curve fitting algorithm. It allows for interpolations and extrapolations of zero-coupon bond rates. This algorithm is used for the extrapolation of [EIOPA risk-free term structures](https://eiopa.europa.eu/Publications/Standards/Technical%20Documentation%20(31%20Jan%202018).pdf) in the Solvency II framework. Details are available in the Technical Paper [QIS 5 Risk-free interest rates](https://eiopa.europa.eu/Publications/QIS/ceiops-paper-extrapolation-risk-free-rates_en-20100802.pdf). Examples of extrapolated yield curves including the parameters applied can be found [here](https://eiopa.europa.eu/Publications/Standards/EIOPA_RFR_20190531.zip).
44
<br /><br />
55

6-
## Implementation
7-
The algorithm has been implemented in vectorized form with numpy. This should guarantee good performance. All functions are in [core.py](https://github.com/simicd/smith-wilson-py/blob/master/smithwilson/core.py).
6+
## How to use the package
7+
1. To use the Smith-Wilson fitting algorithm, first import the Python package and specify the inputs. In the example below the inputs are zero-coupon rates with annual frequency up until year 25. The UFR is 2.9% and the convergence parameter alpha is 0.128562. The `terms` list defines the list of maturities, in this case `[1.0, 2.0, 3.0, ..., 25.0]`
8+
```py
9+
import smithwilson as sw
10+
11+
# Input - Switzerland EIOPA spot rates with LLP of 25 years
12+
# Source: https://eiopa.europa.eu/Publications/Standards/EIOPA_RFR_20190531.zip
13+
# EIOPA_RFR_20190531_Term_Structures.xlsx; Tab: RFR_spot_no_VA
14+
rates = [-0.00803, -0.00814, -0.00778, -0.00725, -0.00652,
15+
-0.00565, -0.0048, -0.00391, -0.00313, -0.00214,
16+
-0.0014, -0.00067, -0.00008, 0.00051, 0.00108,
17+
0.00157, 0.00197, 0.00228, 0.0025, 0.00264,
18+
0.00271, 0.00274, 0.0028, 0.00291, 0.00309]
19+
terms = [float(y + 1) for y in range(len(rates))] # [1.0, 2.0, ..., 25.0]
20+
ufr = 0.029
21+
alpha = 0.128562
22+
23+
```
24+
25+
1. Specify the targeted output maturities. This is the set of terms you want to get rates fitted by Smith-Wilson.
26+
Expand the set of rates beyond the Last Liquid Point (e.g. extrapolate to 150 years with annual frequency):
27+
```py
28+
# Extrapolate to 150 years
29+
terms_target = [float(y + 1) for y in range(150)] # [1.0, 2.0, ..., 150.0]
30+
```
31+
32+
Alternatively, you can retrieve a different frequency (e.g. interpolate quarterly instead of annual):
33+
```py
34+
# Interpolate to quarterly granularity
35+
terms_target = [float(y + 1) / 4 for y in range(25*4)] # [0.25, 0.5, ..., 25.0]
36+
```
37+
38+
A combination of interpolation & extrapolation is possible, too. Same for sets of arbitrary maturities:
39+
```py
40+
# Get rates for a well-specified set of maturities only
41+
terms_target = [0.25, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0]
42+
```
43+
44+
1. Call the Smiwth-Wilson fitting algorithm. This returns the rates as numpy vector with each element corresponding to the maturity in `terms_target`
45+
```py
46+
# Calculate fitted rates based on actual observations and two parametes alpha & UFR
47+
fitted_rates = sw.fit_smithwilson_rates(rates_obs=rates, t_obs=terms,
48+
t_target=terms_target, alpha=alpha, ufr=ufr)
49+
```
50+
51+
1. To display the results and/or processing them it can be useful to turn them into a table, here using the pandas library:
52+
```py
53+
# Ensure pandas package is imported
54+
import pandas as pd
55+
56+
# ...
57+
58+
# Turn inputs & outputs into dataframe
59+
observed_df = pd.DataFrame(data=rates, index=terms, columns=["observed"])
60+
extrapolated_df = pd.DataFrame(data=fitted_rates, index=terms_target, columns=["extrapolated"])
61+
62+
# Combine and print dataframe
63+
print(observed_df.join(extrapolated_df, how="outer"))
64+
```
65+
66+
A complete example can be found in [main.py](https://github.com/simicd/smith-wilson-py/blob/master/main.py)
67+
<br /><br />
68+
69+
## Algorithm
70+
The algorithm is fully vectorized and uses numpy, making it very performant. The code is in [core.py](https://github.com/simicd/smith-wilson-py/blob/master/smithwilson/core.py).
871

972
The function `fit_smithwilson_rates()` expects following parameters:
10-
- Observed rates
73+
- Observed rates
1174
- Observed maturities
1275
- Target maturities
13-
- Convergence parameter alpha
76+
- Convergence parameter alpha
1477
- Ultimate forward rate (UFR)
1578

16-
The observed rates and maturities are assumed to be before the Last LiquidPoint (LLP). The targeted maturity vector can
17-
contain both, more granular maturity structure (interpolation) or terms after the LLP (extrapolation).
79+
The observed rates and maturities are assumed to be before the Last Liquid Point (LLP). The targeted maturity vector can
80+
contain any set of maturities (e.g. more granular maturity structure (interpolation) or terms after the LLP (extrapolation)).
1881
<br /><br />
1982

2083

21-
The fitting algorithm of the Smith-Wilson method calculates first the Wilson-matrix (EIOPA, 2010, p. 16):
84+
The Smith-Wilson fitting algorithm calculates first the Wilson-matrix (EIOPA, 2010, p. 16):
2285

2386
`W = e^(-UFR * (t1 + t2)) * (α * min(t1, t2) - 0.5 * e^(-α * max(t1, t2))
2487
* (e^(α * min(t1, t2)) - e^(-α * min(t1, t2))))`
@@ -34,10 +97,6 @@ With the Smith-Wilson parameter `ζ` and Wilson-matrix `W`, the zero-coupon bond
3497
In the last case, `t` can be any maturity vector, i.e. with additional maturities to extrapolate rates.
3598
<br /><br />
3699

37-
## Usage
38-
To use the Smith-Wilson fitting algorithm import the Python package and call `fit_smithwilson_rates()` with the required parameters. An example can be found in [main.py](https://github.com/simicd/smith-wilson-py/blob/master/main.py)
39-
<br /><br />
40-
41100
## Sources
42101
[EIOPA (2010). QIS 5 Technical Paper; Risk-free interest rates – Extrapolation method](https://eiopa.europa.eu/Publications/QIS/ceiops-paper-extrapolation-risk-free-rates_en-20100802.pdf); p.11ff
43102

main.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,28 @@
44
# Input - Switzerland EIOPA spot rates with LLP 25 years and extrapolation period of 150 years
55
# Source: https://eiopa.europa.eu/Publications/Standards/EIOPA_RFR_20190531.zip
66
# EIOPA_RFR_20190531_Term_Structures.xlsx; Tab: RFR_spot_no_VA
7-
rates = [-0.00803, -0.00814, -0.00778, -0.00725, -0.00652,
8-
-0.00565, -0.0048, -0.00391, -0.00313, -0.00214,
9-
-0.0014, -0.00067, -0.00008, 0.00051, 0.00108,
10-
0.00157, 0.00197, 0.00228, 0.0025, 0.00264,
11-
0.00271, 0.00274, 0.0028, 0.00291, 0.00309]
12-
terms = [float(y + 1) for y in range(len(rates))] # 1.0, 2.0, ..., 25.0
7+
rates = [
8+
-0.00803, -0.00814, -0.00778, -0.00725, -0.00652, -0.00565, -0.0048, -0.00391, -0.00313, -0.00214, -0.0014, -0.00067,
9+
-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
10+
]
11+
terms = [float(y + 1) for y in range(len(rates))] # 1.0, 2.0, ..., 25.0
1312
ufr = 0.029
1413
alpha = 0.128562
1514

1615
# Target - Extrapolate to 150 years
17-
terms_ext = [float(y + 1) for y in range(150)] # 1.0, 2.0, ..., 150.0
16+
terms_target = [float(y + 1) for y in range(150)] # 1.0, 2.0, ..., 150.0
1817

1918
# Calculate fitted rates based on actual observations and two parametes alpha & UFR
20-
rates_ext = sw.fit_smithwilson_rates(rates_obs=rates, t_obs=terms,
21-
t_target=terms_ext, alpha=alpha, ufr=ufr)
19+
fitted_rates = sw.fit_smithwilson_rates(rates_obs=rates, t_obs=terms, t_target=terms_target, alpha=alpha, ufr=ufr)
2220

2321
# Display Outputs
2422
# Create dictionary with maturity as key and rate as value
25-
observed = dict(zip(terms, rates))
26-
extrapolated = dict(zip(terms_ext, rates_ext.flatten()))
23+
extrapolated = dict(zip(terms_target, fitted_rates.flatten()))
24+
print(extrapolated)
2725

28-
# Create and print dataframe
29-
print(pd.DataFrame({"Observed": observed, "Extrapolated": extrapolated}))
26+
# Create dataframe
27+
observed_df = pd.DataFrame(data=rates, index=terms, columns=["observed"])
28+
extrapolated_df = pd.DataFrame(data=fitted_rates, index=terms_target, columns=["extrapolated"])
29+
30+
# Combie and print dataframe
31+
print(observed_df.join(extrapolated_df, how="outer"))

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="smithwilson",
8-
version="0.1.0",
8+
version="0.2.0",
99
author="Dejan Simic",
1010
author_email="dejan.simic",
1111
description="Implementation of the Smith-Wilson yield curve fitting algorithm in Python for interpolations and extrapolations of zero-coupon bond rates",

smithwilson/core.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
2-
from math import exp, log
1+
from math import log
32
import numpy as np
3+
from typing import Union, List
4+
45

5-
def calculate_prices(rates: np.ndarray, t: np.ndarray) -> np.ndarray:
6+
def calculate_prices(rates: Union[np.ndarray, List[float]], t: Union[np.ndarray, List[float]]) -> np.ndarray:
67
"""Calculate prices from zero-coupon rates
78
89
Args:
@@ -13,10 +14,14 @@ def calculate_prices(rates: np.ndarray, t: np.ndarray) -> np.ndarray:
1314
Prices as vector of length n
1415
"""
1516

17+
# Convert list into numpy array
18+
rates = np.array(rates)
19+
t = np.array(t)
20+
1621
return np.power(1 + rates, -t)
1722

1823

19-
def ufr_discount_factor(ufr: float, t: np.ndarray) -> np.ndarray:
24+
def ufr_discount_factor(ufr: float, t: Union[np.ndarray, List[float]]) -> np.ndarray:
2025
"""Calculate Ultimate Forward Rate (UFR) discount factors.
2126
2227
Takes the UFR with a vector of maturities and returns for each of the
@@ -37,10 +42,14 @@ def ufr_discount_factor(ufr: float, t: np.ndarray) -> np.ndarray:
3742

3843
# Convert annualized ultimate forward rate to log-return
3944
ufr = log(1 + ufr)
45+
46+
# Convert list into numpy array
47+
t = np.array(t)
48+
4049
return np.exp(-ufr * t)
4150

4251

43-
def wilson_function(t1: np.ndarray, t2: np.ndarray, alpha: float, ufr: float) -> np.ndarray:
52+
def wilson_function(t1: Union[np.ndarray, List[float]], t2: Union[np.ndarray, List[float]], alpha: float, ufr: float) -> np.ndarray:
4453
"""Calculate matrix of Wilson functions
4554
4655
The Smith-Wilson method requires the calculation of a series of Wilson
@@ -85,7 +94,7 @@ def wilson_function(t1: np.ndarray, t2: np.ndarray, alpha: float, ufr: float) ->
8594
return W
8695

8796

88-
def fit_parameters(rates: np.ndarray, t: np.ndarray, alpha: float, ufr: float) -> np.ndarray:
97+
def fit_parameters(rates: Union[np.ndarray, List[float]], t: Union[np.ndarray, List[float]], alpha: float, ufr: float) -> np.ndarray:
8998
"""Calculate Smith-Wilson parameter vector ζ
9099
91100
Given the Wilson-matrix, vector of discount factors and prices,
@@ -115,13 +124,13 @@ def fit_parameters(rates: np.ndarray, t: np.ndarray, alpha: float, ufr: float) -
115124
# Calculate vector of parameters (p. 17)
116125
# To invert the Wilson-matrix, conversion to type matrix is required
117126
zeta = np.matrix(W).I * (mu - P)
118-
zeta = np.array(zeta) # Convert back to more general array type
127+
zeta = np.array(zeta) # Convert back to more general array type
119128

120129
return zeta
121130

122131

123-
def fit_smithwilson_rates(rates_obs: np.ndarray, t_obs: np.ndarray, t_target: np.ndarray,
124-
alpha: float, ufr: float) -> np.ndarray:
132+
def fit_smithwilson_rates(rates_obs: Union[np.ndarray, List[float]], t_obs: Union[np.ndarray, List[float]],
133+
t_target: Union[np.ndarray, List[float]], alpha: float, ufr: float) -> np.ndarray:
125134
"""Calculate zero-coupon yields with Smith-Wilson method based on observed rates.
126135
127136
This function expects the rates and initial maturity vector to be
@@ -170,7 +179,7 @@ def fit_smithwilson_rates(rates_obs: np.ndarray, t_obs: np.ndarray, t_target: np
170179

171180
# Price vector - equivalent to discounting with zero-coupon yields 1/(1 + r)^t
172181
# for prices where t_obs = t_target. All other matuirites are interpolated or extrapolated
173-
P = ufr_disc - W @ zeta # '@' in numpy is the dot product of two matrices
182+
P = ufr_disc - W @ zeta # '@' in numpy is the dot product of two matrices
174183

175184
# Transform price vector to zero-coupon rate vector (1/P)^(1/t) - 1
176-
return np.power(1/P, 1/t_target) - 1
185+
return np.power(1 / P, 1 / t_target) - 1

0 commit comments

Comments
 (0)