| name | credit-risk |
| description | Assess credit risk and default probability for bonds using credit spreads, rating transitions, and recovery analysis. Requires numpy>=1.24.0, pandas>=2.0.0, scipy>=1.10.0. Use when evaluating corporate bonds, analyzing credit events, estimating default probabilities, or managing credit portfolio risk. |
Credit Risk Assessment and Rating Analysis Skill Development
Objective
Evaluate and model issuer default risk, downgrade probability, and credit spread behavior using both fundamental analysis and market-implied approaches. Develop systematic frameworks for assessing credit quality and monitoring credit migrations.
Skill Classification
Domain: Fixed Income Credit Analysis Level: Advanced Prerequisites: Bond Pricing, Yield Measures Estimated Time: 20-25 hours
Focus Areas
1. Fundamental vs. Market-Implied Credit Risk
Fundamental Credit Analysis
Approach: Bottom-up analysis of issuer financials and business fundamentals.
Key Metrics:
class CreditMetrics:
"""Fundamental credit analysis metrics."""
@staticmethod
def interest_coverage(ebit, interest_expense):
"""Times Interest Earned (TIE) ratio."""
return ebit / interest_expense
@staticmethod
def debt_to_ebitda(total_debt, ebitda):
"""Leverage ratio."""
return total_debt / ebitda
@staticmethod
def debt_to_equity(total_debt, total_equity):
"""Capital structure ratio."""
return total_debt / total_equity
@staticmethod
def current_ratio(current_assets, current_liabilities):
"""Liquidity ratio."""
return current_assets / current_liabilities
@staticmethod
def free_cash_flow_to_debt(fcf, total_debt):
"""Debt service capacity."""
return fcf / total_debt
Market-Implied Credit Risk
Approach: Extract default probability from market prices.
Credit Spread:
Credit Spread = YTM_Corporate - YTM_Treasury
Implied Default Probability:
def implied_default_probability(credit_spread, recovery_rate, years):
"""
Calculate market-implied annual default probability.
Parameters:
-----------
credit_spread : float
Yield spread over risk-free rate (decimal)
recovery_rate : float
Expected recovery in default (decimal, e.g., 0.40 for 40%)
years : float
Time horizon
Returns:
--------
float : Implied annual default probability
"""
loss_given_default = 1 - recovery_rate
annual_pd = credit_spread / loss_given_default
return annual_pd
def cumulative_default_prob(annual_pd, years):
"""
Calculate cumulative default probability.
Returns:
--------
float : Probability of default within 'years' horizon
"""
survival_prob = (1 - annual_pd) ** years
return 1 - survival_prob
2. Credit Rating Agencies
Major Rating Agencies:
- Moody's: Aaa to C scale
- S&P: AAA to D scale
- Fitch: AAA to D scale
Rating Categories:
- Investment Grade: BBB-/Baa3 and above
- High Yield (Junk): BB+/Ba1 and below
Rating Equivalence:
RATING_SCALE = {
'Moody\'s': ['Aaa', 'Aa1', 'Aa2', 'Aa3', 'A1', 'A2', 'A3',
'Baa1', 'Baa2', 'Baa3', 'Ba1', 'Ba2', 'Ba3',
'B1', 'B2', 'B3', 'Caa1', 'Caa2', 'Caa3', 'Ca', 'C'],
'S&P': ['AAA', 'AA+', 'AA', 'AA-', 'A+', 'A', 'A-',
'BBB+', 'BBB', 'BBB-', 'BB+', 'BB', 'BB-',
'B+', 'B', 'B-', 'CCC+', 'CCC', 'CCC-', 'CC', 'C', 'D'],
'Fitch': ['AAA', 'AA+', 'AA', 'AA-', 'A+', 'A', 'A-',
'BBB+', 'BBB', 'BBB-', 'BB+', 'BB', 'BB-',
'B+', 'B', 'B-', 'CCC+', 'CCC', 'CCC-', 'CC', 'C', 'D']
}
def numerical_rating(rating, agency='S&P'):
"""Convert letter rating to numerical score (higher = better)."""
scale = RATING_SCALE[agency]
try:
return len(scale) - scale.index(rating)
except ValueError:
return None
def is_investment_grade(rating, agency='S&P'):
"""Determine if rating is investment grade."""
numeric = numerical_rating(rating, agency)
if agency == 'Moody\'s':
threshold = numerical_rating('Baa3', agency)
else:
threshold = numerical_rating('BBB-', agency)
return numeric >= threshold if numeric else False
Historical Default Rates by Rating:
# Average 10-year cumulative default rates (%)
HISTORICAL_DEFAULT_RATES = {
'AAA': 0.60, 'AA': 1.50, 'A': 2.91,
'BBB': 7.20, 'BB': 19.00, 'B': 35.00,
'CCC': 54.00
}
3. Probability of Default (PD) and Loss Given Default (LGD)
Expected Loss Framework:
Expected Loss = PD × LGD × Exposure at Default
PD Modeling Approaches:
Structural Model (Merton Model)
from scipy.stats import norm
def merton_default_probability(firm_value, debt_face_value,
volatility, risk_free_rate, time_horizon):
"""
Calculate default probability using Merton structural model.
Based on Black-Scholes option pricing framework.
Parameters:
-----------
firm_value : float
Current market value of firm's assets
debt_face_value : float
Face value of debt
volatility : float
Volatility of firm value (annual)
risk_free_rate : float
Risk-free rate
time_horizon : float
Time to debt maturity (years)
Returns:
--------
float : Probability of default
"""
d2 = (np.log(firm_value / debt_face_value) +
(risk_free_rate - 0.5 * volatility**2) * time_horizon) / \
(volatility * np.sqrt(time_horizon))
return norm.cdf(-d2)
Reduced-Form Model
def hazard_rate_pd(hazard_rate, time_horizon):
"""
Calculate default probability from constant hazard rate.
Parameters:
-----------
hazard_rate : float
Instantaneous default intensity (annual)
time_horizon : float
Time period (years)
Returns:
--------
float : Cumulative default probability
"""
return 1 - np.exp(-hazard_rate * time_horizon)
LGD Estimation:
class LGDEstimator:
"""Loss Given Default estimation."""
# Historical average recovery rates by seniority
RECOVERY_RATES = {
'Senior Secured': 0.65,
'Senior Unsecured': 0.45,
'Senior Subordinated': 0.35,
'Subordinated': 0.25,
'Junior Subordinated': 0.15
}
@classmethod
def lgd_from_seniority(cls, seniority):
"""Estimate LGD based on debt seniority."""
recovery = cls.RECOVERY_RATES.get(seniority, 0.40)
return 1 - recovery
@staticmethod
def lgd_from_collateral(debt_value, collateral_value,
liquidation_costs=0.20):
"""
Calculate LGD considering collateral.
Parameters:
-----------
debt_value : float
Outstanding debt amount
collateral_value : float
Market value of collateral
liquidation_costs : float
Costs of liquidating collateral (as fraction)
Returns:
--------
float : Loss given default
"""
net_recovery = collateral_value * (1 - liquidation_costs)
loss = max(0, debt_value - net_recovery)
return loss / debt_value
4. Credit Spreads and Yield Differentials
Components of Credit Spread:
Credit Spread = Expected Loss + Risk Premium + Liquidity Premium
Spread Analysis:
def credit_spread_decomposition(corporate_yield, treasury_yield,
expected_loss_component):
"""
Decompose credit spread into components.
Parameters:
-----------
corporate_yield : float
YTM of corporate bond
treasury_yield : float
YTM of comparable Treasury
expected_loss_component : float
Expected loss (PD × LGD)
Returns:
--------
dict : Spread decomposition
"""
total_spread = corporate_yield - treasury_yield
risk_liquidity_premium = total_spread - expected_loss_component
return {
'total_spread': total_spread,
'expected_loss': expected_loss_component,
'risk_premium': risk_liquidity_premium * 0.60, # Estimate
'liquidity_premium': risk_liquidity_premium * 0.40 # Estimate
}
def option_adjusted_spread_credit(z_spread, option_cost):
"""
Calculate credit-adjusted OAS.
Parameters:
-----------
z_spread : float
Zero-volatility spread
option_cost : float
Value of embedded options
Returns:
--------
float : Option-adjusted spread
"""
return z_spread - option_cost
Credit Spread Curve:
import matplotlib.pyplot as plt
def plot_credit_spread_curve(maturities, spreads, rating, date):
"""
Visualize credit spread term structure.
Parameters:
-----------
maturities : array-like
Bond maturities
spreads : array-like
Credit spreads (basis points)
rating : str
Credit rating
date : str
Observation date
"""
plt.figure(figsize=(10, 6))
plt.plot(maturities, spreads, 'ro-', linewidth=2, markersize=6)
plt.xlabel('Maturity (Years)')
plt.ylabel('Credit Spread (bps)')
plt.title(f'{rating} Credit Spread Curve - {date}')
plt.grid(True, alpha=0.3)
plt.show()
5. Event Risk and Credit Migration
Event Risk Categories:
- M&A Activity: Leverage increases from acquisitions
- Dividend Recapitalization: Debt-funded special dividends
- Regulatory Changes: New compliance costs or restrictions
- Litigation: Major legal judgments
Credit Migration Matrix:
# One-year transition probabilities (%)
TRANSITION_MATRIX = {
'AAA': {'AAA': 90.81, 'AA': 8.33, 'A': 0.68, 'BBB': 0.06, 'Default': 0.00},
'AA': {'AAA': 0.70, 'AA': 90.65, 'A': 7.79, 'BBB': 0.64, 'Default': 0.02},
'A': {'AAA': 0.09, 'AA': 2.27, 'A': 91.05, 'BBB': 5.52, 'Default': 0.06},
'BBB': {'AAA': 0.02, 'AA': 0.33, 'A': 5.95, 'BBB': 86.93, 'BB': 5.30, 'Default': 0.18},
'BB': {'AAA': 0.03, 'BBB': 0.67, 'BB': 80.53, 'B': 8.84, 'Default': 1.06},
'B': {'BB': 0.43, 'B': 83.46, 'CCC': 4.07, 'Default': 4.89},
'CCC': {'B': 0.69, 'CCC': 58.24, 'Default': 26.92}
}
def expected_rating_migration(current_rating, years=1):
"""
Calculate probability distribution of future ratings.
Returns:
--------
dict : Probabilities for each rating category
"""
return TRANSITION_MATRIX.get(current_rating, {})
def downgrade_probability(current_rating, years=1):
"""
Calculate probability of downgrade within time horizon.
"""
transitions = TRANSITION_MATRIX.get(current_rating, {})
# Sum probabilities of lower ratings
ratings_order = ['AAA', 'AA', 'A', 'BBB', 'BB', 'B', 'CCC']
current_index = ratings_order.index(current_rating)
downgrade_prob = sum(transitions.get(r, 0)
for r in ratings_order[current_index+1:])
downgrade_prob += transitions.get('Default', 0)
return downgrade_prob / 100 # Convert to decimal
Expected Competency
Upon completion, you will be able to:
- Perform fundamental credit analysis using financial ratios
- Extract market-implied default probabilities from credit spreads
- Interpret credit ratings and understand rating methodologies
- Model PD and LGD using structural and reduced-form approaches
- Analyze credit spread behavior and decompose spread components
- Assess credit migration risk using transition matrices
- Monitor credit events and evaluate impact on portfolio
Deliverables
1. credit_risk_model.py
Production Python module containing:
- Fundamental credit metrics calculators
- PD/LGD modeling (Merton, hazard rate)
- Credit spread analysis tools
- Rating migration simulator
- Monte Carlo default scenario generator
- Portfolio credit risk aggregation
2. credit_spread_dashboard.md
Analysis documentation including:
- Credit spread tracking vs. Treasuries
- Spread curve analysis by rating tier
- Credit migration monitoring
- Event risk assessment framework
- Portfolio credit exposure report
Reference Materials
Foundational Texts
- Lando, D. Credit Risk Modeling: Theory and Applications
- Duffie, D. & Singleton, K. Credit Risk: Pricing, Measurement, and Management
- CFA Institute. Fixed Income - Credit Analysis
Rating Agency Methodologies
- Moody's: Rating Methodology
- S&P: Corporate Criteria
- Fitch: Rating Criteria
Data Sources
- FRED: Corporate Bond Spreads
- FINRA: Corporate Bond Data
- Bloomberg: Credit default swap (CDS) spreads
Validation Framework
Self-Assessment Checklist
- Calculate key credit ratios from financial statements
- Estimate implied default probability from credit spread
- Interpret rating agency methodologies
- Model PD using Merton framework
- Estimate LGD by seniority and collateral
- Decompose credit spread into components
- Apply credit migration matrices
- Assess portfolio credit concentration risk
Practice Problems
- Calculate implied PD for BB-rated bond: 300 bps spread, 40% recovery
- Estimate LGD for senior secured debt with $100M collateral, $150M debt
- Determine downgrade probability for A-rated issuer over 1 year
- Decompose 250 bps spread into expected loss and risk premium
Integration with Other Skills
- Bond Pricing: Credit spreads adjust discount rates
- Yield Measures: Credit component in yield analysis
- Benchmarking: Credit quality comparison vs. peers
- Portfolio Management: Credit risk aggregation and limits
Standards and Compliance
All credit analysis must document:
- Data sources and dates
- Rating sources and effective dates
- Assumptions (recovery rates, transition probabilities)
- Model validation and back-testing results
Version Control
Version: 1.0.0 Last Updated: 2025-12-07 Author: Ordinis-1 Bond Analysis Framework Status: Production Ready
Previous Skill: duration-convexity/
Next Skill: bond-benchmarking/