Chapter 01
Discounting a bond
Before any curve can be built, one idea has to be fixed: what a future payment is worth today. Everything else in this project is bookkeeping on top of it.
The discount factor
A discount factor \(d_t\) is the price, agreed today, of receiving one unit of currency at time \(t\). It is not a rate and it is not a percentage — it is a price, and it is the most primitive object in fixed income. If \(d_3 = 0.8612\), then a contract paying €1 in three years is worth 86.12 cents now.
Discount factors are ordinarily below 1 and decreasing in \(t\): money later is worth less than money now, and money much later is worth less still. Every rate quoted in this project is a re-expression of these numbers.
Pricing one bond
A coupon bond is a bundle of dated payments: a coupon \(c\) each year, and at maturity the coupon plus the face value \(F\). Its fair price is the sum of those payments, each multiplied by the discount factor for its own date:
\(m\) is the maturity in years, \(c\) the annual coupon in currency, \(F\) the face value — here 100 for every bond.
Read left to right this is a pricing formula: given the discount factors, produce the price. The project reads it right to left. The prices are observed in the market; the discount factors are the unknowns.
Pricing ten bonds at once
One bond gives one equation with \(m\) unknowns — hopeless on its own. The way out is to use a set of bonds whose maturities line up with the dates being solved for. Ten bonds maturing at years 1 through 10 give ten equations in exactly ten unknowns.
Collect the payments into a matrix. Let \(C_{ij}\) be the cash flow that bond \(i\) pays at time \(j\):
Row \(i\) is one bond’s payment schedule. It runs at the coupon level until maturity, jumps by the face value in the maturity column, and is zero afterwards — so \(C\) is lower-triangular.
With \(\mathbf{d}\) the vector of unknown discount factors and \(\mathbf{P}\) the vector of observed prices, the ten pricing equations collapse into a single statement:
Ten equations, ten unknowns, one square matrix. Everything in the next chapter is a different way of getting \(\mathbf{d}\) out of this.
Why the triangular shape matters
Because \(C\) is lower-triangular with non-zero entries on its diagonal — every bond pays back its face value at its own maturity, so \(C_{ii} = c_i + F \geq 100\) — it is guaranteed invertible. The system has exactly one solution, and it is well conditioned. That is not luck; it is the reason the bond set was chosen with one maturity per year.
The triangularity also hands us the third method for free. The first row involves only \(d_1\), so \(d_1\) can be read off directly. The second row then involves only \(d_1\) and \(d_2\), and \(d_1\) is already known. Substituting forward one row at a time solves the whole system without ever forming an inverse. That recursion is what “bootstrapping” originally meant, and it is where the name comes from.
The bonds used here
Ten government bonds, face value 100, one maturing each year. Prices sit below par at the short end and rise back toward par further out; coupons step evenly from 1.50% to 3.75%.
| Maturity | 1Y | 2Y | 3Y | 4Y | 5Y | 6Y | 7Y | 8Y | 9Y | 10Y |
|---|---|---|---|---|---|---|---|---|---|---|
| Price | 96.60 | 93.71 | 91.56 | 90.24 | 89.74 | 90.04 | 91.09 | 92.82 | 95.19 | 98.14 |
| Coupon % | 1.50 | 1.75 | 2.00 | 2.25 | 2.50 | 2.75 | 3.00 | 3.25 | 3.50 | 3.75 |
In code the three arrays are stacked into the (10, 3) input the class expects:
maturities = np.arange(1, 11)
prices = np.array([96.60, 93.71, 91.56, 90.24, 89.74,
90.04, 91.09, 92.82, 95.19, 98.14])
coupons = np.linspace(0.015, 0.0375, num=10)
bonds = np.column_stack((maturities, prices, coupons))
y = YieldCurve(bonds) # builds the cash-flow matrix C