Credit Risk

This notebook covers AbaQuant’s credit-risk toolkit: fundamentals-based credit-proxy scoring, rating-transition matrices, bond valuation by rating, portfolio value distributions, credit-default-swap (CDS) and CDO valuation, Gaussian-copula simulation, and credit VaR/CVaR.

Important: the synthetic credit-proxy score is an accounting heuristic, not an agency rating or probability-of-default model. See docs/domains/credit.rst for interpretation limits.

Sections:

  1. Fundamentals-based credit-proxy scoring

  2. Rating-transition matrices and bond valuation

  3. CDS, CDO, and VaR/CVaR

  4. Gaussian-copula simulation

  5. Visualizations

Setup

import abaquant
print(f"AbaQuant version: {abaquant.__version__}")
AbaQuant version: 1.0.0rc1
import numpy as np

from abaquant.credit.cdo import gauss_hermite_normal, value_tranche
from abaquant.credit.cds import value_cds
from abaquant.credit.copula import gaussian_copula_simulation
from abaquant.credit.distribution import expected_value_and_sigma, independent_distribution
from abaquant.credit.fundamentals import (
    BalanceSheetInputs,
    CashFlowInputs,
    CreditAnalysisInputs,
    CreditHistoricalSeries,
    IncomeStatementInputs,
    MarketEquityObservation,
    PriorPeriodInputs,
    calculate_credit_proxy_metrics,
)
from abaquant.credit.risk import (
    var_cvar_from_distribution,
    var_cvar_from_simulations,
    var_cvar_parametric,
)
from abaquant.credit.transitions import build_transition_matrix
from abaquant.credit.valuation import bond_values_per_rating
from abaquant.visualization import VisualizationError

1. Fundamentals-based credit-proxy scoring

Build a grouped set of accounting inputs (balance sheet, income statement, cash flow, prior period, market equity, and historical series), then run the transparent proxy-scoring model.

inputs = CreditAnalysisInputs(
    balance_sheet=BalanceSheetInputs(
        total_debt=120.0,
        total_equity=300.0,
        current_assets=250.0,
        inventory=40.0,
        current_liabilities=100.0,
        cash_and_cash_equivalents=50.0,
        total_assets=500.0,
        total_liabilities=200.0,
        retained_earnings=110.0,
        long_term_debt=80.0,
    ),
    income_statement=IncomeStatementInputs(
        revenue=450.0,
        gross_profit=200.0,
        ebit=75.0,
        ebitda=90.0,
        interest_expense=10.0,
        net_income=60.0,
    ),
    cash_flow_statement=CashFlowInputs(operating_cash_flow=70.0),
    prior_period=PriorPeriodInputs(
        total_assets=470.0,
        net_income=55.0,
        long_term_debt=90.0,
        current_assets=220.0,
        current_liabilities=105.0,
        shares_outstanding=100.0,
        gross_profit=180.0,
        revenue=420.0,
    ),
    market_equity=MarketEquityObservation(market_value_equity=600.0),
    historical_series=CreditHistoricalSeries(
        earnings_history=(40.0, 46.0, 55.0, 60.0),
        leverage_history=(0.55, 0.49, 0.43, 0.40),
    ),
    reporting_currency="USD",
    reporting_period="FY2025",
)

assessment = calculate_credit_proxy_metrics(inputs)
summary = {
    "synthetic_score": assessment.synthetic_credit_proxy_score,
    "synthetic_band": assessment.synthetic_credit_proxy_band,
    "debt_to_equity": assessment.metrics["debt_to_equity"],
    "current_ratio": assessment.metrics["current_ratio"],
    "altman_z_score": assessment.metrics["altman_z_score"],
    "piotroski_f_score": assessment.metrics["piotroski_f_score"],
}
for key, value in summary.items():
    print(f"{key:20s}: {value}")
synthetic_score     : 96.51
synthetic_band      : strong_balance_sheet_proxy
debt_to_equity      : 0.4
current_ratio       : 2.5
altman_z_score      : 3.8629999999999995
piotroski_f_score   : None

2. Rating-transition matrices and bond valuation

Build a standard rating-transition matrix, value a bond across every destination rating (via spread-adjusted valuation), and compute the exact portfolio value distribution assuming issuer independence.

transition_matrix = build_transition_matrix()
spreads = np.tile(np.linspace(0.01, 0.08, 5), (17, 1))
values_by_rating = bond_values_per_rating(100.0, 0.05, 5, 1, 0.40, spreads)

bonds_data = [
    {"name": "Bond A", "rating_idx": 0, "values": values_by_rating},
    {"name": "Bond B", "rating_idx": 2, "values": values_by_rating * 0.95},
]
distribution = independent_distribution(bonds_data, transition_matrix)
expected_values, moments = expected_value_and_sigma(bonds_data, transition_matrix)

print(f"Transition matrix shape: {transition_matrix.shape}")
print(f"AAA-state bond value:    {float(values_by_rating[0]):.4f}")
print(f"Distribution states:     {len(distribution)}")
print(f"Portfolio expected value: {moments['EV_port']:.4f}")
print(f"Portfolio sigma:          {moments['sigma_port']:.4f}")
Transition matrix shape: (18, 18)
AAA-state bond value:    89.4525
Distribution states:     2
Portfolio expected value: 174.4226
Portfolio sigma:          0.6779

3. CDS, CDO, and VaR/CVaR

Value a plain-vanilla CDS, price a CDO tranche under the one-factor Gaussian-copula model, and compute VaR/CVaR under three approaches: parametric, from simulated values, and from an explicit discrete distribution.

cds = value_cds(hazard_rate=0.03, discount_rate=0.04, maturity=5, recovery_rate=0.40)
nodes, weights = gauss_hermite_normal(10)
tranche = value_tranche(
    hazard_rate=0.03,
    rho=0.25,
    n=20,
    recovery_rate=0.40,
    attachment=0.03,
    detachment=0.07,
    risk_free_rate=0.04,
    periods=np.arange(1.0, 6.0),
    factor_nodes=nodes,
    weights=weights,
)
simulated_values = np.array([95.0, 97.0, 100.0, 102.0, 105.0, 90.0, 88.0])

print(f"CDS fair spread:          {cds['spread']:.6f}")
print(f"CDO tranche protection leg: {tranche['A']:.4f}")
print(f"Parametric VaR (95%):     {var_cvar_parametric(100.0, 5.0)[0.95]['VaR']:.4f}")
print(f"Simulation CVaR (95%):    {var_cvar_from_simulations(simulated_values)[0.95]['CVaR']:.4f}")
distribution_levels = var_cvar_from_distribution(
    [(1.0, 0.25), (2.0, 0.25), (3.0, 0.25), (4.0, 0.25)]
)
print(f"Distribution VaR levels:  {sorted(distribution_levels.keys())}")
CDS fair spread:          0.018357
CDO tranche protection leg: 2.9347
Parametric VaR (95%):     8.2243
Simulation CVaR (95%):    8.7143
Distribution VaR levels:  [0.9, 0.95, 0.99, 0.999]

4. Gaussian-copula simulation

Simulate correlated rating migrations for a small two-bond portfolio under a one-factor Gaussian copula.

corr = np.array([[1.0, 0.25], [0.25, 1.0]])
values = np.linspace(90.0, 105.0, transition_matrix.shape[1])
bonds = [
    {"rating_idx": 0, "values": values},
    {"rating_idx": 1, "values": values * 0.96},
]
simulation = gaussian_copula_simulation(bonds, transition_matrix, corr, n_sims=500, seed=7)
print(f"Simulation shape:  {simulation.shape}")
print(f"First-row sum:      {float(simulation[0].sum()):.4f}")
Simulation shape:  (500,)
First-row sum:      177.2471

5. Visualizations

Credit-metrics dashboard and synthetic-score charts.

try:
    figures = {
        "credit_metrics": assessment.visualize(chart="metrics"),
        "credit_score": assessment.visualize(chart="score"),
    }
    print(f"Created {len(figures)} figures: {list(figures)}")
except VisualizationError as exc:
    print(f"Visualization skipped (optional dependency missing): {exc}")
Created 2 figures: ['credit_metrics', 'credit_score']
../../_images/ce6063e228c9c7c3fc3229b71d6230fbce9379030eff3e322d501c9e07172ddf.png ../../_images/233e3bb800a42217d061bb4ff721dad2dedd403e77647ff87bed8590cf9d922d.png

Takeaway

AbaQuant’s credit stack ranges from lightweight accounting heuristics (proxy scoring) to structural portfolio models (Gaussian-copula CDO tranches). None of it substitutes for a full credit analysis — see docs/domains/credit.rst for the interpretation limits of every metric used here.