Skip to content

Core API

creditriskengine.core.types

Core type definitions for the credit risk engine.

All enums map directly to regulatory classifications from BCBS d424 (Basel III final reforms, December 2017).

Jurisdiction

Bases: StrEnum

Supported regulatory jurisdictions.

Source code in creditriskengine\core\types.py
class Jurisdiction(StrEnum):
    """Supported regulatory jurisdictions."""
    BCBS = "bcbs"
    EU = "eu"
    UK = "uk"
    US = "us"
    INDIA = "india"
    SINGAPORE = "singapore"
    HONG_KONG = "hong_kong"
    JAPAN = "japan"
    AUSTRALIA = "australia"
    CANADA = "canada"
    CHINA = "china"
    SOUTH_KOREA = "south_korea"
    UAE = "uae"
    SAUDI_ARABIA = "saudi_arabia"
    SOUTH_AFRICA = "south_africa"
    BRAZIL = "brazil"
    MALAYSIA = "malaysia"

CreditRiskApproach

Bases: StrEnum

Credit risk capital calculation approach.

Reference: BCBS d424, CRE20 (SA), CRE30-36 (IRB).

Source code in creditriskengine\core\types.py
class CreditRiskApproach(StrEnum):
    """Credit risk capital calculation approach.

    Reference: BCBS d424, CRE20 (SA), CRE30-36 (IRB).
    """
    SA = "standardized"
    FIRB = "foundation_irb"
    AIRB = "advanced_irb"

IRBAssetClass

Bases: StrEnum

IRB asset classes per BCBS CRE30.4.

Source code in creditriskengine\core\types.py
class IRBAssetClass(StrEnum):
    """IRB asset classes per BCBS CRE30.4."""
    CORPORATE = "corporate"
    SOVEREIGN = "sovereign"
    BANK = "bank"
    RETAIL = "retail"
    EQUITY = "equity"

IRBCorporateSubClass

Bases: StrEnum

Corporate sub-classes per BCBS CRE30.5-30.9.

Source code in creditriskengine\core\types.py
class IRBCorporateSubClass(StrEnum):
    """Corporate sub-classes per BCBS CRE30.5-30.9."""
    GENERAL_CORPORATE = "general_corporate"
    SME_CORPORATE = "sme_corporate"
    SPECIALISED_LENDING = "specialised_lending"

IRBSpecialisedLendingType

Bases: StrEnum

Specialised lending categories per BCBS CRE30.7.

Source code in creditriskengine\core\types.py
class IRBSpecialisedLendingType(StrEnum):
    """Specialised lending categories per BCBS CRE30.7."""
    PROJECT_FINANCE = "project_finance"
    OBJECT_FINANCE = "object_finance"
    COMMODITIES_FINANCE = "commodities_finance"
    INCOME_PRODUCING_REAL_ESTATE = "ipre"
    HIGH_VOLATILITY_CRE = "hvcre"

IRBRetailSubClass

Bases: StrEnum

Retail sub-classes per BCBS CRE30.11-30.15.

Source code in creditriskengine\core\types.py
class IRBRetailSubClass(StrEnum):
    """Retail sub-classes per BCBS CRE30.11-30.15."""
    RESIDENTIAL_MORTGAGE = "residential_mortgage"
    QRRE = "qualifying_revolving_retail"
    OTHER_RETAIL = "other_retail"
    SME_RETAIL = "sme_retail"

SAExposureClass

Bases: StrEnum

Standardized approach exposure classes per BCBS CRE20.

Source code in creditriskengine\core\types.py
class SAExposureClass(StrEnum):
    """Standardized approach exposure classes per BCBS CRE20."""
    SOVEREIGN = "sovereign"
    PSE = "public_sector_entity"
    MDB = "multilateral_development_bank"
    BANK = "bank"
    SECURITIES_FIRM = "securities_firm"
    CORPORATE = "corporate"
    CORPORATE_SME = "corporate_sme"
    SUBORDINATED_DEBT = "subordinated_debt"
    EQUITY = "equity"
    RETAIL = "retail"
    RETAIL_REGULATORY = "retail_regulatory"
    RESIDENTIAL_MORTGAGE = "residential_mortgage"
    COMMERCIAL_REAL_ESTATE = "commercial_real_estate"
    LAND_ADC = "land_acquisition_development_construction"
    COVERED_BOND = "covered_bond"
    DEFAULTED = "defaulted"
    OTHER = "other"

CreditQualityStep

Bases: int, Enum

Credit quality steps for SA risk weight mapping.

Maps external ratings to standardized risk weight buckets. CQS 1 = AAA/AA-, CQS 2 = A+/A-, etc. Reference: BCBS d424, CRE20 Table 1-10.

Source code in creditriskengine\core\types.py
class CreditQualityStep(int, Enum):
    """Credit quality steps for SA risk weight mapping.

    Maps external ratings to standardized risk weight buckets.
    CQS 1 = AAA/AA-, CQS 2 = A+/A-, etc.
    Reference: BCBS d424, CRE20 Table 1-10.
    """
    CQS_1 = 1
    CQS_2 = 2
    CQS_3 = 3
    CQS_4 = 4
    CQS_5 = 5
    CQS_6 = 6
    UNRATED = 0

IFRS9Stage

Bases: int, Enum

IFRS 9 impairment stages per IFRS 9.5.5.1-5.5.20.

Source code in creditriskengine\core\types.py
class IFRS9Stage(int, Enum):
    """IFRS 9 impairment stages per IFRS 9.5.5.1-5.5.20."""
    STAGE_1 = 1
    STAGE_2 = 2
    STAGE_3 = 3
    POCI = 4

DefaultDefinition

Bases: StrEnum

Default definition frameworks.

Source code in creditriskengine\core\types.py
class DefaultDefinition(StrEnum):
    """Default definition frameworks."""
    BCBS = "bcbs_default"
    EBA = "eba_default"
    PRA = "pra_default"
    RBI = "rbi_default"
    APRA = "apra_default"
    MAS = "mas_default"

CollateralType

Bases: StrEnum

Eligible collateral types per BCBS CRE22.

Source code in creditriskengine\core\types.py
class CollateralType(StrEnum):
    """Eligible collateral types per BCBS CRE22."""
    CASH = "cash"
    GOLD = "gold"
    DEBT_SECURITIES = "debt_securities"
    EQUITIES = "equities"
    MUTUAL_FUNDS = "mutual_funds"
    RESIDENTIAL_REAL_ESTATE = "rre"
    COMMERCIAL_REAL_ESTATE = "cre_collateral"
    RECEIVABLES = "receivables"
    OTHER_PHYSICAL = "other_physical"
    NETTING = "netting"
    GUARANTEE = "guarantee"
    CREDIT_DERIVATIVE = "credit_derivative"

CRMApproach

Bases: StrEnum

Credit risk mitigation approaches per BCBS CRE22.

Source code in creditriskengine\core\types.py
class CRMApproach(StrEnum):
    """Credit risk mitigation approaches per BCBS CRE22."""
    SIMPLE = "simple"
    COMPREHENSIVE = "comprehensive"
    SUPERVISORY_HAIRCUTS = "supervisory_haircuts"
    OWN_ESTIMATE_HAIRCUTS = "own_estimate_haircuts"

creditriskengine.core.exposure

Exposure and facility data models.

Data models represent the minimum set of attributes required for regulatory capital calculation under both SA and IRB approaches.

Collateral

Bases: BaseModel

Collateral pledged against an exposure.

Source code in creditriskengine\core\exposure.py
class Collateral(BaseModel):
    """Collateral pledged against an exposure."""
    collateral_type: CollateralType
    value: float = Field(ge=0, description="Current market/appraised value")
    currency: str = Field(default="USD", max_length=3)
    haircut: float | None = Field(
        default=None, ge=0, le=1, description="Supervisory or own-estimate haircut"
    )
    ltv: float | None = Field(default=None, ge=0, description="Loan-to-value ratio at origination")

Exposure

Bases: BaseModel

Single credit exposure / facility for capital calculation.

Contains all fields needed for SA, F-IRB, and A-IRB RWA computation, IFRS 9/CECL ECL calculation, and model validation.

Source code in creditriskengine\core\exposure.py
class Exposure(BaseModel):
    """
    Single credit exposure / facility for capital calculation.

    Contains all fields needed for SA, F-IRB, and A-IRB RWA
    computation, IFRS 9/CECL ECL calculation, and model validation.
    """
    # ---- Identifiers ----
    exposure_id: str
    counterparty_id: str

    # ---- Amounts ----
    ead: float = Field(ge=0, description="Exposure at Default amount")
    drawn_amount: float = Field(ge=0, description="Current drawn/outstanding balance")
    undrawn_commitment: float = Field(default=0, ge=0, description="Undrawn committed amount")

    # ---- Classification ----
    jurisdiction: Jurisdiction
    approach: CreditRiskApproach
    sa_exposure_class: SAExposureClass | None = None
    irb_asset_class: IRBAssetClass | None = None
    irb_corporate_subclass: IRBCorporateSubClass | None = None
    irb_retail_subclass: IRBRetailSubClass | None = None

    # ---- SA Parameters ----
    credit_quality_step: CreditQualityStep | None = None
    is_investment_grade: bool | None = None

    # ---- IRB Parameters ----
    pd: float | None = Field(default=None, ge=0, le=1, description="Probability of Default")
    lgd: float | None = Field(default=None, ge=0, le=1, description="Loss Given Default")
    ead_model: float | None = Field(default=None, ge=0, description="EAD from internal model")
    maturity_years: float | None = Field(
        default=None, ge=0, le=30, description="Effective residual maturity M in years"
    )
    turnover_eur_millions: float | None = Field(
        default=None, ge=0, description="Annual turnover for SME firm-size adjustment"
    )

    # ---- Collateral and CRM ----
    collaterals: list[Collateral] = Field(default_factory=list)
    is_guaranteed: bool = False
    guarantor_risk_weight: float | None = None

    # ---- Real Estate ----
    property_value: float | None = Field(default=None, ge=0)
    ltv_ratio: float | None = Field(default=None, ge=0)
    is_income_producing: bool = False
    is_materially_dependent_on_cashflows: bool = False

    # ---- IFRS 9 / ECL ----
    ifrs9_stage: IFRS9Stage | None = None
    days_past_due: int = Field(default=0, ge=0)
    origination_date: date | None = None
    maturity_date: date | None = None
    origination_pd: float | None = Field(
        default=None, ge=0, le=1, description="12-month PD at origination"
    )
    current_pd: float | None = Field(default=None, ge=0, le=1, description="Current 12-month PD")
    is_credit_impaired: bool = False
    effective_interest_rate: float | None = Field(
        default=None, ge=0, description="EIR for ECL discounting"
    )

    # ---- Flags ----
    is_defaulted: bool = False
    is_in_default_workout: bool = False
    currency: str = Field(default="USD", max_length=3)

    @field_validator("pd")
    @classmethod
    def pd_floor_check(cls, v: float | None) -> float | None:
        """Validate PD is between 0 and 1 (inclusive).

        Note: The Basel III PD floor of 0.03% (CRE32.13) is enforced at the
        IRB calculation level (``rwa.irb.formulas``), not at the data model
        level, because SA and ECL workflows accept PDs below the IRB floor.
        """
        if v is not None and not 0 <= v <= 1:
            msg = f"PD must be between 0 and 1, got {v}"
            raise ValueError(msg)
        return v

pd_floor_check(v) classmethod

Validate PD is between 0 and 1 (inclusive).

Note: The Basel III PD floor of 0.03% (CRE32.13) is enforced at the IRB calculation level (rwa.irb.formulas), not at the data model level, because SA and ECL workflows accept PDs below the IRB floor.

Source code in creditriskengine\core\exposure.py
@field_validator("pd")
@classmethod
def pd_floor_check(cls, v: float | None) -> float | None:
    """Validate PD is between 0 and 1 (inclusive).

    Note: The Basel III PD floor of 0.03% (CRE32.13) is enforced at the
    IRB calculation level (``rwa.irb.formulas``), not at the data model
    level, because SA and ECL workflows accept PDs below the IRB floor.
    """
    if v is not None and not 0 <= v <= 1:
        msg = f"PD must be between 0 and 1, got {v}"
        raise ValueError(msg)
    return v

creditriskengine.core.portfolio

Portfolio container for credit exposures.

Portfolio

Bases: BaseModel

Container for a portfolio of credit exposures.

Provides iteration, filtering, and aggregation over exposures.

Source code in creditriskengine\core\portfolio.py
class Portfolio(BaseModel):
    """Container for a portfolio of credit exposures.

    Provides iteration, filtering, and aggregation over exposures.
    """

    model_config = ConfigDict(arbitrary_types_allowed=True)

    name: str = "Unnamed Portfolio"
    jurisdiction: Jurisdiction | None = None
    approach: CreditRiskApproach | None = None
    exposures: list[Exposure] = Field(default_factory=list)

    def add_exposure(self, exposure: Exposure) -> None:
        """Add an exposure to the portfolio."""
        self.exposures.append(exposure)

    def total_ead(self) -> float:
        """Total Exposure at Default across all exposures."""
        return sum(e.ead for e in self.exposures)

    def __len__(self) -> int:
        return len(self.exposures)

    def __iter__(self) -> Iterator[Exposure]:  # type: ignore[override]
        return iter(self.exposures)

    def filter_by_approach(self, approach: CreditRiskApproach) -> list[Exposure]:
        """Return exposures using a specific calculation approach."""
        return [e for e in self.exposures if e.approach == approach]

    def filter_defaulted(self) -> list[Exposure]:
        """Return defaulted exposures."""
        return [e for e in self.exposures if e.is_defaulted]

    def filter_non_defaulted(self) -> list[Exposure]:
        """Return non-defaulted exposures."""
        return [e for e in self.exposures if not e.is_defaulted]

add_exposure(exposure)

Add an exposure to the portfolio.

Source code in creditriskengine\core\portfolio.py
def add_exposure(self, exposure: Exposure) -> None:
    """Add an exposure to the portfolio."""
    self.exposures.append(exposure)

total_ead()

Total Exposure at Default across all exposures.

Source code in creditriskengine\core\portfolio.py
def total_ead(self) -> float:
    """Total Exposure at Default across all exposures."""
    return sum(e.ead for e in self.exposures)

filter_by_approach(approach)

Return exposures using a specific calculation approach.

Source code in creditriskengine\core\portfolio.py
def filter_by_approach(self, approach: CreditRiskApproach) -> list[Exposure]:
    """Return exposures using a specific calculation approach."""
    return [e for e in self.exposures if e.approach == approach]

filter_defaulted()

Return defaulted exposures.

Source code in creditriskengine\core\portfolio.py
def filter_defaulted(self) -> list[Exposure]:
    """Return defaulted exposures."""
    return [e for e in self.exposures if e.is_defaulted]

filter_non_defaulted()

Return non-defaulted exposures.

Source code in creditriskengine\core\portfolio.py
def filter_non_defaulted(self) -> list[Exposure]:
    """Return non-defaulted exposures."""
    return [e for e in self.exposures if not e.is_defaulted]

creditriskengine.core.exceptions

Custom exception hierarchy for creditriskengine.

CreditRiskEngineError

Bases: Exception

Base exception for all creditriskengine errors.

Source code in creditriskengine\core\exceptions.py
class CreditRiskEngineError(Exception):
    """Base exception for all creditriskengine errors."""

ConfigurationError

Bases: CreditRiskEngineError

Raised when configuration is invalid or missing.

Source code in creditriskengine\core\exceptions.py
class ConfigurationError(CreditRiskEngineError):
    """Raised when configuration is invalid or missing."""

JurisdictionError

Bases: CreditRiskEngineError

Raised when jurisdiction-specific config is not found.

Source code in creditriskengine\core\exceptions.py
class JurisdictionError(CreditRiskEngineError):
    """Raised when jurisdiction-specific config is not found."""

ValidationError

Bases: CreditRiskEngineError

Raised when input validation fails.

Source code in creditriskengine\core\exceptions.py
class ValidationError(CreditRiskEngineError):
    """Raised when input validation fails."""

RegulatoryError

Bases: CreditRiskEngineError

Raised when regulatory constraints are violated.

Source code in creditriskengine\core\exceptions.py
class RegulatoryError(CreditRiskEngineError):
    """Raised when regulatory constraints are violated."""

CalculationError

Bases: CreditRiskEngineError

Raised when a calculation fails or produces invalid results.

Source code in creditriskengine\core\exceptions.py
class CalculationError(CreditRiskEngineError):
    """Raised when a calculation fails or produces invalid results."""

DataError

Bases: CreditRiskEngineError

Raised when input data is malformed or insufficient.

Source code in creditriskengine\core\exceptions.py
class DataError(CreditRiskEngineError):
    """Raised when input data is malformed or insufficient."""