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
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 LazyFrameResultfor 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 conversionoriginal_amount- Amount before conversionfx_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_ratesfile: 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 identifierentity_type-corporate,sovereign,institution,individual, etc.country_code- ISO 3166-1 alpha-2 code
Facility (minimum required fields):
facility_reference- Unique identifiercounterparty_reference- Link to counterpartyproduct_type- Product classificationlimit- Committed amountcurrency- ISO 4217 currency codematurity_date- Final maturity
Loan (minimum required fields):
loan_reference- Unique identifiercounterparty_reference- Link to counterpartydrawn_amount- Outstanding balancecurrency- 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:
- Input Schemas - Complete field-by-field documentation
- Data Validation Guide - Validation rules and troubleshooting
- Data Flow - How data flows through the pipeline
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:
- Pass 1: Calculate preliminary risk weights (without CRM)
- 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
-
SA Exposure Class (determined in precedence order): TBC
-
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
-
Approach (from IRB permissions + data availability):
- A-IRB: Permitted +
has_internal_rating+has_internal_lgd - F-IRB: Permitted +
has_internal_rating - Standardised: Default fallback
- A-IRB: Permitted +
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:
- Exposure Value Reduction: Gross exposure is reduced by eligible provisions (SCRA + GCRA)
- 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:
- Regulatory EL: Calculated as
PD x LGD x EAD - EL Shortfall (Provisions < EL): Deducted from CET1 capital
- 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 distributionG(x)= Inverse standard normalR= 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
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rwa_calc-0.1.3.tar.gz.
File metadata
- Download URL: rwa_calc-0.1.3.tar.gz
- Upload date:
- Size: 630.1 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ae835bc9ff6ddabe3ff09cae8fd6d5e721c37b1a8d2dd33f2a06d56d3c6a49c
|
|
| MD5 |
ca174ef3009c6b84193848cebf5bce7a
|
|
| BLAKE2b-256 |
d2f1b7e774f6be340489ea362d94b06d4ced66ebea82af17a40751d1905d78ed
|
File details
Details for the file rwa_calc-0.1.3-py3-none-any.whl.
File metadata
- Download URL: rwa_calc-0.1.3-py3-none-any.whl
- Upload date:
- Size: 183.1 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d77b4bc15764763ddb6a6916edb38ec27635210a83c7e48176be84f4c589fd6
|
|
| MD5 |
216346fbff3f12a8f8990a8084ce48a3
|
|
| BLAKE2b-256 |
883ff248c33da37f5cd0fefcf0e84dd59139ff6cede7c2eb8162622caaa290c8
|