-
Notifications
You must be signed in to change notification settings - Fork 2
/
model_ho_lee.py
230 lines (145 loc) · 5.95 KB
/
model_ho_lee.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
"""
Interest Rate Models
Source Code
Author : William Carpenter
Date : April 2024
Objective: Create a binomial tree interest rate model that takes as arguments
todays forward curve and volatilities. Use the tree to price various bonds and
other fixed income derivatives (caps, floors, swaps, etc.).
"""
import sys
import os
import math
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import newton
def payoff(x, typ):
if typ == "bond":
return x
else:
return 0
def cf_floor(rates, strike, delta, notion, cpn):
'''
Floor Cash Flows
'''
cf = np.zeros([len(rates)+1, len(rates)+1])
for col in range(0, len(cf)-1):
for row in range(0, col+1):
rate = rates[row,col]
cf[row, col] = delta*notion*max(strike/100-rate, 0)
return cf
def cf_cap(rates, strike, delta, notion, cpn):
cf = np.zeros([len(rates)+1, len(rates)+1])
for col in range(0, len(cf)-1):
for row in range(0, col+1):
rate = rates[row,col]
cf[row, col] = delta*notion*max(rate-strike/100, 0)
return cf
def cf_bond(rates, strike, delta, notion, cpn):
cf = np.zeros([len(rates)+1, len(rates)+1])
for col in range(0, len(cf)-1):
for row in range(0, col+1):
cf[row, col] = delta*notion*cpn/100
return cf
def cf_swap(rates, strike, delta, notion, cpn):
cf = np.zeros([len(rates)+1, len(rates)+1])
for col in range(0, len(cf)-1):
for row in range(0, col+1):
rate = rates[row,col]
cf[row, col] = delta*notion*(rate-strike/100)
return cf
def display(arr):
for i in arr:
for j in i:
print("{:8.4f}".format(j), end=" ")
print()
print("\n")
def probTree(length):
prob = np.zeros((length, length))
prob[np.triu_indices(length, 0)] = 0.5
return(prob)
def solver(theta, tree, zcb, i, sigma, delta):
# Create pricing matrix for ZCBs
price = np.zeros([i+2, i+2])
# assign the last row to be payoff of ZCB
price[:,len(price)-1] = 1
# Assign new rates to tree
for row in range(0, i+1):
if row == 0:
tree[row, i] = tree[row, i-1] + theta*delta + sigma*math.sqrt(delta)
else:
tree[row, i] = tree[row-1, i-1] + theta*delta - sigma*math.sqrt(delta)
# need pricing tree?
for col in reversed(range(0, i+1)):
for row in range(0, col+1):
node = np.exp(-1*tree[row, col]*(delta))
price[row, col] = node*(1/2*price[row, col+1] + 1/2*price[row+1, col+1])
return price[0,0] - zcb
def calibrate(tree, zcb, i, sigma, delta):
t0 = 0.5
miter = 1000
theta = newton(solver, t0, args=(tree, zcb, i, sigma, delta))
for row in range(0, i+1):
if row == 0:
tree[row, i] = tree[row, i-1] + theta*delta + sigma*math.sqrt(delta)
else:
tree[row, i] = tree[row-1, i-1] + theta*delta - sigma*math.sqrt(delta)
return [theta, tree]
def build(zcb, sigma, delta):
# empty rates tree
tree = np.zeros([zcb.shape[1], zcb.shape[1]])
# empty theta tree
theta = np.zeros([zcb.shape[1]])
# Initial Zero Coupon rate
tree[0,0] = np.log(zcb[0,0])*-1/delta
r0 = tree[0,0]
for i in range(1, len(theta)):
solved = calibrate(tree, zcb[0,i], i, sigma, delta)
# update theta array
theta[i] = solved[0]
tree = solved[1]
return [r0, tree, theta]
def rateTree(r0, theta, sigma, delta):
tree = np.zeros([len(theta), len(theta)])
tree[0,0] = r0
for col in range(1, len(tree)):
tree[0, col] = tree[0, col-1] + theta[col]*delta+sigma*math.sqrt(delta)
for col in range(1, len(tree)):
for row in range(1, col+1):
tree[row, col] = tree[row-1, col] - 2*sigma*math.sqrt(delta)
return tree
def priceTree(rates, prob, cf, delta, typ, notion):
# include extra column for payoff
tree = np.zeros([len(rates)+1, len(rates)+1])
# assign security payoff
tree[:,len(tree)-1] = payoff(notion, typ)
# interate through the price tree
for col in reversed(range(0,len(tree)-1)):
for row in range(0, col+1):
rate = rates[row,col]
pu = pd = 1/2
tree[row, col] = np.exp(-1*rate*delta)* \
(pu*(tree[row, col+1]+cf[row,col+1]) + pd*(tree[row+1, col+1]+cf[row+1, col+1]))
return (tree[0,0], tree)
# Unit testing
if __name__ == "__main__":
# theta = [0.021145, 0.013807]
# small ho-lee tree
# ho_lee = rateTree(0.0169, [0.021145, 0.013807], 0.015, 0.5, 'BDT')
zero_coupons = pd.read_csv('https://raw.githubusercontent.com/wrcarpenter/Interest-Rate-Models/main/Data/zcbs.csv')
zcbs = zero_coupons.loc[zero_coupons['Date']=='3/8/2024']
zcbs = zcbs.drop("Date", axis=1)
# small example for calibration
zeros = np.array(zcbs.iloc[:,0:60])
x = build(zeros, 0.011, 1/12)
tree_hl = rateTree(x[0], x[2], 0.011, 1/12)
cashfl = cf_bond(tree_hl, 5.00, 1/12, 1, 0.00)
pricing = priceTree(tree_hl, 1/2, cashfl, 1/12, "bond", 1)
print(zeros[0,zeros.shape[1]-1])
print(pricing[0])
print(pricing[0] - zeros[0,zeros.shape[1]-1])
# pd.DataFrame(tr).to_clipboard()
short_tree = tree_hl[:5,:5]
cashfl = cf_bond(short_tree, 5.00, 1/12, 1, 0.00)
short = priceTree(short_tree, 1/2, cashfl, 1/12, "bond", 1)