The internal rate of return is a root
Case study 0.2.4 wrote the net present value at a yearly rate as a sum over the cash flows . Each arrives at the end of year , with today, and money paid out counts as negative:
The internal rate of return (IRR) is the rate that makes this zero. It is the yearly return the project itself earns, quoted in percent per year. A project is worth doing when its IRR beats the rate that money costs you.
To bracket it, look for a sign change (lesson 1.3.2). At nothing is discounted, so the machine's NPV is . At , or 100% a year, the later savings are worth little: the NPV is , so a root lies in .
The guarantee of lesson 1.3.2 also needs a continuous function. The NPV is continuous for every rate above ; at the bottom is 0, and the formula divides by zero.
The machine's NPV falls as the rate rises. It crosses zero once, at the IRR of 7.71%: below that rate the machine is worth buying, above it not.
Try it
The cell finds the root twice: with the bisection function of lesson 1.3.2 and with SciPy's brentq.
Both give 0.07714, or 7.71%. Bisection needs 27 halvings to reach .
Several sign changes, several IRRs
Write , the value today of $1 paid in a year. Then , and the NPV becomes a polynomial in (lesson 1.3.1), with the cash flows as its coefficients:
A rate above means a positive , so each IRR is a positive root of this polynomial. Its degree is , so a project lasting years has at most IRRs (lesson 1.3.1).
A sharper count is Descartes' rule of signs. A polynomial has at most as many positive roots as its list of coefficients has sign changes.
The machine's cash flows, , change sign once, so it has at most one IRR. So does any project that pays out first and earns afterwards.
A mine with two IRRs
A mining project costs $100 million to open. The ore brings in $260 million in year 1, and closing the mine and restoring the land costs $165 million in year 2. The cash flows change sign twice, and the NPV is zero at both 10% and 50%.
Cash flows that change sign twice give two IRRs. The NPV is positive only between them, and it is negative at both ends of the bracket from 0% to 100%.
So the decision flips twice as the cost of money rises. At 5% a year the mine loses $2.04 million, at 25% it gains $2.4 million, and at 60% it loses $1.95 million. Cheap money makes the final clean-up cost count almost in full.
Neither IRR on its own says whether to go ahead. The NPV at your own cost of money does, so desks report it alongside any IRR.
Try it
np.roots finds every root of the polynomial in . NumPy wants the highest power first, so the cell reverses the cash flows with cf[::-1].
For the mine, the roots and give : 10% and 50%. The machine's polynomial has degree 4, but only one positive real root, so one IRR.
Bad brackets, and no IRR at all
A root-finder returns one root from one bracket, or a refusal. These are the traps a desk meets.
- Equal signs hide two IRRs. For the mine, the NPV is at 0% and at 100%.
brentqon refuses, as if there were no IRR, yet there are two (lesson 1.3.2). - Too narrow a bracket. The machine on has NPV 200 and 63.79, both positive, so
brentqrefuses although the IRR is just outside. - Negative rates. A project that never earns back its cost has a negative IRR: gives . A search that starts at 0 misses it, and one that starts at divides by zero. Start at .
- No IRR. When every cash flow has the same sign, the NPV is never zero. Two sign changes do not promise a root either: has none.
- A guess picks the IRR. Spreadsheet IRR functions start from a guess, often 10%, and step towards a root, as Newton's method does (lesson 1.3.3). With two IRRs, the guess decides which one you get.
For , the NPV in is . Its discriminant is , negative, so it has no real root (lesson 1.2.2). The NPV is negative at every rate, and at best , at 50%.
Try it
SciPy's newton without a slope (lesson 1.3.3) shows the guess at work on the mine. From 5% it finds 10%; from 30% it finds 50%.
The tiny differences from 0.1 and 0.5 in the last digits are rounding.
The fix: scan, then bracket
The safe method scans first. Work out the NPV on a grid of rates, from to 1,000% in steps of 1%. Then hand each pair of neighbours with a sign change to brentq.
A grid rate where the NPV is exactly 0 is an IRR itself, so keep it too. A step of 1% can still miss two IRRs less than 1% apart, because the NPV may not change sign between neighbours. So a careful desk also looks at the NPV curve.
Try it
all_irrs scans 1,100 rates and returns every IRR it brackets. It finds the machine's one IRR, both of the mine's, the negative IRR, and nothing for .
A bond's yield is an IRR too
A bond pays a fixed coupon at the end of every year and repays its face value after years. Its price at a yearly yield discounts each payment and adds them, as NPV does:
Take a 5-year bond with a $4 coupon on a face value of $100. At its price is exactly 100: a bond whose yield equals its coupon rate is priced at face value. Raise the yield and the price falls.
| yield | 2% | 3% | 4% | 5% | 6% |
|---|---|---|---|---|---|
| price | 109.43 | 104.58 | 100.00 | 95.67 | 91.58 |
Markets quote the price, and traders want the yield. The yield to maturity is the that solves . It is the IRR of buying the bond: pay the price today, then receive the coupons and the face value.
At a price of 96 those cash flows are . They change sign once, so by the rule of signs there is at most one yield. The price always falls as the yield rises, so each price has exactly one: the inverse function of lesson 1.1.3.
Try it
Subtract the market price and find the root with brentq. At 96 the yield is about 4.92%, and at 104 about 3.12%: a bond priced above face value yields less than its coupon rate.
At 125, more than the $120 the bond will ever pay, the yield is negative, about . The bracket must start below 0 to find it.
Three conventions trip people up with bonds.
- Decimals, not percent. In code, 4% is 0.04.
price(4)gives 1.03, a nonsense price that a quick look catches. - Negative yields are real. Some government bonds in Europe and Japan traded above all their future payments in the late 2010s. Search from , as for IRR.
- Coupon frequency. Many bonds pay coupons twice a year, and quote a yield per half-year, doubled. This case study uses yearly coupons throughout.
Every internal rate of return
Write irrs(cashflows) that returns a list of every rate between and 10 with , smallest first. cashflows[0] happens today.
- Work out the NPV at rates from to 10 in steps of 0.01. Wherever it changes sign between two neighbouring rates, find the root between them with
brentqor your own bisection. - A grid rate where the NPV is exactly 0 is an IRR too: keep it.
- Return an empty list when there is no IRR.
The tests include the machine, the mine with two IRRs, a negative IRR, and two sets of cash flows with no IRR.
See the tests
def _npv(r, cf):
return sum(c / (1 + r) ** t for t, c in enumerate(cf))
def _check(found, expected, what):
assert isinstance(found, list), f"{what}: expected a list, got {found!r}"
assert len(found) == len(expected), f"{what}: expected {len(expected)} IRRs, {expected}, got {len(found)}: {found}"
for got, want in zip(found, expected):
expect_close(got, want, what=what, rel=1e-6, abs_tol=1e-9)
def test_machine():
"""The machine (−1000, then 300 for four years) has one IRR, about 7.71%"""
_check(irrs([-1000, 300, 300, 300, 300]), [0.0771384729520836], "irrs(machine)")
def test_npv_is_zero():
"""At each IRR you return, the NPV is zero: −500, 200, 200, 200"""
cf = [-500, 200, 200, 200]
found = irrs(cf)
assert len(found) == 1, f"expected one IRR, got {found}"
expect_close(_npv(found[0], cf), 0.0, what="NPV at your IRR", abs_tol=1e-6)
def test_one_period():
"""Pay 100 today, get 110 in a year: the only IRR is 10%"""
_check(irrs([-100, 110]), [0.10], "irrs([-100, 110])")
def test_two_irrs():
"""The mine (−100, +260, −165) changes sign twice: its IRRs are 10% and 50%, smallest first"""
_check(irrs([-100, 260, -165]), [0.10, 0.50], "irrs(mine)")
def test_two_irrs_off_grid():
"""−100, +245, −148 has two IRRs, about 8.14% and 36.86%"""
_check(irrs([-100, 245, -148]), [0.08138593383654945, 0.3686140661634508], "irrs([-100, 245, -148])")
def test_negative_irr():
"""−1000, then 400 twice never earns its cost back: the IRR is negative"""
_check(irrs([-1000, 400, 400]), [-0.1366750419291627], "irrs([-1000, 400, 400])")
def test_all_positive():
"""Cash flows that are all positive have no IRR: return []"""
_check(irrs([100, 50, 50]), [], "irrs([100, 50, 50])")
def test_no_real_root():
"""−100, +200, −150 changes sign twice but its NPV is negative at every rate: return []"""
_check(irrs([-100, 200, -150]), [], "irrs([-100, 200, -150])")
Yield to maturity
Write ytm(price, coupon, face, years) for a bond paying coupon once a year and face at the end. Return the yield that makes the bond's price equal price.
The tests include bonds priced at, above and below face value, and a bond with a negative yield. One is a zero-coupon bond, which pays only the face value at the end.
See the tests
def test_at_face_value():
"""A 4% bond priced at its face value of 100 yields 4%"""
expect_close(ytm(100, 4, 100, 5), 0.04, what="ytm(100, 4, 100, 5)", rel=1e-6)
def test_below_face():
"""Priced at 96, the 5-year 4% bond yields about 4.92%"""
expect_close(ytm(96, 4, 100, 5), 0.04921905611384293, what="ytm(96, 4, 100, 5)", rel=1e-6)
def test_above_face():
"""Priced at 104, it yields about 3.12%, below its coupon rate"""
expect_close(ytm(104, 4, 100, 5), 0.03123499524165618, what="ytm(104, 4, 100, 5)", rel=1e-6)
def test_ten_years():
"""A 10-year 5% bond priced at 97 yields about 5.40%"""
expect_close(ytm(97, 5, 100, 10), 0.05396020680608085, what="ytm(97, 5, 100, 10)", rel=1e-6)
def test_zero_coupon():
"""A zero-coupon bond priced at 90, paying 100 in 3 years, yields (100/90)^(1/3) − 1"""
expect_close(ytm(90, 0, 100, 3), (100 / 90) ** (1 / 3) - 1, what="ytm(90, 0, 100, 3)", rel=1e-6)
def test_negative_yield():
"""Priced at 125, above the 120 it will ever pay, the 5-year 4% bond has a negative yield, about −0.87%"""
expect_close(ytm(125, 4, 100, 5), -0.008702265943250905, what="ytm(125, 4, 100, 5)", rel=1e-6)
Summary card
- IRR: the rate that makes . Bracket it with a sign change, then use bisection or
brentq. - With the NPV is a polynomial in . Its number of IRRs is at most the number of sign changes in the cash flows.
- Two sign changes can give two IRRs, or none. Then the IRR rule fails: judge the project by its NPV at your cost of money.
- Scan a grid of rates from and bracket every sign change; one bracket or one guess finds at most one IRR.
- Bond price: , falling as the yield rises. The yield to maturity is the IRR of buying the bond; it can be negative.