Fixed-Income Reference
Definitions, formulas, and worked examples for the core fixed-income concepts used throughout the ALM toolkit.
Duration (Macaulay)
What it measures: The weighted-average time until a bond's cash flows are received, where each weight is the present value of that cash flow as a fraction of the bond's total price.
Formula:
| Symbol | Meaning |
|---|---|
| \(P\) | Bond price (present value) |
| \(CF_t\) | Cash flow at period \(t\) |
| \(y\) | Annual yield (discount rate) |
| \(f\) | Payment frequency per year |
| \(n\) | Total number of periods |
Interpretation: A duration of 4.5 years means the bond's price behaves like a zero-coupon bond maturing in 4.5 years. Higher duration = greater interest-rate sensitivity.
Mini example — 5-year, 4 % semi-annual bond ($100 par):
| Period | Year | Cash Flow | PV @ 4 % | Weight | Year \(\times\) Weight |
|---|---|---|---|---|---|
| 1 | 0.5 | $2.00 | $1.96 | 0.0196 | 0.010 |
| 2 | 1.0 | $2.00 | $1.92 | 0.0192 | 0.019 |
| ... | ... | ... | ... | ... | ... |
| 10 | 5.0 | $102.00 | $83.78 | 0.8378 | 4.189 |
| Total | $100.00 | 1.0000 | \(D \approx 4.56\) yrs |
Code: Bond.duration() in src/alm/asset.py
Modified Duration
What it measures: The percentage price change for a 1 % change in yield. Converts Macaulay duration into a direct risk measure.
Formula:
Interpretation: If \(D_{\text{mod}} = 4.47\), a 1 % (100 bp) rise in yield causes approximately a 4.47 % drop in price.
Relationship to DV01:
Convexity
What it measures: The curvature of the price-yield relationship — how duration itself changes as rates move.
Formula:
Interpretation: Convexity is always positive for option-free bonds. Higher convexity means:
- Price rises more than duration predicts when rates fall
- Price falls less than duration predicts when rates rise
This asymmetry is valuable — all else equal, investors prefer higher convexity.
Second-order price approximation:
Mini example — 5-year, 4 % semi-annual bond ($100 par, \(y = 4\%\)):
| Duration only | Duration + Convexity | |
|---|---|---|
| Rates \(+1\%\) | \(-4.47\%\) | \(-4.47\% + 0.11\% = -4.36\%\) |
| Rates \(-1\%\) | \(+4.47\%\) | \(+4.47\% + 0.11\% = +4.58\%\) |
The convexity term (\(+0.11\%\)) always helps — it dampens losses and amplifies gains.
Code: Bond.convexity() in src/alm/asset.py
DV01 (Dollar Value of a Basis Point)
What it measures: The dollar change in a position's value for a one-basis-point (0.01 %) parallel shift in rates.
Formula (central finite difference):
Interpretation: If DV01 = $45,000 on a $100M bond, a 1 bp rate increase reduces value by roughly $45,000.
Mini example:
| PV | |
|---|---|
| Rate = 3.99 % | $100,044,500 |
| Rate = 4.01 % | $99,955,500 |
| DV01 | $44,500 |
DV01 is the primary metric for measuring and hedging interest-rate risk at the portfolio level. Matching asset and liability DV01s is the first step in immunization.
Code: dv01() in src/alm/core.py
Key Rate Duration (KRD)
What it measures: The sensitivity of a bond's price to a shift at a single point on the yield curve, holding all other rates fixed. KRDs decompose overall duration into contributions from specific maturities.
Formula:
where \(\Delta y_k\) is a bump at tenor \(k\) only (typically 1 bp), interpolated to nearby cash-flow dates.
Key properties:
- KRDs across all tenors sum to the bond's effective duration
- A bullet bond has KRD concentrated at its maturity
- A mortgage or amortizing bond has KRD spread across many tenors
Mini example — 10-year bullet bond:
| Tenor | KRD |
|---|---|
| 1Y | 0.04 |
| 2Y | 0.04 |
| 5Y | 0.07 |
| 10Y | 7.65 |
| 30Y | 0.00 |
| Total | \(\approx\) 7.80 |
Nearly all the rate sensitivity sits at the 10-year point, as expected for a bullet bond.
Why it matters: KRD analysis reveals mismatches that parallel duration hedging misses. A portfolio can be duration-matched overall but have significant exposure to curve twists (e.g. short end rallies, long end sells off).
Immunization
What it measures: A hedging strategy that protects a portfolio's surplus (assets minus liabilities) against parallel shifts in interest rates by matching both duration and convexity.
Duration Matching (First Order)
Match the dollar duration of assets to liabilities:
This neutralizes the portfolio against small parallel rate shifts.
Convexity Matching (Second Order)
Duration matching alone leaves the portfolio exposed to large rate moves. Adding a convexity match closes this gap:
where \(C_\$\) is dollar convexity.
Two-Instrument Immunization
With two hedging instruments (e.g. a 5-year and 10-year swap), solve the 2\(\times\)2 system:
where \(\Delta\text{DV01}\) and \(\Delta C_\$\) are the gaps to close, and \(n_1, n_2\) are the required notionals.
Mini example:
| DV01 per unit | Dollar Convexity per unit | |
|---|---|---|
| 5yr Swap | 4.0 | 20 |
| 10yr Swap | 8.0 | 90 |
| Gap to close | 500 | 80,000 |
Solving: \(n_1 \approx -4{,}375\), \(n_2 \approx 2{,}281\) (receive-fixed on the 10yr, pay-fixed on the 5yr).
Code: immunize() in src/alm/core.py
IRR (Internal Rate of Return)
What it measures: The discount rate that makes the net present value of a series of cash flows equal to zero.
Formula:
Solve for \(r\). There is no closed-form solution — the ALM toolkit uses Newton-Raphson iteration.
Newton-Raphson step:
where \(NPV'(r) = \sum_{t=0}^{n} \frac{-t \cdot CF_t}{(1+r)^{t+1}}\).
Mini example — $100 investment returning $30/yr for 4 years:
| Year | Cash Flow |
|---|---|
| 0 | \(-100\) |
| 1 | \(+30\) |
| 2 | \(+30\) |
| 3 | \(+30\) |
| 4 | \(+30\) |
Setting \(NPV = 0\): \(-100 + 30 \cdot \frac{1-(1+r)^{-4}}{r} = 0\)
Solving: \(r \approx 7.71\%\)
Code: irr() in src/alm/core.py
Quick Reference Table
| Metric | Units | What it answers | First or second order? |
|---|---|---|---|
| Duration | Years | How sensitive is price to a rate change? | First |
| Convexity | Years\(^2\) | How does duration change as rates move? | Second |
| DV01 | Dollars | What is the dollar impact of 1 bp? | First |
| KRD | Years | Where on the curve does the risk sit? | First |
| IRR | % | What return does this investment earn? | N/A |