Bond Price and Yield

Jaime LopezJaime Lopez
4 min read

In this article, an exploration of bond valuation is presented. I describe the fundamental parameters of a bond. The key question is how to decide to invest in a bond compared to other alternatives. Current price and yield to maturity are used to answer that question. Additionally, I examine the inverse relationship between interest rates and bond prices. A Python's implementation is shown.

Bond parameters

The basic parameters of a bond are: (a) years to maturity (\(Y\)), (b) principal or face value (\(F\)), which is the amount that is returned to the investor at the end of the period; (c) coupon rate (\(c_r\)) or coupon value (\(CF\)) ; and (d) frequency of payments by year (\(f\)). The coupon value is an amount to be paid to the investor every year until maturity. The relation between coupon value and principal is given by the coupon rate: \(c_r = CF / F\).

To represent a bond in Python, we can use a data class. In this example, we are using the coupon rate as an attribute, instead of the coupon value. Besides that, we are assigning a default value for the payment frequency, to 1.

from dataclasses import dataclass

@dataclass
class Bond:
    principal: float
    cuppon_rate: float
    maturity_years: int
    payment_frequency: int = 1

For example, below a bond is defined with a face value of $1,000, a coupon rate of 5%, 3 years to maturity, and two payments by years.

bond = Bond(
    principal=1000,
    cuppon_rate=0.05,
    maturity_years=3,
    payment_frequency=2,
)

Current price

The decision to invest in a bond can be based on the estimated current price, i.e., how much money in current dollars represent the coupon and principal payments. To produce that estimation, we need a rate of discount (\(r\)), that can be an interest rate in case that current money can be invested in another stake, for example a certificate of deposit in a bank.

The formula to obtain a bond's current price is:

$$P = \sum_{t = 1}^{f \times Y} \frac{C/f}{(1 + \frac{r}{f})^t} + \frac{F}{(1+\frac{r}{f})^Y}$$

The first term is the sum of the current value of the coupon payments. The second term is the current value of the principal. If the frequency of payments by year is 1, that formula is reduced to:

$$P = \sum_{t = 1}^Y \frac{C}{(1 + r)^t} + \frac{F}{(1+r)^Y}$$

Below, it is an implementation of the current price formula as a Python function. When that function is used with the example bond defined above, if a rate of 6% is used, the result is $ 972.91. Given that result, if we have to pay $1,000.00 now to acquire that bond, we might be loosing $27.09.

import numpy as np

def bond_price(bond: Bond, r: float) -> float:
    """
    Computes the current price of a bond at the market rate `r`
    """
    periods = bond.maturity_years * bond.payment_frequency
    cuppon = bond.principal * bond.cuppon_rate
    rate_factor = 1 + r / bond.payment_frequency
    accum = np.sum([
        (cuppon / bond.payment_frequency) / rate_factor ** t
        for t in range(1, periods + 1)
    ])
    accum += bond.principal / rate_factor ** periods
    return accum

Yield to maturity

Another alternative to valuate a bond is, when given a current price, asking what internal rate that bond has. That internal rate is named yield to maturity. It is used to compare one bond acquisition to other investments alternatives based on return rates. In this case, for the previous formulas, we know \(P\) and we want to calculate \(r\).

As a Python function, below is an implementation to find the yield to maturity of a bond given a price. An iterative approximation is used based on the Newton's method. Using the bond_yield function with the example bond previously defined, if we use $972,91 as the price, the result will be 6.0%.

def bond_yield(
    bond: Bond,
    price: float,
    max_iter = 100,
    tolerance = 1e-5
) -> float:
    periods = bond.maturity_years * bond.payment_frequency
    cuppon = bond.principal * bond.cuppon_rate
    bond_yield = 0.01
    iterations = 0
    while iterations < max_iter:
        yield_price = bond_price(bond, bond_yield)
        diff = yield_price - price
        if abs(diff) < tolerance:
            break
        bond_yield += diff / price * 0.1
        iterations += 1
    return bond_yield

Interest rate and price

Finally, it is important to understand the relation between the interest rate and the price of a bond. If the interest rate goes up, the price goes down. If the interest rate goes down, the price goes up. The relation is convex and inverse, as it is shown in the below figure. In different words, if the interest rate goes up, instead of putting money in a bond, we will be motivated to invest on another alternative with higher return.

import matplotlib.pyplot as plt

def plot_bond_price(bond: Bond, x: np.ndarray, y: np.ndarray):
    plt.plot(x * 100, y / bond.principal * 100, color="g")
    plt.axhline(y=100, color="r", label="Face value")
    plt.ylabel("Price (%)")
    plt.xlabel("Yield (%)")
    plt.legend()

x = np.linspace(0.01, 1.0, 100)
y = np.vectorize(lambda r: bond_price(bond, r))(x)
plot_bond_price(bond, x, y)

0
Subscribe to my newsletter

Read articles from Jaime Lopez directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jaime Lopez
Jaime Lopez