A stream of cash as an integral
Say money arrives at a rate of dollars per year at time , measured in years. In a short time , about dollars arrive. Discounted continuously at the rate (case study 1.4.6), they are worth today.
Adding these pieces over is a Riemann sum (lesson 2.5.1). As , it becomes an integral.
Rule: Present value of a continuous stream
is the discounted rate: what the cash arriving at time is worth today, per year. The present value is the area under it.
For a constant rate , an antiderivative of is : divide by , as in lesson 2.5.2. The fundamental theorem (lesson 2.5.3) then gives a closed form.
For the toll road, , and , so = $78,693.87. Undiscounted, the same 10 years pay $100,000.
Units on a desk
The formula needs one unit of time throughout: in dollars per year, per year and in years. Then is a pure number, as an exponent must be.
- Convert cash flows first. $850 a month is dollars a year.
- Rates enter as decimals. Typing for 5% gives $2,000, nonsense for ten years of $10,000.
- Time is in years. Typing months with a yearly rate gives $199,504.25, almost the value of tolls forever.
Try it
The closed form and SciPy's quad agree on $78,693.87. Then the two unit mistakes above: a rate typed as 5, and the term typed in months.
Continuous against yearly payments
At the same discount rate, a schedule of separate payments is a Riemann sum of the discounted rate. So lesson 2.5.1 says which way it errs.
Ten payments of $10,000 at the year-ends are worth today. That is the right sum of with . The discounted rate falls, so the right sum is too small: $76,742.92, short by $1,950.95.
The area under the discounted rate is the present value of continuous tolls, $78,694. Ten year-end payments are right-end rectangles under the falling curve, so they come to less: $76,743.
Payments at the start of each year form the left sum, which is too large: $80,677.61. The two schedules differ by , or $3,934.69: the gap rule of lesson 2.5.1, with the discounted rate.
| payments | Riemann sum | value today |
|---|---|---|
| at year-ends | right, | $76,742.92 |
| at month-ends | right, | $78,530.04 |
| at mid-year | midpoint, | $78,685.67 |
| continuously | the integral | $78,693.87 |
| at year-starts | left, | $80,677.61 |
Monthly payments close most of the gap, and mid-year payments nearly all of it, because midpoint sums are accurate. That is why valuations often treat a year's cash as arriving at mid-year.
Same rate, same convention
A comparison is fair only when both sides use the same rate. A rate of 5% compounded once a year is not a continuous 5%.
Compounded yearly, 5% discounts year by : a continuous rate of (case study 1.4.6). Valued that way, the year-end payments are worth $77,217.35, not $76,742.92. Convert both sides to one convention before you compare them.
Try it
Each schedule is a sum of discounted payments, all at a continuous 5%. Each line prints its value and its gap from the integral.
The last line discounts the year-end payments with instead: a different rate, so a different value.
A growing stream
If the tolls grow at a continuous rate , then , where is the rate now. The discounted rate becomes , and the same antiderivative, with in place of , gives the present value.
Rule: Growing stream
Growth acts like a lower discount rate, . With and , the toll road is worth $90,634.62.
When the formula breaks
- . The formula reads . But then the discounted rate is the constant , so the present value is : $100,000 here.
- almost equal to . Rates often come out of other sums. is stored as 0.04999999999999999, so a test
g == ragainst fails. The formula then divides two tiny numbers, loses its digits, and it returns $80,000 instead of $100,000. - . If , then and : the growing perpetuity behind dividend-discount models. If , the area grows without limit and there is no finite value; lesson 2.6.3 treats such integrals.
math.expm1(x) computes accurately even for tiny . Writing the top of the fraction as repairs the second case.
The perpetuity also shows how sensitive values are when is close to . At it is $500,000 with , $1,000,000 with 4% and $2,000,000 with 4.5%. A half-point change in a growth guess can double the answer.
Try it
The closed form, quad and a midpoint sum with only 100 strips agree to within 1 cent for 3% growth.
Then the float trap: the plain formula against the expm1 version, with . Last, the perpetuity for three growth rates.
Default intensity
Credit desks model a borrower's default with a hazard rate (lambda), also called a default intensity. It is a rate per year: over a short time , a borrower who has not yet defaulted defaults with probability about .
With a constant , the probability of surviving to time is . The default time then has density : a rate of probability, per year. Integrating that rate over the years gives a total probability (lesson 2.5.3):
At a year, that is 9.52% within 5 years and 18.13% within 10. The shortcut gives 10% and 20%: close while is small, worse as it grows. Level 5 develops densities properly.
A stream that stops at default
Suppose the tolls stop for good if the operator defaults, with nothing recovered. Cash at time arrives only if there has been no default by then, which has probability . So each moment's discounted cash is weighted by it.
Rule: Stream with a hazard rate
The hazard rate adds to the discount rate. That is why a riskier borrower's cash is discounted at a higher rate: the extra acts as a credit spread.
At , the toll road's 10 years are worth $71,916.39, which is $6,777.48 less than without default risk.
Try it
The default probabilities from the closed form, and from quad of the density for 5 years. Then the risky stream, by formula and by quad.
Present value of a continuous stream
Write two functions.
pv_stream(c, r, T, g=0.0, hazard=0.0)returns the present value of income at rate per year for years. It discounts at the continuous raterand stops at a default with hazard ratehazard. Use the closed form with the net rate , written withmath.expm1so that a tiny keeps its digits. When the answer is . RaiseValueErrorifTorhazardis negative.default_probability(hazard, T)returns .
All rates are per year, as decimals, and T is in years.
See the tests
import math
from scipy.integrate import quad
def test_constant_stream():
"""$10,000 a year for 10 years at 5% is worth about $78,693.87"""
expect_close(pv_stream(10_000, 0.05, 10), 78693.86805747332, what="pv_stream(10000, 0.05, 10)")
def test_growing_stream():
"""Growing at 3%, it is worth about $90,634.62"""
expect_close(pv_stream(10_000, 0.05, 10, g=0.03), 90634.62346100906, what="pv_stream(..., g=0.03)")
def test_risky_stream():
"""With a 2% hazard rate, it is worth about $71,916.39"""
expect_close(pv_stream(10_000, 0.05, 10, hazard=0.02), 71916.38517265576, what="pv_stream(..., hazard=0.02)")
def test_matches_quad():
"""It matches numerical integration for a growing, risky stream"""
expected = quad(lambda t: 2500 * math.exp(0.01 * t) * math.exp(-0.07 * t) * math.exp(-0.015 * t), 0, 25)[0]
expect_close(pv_stream(2500, 0.07, 25, g=0.01, hazard=0.015), expected, what="pv_stream(2500, 0.07, 25, g=0.01, hazard=0.015)")
def test_growth_equals_rate():
"""When g equals r the answer is c × T"""
expect_close(pv_stream(1000, 0.04, 8, g=0.04), 8000.0, what="pv_stream(1000, 0.04, 8, g=0.04)")
def test_growth_almost_equals_rate():
"""g = 0.15 − 0.1 is stored as 0.04999999999999999; at r = 0.05 the answer is still c × T"""
expect_close(pv_stream(10_000, 0.05, 10, g=0.15 - 0.1), 100_000.0, what="pv_stream(10000, 0.05, 10, g=0.15 - 0.1)", rel=1e-9)
def test_bad_inputs():
"""A negative term or a negative hazard rate raises ValueError"""
for args in [dict(T=-1), dict(T=10, hazard=-0.01)]:
try:
pv_stream(10_000, 0.05, **args)
except ValueError:
continue
raise AssertionError(f"expected ValueError for {args}")
def test_default_probability():
"""A 2% hazard rate gives a 9.52% chance of default within 5 years"""
expect_close(default_probability(0.02, 5), 0.09516258196404048, what="default_probability(0.02, 5)")
Summary card
- A continuous cash flow at rate dollars per year is worth today: the area under the discounted rate.
- Constant rate: , and forever. Keep , and in per-year units.
- Discrete payments are Riemann sums of the discounted rate: year-end payments are too small, year-start ones too large, mid-year ones close. Compare values only at the same rate convention.
- Growing at : , which is when ; use
math.expm1when is tiny. Forever, if . - A hazard rate gives , and adds to the discount rate of a stream that stops at default.