Bond Price and Yield

Table of Contents

In these notes we'll discuss calculating a bond's price given its yield, and also conversely calculating its yield given its price. Before we do, however, it is important to understand why we do these calculations and what they mean. First consider:

We quote a bond by its yield and not its price.

Why? For students who have more experience with stock this may not make sense at first. However, consider that there is one traded stock for each company, but that there may be many different bonds which qualify as 10-year treasury notes. Each of these notes may have a different price, but they all have the same yield. So while there is no ambiguity in quoting the price of a stock, if you quote the price of one of the notes it is ambiguous—quoting the yield is unique however.

To understand why there may be many prices for treasury notes, consider that a 30 year treasury bond issued 20 years ago is now a 10-year treasury note. Because yields were much higher 20 years ago, this present 10-year note will have a price far above par value ($1000). Similarly, a 20-year bond issued 10 years ago is now a 10 year bond (well the 20 year was last issued in 1986), and it will also have a price above par given declining interest rates. Lastly, a 10 year treasury note sold today will sell for par.

So here we have three separate notes each with different prices but the same yield. This is why quoting the price is not meaningful for bonds. That said, given the yield we have to calculate the price if we would like to buy the bond—bond orders are by price. Hence, here we calculation the price given the yield and vice versa.

We are not valuing a bond here however. Bond valuation means figuring out how much a bond is worth, and can be done using, for example, zero coupon bonds (for treasury bonds). What we are doing is being told the yield, which with the bond's features (maturity, coupon, etc), uniquely determines the price. You can think of this a bond price/yield translation.

1 Calculating Bond Price Given YTM

The only thing we have to take care doing is setting up the cash flow per period and the rate per period. Once we understand these, calculation of the bond value is a simply application of the time-value-of-money formulas we learned earlier.

\[Bond\ Value = Present\ Value\ of\ the\ Coupon\ Payments + Present\ Value\ of\ the\ Par\ Value\]

Letting \(C\), \(r\), \(par\), and \(n\) denote the coupon payment per period, rate per period, par value, and number of periods respectively:

\[Bond\ Value = C \frac{\left(1 - \frac{1}{(1 + r)^n}\right)}{r} + \frac{par}{(1 + r)^n}\]

which you may notice is just the present value of an annuity plus the present value of the bond's par value.

Importantly, note I have written the coupon and rate per period.

Coupon:

  • If the bond has an annual period, then the coupon payment per period is just the coupon rate times the par value ($1000).
  • If the bond has a semiannual period, then the coupon payment per period is the product of the coupon rate times and the par value divided by 2, i.e. \(\frac{coupon\ rate * par}{2}\).

Rate:

  • If the bond has an annual period, then the rate per period is just the yield-to-maturity.
  • If the bond has a semiannual period, then the rate per period is the yield-to-maturity divided by 2.

Number of Periods:

  • If the bond has an annual period, then the number of periods is the number of years.
  • If the bond has a semiannual period, then the number of periods is the number of years times 2.
Period Type Coupon per Period Rate per Period Number of Periods
Annual \(coupon\ rate * par\) \(YTM\) \(number\ of\ years\)
Semiannual \(\frac{coupon\ rate * par}{2}\) \(\frac{YTM}{2}\) \(number\ of\ years * 2\)

where \(YTM\) denotes the yield-to-maturity.

1.1 Annual Payments

The source code below is in Rust, which is a new systems programming language with a seemingly clairvoyant compiler.

fn main() {
    bond_value_annual_payments(0.07, 1000.0, 10.0, 0.05); //ytm, par, years, coupon
}

fn bond_value_annual_payments(ytm: f32, par: f32, years: f32, coupon: f32) {

    let bv: f32 = par * coupon * (1.0 - 1.0 / (1.0 + ytm).powf(years)) / ytm + par / (1.0 + ytm).powf(years);
    println!("The bond's value is: ${:.2}", bv);
}

The bond's value is: $859.53

1.2 Semiannual Payments

The source code below is written in good old C. The bond has the same \(YTM\), par value, years, and coupon rate as the above calculation. The only difference is this bond makes semiannual payments. Note the calculation of the coupon and rate per period, and the number of periods to adjust for the semiannual period.

int
main(){

double ytm = 0.07;
double par = 1000.0;
double years = 10.0;
double coupon = 0.05;

double num_periods = years * 2.0;
double rate_per_period = ytm / 2.0;
double coupon_pmt_per_period = (coupon / 2.0) * par;

double bv = coupon_pmt_per_period * (1 - 1 / pow(1 + rate_per_period, num_periods)) / rate_per_period + par / pow(1 + rate_per_period, num_periods);

printf("The bond's value is $%.2f\n", bv);
return 0;

}

The bond's value is $857.88

1.3 The effect of semiannual compounding

Note the above two bonds have the same coupon, time to maturity, and yield. The only difference is the latter has semiannual compounding. So can you explain why the second bond has a lower price?

2 Calculating the YTM Given the Price

2.1 Annual Payments

from scipy import optimize

coupon_pmt = 1000 * 0.05
years = 10
bv = 859.53  ## this is our bond value above (annual payments)

def eq(YTM):
    return(coupon_pmt * (1 - 1 / pow(1 + YTM, years)) / YTM + 1000 / pow(1 + YTM, years) - bv)

root = optimize.newton(eq, 0.1) ## 0.1 is our initial guess
## note, there are not multiple roots here, but if there were would this root finder list them????

print("The YTM is: ", round(root * 100, 2), "%")
The YTM is:  7.0 %

2.2 Semiannual payments

par <- 1000.0
years <- 10.0
coupon <- 0.05
bv <- 857.88

num_periods <- years * 2.0
coupon_pmt_per_period <- (coupon / 2.0) * par

eq <- function(ytm){
    (coupon_pmt_per_period * (1 - 1 /(1 + ytm / 2)^num_periods) / (ytm / 2) + par / (1 + ytm / 2)^num_periods - bv)^2
}

print(paste0("The YTM is: ", round(optim(0.1, eq)$par * 100, 2), "%"))
[1] "The YTM is: 7%"

3 Interest Rate Risk

We have seen above that the bond's price changes as its yield changes. Further a lower yield increases the bond's price, and conversely a higher yield decreases the bond's price. We can see a the above bond (semiannual period) plotted on YTM in the chart below:

par <- 1000.0
years <- 10.0
coupon <- 0.05
num_periods <- years * 2.0
coupon_pmt_per_period <- (coupon / 2.0) * par

bv <- function(ytm){
    coupon_pmt_per_period * (1 - 1 /(1 + ytm / 2)^num_periods) / (ytm / 2) + par / (1 + ytm / 2)^num_periods
}

library(highcharter)
library(htmlwidgets)
d <- data.frame(cbind(seq(0.01, 0.3, by = 0.01)), round(bv(seq(0.01, 0.3, by = 0.01)), 2))
names(d) <- c("YTM", "Price")
hc <- hchart(d, "line", hcaes(x = YTM, y = Price, value = Price), name = "Bond Price ($)") %>% hc_title(text="Bond Price and Yield")
saveWidget(hc_add_theme(hc, hc_theme_economist()), "bond_plot.html")

So you can see bond's can have a great deal of interest rate risk. This is the risk you buy the bond for $1000 at a 5% YTM and the YTM on the bond increases to 20%, where the bond is worth $361.48 (if years have passes the bond would have a different price, because the above bond is calculated with 10 years to maturity).

4 Reading Ease Score


"Flesch-Kincaid reading ease score: 102.67 Very easy (5th grade)"


"Flesch-Kincaid grade level score: 2.14"

Author: Matt Brigida, Ph.D.

Created: 2021-01-25 Mon 15:03

Validate