Skip to main content

Basel 3.1 Credit Risk RWA Calculator compliant with PRA PS9/24

Project description

This package is still in development and is not production ready

UK Credit Risk RWA Calculator

Documentation

A high-performance Risk-Weighted Assets (RWA) calculator for UK credit risk, supporting both current regulations and future Basel 3.1 implementation. Built with Python using Polars for vectorized performance.

Documentation: https://OpenAfterHours.github.io/rwa_calculator/

Installation

# Install from PyPI
pip install rwa-calc

# Or with uv
uv add rwa-calc

# With UI support (web-based calculator interface)
pip install rwa-calc[ui]

Quick Start

Option 1: Interactive UI (recommended for exploration)

# Install with UI support
pip install rwa-calc[ui]

# Start the web server
rwa-calc-ui

# Open http://localhost:8000 in your browser

Option 2: Python API (recommended for automation)

from datetime import date
from rwa_calc.engine.pipeline import create_pipeline
from rwa_calc.contracts.config import CalculationConfig

config = CalculationConfig.crr(reporting_date=date(2026, 12, 31))
pipeline = create_pipeline()
result = pipeline.run(config)

print(f"Total RWA: {result.total_rwa:,.2f}")

Regulatory Scope

This calculator supports two regulatory regimes:

Regime Effective Period UK Implementation Status
CRR (Basel 3.0) Until 31 December 2026 UK CRR (EU 575/2013 as onshored) Active Development
Basel 3.1 From 1 January 2027 PRA PS9/24 Planned

A configuration toggle allows switching between calculation modes, enabling:

  • Current regulatory reporting under UK CRR
  • Impact analysis and parallel running ahead of Basel 3.1 go-live
  • Seamless transition when Basel 3.1 becomes effective

Development Status

The project follows a phased, test-first approach prioritising CRR (Basel 3.0) implementation before extending to Basel 3.1.

Phase 1: Test Infrastructure - COMPLETE

Component Status Details
Test Data Fixtures Complete 15 fixture types covering all counterparty, exposure, CRM, and mapping scenarios
CRR Expected Outputs Complete 45 scenarios across 8 groups (SA, F-IRB, A-IRB, CRM, Slotting, Supporting Factors, Provisions, Complex)
CRR Acceptance Tests Complete 83 tests (38 validation, 45 implementation stubs)
Basel 3.1 Expected Outputs Not Started Planned for Phase 1.2B

Phase 2: Process Contracts - COMPLETE

Interfaces and contracts between RWA calculator components have been implemented:

Component Location Tests
Domain Enums src/rwa_calc/domain/enums.py -
Error Contracts src/rwa_calc/contracts/errors.py 20
Configuration src/rwa_calc/contracts/config.py 20
Data Bundles src/rwa_calc/contracts/bundles.py 24
Protocols src/rwa_calc/contracts/protocols.py 14
Validation src/rwa_calc/contracts/validation.py 19
Total 6 modules 97 tests

Key features:

  • CalculationConfig.crr() and .basel_3_1() factory methods for framework-specific configuration
  • Protocol-based interfaces (LoaderProtocol, ClassifierProtocol, etc.) for dependency injection
  • LazyFrameResult for error accumulation without exceptions
  • Intermediate pipeline schemas for data validation at component boundaries

Phase 3: Implementation - COMPLETE

Component Location CRR Status Basel 3.1 Status Tests
Domain enums domain/enums.py Complete Complete -
Risk weight tables data/tables/crr_risk_weights.py Complete Planned -
CCF tables engine/ccf.py Complete Complete 15
Data Loader engine/loader.py Complete Complete 31
Hierarchy Resolver engine/hierarchy.py Complete Complete 17
Exposure Classifier engine/classifier.py Complete Complete 19
CRM Processor engine/crm/processor.py Complete Complete -
SA Calculator engine/sa/calculator.py Complete Complete -
IRB Calculator engine/irb/calculator.py Complete Complete -
Slotting Calculator engine/slotting/calculator.py Complete Complete -
Supporting Factors engine/sa/supporting_factors.py Complete N/A -
Output Aggregator engine/aggregator.py Complete Complete 21
Output floor engine/aggregator.py N/A Complete -
Pipeline Orchestrator engine/pipeline.py Complete Complete 30

Key Implementation Features:

  • Pure LazyFrame Operations: Hierarchy resolution uses iterative Polars joins instead of Python dicts for 50-100x performance improvement
  • Output Floor: Full Basel 3.1 output floor with transitional schedule support (50%→72.5%, 2027-2032)
  • Supporting Factors: CRR SME tiered factor (0.7619/0.85) and infrastructure factor (0.75)
  • Summary Generation: RWA aggregation by exposure class and calculation approach
  • Pipeline Orchestrator: Complete pipeline wiring (Loader → HierarchyResolver → Classifier → CRMProcessor → SA/IRB/Slotting Calculators → OutputAggregator) with error accumulation and audit trail

Phase 4: Acceptance Testing - COMPLETE

CRR acceptance tests validate production pipeline outputs against expected values:

Group Description Scenarios Status
CRR-A Standardised Approach 12 10 PASS, 2 SKIP
CRR-B Foundation IRB 6 6 SKIP (needs PD data)
CRR-C Advanced IRB 3 3 SKIP (needs fixtures)
CRR-D Credit Risk Mitigation 6 6 SKIP (needs fixtures)
CRR-E Specialised Lending (Slotting) 4 4 SKIP (needs fixtures)
CRR-F Supporting Factors 7 7 SKIP (needs fixtures)
CRR-G Provisions & Impairments 3 3 SKIP (needs fixtures)
CRR-H Complex/Combined 4 4 SKIP (needs fixtures)

Key achievements:

  • Pipeline-based testing using session-scoped fixtures
  • Scenario-to-exposure reference mapping for all 46 scenarios
  • CRR-A (SA) tests fully operational with 10 passing tests
  • Remaining tests skipped pending fixture data (PD values for IRB, collateral/guarantee data for CRM)

CRR Workbook Components - COMPLETE

Reference implementations for expected output generation:

Component Location Status
CRR Parameters workbooks/crr_expected_outputs/data/crr_params.py Complete
SA Risk Weights workbooks/crr_expected_outputs/calculations/crr_risk_weights.py Complete
CCF Tables workbooks/crr_expected_outputs/calculations/crr_ccf.py Complete
Supporting Factors workbooks/crr_expected_outputs/calculations/crr_supporting_factors.py Complete
CRM Haircuts workbooks/crr_expected_outputs/calculations/crr_haircuts.py Complete
IRB Formulas workbooks/shared/irb_formulas.py Complete
Correlation workbooks/shared/correlation.py Complete
All Scenario Groups workbooks/crr_expected_outputs/scenarios/ Complete


Interactive UI

The calculator includes an interactive web-based UI built with Marimo, providing three applications accessible through a unified server.

Starting the UI Server

# If installed from PyPI
rwa-calc-ui

# Or from source with uv
uv run python src/rwa_calc/ui/marimo/server.py

# Or using uvicorn directly
uvicorn rwa_calc.ui.marimo.server:app --host 0.0.0.0 --port 8000

Available Applications

Application URL Description
RWA Calculator http://localhost:8000/ Run RWA calculations with data validation, framework selection, and export
Results Explorer http://localhost:8000/results Analyze and filter calculation results with aggregation options
Framework Reference http://localhost:8000/reference Regulatory reference documentation for CRR and Basel 3.1

RWA Calculator Features

  • Data path validation and format selection (Parquet/CSV)
  • Framework toggle (CRR / Basel 3.1)
  • IRB approach toggle
  • Summary statistics (EAD, RWA, average risk weight, breakdown by approach)
  • Performance metrics (duration, throughput)
  • Results preview and CSV export

Results Explorer Features

  • Filter by exposure class, approach, and risk weight range
  • Aggregation by exposure class, approach, or risk weight band
  • Column selector for detailed view
  • Export filtered results (CSV and Parquet)

Benchmark Tests

Performance and scale testing is available through dedicated benchmark tests, validating the calculator's performance from 10K to 10M counterparties.

Running Benchmarks

# Run all benchmark tests
uv run pytest tests/benchmarks/ -v

# Run specific scale tests
uv run pytest tests/benchmarks/ -m scale_10k -v
uv run pytest tests/benchmarks/ -m scale_100k -v
uv run pytest tests/benchmarks/ -m scale_1m -v

# Run memory benchmarks
uv run pytest tests/benchmarks/ -m benchmark -v

# Skip slow tests (1M+ scale)
uv run pytest tests/benchmarks/ -m "not slow" -v

Hierarchy Benchmark Tests

Tests for HierarchyResolver performance at scale:

Scale Target Time Test
10K < 1 sec Full hierarchy resolution
100K < 5 sec Full hierarchy resolution
1M < 60 sec Full hierarchy resolution
10M < 10 min Full hierarchy resolution

Memory benchmarks: < 100 MB for 10K, < 500 MB for 100K counterparties.

Pipeline Benchmark Tests

End-to-end RWA calculation pipeline performance:

Scale SA Only SA + IRB Description
10K < 2 sec < 3 sec Quick validation
100K < 10 sec < 15 sec Standard benchmark
1M < 120 sec - Large portfolio
10M < 20 min - Enterprise scale

Approach-Specific Benchmarks (100K scale)

  • SA only (no IRB)
  • Full IRB (all eligible exposures)
  • IRB with Slotting
  • Partial IRB (corporate only)
  • Basel 3.1 with output floor

Test Markers

Marker Description
@pytest.mark.scale_10k 10K counterparty tests
@pytest.mark.scale_100k 100K counterparty tests
@pytest.mark.scale_1m 1M counterparty tests
@pytest.mark.scale_10m 10M counterparty tests
@pytest.mark.slow Long-running tests (1M+)
@pytest.mark.benchmark Memory and performance benchmarks

Key Differences Between Regimes

Area CRR (Basel 3.0) Basel 3.1 (PRA PS9/24)
1.06 Scaling Factor Applies to ALL IRB RWA Removed
Output Floor None 72.5% of SA RWA
IRB Scope All exposure classes permitted Excludes central govt, equity, large corporates (>EUR 500m), CIUs
PD Floors 0.03% (single floor) Differentiated: 0.03% corp, 0.05% retail, 0.10% QRRE
LGD Floors (A-IRB) None 0%-25% by collateral type
SME Supporting Factor Tiered: 0.7619 (<=EUR 2.5m) / 0.85 (>EUR 2.5m) Withdrawn
Infrastructure Factor 0.75 (flat) Withdrawn
Retail Threshold EUR 1m aggregate exposure GBP 880k aggregate exposure
SA Risk Weights CRR Part Three, Title II Revised tables (granular LTV bands)
Real Estate Whole loan approach Split by LTV bands, ADC treatment
Slotting (Strong) 70% RW 50% RW (differentiated from Good)

CRR Implementation Details

EUR to GBP Conversion

CRR specifies regulatory thresholds in EUR. For UK implementation, these are converted to GBP using a configurable exchange rate.

Configuration: src/rwa_calc/config/fx_rates.py

EUR_GBP_RATE = Decimal("0.88")  # 1 EUR = 0.88 GBP
Threshold EUR (Regulatory) GBP (Derived)
SME Exposure Threshold EUR 2,500,000 GBP 2,200,000
SME Turnover Threshold EUR 50,000,000 GBP 44,000,000

To update the rate, modify EUR_GBP_RATE in the config file. All dependent GBP thresholds will automatically reflect the new rate.

Note: Basel 3.1 (PRA PS9/24) specifies thresholds directly in GBP, so FX conversion is not required for Basel 3.1 calculations.

FX Conversion for Exposures

The calculator supports converting exposure amounts from their original currencies to a configurable reporting currency (default: GBP). This enables consistent RWA calculations across multi-currency portfolios.

Configuration: CalculationConfig settings

config = CalculationConfig(
    framework=RegulatoryFramework.CRR,
    reporting_date=date(2026, 12, 31),
    base_currency="GBP",           # Target reporting currency
    apply_fx_conversion=True,       # Enable/disable FX conversion
)

FX Rates File: fx_rates/fx_rates.parquet

Column Type Description
currency_from String Source currency (e.g., "USD")
currency_to String Target currency (e.g., "GBP")
rate Float64 Multiplier: amount_in_target = amount_in_source * rate

Converted Data:

  • Exposures: drawn_amount, undrawn_amount, nominal_amount
  • Collateral: market_value, nominal_value
  • Guarantees: amount_covered
  • Provisions: amount

Audit Trail: Original values are preserved in new columns:

  • original_currency - Currency before conversion
  • original_amount - Amount before conversion
  • fx_rate_applied - Rate used (null if no conversion needed)

Behaviour:

  • Exposures already in target currency are unchanged
  • Missing FX rates: Original values retained, warning logged
  • apply_fx_conversion=False: Conversion skipped entirely
  • No fx_rates file: Conversion skipped

CRR SME Supporting Factor (Tiered Approach)

Per CRR2 Art. 501, the SME supporting factor uses a tiered structure:

  • Tier 1: Exposures up to EUR 2.5m: factor of 0.7619 (23.81% RWA reduction)
  • Tier 2: Exposures above EUR 2.5m: factor of 0.85 (15% RWA reduction)

Formula:

effective_factor = [min(E, threshold) x 0.7619 + max(E - threshold, 0) x 0.85] / E

This tiered approach means smaller SME exposures get proportionally more capital relief than larger exposures.

CRR IRB 1.06 Scaling Factor

CRR applies a 1.06 scaling factor to all IRB risk-weighted assets (Art. 153(1)). This factor was introduced in Basel II and retained in CRR.

CRR:      RWA = K x 12.5 x 1.06 x EAD x MA
Basel 3.1: RWA = K x 12.5 x EAD x MA

Impact: IRB RWA is 6% higher under CRR compared to the base formula.


Overview

The calculator implements the full credit risk framework for both regimes as adopted by the UK Prudential Regulation Authority (PRA). It supports both Standardised Approach (SA) and Internal Ratings-Based (IRB) approaches with full Credit Risk Mitigation (CRM) capabilities.


Data Requirements

To run an RWA calculation, you need to provide data files in Parquet or CSV format. The table below summarises the required inputs.

Required Data Files

Category File Required Description
Counterparties counterparty/*.parquet Yes Borrower/obligor information by entity type
Exposures exposures/facilities.parquet Yes Credit facilities (committed limits)
exposures/loans.parquet Yes Drawn loan exposures
exposures/contingents.parquet No Off-balance sheet commitments
CRM collateral/collateral.parquet No Collateral holdings
guarantee/guarantee.parquet No Guarantee protection
provision/provision.parquet No IFRS 9 provisions
Ratings ratings/ratings.parquet No Internal/external credit ratings
FX Rates fx_rates/fx_rates.parquet No Currency conversion rates
Mappings mapping/org_mapping.parquet No Organisation hierarchy (rating inheritance)
mapping/lending_mapping.parquet No Lending groups (retail threshold)
exposures/facility_mapping.parquet No Facility-to-loan hierarchy

Key Fields Summary

Counterparty (minimum required fields):

  • counterparty_reference - Unique identifier
  • entity_type - corporate, sovereign, institution, individual, etc.
  • country_code - ISO 3166-1 alpha-2 code

Facility (minimum required fields):

  • facility_reference - Unique identifier
  • counterparty_reference - Link to counterparty
  • product_type - Product classification
  • limit - Committed amount
  • currency - ISO 4217 currency code
  • maturity_date - Final maturity

Loan (minimum required fields):

  • loan_reference - Unique identifier
  • counterparty_reference - Link to counterparty
  • drawn_amount - Outstanding balance
  • currency - ISO 4217 currency code

Data Formats

  • Parquet (recommended): Best performance with Polars lazy evaluation
  • CSV: Supported via CSVLoader

Documentation

For complete schema definitions including all fields, types, and valid enum values:


Key Features

  • Dual Regulatory Compliance: Full support for UK CRR (Basel 3.0) and PRA PS9/24 (Basel 3.1) with UK-specific deviations
  • High Performance: Polars-native vectorized calculations - no row-by-row iteration - utilising LazyFrame optimisations
  • Fluent API: Polars namespace extensions for readable, chainable calculations across all approaches
  • Dual Approach Support: Both Standardised (SA) and IRB (F-IRB & A-IRB) approaches
  • Complete CRM: Collateral at counterparty/facility/loan level with supervisory haircuts and RWA-optimized allocation
  • Provisions/Impairments: Full IFRS 9 ECL integration with EL comparison for IRB
  • Complex Hierarchies: Support for multi-level exposure and counterparty hierarchies
  • Dynamic Classification: Exposure class and approach determined from counterparty attributes
  • Audit Trail: Full calculation transparency for regulatory review

Polars Namespaces

The calculator provides 8 Polars namespace extensions for fluent, chainable calculations:

Namespace Description Example
lf.sa Standardised Approach calculations exposures.sa.apply_risk_weights(config)
lf.irb IRB approach calculations exposures.irb.apply_all_formulas(config)
lf.crm Credit Risk Mitigation exposures.crm.apply_collateral(collateral, config)
lf.haircuts Collateral haircuts collateral.haircuts.apply_all_haircuts(config)
lf.slotting Specialised lending slotting exposures.slotting.apply_slotting_weights(config)
lf.hierarchy Hierarchy resolution counterparties.hierarchy.resolve_ultimate_parent(mappings)
lf.aggregator Result aggregation results.aggregator.apply_output_floor(sa_results, config)
lf.audit / expr.audit Audit trail formatting lf.audit.build_sa_calculation()

Project Structure

rwa_calculator/
├── src/rwa_calc/
│   ├── config/                         # Configuration
│   │   └── fx_rates.py                 # EUR/GBP FX rate configuration
│   ├── domain/                         # Core domain models
│   │   └── enums.py                    # RegulatoryFramework, ExposureClass, ApproachType, CQS, etc.
│   ├── contracts/                      # Component interfaces & data contracts
│   │   ├── bundles.py                  # Data transfer objects (RawDataBundle, CounterpartyLookup, etc.)
│   │   ├── config.py                   # CalculationConfig with .crr()/.basel_3_1() factories
│   │   ├── errors.py                   # CalculationError, LazyFrameResult
│   │   ├── protocols.py                # LoaderProtocol, ClassifierProtocol, etc.
│   │   └── validation.py               # Schema validation utilities
│   ├── data/                           # Data loading & schemas
│   │   ├── schemas.py                  # Polars schemas (input + intermediate pipeline)
│   │   └── tables/                     # Regulatory reference tables
│   │       ├── crr_risk_weights.py     # SA risk weight lookups
│   │       ├── crr_firb_lgd.py         # F-IRB supervisory LGD
│   │       └── crr_slotting.py         # Slotting risk weights
│   ├── engine/                         # Vectorized calculation engines
│   │   ├── loader.py                   # ParquetLoader, CSVLoader
│   │   ├── hierarchy.py                # HierarchyResolver (pure LazyFrame)
│   │   ├── hierarchy_namespace.py      # HierarchyLazyFrame Polars namespace
│   │   ├── fx_converter.py             # FXConverter (multi-currency support)
│   │   ├── classifier.py               # ExposureClassifier
│   │   ├── ccf.py                      # CCFCalculator
│   │   ├── aggregator.py               # OutputAggregator with floor application
│   │   ├── aggregator_namespace.py     # AggregatorLazyFrame Polars namespace
│   │   ├── audit_namespace.py          # AuditLazyFrame & AuditExpr namespaces
│   │   ├── pipeline.py                 # RWAPipeline orchestrator
│   │   ├── crm/                        # Credit Risk Mitigation
│   │   │   ├── processor.py            # CRMProcessor
│   │   │   ├── namespace.py            # CRMLazyFrame Polars namespace
│   │   │   ├── haircuts.py             # Supervisory haircuts
│   │   │   └── haircuts_namespace.py   # HaircutsLazyFrame Polars namespace
│   │   ├── sa/                         # Standardised Approach
│   │   │   ├── calculator.py           # SACalculator
│   │   │   ├── namespace.py            # SALazyFrame & SAExpr namespaces
│   │   │   └── supporting_factors.py   # SME & infrastructure factors (CRR)
│   │   ├── irb/                        # IRB Approach
│   │   │   ├── calculator.py           # IRBCalculator
│   │   │   ├── formulas.py             # K, correlation, maturity adjustment
│   │   │   └── namespace.py            # IRBLazyFrame & IRBExpr Polars namespaces
│   │   └── slotting/                   # Specialised Lending
│   │       ├── calculator.py           # SlottingCalculator
│   │       └── namespace.py            # SlottingLazyFrame & SlottingExpr namespaces
│   ├── api/                            # API layer
│   │   ├── service.py                  # RWAService facade
│   │   ├── models.py                   # Request/response models
│   │   └── validation.py               # Data path validation
│   ├── reporting/                      # Output generation (planned)
│   │   ├── pra/                        # CAP+
│   │   └── corep/                      # COREP templates
│   └── ui/                             # Interactive UI
│       └── marimo/                     # Marimo applications
│           ├── server.py               # Multi-app ASGI server
│           ├── rwa_app.py              # RWA Calculator app
│           ├── results_explorer.py     # Results Explorer app
│           └── framework_reference.py  # Regulatory reference app
│
├── workbooks/                          # Reference implementations
│   ├── shared/                         # Common utilities
│   │   ├── fixture_loader.py           # Test data loader
│   │   ├── irb_formulas.py             # IRB K calculation
│   │   └── correlation.py              # Asset correlation
│   ├── crr_expected_outputs/           # CRR (Basel 3.0) workbook
│   │   ├── data/
│   │   │   └── crr_params.py           # CRR regulatory parameters
│   │   ├── calculations/
│   │   │   ├── crr_risk_weights.py     # SA risk weights
│   │   │   ├── crr_ccf.py              # Credit conversion factors
│   │   │   ├── crr_haircuts.py         # CRM haircuts
│   │   │   ├── crr_supporting_factors.py
│   │   │   └── crr_irb.py              # CRR IRB wrapper
│   │   └── scenarios/                  # Expected output scenarios
│   │       ├── group_crr_a_sa.py       # SA scenarios
│   │       ├── group_crr_b_firb.py     # F-IRB scenarios
│   │       ├── group_crr_c_airb.py     # A-IRB scenarios
│   │       ├── group_crr_d_crm.py      # CRM scenarios
│   │       ├── group_crr_e_slotting.py # Slotting scenarios
│   │       ├── group_crr_f_supporting_factors.py
│   │       ├── group_crr_g_provisions.py
│   │       └── group_crr_h_complex.py  # Complex/combined
│   └── basel31_expected_outputs/       # Basel 3.1 workbook (planned)
│
├── tests/
│   ├── acceptance/
│   │   ├── crr/                        # CRR acceptance tests
│   │   │   ├── test_scenario_crr_a_sa.py
│   │   │   ├── test_scenario_crr_b_firb.py
│   │   │   └── ... (8 test files)
│   │   └── basel31/                    # Basel 3.1 acceptance tests (planned)
│   ├── benchmarks/                     # Performance & scale tests
│   │   ├── test_hierarchy_benchmark.py # Hierarchy resolution (10K-10M)
│   │   └── test_pipeline_benchmark.py  # End-to-end pipeline (10K-10M)
│   ├── contracts/                      # Contract/interface tests (97 tests)
│   │   ├── test_bundles.py             # Data bundle tests
│   │   ├── test_config.py              # Configuration tests
│   │   ├── test_errors.py              # Error handling tests
│   │   ├── test_protocols.py           # Protocol compliance tests
│   │   └── test_validation.py          # Validation utility tests
│   ├── unit/
│   │   └── test_fx_converter.py        # FX conversion unit tests (14 tests)
│   ├── fixtures/                       # Test data generators
│   │   ├── counterparty/               # Sovereign, institution, corporate, retail
│   │   ├── exposures/                  # Facilities, loans, contingents
│   │   ├── collateral/                 # Cash, bonds, equity, RE
│   │   ├── guarantee/                  # Guarantees
│   │   ├── provision/                  # IFRS 9 provisions
│   │   ├── ratings/                    # External/internal ratings
│   │   ├── mapping/                    # Org and lending hierarchies
│   │   └── fx_rates/                   # FX rates for currency conversion
│   └── expected_outputs/
│       ├── crr/                        # CRR expected outputs
│       │   ├── expected_rwa_crr.csv
│       │   ├── expected_rwa_crr.json
│       │   └── expected_rwa_crr.parquet
│       └── basel31/                    # Basel 3.1 expected outputs (planned)
│
└── ref_docs/                           # Regulatory reference documents

CRR Acceptance Test Scenarios

Scenario Groups

Group Description Scenarios Expected Outputs Pipeline Tests
CRR-A Standardised Approach 12 Complete 10 PASS, 2 SKIP
CRR-B Foundation IRB 6 Complete 6 SKIP
CRR-C Advanced IRB 3 Complete 3 SKIP
CRR-D Credit Risk Mitigation 6 Complete 6 SKIP
CRR-E Specialised Lending (Slotting) 4 Complete 4 SKIP
CRR-F Supporting Factors 7 Complete 7 SKIP
CRR-G Provisions & Impairments 3 Complete 3 SKIP
CRR-H Complex/Combined 4 Complete 4 SKIP

Note: Skipped tests await fixture data with required fields (PD values for IRB, collateral/guarantees for CRM, slotting categories, etc.).

Running Tests

# Run all tests
uv run pytest -v

# Run contract tests (97 tests)
uv run pytest tests/contracts/ -v

# Run CRR acceptance tests
uv run pytest tests/acceptance/crr/ -v

# Run specific scenario group
uv run pytest tests/acceptance/crr/test_scenario_crr_a_sa.py -v

# Run type checking
uv run mypy --package rwa_calc.contracts --package rwa_calc.domain

Test Results (826 tests):

  • 97 contract tests PASS - Verify interfaces, configuration, and validation
  • 81 acceptance tests PASS - Verify expected outputs and SA/IRB/CRM/Slotting calculations
  • 3 acceptance tests SKIP - Await fixture data for advanced scenarios
  • 31 loader tests PASS - Data loading from Parquet/CSV
  • 17 hierarchy tests PASS - Counterparty/facility hierarchy resolution
  • 19 classifier tests PASS - Exposure classification and approach assignment
  • 15 CCF tests PASS - Credit conversion factors
  • 21 aggregator tests PASS - Output aggregation and floor application
  • 30 pipeline tests PASS - Full pipeline orchestration
  • 139 namespace tests PASS - Polars namespace extensions (SA, CRM, Haircuts, Slotting, Hierarchy, Aggregator, Audit)
  • 14 FX converter tests PASS - Currency conversion for exposures, collateral, guarantees, provisions
  • Total: 826 passed, 4 skipped

How It Works

  • Counterparty hierarchies allow for lending groups (for Retail classification) and org groups (for parent ratings) to be calculated
  • Facilities and loans are combined into a hierarchy internally
  • Drawn amounts aggregate bottom-up from loans to facilities
  • Undrawn amounts are calculated at the root facility level
  • RWA is calculated on both drawn (loans) and undrawn (facilities) exposures
  • Collateral is prorated to optimise RWA calculation
  • Provisions are allocated to optimise the RWA calculation

Counterparty Hierarchies

Lending Groups (Retail Classification)

Used to determine retail eligibility based on aggregate group exposure:

Lending Group Lead
├── Counterparty A (exposure: £400k)
├── Counterparty B (exposure: £200k)
└── Sub-Group Lead
    └── Counterparty C (exposure: £150k)
Total: £750k - below £880k threshold, qualifies for retail treatment
  • Exposure aggregated to root group level
  • Threshold comparison determines retail eligibility (£880k per PRA PS9/24)
  • No rating inheritance from lead

Org Hierarchy (Rating & Turnover Inheritance)

Used for rating and SME classification:

Org Lead (CQS 2, turnover: £80M)
├── Subsidiary A (inherits CQS 2, £80M)
├── Subsidiary B (inherits CQS 2, £80M)
└── Sub-Org Lead (inherits CQS 2, £80M)
    └── Subsidiary C (inherits CQS 2, £80M)
  • Rating cascades from root through entire hierarchy
  • Turnover cascades for SME classification

Exposure Hierarchy Model

The calculator uses a facilities + loans hierarchy model where:

  • Facilities define committed credit limits (parent nodes)
  • Loans represent actual drawings (leaf nodes)

Data Model

Facilities and loans are loaded as separate files and combined internally:

# rwa_config.yaml
data_paths:
  facilities: "data/{period}/facilities.parquet"   # Required
  loans: "data/{period}/loans.parquet"             # Required
  counterparties: "data/{period}/counterparties.parquet"

The hierarchy supports unlimited nesting:

Master Facility (committed: £10M)
├── Sub-Facility A (committed: £6M)
│   ├── Loan A1 (drawn: £2M)
│   └── Loan A2 (drawn: £1.5M)
└── Sub-Facility B (committed: £4M)
    └── Loan B1 (drawn: £3M)

Reporting View Output

The extract_reporting_view() function produces a flattened view for RWA calculation with both facilities and loans:

external_id node_type drawn_amount undrawn_amount
MASTER_FAC facility 0 £3.5M
LOAN_A1 loan £2M 0
LOAN_A2 loan £1.5M 0
LOAN_B1 loan £3M 0

Key principles:

  • Root facilities carry the undrawn exposure (committed minus total drawn)
  • Loans carry the drawn exposure
  • RWA is calculated on both drawn and undrawn portions separately
  • Intermediate sub-facilities are excluded from the reporting view (their committed amounts are reflected in the root facility's undrawn)

Amount Calculation

  • Drawn amounts: Aggregated bottom-up from loans to facilities
  • Undrawn amounts: Calculated at root facility level as committed - aggregated_drawn
  • Risk parameters: Inherited top-down (exposure_class, pd, lgd, cqs)

Credit Risk Mitigation

Collateral can be linked at three levels in the exposure hierarchy, providing flexibility in how security is allocated.

Allocation Levels

Level Link Field Behaviour
Counterparty counterparty_id Expands to all exposures of the counterparty
Facility facility_id Expands to facility + all descendant loans
Loan exposure_ids Direct allocation to specific exposures

Priority: If multiple fields are populated, the most specific wins: exposure_ids > facility_id > counterparty_id

Example: Counterparty-Level Collateral

Setup:

Counterparty: CPTY001
├── FAC001 (Master Facility, £0 drawn, £5M committed)
│   ├── LOAN_A (£2M drawn, RW=100%)
│   └── LOAN_B (£1M drawn, RW=50%)
└── FAC002 (Master Facility, £0 drawn, £2M committed)
    └── LOAN_C (£1.5M drawn, RW=75%)

Collateral: £2M cash linked to counterparty_id=CPTY001

Allocation Result (RWA-optimized):

Exposure Drawn Risk Weight Collateral Allocated Net Exposure
LOAN_A £2M 100% £2M (priority) £0
LOAN_B £1M 50% £0 £1M
LOAN_C £1.5M 75% £0 £1.5M
FAC001 £0 - £0 £0
FAC002 £0 - £0 £0

RWA Benefit: £2M x 100% = £2M RWA saved (allocated to highest RW exposure first)

Two-Pass Optimization

When collateral covers multiple exposures, the calculator uses a two-pass approach:

  1. Pass 1: Calculate preliminary risk weights (without CRM)
  2. Pass 2: Allocate collateral to highest risk-weight exposures first

This maximises capital benefit per Basel CRE22 guidance.

Exposure Classification

Exposure class and approach are dynamically determined from counterparty attributes:

Classification Logic

  1. SA Exposure Class (determined in precedence order): TBC

  2. IRB Exposure Class (for IRB-permitted exposures):

    • Corporates: Sub-classified by revenue threshold (£440m) and specialised lending criteria
    • Retail: Sub-classified by property security, QRRE criteria, and SME threshold (£0.88m)
    • Note: Central govs, Equity, CIUs must use SA (IRB withdrawn) for Basel 3.1
  3. Approach (from IRB permissions + data availability):

    • A-IRB: Permitted + has_internal_rating + has_internal_lgd
    • F-IRB: Permitted + has_internal_rating
    • Standardised: Default fallback

Provisions & Impairments

The calculator fully integrates IFRS 9 provisions/impairments into both SA and IRB calculations.

SA Provisions (PRA PS9/24 Chapter 3)

For Standardised Approach exposures:

  1. Exposure Value Reduction: Gross exposure is reduced by eligible provisions (SCRA + GCRA)
  2. Defaulted Exposure Risk Weight: Based on SCRA coverage ratio
    • SCRA coverage >= 20% of unsecured portion -> 100% RW
    • SCRA coverage < 20% of unsecured portion -> 150% RW

IRB Provisions (PRA PS9/24 Chapter 5)

For IRB exposures, provisions are compared to Expected Loss:

  1. Regulatory EL: Calculated as PD x LGD x EAD
  2. EL Shortfall (Provisions < EL): Deducted from CET1 capital
  3. EL Excess (Provisions > EL): Added to Tier 2 capital (capped at 0.6% of IRB RWA)

F-IRB Collateral-Based LGD (PRA PS9/24 Chapter 5)

For F-IRB exposures, supervisory LGD values depend on the collateral type securing the exposure:

Collateral Type Supervisory LGD
Cash, Gold, Securities 0%
Receivables 35%
Commercial/Residential RE 35%
Other Physical 40%
Unsecured 45%
Subordinated (any) 75%

When an exposure is partially secured, the effective LGD is blended:

effective_lgd = coverage x lgd_secured + (1 - coverage) x 45%

Key Regulatory Parameters

CRR (Basel 3.0) Parameters

Parameter Value Reference
1.06 Scaling Factor Applies to all IRB CRR Art. 153(1)
PD Floor (all classes) 0.03% CRR Art. 163
F-IRB LGD (Unsecured) 45% CRR Art. 161
F-IRB LGD (Subordinated) 75% CRR Art. 161
SME Turnover Threshold EUR 50m (GBP 44m) CRR Art. 501
SME Exposure Threshold EUR 2.5m (GBP 2.2m) CRR2 Art. 501
SME Factor Tier 1 0.7619 CRR2 Art. 501
SME Factor Tier 2 0.85 CRR2 Art. 501
Infrastructure Factor 0.75 CRR Art. 501a
Institution CQS2 RW (UK) 30% UK deviation
FX Haircut 8% CRR Art. 224

Basel 3.1 Parameters (PRA PS9/24)

Parameter Value Reference
Output Floor 72.5% of SA equivalent PS9/24 Ch. 6
PD Floor (Corporate) 0.03% PS9/24 Ch. 5
PD Floor (Retail) 0.05% PS9/24 Ch. 5
PD Floor (QRRE) 0.10% PS9/24 Ch. 5
LGD Floor (Senior Unsecured) 25% PS9/24 Ch. 5
Institution CQS2 RW 30% PS9/24 Ch. 3.3 (UK deviation)
FX Haircut 8% PS9/24 Ch. 4
Large Corporate Revenue £440m PS9/24 Ch. 5
Retail SME Exposure £0.88m PS9/24 Ch. 5
QRRE Max Individual Exposure £90,000 PS9/24 Ch. 5

Output Floor Phase-In (Basel 3.1)

Year Floor %
2027 50%
2028 55%
2029 60%
2030 65%
2031 70%
2032+ 72.5%

SA Risk Weight Tables

Sovereigns (CQS-based)

CQS 1 2 3 4 5 6 Unrated
RW 0% 20% 50% 100% 100% 150% 100%

Institutions (ECRA)

CQS 1 2 3 4 5 6 Unrated
RW 20% 30%* 50% 100% 100% 150% 50%

*UK deviation from Basel 50%

Residential Mortgages (Basel 3.1 LTV-based)

LTV <=50% <=60% <=70% <=80% <=90% <=100% >100%
RW 20% 25% 30% 35% 40% 50% 70%

CRR Residential Mortgages (Simpler)

LTV <=80% >80%
RW 35% Split: 35% on 80%, 75% on excess

IRB Formula

The IRB capital requirement (K) is calculated as:

K = LGD x N[(1-R)^(-0.5) x G(PD) + (R/(1-R))^(0.5) x G(0.999)] - PD x LGD

Where:

  • N(x) = Standard normal cumulative distribution
  • G(x) = Inverse standard normal
  • R = Asset correlation (varies by exposure class)
  • PD = Probability of Default (floored)
  • LGD = Loss Given Default

Risk Weight = K x 12.5 (x 1.06 for CRR)

Using the Polars Namespaces

All namespaces are registered when importing from rwa_calc.engine. Each provides fluent, chainable methods:

import polars as pl
from datetime import date
from rwa_calc.contracts.config import CalculationConfig
from rwa_calc.engine import (
    SALazyFrame, IRBLazyFrame, CRMLazyFrame,
    HaircutsLazyFrame, SlottingLazyFrame,
    HierarchyLazyFrame, AggregatorLazyFrame, AuditLazyFrame,
)

config = CalculationConfig.crr(reporting_date=date(2026, 12, 31))

# SA Calculation
sa_result = (
    exposures
    .sa.prepare_columns(config)
    .sa.apply_risk_weights(config)
    .sa.calculate_rwa()
    .sa.apply_supporting_factors(config)
)

# IRB Calculation
irb_result = (
    exposures
    .irb.classify_approach(config)
    .irb.apply_firb_lgd(config)
    .irb.prepare_columns(config)
    .irb.apply_all_formulas(config)
)

# CRM Processing
crm_result = (
    exposures
    .crm.initialize_ead_waterfall()
    .crm.apply_collateral(collateral, config)
    .crm.apply_guarantees(guarantees, counterparty_lookup, config)
    .crm.finalize_ead()
)

# Haircut Calculation
haircut_result = (
    collateral
    .haircuts.classify_maturity_band()
    .haircuts.apply_collateral_haircuts(config)
    .haircuts.apply_fx_haircut("exposure_currency")
    .haircuts.calculate_adjusted_value()
)

# Hierarchy Resolution
hierarchy_result = (
    counterparties
    .hierarchy.resolve_ultimate_parent(org_mappings)
    .hierarchy.inherit_ratings(ratings)
)

# Aggregation with Output Floor
aggregated = (
    results
    .aggregator.combine_approach_results(sa=sa_results, irb=irb_results)
    .aggregator.apply_output_floor(sa_for_floor, config)
)

# Audit Trail
audited = exposures.audit.build_sa_calculation()

Asset Correlation (R) by Exposure Class

Exposure Class Correlation Formula Parameters
Corporate / Institution R = 0.12 x f(PD) + 0.24 x (1 - f(PD)) R_min=12%, R_max=24%
Corporate SME R_corp x [1 - 0.04 x (1 - (min(S,50)-5)/45)] Firm-size adjustment
Retail Mortgage R = 0.15 (fixed) 15%
QRRE R = 0.04 (fixed) 4%
Other Retail R = 0.03 x f(PD) + 0.16 x (1 - f(PD)) R_min=3%, R_max=16%

SME Firm-Size Adjustment:

  • S = Annual turnover in EUR millions
  • Applied when turnover < EUR 50m
  • Reduces correlation by up to 4% for smallest firms (S=5m)

Maturity Adjustment (Non-Retail Only)

MA = (1 + (M - 2.5) x b) / (1 - 1.5 x b)
where b = (0.11852 - 0.05478 x ln(PD))^2
  • M = Effective maturity (F-IRB: 2.5 years default; A-IRB: bank estimate, 1-5 years)
  • Retail exposures do not have maturity adjustment (MA = 1.0)

Output Floor (Basel 3.1 Only)

The output floor ensures IRB RWA is at least 72.5% of what SA RWA would be:

Total Standardised Equivalent = SA RWA + IRB SA Equivalent RWA
Floor = 72.5% x Total Standardised Equivalent
Final RWA = max(Actual RWA, Floor)

The OutputFloorResult provides full visibility:

Field Description
sa_rwa RWA for exposures using SA approach
irb_rwa RWA for exposures using IRB approach
irb_sa_equivalent_rwa What IRB exposures would have been under SA
total_standardised_equivalent SA RWA + IRB SA Equivalent
floor_rwa 72.5% x Total Standardised Equivalent
floor_binding True if floor > actual RWA
final_rwa max(actual, floor)
irb_benefit RWA reduction from using IRB

License

Apache-2.0 license

References

Current Regulations (CRR / Basel 3.0)

Basel 3.1 Implementation (January 2027)

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rwa_calc-0.1.2.tar.gz (610.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rwa_calc-0.1.2-py3-none-any.whl (179.8 kB view details)

Uploaded Python 3

File details

Details for the file rwa_calc-0.1.2.tar.gz.

File metadata

  • Download URL: rwa_calc-0.1.2.tar.gz
  • Upload date:
  • Size: 610.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for rwa_calc-0.1.2.tar.gz
Algorithm Hash digest
SHA256 ea65b741b7848670d496d4b8542c9cbde2cd010745bb529a45036e4e086fa7ad
MD5 06dc75fbaf26d066fccf625fcd594b40
BLAKE2b-256 4c21f959e3b5f25f1da1ae9efb44251ac92c21a4ced4ac7d90758f76f998438d

See more details on using hashes here.

File details

Details for the file rwa_calc-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: rwa_calc-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 179.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for rwa_calc-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8f5722e646db0ee038305b534a1c1c76e214b0e546553fa02f1852cc752c9d0e
MD5 8ab73bcbeb508a39cf8a7f57eab02013
BLAKE2b-256 be56532432dfd219e8fd8539a03f7d14f703cce1288d5ff02d925a723549b308

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page