Bond prices are determined by using the coupon rates and cash flows to find the present value of the bond.
\[PV(CF_t) = B(0,t)CF_t\]
where \(CF_t\) is the cash flow at time \(t\) and \(B(0,t)\) is the discount factor or the price at time \(0\) on the dollar.
\[PV(CF_t) = \dfrac{CF_t}{[1+R(0,t)]^t}\]
where \(R(0,t)\) is the annual spot rate at time \(0\) with the maturity of \(t\). We can rearrange to
\[B(0,t) = \dfrac{1}{[1+R(0,t)]^t}\]
\(B(0,t)\) can also be referred to as the price of the zero coupon bond. Most bonds are not zero coupon but it is possible to construct almost all payment structures using zero coupon bonds.
We can imply zero coupon rates with different maturities from the bonds on the market. Then we can use these rates to build a term structure model to price any bond. Strict violations of the term structure can be a buy/sell opportunity as well as an arbitrage opportunity.
calculate_bond_price<-function(face_value=1000,coupon_rate=0.05,maturity=1,yearly_coupons=0){
#This function calculates the price of the bond B(0,t) given
#its face value, maturity, annual coupon rate and equidistant payment
#if yearly_coupons == 0, it only pays out at the maturity
#if yearly_coupons == 1, it pays out annually
#if yearly_coupons == 2, it pays out semiannually
if(yearly_coupons==0){
face_value/((1+coupon_rate)^maturity)
}else{
face_value/((1+coupon_rate/yearly_coupons)^(yearly_coupons*maturity))
}
}
calculate_bond_price()
## [1] 952.381
We can also construct zero coupon bonds from coupon paying bonds if we have the appropriate securities. From the lecture notes suppose we have two bonds.
\[99 = \dfrac{8}{(1+R(0,1))} + \dfrac{108}{(1+R(0,2))^2} = \dfrac{8}{1.0526} + \dfrac{108}{(1+R(0,2))^2}\]
\[R(0,2) = 8.07\%\]
The price of the 2 year pure discount bond would be \(99 - 0.08(95) = 91.4\) by going long with the two year bond and short with one year bond by 0.08 units (to offset gains from the first year coupon).
This is the method where interest rate is applied only once. Suppose an interest rate of 0.05 and a maturity of 2 years. What would be the price of $100 will be at the maturity.
\[100*(1+0.05)^(1.5) = 107.59\]
If the interest is perpetually added to the principal investment, then we have a compounding interest rate. Suppose the same example but compounded semiannually.
\[100*(1+0.05/2)^(2*1.5) = 107.6891\]
which yields a annual nominal interest rate of \((1.076891)^(1/1.5)-1 = 0.050625\).
Now suppose frequency of the compounding is so high that at the time period between added interest is infinitesimal (close to zero). Then at the limiting case
\[\lim_{n->\infty}\left(1+r/n\right)^{nT} = e^{rT}\]
Looks familiar?
So with our example continuously compounding annual interest rate is \(e^(0.05)-1=0.051271\).
Given a set of zero coupon bond prices we can calculate the continuous yield values with \(-log(B(0,T)/T)\).
calculate_yield<-function(bond_price,maturity){
-log(bond_price)/maturity
}
#Example bond price is 0.987 and maturity is half a year.
calculate_yield(0.987,0.5)
## [1] 0.02617048
Suppose there are two bonds with different maturities (say \(t\) and \(T\), \(T > t\)) and spot interest rates (\(R(0,t) and R(0,T)\)). The additional rate between the maturities (\(R(t,T)\)) is called the forward rate, denoted as \(F(0,t,T-t)\).
\[(1+R(0,T))^T = (1+R(0,t))^t(1+F(0,t,T-t))^{(T-t)}\]
can be rearranged into
\[F(0,t,T-t) = \left(\dfrac{(1+R(0,T))^T}{(1+R(0,t))^t}\right)^{1/(T-t)}-1\]
imply_forward_rate<-function(R0t1=0.04,R0t2=0.045,t1=1,t2=2){
((1+R0t2)^t2/(1+R0t1)^t1)^(1/(t2-t1)) -1
}
imply_forward_rate()
## [1] 0.05002404
\(F(0,t,T-t)\) is also equivalent to the zero coupon rate at the future \(R(t,T-t)\).
Interest rates do not only change with maturity but they also change in time. We will also use an R package called YieldCurve
for some data and calculations.
install.packages("YieldCurve")
Let’s load the library and check the Fed Yield Curve data.
library(YieldCurve)
#Call the data
data(FedYieldCurve)
head(FedYieldCurve)
## R_3M R_6M R_1Y R_2Y R_3Y R_5Y R_7Y R_10Y
## 1981-12-31 12.92 13.90 14.32 14.57 14.64 14.65 14.67 14.59
## 1982-01-31 14.28 14.81 14.73 14.82 14.73 14.54 14.46 14.43
## 1982-02-28 13.31 13.83 13.95 14.19 14.13 13.98 13.93 13.86
## 1982-03-31 13.34 13.87 13.98 14.20 14.18 14.00 13.94 13.87
## 1982-04-30 12.71 13.13 13.34 13.78 13.77 13.75 13.74 13.62
## 1982-05-31 13.08 13.76 14.07 14.47 14.48 14.43 14.47 14.30
The correlation matrix shows that yield rates are not perfectly correlated, so there are shifts and changes in shape in time.
R_3M | R_6M | R_1Y | R_2Y | R_3Y | R_5Y | R_7Y | R_10Y | |
---|---|---|---|---|---|---|---|---|
R_3M | 1.0000000 | 0.9983390 | 0.9940045 | 0.9837559 | 0.9744780 | 0.9546189 | 0.9399504 | 0.9230412 |
R_6M | 0.9983390 | 1.0000000 | 0.9981715 | 0.9899820 | 0.9817197 | 0.9632268 | 0.9491761 | 0.9332366 |
R_1Y | 0.9940045 | 0.9981715 | 1.0000000 | 0.9959937 | 0.9900195 | 0.9746174 | 0.9621895 | 0.9478956 |
R_2Y | 0.9837559 | 0.9899820 | 0.9959937 | 1.0000000 | 0.9984844 | 0.9896811 | 0.9808896 | 0.9694621 |
R_3Y | 0.9744780 | 0.9817197 | 0.9900195 | 0.9984844 | 1.0000000 | 0.9958583 | 0.9896185 | 0.9804575 |
R_5Y | 0.9546189 | 0.9632268 | 0.9746174 | 0.9896811 | 0.9958583 | 1.0000000 | 0.9983629 | 0.9936744 |
R_7Y | 0.9399504 | 0.9491761 | 0.9621895 | 0.9808896 | 0.9896185 | 0.9983629 | 1.0000000 | 0.9981232 |
R_10Y | 0.9230412 | 0.9332366 | 0.9478956 | 0.9694621 | 0.9804575 | 0.9936744 | 0.9981232 | 1.0000000 |
In this part we will see the methods of extracting and constructing bond prices and yields.
Suppose you are given the following bond rates (from lecture notes). Remember nominal rate is 100.
Coupon | Maturity | Price | |
---|---|---|---|
Bond 1 | 5.0 | 1 | 101.0 |
Bond 2 | 5.5 | 2 | 101.5 |
Bond 3 | 5.0 | 3 | 99.0 |
Bond 4 | 6.0 | 4 | 100.0 |
The zero coupon bond prices (\(B(0,t)\)) can be found with the following system of equations.
\[\begin{align*} 101 &= 105B(0,1) \\ 101.5 &= 5.5B(0,1) + 105.5B(0,2) \\ 99 &= 5B(0,1) + 5B(0,2) + 105B(0,3) \\ 100 &= 6B(0,1) + 6B(0,2) + 6B(0,3) + 106B(0,4) \end{align*}\]Then we obtain \(B(0,1)=0.9619, B(0,2)=0.9114, B(0,3) = 0.85363, B(0,4) = 0.789\). Their corresponding zero coupon yield rates are \(R(0,1)=3.96\%, R(0,2)=4.717\%, R(0,3)=5.417\%, R(0,4)=6.103\%\).
get_zero_coupon<-function(coupons=c(5,5.5,5,6),BondPrices=c(101,101.5,99,100),nominal_value=100){
#We assume both coupons and BondPrices vectors are arranged to 1 year increasing maturity.
price_matrix <- matrix(0,nrow=length(coupons),ncol=length(coupons))
#Assign the coupons for each year
for(i in 1:length(coupons)){
price_matrix[i,1:i] <- coupons[i]
}
#Add the maturity nominal value
diag(price_matrix) <- diag(price_matrix) + nominal_value
#Solve the system of equations to get B(0,t)
zero_coupon_prices<-solve(price_matrix,BondPrices)
#Get zero coupon yields R(0,t)
zero_coupon_yields <- (1/zero_coupon_prices)^(1/1:length(coupons))-1
return(list(B0t=zero_coupon_prices,R0t=zero_coupon_yields))
}
get_zero_coupon()
## $B0t
## [1] 0.9619048 0.9119386 0.8536265 0.7890111
##
## $R0t
## [1] 0.03960396 0.04717001 0.05417012 0.06103379
R03<-0.055
R04<-0.06
R03p75<-((4-3.75)*0.055+(3.75-3)*0.06)/(4-3)
R03p75
## [1] 0.05875
##Or use the R function
yield_interpolate<-approxfun(x=c(3,4),y=c(0.055,0.06))
yield_interpolate(3.75)
## [1] 0.05875
Suppose our rates are as following: \(R(0,1)=0.03, R(0,2)=0.05, R(0,3)=0.055, R(0,4)=0.06\). We are going to solve the following structure for a, b, c and d.
\[\begin{align*} R(0,t_1) &= at_1^3 + bt_1^2 + ct_1 + d \\ R(0,t_2) &= at_2^3 + bt_2^2 + ct_2 + d \\ R(0,t_3) &= at_3^3 + bt_3^2 + ct_3 + d \\ R(0,t_4) &= at_4^3 + bt_4^2 + ct_4 + d \end{align*}\]A_mat<-t(matrix(1:4,nrow=4,ncol=4,byrow=TRUE)^(3:0))
abcd_vec<-solve(A_mat,c(0.03,0.05,0.055,0.06))
#Interpolate for a bond of 2.5 years
t_val<-2.5
sum(abcd_vec*((2.5)^(3:0)))
## [1] 0.0534375
#Or we can use the cubic spline function of R
yield_spline<-splinefun(x=1:4,y=c(0.03,0.05,0.055,0.06))
yield_spline(2.5)
## [1] 0.0534375
Instead of bootstrap techniques we will use models. Nelson Siegel model is a popular way of modeling interest rate yield curve.
\[R(0,\theta) = \beta_0 + \beta_1\left(\dfrac{1-exp(-\theta/\tau)}{\theta/\tau}\right) + \beta_2\left[\dfrac{1-exp(-\theta/\tau)}{\theta/\tau} - exp(-\theta/\tau)\right]\]
where \(\theta\) is the maturity, \(\beta_0\) is the level parameter (long-term yield rate), \(\beta_1\) is the slope parameter (long/short term spread), \(\beta_2\) is the curvature parameter and \(\tau\) is the scale parameter.
nelson_siegel_calculate<-function(theta,tau,beta0,beta1,beta2){
beta0 + beta1*(1-exp(-theta/tau))/(theta/tau) + beta2*((1-exp(-theta/tau))/(theta/tau) - exp(-theta/tau))
}
###Let's plot the yield curve of NS with the following parameters
ns_data <-
data.frame(maturity=1:30) %>%
mutate(ns_yield=nelson_siegel_calculate(theta=maturity,tau=3.3,beta0=0.07,beta1=-0.02,beta2=0.01))
head(ns_data)
## maturity ns_yield
## 1 1 0.05398726
## 2 2 0.05704572
## 3 3 0.05940289
## 4 4 0.06122926
## 5 5 0.06265277
## 6 6 0.06376956
ggplot(data=ns_data, aes(x=maturity,y=ns_yield)) + geom_point() + geom_line()
You can play with the parameters to get better estimates of the yield curve.
R package YieldCurve
mentioned above has a Nelson Siegel curve estimation function. See the help file for details.
Nelson.Siegel(as.matrix(FedYieldCurve[1:3,]),maturity=c(3,6,12,24,36,60,72,120))
## beta_0 beta_1 beta_2 lambda
## 1981-12-31 14.70711 -5.3917409 3.269125 0.5123605
## 1982-01-31 14.35240 -0.7602066 2.834508 0.1887807
## 1982-02-28 13.74481 -0.9247232 2.681840 0.1236869
Note: We refer lambda as tau (\(\tau\)), the shape parameter.
Consider the price of a bond delivering future cash flows \(F_i\) as \(P_0 = \sum_iF_ie^{-\theta_iR(0,\theta_i)}\). So the change in the price with beta parameters is as follows.
\[\dfrac{\partial P_{0}}{\partial \beta_{0}} = -\sum_{i}\theta_{i}F_i e^{-\theta_{i} R(0,\theta_{i})}\]
\[\dfrac{\partial P_0}{\partial \beta_1} = -\sum_i\theta_i \left[ \dfrac{1 - exp(- \theta / \tau)}{\theta/\tau}\right]F_ie^{-\theta_i R(0,\theta_i)}\]
\[\dfrac{\partial P_0}{\partial \beta_2} = - \sum_i \theta_i \left[ \dfrac{1 - exp(- \theta / \tau)}{\theta / \tau} - exp(- \theta / \tau) \right]F_ie^{-\theta_iR(0,\theta_i)}\]
nelson_siegel_sensitivities<-function(tau=3,beta0=0.08,beta1=-0.03,beta2=-0.01,nominal_rate=100,coupon_rate,maturity){
f_i <- nominal_rate*rep(coupon_rate,maturity)
f_i[length(f_i)]<-f_i[length(f_i)]+nominal_rate
theta_i <- 1:maturity
r_i <- 0
for(i in 1:maturity){
r_i[i] <- nelson_siegel_calculate(theta=i,tau=tau,beta0=beta0,beta1=beta1,beta2=beta2)
}
beta0_sens <- -sum(f_i*theta_i*exp(-theta_i*r_i))
beta1_sens <- -sum(f_i*theta_i*(1-exp(-theta_i/tau))/(theta_i/tau)*exp(-theta_i*r_i))
beta2_sens <- -sum(f_i*theta_i*((1-exp(-theta_i/tau))/(theta_i/tau) - exp(-theta_i/tau))*exp(-theta_i*r_i))
return(c(Beta0=beta0_sens,Beta1=beta1_sens,Beta2=beta2_sens))
}
nelson_siegel_sensitivities(coupon_rate=0.05,maturity=2)
## Beta0 Beta1 Beta2
## -192.51332 -141.08199 -41.27936
nelson_siegel_sensitivities(coupon_rate=0.05,maturity=7)
## Beta0 Beta1 Beta2
## -545.4198 -224.7767 -156.7335
nelson_siegel_sensitivities(coupon_rate=0.05,maturity=15)
## Beta0 Beta1 Beta2
## -812.6079 -207.1989 -173.0285
These R lecture notes will also be following Fixed-Income Securities: Valuation Risk Management and Portfolio Strategies by Martellini, Priaulet and Priaulet.↩
Bonds can be bought or sold at an extra premium or discount on top of their yields. Pure discount means there is no extra discount or premium.↩