Tier 2 · Internal Risk Management

Credit Pricing — Hazard-Rate Family

M-133 · lifecycle: monitoring · RAT-133-v1.0.3

Intended Use

Credit Pricing — Hazard-Rate Family Price credit derivatives (CDS, credit-index futures, total return swaps) with hazard-rate bootstrapping for credit exposure and counterparty modeling.

Family-level model covering credit derivative pricing via hazard- rate term-structure bootstrapping from CDS spreads. Used by CECL bond loss model (M-040) for credit-spread inputs and counterparty concentration (M-052) for CVA support. Implementations: 2 FinView L1 + 2 Gold Copy L1.


Components

Inputs, processing, outputs

data sources
DS-001 · DS-011 · DS-027 · DS-031 · DS-056
assumptions
A-030, A-031, A-032, A-033, A-034, A-035, A-090, A-091, A-100, A-101
engines
finview.L1.cds_pricer
finview.L1.credit_index_futures_pricer
goldcopy.L1.cds_pricer
goldcopy.L1.trs_pricer
contracts
credit_pricing_v1
upstream
M-137
dimensions
D2

Methodology & Mechanics

Methodology

M-133 is the Credit Pricing (hazard-rate) family — the FinView-resident model that values single-name credit default swaps (CDS) and credit-index futures using a reduced-form, intensity-based survival model. It is FinView-resident: the engines, contract definitions, and tests all live in [redacted]; nothing in this card depends on InsModel actuarial machinery or on any 10-K / firm-disclosed data.

The family is implemented by two FinView L1 engines plus two Gold Copy L1 challengers (per model_registry.yaml::M-133):

  • finview.L1.cds_pricer — single-name CDS (finlib/derivatives/cds_pricer.py, class CDSPricer).
  • finview.L1.credit_index_futures_pricer — IG/HY credit-index futures as a weighted basket of CDS (finlib/derivatives/credit_index_futures_pricer.py, class CreditIndexFuturesPricer).
  • goldcopy.L1.cds_pricer and goldcopy.L1.trs_pricer — independent challenger implementations (the "gold copy"), whose frozen outputs are pinned in finlib/gold_test_derivatives.py and used as cross-check baselines.

Reduced-form hazard-rate CDS pricing. A CDS exchanges two legs. The protection (default) leg pays the buyer (1 - R) * notional at the moment of default; the premium leg is the running spread the buyer pays (quarterly here) for as long as the reference entity survives. Under the reduced-form model, default arrives as the first jump of a Poisson process with intensity (hazard rate) lambda. With a flat hazard, the survival probability is Q(t) = exp(-lambda * t) and the instantaneous default probability over (t-dt, t] is Q(t-dt) - Q(t).

The engine (CDSPricer.compute_fair_value) builds a quarterly payment grid t_i out to maturity T, computes discount factors DF(t_i) (flat risk_free_rate, or interpolated from a supplied zero curve via finlib.curve_builder.interpolate_curve), and forms:

  • Premium leg PV = spread * dt * sum( DF(t_i) * Q(t_i) ) plus an accrual-on-default term spread * 0.5 * dt * sum( DF(t_i) * (Q(t_{i-1}) - Q(t_i)) ) — the ISDA convention that, on average, default lands mid-coupon so half a period's premium has accrued.
  • Protection leg PV = (1 - R) * sum( DF(t_i) * (Q(t_{i-1}) - Q(t_i)) ).
  • Risky PV01 (RPV01) = dt * sum( DF * Q ) + 0.5 * dt * sum( DF * dQ ) — the present value of 1 unit of running spread on the surviving notional, the credit analogue of a swap's annuity.
  • Par (breakeven) spread = protection_leg_PV / (notional * RPV01), the spread that sets fair value to zero.
  • Fair value / MTM for a protection buyer = protection_leg_PV - premium_leg_PV (sign-flipped for a protection seller).

Hazard bootstrapping from spreads. The hazard itself is not a free parameter — it is bootstrapped from observed credit spreads in finlib/credit_data.py. The closed-form credit-triangle relation lambda = spread / (1 - R) is implemented by get_hazard_rate(rating, recovery, live_spreads=...), and get_survival_probability exponentiates it. For a full term structure, get_piecewise_hazard_curve bootstraps piecewise-constant hazards from the 5-tenor OAS curve (cum_hazard = spread * t / (1 - R), differenced across tenors) — so the family supports both the single-flat-hazard fast path (used by the snapshot) and a bootstrapped term structure. Spreads enter as a live 7-tier FRED ICE-BofA OAS curve (AAA, AA, A, BBB, BB, B, CCC), with 14 S&P notches mapped onto the 7 tiers by linear notch interpolation (_interpolate_notch / _NOTCH_INTERP) and a static maturity multiplier table (_MATURITY_OAS_MULTIPLIER) scaling the base ~5Y spread.

Credit-index futures reuse CDSPricer: CreditIndexFuturesPricer.index_spread prices each rating bucket of the IG (AAA/AA/A/BBB) or HY (BB/B/CCC) composition, takes the weighted-average par spread as the model index spread, and converts spread basis (model_spread - traded_spread) into P&L via the basket RPV01 and a 100 - spread/100 futures-price convention.


Key Assumptions

Key Assumptions and Their Justification

ID Assumption Value / source Justification
A-030 Recovery rate (single-name) 0.40 default (CDSPricer.recovery_rate) ISDA market-standard senior-unsecured recovery; the credit triangle is highly sensitive to R, and 40% is the convention for IG/HY single names. Tier-specific recoveries (0.65 senior-secured, 0.45 senior-unsecured, 0.25 subordinated, 0.90 gov-agency) are available in RECOVERY_RATES for non-default cases.
A-031 Hazard / credit curve lambda = OAS / (1 - R); bootstrapped from 7-tier FRED OAS via get_hazard_rate / get_piecewise_hazard_curve The credit-triangle identity is the standard reduced-form mapping from spread to default intensity. Calibrating to live OAS keeps the model arbitrage-consistent with traded spreads rather than to stale published default tables.
A-032 Discount curve Flat risk_free_rate = 0.04, or interpolated zero curve from supplied discount_factors / tenor_years Separating discounting from the hazard curve is correct reduced-form practice (independence of rates and credit). The flat 4% is a deterministic default for reproducible snapshots; production passes a real DF curve.
A-033 Premium frequency / accrual Quarterly (payment_frequency = 4) + 0.5·dt accrual-on-default Quarterly is the standard CDS coupon frequency; the half-period accrual is the ISDA standard-model treatment of mid-coupon default.
A-034 Notch interpolation Linear blend of adjacent FRED tiers (_NOTCH_INTERP) The live feed publishes 7 tiers; 14 S&P notches must be interpolated. Linear blending between adjacent tiers is transparent and monotone.
A-035 Maturity OAS multiplier Static step table (_MATURITY_OAS_MULTIPLIER, 0.60×@1Y … 1.75×@30Y+) Captures the upward-sloping credit term structure without a full per-tenor live curve. Flagged below as static (not recalibrated daily).
A-090 Credit spread curve (AAA–CCC) spread_recovery domain, assumption_domains.yaml: "FRED ICE BofA OAS indices, daily" (derivation: published_source) The live-OAS curve that feeds calibration: credit_data.py ingests the 7-tier FRED ICE-BofA OAS feed (get_oas_for_rating), and get_hazard_rate maps a rating's OAS into a hazard via the credit triangle. A-090 is the source of every implied hazard the family produces.
A-091 Spread term structure spread_recovery domain: "Linear interpolation between 1Y, 3Y, 5Y, 7Y, 10Y observed OAS" (derivation: data_calibrated) The interpolation method that shapes the OAS curve across tenors before hazard derivation — the methodology side of the live-OAS calibration path in credit_data.py. Distinct from the static _MATURITY_OAS_MULTIPLIER step table (A-035); A-091 is the declared interpolation convention.
A-100 Netting benefit assumption counterparty domain, assumption_domains.yaml: "Full netting within ISDA master agreement" (derivation: published_source) Relates to the CVA-support consumption by M-052: when the family's outputs feed counterparty exposure, full ISDA-master netting is assumed. Not exercised by the standalone single-name CDS snapshot; load-bearing only on the CVA-support path.
A-101 (counterparty domain — "Collateral posting threshold") Counterparty collateral threshold counterparty domain, assumption_domains.yaml: "Per counterparty CSA terms (from D2 filings)" (derivation: published_source) The counterparty/CSA-side A-101 — disambiguated from the unrelated mortality-domain A-101 ("GMxB longevity stress", a Solvency II variable-annuity entry that does not bind this model). Like A-100, it relates to the CVA-support use consumed by M-052, not to the core CDS pricing path.

Prose. The single most load-bearing assumption is the recovery rate (A-030): because hazard is derived as spread/(1-R), a change in R rescales both the implied hazard and the protection leg's (1-R) payoff, so recovery sits on both sides of the valuation. The credit/hazard curve (A-031) is the model's calibration target — the family deliberately reads hazard from market spreads (live FRED OAS), not from Moody's published cumulative default tables (those exist in credit_data.py as MOODY_CUMULATIVE_PD but feed PD/transition utilities, not the CDS pricing path). The discount curve (A-032) is kept independent of credit, which is the correct reduced-form factorization; the snapshot uses a flat 4% purely for determinism. Per model_registry.yaml::M-133 and data_source_registry.yaml::DS-056, the intended live single-name/CDX feed (DTCC TIWPRR + FRED CDX) is registered planned, not yet wired — until that fetcher lands the family calibrates off the BAML/ICE-BofA OAS proxy, and this proxy substitution is surfaced here per the registry's documentation requirement.

The four additional declared assumptions split cleanly by which path they bind. A-090 (credit spread curve AAA–CCC) and A-091 (spread term structure) govern the live-OAS calibration that feeds get_oas_for_ratingget_hazard_rate in credit_data.py — they are the source-and-interpolation pair sitting directly under A-031's credit-triangle mapping. A-100 (netting benefit) and A-101 (counterparty, the counterparty-domain "Collateral posting threshold" entry — not the like-numbered mortality-domain GMxB-longevity A-101) are not exercised by the standalone CDS/index pricing path; they bind only the CVA-support consumption by M-052, where the family's credit spreads inform counterparty exposure under ISDA netting and CSA collateral terms.


Output Snapshot

Output Snapshot

Deterministic single-name run of finview.L1.cds_pricer (CDSPricer) — reproducible, requires no live market data. The credit curve is the frozen 7-tier OAS dict the engine's own unit tests pin to (tests/test_cds_pricer.py::SAMPLE_LIVE_SPREADS); convention is python scripts/model_snapshots.py M-133 (the FinView snapshot harness), asserted by tests/test_cds_pricer.py.

Input: 5Y BBB single-name CDS · 100 bps running spread · 40% recovery · $10,000,000 notional · buy-protection · quarterly premium · 4% flat discount.

output value meaning
CDS fair value / MTM (buy protection) 128,433.32 protection bought at 100 bps vs a 130 bps par market → positive MTM to the buyer
upfront (% of notional) 1.2843% fair value expressed as an ISDA-style upfront on $10M notional
par (breakeven) spread 129.9997 bps spread that zeroes fair value — recovers the BBB market OAS (130 bps)
risky PV01 / RPV01 4.281156 PV of 1 unit of running spread on the surviving notional (credit annuity)
protection (default) leg PV 556,548.94 (1-R)·sum(DF·dQ) — expected discounted default payout
premium leg PV (incl. accrual-on-default) 428,115.62 spread·dt·sum(DF·Q) + half-period accrual
hazard rate (lambda) 0.021667 130 bps / (1 - 0.40) / 1e4 — the credit-triangle intensity
5Y survival probability Q(5) 0.897328 exp(-lambda·5) — entity survives the full term with ~89.7% probability

The result is internally consistent: the par spread recovers the 130 bps BBB market OAS to four significant figures, confirming the breakeven-spread inversion is the exact left-inverse of the leg PVs (the engine's test_par_spread_consistency re-prices at par and asserts |FV| < 100). Fair value equals protection_leg_PV - premium_leg_PV = 556,548.94 - 428,115.62 = 128,433.32: because the contract pays only 100 bps while the entity's fair spread is ~130 bps, the protection buyer is under-paying and holds a positive mark. These numbers also tie out to the Gold Copy challenger baseline in finlib/gold_test_derivatives.py (CME_CDS_IG_5Y: par_spread_bps 129.9997, rpv01 4.3443, hazard_rate 0.0217) — the par spread and hazard match exactly; RPV01 differs only because the gold-copy position is $1M notional with a slightly different payment grid, not a methodology gap.

The snapshot was executed under two venvs and produced byte-identical output: FinView ([redacted]) and InsModel ([redacted]). Cross-repo determinism holds because the pricing path depends only on numpy plus the FinView source on sys.path.

Captured 2026-06-04 · deterministic, no live data.


Limitations

Limitations and Known Gaps

  1. No live CDS/CDX feed (DS-056 planned, not wired). Per model_registry.yaml::M-133 and data_source_registry.yaml::DS-056, the intended single-name/index feed (DTCC TIWPRR + FRED CDX) is registered planned. The family currently calibrates hazard off the BAML/ICE-BofA OAS proxy, not from traded CDS quotes. The snapshot goes one step further and uses a frozen test OAS dict for determinism. Implied hazards are therefore proxy-derived, and basis between cash-bond OAS and traded CDS spread is unmodeled.

  2. Flat single-name hazard in the primary CDS path. CDSPricer.compute_fair_value uses one flat lambda from get_hazard_rate for the whole term. A bootstrapped piecewise-constant term structure exists (get_piecewise_hazard_curve) but is not invoked by the single-name pricer — only the maturity-multiplier table approximates curve shape. For a 5Y CDS this is mild; for long-dated or steep-curve names it understates term-structure effects.

  3. Static maturity multipliers (A-035). _MATURITY_OAS_MULTIPLIER is a fixed step table, not recalibrated to the live term structure each day, as credit_data.py's own docstring notes ("maturity multipliers are static"). The 5Y point is the un-scaled base (1.00×), so the canonical snapshot is unaffected; off-tenor pricing inherits the static shape.

  4. Constant recovery; no recovery–default correlation. Recovery is a fixed input (A-030, default 0.40) with no stochastic recovery and no wrong-way correlation between recovery and default intensity. Because hazard is spread/(1-R), recovery error propagates into both implied default probability and the protection payoff — the dominant valuation sensitivity.

  5. Index pricer is a weighted single-name basket, not a tranche/correlation model. CreditIndexFuturesPricer averages bucket par spreads with a fixed IG/HY composition; it has no default correlation / copula and cannot price index tranches or capture index-vs-intrinsic (skew) basis.

  6. No accrued-coupon / standard-coupon (100/500 bps) settlement mechanics. The pricer values the running-spread MTM and reports an upfront-equivalent, but does not implement the full ISDA standard-coupon + accrued + upfront cash-settlement decomposition used for post-2009 standardized CDS.

  7. Cross-repo (FinView-resident) governance. This is a Tier-2 model under §6.3 trigger (b) (feeds Tier-1 CECL M-040 and Counterparty M-052). It is FinView-resident but governed from the InsightALM modelling registry; its documentation_pack in model_registry.yaml currently shows model_card: missing, validation_evidence: missing, change_log: missing, and last_validated_on: null — this card closes the model-card gap; validation-evidence and an independent challenger sign-off remain under-built (BV-032 honesty: stated as gaps, not claimed as complete). The cross-repo governance risk itself is the open 2L finding INV-032 (shared with M-040 / M-130–M-137): the FinView-resident finlib pricing engines are reached from InsModel consumers via sys.path with no packaged/versioned interface and no sibling-aware CI drift gate, so a finlib signature change would not fail InsModel loudly. This card cross-references INV-032; it does not claim the packaged interface or drift gate exists — building them is a tracked remediation item (see below).

Tracked for ratification (not applied in this documentation pass). The following are output-changing / modeling-code / governance items left for ratification, noted here for transparency: produce an independent (2L) validation-evidence pack + peer-review sign-off (par-spread inversion, monotonicity, RPV01, symmetry, plus an effective-challenge write-up vs the Gold Copy challenger) and set last_validated_on / peer_review.signed_off_by / ratification_ref; wire the live DS-056 (DTCC TIWPRR + FRED CDX) single-name/CDX feed so hazard calibration moves off the BAML/ICE-BofA OAS proxy (the snapshot currently uses a frozen test OAS dict); implement the INV-032 remediation (packaged/versioned finlib interface + sibling-aware CI drift gate); optionally invoke get_piecewise_hazard_curve in the single-name CDS path instead of the flat get_hazard_rate, and/or recalibrate the static _MATURITY_OAS_MULTIPLIER table to a live term structure (Limitations 2 and 3). No model outputs were changed in this pass.


Validation Evidence

Validation Packet

Check Where What it asserts
Par-spread consistency tests/test_cds_pricer.py::test_par_spread_consistency Re-pricing at the engine's own par spread yields |fair_value| < 100 on $1M notional — the breakeven inversion is the exact left-inverse of the leg PVs.
Spread-widening monotonicity ::test_protection_value_on_spread_widening A protection buyer paying 50 bps when the BBB market is ~130 bps holds positive value (FV > 0).
RPV01 positivity ::test_rpv01_positive Risky annuity is strictly positive.
Buy/sell symmetry ::test_sell_protection_symmetry buy_FV + sell_FV ≈ 0 (< $1) — the two directions are exact mirror images.
Output completeness ::test_result_keys All of fair_value, premium_leg_pv, protection_leg_pv, par_spread_bps, rpv01, hazard_rate are returned.
Discount-curve path ::test_with_curve Pricing with an interpolated zero curve (finlib.curve_builder.interpolate_curve) succeeds and keeps RPV01 > 0.
Index basket pricing tests/test_credit_index_futures_pricer.py CreditIndexFuturesPricer weighted-basket spread, basis P&L, and 100 - spread/100 price convention.
Gold Copy challenger cross-check finlib/gold_test_derivatives.py::CME_CDS_IG_5Y Frozen challenger baseline (par_spread_bps 129.9997, hazard_rate 0.0217) matches the FinView engine — par spread and hazard tie out exactly.
Cross-venv determinism this card's Output Snapshot Byte-identical output under FinView and InsModel venvs.
Engine binding .governance/binding_manifest.yaml::finview.L1.cds_pricer @engine('finview.L1.cds_pricer') decorator binds CDSPricer to its governed engine contract (finlib/governance.engine).

Test result at capture: 21 passed (pytest tests/test_cds_pricer.py tests/test_credit_index_futures_pricer.py). Independent (2L) validation sign-off and a formal validation-evidence pack are not yet producedmodel_registry.yaml::M-133 shows validation_evidence: missing, peer_review.status: pending, last_validated_on: null.


References

References

Standards / methodology: - ISDA Standard CDS Model — the standardized hazard-rate / survival-curve CDS pricing and upfront-conversion model (premium leg vs protection leg, half-period accrual-on-default). Implemented in finlib/derivatives/cds_pricer.py. - J. Hull & A. White, Valuing Credit Default Swaps (reduced-form intensity model) — the lambda, Q(t) = exp(-lambda·t) survival formulation. - D. Duffie & K. Singleton, Credit Risk: Pricing, Measurement, and Management — reduced-form / intensity-based default modeling and the spread ≈ lambda·(1-R) credit-triangle relation. - O'Kane & Turnbull, "Valuation of Credit Default Swaps" (Lehman QCR) — RPV01 / risky annuity and accrual-on-default conventions used in the engine.

Engine source (FinView-resident): - finlib/derivatives/cds_pricer.pyCDSPricer, engine finview.L1.cds_pricer. - finlib/derivatives/credit_index_futures_pricer.pyCreditIndexFuturesPricer, engine finview.L1.credit_index_futures_pricer. - finlib/credit_data.pyget_hazard_rate, get_survival_probability, get_piecewise_hazard_curve, notch interpolation, FRED OAS calibration; engine finview.L0.credit_data. - finlib/gold_test_derivatives.py — Gold Copy challenger baselines (goldcopy.L1.cds_pricer, goldcopy.L1.trs_pricer).

Tests: - tests/test_cds_pricer.py — 6 single-name CDS tests (par consistency, monotonicity, RPV01, symmetry, keys, curve path). - tests/test_credit_index_futures_pricer.py — credit-index futures basket tests.

Internal: - insightalm/modelling/model_registry.yaml::M-133 — registry entry, Tier-2 materiality, engine list, lifecycle, DS-056 proxy note. - insightalm/modelling/data_source_registry.yaml::DS-056 — planned DTCC TIWPRR + FRED CDX feed (PL-07 critical gap). - insightalm/modelling/model_cards/M-001.md — model-card depth exemplar.


Change Log

Change Log

Card change history. Code-side change history lives in git log of the component files.

  • 2026-05-08 — stub created from registry data per Decision 023 Phase 5 / B-07.
  • 2026-06-04 — Tier-2 hand-authoring of Methodology, Key Assumptions and Their Justification, Output Snapshot, Limitations and Known Gaps (×7), Validation Packet, and References from the FinView cds_pricer.py / credit_index_futures_pricer.py / credit_data.py engines + gold_test_derivatives.py challenger + bound A-NNN entries. Stub marker advanced from to. No model outputs, back-test numbers, or validation results were fabricated.
  • 2026-06-06 — documentation-only accuracy pass. Added the missing Standards Coverage section (ASOP 56 / SR 26-2 §6.3 trigger (b) Tier-2 / internal; no statutory/GAAP accounting standard asserted for this pricing family). Extended Key Assumptions to cover the four declared-but-undocumented entries A-090 / A-091 (live FRED-OAS calibration path) and A-100 / A-101 (CVA-support consumption by M-052), with A-101 disambiguated to the counterparty-domain "Collateral posting threshold" entry vs the unrelated mortality-domain GMxB-longevity A-101. Cited open 2L finding INV-032 (cross-repo finlib drift gate) by ID in Limitation 7, matching the M-040 / M-130 / M-131 pattern, and added a "Tracked for ratification" note enumerating the gated items. No model outputs, back-test numbers, or validation results were fabricated or changed.

2L Inventory Review

Open findings (3)

Independent 2nd-line review (INV-2026-06) — implemented capability vs registered scope. Each carries a recommended fix and is tracked in insightalm-mrm until closed.

HIGH INV-029 · P5 · validation-gap

Validation evidence + change logs missing across most of the inventory

Only M-001/M-020/M-050 carried full documentation packs before this pass. Most models record validation_evidence: missing and change_log: missing with peer_review: pending. Gold tests freeze behaviour but many assert only structural invariants (e.g. reserve>0), not correctness against external truth. The flagship T0-vs-10-K match is circular (BV-032).

Recommendation: For each Tier-1 model: produce a validation-evidence pack (back-test vs disclosed results once BV-032 re-calibration lands, sensitivity suite, challenger comparison), a change log, and a 2L ratification. Sequence behind BV-032 (firm-data) for anything needing 10-K reconciliation.

low FIND-133-CARD-STALE · ·

M-133.md Limitation 7 + the "Tracked for ratification" note still describe INV-032 (the packaged finlib interface + sibling-aware drift gate) as NOT yet built. Both now exist and are merged (FinView #111 / InsModel #90); this re-review clears COND-002 and approves the model.

Recommendation: Update the card to mark INV-032 closed (packaged finlib.__version__ 1.0.0 + the check_finlib_drift.py gate now exist); bump registry lifecycle.ratification_ref to RAT-133-v1.0.3 and set peer_review.status: signed. Doc/metadata only. (FIND-133-01.)

LOW INV-032 · cross-repo · cross-repo

FinView-resident pricing/CECL engines reached cross-repo with no drift gate

M-040 (CECL) and M-130-137 (asset pricing) are FinView-resident, consumed by InsModel only via sys.path insertion. No packaged interface or drift gate — a finlib signature change silently breaks the InsModel snapshot harness / consumers.

Recommendation: Define a packaged interface (or a versioned contract) for the FinView pricing library and a CI drift gate, so cross-repo consumers fail loudly on signature change. Tie to D041 ownership.


Validation Coverage

Per-tier expectations

Per MRM Framework §10.2 + §10.3, this model's regulatory_frameworks tag list activates the following overlays:

asop_56 sr_26_2 internal
component tier-2 expectation status
Registry entry required present
Model card (§10.5 doc pack) required present
Validation evidence required present
Change log required present
Independent effective challenge (2L) required attested

Ratification

Ratified — RAT-133-v1.0.3

Latest ratification on file: RAT-133-v1.0.3. Authored by 2L (mrm-peer-reviewer) per Decision 028 charter §5 Pattern A.