CECL Bond Loss Model Estimate lifetime expected credit losses on the bond portfolio for ASC 326 financial reporting.
Per §12.2 worked example. PD-LGD-EAD framework with vendor PD inputs from Moody's. Sensitivity primarily to PD/LGD inputs and scenario stress overlays.
Inputs, processing, outputs
Methodology
M-040 implements the ASC 326 Current Expected Credit Loss (CECL) standard for the fixed-income (bond) portfolio. CECL replaced the prior incurred-loss model with a lifetime expected-loss measure: at every reporting date the allowance equals the credit losses expected over the entire remaining contractual life of each instrument, recognized up front rather than when a loss event becomes probable.
The engine is FinView-resident (cross-repo): the implementation is finlib.cecl.CECLCalculator (governance id finview.L3.cecl_calculator) in [redacted], consuming the credit-parameter library finlib.credit_data (governance id finview.L0.credit_data). The CECL allowance it produces is consumed by the insurance capital/reserve stack (the bond-portfolio expected-loss feeds invested-asset impairment and capital adequacy), but the source code does not live in InsModel — see Limitation 1.
The model uses a discounted-cash-flow loss method built on a PD × LGD × EAD decomposition rendered period-by-period (ASC 326-20-30-4 permits a DCF method; the period losses are PV-discounted at the instrument's effective interest rate):
-
Government/agency carve-out. Instruments rated
AAA/AA+and in sectorGovernment/Agencyare assigned zero ECL (_is_government_bond), reflecting the ASC 326 practical recognition that US sovereign/agency exposures carry no expected credit loss. -
LGD (loss given default).
LGD = 1 − recovery_rate. Recovery defaults by seniority/sector fromRECOVERY_RATES: senior-secured 0.65, senior-unsecured 0.45 (the corporate-bond default), subordinated 0.25, government/agency 0.90. A generic BBB corporate is therefore LGD = 0.55. -
PD / hazard rate. With
pd_source='moody',get_counterparty_pdmaps the S&P-scale rating to its Moody's equivalent (BBB → Baa) and reads the Moody's published cumulative-default tableMOODY_CUMULATIVE_PD(1y/3y/5y/10y), interpolating to the bond's maturity. A continuous hazard rate is backed out asλ = −ln(1 − PD_cum) / T. Withpd_source='oas'the hazard is instead derived from live FRED/OAS spreads (λ = spread / (1 − recovery)); the snapshot uses the deterministic Moody's path so no live data is touched. -
Stress multiplier. The hazard is scaled by a scenario factor from
PD_MULTIPLIERS(base 1.0, mild 1.5, stress 2.0, severe 3.0) before cash-flow generation, giving CCAR-style scenario sensitivity. -
Cash-flow loss schedule (EAD).
BondCashflows.generate_schedule(hazard_rate, recovery_rate)produces a period-by-period schedule carryingdefault_amountandrecoveryper coupon period (semiannual by default). The period loss isdefault_amount − recovery, and each is PV-discounted at the effective interest rate (the coupon rate as the ASC 326 EIR proxy):pv_loss = loss / (1 + r)^t. -
Lifetime ECL & allowance.
lifetime_ecl = Σ pv_lossover all periods to maturity;ecl_rate = lifetime_ecl / face_value. At portfolio levelcompute_portfolio_eclsums per-bond ECL and rolls it upecl_by_ratingandecl_by_sector. The lifetime ECL is the CECL allowance.
Key Assumptions and Their Justification
| ID | Assumption | Value / source | Justification |
|---|---|---|---|
| K-1 | PD source | Moody's published cumulative default rates (MOODY_CUMULATIVE_PD, 1y/3y/5y/10y by Aaa…Caa) |
Industry-standard rating-agency default experience (Moody's Annual Default Study, 1970–2023); transparent, auditable, no live-data dependency. See Q-07 / Limitation 4. |
| K-2 | Rating crosswalk | S&P scale → Moody's (_SP_TO_MOODY); 14 notches collapse to 7 Moody's buckets |
Lets a single S&P-scale portfolio feed Moody's tables; BBB→Baa, A→A, etc. Loses sub-notch granularity (Limitation 3). |
| K-3 | LGD / recovery | 1 − recovery; recovery by seniority: sr-secured 0.65, sr-unsecured 0.45, sub 0.25, gov/agency 0.90 |
Standard market recovery assumptions; senior-unsecured 0.45 is the long-run corporate average. Static, not name-specific (Limitation 2). |
| K-4 | Discount rate (EIR) | Effective interest rate ≈ coupon rate (fallback 5%) | ASC 326-20-30 requires DCF losses discounted at the instrument's EIR; coupon is the available proxy in the canonical input (Limitation 5). |
| K-5 | Reasonable-and-supportable horizon | Full contractual life (to maturity) | CECL is a lifetime measure; the model projects period losses over the entire remaining life rather than a truncated R&S window. |
| K-6 | Reversion to historical | Implicit — Moody's long-run average PDs are the through-the-cycle mean | ASC 326 requires reversion to historical loss experience beyond the R&S horizon; using through-the-cycle Moody's averages means the model is effectively at the reverted mean already (Limitation 6). |
| K-7 | Government/agency ECL | Zero for AAA/AA+ Government/Agency | US sovereign/agency exposures are treated as carrying no expected credit loss under ASC 326 practical application. |
| K-8 | Scenario stress | Hazard × {1.0, 1.5, 2.0, 3.0} | Multiplicative hazard stress provides base/mild/stress/severe scenarios for CCAR-style sensitivity without re-sourcing PDs. |
Prose. The two load-bearing assumptions are the PD table (K-1) and LGD (K-3). The Moody's cumulative-default table is the spine of the lifetime measure: PD at maturity is interpolated from the four published tenor points (e.g. Baa 10y = 3.27%) and converted to a continuous hazard. Because these are through-the-cycle averages, the model satisfies CECL's reversion requirement (K-6) almost by construction — there is no explicit R&S-then-revert two-segment curve; the single Moody's curve is already the long-run mean. The reasonable-and-supportable horizon (K-5) is the full contractual life rather than the more common 1–2 year R&S window, which is conservative and avoids a reversion-segment modeling choice but reduces sensitivity to near-term macro forecasts. LGD is seniority-bucketed, not name-level — a known simplification (Limitation 2).
Output Snapshot
Deterministic run of CECLCalculator (finview.L3.cecl_calculator, FinView) — reproducible, requires no live firm data (BV-032 immune); Moody's published PD table, base scenario, no FRED/OAS live spreads. Reproduce with python scripts/model_snapshots.py M-040; the underlying engine is asserted by FinView tests/test_cecl.py (6 tests, all passing — test_bbb_10y_ecl_reasonable bounds this exact BBB 10Y bond's ECL to (5k, 30k) and asserts LGD = 0.55).
Input: canonical 2-bond portfolio — UST $1M par · 4.00% cpn · 10y · AAA/Government (zero-ECL) + BBB corporate $1M par · 5.25% cpn · 10y · senior-unsecured.
| output | value | meaning |
|---|---|---|
| par / amortized cost (BBB bond) | 1,000,000.00 | exposure at default (EAD) basis |
| pd_cumulative (10y, Moody's Baa) | 0.0327 | lifetime cumulative default probability |
| lgd (1 − recovery, sr unsecured) | 0.55 | loss given default |
| hazard_rate (λ, base) | 0.00332 | continuous default intensity backed from PD |
| ecl_rate (lifetime_ecl / par) | 0.0134 | lifetime ECL as % of par |
| lifetime_ecl (BBB allowance) | 13,387.20 | CECL lifetime expected credit loss, PV-discounted |
| ecl AAA/Government (zero-ECL rule) | 0.00 | government carve-out applied |
| total_ecl (portfolio allowance) | 13,387.20 | portfolio CECL allowance |
| ecl_rate (portfolio) | 0.0067 | portfolio allowance / $2M total par |
The undiscounted PD×LGD×EAD product for the BBB bond is 0.0327 × 0.55 × $1M ≈ $17,985; the reported $13,387 is lower because losses are spread across 20 semiannual periods and PV-discounted at the 5.25% EIR — exactly the ASC 326 DCF mechanic. The Treasury contributes zero by the government carve-out, so the portfolio allowance equals the single corporate ECL.
Captured 2026-06-04 · deterministic, no live data.
Limitations and Known Gaps
-
Cross-repo FinView ownership. The engine is FinView-resident (
finlib.cecl.CECLCalculator,finview.L3.cecl_calculator), not InsModel. The insurance capital/reserve stack consumes its CECL allowance but does not own the code, version, or test suite. Governance crosses a repo boundary; any change-control, MRM ratification, and registry binding for M-040 must reference FinView artifacts, and the model card's authority over the implementation is by reference only. -
LGD is seniority-bucketed, not name-level. Recovery is a static lookup by seniority/sector (
RECOVERY_RATES), not calibrated per issuer, vintage, or collateral. Senior-unsecured 0.45 is a long-run market average; idiosyncratic recovery dispersion is not captured. -
14 S&P notches collapse to 7 Moody's buckets. The
_SP_TO_MOODYcrosswalk maps e.g. BBB+/BBB/BBB- all to Baa, so sub-notch credit differentiation is lost in the Moody's PD path. (The OAS path preserves notch detail via interpolation, but the deterministic snapshot uses Moody's.) -
Q-07 — Moody's PD is vendor data, not a promoted vendor model. The cumulative-default table is hard-coded vendor data in
finlib.credit_data.MOODY_CUMULATIVE_PD. It has not been promoted through the vendor-model governance process (no vendor-model registry entry, no independent validation packet, no provenance/version pin to a specific Moody's Annual Default Study release). It is consumed as a constant. This is an open governance gap (Q-07). -
EIR proxied by coupon rate. ASC 326 requires DCF discounting at the instrument's effective interest rate; the engine uses the coupon rate (fallback 5%) as the EIR proxy. For bonds bought at a material premium/discount to par the true EIR diverges from the coupon, biasing the PV of losses.
-
No explicit reasonable-and-supportable / reversion segmentation. CECL contemplates a forecast-based R&S horizon that reverts to historical experience. The model uses through-the-cycle Moody's averages over the full life — effectively "reverted everywhere." This is defensible but means near-term macro forecasts and a documented reversion technique are absent; the scenario multiplier (K-8) is the only forward-looking lever.
-
No 10-K / live-firm validation. The snapshot is a deterministic engine demonstration on a canonical synthetic portfolio. No claim is made that these figures reconcile to any issuer's 10-K disclosed CECL allowance (BV-032 firm-data divergence is avoided by not using live data).
-
Mid-period timing / amortization simplifications.
BondCashflowsrecognizes default and recovery at period end (not mid-period) and the canonical input is a bullet (amortization='bullet'); amortizing or prepaying (CPR) structures are supported by the engine but not exercised in the canonical snapshot.
Validation Packet
| Check | Where | Evidence |
|---|---|---|
| BBB 10Y ECL in reasonable band | FinView tests/test_cecl.py::test_bbb_10y_ecl_reasonable |
asserts 5,000 < lifetime_ecl < 30,000 and lgd == 0.55 (snapshot: 13,387.20 / 0.55) ✓ |
| Government bond zero-ECL | test_government_bond_zero_ecl |
AAA/Government → lifetime_ecl == 0.0, ecl_rate == 0.0 ✓ |
| Stress monotonicity | test_stress_increases_ecl |
stress-scenario ECL > base-scenario ECL ✓ |
| Rating monotonicity | test_higher_rating_lower_ecl |
A-rated ECL < BBB ECL ✓ |
| Portfolio additivity | test_portfolio_ecl |
total_ecl = Σ bond ECL; AAA bucket 0, BBB bucket > 0; 2 bond_detail rows ✓ |
| OAS PD path | test_oas_pd_source |
live_spreads → positive ECL and hazard_rate ✓ |
| Deterministic snapshot | scripts/model_snapshots.py M-040 |
reproduces table above with no live data ✓ |
| Full CECL suite | pytest tests/test_cecl.py |
6 passed ✓ |
Sanity invariant: PV-discounted lifetime ECL ($13,387) < undiscounted PD×LGD×EAD ($17,985), consistent with ASC 326 DCF discounting over the bond's life.
Aggregate-risk view (per MRM §10.5 item 8): the CECL allowance is consumed downstream by the insurance capital/reserve stack (invested-asset impairment / capital adequacy). M-040 shares upstream component-model families M-130 (bond pricing) and M-133 (credit-pricing hazard-rate) with M-052 (Counterparty Concentration & Network Risk) per model_registry.yaml — so a change in those shared pricing/credit-spread components propagates to both the CECL allowance here and counterparty exposure in M-052. (The finview.L0.credit_data engine itself is bound to M-040 only.)
Open MRM items: Q-07 (Moody's PD vendor-data promotion) and Limitation 1 (cross-repo FinView ownership / change-control binding) are unresolved and should be tracked as governance issues. A full validation evidence pack with outcomes back-testing of the CECL allowance vs realized credit losses (COND-001 / RAT-040), independent effective-challenge / ratification closure (RAT-040 → approved), Q-07 vendor-model promotion, and the INV-032 packaged-interface / CI drift gate for the FinView-resident engines are gated modeling/governance items tracked for ratification — out of scope for this documentation pass.
References
Regulatory / accounting: - FASB ASC 326 (CECL) — Financial Instruments — Credit Losses; ASC 326-20 (measurement of expected credit losses on amortized-cost assets), 326-20-30 (DCF method, discount at effective interest rate). - ASU 2016-13 — the standard that introduced CECL.
Vendor data:
- Moody's Annual Default Study (1970–2023) — cumulative default rates by rating and the 1-year transition matrix, encoded in finlib.credit_data.MOODY_CUMULATIVE_PD / MOODY_TRANSITION_MATRIX. (Vendor data, not a promoted vendor model — Q-07.)
Engine source (FinView-resident, cross-repo):
- [redacted] — CECLCalculator (finview.L3.cecl_calculator): compute_bond_ecl, compute_portfolio_ecl.
- [redacted] — finview.L0.credit_data: MOODY_CUMULATIVE_PD, RECOVERY_RATES, _SP_TO_MOODY, get_counterparty_pd, _pd_from_moody, _pd_from_oas.
- [redacted] — BondCashflows.generate_schedule (period-by-period default/recovery cash-flow loss schedule).
Tests:
- [redacted] — 6 tests (government zero-ECL, BBB band, stress monotonicity, portfolio additivity, OAS path, rating monotonicity).
Internal: - BV-032 — firm-data divergence (avoided here by deterministic, no-live-data snapshot). - Q-07 — Moody's PD as vendor data not yet promoted to a governed vendor model.
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-05/06 — Tier-1 hand-authoring of Methodology, Key Assumptions and Their Justification, Output Snapshot, Limitations and Known Gaps, Validation Packet, and References from the FinView CECL engine code (
finlib/cecl.py,finlib/credit_data.py,finlib/bond_cashflows.py) + the deterministicmodel_snapshots.py M-040run. Stub marker advanced to ``. - 2026-06-06 — documentation pass: added Standards Coverage (ASC 326-20 / 326-20-30-4, ASOP 56, PCAOB AS 2501 mapped to the engine) and Dependencies (no upstream M-NNN models; relies on
finview.L3.cecl_calculator+finview.L0.credit_data; shares M-130 / M-133 component families with M-052; allowance consumed by the capital/reserve stack) sections, and an Aggregate-risk view note (§10.5 item 8). Doc-currency baseline confirmed: stamp present (model_doc_stamps.yaml), statuscurrent, fingerprint39e093d0aca90fe6, no identity/currency drift. Registry-grounded only; no model outputs changed.
Open findings (2)
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.
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.
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.
Per-tier expectations
Per MRM Framework §10.2 + §10.3, this model's regulatory_frameworks tag list activates the following overlays:
| component | tier-1 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 | pending |
Conditionally approved — RAT-040-v1.0.2
2L issued RAT-040-v1.0.2 with the following conditions outstanding. Bridge audit Rule 2 treats this as a WARN; the model can remain in monitoring with the conditions on record.
| id | deadline | condition |
|---|---|---|
| COND-003 | 2026-08-23 | The M-040 validation evidence pack is structurally complete and real, but one §10.5 item-5 sub-requirement is honestly disclosed as PENDING: a backtest of the CECL allowance against a realized-credit-loss outcome (or a disclosed-issuer CECL allowance), which §10.5 expects "annual or each reporting period." No such reconciliation dataset yet exists on the platform (deliberately avoided to keep the deterministic snapshot BV-032-immune), so the sub-item is intentionally absent rather than synthesized — see pack §4 and card Limitation 7. The backtest_results.yaml 'covered' label (BT-005..007) was independently verified to be a DFAST scenario-stress plausibility check, NOT a realized-outcome reconciliation, and does NOT clear this condition. For a Tier-1 model this is an adequacy gap in validation evidence; per charter §4.2 the model is held at conditionally_approved until a realized-loss / disclosed-allowance reconciliation is sourced and a back-test run is added (within tolerance, or with the residual explained). |