<jupyter_start><jupyter_text>Bond Yield---Let $V(t)$ denote the price of a zero-coupon bond that will mature at time $t$ ($0yield to maturity (YTM) or simply yield, denoted by $y(t)$, is defined as$$ y(t) = \begin{cases} \displaystyle \left(\frac{F}{V(t)}\right)^{\frac1{t}} - 1, & (\text{one-year compound}) \\ \displaystyle \frac1{t}\log\frac{F}{V(t)}, & (\text{continuous compound}) \\ \end{cases}$$The yield to maturity of a coupon-bearing bond is the internal rate of return on its cash flow. In this context, $y(t)$ must be constant for any $t$, though $y$ is time-varying in reality. Therefore the yield $y(t)$ is the real-valued solution of the following polynomial equation.\begin{align*} P(0) &= C\sum_{t=1}^{T-1}B(t,y) + (C+F)B(T,y), \\ B(t,y) &= \begin{cases} \displaystyle \frac{1}{(1+y)^t}, & (\text{one-year compound}) \\ \displaystyle e^{-yt}, & (\text{continuous compound}) \\ \end{cases}\end{align*}<jupyter_code>%matplotlib inline
import numpy as np
import numpy.polynomial.polynomial as pol
import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>The function `Bond_Yield` computes the bond yield.<jupyter_code>def Bond_Yield(Price, Maturity, CouponRate, FaceValue):
    Coupon = 0.01 * CouponRate * FaceValue
    CF = np.hstack((-Price, np.tile(Coupon, int(Maturity) - 1), FaceValue + Coupon))
    Roots = pol.polyroots(CF)
    Real = np.real(Roots[np.isreal(Roots)])
    Positive = (Real[Real > 0.0]).item(0)
    return (1.0 / Positive - 1.0) * 100<jupyter_output><empty_output><jupyter_text>The function `Bond_Price` computes the bond price.<jupyter_code>def Bond_Price(Yield, Maturity, CouponRate, FaceValue):
    Coupon = 0.01 * CouponRate * FaceValue
    CF = np.hstack((0.0, np.tile(Coupon, int(Maturity) - 1), FaceValue + Coupon))
    return pol.polyval(1.0 / (1.0 + 0.01 * Yield), CF)
P_A = Bond_Price(7, 7, 5, 100)
Y_B = Bond_Yield(98, 5, 5, 100)
print([P_A, Y_B])
V_Yield = np.linspace(0, 12, 41)
V_Price = np.array([Bond_Price(Yield, 7, 5, 100) for Yield in V_Yield])
fig1 = plt.figure(num=1, facecolor='w')
plt.plot(V_Yield, V_Price, 'b-')
plt.xlabel('yield')
plt.ylabel('price')
plt.show()<jupyter_output><empty_output><jupyter_text>Duration and Convexity---The price sensitivity to a infinitesimally small shift in the yield curve is given by\begin{align*} \frac{\nabla_y P(y)}{P(y)} &= \begin{cases} \displaystyle -\frac{D(y)}{1+y}, &  (\text{one-year compound}) \\ & \\ \displaystyle -D(y), & (\text{continuous compound}) \\ \end{cases} \\ \\ D(y) &= \frac{C\sum_{t=1}^T tB(t,y) + FTB(T,y)}{P(y)}.\end{align*}$D(y)$ is called the duration.The following cell defines a function to compute the duration of a coupon-bearing bond.<jupyter_code>def Bond_Duration(Yield, Maturity, CouponRate, FaceValue):
    Price = Bond_Price(Yield, Maturity, CouponRate, FaceValue)
    Coupon = 0.01 * CouponRate * FaceValue
    CF = np.hstack((np.tile(Coupon, int(Maturity) - 1), Coupon + FaceValue))
    Coef = np.linspace(1, Maturity, Maturity) * CF
    return pol.polyval(1.0 / (1.0 + 0.01 * Yield), np.hstack((0.0, Coef))) / Price<jupyter_output><empty_output><jupyter_text>The convexity $C(y)$ is defined as\begin{align*} C(y) &= \frac{\nabla_y^2 P(y)}{P(y)} = \frac{S(y) + (1 + D(y))D(y)}{(1+y)^2},\ (\text{one-year compound}) \\ & \\ S(y) &= \frac{C\sum_{t=1}^T(t-D(y))^2B(t,y)+F(T-D(y))^2B(T,y)}{P(y)},\end{align*}where $S(y)$ is called the dispersion.The following cell defines a function to compute the convexity of a coupon-bearing bond<jupyter_code>def Bond_Convexity(Yield, Maturity, CouponRate, FaceValue):
    Price = Bond_Price(Yield, Maturity, CouponRate, FaceValue)
    Duration = Bond_Duration(Yield, Maturity, CouponRate, FaceValue)
    Coupon = 0.01 * CouponRate * FaceValue
    CF = np.hstack((np.tile(Coupon, int(Maturity) - 1), Coupon + FaceValue))
    Coef = (np.linspace(1, Maturity, Maturity) - Duration)**2 * CF
    Dispersion = pol.polyval(1.0 / (1.0 + 0.01 * Yield), np.hstack((0.0, Coef))) \
                / Price
    return (Dispersion + (1.0 + Duration) * Duration) / (1.0 + 0.01 * Yield)**2
P_A = Bond_Price(5, 10, 7, 100)
P_B = Bond_Price(5, 8, 0.9, 100)
D_A = Bond_Duration(5, 10, 7, 100)
D_B = Bond_Duration(5, 8, 0.9, 100)
C_A = Bond_Convexity(5, 10, 7, 100)
C_B = Bond_Convexity(5, 8, 0.9, 100)
print([P_A, P_B, D_A, D_B, C_A, C_B])
V_Yield = np.linspace(0, 12, 41)
V_Price_A = np.array([Bond_Price(Yield, 10, 7, 100) for Yield in V_Yield])
V_Price_B = np.array([Bond_Price(Yield, 8, 0.9, 100) for Yield in V_Yield])
fig1 = plt.figure(num=1, facecolor='w')
plt.plot(V_Yield, V_Price_A / P_A, 'b-')
plt.plot(V_Yield, V_Price_B / P_B, 'r--')
plt.axhline(1, color='k', linestyle=':', linewidth=0.5)
plt.axvline(5, ymin=0, ymax=0.8, color='k', linestyle=':', linewidth=0.5)
plt.xlabel('yield')
plt.ylabel('price')
Legend_A = 'bond A (D ={0:8.4f}, C ={1:8.4f})'.format(D_A, C_A)
Legend_B = 'bond B (D ={0:8.4f}, C ={1:8.4f})'.format(D_B, C_B)
plt.legend([Legend_A, Legend_B], loc='best', frameon=False)
plt.show()<jupyter_output><empty_output><jupyter_text>`numpy.linalg` is a module in NumPy that provides functions for matrix manupilations.<jupyter_code>import numpy.linalg as la<jupyter_output><empty_output><jupyter_text>Yield Curve Estimation---Since long-term bonds are coupon-bearing ones, we have to estimate the yield curve with price data of coupon-bearing bonds. Suppose $N$ bonds with various maturities $T_1,\dots,T_N$ are traded in the market. They must be issued by the same agent. The price of bond $n$ is $P_n$ and its cash flow is $\{C_n(1),\dots,C_n(\bar T)\}$ where $\bar T$ is the longest maturity in the market. Since bond $n$ will mature at $T_n$, $C_n(t)=0$ for $t>T_n$. Then the bond price is given by$$ P_n = \sum_{t=1}^{T_n} B(t)C_n(t) = \sum_{t=1}^{\bar T} B(t)C_n(t),\quad (n=1,\dots,N)$$where $B(t)$ is the discount factor.The discount factors $B(1),\dots,B(\bar T)$ are obtained as the solution of the following system of equations:$$\label{pyfin.bond.bootstrap_matrix1}\begin{bmatrix}P_1 \\ P_2 \\ \vdots \\ P_N\end{bmatrix}=\begin{bmatrix}C_1(1) & C_1(2) & \cdots & C_1(\bar T) \\C_2(1) & C_2(2) &  \cdots & C_2(\bar T) \\\vdots & & \ddots & \vdots \\C_N(1) & C_N(2) & \cdots & C_N(\bar T) \\\end{bmatrix}\begin{bmatrix}B(1) \\ B(2) \\ \vdots \\ B(\bar T))\end{bmatrix}.$$Finally, the yield curve $y(t)$ is given by$$ y(t) = \begin{cases} \displaystyle \left(\frac1{B(t)}\right)^{\frac1{t}} - 1, & (\text{one-year compound}) \\ \\ \displaystyle \frac1{t}\log\frac1{B(t)}, & (\text{continuous compound}) \\ \end{cases}$$<jupyter_code>Bond = np.array([
    [ 99.90,  1, 2.0],
    [100.10,  2, 2.3],
    [100.66,  3, 2.6],
    [ 99.77,  4, 2.4],
    [ 98.38,  5, 2.2],
    [ 96.00,  6, 1.9],
    [ 93.70,  7, 1.7],
    [ 95.32,  8, 2.1],
    [ 95.21,  9, 2.2],
    [ 97.00, 10, 2.5]
])
F = 100
Yield = np.array([Bond_Yield(Bond[idx,0], Bond[idx,1], Bond[idx,2], F)
                  for idx in range(Bond.shape[0])])
P = Bond[:,0]
C = F * np.identity(Bond.shape[0]) \
    + np.tril(np.transpose(np.tile(0.01 * Bond[:, 2] * F, (Bond.shape[0], 1))))
print(C)
V = la.solve(C, P)
ZeroRate = (np.power(1.0 / V, 1.0 / Bond[:, 1]) - 1.0) * 100
print(np.vstack((Yield, ZeroRate)).T)<jupyter_output>[[2.1021021  2.1021021 ]
 [2.24830752 2.24999048]
 [2.36949285 2.37393675]
 [2.4610808  2.46752954]
 [2.54919387 2.55795852]
 [2.6293444  2.63970801]
 [2.69977972 2.71164925]
 [2.75996194 2.77765736]
 [2.80975249 2.83112753]
 [2.84898646 2.87581039]]<jupyter_text>The following cell creates a figure of bond yield curves<jupyter_code>fig1 = plt.figure(num=1, facecolor='w')
plt.plot(Bond[:,1], ZeroRate, 'b-')
plt.plot(Bond[:,1], Yield, 'r--')
plt.xlabel('time to maturity')
plt.ylabel('yield')
plt.legend(['yield curve (zero-coupon bond)',
            'yield curve (coupon-bearing bond)'], loc='best', frameon=False)
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.19449751623996944, 'fraction_numerical': 0.06317666539294357, 'mean_word_length': 4.316181448882871, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '212488', 'license': 'MIT', 'n_tokens_mistral': 3561, 'n_tokens_neox': 3255, 'n_words': 754, 'path': 'notebook/ges_bond.ipynb', 'repo_name': 'nakatsuma/GES-PEARL', 'size': '7851'}