Yield Curve Behavior

Yield Curve Behavior in Data

# install.packages("YieldCurve")
library(YieldCurve)
## Loading required package: xts
## Warning: package 'xts' was built under R version 4.0.2
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
# Call Fed Yield Curve Dataset
data(FedYieldCurve) 
YC <- FedYieldCurve
YC.diff <- diff(YC)

# Time series plot of all interest rates over time
plot(YC)

# YC as of 1981-12-31, 1990-03-31, 1998-07-31, 2006-11-30
par(mfrow = c(2,2))
plot(as.numeric(YC[1,]), type = "l")
plot(as.numeric(YC[100,]), type = "l")
plot(as.numeric(YC[200,]), type = "l")
plot(as.numeric(YC[300,]), type = "l")

# Time Series Plot of the short-term rate (3-Month Rate)
par(mfrow = c(3,1))
plot(YC[,1])
plot(diff(YC[,1]))
hist(diff(YC[,1]))

# Time Series Plot of the short-term rate (6-Month Rate)
par(mfrow = c(3,1))
plot(YC[,2])
plot(diff(YC[,2]))
hist(diff(YC[,2]))

# Time Series Plot of the short-term rate (1-Year Rate)
par(mfrow = c(3,1))
plot(YC[,3])
plot(diff(YC[,3]))
hist(diff(YC[,3]))

# Time Series Plot of the short-term rate (2-Year Rate)
par(mfrow = c(3,1))
plot(YC[,4])
plot(diff(YC[,4]))
hist(diff(YC[,4]))

# Time Series Plot of the short-term rate (3-Year Rate)
par(mfrow = c(3,1))
plot(YC[,5])
plot(diff(YC[,5]))
hist(diff(YC[,5]))

# Time Series Plot of the short-term rate (5-Year Rate)
par(mfrow = c(3,1))
plot(YC[,6])
plot(diff(YC[,6]))
hist(diff(YC[,6]))

# Time Series Plot of the short-term rate (7-Year Rate)
par(mfrow = c(3,1))
plot(YC[,7])
plot(diff(YC[,7]))
hist(diff(YC[,7]))

# Time Series Plot of the short-term rate (10-Year Rate)
par(mfrow = c(3,1))
plot(YC[,8])
plot(diff(YC[,8]))
hist(diff(YC[,8]))

# Correlation of all the rate change (discard row 1)
cor(YC.diff[-1,]) 
##            R_3M      R_6M      R_1Y      R_2Y      R_3Y      R_5Y      R_7Y
## R_3M  1.0000000 0.9328258 0.8329279 0.7165065 0.6486443 0.5609318 0.5062884
## R_6M  0.9328258 1.0000000 0.9581396 0.8703235 0.8089202 0.7326576 0.6825867
## R_1Y  0.8329279 0.9581396 1.0000000 0.9561221 0.9129480 0.8474693 0.8016415
## R_2Y  0.7165065 0.8703235 0.9561221 1.0000000 0.9855402 0.9439361 0.9045949
## R_3Y  0.6486443 0.8089202 0.9129480 0.9855402 1.0000000 0.9753412 0.9454168
## R_5Y  0.5609318 0.7326576 0.8474693 0.9439361 0.9753412 1.0000000 0.9875644
## R_7Y  0.5062884 0.6825867 0.8016415 0.9045949 0.9454168 0.9875644 1.0000000
## R_10Y 0.4724634 0.6479118 0.7640692 0.8660014 0.9118204 0.9674999 0.9879781
##           R_10Y
## R_3M  0.4724634
## R_6M  0.6479118
## R_1Y  0.7640692
## R_2Y  0.8660014
## R_3Y  0.9118204
## R_5Y  0.9674999
## R_7Y  0.9879781
## R_10Y 1.0000000

What Drives the Yield Curve Movement: The Theory

There are several theories and hypotheses that attempt to explain the shape and behavior of the yield curve:

  1. Expectations Theory: This theory suggests that the shape of the yield curve reflects market participants’ expectations of future interest rates. It posits that long-term interest rates are an average of current and expected short-term interest rates. If investors expect rates to rise, the yield curve may be upward-sloping; if they expect rates to fall, it may be downward-sloping.

  2. Liquidity Preference Theory: This theory asserts that investors demand a premium (higher yield) for holding longer-term bonds to compensate for the increased risk associated with tying up their funds for an extended period. This explains why longer-term yields tend to be higher than short-term yields, resulting in an upward-sloping yield curve.

  3. Market Segmentation Theory: According to this theory, different market participants have preferences for specific maturities based on their investment needs and risk appetite. As a result, the supply and demand dynamics in each maturity segment can cause variations in yields, leading to a segmented or humped yield curve.

  4. Preferred Habitat Theory: Building upon the market segmentation theory, this theory suggests that investors may be willing to move across different maturity segments (or “habitats”) if they are compensated adequately for the perceived risk. It explains deviations from a purely segmented yield curve and helps explain transitions between different curve shapes.

  5. Term Premium Theory: This theory focuses on the risk premium embedded in long-term bonds due to uncertainty and potential price volatility over a more extended period. It suggests that long-term yields incorporate an additional term premium to compensate investors for holding longer maturities.

Key Factors That Move the Yield Curve

Note: Duration can only capture a small level change of the yield curve!

Note: Other types of duration could work too: Effective Duration, Effective Convexity

Note: Other types of duration could work too: Key Rate Duration and Key Rate Convexity

… But What About Spread?

Component of Bond Spread: The Diagram That I Love!

Farahvash, P. (2020). Asset-liability and liquidity management. John Wiley & Sons.

Types of Spread Jargons

How to Decompose/Capture the Yield Curve Behavior using 3 factor model?

Nelson-Seigel Model: The Model

## Nelson-Siegel Model 
## Reading: Estimating the Yield Curve Using the Nelson-Siegel Modelby Jan Annaert et al (2010)
## Link: https://www.efmaefm.org/0EFMAMEETINGS/EFMA%20ANNUAL%20MEETINGS/2010-Aarhus/papers/Smoothed_Bootstrap_-_Nelson-Siegel_Revisited_June_2010.pdf

### Yield Calculation from NS Model: Note - tau is lambda
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))
}

#install.packages("dplyr")
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:xts':
## 
##     first, last
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
ns_data <-
  data.frame(maturity=1:30) %>%
  mutate(ns_yield=nelson_siegel_calculate(theta=maturity,tau=3,beta0=3,beta1=-3,beta2=-3))

head(ns_data)
##   maturity   ns_yield
## 1        1 0.04715752
## 2        2 0.16100543
## 3        3 0.31091497
## 4        4 0.47697854
## 5        5 0.64657898
## 6        6 0.81201170
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))
}

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
library(ggplot2)

ggplot(data=ns_data, aes(x=maturity,y=ns_yield)) + geom_point() + geom_line()

Nelson-Seigel Model: Parameter Estimation

# Parameter Estimation 

# install.packages("YieldCurve")
library(YieldCurve)
?Nelson.Siegel

data(FedYieldCurve) # Call Fed Yield Curve Dataset

NS_result <- Nelson.Siegel(as.matrix(FedYieldCurve[1:10,]),maturity=c(3,6,12,24,36,60,72,120))

Nelson-Seigel Model: Yield Curve Forecasting

# Yield Curve Forecasting

library(forecast)
## Warning: package 'forecast' was built under R version 4.0.5
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
#b0
b0 <- NS_result[,1]
plot(b0, type = "l")

acf(b0)

pacf(b0)

b0.mod <- auto.arima(b0)
b0.for <- forecast(b0.mod, h = 12)

#b1
b1 <- NS_result[,2]
plot(b1, type = "l")

acf(b1)

pacf(b1)

b1.mod <- auto.arima(b1)
b1.for <- forecast(b1.mod, h = 12)

#b2
b2 <- NS_result[,3]
plot(b2, type = "l")

acf(b2)

pacf(b2)

b2.mod <- auto.arima(b2)
b2.for <- forecast(b2.mod, h = 12)

#lambda or tau
b3 <- NS_result[,4]
plot(b3, type = "l")

acf(b3)

pacf(b3)

b3.mod <- auto.arima(b3)
b3.for <- forecast(b3.mod, h = 12)

#Yield Curve Forecasting
h <- 12
a <- b0.for[["mean"]][h]
b <- b1.for[["mean"]][h]
c <- b2.for[["mean"]][h]
d <- b3.for[["mean"]][h]

ns_forecast <-
  data.frame(maturity=1:30) %>%
  mutate(ns_yield=nelson_siegel_calculate(theta=maturity,tau=d,beta0=a,beta1=b,beta2=c))

ggplot(data=ns_forecast, aes(x=maturity,y=ns_yield)) + geom_point() + geom_line()

Nelson-Seigel Model: Yield Curve Scenario Analysis

#Bond Sensitivities
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

What about Vasicek and CIR?