Time Value of Money and Interest Rates › Valuing Cash Flow Streams

Michael BrenndoerferUpdated February 6, 202616 min read
Quantitative FinanceAll chapters
Part 1 of 15Mathematical Foundations
Chapter 1Time Value of Money and Interest Rates › Valuing Cash Flow Streams
Reading section 5 of 9 · Valuing Cash Flow Streams · 16 minSection 5 of 9 · 16 min

Book contents

Section 5 of 9 · 16 min read

Time Value of Money and Interest Rates › Valuing Cash Flow StreamsOne section · Sections in this chapter5 / 9 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Financial instruments and investments can involve multiple cash flows over time:

  • A fixed-coupon bond pays periodic coupons.
  • An amortizing loan may require monthly payments.
  • A business valuation can use forecast operating cash flows, which are not the same as accounting profits.

The time value of money framework values a stream by treating each cash flow separately and summing the results.

Net Present Value

The Net Present Value (NPV) is the sum of all discounted cash flows. It takes each future cash flow, discounts it back to today using the appropriate discount factor, and adds up all these present values. This single number captures the total value of an entire cash flow stream in today's dollars:

NPV=∑t=0TCFt(1+r)tNPV = \sum_{t=0}^{T} \frac{CF_t}{(1+r)^t}

where:

  • NPVNPV: net present value (total value in today's dollars)
  • CFtCF_t: cash flow occurring at time tt (negative for outflows, positive for inflows)
  • rr: discount rate (required rate of return)
  • TT: final period of the cash flow stream
  • (1+r)−t(1+r)^{-t}: discount factor for period tt
  • ∑t=0T\sum_{t=0}^{T}: summation over all periods from 0 (today) to TT (final period)

Here the periods are years and rr is a constant effective annual rate with r>−1r>-1. The code also accepts fractional times measured in years. Each cash-flow amount must have a corresponding time. Both inputs must be one-dimensional arrays with the same shape; the function rejects mismatches rather than allowing NumPy to broadcast them. At r=0r=0, discount factors are one and NPV is simply the signed sum of the cash flows.

A positive NPV indicates the investment creates value; a negative NPV indicates it destroys value. NPV answers the question of net benefit in today's dollars by converting all future cash flows to a common time reference. The discount rate rr represents your opportunity cost, meaning what you could earn on alternative investments of similar risk. By using this rate to discount future cash flows, you are implicitly comparing the investment to your next best alternative.

The NPV framework accounts for when each cash flow occurs. At a positive discount rate, an equal positive inflow has a higher present value when received earlier. An equal negative outflow has a more negative present value when paid earlier: its present cost is larger. Discount factors capture both effects, so the sign matters as well as the timing.

Net Present Value

Net present value is the sum of all cash flows discounted to the present. It measures the total value created (or destroyed) by an investment in today's dollars.

In[22]:
Code
import numpy as np


def npv(cash_flows, times, r):
    """
    Calculate net present value of cash flows.

    Parameters:
    - cash_flows: array of cash flow amounts
    - times: array of times when cash flows occur
    - r: discount rate (annual)
    """
    cash_flows = np.asarray(cash_flows, dtype=float)
    times = np.asarray(times, dtype=float)
    if cash_flows.ndim != 1 or times.ndim != 1 or cash_flows.shape != times.shape:
        raise ValueError("cash_flows and times must be paired one-dimensional arrays")
    discount_factors = (1.0 + r) ** (-times)
    return np.sum(cash_flows * discount_factors)


# Investment example: Pay $10,000 now, receive $3,000/year for 5 years
cf = [-10000, 3000, 3000, 3000, 3000, 3000]
times = [0, 1, 2, 3, 4, 5]
r = 0.08

investment_npv = npv(cf, times, r)
Out[23]:
Console
Investment Analysis
========================================
Initial investment: $10,000
Annual cash flows: $3,000 for 5 years
Discount rate: 8%

NPV: $1,978.13
Decision: Accept

The NPV is positive, indicating the investment generates value at an 8% discount rate. However, NPV is highly sensitive to the discount rate chosen. Let's examine this sensitivity:

Out[24]:
Visualization
Line chart showing NPV declining from about $4,560 at a 1% discount rate to negative values above approximately 15.2%.
NPV sensitivity to discount rate for an investment of $10,000 returning $3,000 annually for 5 years. The NPV declines as the discount rate increases, crossing zero at approximately 15% (the internal rate of return).

The discount rate where NPV equals zero is called the Internal Rate of Return (IRR). For this investment, the IRR is approximately 15.2%, meaning the investment breaks even at that discount rate.

Annuities

An annuity is a series of equal payments made at regular intervals. Fixed-rate mortgages, amortizing car loans, and fixed-payment annuity products can be modeled as annuities. The defining characteristic is regularity: the same amount paid at the same interval for a fixed number of periods. This structure is common enough in finance to merit its own specialized formulas.

Rather than discounting each payment individually and summing, which would require TT separate calculations, we can derive a closed-form expression that computes the entire sum at once. The present value of an ordinary annuity (payments at end of each period) is:

PV=C⋅1−(1+r)−TrPV = C \cdot \frac{1 - (1+r)^{-T}}{r}

where:

  • PVPV: present value of the annuity
  • CC: periodic payment amount
  • rr: periodic interest rate (e.g., monthly rate for monthly payments)
  • TT: total number of payment periods
  • 1−(1+r)−Tr\frac{1 - (1+r)^{-T}}{r}: the annuity factor (present value of $1 per period for TT periods)

Here TT is a nonnegative integer count of payments and r>−1r>-1. Solving for the payment needed to repay a loan requires T>0T>0; an empty payment schedule cannot repay a positive principal. The zero-rate case is handled separately below.

The annuity formula comes from summing a geometric series of discount factors. The series 1/(1+r)+1/(1+r)2+…+1/(1+r)T1/(1+r) + 1/(1+r)^2 + \ldots + 1/(1+r)^T forms a geometric progression with first term 1/(1+r)1/(1+r) and common ratio 1/(1+r)1/(1+r). Applying the geometric series formula and simplifying yields the expression above. Rather than computing C/(1+r)+C/(1+r)2+…+C/(1+r)TC/(1+r) + C/(1+r)^2 + \ldots + C/(1+r)^T individually, the closed-form solution captures all terms at once.

For r>0r>0, the formula approaches C/rC/r (a perpetuity) as TT increases because (1+r)−T(1+r)^{-T} shrinks toward zero. If r=0r=0, the present value is instead CTCT; for −1<r<0-1<r<0 and positive payments, it grows without a finite perpetuity limit. The intuition that very distant payments contribute negligibly therefore depends on a positive discount rate.

Annuity

An annuity is a series of equal cash flows occurring at regular intervals. The annuity formula allows direct calculation of present or future value without summing each individual payment.

The term 1−(1+r)−Tr\frac{1 - (1+r)^{-T}}{r} is called the annuity factor. It represents the present value of receiving $1 per period for TT periods. This factor depends only on the interest rate and number of periods, not on the payment amount, making it a useful building block. Once you know the annuity factor, you simply multiply by the payment amount to get the present value of any annuity with those characteristics.

In[25]:
Code
def annuity_pv(payment, r, periods):
    """Present value of ordinary annuity."""
    if r == 0:
        return payment * periods
    annuity_factor = -np.expm1(-periods * np.log1p(r)) / r
    return payment * annuity_factor


def annuity_payment(pv, r, periods):
    """Calculate payment for a positive integer count of periods and r > -1."""
    if periods <= 0 or int(periods) != periods:
        raise ValueError("periods must be a positive integer")
    if r <= -1:
        raise ValueError("r must exceed -1")
    if r == 0:
        return pv / periods
    annuity_factor = -np.expm1(-periods * np.log1p(r)) / r
    return pv / annuity_factor


# Mortgage example: $300,000 loan, 6% annual rate, 30 years
loan_amount = 300000
annual_rate = 0.06
years = 30
monthly_rate = annual_rate / 12
months = years * 12

monthly_payment = annuity_payment(loan_amount, monthly_rate, months)
total_paid = monthly_payment * months
total_interest = total_paid - loan_amount
mortgage_years = 30  # Store for output display
Out[26]:
Console
Mortgage Analysis
========================================
Loan amount:      $  300,000.00
Annual rate:             6.00%
Term:                       30 years

Monthly payment:  $    1,798.65
Total payments:   $  647,514.57
Total interest:   $  347,514.57

Interest/Principal ratio: 1.16x

Over 30 years, this borrower pays about 2.16 times the original principal in total: roughly $647,515 of payments on a $300,000 loan. Total interest is about 116% of the original principal. Most of the early payments go toward interest, with principal reduction accelerating over time. This amortization structure has practical implications for refinancing decisions.

Out[27]:
Visualization
Stacked area chart showing interest declining and principal increasing over 30 years of mortgage payments.
Mortgage amortization over 30 years showing annual totals of the interest and principal components of twelve monthly payments. Interest (blue) dominates early annual totals, while principal repayment (orange) increases in later years. The first annual total with more principal than interest occurs in year 19.

Perpetuities

A perpetuity is an annuity that continues indefinitely in the model. Perpetual securities have no contractual maturity, although their terms may permit redemption. A permanent endowment can be planned over an indefinite horizon. In these settings, the perpetuity formula can provide a useful approximation.

For constant positive payments beginning one period from now, and a constant positive per-period discount rate rr, the present value is:

PV=CrPV = \frac{C}{r}

where:

  • PVPV: present value of the perpetuity
  • CC: constant periodic payment
  • rr: discount rate per period

The present value equals the principal amount that, if invested at rate rr, would generate exactly CC in interest each period forever, leaving the principal intact. If you have C/rC/r dollars earning rate rr, your interest income is (C/r)×r=C(C/r) \times r = C per period. You can withdraw this interest forever without touching the principal, creating a perpetual income stream of CC per period.

This formula emerges from taking the limit of the annuity formula as T→∞T \to \infty:

lim⁡T→∞C⋅1−(1+r)−Tr=Cr\lim_{T \to \infty} C \cdot \frac{1 - (1+r)^{-T}}{r} = \frac{C}{r}

For a fixed positive rr, the term (1+r)−T(1+r)^{-T} tends to zero as TT grows without bound, leaving C/rC/r. A finite cutoff needs a rate-specific check. Omitting payments after period TT leaves a tail worth (C/r)(1+r)−T(C/r)(1+r)^{-T}, or a fraction (1+r)−T(1+r)^{-T} of the perpetuity value. At 4% per period, the fraction beyond payment 1,000 is about 9.3×10−189.3\times10^{-18}; at 0.01% per period it is still about 90.48%. A positive rate alone does not make a particular finite cutoff negligible.

Perpetuity

A perpetuity is a stream of equal payments continuing indefinitely. For positive payments beginning one period from now and a positive per-period discount rate, its present value is PV=C/rPV=C/r.

In[28]:
Code
def perpetuity_pv(payment, r):
    """PV of end-period level payments under the model's positive-rate assumption."""
    return payment / r


# Example: Endowment providing $50,000 annually at 4% rate
annual_payment = 50000
r = 0.04

endowment_needed = perpetuity_pv(annual_payment, r)
Out[29]:
Console
Annual scholarship payment: $50,000
Interest rate: 4.0%

Endowment required: $1,250,000

To fund a $50,000 annual scholarship in perpetuity at a constant 4% return, with the first withdrawal at year-end, the model requires $1.25 million. Each year's interest funds that year's withdrawal, leaving the nominal principal intact.

A growing perpetuity accounts for payments that change at a constant rate gg. For the usual nonnegative-payment model, take C>0C>0, 1+r>01+r>0, and 1+g≥01+g\geq0; if every payment must remain strictly positive, require 1+g>01+g>0.

PV=Cr−g,q=1+g1+r,∣q∣<1PV = \frac{C}{r - g}, \qquad q=\frac{1+g}{1+r}, \qquad |q|<1

where:

  • PVPV: present value of the growing perpetuity
  • CC: first payment (occurring one period from now)
  • rr: discount rate per period, with positive discount gross factor 1+r>01+r>0
  • gg: constant payment growth rate, with 1+g≥01+g\geq0 for nonnegative payments (strictly greater than zero for payments that remain positive forever)
  • r−gr - g: the discount-growth spread in the closed form; under these nonnegative gross-factor assumptions, ∣q∣<1|q|<1 reduces to r>gr>g

Growth partially offsets discounting. When payments grow at 2% and the discount rate is 5%, the discount-growth spread r−gr-g is 3 percentage points. Successive present-value terms have ratio q=1.02/1.05q=1.02/1.05, so the decline per term is 1−q≈2.857%1-q\approx2.857\%. The equivalent discount-rate quote is instead 1/q−1=1.05/1.02−1≈2.941%1/q-1=1.05/1.02-1\approx2.941\%; these are different quantities. With CC and rr fixed in the positive-gross-factor domain, as gg approaches rr from below, the terms barely decline and the sum grows without bound.

To see why mathematically, the tt-th payment is C(1+g)t−1C(1+g)^{t-1}, so its present value is C/(1+r)⋅qt−1C/(1+r)\cdot q^{t-1} with q=(1+g)/(1+r)q=(1+g)/(1+r). A geometric series converges exactly when ∣q∣<1|q|<1. In the nonnegative-payment domain, q≥0q\geq0, so this becomes q<1q<1 and, because 1+r>01+r>0, is equivalent to g<rg<r. If g≥rg\geq r, the nonnegative discounted terms do not decay to zero and the infinite sum diverges.

Applied to a constant-growth dividend stream, this formula gives the Gordon Growth Model for stock valuation.

Out[30]:
Visualization
Line chart showing annuity present value approaching the horizontal perpetuity asymptote.
Present value of a $1,000 annual annuity converges to the perpetuity value as the number of periods increases. At a 5% discount rate, a 50-year annuity captures about 91% of the perpetuity value. At 100 years, the remaining tail is about 0.76% ($152.09 of the $20,000 perpetuity value), and it decreases for longer terms.