11 — Scenario Analysis¶
Learning objectives¶
Apply a named, hypothetical scenario to a multi-sleeve portfolio by computing each shock's P&L contribution separately, then combining them — and see shocks that partially offset each other in the same scenario.
Free learning pack¶
reference/concepts/stress_testing.md- CFA active portfolio management page
- PIMCO fixed-income education hub
Do not search for more material until these are insufficient.
Distinction¶
Scenario analysis asks: what would happen if these shocks occurred?
— as opposed to VaR, which asks "what's the loss at a given statistical
confidence?" (see notebooks/active/19_var_and_expected_shortfall.ipynb).
This repo keeps shocks explicit and separate rather than summing them in
one aggregator function — each leg is auditable on its own. Reuse
key_rate_return_approximation (curve), spread_pnl (credit), and
simple multiplication (equity) per leg.
PREDICT¶
A risk-off scenario shocks Treasury yields down (curve rallies) and widens credit spreads and sells off equities, all at once. For a portfolio holding both rate-sensitive bonds and credit risk: does the curve P&L help or hurt in this scenario? Could it be large enough to meaningfully offset the credit and equity losses, or is that wishful thinking?
from pm.scenarios import Scenario
risk_off = Scenario(
name="Risk Off",
curve_bp={"2Y": -25, "5Y": -40, "10Y": -50, "30Y": -55},
ig_spread_bp=75,
hy_spread_bp=200,
equity_return=-0.20,
)
# A hypothetical multi-sleeve portfolio:
rates_sleeve_mv = 12_000_000
rates_sleeve_krds = [1.5, 2.0, 3.0, 1.0] # 2Y, 5Y, 10Y, 30Y, portfolio-level
ig_mv, ig_spread_duration = 8_000_000, 4.8
hy_mv, hy_spread_duration = 3_000_000, 3.2
equity_mv = 5_000_000
MANUAL FIRST¶
Calculate each contribution separately before aggregating:
- Curve:
key_rate_return_approximation(krds, shocks_in_decimal)gives a return; multiply byrates_sleeve_mvfor dollars. Remember to convertcurve_bpvalues from basis points to decimal (divide by 10,000), and match the order ofrates_sleeve_krdsto["2Y","5Y","10Y","30Y"]. - Credit:
spread_pnl(market_value, spread_duration, spread_change_bp)for the IG and HY legs separately. - Equity: just
equity_mv * risk_off.equity_return.
from pm.fixed_income.credit import spread_pnl
from pm.fixed_income.curve import key_rate_return_approximation
tenors = ["2Y", "5Y", "10Y", "30Y"]
# MANUAL FIRST:
curve_shocks_decimal = None # [risk_off.curve_bp[t] / 10000 for t in tenors]
curve_pnl = None # key_rate_return_approximation(...) * rates_sleeve_mv
ig_pnl = None
hy_pnl = None
equity_pnl = None
total_pnl = None
print("curve:", curve_pnl, " IG:", ig_pnl, " HY:", hy_pnl, " equity:", equity_pnl)
print("total risk-off P&L:", total_pnl)
# CHECK (uncomment after your attempt):
# assert curve_pnl > 0, "yields falling should help a duration-long rates sleeve"
# assert ig_pnl < 0 and hy_pnl < 0 and equity_pnl < 0
# import numpy as np
# assert np.isclose(total_pnl, -1_093_000, atol=1000)
# assert abs(curve_pnl) < abs(ig_pnl) + abs(hy_pnl) + abs(equity_pnl), (
# "the curve rally helps, but check whether it's actually enough to offset everything else"
# )
ORAL CHECK¶
Why might falling Treasury yields and widening credit spreads partially offset one another in a credit portfolio? Using your own numbers above: did the offset in this particular scenario end up being large, negligible, or somewhere in between — and what would need to be true about the portfolio's mix for the offset to fully cancel the loss?
Try /tutor stress testing for an adaptive walkthrough.