OpenStax Principles of Finance (CC BY 4.0) ยท MIT OCW 15.401 (CC BY-NC-SA 4.0)
A stock is worth the present value of expected cash returned to shareholders — dividends, buybacks, or liquidation proceeds. The dividend discount model is the cleanest form, but the cash flows are uncertain, so every valuation is only as good as its growth assumptions.
Dividend discount model
The dividend discount model (DDM) values a stock as the sum of all future dividends discounted at the required return r. If you hold the stock forever, price equals ∑ Dt/(1+r)t. For a finite holding period, you discount dividends received plus the sale price.
Scheme
(define (range a b) (if (> a b) '() (cons a (range (+ a 1) b))))
; DDM: P = sum of D_t / (1+r)^t; Finite holding: P = sum(D_t/(1+r)^t) + P_sell/(1+r)^n
(define (ddm-finite dividends sell-price r)
(let ((n (length dividends)))
(+ (apply + (map (lambda (d t)
(/ d (expt (+ 1 r) t)))
dividends
(range 1 n)))
(/ sell-price (expt (+ 1 r) n)))))
; Expected dividends: $2, $2.10, $2.20 over 3 years; Expected sale price: $50; Required return: 10%
(define divs (list 22.102.20))
(define p (ddm-finite divs 500.10))
(display "Stock value (3yr hold): $")
(display (/ (round (* p 100)) 100))
(newline)
; Break it down
(display "PV of dividends: $")
(display (/ (round (* (- p (/ 50 (expt 1.103))) 100)) 100))
(newline)
(display "PV of sale price: $")
(display (/ (round (* (/ 50 (expt 1.103)) 100)) 100))
Python
# Dividend discount model โ finite holding perioddef ddm_finite(dividends, sell_price, r):
pv_divs = sum(d / (1 + r) ** (t + 1) for t, d inenumerate(dividends))
pv_sell = sell_price / (1 + r) ** len(dividends)
return pv_divs + pv_sell
divs = [2.00, 2.10, 2.20]
p = ddm_finite(divs, 50, 0.10)
pv_sell = 50 / 1.10 ** 3print(f"Stock value (3yr hold): ${p:.2f}")
print(f"PV of dividends: ${p - pv_sell:.2f}")
print(f"PV of sale price: ${pv_sell:.2f}")
Gordon growth model
If dividends grow at a constant rate g forever, the DDM collapses to a closed form: P = D1 / (r - g). This is the Gordon growth model, a special case of the growing perpetuity. It only works when r > g. Small changes in g swing the price dramatically.
Scheme
; Gordon growth model: P = D1 / (r - g)
(define (gordon d1 r g)
(if (<= r g)
(display "Error: r must exceed g")
(/ d1 (- r g))))
; Stock paying $3 next year, 10% required return
(display "g = 2%: $") (display (gordon 30.100.02)) (newline)
(display "g = 4%: $") (display (gordon 30.100.04)) (newline)
(display "g = 6%: $") (display (gordon 30.100.06)) (newline)
(display "g = 8%: $") (display (gordon 30.100.08)) (newline)
; Implied growth rate: g = r - D1/P
(define (implied-growth d1 r price)
(- r (/ d1 price)))
(newline)
(display "Stock at $50, D1=$3, r=10%: implied g = ")
(display (* 100 (implied-growth 30.1050)))
(display "%")
Python
# Gordon growth model: P = D1 / (r - g)def gordon(d1, r, g):
assert r > g, "r must exceed g"return d1 / (r - g)
# Sensitivity to growth ratefor g in [0.02, 0.04, 0.06, 0.08]:
print(f"g = {g:.0%}: ${gordon(3, 0.10, g):.2f}")
# Implied growth ratedef implied_growth(d1, r, price):
return r - d1 / price
ig = implied_growth(3, 0.10, 50)
print(f"\nStock at $50, D1=$3, r=10%: implied g = {ig:.0%}")
P/E ratios and relative valuation
The price-to-earnings ratio (P/E) divides stock price by earnings per share. A high P/E can mean the market expects high growth, or that the stock is overpriced. Comparing P/E across similar firms is relative valuation — faster than DCF, but only as good as the comparables.
# P/E ratios and relative valuation
companies = [
("AlphaCo", 150, 5.00),
("BetaInc", 80, 4.00),
("GammaTech", 200, 2.50),
]
for name, price, eps in companies:
pe = price / eps
ey = eps / price
print(f"{name}: P/E = {pe:.1f}, E/P = {ey:.1%}")
# Relative valuation: if BetaInc traded at AlphaCo's P/E
alpha_pe = 150 / 5.00
beta_implied = alpha_pe * 4.00print(f"\nBetaInc at AlphaCo's P/E ({alpha_pe:.0f}x): ${beta_implied:.2f}")
Stock returns and holding period
The holding period return (HPR) includes both price appreciation and dividends: HPR = (P1 - P0 + D) / P0. Annualizing over multiple years uses geometric compounding. Total return is what actually hits your account.
Scheme
; Holding period return
(define (hpr p0 p1 dividends)
(/ (+ (- p1 p0) dividends) p0))
; Bought at $40, sold at $48, received $2 in dividends
(define r (hpr 40482))
(display "HPR: ") (display (* 100 r)) (display "%") (newline)
(display " Price return: ") (display (* 100 (/ (- 4840) 40))) (display "%") (newline)
(display " Dividend yield: ") (display (* 100 (/ 240))) (display "%") (newline)
; Annualized return over multiple years; (1 + total)^(1/n) - 1
(define (annualized-return total-return years)
(- (expt (+ 1 total-return) (/ 1 years)) 1))
; 50% total return over 3 years
(newline)
(define ann (annualized-return 0.503))
(display "50% over 3 years = ")
(display (/ (round (* ann 10000)) 100))
(display "% annualized")