14 Hull-White单因子利率模型三叉树
14.1 简介
14.1.1 Hull-White单因子模型
Hull-White单因子模型是一种描述瞬时无风险利率变化过程的模型。它基于具有均值回归特性的Vasicek模型,此外该模型计算的初始利率期限结构能够与市场上观察到的利率期限结构相吻合。它可以表示为
d
r
=
(
θ
(
t
)
−
a
r
)
d
t
+
σ
d
z
.
dr = (\theta(t)-ar)dt+\sigma dz\;.
dr=(θ(t)−ar)dt+σdz.
其中
r
=
r
(
t
)
r=r(t)
r=r(t),为在
t
t
t时刻的瞬时无风险利率,
a
a
a和
σ
\sigma
σ为常数,
θ
(
t
)
\theta(t)
θ(t)为由初始利率期限结构所确定的函数。
14.1.2 三叉树利率树形
类似于使用二叉树描述股价变化过程,我们可以用三叉树描述瞬时无风险利率
r
r
r的变化过程。当利率过大或者过小时,我们可以调整树形上节点的分叉方式来保证分叉各方向的概率均非负,并保持树形精度。而且描述股价变化的树形中股价是成比例上升或者下降,不会出现负股价节点。但描述利率变化的树形中利率是按固定数值上升或者下降,如果利率过小时不改变树形分叉将会出现过多无效的负利率节点。
这里不过多重复《期权、期货及其他衍生产品》书中内容。但简单说明几点:
- 树形中每个节点处的利率 R R R为从该节点的时刻开始,之后 Δ t \Delta t Δt时间段内的无风险利率,等于 r ( t ) r(t) r(t)在该段时间的平均值。而且对于 N N N次分叉的树形将会有 N + 1 N+1 N+1个 Δ t \Delta t Δt时间段,这与股价树形 N N N次分叉有 N N N个 Δ t \Delta t Δt不同。
- 树形的生成主要分为两部分:1. 忽略模型中的 θ ( t ) \theta(t) θ(t)项,生成利率树形;2. 通过要求由树形计算出的 P ( 0 , t i ) P(0,t_i) P(0,ti)和市场上初始利率期限结构相符合来对每层利率进行调整,这里 t i t_i ti为树形上每层对应的时刻。即实现引入 θ ( t ) \theta(t) θ(t)项相同的目的。
- 关于节点中的 Q Q Q值,其代表瞬时无风险利率从根节点处开始,到该节点所有可能路径下零息债券价格的加权平均。零息债券价格依赖于利率变化路径,同时权重为该路径上所有分叉概率的乘积。
14.1.3 零息债券期权
零息债券期权对应标的资产为零息债券,其中债券到期时间
T
∗
T^\ast
T∗大于期权到期时间
T
T
T,债券本金一般记为
L
L
L,期权执行价格为
K
K
K。零息债券的价格和期权价格的贴现都只依赖于瞬时无风险利率的变化过程,当我们假设利率的变化过程服从Hull-White单因子模型后,零息债券期权的价格有解析解。但作为例子,我们也可以使用利率树形来计算零息债券期权的价格。一种方式是先生成
r
r
r由
t
=
0
t=0
t=0到
t
=
T
∗
t=T^\ast
t=T∗变化的树形,由债券本金
L
L
L从债券到期日
T
∗
T^\ast
T∗往回倒推出期权执行时刻债券在每个节点的价格,并得到期权在节点的价格,然后对期权价格使用节点处的
Q
Q
Q值进行加权和贴现。另一种方式是只生成
r
r
r从
t
=
0
t=0
t=0到
t
=
T
t=T
t=T时的利率树形,在树形末层,根据每个节点处的
R
R
R值,使用
P
(
T
,
T
∗
)
P(T,T^\ast)
P(T,T∗)的解析表达式来直接计算出节点处债券的价格,然后同样得到期权价格,再用
Q
Q
Q值进行加权和贴现。
这里我们使用同书上例子中一样的解析和树形并用的方法来计算零息债券期权的价格。
14.2 生成树形和计算零息债券期权价格步骤
14.2.1 生成利率三叉树
- 确定相关参数。对于 0 0 0至 T T T之间 s t e p s steps steps次分叉的三叉树, Δ t = T / ( 1 + s t e p s ) , Δ R = σ 3 Δ t \Delta t=T/(1+steps),\;\Delta R=\sigma\sqrt{3\Delta t} Δt=T/(1+steps),ΔR=σ3Δt。考虑节点 ( i , j ) (i,j) (i,j), i i i为层数, i = 0 , 1 , . . . , s t e p s i=0,1,...,steps i=0,1,...,steps, j j j为该层上节点的位置, j = − i , − i + 1 , . . . , i − 1 , i j=-i,-i+1,...,i-1,i j=−i,−i+1,...,i−1,i。当层数 i ≥ j m a x i\geq j_{max} i≥jmax时, j j j的取值范围将固定为 − j m a x , . . . , j m a x -j_{max},...,j_{max} −jmax,...,jmax,这里 j m a x j_{max} jmax为大于或等于 0.184 / ( a Δ t ) 0.184/(a\Delta t) 0.184/(aΔt)的最小整数。对于 j = ± j m a x j=\pm j_{max} j=±jmax的节点,其分叉方式为书中所描述的非标准分叉。
- 由已知市场上的零息利率期限结构生成树形各层的时刻对应的 P ( 0 , t i ) P(0,t_i) P(0,ti)。比如已知初始的零息利率为 [ [ T 0 , r 0 ] , . . . , [ T M , R M ] ] [[T_0,r_0],...,[T_M,R_M]] [[T0,r0],...,[TM,RM]],我们需要计算出 P ( 0 , t 0 = 0 ) , P ( 0 , t 1 = Δ t ) , . . . , P ( 0 , t s t e p s + 1 = ( s t e p s + 1 ) Δ t ) P(0, t_0=0), P(0, t_1=\Delta t), ..., P(0, t_{steps+1}=(steps+1)\Delta t) P(0,t0=0),P(0,t1=Δt),...,P(0,tsteps+1=(steps+1)Δt)。计算时未知的利率由已知利率结构线性插值得出。这些债券价格会在计算树形每层利率的调整时被用到。
- 初始化树形的一个根节点。树形中的节点在Python中可以使用
dict()
结构,C/C++中可以自定义一个struct
结构。三叉树中节点属性包括:“prob”:到达该节点的概率,“Probs: [ P d , P m , P u ] [P_d,P_m,P_u] [Pd,Pm,Pu]”:节点向不同方向分叉的概率,“Q”:到达该节点所有可能路径的概率与贴现的加权,“R”:从该节点开始后 Δ t \Delta t Δt时间段内的短期无风险利率。对于根节点,其初始化为,“prob:1”,“Probs: [None, None, None]“,”Q“:1,”R:None“。 - 从
i
=
0
i=0
i=0层开始,到第
i
=
s
t
e
p
s
−
1
i=steps-1
i=steps−1层,依次处理该层节点同时初始化下一层的节点。具体为:
- 初始化下一层的节点,每个节点为 { " p r o b " : 0 , " P r o b s " : [ N o n e , N o n e , N o n e ] , " Q " : 0 , " R " : N o n e } \{"prob":0, "Probs":[None, None, None], "Q":0, "R":None\} {"prob":0,"Probs":[None,None,None],"Q":0,"R":None}。
- 由当前第 i i i层各节点已知的 Q Q Q值和前面计算出的 P ( 0 , t i + 1 ) P(0,t_{i+1}) P(0,ti+1),计算出 α i \alpha_i αi,得出节点 ( i , j ) (i,j) (i,j)处 R i j = j Δ R + α i R_{ij}=j\Delta R+\alpha _i Rij=jΔR+αi,并填入每个节点的"R"。
- 将每个节点向不同方向分叉的概率计算出并填入该节点。
- 将该层每个节点已知的到达该节点的"Q"和"prob",按该节点分叉方向和对应概率传递给下一层相应节点。其中“Q”的传递需要考虑当前节点利率在 Δ t \Delta t Δt时间段的贴现值。
- 树形的最后一层 i = s t e p s i=steps i=steps,由于不会继续分叉,只需要同上计算出该层每个节点处的短期无风险利率R并填入即可。
14.2.2 计算零息债券期权价格
对于期权到期日为 T T T,执行价格为 K K K,相应债券到期日为 T ∗ T^\ast T∗,本金为 L L L的零息债券期权。使用Hull-White单因子利率模型,假定参数 a , σ a,\,\sigma a,σ已校准。考虑一个分叉 s t e p s steps steps步的三叉树,则 Δ t = T / s t e p s \Delta t = T/steps Δt=T/steps,树形末层利率对应时间段为 T T T至 T + Δ t T+\Delta t T+Δt,这么设定是为了和 P ( T , T ∗ ) P(T,T^\ast) P(T,T∗)的解析表达式相符合。
- 在 0 0 0至 T + Δ t T+\Delta t T+Δt时间段内由上面所述步骤生成一个利率三叉树。
- 在树形末层由Hull-White单因子模型下 P ( T , T ∗ ) P(T,T^\ast) P(T,T∗)解析表达式计算出每个节点对应债券价格。
- 由债券价格和期权执行价格判断是否执行期权,得到该末层每个节点处期权价格。
- 计算出末层每个节点处期权价格乘以该节点的 Q Q Q值之和,即为最终需要的零息债券期权价格。
14.3 步骤Python代码实现
import numpy as np
def calculate_init_zero_bonds(init_zero_rates, T, steps):
""" 线性插值零息利率,计算出树形每层对应时刻P(0,ti)的值,
在计算利率调整量alpha时需要用到。
树形的末端时间T应该在初始零息利率数组时间范围内。
"""
rates = list(init_zero_rates)
dt = T/(steps+1)
Pti = []
p = 0
for i in range(steps+2):
ti = dt*i
while ti > rates[p][0]:
p += 1
if p == 0:
ri = rates[0][1]
else:
ri = rates[p-1][1]+(rates[p][1]-rates[p-1][1])/(rates[p][0]-rates[p-1][0])*(ti-rates[p-1][0])
Pti.append(np.exp(-ri*ti))
return Pti
def build_rates_tree(a, sigma, T, steps, init_zero_rates):
""" 和股票价格树形不同的是,这里每个节点的利率为从该层时刻开始
到下一层的时刻之间的利率R,是瞬时无风险利率r在t至t+delta t之间的平均值。
"""
dt = T/(steps+1.0) # 分叉steps次,需要考虑(steps+1)个时间区间。
dR = sigma*np.sqrt(3*dt)
j_max = int(np.ceil(0.184/a/dt))
Pti = calculate_init_zero_bonds(init_zero_rates, T, steps)
# 初始化树,Probs为分叉概率,prob为到该节点概率。
tree = [[{"Probs":[None, None, None], "prob":1, "Q":1, "R":None}]]
# 先生成和处理出现非标准分叉之前的树形。
# 流程为:
# 1. 产生下一层树形空节点。
# 2. 计算当前层每个节点处 R,需要先计算出该层的利率调整alpha。
# 3. 计算当前层每个节点每个方向的分叉概率。
# 4. 把到节点的概率和“Q”的值传递到下一层初始化好的节点。
for lvl in range(min(steps, j_max)):
next_lvl_nodes = []
for j in range(2*lvl+3):
next_lvl_nodes.append({"Probs":[None, None, None], "prob":0, "Q":0, "R":None})
tree.append(next_lvl_nodes)
nodes = tree[lvl]
S = 0
for j in range(-lvl, lvl+1, 1):
S += nodes[j]["Q"]*np.exp(-j*dR*dt)
alpha = (np.log(S)-np.log(Pti[lvl+1]))/dt
# Python里list[-num]会自动从后往前数取值。
for j in range(-lvl, lvl+1, 1):
nodes[j]["R"] = j*dR+alpha
nodes[j]["Probs"][0] = 1.0/6.0+0.5*(a*a*j*j*dt*dt+a*j*dt)
nodes[j]["Probs"][1] = 2.0/3.0-a*a*j*j*dt*dt
nodes[j]["Probs"][2] = 1.0/6.0+0.5*(a*a*j*j*dt*dt-a*j*dt)
for k in range(3):
tree[lvl+1][j+k-1]["prob"] += nodes[j]["prob"]*nodes[j]["Probs"][k]
tree[lvl+1][j+k-1]["Q"] += nodes[j]["Q"]*nodes[j]["Probs"][k]*np.exp(-nodes[j]["R"]*dt)
# 生成树形开始出现非标准分叉的层。
# 过程类似上面。但先处理每层中间标准分叉的节点,再处理非标准分叉节点。
if steps > j_max:
for lvl in range(j_max, steps, 1):
next_lvl_nodes = []
for j in range(2*j_max+1):
next_lvl_nodes.append({"Probs":[None, None, None], "prob":0, "Q":0, "R":None})
tree.append(next_lvl_nodes)
nodes = tree[lvl]
S = 0
for j in range(-j_max, j_max+1, 1):
S += nodes[j]["Q"]*np.exp(-j*dR*dt)
alpha = (np.log(S)-np.log(Pti[lvl+1]))/dt
# 处理标准分叉节点。
for j in range(-j_max+1, j_max, 1):
nodes[j]["R"] = j*dR+alpha
nodes[j]["Probs"][0] = 1.0/6.0+0.5*(a*a*j*j*dt*dt+a*j*dt)
nodes[j]["Probs"][1] = 2.0/3.0-a*a*j*j*dt*dt
nodes[j]["Probs"][2] = 1.0/6.0+0.5*(a*a*j*j*dt*dt-a*j*dt)
for k in range(3):
tree[lvl+1][j+k-1]["prob"] += nodes[j]["prob"]*nodes[j]["Probs"][k]
tree[lvl+1][j+k-1]["Q"] += nodes[j]["Q"]*nodes[j]["Probs"][k]*np.exp(-nodes[j]["R"]*dt)
# 处理上下边界非标准分叉节点。
j = -j_max
nodes[j]["R"] = j*dR+alpha
nodes[j]["Probs"][0] = 7.0/6.0+0.5*(a*a*j*j*dt*dt+3*a*j*dt)
nodes[j]["Probs"][1] = -1.0/3.0-a*a*j*j*dt*dt-2.0*a*j*dt
nodes[j]["Probs"][2] = 1.0/6.0+0.5*(a*a*j*j*dt*dt+a*j*dt)
for k in range(3):
tree[lvl+1][j+k]["prob"] += nodes[j]["prob"]*nodes[j]["Probs"][k]
tree[lvl+1][j+k]["Q"] += nodes[j]["Q"]*nodes[j]["Probs"][k]*np.exp(-nodes[j]["R"]*dt)
j = j_max
nodes[j]["R"] = j*dR+alpha
nodes[j]["Probs"][0] = 1.0/6.0+0.5*(a*a*j*j*dt*dt-a*j*dt)
nodes[j]["Probs"][1] = -1.0/3.0-a*a*j*j*dt*dt+2.0*a*j*dt
nodes[j]["Probs"][2] = 7.0/6.0+0.5*(a*a*j*j*dt*dt-3*a*j*dt)
for k in range(3):
tree[lvl+1][j+k-2]["prob"] += nodes[j]["prob"]*nodes[j]["Probs"][k]
tree[lvl+1][j+k-2]["Q"] += nodes[j]["Q"]*nodes[j]["Probs"][k]*np.exp(-nodes[j]["R"]*dt)
# 树形最后一层上面并没有计算。
# 这里把该层的利率计算出并填入。
S = 0
radius = min(steps, j_max)
for j in range(-radius, radius+1, 1):
S += tree[steps][j]["Q"]*np.exp(-j*dR*dt)
alpha = (np.log(S)-np.log(Pti[steps+1]))/dt
for j in range(-radius, radius+1, 1):
tree[steps][j]["R"] = j*dR+alpha
return tree
def PtT_explicit(t, T, R, dt, a, sigma, init_zero_rates):
""" 使用Hull-White单因子模型P(t,T)解析表达式计算价格。
R为从t开始到t+delta t的利率。
"""
rates = list(init_zero_rates)
def P(t, rates):
p = 0
while t > rates[p][0]:
p += 1
if p == 0:
r_t = rates[0][1]
else:
r_t = rates[p-1][1]+(rates[p][1]-rates[p-1][1])/(rates[p][0]-rates[p-1][0])*(t-rates[p-1][0])
return np.exp(-r_t*t)
def B(t, T):
return (1.0-np.exp(-a*(T-t)))/a
B_hat = B(t, T)/B(t, t+dt)*dt
A_hat = P(T, rates)/P(t, rates)/np.power(P(t+dt, rates)/P(t, rates), B(t, T)/B(t, t+dt))
A_hat /= np.exp(sigma*sigma/4/a*(1-np.exp(-2*a*t))*B(t, T)*(B(t, T)-B(t, t+dt)))
return A_hat * np.exp(-B_hat*R)
def bond_option(K, t, T, steps, a, sigma, init_zero_rates):
""" 计算零息债券上期权价格。
先生成t=0到t=T+delta t的利率树形,这里多一个时间间隔是为了和解析表达式相符合。
然后在树形末端用P(t,T)解析表达式计算债券价格。
最后直接用树形末端的"Q"值对期权价格加权。
"""
dt = t/steps
tree = build_rates_tree(a, sigma, t+dt, steps, init_zero_rates)
call_price = 0
put_price = 0
for j in range(len(tree[-1])):
P = 100.0*PtT_explicit(t, T, tree[-1][j]["R"], dt, a, sigma, init_zero_rates)
if P > K:
call_price += (P-K)*tree[-1][j]["Q"]
else:
put_price += (K-P)*tree[-1][j]["Q"]
return call_price, put_price
14.4 计算示例
我们参考《期权、期货及其他衍生产品》书中第31章例31-4,债券期权为零息债券上的看跌期权,期权执行时间为
T
=
3.0
T=3.0
T=3.0,债券到期时间为
T
∗
=
9.0
T^\ast=9.0
T∗=9.0,期权执行价为
K
=
63.0
K=63.0
K=63.0,债券本金为
L
=
100.0
L=100.0
L=100.0。初始利率结构已知,Hull-White单因子模型中已知参数
a
=
0.1
,
σ
=
0.01
a=0.1,\;\sigma=0.01
a=0.1,σ=0.01。
我们按前面所述步骤生成一个利率三叉树,并计算出期权价格。50步的树形,结果为为1.80934;100步的树形,结果为1.81444;200步的树形,结果为1.80974;500步的树形,结果为1.80928 。和纯解析计算的结果1.8093比较相符。
init_rates = [[3/365, 0.0501722], [31/365, 0.0498284], [62/365, 0.0497234], [94/365, 0.0496157],\
[185/365, 0.0499058], [367/365, 0.0509389], [731/365, 0.0579733], [1096/365, 0.0630595], \
[1461/365, 0.0673464], [1826/365, 0.0694816], [2194/365, 0.0708807], [2558/365, 0.0727527], \
[2922/365, 0.0730852], [3287/365, 0.0739790], [3653/365, 0.0749015]]
if __name__ == "__main__":
call_price, put_price = bond_option(63, 3, 9, 200, 0.1, 0.01, init_rates)
print("零息债券期权,看涨和看跌期权价格分别为: {0:.5f} {1:.5f}".format(call_price, put_price))
# tree = build_rates_tree(0.1, 0.01, 3, 50, init_rates)
零息债券期权,看涨和看跌期权价格分别为: 1.05458 1.80974
14.5 参考资料
- 《期权、期货及其他衍生产品》,John C. Hull 著,王勇、索吾林译。