Chapter 5 — Time Value of Money

"A dollar today is worth more than a dollar tomorrow."


After this chapter you will be able to:

  • Compute present and future values under simple, compound, and continuous compounding conventions
  • Apply the five major day count conventions (Act/360, Act/365, Act/Act, 30/360, 30E/360) and explain why they matter
  • Price annuities, perpetuities, and growing perpetuities using closed-form formulas
  • Build an amortisation schedule for a mortgage or bond
  • Compute NPV and solve for IRR using Newton-Raphson and Brent's method

Every valuation in finance — every bond, every option, every corporate investment decision — rests on a single foundation: the time value of money. The concept is ancient (Italian merchants in the 13th century wrote contracts with implicit discounting), but it was Irving Fisher's 1930 book The Theory of Interest that placed it on rigorous mathematical footing. Fisher's central insight was that the market rate of interest represents the price at which people trade present goods for future goods. When a borrower offers 5% interest, they are saying: "Give me \$100 today and I will give you \$105 in one year." The equilibrium interest rate is set by the tension between impatience (people prefer present consumption) and investment opportunity (capital deployed today can produce more tomorrow).

From this simple foundation comes an extraordinary range of tools. Present value and future value are two sides of the same coin: PV asks "what is a future cash flow worth today?" while FV asks "what will today's cash grow to?". The net present value (NPV) rule for capital budgeting — invest if and only if NPV > 0 — follows directly, and it underlies every corporate finance decision from factory construction to merger evaluation. Annuities and perpetuities extend the framework to regular cash flow streams, giving closed-form formulas for mortgages, pension sums, and the perpetual dividend growth model. The internal rate of return (IRR) asks the inverse question: at what discount rate does an investment break even?

This chapter implements the full TVM toolkit in OCaml: compounding and discounting under multiple conventions, annuity and perpetuity pricing, NPV and IRR computation, and a reusable cash flow scheduling engine that will underpin every fixed-income instrument in the chapters ahead.


5.1 The Core Principle

The time value of money is the most fundamental concept in finance: a cash flow received sooner is worth more than the same amount received later, because money can be invested to earn a return in the intervening period.

Given:

  • $r$ = interest rate per period
  • $n$ = number of periods
  • $PV$ = present value
  • $FV$ = future value

Future value of a lump sum:

$$FV = PV \cdot (1 + r)^n$$

Present value:

$$PV = \frac{FV}{(1 + r)^n} = FV \cdot d(r, n)$$

where $d(r, n) = (1+r)^{-n}$ is the discount factor.


5.2 Day Count Conventions

Financial markets use various conventions for counting the days in a period and the days in a year. These affect every interest rate calculation.

ConventionDay CountYear BasisUsage
Act/360Actual calendar days360USD money market, FRAs
Act/365Actual calendar days365GBP, AUD
Act/ActActual daysActual days in yearGovernment bonds (Treasuries)
30/36030 days per month360Corporate bonds (US)
30E/360European 30/360360Eurobonds
type day_count_convention =
  | Act360
  | Act365
  | Act365_25   (* for long periods *)
  | ActAct_ICMA
  | Thirty360
  | ThirtyE360

type date = {
  year  : int;
  month : int;
  day   : int;
}

(** Is a year a leap year? *)
let is_leap_year y =
  (y mod 4 = 0 && y mod 100 <> 0) || (y mod 400 = 0)

(** Days in a month *)
let days_in_month y m =
  match m with
  | 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31
  | 4 | 6 | 9 | 11 -> 30
  | 2 -> if is_leap_year y then 29 else 28
  | _ -> failwith "invalid month"

(** Serial date number (days since 1900-01-01) *)
let to_serial { year; month; day } =
  let y = if month <= 2 then year - 1 else year in
  let m = if month <= 2 then month + 12 else month in
  let a = y / 100 in
  let b = 2 - a + a / 4 in
  int_of_float (Float.floor (365.25 *. float_of_int (y + 4716)))
  + int_of_float (Float.floor (30.6001 *. float_of_int (m + 1)))
  + day + b - 1524

(** Actual calendar days between two dates *)
let actual_days d1 d2 = to_serial d2 - to_serial d1

(**
    Year fraction under a given day count convention.
    
    This is the fraction of a year represented by the period [d1, d2].
*)
let year_fraction convention d1 d2 =
  let actual = float_of_int (actual_days d1 d2) in
  match convention with
  | Act360     -> actual /. 360.0
  | Act365     -> actual /. 365.0
  | Act365_25  -> actual /. 365.25
  | ActAct_ICMA ->
    (* Count days in the coupon period straddling d1 and d2 *)
    let days_in_year = if is_leap_year d1.year then 366.0 else 365.0 in
    actual /. days_in_year
  | Thirty360 ->
    (* Each month counts as 30 days; capped at 30 *)
    let d1_d = min d1.day 30 in
    let d2_d = if d1.day >= 30 then min d2.day 30 else d2.day in
    let days_30 = float_of_int (
      360 * (d2.year - d1.year)
      + 30 * (d2.month - d1.month)
      + (d2_d - d1_d)
    ) in
    days_30 /. 360.0
  | ThirtyE360 ->
    let d1_d = min d1.day 30 in
    let d2_d = min d2.day 30 in
    let days_e = float_of_int (
      360 * (d2.year - d1.year)
      + 30 * (d2.month - d1.month)
      + (d2_d - d1_d)
    ) in
    days_e /. 360.0

(** Unit test *)
let () =
  let d1 = { year = 2025; month = 1; day = 15 } in
  let d2 = { year = 2025; month = 7; day = 15 } in
  Printf.printf "Act/360:    %.6f\n" (year_fraction Act360 d1 d2);    (* 0.500000 *)
  Printf.printf "Act/365:    %.6f\n" (year_fraction Act365 d1 d2);    (* 0.493151 *)
  Printf.printf "30/360:     %.6f\n" (year_fraction Thirty360 d1 d2)  (* 0.500000 *)

5.3 Discount Factors and Compounding

5.3.1 Simple Interest

Used for short-dated money market instruments (T-bills, FRAs, deposits):

$$DF = \frac{1}{1 + r \cdot \tau}$$

where $\tau$ is the year fraction.

5.3.2 Compound Interest

$$DF = \frac{1}{(1 + r/m)^{m \cdot T}}$$

5.3.3 Continuous Compounding

The limit as $m \to \infty$:

$$DF = e^{-r \cdot T}$$

Continuous compounding is used in most derivative pricing and is analytically convenient.

type rate_convention =
  | Simple       of { tau : float }
  | Compound     of { freq : int; tau : float }
  | Continuous   of { tau : float }

(** Compute discount factor *)
let discount_factor rate convention =
  match convention with
  | Simple { tau }         -> 1.0 /. (1.0 +. rate *. tau)
  | Compound { freq; tau } ->
    let n = float_of_int freq in
    1.0 /. (1.0 +. rate /. n) ** (n *. tau)
  | Continuous { tau }     -> exp (-. rate *. tau)

(** Convert between rate conventions *)
let convert_to_continuous rate = function
  | Simple { tau }         ->
    (* r_c = (1/τ) * log(1 + r_s * τ) *)
    log (1.0 +. rate *. tau) /. tau
  | Compound { freq; tau = _ } ->
    (* r_c = m * log(1 + r_m / m) *)
    let m = float_of_int freq in
    m *. log (1.0 +. rate /. m)
  | Continuous _ -> rate   (* already continuous *)

let convert_from_continuous r_c = function
  | Simple { tau } -> (exp (r_c *. tau) -. 1.0) /. tau
  | Compound { freq; _ } ->
    let m = float_of_int freq in
    m *. (exp (r_c /. m) -. 1.0)
  | Continuous _ -> r_c

5.4 Annuities and Perpetuities

5.4.1 Ordinary Annuity

An annuity pays $C$ at the end of each period for $n$ periods. Present value:

$$PV_{\text{annuity}} = C \cdot \frac{1 - (1+r)^{-n}}{r} = C \cdot a(r, n)$$

where $a(r,n)$ is the annuity factor.

5.4.2 Annuity Due

Payments at the beginning of each period:

$$PV_{\text{due}} = C \cdot a(r, n) \cdot (1+r)$$

5.4.3 Perpetuity

An infinite annuity:

$$PV_{\text{perp}} = \frac{C}{r}$$

(** Present value annuity factor *)
let annuity_factor ~rate ~n_periods =
  if Float.abs rate < 1e-12 then float_of_int n_periods   (* zero rate limit *)
  else (1.0 -. (1.0 +. rate) ** (-. float_of_int n_periods)) /. rate

(** PV of ordinary annuity *)
let annuity_pv ~payment ~rate ~n_periods =
  payment *. annuity_factor ~rate ~n_periods

(** PV of annuity due (payments at start of period) *)
let annuity_due_pv ~payment ~rate ~n_periods =
  annuity_pv ~payment ~rate ~n_periods *. (1.0 +. rate)

(** PV of growing annuity: payment grows at rate g *)
let growing_annuity_pv ~payment ~rate ~growth ~n_periods =
  if Float.abs (rate -. growth) < 1e-12 then
    payment *. float_of_int n_periods /. (1.0 +. rate)
  else
    payment /. (rate -. growth)
    *. (1.0 -. ((1.0 +. growth) /. (1.0 +. rate)) ** float_of_int n_periods)

(** Perpetuity PV *)
let perpetuity_pv ~payment ~rate = payment /. rate

(** Growing perpetuity: Gordon Growth Model for equity *)
let gordon_growth_model ~dividend ~rate ~growth =
  assert (growth < rate);
  dividend /. (rate -. growth)

(** Loan amortisation: constant payment per period *)
let loan_payment ~principal ~rate ~n_periods =
  principal *. rate /. (1.0 -. (1.0 +. rate) ** (-. float_of_int n_periods))

(** Generate full amortisation schedule *)
type amort_row = {
  period    : int;
  payment   : float;
  interest  : float;
  principal : float;
  balance   : float;
}

let amortisation_schedule ~principal ~rate ~n_periods =
  let pmt = loan_payment ~principal ~rate ~n_periods in
  let balance = ref principal in
  List.init n_periods (fun i ->
    let interest   = !balance *. rate in
    let principal_ = pmt -. interest in
    balance := !balance -. principal_;
    { period = i + 1; payment = pmt; interest;
      principal = principal_; balance = Float.max 0.0 !balance }
  )

let () =
  Printf.printf "\n=== Loan Amortisation Schedule ===\n";
  Printf.printf "Principal: $200,000  Rate: 5%% p.a.  Term: 30 years (monthly)\n\n";
  Printf.printf "%-8s %-12s %-12s %-12s %-14s\n"
    "Period" "Payment" "Interest" "Principal" "Balance";
  Printf.printf "%s\n" (String.make 60 '-');
  let schedule = amortisation_schedule
    ~principal:200_000.0 ~rate:(0.05 /. 12.0) ~n_periods:360 in
  (* Print first 3 and last 3 rows *)
  let print_row r =
    Printf.printf "%-8d $%-11.2f $%-11.2f $%-11.2f $%-13.2f\n"
      r.period r.payment r.interest r.principal r.balance
  in
  List.iter print_row (List.filteri (fun i _ -> i < 3 || i >= 357) schedule)

5.5 Internal Rate of Return

The IRR is the discount rate that makes the NPV of a cash flow stream equal to zero:

$$\sum_{t=0}^{n} \frac{CF_t}{(1 + IRR)^t} = 0$$

This is a polynomial root-finding problem; multiple solutions exist when cash flows change sign more than once (Descartes' Rule).

(** Net Present Value of a cash flow stream *)
let npv ~rate flows =
  List.fold_left (fun (acc, t) cf ->
    (acc +. cf /. (1.0 +. rate) ** t, t +. 1.0)
  ) (0.0, 0.0) flows
  |> fst

(**
    Internal Rate of Return via Brent's method.
    cash_flows.(0) is typically negative (initial investment).
    Raises if no sign change found (no real IRR).
*)
let irr cash_flows =
  let f r = npv ~rate:r cash_flows in
  (* Find bracket *)
  let rec find_bracket lo hi =
    if hi > 100.0 then Error "No IRR found in [-0.99, 100]"
    else if f lo *. f hi < 0.0 then Ok (lo, hi)
    else find_bracket hi (hi *. 3.0)
  in
  match find_bracket (-. 0.999) 0.01 with
  | Error _ ->
    (* Try another bracket *)
    (match find_bracket 0.001 1.0 with
     | Error e -> Error e
     | Ok (lo, hi) -> brent ~f lo hi)
  | Ok (lo, hi) -> brent ~f lo hi

(** Modified IRR — addresses multiple IRR problem *)
let mirr cash_flows ~finance_rate ~reinvest_rate =
  let n = List.length cash_flows in
  let negatives = List.mapi (fun i cf -> (i, cf)) cash_flows
                  |> List.filter (fun (_, cf) -> cf < 0.0) in
  let positives = List.mapi (fun i cf -> (i, cf)) cash_flows
                  |> List.filter (fun (_, cf) -> cf > 0.0) in
  let pv_neg = List.fold_left (fun acc (i, cf) ->
    acc +. cf /. (1.0 +. finance_rate) ** float_of_int i
  ) 0.0 negatives in
  let fv_pos = List.fold_left (fun acc (i, cf) ->
    acc +. cf *. (1.0 +. reinvest_rate) ** float_of_int (n - 1 - i)
  ) 0.0 positives in
  (fv_pos /. (Float.abs pv_neg)) ** (1.0 /. float_of_int (n - 1)) -. 1.0

5.6 A Cash Flow Engine

Let us build a reusable cash flow engine that underpins the entire fixed income section:

(** A general cash flow with amount, timing, and currency *)
type cash_flow = {
  date     : date;
  amount   : float;
  currency : string;
}

(** A cash flow schedule *)
type schedule = {
  flows    : cash_flow list;
  currency : string;
}

(** Generate a bond coupon schedule *)
let bond_schedule ~issue ~maturity ~coupon_rate ~frequency ~face ~currency =
  let n = frequency in   (* coupons per year *)
  let months_per_period = 12 / n in
  let rec generate current acc =
    let next = add_months current months_per_period in
    let coupon_amount = face *. coupon_rate /. float_of_int n in
    if date_le next maturity then
      generate next ({ date = next; amount = coupon_amount; currency } :: acc)
    else
      (* Final coupon + principal at maturity *)
      let final_coupon = { date = maturity; amount = coupon_amount; currency } in
      let principal    = { date = maturity; amount = face; currency } in
      List.rev (principal :: final_coupon :: acc)
  in
  let flows = generate issue [] in
  { flows; currency }

(** PV of a schedule given a discount function *)
let present_value ~discount ~valuation_date { flows; _ } =
  List.fold_left (fun acc { date; amount; _ } ->
    let tau = year_fraction Act365 valuation_date date in
    if tau < 0.0 then acc   (* past cash flows ignored *)
    else acc +. amount *. discount tau
  ) 0.0 flows

(** Accrued interest: coupon earned but not yet paid *)
let accrued_interest ~valuation_date ~last_coupon_date ~next_coupon_date ~coupon_amount ~convention =
  let tau_accrued = year_fraction convention last_coupon_date valuation_date in
  let tau_full    = year_fraction convention last_coupon_date next_coupon_date in
  coupon_amount *. (tau_accrued /. tau_full)

(** Clean price = dirty price - accrued interest *)
let clean_price ~dirty_price ~accrued = dirty_price -. accrued

5.7 Chapter Summary

Time value of money is deceptively simple as a concept but surprisingly rich in its details. The core operation — discounting a future cash flow by a factor $(1+r)^{-n}$ — is trivially mechanical. The complexity enters through compounding conventions, day count rules, and the treatment of irregular cash flows.

Compounding frequency matters substantially at high rates or long horizons. The gap between annual and continuous compounding at 10% over 20 years is about 6% of the final amount — enough to affect real decisions. Continuous compounding, equivalent to the limit of infinitely frequent compounding, is used almost universally in derivatives pricing because it makes the mathematics cleaner: discount factors multiply, log-returns add, and Itô's lemma takes its standard form.

Day count conventions are one of finance's most persistent sources of confusion and bug-prone code. The difference between Act/360 and Act/365 for a 6-month deposit at 5% is roughly 0.7 basis points — small but real, and it accumulates across a large portfolio. The 30/360 convention (treating each month as exactly 30 days) was designed for human calculation before computers but persists in bond markets by tradition. Correct implementation of day count arithmetic is essential for pricing instruments against market quotes.

The cash flow scheduling engine built in this chapter — generating coupon dates, computing accrued interest, handling month-end conventions — is reused directly in Chapters 6, 7, and 8. It is the plumbing beneath every fixed-income calculation in this book.


Exercises

5.1 A zero-coupon bond matures in 5 years and is priced at 78.35 per 100 face value. Compute the yield under Act/365, Act/360, and 30/360 day count conventions. Explain the difference.

5.2 Build an interest-only vs principal-and-interest mortgage model. For a \$500,000 mortgage at 6% for 30 years, compute total interest paid under each structure.

5.3 A project has cash flows: −\$1M, \$300K, \$400K, \$500K, \$200K at years 0–4. Compute the NPV at 8% discount rate and the IRR. Does the project add value?

5.4 Implement day count conversion between Act/360 and Act/365 for a given rate and time period. Show that the difference matters for a 90-day deposit at 5%.


Next: Chapter 6 — Bonds and Fixed Income Instruments