Skip to content

Commit 95b2a03

Browse files
committed
Improve type annotations and DataFrame code
1 parent 36ed88d commit 95b2a03

File tree

3 files changed

+47
-32
lines changed

3 files changed

+47
-32
lines changed

README.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,30 @@ This Python package implements the Smith-Wilson yield curve fitting algorithm. I
4444
1. Call the Smiwth-Wilson fitting algorithm. This returns the rates as numpy vector with each element corresponding to the maturity in `terms_target`
4545
```py
4646
# Calculate fitted rates based on actual observations and two parametes alpha & UFR
47-
rates_ext = sw.fit_smithwilson_rates(rates_obs=rates, t_obs=terms,
48-
t_target=terms_target, alpha=alpha, ufr=ufr)
47+
fitted_rates = sw.fit_smithwilson_rates(rates_obs=rates, t_obs=terms,
48+
t_target=terms_target, alpha=alpha, ufr=ufr)
4949
```
5050

5151
1. To display the results and/or processing them it can be useful to turn them into a table, here using the pandas library:
5252
```py
53+
# Ensure pandas package is imported
5354
import pandas as pd
5455

55-
# Create dictionary with maturity as key and rate as value
56-
observed = dict(zip(terms, rates))
57-
extrapolated = dict(zip(terms_target, rates_ext.flatten()))
58-
# Create and print dataframe
59-
print(pd.DataFrame({"Observed": observed, "Extrapolated": extrapolated}))
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"))
6064
```
6165

6266
A complete example can be found in [main.py](https://github.com/simicd/smith-wilson-py/blob/master/main.py)
6367
<br /><br />
6468

6569
## Algorithm
66-
The algorithm is fully vectorized and uses numpy, making it very performant. All functions are in [core.py](https://github.com/simicd/smith-wilson-py/blob/master/smithwilson/core.py).
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).
6771

6872
The function `fit_smithwilson_rates()` expects following parameters:
6973
- Observed rates

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_target = [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_target, 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_target, 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"))

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)