Bootstrap a spot yield curve from instrument prices
Source:R/bootstrap_spot_curve.R
bootstrap_spot_curve.RdRecovers annual effective spot rates and discount factors sequentially from observed dirty prices and dated cash flows. Each instrument supplies one new maturity; all its earlier positive cash flows must occur at maturities already bootstrapped.
Usage
bootstrap_spot_curve(
.data,
col_instrument = "instrument",
col_price = "price",
col_t = "t",
col_cf = "cf"
)Arguments
- .data
Nonempty data frame with one row per priced instrument.
- col_instrument
Character scalar naming the unique instrument-ID column.
- col_price
Character scalar naming the observed dirty-price column.
- col_t
Character scalar naming the list-column of positive payment times in years.
- col_cf
Character scalar naming the list-column of nonnegative cash flows corresponding to
col_t.
Value
A tibble ordered by maturity, with columns instrument,
t, price, known_pv, terminal_cf, v,
i, repriced_price, and repricing_error. The columns
v and i contain the new maturity's discount factor and
annual effective spot rate. repricing_error is the model price
minus the observed dirty price, in monetary units.
Details
For instrument \(j\), with maturity \(t_j\), dirty price \(P_j\), and cash flows \(CF_{j,k}\), the price identity is $$P_j = \sum_{k < j} CF_{j,k} v(t_k) + CF_{j,j} v(t_j).$$ Hence the new discount factor is recovered exactly as $$v(t_j) = \frac{P_j - \sum_{k < j} CF_{j,k} v(t_k)}{CF_{j,j}}.$$ The annual effective spot rate is $$i_{t_j} = v(t_j)^{-1/t_j} - 1.$$
Each input row represents one instrument; t and cf are
list-columns. Repeated payment times within an instrument are combined,
allowing coupon and redemption flows produced by bond_cash_flows().
Instruments are processed by increasing final positive-payment time, not
input row order. Zero cash flows are ignored. Duplicate final maturities,
missing earlier discount-factor nodes, zero or negative residual prices,
and nonpositive terminal cash flows are rejected.
This is exact triangular bootstrapping: it does not interpolate,
extrapolate, fit a curve, or estimate a least-squares solution. An annual
effective spot rate can be negative; its discount factor must be positive.
Input prices and cash flows must use the same monetary units.
Both the positive finite discount factor and its annual effective rate above
-1 must be representable. Rounded input prices can make a small terminal
residual ill-conditioned; no tolerance silently replaces a nonpositive
residual. repricing_error reports the recomputed floating-point discrepancy,
not an independent accuracy guarantee or a convergence test.
Examples
# Recover a known 1-, 2-, and 3-year annual effective spot curve.
t <- 1:3
i <- c(0.04, 0.045, 0.05)
v <- (1 + i)^(-t)
instruments <- tibble::tibble(
instrument = c("Zero 1Y", "Coupon 2Y", "Coupon 3Y"),
price = c(100 * v[1],
5 * v[1] + 105 * v[2],
6 * v[1] + 6 * v[2] + 106 * v[3]),
t = list(1, c(1, 2), c(1, 2, 3)),
cf = list(100, c(5, 105), c(6, 6, 106))
)
curve <- instruments |> bootstrap_spot_curve()
curve
#> # A tibble: 3 × 9
#> instrument t price known_pv terminal_cf v i repriced_price
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Zero 1Y 1 96.2 0 100 0.962 0.0400 96.2
#> 2 Coupon 2Y 2 101. 4.81 105 0.916 0.045 101.
#> 3 Coupon 3Y 3 103. 11.3 106 0.864 0.050 103.
#> # ℹ 1 more variable: repricing_error <dbl>
yield_curve(t = curve$t, i = curve$i)
#> # A tibble: 1 × 4
#> t i v i_effective
#> <list> <list> <list> <list>
#> 1 <dbl [3]> <dbl [3]> <dbl [3]> <dbl [3]>