IRB Approach¶
The Internal Ratings-Based (IRB) approach allows banks with regulatory approval to use their own risk estimates for calculating RWA. This provides greater risk sensitivity than the Standardised Approach.
Overview¶
Two IRB variants are available:
| Approach | PD | LGD | EAD | CCF |
|---|---|---|---|---|
| Foundation IRB (F-IRB) | Bank | Supervisory | Supervisory | Supervisory |
| Advanced IRB (A-IRB) | Bank | Bank | Bank | Bank |
Basel 3.1 Restrictions
Under Basel 3.1, A-IRB is no longer permitted for: - Large corporates (revenue > £500m) - Banks/Financial Institutions - Equity exposures
IRB Formula¶
The core IRB formula calculates the capital requirement (K). See irb/formulas.py for the pure Polars implementation.
Where: - N() = Standard normal cumulative distribution function - G() = Inverse standard normal distribution function - R = Asset correlation - PD = Probability of Default - LGD = Loss Given Default - MA = Maturity Adjustment (for non-retail)
Then:
Stats Backend¶
The IRB formulas require statistical functions (normal CDF and inverse CDF). The calculator uses polars-normal-stats for native Polars CDF/PPF expressions.
Capital K Formula Implementation (formulas.py)
See the _polars_capital_k_expr() function in src/rwa_calc/engine/irb/formulas.py
for the full Polars expression implementation.
Risk Parameters¶
Probability of Default (PD)¶
PD is the likelihood of default within one year.
Floors:
| Exposure Class | CRR | Basel 3.1 |
|---|---|---|
| Corporate | 0.03% | 0.05% |
| Large Corporate | 0.03% | 0.05% |
| Bank/Institution | 0.03% | 0.05% |
| Retail Mortgage | 0.03% | 0.05% |
| Retail QRRE (Transactor) | 0.03% | 0.03% |
| Retail QRRE (Revolver) | 0.03% | 0.10% |
| Retail Other | 0.03% | 0.05% |
Loss Given Default (LGD)¶
LGD is the percentage of exposure lost after recoveries.
F-IRB Supervisory LGD:
| Exposure Type | CRR | Basel 3.1 |
|---|---|---|
| Senior Unsecured | 45% | 40% |
| Subordinated | 75% | 75% |
| Secured by Financial Collateral | 0% | 0% |
| Secured by Receivables | 35% | 20% |
| Secured by CRE/RRE | 35% | 20% |
| Secured by Other Collateral | 40% | 25% |
A-IRB LGD Floors (Basel 3.1 only, PRA PS1/26):
| Collateral Type | LGD Floor |
|---|---|
| Unsecured Senior | 25% |
| Unsecured Subordinated | 50% |
| Financial Collateral | 0% |
| Receivables | 10% |
| Commercial Real Estate | 10% |
| Residential Real Estate | 5% |
| Other Physical | 15% |
Exposure at Default (EAD)¶
F-IRB:
- On-Balance Sheet: Gross carrying amount
- Off-Balance Sheet: Regulatory CCFs apply based on risk_type:
| Risk Type | SA CCF | F-IRB CCF | Notes |
|---|---|---|---|
| FR (full_risk) | 100% | 100% | Guarantees, credit substitutes |
| MR (medium_risk) | 50% | 75% | Committed undrawn, NIFs, RUFs |
| MLR (medium_low_risk) | 20% | 75% | Documentary credits, trade finance |
| LR (low_risk) | 0% | 0% | Unconditionally cancellable |
CRR Art. 166(8): Under F-IRB, MR and MLR categories both use 75% CCF.
CRR Art. 166(9) Exception: Short-term letters of credit arising from the movement of goods retain 20% CCF under F-IRB. Flag these exposures with is_short_term_trade_lc = True.
A-IRB:
- Bank estimates EAD using internal models
- Provide modelled CCF in ccf_modelled column (0.0-1.5, can exceed 100% for Retail IRB)
- When ccf_modelled is provided, it takes precedence over risk_type lookup
- Subject to CCF floors under Basel 3.1
Maturity (M)¶
Effective maturity affects capital through the maturity adjustment.
- Range: 1 year (floor) to 5 years (cap)
- Retail exemption: No maturity adjustment for retail
Asset Correlation¶
Asset correlation (R) determines how sensitive the exposure is to systematic risk. See _polars_correlation_expr() for the pure Polars implementation.
Corporate/Bank Correlation¶
This produces: - R = 24% for very low PD - R = 12% for high PD
SME Size Adjustment¶
For SME corporates (turnover €5m-€50m). The turnover_m column is provided in GBP millions; the calculator converts to EUR using config.eur_gbp_rate:
# Convert GBP turnover to EUR
S_eur = turnover_gbp / eur_gbp_rate # e.g. £25m / 0.8732 = €28.6m
# Clamp to range and apply adjustment
S = min(50, max(5, S_eur))
R_adjusted = R - 0.04 × (1 - (S - 5) / 45)
This reduces correlation by up to 4 percentage points for smaller firms.
Retail Correlations¶
| Retail Type | Correlation |
|---|---|
| Residential Mortgage | 15% |
| QRRE | 4% |
| Other Retail | 3-16% (PD-dependent) |
Other Retail:
Actual Correlation Implementation (formulas.py)
See _polars_correlation_expr() in src/rwa_calc/engine/irb/formulas.py
for the full Polars expression implementation including SME adjustment.
Maturity Adjustment¶
For non-retail exposures. See _polars_maturity_adjustment_expr() for the pure Polars implementation.
# Maturity factor b
b = (0.11852 - 0.05478 × ln(PD))^2
# Maturity adjustment
MA = (1 + (M - 2.5) × b) / (1 - 1.5 × b)
Where M is effective maturity in years.
Actual Maturity Adjustment (formulas.py)
See _polars_maturity_adjustment_expr() in src/rwa_calc/engine/irb/formulas.py
for the full Polars expression implementation.
Example values:
| PD | M=1yr | M=2.5yr | M=5yr |
|---|---|---|---|
| 0.03% | 0.853 | 1.000 | 1.221 |
| 0.10% | 0.880 | 1.000 | 1.177 |
| 1.00% | 0.934 | 1.000 | 1.099 |
| 5.00% | 0.966 | 1.000 | 1.050 |
Scaling Factor¶
| Framework | Scaling Factor |
|---|---|
| CRR | 1.06 |
| Basel 3.1 | 1.00 (none) |
Expected Loss (EL)¶
IRB requires calculation of Expected Loss:
EL is compared to provisions: - EL > Provisions: Shortfall deducted from capital - EL < Provisions: Excess added to Tier 2 (with limits)
Detailed Calculation Example¶
Exposure: - Corporate loan, £50m - Bank-estimated PD: 0.50% - F-IRB (LGD = 45%) - Maturity: 3 years - Counterparty turnover: £25m (SME)
Step 1: Apply PD Floor
Step 2: Calculate Asset Correlation
# Base correlation
R_base = 0.12 × (1 - exp(-50 × 0.005)) / (1 - exp(-50)) +
0.24 × (1 - (1 - exp(-50 × 0.005)) / (1 - exp(-50)))
R_base = 0.12 × 0.221 + 0.24 × 0.779 = 0.214
# SME adjustment (turnover £25m, converted to EUR)
S_eur = 25 / 0.8732 = 28.63 # GBP → EUR conversion
S = min(50, max(5, 28.63)) = 28.63
adjustment = 0.04 × (1 - (28.63 - 5) / 45) = 0.04 × 0.475 = 0.019
R = 0.214 - 0.019 = 0.195
Step 3: Calculate Capital Requirement (K)
# Intermediate calculations
G_PD = norm.ppf(0.005) = -2.576
G_999 = norm.ppf(0.999) = 3.090
term1 = (1 - R)^(-0.5) × G_PD = 1.115 × (-2.576) = -2.871
term2 = (R / (1-R))^0.5 × G_999 = 0.492 × 3.090 = 1.521
K_pre = LGD × N(term1 + term2) - LGD × PD
K_pre = 0.45 × N(-1.350) - 0.45 × 0.005
K_pre = 0.45 × 0.0885 - 0.00225
K_pre = 0.0398 - 0.00225 = 0.0376
Step 4: Calculate Maturity Adjustment
b = (0.11852 - 0.05478 × ln(0.005))^2 = (0.11852 + 0.290)^2 = 0.167
MA = (1 + (3 - 2.5) × 0.167) / (1 - 1.5 × 0.167)
MA = 1.0835 / 0.7495 = 1.446
Step 5: Calculate RWA
# CRR
RWA_CRR = K × 12.5 × EAD × MA × 1.06
RWA_CRR = 0.0376 × 12.5 × 50,000,000 × 1.446 × 1.06
RWA_CRR = £36,019,860
RW_CRR = RWA / EAD = 72.0%
# Basel 3.1 (no scaling)
RWA_B31 = 0.0376 × 12.5 × 50,000,000 × 1.446 × 1.00
RWA_B31 = £33,981,000
RW_B31 = 68.0%
Step 6: Check Output Floor (Basel 3.1)
# SA equivalent RWA (assume 100% RW corporate)
RWA_SA = 50,000,000 × 100% = £50,000,000
# Output floor (72.5%)
Floor = 50,000,000 × 0.725 = £36,250,000
# Final RWA
RWA_final = max(33,981,000, 36,250,000) = £36,250,000
Implementation¶
Using the IRB Namespace (Recommended)¶
The IRB module provides a Polars namespace extension for fluent, chainable calculations:
import polars as pl
from datetime import date
from rwa_calc.contracts.config import CalculationConfig
import rwa_calc.engine.irb.namespace # Registers .irb namespace
config = CalculationConfig.crr(reporting_date=date(2026, 12, 31))
# Create sample exposure data
exposures = pl.LazyFrame({
"exposure_reference": ["EXP001"],
"pd": [0.005],
"lgd": [0.45],
"ead_final": [50_000_000.0],
"maturity": [3.0],
"exposure_class": ["CORPORATE"],
"turnover_m": [25.0], # Annual turnover in millions
})
# Fluent IRB calculation pipeline
result = (
exposures
.irb.classify_approach(config) # Determine F-IRB vs A-IRB
.irb.apply_firb_lgd(config) # Apply supervisory LGD for F-IRB
.irb.prepare_columns(config) # Ensure required columns exist
.irb.apply_all_formulas(config) # Run full IRB calculation
.collect()
)
# Access results
print(result.select([
"pd_floored", "correlation", "k", "maturity_adjustment", "rwa", "expected_loss"
]))
Individual formula steps can also be chained:
result = (
exposures
.irb.apply_pd_floor(config)
.irb.apply_lgd_floor(config)
.irb.calculate_correlation(config)
.irb.calculate_k(config)
.irb.calculate_maturity_adjustment(config)
.irb.calculate_rwa(config)
.irb.calculate_expected_loss(config)
)
Using the IRB Calculator¶
from rwa_calc.engine.irb.calculator import IRBCalculator
from rwa_calc.contracts.config import CalculationConfig
# Create calculator
calculator = IRBCalculator()
# Calculate — takes a CRMAdjustedBundle, returns a LazyFrameResult
result = calculator.calculate(
data=crm_adjusted_bundle,
config=CalculationConfig.crr(reporting_date=date(2026, 12, 31))
)
# Result is a LazyFrameResult containing the LazyFrame and any errors
irb_rwa_df = result.data.collect()
print(irb_rwa_df.select("rwa", "expected_loss"))
Using IRB Formulas Directly¶
The IRB formulas are implemented in irb/formulas.py. Both scalar functions and pure Polars expression functions are available.
Implementation Architecture
- Vectorized expressions: Pure Polars expressions for bulk processing (used by namespace and calculator)
- Scalar wrappers: Thin wrappers around vectorized expressions for single-value calculations
from rwa_calc.engine.irb.formulas import (
calculate_k,
calculate_correlation,
calculate_maturity_adjustment,
calculate_irb_rwa, # Convenience function for single exposures
)
# Calculate components
R = calculate_correlation(
pd=0.005,
exposure_class="CORPORATE",
turnover_m=25.0, # Turnover in GBP millions (converted to EUR internally)
)
MA = calculate_maturity_adjustment(pd=0.005, maturity=3)
K = calculate_k(pd=0.005, lgd=0.45, correlation=R)
# Calculate RWA
rwa = K * 12.5 * ead * MA * scaling_factor
# Or use the convenience function for complete calculation
result = calculate_irb_rwa(
ead=50_000_000,
pd=0.005,
lgd=0.45,
correlation=R,
maturity=3.0,
apply_scaling_factor=True, # CRR 1.06 factor
)
print(f"RWA: {result['rwa']:,.0f}")
Scalar K Calculation (formulas.py)
See calculate_k() in src/rwa_calc/engine/irb/formulas.py
for the scalar wrapper around the Polars expression.
Expected Loss Calculation¶
from rwa_calc.engine.irb.formulas import calculate_expected_loss
el = calculate_expected_loss(
pd=0.005,
lgd=0.45,
ead=50_000_000
)
# el = 0.005 × 0.45 × 50,000,000 = £112,500
F-IRB vs A-IRB Comparison¶
| Parameter | F-IRB | A-IRB |
|---|---|---|
| PD | Bank estimate | Bank estimate |
| LGD | Supervisory (45%/75%) | Bank estimate (floored) |
| EAD | Regulatory | Bank estimate |
| CCF | Regulatory | Bank estimate |
| Typical RW | Higher | Lower |
| Complexity | Lower | Higher |
| Approval | Easier | Harder |
Regulatory References¶
| Topic | CRR Article | BCBS CRE |
|---|---|---|
| IRB approach overview | Art. 142-150 | CRE30 |
| K formula | Art. 153 | CRE31 |
| PD estimation | Art. 178-180 | CRE32 |
| LGD estimation | Art. 181 | CRE32 |
| Correlation | Art. 153 | CRE31 |
| Maturity adjustment | Art. 162 | CRE31 |
| Supervisory LGD | Art. 161 | CRE32 |
Next Steps¶
- Standardised Approach - Compare with SA
- Credit Risk Mitigation - CRM under IRB
- Supporting Factors - SME correlation adjustment