A euro tomorrow is not a euro today
If you can earn 5% a year, 100 today becomes 105 in a year. So 105 promised in a year is worth only 100 today. That is discounting: converting a future cash amount to its present value by removing the interest it could have earned. For an annual rate r and t years, PV = cash / (1 + r) ** t. Every bond, loan, annuity and pension is priced this way, which makes it the base of fixed income and option pricing in the next topics.
def present_value(cash, rate, years):
return cash / (1 + rate) ** years
print(round(present_value(100, 0.05, 3), 4)) # 86.3838 100 received in 3 years, worth 86.38 today
print(round(present_value(100, 0.05, 0), 4)) # 100.0 no waiting, no discount
print(round(present_value(100, 0.10, 3), 4)) # 75.1315 a higher rate discounts harderNet present value
Real investments pay in streams: a bond pays coupons and a final principal, a project costs money now and returns it over years. The net present value (NPV) discounts each cash flow by its own time and adds them up. List the flows with the first at time 0, so the times are 0, 1, 2, ... (np.arange(len(flows))), and discount the whole array in one call. A positive NPV means the investment earns more than the discount rate; negative means it earns less. As the rate rises, the NPV falls.
import numpy as np
flows = np.array([-100.0, 30.0, 40.0, 50.0]) # pay 100 now, receive 30, 40 and 50 over the next three years
times = np.arange(len(flows)) # 0, 1, 2, 3
for rate in (0.08, 0.12, 0.20):
print(rate, round((flows / (1 + rate) ** times).sum(), 3))
# 0.08 1.763 positive: worth doing at 8%
# 0.12 -5.738 negative: not at 12%
# 0.2 -18.287 the higher the rate, the lower the NPVCompounding forward, and continuous rates
Going forward is the mirror image: FV = PV * (1 + r) ** t. If interest is paid m times a year at the quoted rate r, each period earns r / m and there are m * t periods: FV = PV * (1 + r / m) ** (m * t). More frequent payment grows a little more: 100 at 5% becomes 105.00 yearly, 105.12 monthly, 105.13 daily. In the limit of continuous payment the growth factor is exp(r * t) and the discount factor is exp(-r * t), the form used in option pricing because it multiplies nicely over time.
import math
for m in (1, 2, 12, 365):
print(m, round(100 * (1 + 0.05 / m) ** (m * 1), 4))
# 1 105.0
# 2 105.0625
# 12 105.1162
# 365 105.1267
print(round(100 * math.exp(0.05), 4)) # 105.1271 continuous compounding, the limit
print(round(math.exp(-0.05 * 3), 5)) # 0.86071 the continuous discount factor for 3 years
print(round(100 * (1 + 0.05 / 12) ** (12 * 3), 3)) # 116.147 100 grows for 3 years with monthly interest# How long to double the money at 5%? Exactly ln 2 / ln 1.05, roughly 72 / 5
print(round(math.log(2) / math.log(1.05), 2), round(72 / 5, 1)) # 14.21 14.4ln(1 + r) or exp(r) - 1 when you move between conventions.PV = cash / (1 + r) ** t: later money is worth less; a higher rate discounts harder.- NPV = sum of the discounted flows, the first at time 0:
(flows / (1 + r) ** np.arange(n)).sum(); positive means worth doing. FV = PV * (1 + r / m) ** (m * t); continuous: growthexp(r * t), discountexp(-r * t); always state the convention.
Next up: bond prices, discounting a coupon stream and a final principal at a market rate.
Lab · what's graded
- Bring a future payment to today with
present_value25 pts - Value a stream of payments with
npv25 pts - Compound interest forward with
future_value25 pts - Discount continuously with
continuous_discount_factor25 pts