Fixed Income Risk Analytics · PCA · Scenario Analysis
This project uses 20 years of US Treasury yield data to identify the three patterns that drive almost all yield-curve movements — then stress-tests a fixed income portfolio against each of them.
01 — The Data
We pull daily yields for nine maturities from the Federal Reserve's FRED database — everything from one-month T-bills to the 30-year long bond. That gives us a complete picture of how the curve has shifted through multiple cycles: the 2008 financial crisis, COVID-era zero rates, and the 2022 hiking cycle that moved faster than almost any in history.
The chart above shows the kind of volatility this analysis has to account for. Yields went from near zero in 2021 to over 5% by 2023 — a move of hundreds of basis points in under two years. A portfolio that wasn't stress-tested for that kind of shock would have been caught off guard.
02 — Finding the Patterns
Rather than treating each of the nine yields as a separate risk, we asked: what are the most common shapes of yield-curve movement? Principal Component Analysis answers exactly that question by finding the directions in which the curve tends to move most.
Why does PC1 explain "only" 58%? In textbook examples you often see PC1 explaining 80%+ of yield-curve variance. The reason it is lower here is the 2022–2023 hiking cycle, which created unusually large slope movements as the short end rose far faster than the long end. Those big slope swings boosted PC2's share of total variance significantly.
03 — The Portfolio
The portfolio is designed to span the entire yield curve — from short-dated notes through to the 30-year long bond — plus an interest-rate swap to represent the derivatives exposure that most real-world fixed income books carry. Each position is $1 million notional, giving a $4 million total portfolio.
The 30Y bond alone accounts for over half the portfolio's rate sensitivity, even though it is just one of four equal-notional positions. This is the inevitable consequence of duration: longer-maturity bonds are simply far more sensitive to rate moves.
| Instrument | Notional | YTM | DV01 ($) | Duration (yrs) | Convexity |
|---|---|---|---|---|---|
| 2Y Treasury Note | $1,000,000 | 3.48% | 191.57 | 1.92 | 4.67 |
| 10Y Treasury Note | $1,000,000 | 4.08% | 814.03 | 8.14 | 78.46 |
| 30Y Treasury Bond | $1,000,000 | 4.72% | 1,594.11 | 15.96 | 369.87 |
| 5Y Receive-Fixed Swap | $1,000,000 | 3.65% | 453.15 | 4.53 | 23.82 |
| Portfolio Total | $4,000,000 | — | 3,052.86 | — | — |
Dollar loss for a 1 basis-point (0.01%) rise in yield. The 30Y bond's DV01 of $1,594 means it alone loses $1,594 if the 30Y yield ticks up by just one hundredth of a percent. The total DV01 of $3,053 is the first number any risk manager looks at.
Percentage price change per 1% yield move. The 30Y bond's duration of 15.96 years means a 1% rate rise wipes roughly 16% off its value — $160,000 on $1M notional.
The second-order rate effect. High convexity (30Y bond: 370) means gains from a rate fall are slightly larger than losses from an equal rate rise. In large shocks this asymmetry becomes real money.
04 — Stress Testing
We apply each principal component as a shock to the yield curve — scaled to historical 1-sigma and 2-sigma magnitudes — and reprice the entire portfolio. The chart below shows what the curve looks like under each scenario before we even touch the portfolio.
The key insight: the portfolio is net long duration. A parallel rate rise is the dominant risk at ~$29K per 2-sigma event. Slope risk is the second-largest driver at ~$12K. Curvature risk is a distant third at ~$4.5K. This ordering — level, slope, then curvature — is exactly how professional fixed income desks think about and hedge their books.
05 — How It Was Built
The entire analysis runs as a Python pipeline: from raw FRED CSVs through PCA, shock calibration, portfolio pricing, scenario repricing, and finally chart generation. Each step is a self-contained script.
Read nine FRED CSV files, replace missing-value markers, forward-fill isolated gaps (weekends, holidays), and drop dates where data is too sparse.
Compute daily yield changes (not levels), standardise across tenors, run sklearn's PCA. Flip component signs so each PC is economically interpretable.
Measure the historical distribution of daily PC scores. Define ±1 and ±2 standard deviation shocks per component and convert back into basis-point moves at every tenor.
Price each instrument using discounted cash-flow maths against the current interpolated yield curve. Compute DV01, Modified Duration, and Convexity via central-difference bumping.
Apply each shock, reprice every instrument, and record the P&L. Decompose P&L into a first-order duration effect and a second-order convexity contribution.
Generate six publication-quality charts covering yield history, PCA factor shapes, explained variance, shocked curves, a P&L heatmap, and per-instrument attribution.
Python · pandas · NumPy · scikit-learn (PCA) · matplotlib · seaborn · SciPy (interpolation)
Federal Reserve Economic Data (FRED) — series DGS1MO through DGS30. Daily observations, approximately 5,000+ trading days.
Standard semi-annual coupon DCF. Par bonds (coupon = YTM at base curve). Yield interpolated at each maturity using scipy's cubic spline.
Receive-fixed, pay-floating. NPV = PV of fixed leg − notional. At inception the fixed rate equals the 5Y par swap rate so NPV starts at zero; P&L is measured relative to notional for risk scaling.