1 Introduction

Why value bonds?

Bonds are securities issued by governments or corporations that pay interest over a fixed schedule and are the most well-known type of fixed income securities. The US fixed income market is 1.5x larger than the US stock market but most fixed income instruments, including bonds, trade very infrequently.

Consequently, a bond’s price may be a less reliable indicator of its value and analytical techniques are necessary when analyzing and valuing bonds.

The fixed income market is large and filled with complex instruments. The code below focuses on plain vanilla bonds and demonstrates the fundamentals of bond analysis.

Key valuation concepts
* Annual coupon
* Fixed rate
* Fixed maturity
* Option-free bond

1.1 Future value

Computing the opportunity cost of purchasing a bond relates to the bond’s future value and the ‘time value of money’.

The growth rate of the opportunity cost is compounded, which means the future value of $100 two years from now is equal to the future value of $100 one year from now growing at the opportunity cost.

The code below calculates the future value of $100 one year from now and two years from now assuming an opportunity cost of 10%.

Steps
* Create pv (present value) with a value of 100
* Create r (interest rate) of 0.10
* Calculate the future value (fv1) of $100 one year from now
* Calculate the future value (fv2) of $100 two years from now

# Create pv
pv <- 100

# Create r
r <- 0.10

# Calculate fv1
fv1 <- 100 * (1 + r)

# Calculate fv2
fv2 <- 100 * (1 + r)^2

# Print
fv1
## [1] 110
fv2
## [1] 121

1.2 Present value

Computing a bond’s present value is the reverse process.

If you expect to receive $100 one year from now, the present value of that $100 will be less today (because money now is preferable to later). The value of $100 two years from now, is less than the present value of $100 one year now.

Bond characteristics
* Interest rate (r) of 0.10
* Future value (fv1) one year from now
* Future value (fv2) two years from now

Steps
* Calculate the present value (pv1) of fv1
* Repeat the process to calculate the present value (pv2) of fv2

# Calculate pv1
pv1 <- fv1 / (1 + r)

# Calculate pv2
pv2 <- fv2 / (1 + r)^2

# Print pv1 and pv2
pv1
## [1] 100
pv2
## [1] 100

1.3 Cash flows

Next, we focus on the cash flows.

Bond characteristics
* $100 par value
* 5% coupon rate
* 5 years to maturity
* $100 par value

Steps
* Create vector (cf) containing the bond’s cash flows
* Convert to dataframe

# Create vector of cash flows
cf <- c(5, 5, 5, 5, 105)

# Convert to data frame
cf <- data.frame(cf)
cf

1.4 Discounting the bond cash flows

After laying out the bond’s cash flows, we can calculate the present value of each cash flow and value the bond. The value of the bond is the sum of the present value of its cash flows.

In this exercise, you will calculate the appropriate present value factor, pv_factor, for each cash flow, so we can calculate each cash flow’s present value pv. You will then sum the pv to find the value of the bond.

Bond characteristics
* 6% yield (used as discount rate)
* Cash flows (cf)

Steps
* Add a new column (t) as a time index
* Ensure these are ‘numeric’
* Add new column to store the discount factors (pv_factor)
* Add column (pv) to store the present value of each cash flow
* Sum pv values to find the overall bond value

# Add column t
cf$t <- as.numeric(rownames(cf))

# Calculate pv_factor
cf$pv_factor <- 1 / (1 + 0.06)^cf$t

# Calculate pv
cf$pv <- cf$cf * cf$pv_factor

# View dataframe
cf
# Calculate the bond price
sum(cf$pv)
## [1] 95.78764

1.5 Creating a bond valuation function

These steps can be written into a scaleable / faster / reusable function. It needs to incorporate all key features of the bond.

Key inputs
* Par value (p)
* Coupon rate (r)
* Time to maturity (ttm)
* Yield (y)

Bond characteristics
* $100 par value
* 5% coupon rate
* 5 years to maturity
* 6% yield to maturity
* Should give $95.79 price

# Create function
bondprc <- function(p, r, ttm, y) {
  cf <- c(rep(p * r, ttm - 1), p * (1 + r))
  cf <- data.frame(cf)
  cf$t <- as.numeric(rownames(cf))
  cf$pv_factor <- 1 / (1 + y)^cf$t
  cf$pv <- cf$cf * cf$pv_factor
  sum(cf$pv)
}

# Verify prior result
bondprc(100, 0.05, 5, 0.06)
## [1] 95.78764

2 Yield To Maturity

The YTM measures the expected return to bond investors if they hold the bond until maturity AKA the compensation investors demand for the risk they are bearing by investing in a particular bond.

2.1 Moody’s Baa index

A bond’s yield can be estimated by looking at the yield of comparable bonds.

The following code takes the Baa credit rating index from Moody’s as a benchmark, issuance on September 30, 2016.

Steps
* Load the Quandl package
* Obtain data (baa) Moody’s Baa index (“FED/RIMLPBAAR_N_M”)
* Save to baa
* Identify the yield (baa_yield) for baa on 30/09/2016
* Convert baa_yield$Value to decimals from percentage

# Load Quandl package
library(Quandl)

# Obtain Moody's Baa index data
baa <- Quandl("FED/RIMLPBAAR_N_M")

# Identify 30th September 2016 yield
baa_yield <- subset(baa, baa$Date == "2016-09-30")

# Convert yield to decimals and view
baa_yield <- baa_yield$Value / 100
baa_yield
## [1] 0.0431

2.2 Valuation using the benchmark yield

Bond characteristics
* Yield on comparable bonds is 4.29%
* $100 par value
* 5% coupon rate
* 5 years to maturity

The code below uses the bondprc function and baa_yield.

Steps
* Use prepared bondprc() to value the bond
* Four arguments: the par value (p), the coupon rate (r), the time to maturity (ttm), and the yield of the bond (y)

# Value bond
bondprc(p = 100, r = 0.05, ttm = 5, y = 0.0429)
## [1] 103.1352

2.3 Inverse price/yield relationship

Changes in yield affect a bonds price (aka interest rate sensitivity). Plotting bond price at different yield levels will show a non-linear inverse price / yield relationship.

Bond characteristics
* $100 par value
* 10% coupon rate
* 20 years to maturity

Steps
* Create vector (prc_yld) from 2% to 40% with increments of 1%
* Use prc_yld to a data frame
* Use bondprc() to calculate bond price at different yield levels
* Plot relationship between price and yield to maturity

# Generate prc_yld
prc_yld <- seq(0.02, 0.4, 0.01)

# Convert prc_yld to data frame
prc_yld <- data.frame(prc_yld)

# Calculate bond price given different yields
for (i in 1:nrow(prc_yld)) {
     prc_yld$price[i] <- bondprc(100, 0.10, 20, prc_yld$prc_yld[i])  
}

# Plot P/YTM relationship
plot(prc_yld,
     type = "l",
     col = "blue",
     main = "Price/YTM Relationship")

# Credit spreads

2.4 US Treasury yields

Since corporate bonds are riskier than US Treasuries, the risk-free rate is the baseline rate we would expect corporate bonds to yield. However, US Treasury yields are not constant and could change substantially through time. We can see this by plotting the US Treasury yield data over a long period.

The code below plots 10-Year US Treasury yield data from the Federal Reserve Electronic Database (FRED) from January 2006 to September 2016.

Steps
* Load the quantmod package
* Use getSymbols() to obtain data on DGS10 from FRED
* Subset data (t10yr) from January 2006 to September 2016

# Load quantmod
library(quantmod)
## Loading required package: TTR
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
## Version 0.4-0 included new data defaults. See ?getSymbols.
# Obtain Treasury yield data
t10yr <- getSymbols(Symbols = "DGS10", src = "FRED", auto.assign = FALSE)
## 'getSymbols' currently uses auto.assign=TRUE by default, but will
## use auto.assign=FALSE in 0.5-0. You will still be able to use
## 'loadSymbols' to automatically load data. getOption("getSymbols.env")
## and getOption("getSymbols.auto.assign") will still be checked for
## alternate defaults.
## 
## This message is shown once per session and may be disabled by setting 
## options("getSymbols.warning4.0"=FALSE). See ?getSymbols for details.
# Subset data
t10yr <- t10yr["2006-01/2016-09"]

# Plot yields
 plot(x = index(t10yr),
     y = t10yr$DGS10,
     xlab = "Date",
     ylab = "Yield (%)",
     type = "l",
     col = "red",
     main = "10-Year US Treasury Yields")

2.5 Credit spread

Corporate bond yields are affected by the risk-free rate AND the size of the credit spread. Both change through time.

The investment grade (IG) spread can be viewed as the market’s perception of how risky investing in bonds is at a certain point in time. The larger the IG spread, the more compensation investors demand for investing in riskier bonds.

One way to observe the spread is to compare the yield on the highest rated investment grade bonds (Aaa rating) and the yield on the lowest investment grade bonds (Baa rating).

Steps
* Load Quandl package * Retrieve yield data on Aaa and Baa * Generate a diff column for the spread and converted to percentage points
* Plot() and view spread over time

library(tidyverse)
## ── Attaching packages ──────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.2     ✓ purrr   0.3.4
## ✓ tibble  3.0.3     ✓ dplyr   1.0.2
## ✓ tidyr   1.1.2     ✓ stringr 1.4.0
## ✓ readr   1.3.1     ✓ forcats 0.5.0
## ── Conflicts ─────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::first()  masks xts::first()
## x dplyr::lag()    masks stats::lag()
## x dplyr::last()   masks xts::last()
# Need to use Quandl
spread <- Quandl(c("FED/RIMLPAAAR_N_M", "FED/RIMLPBAAR_N_M")) %>% 
  rename("date" = 1,
         "aaa" = 2,
         "baa" = 3)


# Examine first and last six elements in spread
head(spread)
tail(spread)
# Calculate spread$diff
spread$diff <- (spread$baa - spread$aaa) * 100

# Plot spread
plot(x = spread$date,
     y = spread$diff,
     type = "l",
     xlab = "Date",
     ylab = "Spread (bps)",
     col = "red",
     main = "Baa - Aaa Spread")

The Aaa-Baa spread varies considerably over time and was especially high during the global finacial crisis in the late-2000s.

2.6 Bond yield through trial-and-error

One way to find the bond yield is by trial-and-error. You first start with an initial guess, say 5% and find the price given a 5% yield. E.g. if the resulting price higher / too high compared to the actual observed price (due to the inverse relationship between price and yield) the next guess has to be a higher yield.

Bond characteristics
* Bond price is $95.79
* 5% coupon rate * 5 years to maturity
* $100 par value

The code below calculates the price using a 5%, 6%, and 7% yield to maturity. The bond yield to maturity to value the bond at $95.79 can be reverse engineered.

Steps
* Use bondprc() to calculate bond price
* Use bondprc() to calculate the bond value with a 7% yield
* Repeat for the value of the bond with a 6% yield

# Value bond using 5% yield
bondprc(p = 100, r = 0.05, ttm = 5, y = 0.05)
## [1] 100
# Value bond using 7% yield
bondprc(p = 100, r = 0.05, ttm = 5, y = 0.07)
## [1] 91.79961
# Value bond using 6% yield
bondprc(p = 100, r = 0.05, ttm = 5, y = 0.06)
## [1] 95.78764

2.7 Bond yield with algorithm

Trial-and-error is a very long. An alternative is to use an algorithm that does the work for you.

Bond characterstics
* Bond price is $95.79 * $100 par value
* 5% coupon rate
* 5 years to maturity

Steps
* Create vector of cash flows (cf)
* Create a simple bond valuation function (bval()) to calculate the value at each time period
* Write ytm() function using uniroot()
* Use ytm() on cf vector to find yield to maturity

# Create cash flow vector showing purchase, coupons and value at maturity 
cf <- c(-95.79, 5, 5, 5, 5, 105)

# Create bond valuation function
bval <- function(i, cf,
     t=seq(along = cf))
     sum(cf / (1 + i)^t)

# Create ytm() function using uniroot
ytm <- function(cf) {
    uniroot(bval, c(0, 1), cf = cf)$root
}

# Use ytm() function to find yield
ytm(cf)
## [1] 0.05999552

3 Interest rate risk

Interest rate risk is the biggest risk that bond investors face. When interest rates rise, bond prices fall. Because of this, much attention is paid to how sensitive a particular bond’s price is to changes in interest rates. You can refer to a basis point as an 01, because it represents 0.01%.

3.1 Value of basis point (PV01)

You can calculate the PV01 by calculating the value of a bond and the value of the same bond with a one basis point change in yield.

Bond characteristics
* $100 par value
* 10% coupon
* 20 years to maturity
* 10% yield to maturity

Steps
* Use two calls to bondprc()
* Calculate PV01 from the value of one bond minus the value the other with 1bp difference in interest rate
* Use abs() to ensure a positive output

# Calculate the PV01
abs(bondprc(p = 100, r = 0.10, ttm = 20, y = 0.1001) 
  - bondprc(p = 100, r = 0.10, ttm = 20, y = 0.10))
## [1] 0.08507756

3.2 Approximate bond duration

The full duration formula is more complex but a useful approximation is called the approximate duration. This formula provides a simple way to estimate duration without getting into the nitty-gritty.

(P(down)−P(up))/(2∗P∗Δy)

Notation
* P is the price of the bond
* P(down) is the price of the bond if yield decreases
* P(up) is the price of the bond if yield increases
* Δy is the expected change in yield

Bond characteristics * $100 par value
* 10% coupon rate
* 20 years to maturity
* 10% yield to maturity
* 1% expected change in yield

Steps
* bondprc() to calculate the bond price (px) today
* bondprc() to calculate the bond price (pc_up) if the yield increases by 1%
* bondprc() to calculate the bond price (pc_down) if the yield decreases by 1%
* Calculate the approximate duration assuming a 1% change in yields

# Calculate bond price today
px <- bondprc(p = 100, r = 0.1, ttm = 20, y = 0.1)
px
## [1] 100
# Calculate bond price if yields increase by 1%
px_up <- bondprc(p = 100, r = 0.1, ttm = 20, y = 0.11)
px_up
## [1] 92.03667
# Calculate bond price if yields decrease by 1%
px_down <- bondprc(p = 100, r = 0.1, ttm = 20, y = 0.09)
px_down
## [1] 109.1285
# Calculate approximate duration
duration <- (px_down - px_up) / (2 * px * 0.01)
duration
## [1] 8.545937

3.3 Effect on bond price by duration

When we know a bond’s duration, we can come up with an estimate of the bond’s price assuming some expected change in yield as coded below.

Calulation
* Yields are expected to decrease by 1% * Bond price (px) is $100
* Bond duration is 8.545937
* −D∗Δy

Notation
* D is the duration
* Δy is the change in yield

Steps
* Estimate percentage change (duration_pct_change) based on duration if yields are expected to decrease by 1%
* Estimate dollar change (duration_dollar_change) based on (duration_pct_change) and px if yields are expected to decrease by 1%

# Estimate percentage change
duration_pct_change <- -duration * -0.01
duration_pct_change
## [1] 0.08545937
# Estimate dollar change
duration_dollar_change <- px * duration_pct_change
duration_dollar_change
## [1] 8.545937

3.4 Approximate convexity

Bond price estimates can be improved by adding the convexity term to the duration effect, accounting for how bowed the price/YTM curve is for the bond

Bond characteristics
* $100 par value
* 10% coupon
* 20 years to maturity
* 10% yield to maturity
* 1% expected change in yield

The approximate convexity formula is
(P(up)+P(down)−2∗P)/(P∗Δy2)

Notation
* P is the price of the bond
* P(up) is the price of the bond when yields increase
* P(down) is the price of the bond when yields decrease
* Δy is the expected change in yield

# Calculate approximate convexity
convexity <- (px_up + px_down - 2 * px) / (px * (0.01)^2)
convexity
## [1] 116.5218

3.5 Effect of convexity on bond price

Now that you’ve calculated convexity for your bond, you can estimate the effect of convexity on the bond’s price.

Percentage change based on convexity is given by
0.5∗convexity∗(Δy)2

Values
* Convexity 116.521
* Bond price (px) is $100

Steps
* Estimate percentage change (convexity_pct_change) based on convexity if yields decrease by 1%
* Estimate dollar change (convexity_dollar_change) based on convexity if yields decrease by 1%

# Estimate percentage change
convexity_pct_change <- 0.5 * convexity * (-0.01)^2
convexity_pct_change
## [1] 0.005826088
# Estimate dollar change
convexity_dollar_change <- px * convexity_pct_change
convexity_dollar_change
## [1] 0.5826088

3.6 Combining duration and convexity

Values
* Dollar change due to duration of 8.5459 (duration_dollar_change)
* Dollar change due to convexity of 0.5826 (convexity_dollar_change)
* Bond price is $100 (px)

# Estimate change in price
price_change <- duration_dollar_change + convexity_dollar_change

# Estimate price
price <- px + duration_dollar_change + convexity_dollar_change

# Values
price_change
## [1] 9.128546
price
## [1] 109.1285