IEOR E4706: Term Structure Lattice Models

Summary

short rate 的二叉树模型,假设 q-martingale 的概率是 0.5/0.5。

在利率二叉树中,利率上升与下跌的概率是相同的;而在股票二叉树模型中,上涨下跌是由 u d 计算而得。之所以假设利率二叉树上升下降概率相同是因为利率由均值复归的特性。

利率二叉树和yield curve是对应的。如果使用BDT或Ho-Lee这样的二叉树,需要对参数进行校准。

从无息债券定价的 backward evaluation 开始,回溯法是通用的方法;在此基础上以债券为标的的衍生品的定价...

如果衍生品不能提前行权,也可先计算行权日状态价格再直接定价。

二叉树模型

从短期利率的二叉树模型开始。

构造一个无套利的二叉树

\(r_{i,j}\) : 时间 i 状态 j 下的短期利率。

鞅定价:

\[S_i(j) = \frac1{1+r_{i,j}} [q_uS_{i+1}(j+1) + q_d S_{i+1}(j)] + \text{dividend}\]

\(q\) 是与鞅对应的测度。

从二叉树到利率期限结构

例1:short rate 的二叉树 r 给出;为无息债券定价:T=4, F=100, q=0。

backward evaluation 得出 bond lattice,发现其 t=0 时刻价格为 77.22。从而计算出四年利率 \(s_4\)。如果有短期利率,就可以推导出利率期限结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Params
T=4
F=100
q=0

shortRateLattice = np.ones((T+1, T+1)) * np.nan
shortRateLattice[0,0] = 0.06
u=1.25
d=0.9

for t in range(1, T+1):
shortRateLattice[t, 0] = shortRateLattice[t-1, 0] * d
shortRateLattice[t, 1:t+1] = shortRateLattice[t-1, :t] * u
shortRateLattice.round(3)
1
2
3
4
5
array([[0.06 ,   nan,   nan,   nan,   nan],
[0.054, 0.075, nan, nan, nan],
[0.049, 0.068, 0.094, nan, nan],
[0.044, 0.061, 0.084, 0.117, nan],
[0.039, 0.055, 0.076, 0.105, 0.146]])
1
2
3
4
5
6
7
8
9
10
11
# Q-martingale's probability
qu = 0.5
qd = 0.5

bondLattice = np.ones_like(shortRateLattice) * np.nan

bondLattice[-1] = F

for t in range(T-1, -1, -1):
bondLattice[t, :t+1] = (bondLattice[t+1, :t+1] * qd + bondLattice[t+1, 1:t+2] * qu) / (1+shortRateLattice[t, :t+1]) +q*F
bondLattice.round(2)
1
2
3
4
5
array([[ 77.22,    nan,    nan,    nan,    nan],
[ 84.43, 79.27, nan, nan, nan],
[ 90.64, 87.35, 83.08, nan, nan],
[ 95.81, 94.27, 92.22, 89.51, nan],
[100. , 100. , 100. , 100. , 100. ]])

利率衍生品定价

债券的期权

例2:4年到期无息债券 的 european call (2年到期, K=84)

首先推导出 bond lattice,然后 t=2 期权价值 \(C = \max{(S-K, 0)}\)。由于是欧式期权,仅能在 t=2 行权,所以根据 option lattice 的 t=2 回溯即可。

1
2
3
4
5
6
7
8
9
10
# European Call option on the bond
tau = 2
K = 84

eurocallLattice = np.zeros((tau+1, tau+1)) * np.nan
eurocallLattice[-1] = np.clip(bondLattice[tau, :eurocallLattice[-1].shape[0]] - K, 0, None)
for t in range(tau-1, -1, -1):
eurocallLattice[t,:t+1] = (eurocallLattice[t+1, 1:t+2] * qu + eurocallLattice[t+1, :t+1] * qd) / (1 + shortRateLattice[t, :t+1])

eurocallLattice.round(2)
1
2
3
array([[2.97,  nan,  nan],
[4.74, 1.56, nan],
[6.64, 3.35, 0. ]])

例3: 还是上例的条件,换成美式看跌

美式看跌可以提前行权,所以在回溯之前的每个节点时,需要比较 bond lattice 对应节点的行权收益 (exercise value) 和折算的收益 (continuation value)。

1
2
3
4
5
6
7
8
9
# American Put on the bond
K_ = 88
ameputLattice = np.zeros_like(eurocallLattice) * np.nan
ameputLattice[-1] = np.clip(K_-bondLattice[tau, :tau+1], 0, None)
for t in range(tau-1, -1, -1):
continuation_value = (ameputLattice[t+1, 1:t+2] * qu + ameputLattice[t+1, :t+1] * qd) / (1 + shortRateLattice[t, :t+1])
exercise_value = np.clip(K_-bondLattice[t, :t+1], 0, None)
ameputLattice[t, :t+1] = np.max(np.vstack([continuation_value, exercise_value]), axis=0)
ameputLattice.round(2)
1
2
3
array([[10.78,   nan,   nan],
[ 3.57, 8.73, nan],
[ 0. , 0.65, 4.92]])

债券的期货

假设完备市场 (任何证券都能够被唯一地定价;不完备时,如果某证券能够被复制,则也能被定价),根据鞅定价理论

\[ 0= E_{n-1}^Q \left[ \frac{F_n - F_{n-1}}{B_n} \right]\]

\[F_{n-1} = E_{n-1}^Q \left[ F_n \right]\]

又,在到期日

\[F_n = S_n\]

重期望公式 iterated expectation

\[F_0 = E_0^Q [S_n]\]

如果债券含息,上式依然成立,但需要把 \(S_n\) 去息。二叉树回溯时做处理。

债券的远期

远期和期货最大的区别是逐日盯市和保证金。

\[ 0 = E_0^Q \left[ \frac{S_n - G_0}{B_n} \right]\]

\[G_0 = \frac{E_0^Q\left[\frac{S_n}{B_n}\right]}{E_0^Q\left[\frac{1}{B_n}\right]}\]

在上一章 鞅定价 提及远期和期货的价格关系,标的和利率正相关时,期货价格更高。

债券不付息时,

\[G_0 = \frac{S_0}{E_0^Q\left[\frac{1}{B_n}\right]} = \frac{E_0^Q\left[S_0\right]}{d(0,n)}\]

期货和远期的例子

例4 含息债券的远期合约定价。 参数:债券6年到期,远期合约 4-6,息率 q=10%,计算该远期合约的价格。短期利率二叉树与之前相同。

这个合约的underlying asset可以看作0-4年不付息,4-6年付息。首先从第六年对含息债券进行回溯,然后按照无息债券继续回溯到 t=0。然后套用远期的定价公式,由于前四年不付息,可以直接套用上式无息债券的远期定价。discounting factor 可以用之前例子中无息债券回溯的结果 \(d_4 = 100/77.22\),也可以通过二项分布的pmf计算期望。

1
2
3
4
5
6
# params
t=4
tau=2
T=t+tau
q=0.1

1
2
3
4
5
6
7
8
9
shortRateLattice = np.ones((T+1, T+1)) * np.nan
shortRateLattice[0,0] = 0.06
u=1.25
d=0.9

for t in range(1, T+1):
shortRateLattice[t, 0] = shortRateLattice[t-1, 0] * d
shortRateLattice[t, 1:t+1] = shortRateLattice[t-1, :t] * u
shortRateLattice.round(3)
array([[0.06 ,   nan,   nan,   nan,   nan,   nan,   nan],
       [0.054, 0.075,   nan,   nan,   nan,   nan,   nan],
       [0.049, 0.068, 0.094,   nan,   nan,   nan,   nan],
       [0.044, 0.061, 0.084, 0.117,   nan,   nan,   nan],
       [0.039, 0.055, 0.076, 0.105, 0.146,   nan,   nan],
       [0.035, 0.049, 0.068, 0.095, 0.132, 0.183,   nan],
       [0.032, 0.044, 0.062, 0.085, 0.119, 0.165, 0.229]])
1
2
3
4
5
6
7
bondLattice = np.ones_like(shortRateLattice)*np.nan
bondLattice[-1] = 110
for t in (5,):
bondLattice[t, :t+1] = (bondLattice[t+1, 1:t+2] * 0.5 + bondLattice[t+1, :t+1]*0.5)/ (1+ shortRateLattice[t, :t+1]) + q*100
for t in range(4, -1, -1):
bondLattice[t, :t+1] = (bondLattice[t+1, 1:t+2] * 0.5 + bondLattice[t+1, :t+1]*0.5)/ (1+ shortRateLattice[t, :t+1])
bondLattice.round(2)
array([[ 79.83,    nan,    nan,    nan,    nan,    nan,    nan],
       [ 89.24,  79.99,    nan,    nan,    nan,    nan,    nan],
       [ 97.67,  90.45,  81.53,    nan,    nan,    nan,    nan],
       [104.99,  99.85,  93.27,  85.08,    nan,    nan,    nan],
       [111.16, 108.  , 103.83,  98.44,  91.66,    nan,    nan],
       [116.24, 114.84, 112.96, 110.46, 107.19, 102.98,    nan],
       [110.  , 110.  , 110.  , 110.  , 110.  , 110.  , 110.  ]])
1
2
import scipy.stats as spst
spst.binom.pmf(range(5),4, p=0.5)
array([0.0625, 0.25  , 0.375 , 0.25  , 0.0625])

\[G_0 = \frac{E_0^Q\left[\frac{S_4}{B_4}\right]}{E_0^Q\left[\frac{1}{B_4}\right]} = S_0/d_4, d_4 = 77.22/100\]

1
2
3
4
5
6
# Numeraire
numeraireLattice = np.ones((5,5))*np.nan
numeraireLattice[0,0] = 1
for t in range(1, 5):
numeraireLattice[t, 0] = numeraireLattice[t-1, 0] * (shortRateLattice[t, 0]+1)
numeraireLattice[t, 1:t+1] =numeraireLattice[t-1, :t] * (shortRateLattice[t-1, 0:t] + 1)
1
numeraireLattice
array([[1.        ,        nan,        nan,        nan,        nan],
       [1.054     , 1.06      ,        nan,        nan,        nan],
       [1.1052244 , 1.110916  , 1.1395    ,        nan,        nan],
       [1.15356692, 1.15893831, 1.18590283, 1.24632813,        nan],
       [1.19897823, 1.20402393, 1.22934381, 1.28596338, 1.3923822 ]])
1
2
p = spst.binom.pmf(range(5),4, p=0.5)
(p*bondLattice[4, :5]/numeraireLattice[4, :5]).mean() / (p/numeraireLattice[4, :5]).mean()
103.39897946734145

例5 对含息债券的期货合约定价

期货需要关注中间节点,每一个阶段都无套利。因而可以作出期货的二叉树。

期货价格回溯的公式 \[F_{n-1} = E_{n-1}^Q[F_n]\] 不需要除以计价资产。

1
2
futuresLattice = np.ones((5, 5))*np.nan
futuresLattice[-1] = bondLattice[4, :5]
1
2
for t in range(3, -1, -1):
futuresLattice[t, :t+1] = (futuresLattice[t+1, 1:t+2] * 0.5 + futuresLattice[t+1, :t+1] * 0.5)
1
futuresLattice
array([[103.22201887,          nan,          nan,          nan,
                 nan],
       [105.63552539, 100.80851235,          nan,          nan,
                 nan],
       [107.74653381, 103.52451698,  98.09250772,          nan,
                 nan],
       [109.5799363 , 105.91313132, 101.13590263,  95.0491128 ,
                 nan],
       [111.16255143, 107.99732117, 103.82894147,  98.44286379,
         91.65536181]])

利率上限 Caplet 的定价

caplet 简而言之是以利率为标的资产的 european call。

settled in arrears: if maturity is \(\tau\) and the strike is \(c\) then the payoff at time \(\tau\) is \((r_{\tau -1} - c)^+\).

例6: 为一个 Caplet 定价,基于之前的短期利率树。参数 t=6, c=0.02。

由于 caplet 是european call,不涉及到中间结点的比较,直接根据标的资产的lattice进行计算。

1
2
t = 6
c = 0.02
1
shortRateLattice.round(3)
array([[0.06 ,   nan,   nan,   nan,   nan,   nan,   nan],
       [0.054, 0.075,   nan,   nan,   nan,   nan,   nan],
       [0.049, 0.068, 0.094,   nan,   nan,   nan,   nan],
       [0.044, 0.061, 0.084, 0.117,   nan,   nan,   nan],
       [0.039, 0.055, 0.076, 0.105, 0.146,   nan,   nan],
       [0.035, 0.049, 0.068, 0.095, 0.132, 0.183,   nan],
       [0.032, 0.044, 0.062, 0.085, 0.119, 0.165, 0.229]])
1
2
3
4
capletLattice = np.ones((6,6)) * np.nan
for t in range(5, -1, -1):
capletLattice[t, :t+1] = (np.clip(shortRateLattice[t+1, 1:t+2]-c, 0, None) * 0.5 + np.clip(shortRateLattice[t+1, :t+1]-c, 0, None) * 0.5) / (1+shortRateLattice[t, :t+1])
capletLattice.round(3)
array([[0.042,   nan,   nan,   nan,   nan,   nan],
       [0.036, 0.056,   nan,   nan,   nan,   nan],
       [0.031, 0.049, 0.074,   nan,   nan,   nan],
       [0.026, 0.043, 0.065, 0.095,   nan,   nan],
       [0.021, 0.037, 0.057, 0.084, 0.12 ,   nan],
       [0.017, 0.031, 0.05 , 0.075, 0.108, 0.149]])
1
2
3
4
capletLattice = np.ones((6,6)) * np.nan
for t in range(5, -1, -1):
capletLattice[t, :t+1] = (np.clip(shortRateLattice[t+1, 1:t+2]-c, 0, None) * 0.5 /(1+shortRateLattice[t+1, 1:t+2]) + np.clip(shortRateLattice[t+1, :t+1]-c, 0, None) * 0.5 / (1+shortRateLattice[t+1, :t+1]) ) / (1+shortRateLattice[t, :t+1])
capletLattice.round(3)
array([[0.039,   nan,   nan,   nan,   nan,   nan],
       [0.034, 0.052,   nan,   nan,   nan,   nan],
       [0.029, 0.046, 0.067,   nan,   nan,   nan],
       [0.025, 0.04 , 0.06 , 0.084,   nan,   nan],
       [0.021, 0.035, 0.053, 0.076, 0.103,   nan],
       [0.017, 0.03 , 0.047, 0.068, 0.094, 0.124]])

The Forward Equations

定义基本证券 elementary security 或称 Arrow-Debreu / AD-security

\[P_e(i,j)\]

为在时间 i 和状态 j 支付 $1 的证券在 0 时刻的价格。又称为状态价格。

前向递推,上一阶段的基本证券到下一个阶段会复利,因而下一阶段的基本证券需要折减。状态价格满足这些 forward equations:

\[P_e(k+1, s) = \frac{P_e(k, s-1)}{2(1 + r_{k,s-1})} + \frac{P_e(k, s)}{2(1 + r_{k,s})}, 0<s<k+1\]

\[P_e(k+1, 0) = \frac{P_e(k, 0)}{2(1 + r_{k,0})}\]

\[P_e(k+1, k+1) = \frac{P_e(k, k)}{2(1 + r_{k,k})}\]

计算状态价格并根据状态价格推导期限结构的复杂度是 \(O(T^2)\),而回溯无息债券价格的复杂度是 \(O(T^3)\)。需要注意,状态价格定价法只适用于行权日确定的证券。

二叉树中的对冲,例7

Given a short rate lattice

1
2
3
4
5
short_rate = np.array([
[6, 0, 0],
[5.4, 7.8, 0],
[4.86, 7.02, 10.14]
]) / 100
  1. Compute the elementary prices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def state_prices(r: np.array):
T = r.shape[0] - 1
Pe = np.zeros((T+1, T+1))

Pe[0,0] = 1

for k in range(T):
for s in range(1, k+1):
Pe[k+1, s] = Pe[k, s-1]/(2*(1+r[k, s-1])) + Pe[k,s] / (2*(1+r[k,s]))
Pe[k+1, 0] = Pe[k, 0] / (2*(1+r[k,0]))
Pe[k+1, k+1] = Pe[k,k] / (2*(1+r[k,k]))

return Pe

state_prices(short_rate)
1
2
3
array([[1.        , 0.        , 0.        ],
[0.47169811, 0.47169811, 0. ],
[0.22376571, 0.44254962, 0.21878391]])
  1. Given a different short rate lattice & elementary prices. Find the prices of a zero-coupon bond with face value $100 that matures at t=3
1
2
3
4
5
6
7
8
9
10
11
12
13
r = np.array([
[6, 0, 0, 0],
[5.4, 7.2, 0, 0],
[4.86, 6.48, 8.64, 0],
[4.37, 5.83, 7.78, 10.37],
]) /100

Pe = np.array([
[1, 0, 0, 0],
[0.4717, 0.4717, 0, 0],
[0.2238, 0.4438, 0.22, 0],
[0.1067, 0.3151, 0.3096, 0.1013],
])
1
np.sum(Pe[3])*100
83.27
  1. Compute the price of a Euro-call on the zero coupon bond of (b) with K=$93 and expiration t=2
1
2
3
4
# Payoff in different states at t=2
# backward from t=3 to t=2
payoff = (100 / (1+r[2]) [:3] - 93).clip(min=0)
payoff
array([2.3652489 , 0.91435011, 0.        ])
1
np.dot(payoff, Pe[2,:3])
0.935131284573487
  1. Immunize the obligation of value today $100 and (1,1) value of $95 using date 1 elementary securities.
1
(117+95)*0.4717
100.0004

PS: 根据鞅定价理论,该组合在 t=1 时的价值折现到 t=0 也是 100。

\[100 = \frac12 \frac{95}{1.06} + \frac12 \frac{x}{1.06}\]

或者按照状态价格,

\[0.4717 x + 0.4717 \times 95 = 100\]

状态价格本身就包含了折现,用于确定现在的价格。

  1. Compute the value of the forward swap that begins at t=1 and ends at t=3. Notional principal $1. Fixed rate of the swap is 5%. Payments at t=i (i=1,2,3) are based on the fixed rate minus the floating rate that prevailed at t=i-1.

第 i 个时间点上,持有 swap 的 PNL 实际上可以折算到第 i-1 个时间点上。t = 2,3 的swap 实际上看 t=1,2 的各个节点。swap可以看成是一系列单阶段。

\[(r_{i,j} - 0.05) \times P_e(i,j) / (1+r_{i, j})\]

1
r_ = np.where(r==0, np.nan, r)
1
Pe_ = np.where(Pe==0, np.nan, Pe)
1
2
swap_lattice = (Pe[:3] * (r_[:3] - 0.05) / (1+r_[:3]))
swap_lattice
array([[ 0.00943396,         nan,         nan,         nan],
       [ 0.00179013,  0.00968041,         nan,         nan],
       [-0.0002988 ,  0.00616852,  0.00737113,         nan]])
1
np.nansum(swap_lattice[1:])
0.024711398807682642

Some specific models

需要对 lattice 进行调整,使得期限结构(无息债券价格)或利率波动性等与市场匹配。使用两种模型可以实现。

The Ho-Lee Model

short rate 满足

\[r_{i,j} = a_i + b_i j\]

漂移项+波动率放大系数。\(N(i,j)\) 处的标准差是 \(b_i/2\)。 (?)

连续时间

\[dr_t = \mu_tdt + \sigma_tdW_t\]

优点在于 tractability。 缺点在于并非均值回归。

The Black-Derman-Toy(BDT) model

\[r_{i,j} = a_i e^{b_i j}\]

\[\ln{r_{i,j}} = \log{a_i} + b_i j\]

连续时间 记 \(Y_t := \ln{r_t}\)

\[dY_t = \left( a_t + \frac1{\sigma t}\frac{\partial \sigma}{\partial t} Y_t \right)dt + \sigma_t dW_t\]

校正期限结构

假设市场的利率期限结构 spot rate

\[(s_1, ..., s_n)\]

\(t\) 时刻的 $ $1 $ 按照 \(s_t\) 折现,应该等于该时刻的状态价格相加。

为简化,假设 \(b\) 取相同值。

\[\begin{aligned} \frac{1}{(1+s_i)^i} &= \sum_{j=0}^i{P_e(i,j)}\\ &= \frac{P_e(i-1, 0)}{2(1+r_{i-1, 0)}} + \frac{P_e(i-1, i-1)}{2(1 + r_{i-1,i-1})} + \sum_{j=1}^{i-1}{\left(\frac{P_e(i-1, j)}{2(1+r_{i-1, j})} + \frac{P_e(i-1, j-1)}{2(1+r_{i-1, j-1})} \right) } \\ &= \frac{P_e(i-1, 0)}{2(1+a_{i-1})} + \frac{P_e(i-1, i-1)}{2(1+a_{i-1}e^{b(i-1)})} + \sum_{j=1}^{i-1}{\left(\frac{P_e(i-1, j)}{2(1+a_{i-1} e^{b j})} + \frac{P_e(i-1, j-1)}{2(1+a_{i-1} e^{b (j-1)})} \right) } \\ \end{aligned}\]

根据已有的利率期限结构,前向更新二叉树,求解 \(a_i\)

例8 使用BDT模型为 payer swaption 定价。

题设: 2-8,给出利率期限结构,payer pay fixed and receive floating。 fixed rate = 11.65%。

题解:和前例的 (e) 类似。首先 BDT 校正确定 short rate lattice,然后基于此定价。每一次交换折算到上一个时间点上 (为何折算?因为可以确定地分配到父节点上);swap的价值向前折算累计;到 t=2 时,注意该 swaption 由于其期权性质,价格非负。

1
from scipy.optimize import minimize
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
def BDT_solver(arr_s: np.array, b: float):
T = len(arr_s)
Pe = np.ones((T + 1, T + 1)) * np.nan
Pe[0, 0] = 1

arr_a = []
for i in range(1, T + 1):
left_side = 1 / (1 + arr_s[i - 1])**i

def target(a_im1):
right_side = Pe[i-1, 0] / 2/(1+a_im1) + \
np.sum( [(Pe[i-1, j] /2 /(1+a_im1*np.exp(b*j)) + Pe[i-1, j-1] / 2 / (1+a_im1*np.exp(b*(j-1)))) for j in range(1, i)] ) + \
Pe[i-1, i-1] / 2 / (1 + a_im1*np.exp(b*(i-1)))
return (left_side - right_side)**2

res = minimize(target, 0)
a_im1 = res.x
# print(f"a{i-1}: {a_im1}")
arr_a.append(a_im1)
Pe[i, 0] = Pe[i - 1, 0] / 2 / (1 + a_im1)
for j in range(1, i):
Pe[i, j] = Pe[i - 1, j] / 2 / (1 + a_im1 * np.exp(b * j)) + Pe[
i - 1, j - 1] / 2 / (1 + a_im1 * np.exp(b * (j - 1)))
Pe[i, i] = Pe[i - 1, i - 1] / 2 / (1 + a_im1 * np.exp(b * (i - 1)))
return Pe, np.array(arr_a).flatten()
1
2
3
4
arr_s = np.array([7.3, 7.62, 8.1, 8.45, 9.2, 9.64, 10.12, 10.45, 10.75, 11.22]) / 100
b = 0.005

Pe, a = BDT_solver(arr_s,b)
1
Pe.round(3)
array([[1.   ,   nan,   nan,   nan,   nan,   nan,   nan,   nan,   nan,
          nan,   nan],
       [0.466, 0.466,   nan,   nan,   nan,   nan,   nan,   nan,   nan,
          nan,   nan],
       [0.216, 0.432, 0.216,   nan,   nan,   nan,   nan,   nan,   nan,
          nan,   nan],
       [0.099, 0.297, 0.297, 0.099,   nan,   nan,   nan,   nan,   nan,
          nan,   nan],
       [0.045, 0.181, 0.271, 0.181, 0.045,   nan,   nan,   nan,   nan,
          nan,   nan],
       [0.02 , 0.101, 0.201, 0.201, 0.1  , 0.02 ,   nan,   nan,   nan,
          nan,   nan],
       [0.009, 0.054, 0.135, 0.18 , 0.135, 0.054, 0.009,   nan,   nan,
          nan,   nan],
       [0.004, 0.028, 0.084, 0.139, 0.139, 0.083, 0.028, 0.004,   nan,
          nan,   nan],
       [0.002, 0.014, 0.05 , 0.099, 0.123, 0.099, 0.049, 0.014, 0.002,
          nan,   nan],
       [0.001, 0.007, 0.028, 0.066, 0.098, 0.098, 0.065, 0.028, 0.007,
        0.001,   nan],
       [0.   , 0.003, 0.015, 0.041, 0.071, 0.085, 0.071, 0.04 , 0.015,
        0.003, 0.   ]])
1
a.round(3)
array([0.073, 0.079, 0.09 , 0.094, 0.121, 0.117, 0.129, 0.126, 0.129,
       0.152])
1
2
3
4
# use ai & b to derive r_ij
short_rate_lattice = np.tril(np.outer(a, np.exp(np.arange(T)*b))).round(4)
# Change 0 to np.nan
short_rate_lattice = np.where(short_rate_lattice==0, np.nan, short_rate_lattice)
1
short_rate_lattice
array([[0.073 ,    nan,    nan,    nan,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.0792, 0.0796,    nan,    nan,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.0902, 0.0907, 0.0911,    nan,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.0944, 0.0948, 0.0953, 0.0958,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.1213, 0.1219, 0.1225, 0.1231, 0.1238,    nan,    nan,    nan,
           nan,    nan],
       [0.1172, 0.1178, 0.1184, 0.119 , 0.1195, 0.1201,    nan,    nan,
           nan,    nan],
       [0.1285, 0.1292, 0.1298, 0.1305, 0.1311, 0.1318, 0.1324,    nan,
           nan,    nan],
       [0.1257, 0.1263, 0.1269, 0.1276, 0.1282, 0.1288, 0.1295, 0.1301,
           nan,    nan],
       [0.1292, 0.1298, 0.1305, 0.1311, 0.1318, 0.1325, 0.1331, 0.1338,
        0.1345,    nan],
       [0.1519, 0.1527, 0.1535, 0.1542, 0.155 , 0.1558, 0.1566, 0.1573,
        0.1581, 0.1589]])
1
(short_rate_lattice[9] - 0.1165) / (1+short_rate_lattice[9])
array([0.03073183, 0.03140453, 0.03207629, 0.03266332, 0.03333333,
       0.03400242, 0.03467059, 0.03525447, 0.0359209 , 0.03658642])
1
2
3
4
5
6
swaption_lattice = np.ones_like(short_rate_lattice) * np.nan
swaption_lattice[9] = (short_rate_lattice[9] - 0.1165) / (1+short_rate_lattice[9])

for t in range(8, 1, -1):
swaption_lattice[t, :t+1] = (short_rate_lattice[t, :t+1] - 0.1165) / (1+short_rate_lattice[t, :t+1]) + (0.5 * swaption_lattice[t+1, 1:t+2] + 0.5 * swaption_lattice[t+1, :t+1] ) / (1+ short_rate_lattice[t, :t+1])

1
swaption_lattice.round(4)
array([[    nan,     nan,     nan,     nan,     nan,     nan,     nan,
            nan,     nan,     nan],
       [    nan,     nan,     nan,     nan,     nan,     nan,     nan,
            nan,     nan,     nan],
       [-0.0018,  0.0011,  0.004 ,     nan,     nan,     nan,     nan,
            nan,     nan,     nan],
       [ 0.0231,  0.0257,  0.0284,  0.0311,     nan,     nan,     nan,
            nan,     nan,     nan],
       [ 0.0461,  0.0486,  0.0511,  0.0535,  0.056 ,     nan,     nan,
            nan,     nan,     nan],
       [ 0.0458,  0.048 ,  0.0502,  0.0524,  0.0546,  0.0568,     nan,
            nan,     nan,     nan],
       [ 0.0495,  0.0514,  0.0533,  0.0553,  0.0571,  0.059 ,  0.0609,
            nan,     nan,     nan],
       [ 0.0431,  0.0446,  0.0461,  0.0477,  0.0492,  0.0507,  0.0523,
         0.0538,     nan,     nan],
       [ 0.0388,  0.0399,  0.041 ,  0.0421,  0.0433,  0.0444,  0.0455,
         0.0466,  0.0478,     nan],
       [ 0.0307,  0.0314,  0.0321,  0.0327,  0.0333,  0.034 ,  0.0347,
         0.0353,  0.0359,  0.0366]])
1
2
3
4
5
6
7
# the swaption (option) is exercised only if S2>0. 
swaption_lattice[2] = np.clip(swaption_lattice[2], 0, None)

for t in range(1, -1, -1):
swaption_lattice[t, :t+1] = (0.5 * swaption_lattice[t+1, 1:t+2] + 0.5 * swaption_lattice[t+1, :t+1] ) / (1+ short_rate_lattice[t, :t+1])
swaption_lattice.round(4)

array([[0.0013,    nan,    nan,    nan,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.0005, 0.0024,    nan,    nan,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.    , 0.0011, 0.004 ,    nan,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.0231, 0.0257, 0.0284, 0.0311,    nan,    nan,    nan,    nan,
           nan,    nan],
       [0.0461, 0.0486, 0.0511, 0.0535, 0.056 ,    nan,    nan,    nan,
           nan,    nan],
       [0.0458, 0.048 , 0.0502, 0.0524, 0.0546, 0.0568,    nan,    nan,
           nan,    nan],
       [0.0495, 0.0514, 0.0533, 0.0553, 0.0571, 0.059 , 0.0609,    nan,
           nan,    nan],
       [0.0431, 0.0446, 0.0461, 0.0477, 0.0492, 0.0507, 0.0523, 0.0538,
           nan,    nan],
       [0.0388, 0.0399, 0.041 , 0.0421, 0.0433, 0.0444, 0.0455, 0.0466,
        0.0478,    nan],
       [0.0307, 0.0314, 0.0321, 0.0327, 0.0333, 0.034 , 0.0347, 0.0353,
        0.0359, 0.0366]])

讲义