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.rstfor interpretation limits.
Sections:
Fundamentals-based credit-proxy scoring
Rating-transition matrices and bond valuation
CDS, CDO, and VaR/CVaR
Gaussian-copula simulation
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']
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.