Bond Pricing With Python: Present Value, Yield to Maturity, and Duration

T

Tobechi

Guest
Imagine a friend asks to borrow $1,000 today and promises to return exactly $1,000 a year from now, without interest. You trust him, but you might still hesitate. The money could be earning interest somewhere else while you wait. And if prices rise during that year, the $1,000 you get back may not buy what it does today. The amount is the same, but its value is not. That is the basic idea behind bond pricing. A bond is a promise of future payments, and to know what that promise is worth today, you have to account for the time between now and when you receive the money. We will build that idea in three steps: the definitions, the mathematics, and finally the code.

A very old idea​


Lending with interest is hardly a modern invention. Clay tablets from Mesopotamia once recorded loans of grain and silver at stated rates of interest, reflecting an understanding that a measure of grain today was worth more than the promise of the same measure after the harvest. The difference had to be accounted for. For centuries, however, charging interest was considered close to sinful. Money was seen as barren, something that could not produce more money on its own, so charging interest could look like getting something for nothing. Medieval Christian and Islamic scholars called it usury. What they earlier failed to realizes is that what was at stake wasn’t the money itself, but the lender giving up the use of their capital, waiting for it to be returned and taking the risk that it might not eventually be. The interest therefore compensated them for that time and risk. The modern bond took the same basic idea and scaled it up. Renaissance city-states like Venice and Florence needed more money than taxes could provide, they borrowed from their citizens and promised regular payments in return. Those promises could then be bought and sold. Amidst all, one question still stood: what are future payments worth today?

The core idea: a dollar later is worth less than a dollar now​


The formal name for this is the time value of money. Suppose your money can reliably earns 5 percent somewhere safe, put $100 away today, and a year later you have 105. Now reverse the question: if someone promises to give you $105 a year from now, how much would you pay for it today? You'd pay $100. At 5 percent, that is all you need today to have $105 a year from now. Paying more would mean giving up money you could have kept or put to work elsewhere. Taking a future payment and working out what it is worth today is called discounting. The rate used to do this is the discount rate, and the resulting amount is the present value. Put simply, present value asks: how much would I need today, at the going rate to end up with a certain amount in the future?

To solve for it, for a payment one year away, you divide by (1 + r), where r is the rate. For a payment two years away, you divide by (1 + r) twice, which is (1 + r) squared. A payment n years away is divided by (1 + r) to the power n. The exponent simply tells us how many years we are discounting the money back, and the further away the payment is, the less it is worth today. A higher rate makes future money worth less today. At 10 percent rather than 5, you need even less than $100 today to arrive at the same $105 in a year. This relationship is what drives the way body prices respond to changes in interest rates. That 5 percent is not an arbitrary number either. It represents the return you could earn elsewhere on money exposed to a similar level of risk. Economists call this the opportunity cost of capital: what you could have earned if you had not tied your money up here. That rate also reflects several things. There is the preference for having money now rather than later. There is expected inflation, because if prices rise, you need more money in the future to maintain the same purchasing power. And there is risk, because the promised payment may not arrive at all. So when you see a single rate quoted, it is really bringing these different pressures together.

What a bond actually is​


A bond is essentially a loan divided into a series of promises. You lend money to a borrower, usually a government or a company, and in return, they agree to make specific payments over time. There are two main types of payment. While the loan is outstanding, the bond pays regular interest, known as coupons, usually once or twice a year. Then, when the bond reaches maturity, the borrower repays the original amount, known as the face value or par value. A ten year bond with annual coupons, for example, would make ten interest payments before returning the original amount at the end. The coupon rate is the interest the bond pays, expressed as a percentage of its face value. A 5 percent coupon on a $1,000 bond pays $50 a year for as long as the bond remains outstanding, regardless of what happens to market rates afterwards. The maturity simply tells you how long you have to wait for the final repayment. Both are fixed when the bond is issued. And because each of these payments arrives in the future, we already know what to do with them: discount them back to today. The price of a bond is the present value of all its future payments added together.

Working the math​


Now let’s price one. Take a bond with a face value of $1,000, an annual coupon rate of 5 percent, and three years until maturity. That 5 percent coupon means it pays $50 each year. If the market discount rate for similar bonds is 6 percent, what should this bond be worth today? It promises $50 at the end of the first year, another $50 at the end of the second, and $1,050 at the end of the third year: the final $50 coupon plus the $1,000 face value. The general formula for the price is:

Formula for a bond price


Here P is the price, C is each coupon, F is the face value, r is the discount rate per period, n is the number of periods. Breaking it down one payment at a time:

The year-one coupon, discounted one year: 50 divided by 1.06, which is 47.17 dollars.

The year-two coupon, discounted two years: 50 divided by 1.06 squared, which is 50 divided by 1.1236, giving 44.50 dollars.

The year-three payment of 1,050 dollars, discounted three years: 1,050 divided by 1.06 cubed, which is 1,050 divided by 1.191016, giving 881.60 dollars.

Add the three together: 47.17 plus 44.50 plus 881.60 comes to 973.27 dollars.

So the bond is worth 973.27 dollars today, less than the 1,000 it pays back at the end. The reason is simple. This bond pays 5 percent, but investors can get 6 percent elsewhere, so no one will pay full price for it. The price has to fall far enough that what you save at purchase makes up for the smaller coupon. That is what a discount bond is: one priced below face value because its coupon is below the market rate. Flip it, if the market rate is 4 percent and the bond still pays 5, the bond is now the better deal, and buyers push its price above 1,000. That is a premium bond. When the market rate and the coupon rate are equal, the price sits exactly at face value. In summary, the price moves opposite to the rate: rate up, price down, and rate down, price up.

A shortcut hiding in the coupons​


Discounting each coupon separately is fine for three years. For a thirty-year bond with semiannual coupons it is sixty payments, and nobody wants to do sixty divisions by hand. There is a faster way, and it comes from the fact that all coupons are equal. Equal payments at regular intervals are called an annuity, and the discounted sum of an annuity folds into one expression:

5PlLwN7nmlgYN9xshp2bPVmCChr1-7u93b9r.jpeg


That fraction is the annuity factor. Whatever the number of periods, it prices the entire coupon stream in a single multiplication. The face value is still discounted on its own as a single payment at the end, so the full price is:

5PlLwN7nmlgYN9xshp2bPVmCChr1-2qa3bcz.png


Check it against our bond. The annuity factor for three years at 6 percent, (1 - 1.06^-3) / 0.06, is 2.673012. Multiply by the 50 dollar coupon gives 133.65. The 1,000 dollar face value discounted three years gives 839.62. Together that is 973.27, the same figure to the cent.

When coupons come twice a year​


Most real bonds pay twice a year, not annually. A bond with a 5 percent annual coupon paid semiannually pays 2.5 percent every six months, so 25 dollars twice a year instead of 50 once. The annual market rate halves too, into a rate per period and the periods double: a ten-year bond has twenty coupon dates. None of that changes the method. You discount every payment and add them up, only now you count in half-years. Take our bond again, 1,000 dollars, 5 percent coupon, three years, 6 percent market rate, but semiannual, it pays 25 dollars every six months for six periods, then 1,000 at the end, each discounted at 3 percent. That comes to 972.91, just under the 973.27 from before. Small here, but real, and on a long bond it grows large enough that a price means nothing unless you know how often it compounds.

Yield to maturity​


So far we have gone from a rate to a price. Real market usually runs the other way. The price is sitting there on the screen; what you want to know is the return you get if you buy at that price and hold to the end. That is the yield to maturity, or YTM. YTM is the rate that makes all the bond's future payments, once discounted, add back up to the price it is trading at. Same equation, flipped: the price is the input, the rate is what you solve for. It catches everything in a one number, the coupon plus whatever you make or lose from buying below or above face value, which is what lets you set two very different bonds side by side and compare them. Buy below face value and your YTM sits above the coupon rate, since you pocket the coupons and the rise back up to par on top. Buy above it and your YTM sits below, because the drift back down to par eats into what you keep. The catch is you cannot just rearrange the formula and read the rate off. It sits inside several powers at once and won't come out. So you search. Guess a rate, price the bond with it, compare against the real price. Too high means your rate was too low, so bump it up. If the rate came out too low, you do the opposite. You keep closing the gap until the two prices meet.

Supporting Code​


Everything above fits in a few lines of Python, using nothing beyond the standard library. Start with the most basic operation, discounting a single future payment back to today.

Code:
def present_value(amount, rate, years):
    """Discount a single future payment back to its value today.

    amount: the future cash payment
    rate: the discount rate per period, as a decimal (0.06 for 6%)
    years: how many periods until the payment arrives
    """
    return amount / (1 + rate) ** years

That one line is the time value of money made executable. Dividing by (1 + rate) ** years is exactly the discounting we did earlier.

Next, price a bond by discounting each payment and summing them. Passing the number of periods per year lets one function handle annual and semiannual bonds.

Code:
def bond_price(face_value, annual_coupon_rate, annual_rate,
               years, periods_per_year=1):
    """Price a bond as the sum of its discounted future payments.

    face_value : the amount repaid at maturity (e.g. 1000)
    annual_coupon_rate : annual coupon as a decimal (0.05 for 5%)
    annual_rate : annual market discount rate as a decimal
    years : number of years to maturity
    periods_per_year : 1 for annual coupons, 2 for semiannual
    """
    n = years * periods_per_year
    coupon = face_value * annual_coupon_rate / periods_per_year
    rate = annual_rate / periods_per_year
    price = 0.0
    for t in range(1, n + 1):
        price += present_value(coupon, rate, t)
    price += present_value(face_value, rate, n)
    return price

Run it against the bond we priced by hand, both ways.

Code:
annual = bond_price(1000, 0.05, 0.06, 3, periods_per_year=1)
semi   = bond_price(1000, 0.05, 0.06, 3, periods_per_year=2)
print(f"Annual coupons: {annual:.2f}") # 973.27
print(f"Semiannual coupons: {semi:.2f}") # 972.91

Both match the hand arithmetic, 973.27 and 972.91. Same arithmetic as before, just without the risk of a slipped decimal.

Now yield to maturity by search. This uses bisection: hold a low guess and a high guess that bracket the true rate, price at the midpoint, and keep whichever half the answer is in. The bracket halves every step.

Code:
def yield_to_maturity(price, face_value, annual_coupon_rate, years,
                      periods_per_year=1, tolerance=1e-8, max_iterations=200):
    """Find the annual discount rate that reprices the bond to `price`."""
    low, high = 0.0, 1.0  # search annual rates between 0% and 100%

    for _ in range(max_iterations):
        guess = (low + high) / 2
        priced = bond_price(face_value, annual_coupon_rate, guess, years, periods_per_year)
        if abs(priced - price) < tolerance:
            return guess
        # Price falls as rate rises. Priced too high means rate too low.
        if priced > price:
            low = guess
        else:
            high = guess
    return guess

Point it at the bond's price and confirm it recovers the rate.

Code:
ytm = yield_to_maturity(973.27, 1000, 0.05, 3, periods_per_year=1)
print(f"Yield to maturity: {ytm:.4%}") # 6.0000%

The search lands back on 6 percent, the rate we used to price the bond originally. Pricing and yield are the same equation solved from opposite ends, and now both directions run in code you can point at any bond you like.

Measuring the see-saw: duration and convexity​


We said price and rate move against each other. Duration is the number that says how much. If rates move, how hard does your bond's price get hit?

The clean way to see it is with a little calculus, one paragraph's worth. Price is a function of yield, and duration is basically the slope of that function, the derivative of price against yield, divided by the price so it lands as a percentage. Read it as sensitivity: a duration of 4 means a one point move in yield moves the price about 4 percent the other way. Longer bonds run higher, since their payments sit further out where discounting bites hardest, so they move more when rates shift. It's why a fund holding thirty-year bonds feels a rate change far more than someone holding a two-year note.

The usual version, modified duration, has a hands-on definition: price the bond a touch above and a touch below its current yield, and measure how far it moved. Scale that, and you have the duration.

Code:
def modified_duration(price, face_value, annual_coupon_rate, years,
                      periods_per_year=1, bump=0.0001):
    """Approximate modified duration by bumping the yield up and down.

    Returns the approximate percent price change for a 1 percentage
    point (100 basis point) change in yield.
    """
    ytm = yield_to_maturity(price, face_value, annual_coupon_rate, years, periods_per_year)
    price_up = bond_price(face_value, annual_coupon_rate, ytm + bump, years, periods_per_year)
    price_down = bond_price(face_value, annual_coupon_rate, ytm - bump, years, periods_per_year)
    # Central difference: the slope of price with respect to yield,
    # divided by price, flipped positive.
    return -(price_up - price_down) / (2 * bump * price)

Run it on our bond.

Code:
dur = modified_duration(973.27, 1000, 0.05, 3, periods_per_year=1)
print(f"Modified duration: {dur:.4f}")   # about 2.6956

A duration near 2.70 says that if the yield jumps from 6 percent to 7 percent, the price should fall by roughly 2.7 percent, and rise by about the same if it drops to 5. You can check it directly: repricing at 7 percent gives a fall of about 2.65 percent, close to the prediction, and the small gap is not noise. It is the next idea.

Duration treats the price-yield relationship as a straight line, but it is really a curve, bowed so that prices rise a little faster than duration predicts when yields fall and drop a little slower when yields rise. That curvature is convexity, the second-order correction. For small moves, duration alone is close enough. For large moves, or careful risk work, convexity catches what the straight line misses, and it leans in the holder's favor: a bond loses a little less and gains a little more than duration alone suggests.

Who actually issues these, and why​


.The mathematics is the same, but bonds exist in a real market, and it helps to understand who is issuing them and why.

Governments are the largest issuers by far. When a government spends more than it collects in taxes, it can raise the difference by selling bonds. When a stable government borrows in a currency it controls, its bonds are generally treated as relatively low risk because the government can create more of that currency to meet its obligations. This is why government bond yields are used as a reference point for much of the wider market. The ten year yield you often hear about, for example, refers to a government bond, and changes in it can influence mortgage rates, corporate borrowing costs and the valuation of other assets. Companies issue bonds for a different reason. Instead of borrowing from a bank or raising money by selling shares, a company can borrow from many investors at once without giving up ownership. But investors are taking on the risk that the company may not be able to repay them, so corporate bonds generally offer higher yields than government bonds. The greater the perceived risk, the higher the return investors will demand. The bond market is also much larger than it may appear. The global market holds around $160 trillion in debt, roughly comparable in size to the global stock market. Pension funds and insurers are major bond investors because their own obligations tend to come due on a schedule, making predictable bond payments useful for meeting them. Central banks also influence bond markets through their monetary policy and, in some cases, by buying and selling government bonds. By now, the movement in bond prices should make more sense. A bond's price is based on its future payments and the rate used to discount them. When that rate changes, the price changes too. An inflation reading can shift expectations about interest rates. A central bank announcement can change what investors expect to earn. A change in a company's outlook can alter how much risk investors think they are taking. Each of these can affect the rate used to price a bond, and therefore its value.

Where this leaves you​


At this point, you can look at bonds in both directions: from a rate to a price, or from a price back to a rate. You can price annual and semiannual bonds, use the annuity factor to simplify a long stream of coupon payments, and use duration to estimate how much a bond's price may move when rates change. There is more to bond pricing, but it builds on the same ideas. In practice, the rate we have used throughout is connected to rates across different maturities, which together form the yield curve. Default risk can be measured separately. Some bonds can be called before maturity, which changes the calculations, while inflation linked bonds account for changes in purchasing power. But the foundation remains the same. A bond is a series of future payments, and each payment has to be valued according to when you will receive it. Once you understand that, the rest is largely a matter of building on the same idea.

5PlLwN7nmlgYN9xshp2bPVmCChr1-4db3bbu.gif.webp
 

Thread statistics

Created
Tobechi,
Replies
0
Views
0
Back
Top