03-信用衍生品 (Credit Derivatives)
预计学习时间:2-3 小时
难度:⭐⭐⭐⭐
核心问题:“借钱不还怎么办”这个最朴素的金融风险,怎么量化、怎么定价?
概述
信用衍生品处理的是金融世界中最古老的风险——违约风险。
利率衍生品处理”利率变了怎么办”,信用衍生品处理”对方不还钱了怎么办”。后者的核心区别在于:违约是一个不可逆的跳跃事件,一旦发生就不可挽回。
"借钱不还怎么办" → 违约概率 → CDS → Merton 模型 → CDO → CVA/DVA
一、违约概率:借钱不还的可能性
1.1 违约概率的基本概念
白话解释:违约概率就是”这个借款人(或这家公司)在未来某个时间内不还钱的概率”。
| 概念 | 含义 | 例子 |
|---|---|---|
| 违约概率(PD) | 在给定时间内违约的概率 | 未来 1 年违约概率 2% |
| 违约损失率(LGD) | 违约时能收回多少 | 违约后收回 40%,损失 60% |
| 违约风险暴露(EAD) | 违约时暴露的金额 | 贷款余额 1000 万 |
| 预期损失(EL) | EL = PD × LGD × EAD | 2% × 60% × 1000 万 = 12 万 |
1.2 历史违约率 vs 预期违约概率
历史违约率:根据过去的违约数据统计出来的频率。比如”过去 20 年,BBB 评级公司中有 0.2% 在一年内违约”。
预期违约概率(风险中性):从市场价格(如债券收益率、CDS 利差)反推出来的违约概率。这个概率通常高于历史违约率,因为市场包含了风险溢价。
为什么两者不同?
- 历史违约率是”平均情况”——但你买的债券可能运气不好
- 市场价格还包含了违约风险补偿(风险溢价)
- 市场还反映了投资者对未来经济环境的预期
1.3 从信用利差反推违约概率
信用利差 = 企业债券收益率 - 无风险债券收益率
白话解释:信用利差就是”投资者要求多拿多少利息,才愿意承担违约风险”。
如果我们做一个简化的假设(违约只发生在到期日,违约损失率 = LGD):
即:信用利差 ≈ 违约概率 × 违约损失率
更精确的连续复利近似:
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# 从信用利差反推违约概率
# ============================================================
def implied_pd(spread, lgd, T):
"""
从信用利差反推风险中性违约概率
参数:
spread: 信用利差(年化,小数形式)
lgd: 违约损失率(0-1)
T: 期限(年)
"""
# 简化公式: PD ≈ spread * T / LGD
pd_simple = spread * T / lgd
# 更精确的公式: PD = 1 - exp(-spread * T / LGD)
pd_continuous = 1 - np.exp(-spread * T / lgd)
return pd_simple, pd_continuous
# 不同信用评级的信用利差
ratings = ['AAA', 'AA', 'A', 'BBB', 'BB', 'B', 'CCC']
spreads = [0.005, 0.010, 0.020, 0.035, 0.060, 0.100, 0.200] # 年化利差
T = 1.0 # 1 年期
lgd = 0.60 # 违约损失率 60%
print("从信用利差反推违约概率")
print("=" * 60)
print(f"{'评级':>6} {'信用利差':>10} {'违约概率(简化)':>15} {'违约概率(连续)':>15}")
print("-" * 55)
pds = []
for rating, spread in zip(ratings, spreads):
pd_s, pd_c = implied_pd(spread, lgd, T)
pds.append(pd_c)
print(f"{rating:>6} {spread:>10.2%} {pd_s:>15.4%} {pd_c:>15.4%}")
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:违约概率 vs 评级
colors = ['green', 'green', 'lime', 'yellow', 'orange', 'red', 'darkred']
axes[0].bar(range(len(ratings)), [p * 100 for p in pds], color=colors, alpha=0.7)
axes[0].set_xticks(range(len(ratings)))
axes[0].set_xticklabels(ratings)
axes[0].set_ylabel('违约概率(%)')
axes[0].set_title('各评级 1 年期违约概率(从信用利差反推)')
# 右图:违约概率 vs 信用利差
spread_range = np.linspace(0, 0.20, 100)
pd_range = [implied_pd(s, lgd, T)[1] for s in spread_range]
axes[1].plot(spread_range * 10000, np.array(pd_range) * 100, 'b-', lw=2)
axes[1].set_xlabel('信用利差(基点 bps)')
axes[1].set_ylabel('违约概率(%)')
axes[1].set_title(f'违约概率 vs 信用利差(LGD={lgd:.0%},T={T}年)')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print(f"\n白话解释:")
print(f" 一个 BBB 评级(利差 350bps)的公司,1 年内违约概率约 {pds[3]:.2%}")
print(f" 一个 B 评级(利差 1000bps)的公司,1 年内违约概率约 {pds[5]:.2%}")
print(f" 评级越低 → 利差越高 → 违约概率越大 → 债券价格越低")二、CDS(信用违约互换)
2.1 什么是 CDS
白话解释:CDS 就是”违约保险”。你每年付一笔保费给保险公司(保护卖方),如果公司违约了,保险公司赔你损失。
| 角色 | 类比 | 收益 |
|---|---|---|
| 保护买方(Buyer) | 买保险的人 | 违约时获赔偿 |
| 保护卖方(Seller) | 卖保险的人 | 收保费,违约时赔钱 |
| 参考实体(Reference Entity) | 被保险的标的 | 违约则触发赔付 |
| 保费(Spread) | 保险费 | 每年按名义本金的一定比例 |
| 名义本金(Notional) | 保险金额 | 通常 1000 万美元 |
2.2 CDS 的运作方式
保护买方 ──每年付保费(Spread)──→ 保护卖方
保护买方 ←──违约时赔付─── 保护卖方
举例:
你持有 A 公司发行的 1000 万美元债券。你担心 A 公司违约,于是买了一个 CDS:
- 你每年付给保护卖方 200 个基点(2%),即每年 20 万
- 如果 A 公司违约了,保护卖方赔给你 1000 万 × LGD
CDS 的两种赔付方式:
- 实物交割:你把违约债券交给卖方,卖方付给你面值
- 现金结算:卖方直接付给你面值和债券残值的差额
2.3 CDS 定价(强度模型)
CDS 的定价基于违约强度(hazard rate)模型。
违约强度():在极短时间 内违约的概率 ≈ 。你可以把它理解为”违约事件的瞬时发生率”,类似泊松过程。
在常数违约强度假设下:
- 生存概率(到时间 没有违约):
- 违约概率密度(在时间 附近违约):
CDS 定价的公式:
保费现值 =
赔付现值 =
其中 是 CDS 价差(保费率), 是回收率。
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# CDS 定价(简化强度模型)
# ============================================================
def cds_pricing(hazard_rate, recovery_rate, risk_free_rate, maturity, payment_freq=4):
"""
CDS 定价(常数违约强度模型)
参数:
hazard_rate: 违约强度 λ(年化)
recovery_rate: 回收率(违约后能收回的比例)
risk_free_rate: 无风险利率
maturity: CDS 期限(年)
payment_freq: 每年付保费次数(默认季度)
返回:
cds_spread: 公平 CDS 价差(年化)
"""
dt = 1.0 / payment_freq
n_payments = int(maturity * payment_freq)
# 保费端现值(Premium Leg)
premium_pv = 0
for i in range(1, n_payments + 1):
t = i * dt
survival_prob = np.exp(-hazard_rate * t)
discount = np.exp(-risk_free_rate * t)
premium_pv += discount * survival_prob * dt
# 赔付端现值(Protection Leg)
protection_pv = 0
for i in range(1, n_payments + 1):
t = i * dt
# 在 [t-dt, t] 期间违约的概率
default_prob = np.exp(-hazard_rate * (t - dt)) - np.exp(-hazard_rate * t)
discount = np.exp(-risk_free_rate * t)
protection_pv += (1 - recovery_rate) * default_prob * discount
# 公平价差 = 赔付现值 / 保费现值因子
cds_spread = protection_pv / premium_pv
return cds_spread, premium_pv, protection_pv
# 参数
hazard_rate = 0.02 # 违约强度 2%(年化)
recovery_rate = 0.40 # 回收率 40%
risk_free_rate = 0.03 # 无风险利率 3%
maturity = 5.0 # 5 年期 CDS
spread, prem_pv, prot_pv = cds_pricing(hazard_rate, recovery_rate, risk_free_rate, maturity)
print("=" * 50)
print("CDS 定价(强度模型)")
print("=" * 50)
print(f"违约强度: {hazard_rate:.2%}")
print(f"回收率: {recovery_rate:.0%}")
print(f"无风险利率: {risk_free_rate:.2%}")
print(f"期限: {maturity} 年")
print(f"\n公平 CDS 价差: {spread:.4%} ({spread * 10000:.0f} bps)")
print(f"保费端现值系数: {prem_pv:.6f}")
print(f"赔付端现值: {prot_pv:.6f}(每 1 元名义本金)")
print()
print("白话解释:")
print(f" 为了保护 1 亿元的名义本金不违约,")
print(f" 你每年需要付保费 {spread*10000:.0f} bps,即 {spread*1e8:.0f} 元。")
print(f" 5 年总共付保费约 {spread*1e8*5:.0f} 元。")
# CDS 价差随违约强度的变化
lambda_range = np.linspace(0.005, 0.05, 50)
spreads = [cds_pricing(l, recovery_rate, risk_free_rate, maturity)[0]
for l in lambda_range]
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:价差 vs 违约强度
axes[0].plot(lambda_range * 100, np.array(spreads) * 10000, 'b-', lw=2)
axes[0].set_xlabel('违约强度 lambda(%)')
axes[0].set_ylabel('CDS 价差(bps)')
axes[0].set_title('CDS 价差 vs 违约强度')
axes[0].grid(True, alpha=0.3)
# 右图:不同期限的生存概率
t_range = np.linspace(0, 10, 200)
for lam, color in zip([0.01, 0.02, 0.03, 0.05], ['green', 'blue', 'orange', 'red']):
survival = np.exp(-lam * t_range)
axes[1].plot(t_range, survival * 100, color=color, lw=2,
label=f'lambda={lam:.0%}')
axes[1].set_xlabel('时间(年)')
axes[1].set_ylabel('生存概率(%)')
axes[1].set_title('不同违约强度下的生存概率')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 从市场 CDS 价差反推违约强度
def implied_hazard_rate(cds_spread, recovery_rate, risk_free_rate, maturity):
"""从 CDS 价差反推违约强度(二分法)"""
low, high = 0.0001, 0.5
for _ in range(100):
mid = (low + high) / 2
calculated_spread = cds_pricing(mid, recovery_rate, risk_free_rate, maturity)[0]
if calculated_spread < cds_spread:
low = mid
else:
high = mid
return (low + high) / 2
market_cds = 0.03 # 市场价差 300 bps
implied_lambda = implied_hazard_rate(market_cds, recovery_rate, risk_free_rate, maturity)
print(f"\n市场 CDS 价差 = {market_cds*10000:.0f} bps")
print(f"反推违约强度 = {implied_lambda:.4%}")三、信用评级与迁移矩阵
3.1 信用评级
评级机构(Moody’s、S&P、Fitch)给发债主体打分,用来量化其信用风险。
| S&P 评级 | Moody’s 评级 | 含义 | 典型年违约率 |
|---|---|---|---|
| AAA | Aaa | 最高信用质量 | ~0.01% |
| AA | Aa | 很高信用质量 | ~0.02% |
| A | A | 高信用质量 | ~0.06% |
| BBB | Baa | 中等信用质量 | ~0.18% |
| BB | Ba | 投机级 | ~0.82% |
| B | B | 高投机级 | ~3.5% |
| CCC/C | Caa/C | 极高投机级 | ~15%+ |
| D | C | 违约 | 100% |
投资级 vs 投机级:BBB- 及以上是”投资级”(Investment Grade),BB+ 及以下是”投机级”或”垃圾债”(High Yield / Junk Bond)。这个分界线在实际金融中极其重要——很多机构投资者被法规限制只能买投资级债券。
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# 信用评级:违约率可视化
# ============================================================
ratings = ['AAA', 'AA', 'A', 'BBB', 'BB', 'B', 'CCC/C']
default_rates = [0.0001, 0.0002, 0.0006, 0.0018, 0.0082, 0.035, 0.15]
colors = ['darkgreen', 'green', 'limegreen', 'yellow', 'orange', 'red', 'darkred']
fig, ax = plt.subplots(figsize=(10, 5))
bars = ax.bar(range(len(ratings)), [d * 100 for d in default_rates],
color=colors, alpha=0.8, edgecolor='black')
ax.set_xticks(range(len(ratings)))
ax.set_xticklabels(ratings, fontsize=12)
ax.set_ylabel('年违约率(%)')
ax.set_title('不同信用评级的典型年违约率')
ax.axvline(x=2.5, color='gray', linestyle='--', alpha=0.5)
ax.text(1, 0.12, '投资级', ha='center', fontsize=12, color='green')
ax.text(4.5, 0.12, '投机级', ha='center', fontsize=12, color='red')
ax.set_yscale('log') # 对数坐标更好展示差异
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
print("关键观察:")
print(" 从 AAA 到 BBB(投资级),违约率从 0.01% 到 0.18%,增加 18 倍")
print(" 从 BB 到 CCC(投机级),违约率从 0.82% 到 15%,增加 18 倍")
print(" 投资级和投机级之间的违约率差异跨越了两个数量级")3.2 迁移矩阵(Transition Matrix)
公司的信用评级不是一成不变的。迁移矩阵描述的是:一年后,一个 BBB 评级的公司有多大可能变成 BB、A 或违约?
迁移矩阵示意(一年期,简化数据):
当前评级 AAA AA A BBB BB B CCC 违约
AAA [0.91 0.08 0.01 0.00 0.00 0.00 0.00 0.00]
AA [0.01 0.90 0.08 0.01 0.00 0.00 0.00 0.00]
A [0.00 0.02 0.91 0.06 0.01 0.00 0.00 0.00]
BBB [0.00 0.00 0.04 0.89 0.05 0.01 0.00 0.01] ← 每行加起来 = 1
BB [0.00 0.00 0.00 0.05 0.85 0.07 0.02 0.01]
B [0.00 0.00 0.00 0.00 0.06 0.83 0.08 0.03]
CCC [0.00 0.00 0.00 0.00 0.01 0.08 0.70 0.21]
D [0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00] ← 吸收态
白话解读(BBB 那一行):
一个 BBB 公司,一年后:
- 4% 的概率升到 A(信用改善)
- 89% 的概率维持 BBB(不变)
- 5% 的概率降到 BB(信用恶化)
- 1% 的概率降到 B
- 1% 的概率违约
多年累积迁移:迁移矩阵的幂次方。两年后的迁移概率 = ,五年后 = 。
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# 信用迁移矩阵模拟
# ============================================================
# 简化的 7 个评级 + 违约
rating_names = ['AAA', 'AA', 'A', 'BBB', 'BB', 'B', 'CCC', 'D']
# 一年期迁移矩阵(简化数据,非精确市场值)
M1 = np.array([
# AAA AA A BBB BB B CCC D
[0.907, 0.083, 0.008, 0.001, 0.001, 0.000, 0.000, 0.000], # AAA
[0.007, 0.906, 0.078, 0.006, 0.002, 0.001, 0.000, 0.000], # AA
[0.001, 0.022, 0.910, 0.056, 0.008, 0.002, 0.001, 0.000], # A
[0.000, 0.003, 0.042, 0.892, 0.050, 0.009, 0.002, 0.002], # BBB
[0.000, 0.001, 0.004, 0.055, 0.847, 0.072, 0.015, 0.006], # BB
[0.000, 0.000, 0.002, 0.005, 0.068, 0.828, 0.075, 0.022], # B
[0.000, 0.000, 0.000, 0.008, 0.022, 0.105, 0.650, 0.215], # CCC
[0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 1.000], # D(吸收态)
])
# 计算多年累积迁移概率
M5 = np.linalg.matrix_power(M1, 5) # 5 年
M10 = np.linalg.matrix_power(M1, 10) # 10 年
M20 = np.linalg.matrix_power(M1, 20) # 20 年
# 对比不同期限的违约概率
print("=" * 65)
print("不同评级在不同时间跨度内的累积违约概率")
print("=" * 65)
print(f"{'评级':<8} {'1 年':>10} {'5 年':>10} {'10 年':>10} {'20 年':>10}")
print("-" * 50)
for i, rating in enumerate(rating_names[:-1]): # 排除 D
d1 = M1[i, -1]
d5 = M5[i, -1]
d10 = M10[i, -1]
d20 = M20[i, -1]
print(f"{rating:<8} {d1:>10.2%} {d5:>10.2%} {d10:>10.2%} {d20:>10.2%}")
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:不同期限的违约概率对比
x = np.arange(len(rating_names[:-1]))
width = 0.2
d1 = [M1[i, -1] for i in range(len(rating_names) - 1)]
d5 = [M5[i, -1] for i in range(len(rating_names) - 1)]
d10 = [M10[i, -1] for i in range(len(rating_names) - 1)]
d20 = [M20[i, -1] for i in range(len(rating_names) - 1)]
axes[0].bar(x - 1.5*width, d1, width, label='1 年', color='steelblue')
axes[0].bar(x - 0.5*width, d5, width, label='5 年', color='orange')
axes[0].bar(x + 0.5*width, d10, width, label='10 年', color='coral')
axes[0].bar(x + 1.5*width, d20, width, label='20 年', color='darkred')
axes[0].set_xticks(x)
axes[0].set_xticklabels(rating_names[:-1])
axes[0].set_ylabel('累积违约概率')
axes[0].set_title('不同评级的累积违约概率随时间增长')
axes[0].legend()
axes[0].grid(True, alpha=0.3, axis='y')
# 右图:BBB 公司的评级分布随时间变化
bbb_idx = 3 # BBB 在数组中的索引
for years, color in [(1, 'steelblue'), (5, 'orange'), (10, 'coral'), (20, 'darkred')]:
M_n = np.linalg.matrix_power(M1, years)
dist = M_n[bbb_idx, :-1] # 排除违约列
axes[1].plot(range(len(rating_names) - 1), dist * 100, 'o-', color=color,
lw=2, label=f'{years} 年后')
axes[1].set_xticks(range(len(rating_names) - 1))
axes[1].set_xticklabels(rating_names[:-1])
axes[1].set_ylabel('概率(%)')
axes[1].set_title('BBB 公司的未来评级分布(随时间扩散)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("\n白话解释:")
print(" 一个 BBB 公司 1 年违约率只有 0.2%,看起来很安全")
print(" 但 10 年违约率上升到 2% 以上,20 年超过 5%")
print(" 这就是为什么长期债券的信用风险比短期债券大得多")
print(" 评级分布随时间'扩散'——信用质量可能改善也可能恶化")四、Merton 结构模型
3.1 核心思想:公司 = 期权
白话解释:Merton 模型最巧妙的地方在于——把一家公司看作一个期权。
想象一下:
- 公司有资产(机器、专利、现金等),价值 ,随时在变动
- 公司有负债(借款、债券等),面值 ,到期要还
在债券到期时:
- 如果 :公司还清债务,股东拿到剩余部分 → 没违约
- 如果 :公司资不抵债,违约,股东什么都没有 → 违约了
看!股东的权益 ——这不就是一个看涨期权吗?
标的资产 = 公司资产 ,行权价 = 负债面值 ,到期日 = 债券到期日。
3.2 用 BSM 框架推导信用利差
因为股权 是一个看涨期权,我们可以直接用 BSM 公式定价:
其中:
从风险中性角度,违约概率就是 (公司资产不足以覆盖负债的概率)。
信用利差近似为:
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
# ============================================================
# Merton 结构模型
# ============================================================
def merton_model(V, D, r, sigma_V, T, recovery_rate=0.4):
"""
Merton 模型:将公司看作期权来评估违约风险
参数:
V: 公司资产价值
D: 负债面值
r: 无风险利率
sigma_V: 资产波动率
T: 负债到期时间
recovery_rate: 回收率
返回:
dict: 股权价值、违约概率、信用利差等
"""
d1 = (np.log(V / D) + (r + 0.5 * sigma_V**2) * T) / (sigma_V * np.sqrt(T))
d2 = d1 - sigma_V * np.sqrt(T)
# 股权价值(BSM Call)
equity = V * norm.cdf(d1) - D * np.exp(-r * T) * norm.cdf(d2)
# 违约概率(风险中性)
pd_rn = norm.cdf(-d2)
# 信用利差
credit_spread = pd_rn * (1 - recovery_rate) / T
# 距离违约的距离
distance_to_default = d2
return {
'equity': equity,
'pd_rn': pd_rn,
'credit_spread': credit_spread,
'd1': d1,
'd2': d2,
'distance_to_default': distance_to_default
}
# 模拟两家公司
np.random.seed(42)
companies = {
'稳健公司 A': {'V': 150, 'D': 100, 'sigma_V': 0.15},
'高风险公司 B': {'V': 110, 'D': 100, 'sigma_V': 0.35},
}
r = 0.05
T = 1.0
print("=" * 60)
print("Merton 结构模型:两家公司的违约风险评估")
print("=" * 60)
print(f"{'':>15} {'资产V':>6} {'负债D':>6} {'D/V':>6} {'sigma_V':>8} "
f"{'股权E':>8} {'违约概率':>10} {'信用利差':>10}")
print("-" * 80)
results = {}
for name, params in companies.items():
res = merton_model(params['V'], params['D'], r, params['sigma_V'], T)
results[name] = res
print(f"{name:>15} {params['V']:>6} {params['D']:>6} "
f"{params['D']/params['V']:>6.2f} {params['sigma_V']:>8.2%} "
f"{res['equity']:>8.1f} {res['pd_rn']:>10.4%} "
f"{res['credit_spread']:>10.2%}")
# 信用利差的 5 大决定因素
print("\n" + "=" * 50)
print("信用利差的 5 大决定因素(Merton 模型视角)")
print("=" * 50)
# 1. 资产/负债比
print("\n1. 杠杆率(D/V 越高,违约概率越大)")
V_range = np.linspace(80, 200, 50)
pd_vs_V = [merton_model(v, 100, r, 0.20, T)['pd_rn'] for v in V_range]
fig, axes = plt.subplots(2, 3, figsize=(16, 10))
axes[0, 0].plot(V_range, np.array(pd_vs_V) * 100, 'b-', lw=2)
axes[0, 0].axvline(x=100, color='red', linestyle='--', label='D=100')
axes[0, 0].set_xlabel('资产价值 V')
axes[0, 0].set_ylabel('违约概率(%)')
axes[0, 0].set_title('1. 杠杆率:资产价值越高越安全')
axes[0, 0].legend()
# 2. 资产波动率
print("2. 资产波动率(sigma_V 越大,违约概率越大)")
sigma_range = np.linspace(0.05, 0.50, 50)
pd_vs_sigma = [merton_model(120, 100, r, s, T)['pd_rn'] for s in sigma_range]
axes[0, 1].plot(sigma_range * 100, np.array(pd_vs_sigma) * 100, 'r-', lw=2)
axes[0, 1].set_xlabel('资产波动率 sigma_V(%)')
axes[0, 1].set_ylabel('违约概率(%)')
axes[0, 1].set_title('2. 波动率:资产波动越大越危险')
# 3. 到期时间
print("3. 到期时间(T 越长,违约概率越大——更长时间更多不确定性)")
T_range = np.linspace(0.1, 10, 50)
pd_vs_T = [merton_model(120, 100, r, 0.20, t)['pd_rn'] for t in T_range]
axes[0, 2].plot(T_range, np.array(pd_vs_T) * 100, 'g-', lw=2)
axes[0, 2].set_xlabel('到期时间 T(年)')
axes[0, 2].set_ylabel('违约概率(%)')
axes[0, 2].set_title('3. 期限:到期时间越长风险越大')
# 4. 回收率
print("4. 回收率(R 越低,信用利差越大——违约损失更大)")
R_range = np.linspace(0, 0.8, 50)
spread_vs_R = [merton_model(120, 100, r, 0.20, T, R)['credit_spread'] * 10000
for R in R_range]
axes[1, 0].plot(R_range * 100, spread_vs_R, 'm-', lw=2)
axes[1, 0].set_xlabel('回收率 R(%)')
axes[1, 0].set_ylabel('信用利差(bps)')
axes[1, 0].set_title('4. 回收率:回收率越低利差越大')
# 5. 无风险利率
print("5. 无风险利率(影响较复杂,通常影响较小)")
r_range = np.linspace(0.01, 0.10, 50)
pd_vs_r = [merton_model(120, 100, ri, 0.20, T)['pd_rn'] for ri in r_range]
axes[1, 1].plot(r_range * 100, np.array(pd_vs_r) * 100, 'c-', lw=2)
axes[1, 1].set_xlabel('无风险利率(%)')
axes[1, 1].set_ylabel('违约概率(%)')
axes[1, 1].set_title('5. 利率:影响方向取决于具体情况')
# 综合热力图
V_grid = np.linspace(80, 180, 40)
sigma_grid = np.linspace(0.05, 0.50, 40)
VV, SS = np.meshgrid(V_grid, sigma_grid)
PP = np.array([[merton_model(v, 100, r, s, T)['pd_rn'] for v in V_grid] for s in sigma_grid])
im = axes[1, 2].contourf(VV, SS, PP * 100, levels=20, cmap='RdYlGn_r')
plt.colorbar(im, ax=axes[1, 2])
axes[1, 2].set_xlabel('资产价值 V')
axes[1, 2].set_ylabel('资产波动率 sigma_V')
axes[1, 2].set_title('违约概率热力图(资产 vs 波动率)')
plt.tight_layout()
plt.show()
print("\n五大因素总结:")
print(" 杠杆高(D/V大)→ 危险")
print(" 波动大(sigma大)→ 危险")
print(" 期限长(T大)→ 危险")
print(" 回收低(R小)→ 损失大")
print(" 利率影响复杂 → 需具体分析")3.3 Merton 模型的局限
| 局限 | 具体问题 |
|---|---|
| 只有一个到期日 | 真实公司有多种到期日的债券 |
| 违约只在到期时 | 真实违约可能在任何时候发生 |
| 资产价值不可观测 | 只能从股价和波动率反推 |
| 假设资产服从 GBM | 真实资产分布有跳跃和厚尾 |
| 忽略债务优先级 | 不同债券的优先级不同 |
五、CDO/CLO 结构化产品
4.1 什么是 CDO
白话解释:CDO(Collateralized Debt Obligation)是一种”打包再分拆”的产品。
想象你有一筐苹果:
- 有些苹果品质好(AAA 评级债券)
- 有些苹果品质一般(BBB 评级债券)
- 有些苹果有点烂(BB 评级债券)
CDO 的做法是:把这筐苹果打成汁,然后分成几杯果汁:
| 层级(Tranche) | 特征 | 类比 |
|---|---|---|
| 高级层(Senior) | 最先受保护,违约时最后受损 | ”好果汁”——用最好的苹果做 |
| 夹层(Mezzanine) | 中等保护 | ”普通果汁”——混在一起 |
| 股权层(Equity) | 最先吸收损失,收益率最高 | ”剩渣”——但便宜,高风险高回报 |
白话解读:你买了高级层,就算底层有少量债券违约,也不会影响你——因为股权层先扛损失。只有当违约严重到把股权层和夹层都”烧光”了,高级层才会受损。
4.2 CLO(贷款抵押债券)
CLO 和 CDO 类似,但底层资产是银行贷款而不是债券。CLO 是银行管理资本的一种方式——把贷款打包出售,释放资本金。
4.3 2008 年金融危机与 CDO
2008 年金融危机的根源之一就是 CDO:
- 底层资产质量差:次级房贷(subprime mortgage)被包装成 AAA 的 CDO
- 评级失灵:评级机构给了高风险资产 AAA 评级
- 相关性假设错误:模型假设不同地区的房贷违约是不相关的(分散化),但房地产泡沫破裂时大家同时违约
- 杠杆叠加:CDO 上面还有 CDO²(CDO 的 CDO),杠杆率极高
关键教训:模型假设的”分散化”在系统性危机中完全失效。
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# CDO 结构化产品模拟
# ============================================================
def simulate_cdo(n_bonds, pd_per_bond, lgd, n_sims=100000, seed=42):
"""
模拟 CDO 的损失分布
参数:
n_bonds: 底层债券数量
pd_per_bond: 每个债券的违约概率
lgd: 违约损失率
n_sims: 模拟次数
"""
np.random.seed(seed)
# 模拟违约(假设独立)
defaults = np.random.binomial(1, pd_per_bond, size=(n_sims, n_bonds))
total_losses = np.sum(defaults, axis=1) * lgd # 总损失(占名义本金的比例)
return total_losses
# 参数
n_bonds = 100 # 100 个底层债券
pd_per_bond = 0.05 # 每个债券 5% 违约概率
lgd = 0.60 # 违约损失率 60%
notional = 1000 # 总名义本金 1000
# 模拟损失分布
losses_pct = simulate_cdo(n_bonds, pd_per_bond, lgd / n_bonds)
# 定义分层
tranches = {
'Equity (0-3%)': {'attachment': 0.00, 'detachment': 0.03},
'Mezzanine (3-7%)': {'attachment': 0.03, 'detachment': 0.07},
'Senior (7-12%)': {'attachment': 0.07, 'detachment': 0.12},
'Super Senior (12%+)': {'attachment': 0.12, 'detachment': 1.00},
}
print("=" * 60)
print("CDO 结构化产品模拟")
print("=" * 60)
print(f"底层债券数量: {n_bonds}")
print(f"每个债券违约概率: {pd_per_bond:.2%}")
print(f"违约损失率: {lgd:.0%}")
print(f"总名义本金: {notional}")
print()
# 计算每层的损失
for name, params in tranches.items():
a, d = params['attachment'], params['detachment']
# 该层的损失 = min(总损失, d) - min(总损失, a)
layer_loss = np.minimum(losses_pct, d) - np.minimum(losses_pct, a)
expected_loss = np.mean(layer_loss)
max_loss = np.max(layer_loss)
print(f"{name:>25} [{a*100:.0f}%-{d*100:.0f}%]:")
print(f" 预期损失: {expected_loss:.4%} of notional "
f"= {expected_loss * notional:.2f}")
print(f" 最大损失: {max_loss:.4%} of notional")
print()
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:损失分布
axes[0].hist(losses_pct * 100, bins=100, density=True, alpha=0.7, color='steelblue')
axes[0].axvline(x=3, color='red', linestyle='--', label='Equity/Mezz 边界 (3%)')
axes[0].axvline(x=7, color='orange', linestyle='--', label='Mezz/Senior 边界 (7%)')
axes[0].axvline(x=12, color='green', linestyle='--', label='Senior/Super 边界 (12%)')
axes[0].set_xlabel('总损失率(%)')
axes[0].set_ylabel('概率密度')
axes[0].set_title('CDO 底层资产损失分布')
axes[0].legend()
# 右图:分层结构图
y_positions = [4, 3, 2, 1]
widths = [(3-0), (7-3), (12-7), (100-12)]
colors = ['darkred', 'orange', 'steelblue', 'green']
labels = ['Equity\n(0-3%)', 'Mezzanine\n(3-7%)', 'Senior\n(7-12%)', 'Super Senior\n(12%-100%)']
for y, w, c, l in zip(y_positions, widths, colors, labels):
axes[1].barh(y, w, left=[0, 3, 7, 12][y_positions.index(y)],
color=c, alpha=0.7, height=0.6, edgecolor='black')
x_center = [0, 3, 7, 12][y_positions.index(y)] + w / 2
axes[1].text(x_center, y, l, ha='center', va='center', fontsize=9, fontweight='bold')
axes[1].set_xlabel('损失吸收(%)')
axes[1].set_title('CDO 分层结构')
axes[1].set_yticks([])
axes[1].set_xlim(0, 30)
plt.tight_layout()
plt.show()
print("白话解释:")
print(" Equity 层最先吸收损失——损失不超过 3% 时,只有 Equity 层受损")
print(" Mezzanine 层在 3%-7% 之间吸收损失")
print(" Senior 层在 7%-12% 之间")
print(" Super Senior 只有在损失超过 12% 时才会受损")
print()
print(" 2008 年的问题:底层房贷质量太差,损失轻松超过 12%,")
print(" 'AAA 评级'的 Super Senior 层也大规模亏损。")4.4 违约相关性与 Copula 模型
上面的 CDO 模拟假设各债券的违约是独立的。但在现实中,经济恶化时企业会同时违约——这就是违约相关性。
白话解释:
- 独立违约:100 家公司,每家 2% 违约率,平均 2 家违约,波动很小
- 高度相关:如果经济危机来了,可能 20 家同时违约;如果经济好,0 家违约
相关性悖论(这是 CDO 定价中最重要的反直觉结论):
违约相关性对 CDO 各层的影响:
相关性上升时:
权益层(Equity) → 风险先升后降
夹层(Mezzanine) → 风险显著上升
高级层(Senior) → 风险大幅上升(分散化失效!)
直觉解释:
低相关性 → 违约分散,几乎肯定有少量违约 → 权益层持续受损
高相关性 → 要么一起违约(大损失),要么都没事 → 高级层开始受损
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
# ============================================================
# CDO 定价:高斯 Copula 模型(违约相关性)
# ============================================================
def simulate_cdo_copula(n_names, n_sim, pd, lgd, correlation,
tranche_attachments):
"""
高斯 Copula 模型模拟 CDO 各层损失分布
使用单因子模型:
X_i = sqrt(rho) * Z + sqrt(1-rho) * eps_i
违约条件: X_i < N^(-1)(PD)
参数:
n_names: 底层债务人数量
n_sim: 模拟次数
pd: 每个债务人的违约概率
lgd: 违约损失率
correlation: 违约相关性 rho
tranche_attachments: {层名: (下界, 上界)}
返回:
tranche_losses: 各层的损失率
portfolio_losses: 资产池总损失率
"""
threshold = norm.ppf(pd) # 违约阈值
portfolio_losses = np.zeros(n_sim)
for sim in range(n_sim):
# 系统性因子(所有债务人共享)
Z = np.random.randn()
# 条件违约概率
conditional_pd = norm.cdf(
(threshold - np.sqrt(correlation) * Z)
/ np.sqrt(1 - correlation)
)
# 模拟违约
defaults = np.random.rand(n_names) < conditional_pd
loss = np.mean(defaults) * lgd
portfolio_losses[sim] = loss
# 各层损失
tranche_losses = {}
for name, (lower, upper) in tranche_attachments.items():
layer_loss = np.minimum(
np.maximum(portfolio_losses - lower, 0), upper - lower
)
tranche_losses[name] = layer_loss / (upper - lower)
return tranche_losses, portfolio_losses
# 参数
n_names = 100
n_sim = 50000
pd = 0.02
lgd = 0.60
tranches = {
'Equity': (0.00, 0.03),
'Mezzanine': (0.03, 0.07),
'Senior': (0.07, 0.12),
'Super Senior': (0.12, 1.00),
}
np.random.seed(42)
# 不同相关性下各层的预期损失
corr_range = np.linspace(0.01, 0.80, 20)
results = {name: [] for name in tranches}
for corr in corr_range:
tl, _ = simulate_cdo_copula(
n_names, n_sim, pd, lgd, corr, tranches
)
for name in tranches:
results[name].append(np.mean(tl[name]))
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:各层预期损失 vs 相关性
colors_map = {'Equity': 'darkred', 'Mezzanine': 'orange',
'Senior': 'steelblue', 'Super Senior': 'green'}
for name in tranches:
axes[0].plot(corr_range * 100,
np.array(results[name]) * 100,
lw=2, label=name, color=colors_map[name])
axes[0].set_xlabel('违约相关性 rho(%)')
axes[0].set_ylabel('层内预期损失率(%)')
axes[0].set_title('违约相关性对 CDO 各层预期损失的影响')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 右图:不同相关性下资产池损失分布
for corr_val, color, label in [(0.05, 'green', 'rho=5%'),
(0.30, 'blue', 'rho=30%'),
(0.70, 'red', 'rho=70%')]:
np.random.seed(42)
_, pl = simulate_cdo_copula(
n_names, n_sim, pd, lgd, corr_val, tranches
)
axes[1].hist(pl * 100, bins=80, density=True, alpha=0.5,
color=color, label=label)
axes[1].set_xlabel('资产池总损失率(%)')
axes[1].set_ylabel('概率密度')
axes[1].set_title('不同违约相关性下的资产池损失分布')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
print("关键观察:")
print(" 1. Equity 层在低相关性时损失较高(总是有少量违约)")
print(" 2. Senior 层在高相关性时损失急剧上升(分散化失效)")
print(" 3. 高相关性下损失分布变得双峰(要么没事,要么一起爆)")
print()
print("2008 年危机的本质:")
print(" 相关性从 5% 飙升到 70%+ → 分散化完全失效")
print(" → 原本'安全'的 Senior 和 Super Senior 层大规模亏损")六、Counterparty 风险:CVA / DVA
5.1 对手方风险
白话解释:你和银行签了一个衍生品合约,银行欠你钱。但如果银行倒闭了呢?你的合约价值就变成了废纸。
这就是对手方风险(Counterparty Risk):交易对手违约,导致你遭受损失的风险。
在 2008 年之前,很多人认为这种风险可以忽略(大型银行”大而不倒”)。2008 年之后,大家意识到必须量化这种风险。
5.2 CVA(Credit Valuation Adjustment)
CVA = 无对手方风险的衍生品价值 - 有对手方风险的衍生品价值
白话解释:CVA 是因为对手方可能违约而导致的”价值折扣”。你需要把衍生品的价值打一个折扣来反映对手方风险。
CVA 的简化公式:
其中:
| 变量 | 含义 |
|---|---|
| 违约损失率 | |
| 预期正暴露(Expected Positive Exposure)——你的衍生品在 时刻暴露给对手方的价值 | |
| 时刻的违约概率密度 |
白话解读:
- EPE:如果对手方在 时刻违约,你大概会损失多少?(只有衍生品价值为正时才暴露风险)
- CVA 就是把每个时刻的”可能损失”按违约概率加权求和
5.3 DVA(Debt Valuation Adjustment)
白话解释:CVA 是”别人可能不还我钱”的风险折扣。DVA 是”我可能不还别人钱”的风险调整。
当你的信用状况恶化时,DVA 上升——你欠别人的钱”变便宜了”。这在直觉上很奇怪:你自己信用越差,你的衍生品价值反而越高(因为 DVA 增加了)。
这就是”mark-to-market”会计的一个反直觉特性。2008 年金融危机时,很多银行的财报中出现了 DVA 带来的”利润”——因为它们自己的信用评级被下调了。
5.4 Python 模拟 CVA
import numpy as np
import matplotlib.pyplot as plt
# ============================================================
# CVA 计算(简化版蒙特卡洛)
# ============================================================
def calculate_cva(notional, r, hazard_rate, lgd, n_paths=50000, n_steps=50):
"""
简化版 CVA 计算
参数:
notional: 衍生品名义本金
r: 无风险利率
hazard_rate: 对手方违约强度
lgd: 违约损失率
n_paths: 蒙特卡洛路径数
n_steps: 时间步数
"""
T = 5.0 # 衍生品期限 5 年
dt = T / n_steps
times = np.linspace(dt, T, n_steps)
# 1. 模拟衍生品价值路径(简化:假设是一个利率互换的固定端价值)
# 互换价值 ≈ 收到的浮动利率现值 - 付出的固定利率现值
# 这里用一个简单的随机过程模拟
np.random.seed(42)
W = np.random.standard_normal((n_steps, n_paths))
# 简化的互换价值模型
swap_value = np.zeros((n_steps, n_paths))
sigma_swap = 0.02 # 互换价值波动率
for i in range(n_steps):
drift = 0.001 # 轻微的正漂移(假设收到浮动利率略高于固定利率)
swap_value[i] = swap_value[i-1] if i > 0 else 0.01 * notional
swap_value[i] += swap_value[i] * drift * dt
swap_value[i] += swap_value[i] * sigma_swap * np.sqrt(dt) * W[i]
swap_value[i] = np.maximum(swap_value[i], -0.3 * notional) # 下限
# 2. 计算每个时间步的 EPE(Expected Positive Exposure)
epe = np.array([np.mean(np.maximum(swap_value[i], 0)) for i in range(n_steps)])
# 3. 计算每个时间步的违约概率
default_probs = np.array([
np.exp(-hazard_rate * times[i-1]) - np.exp(-hazard_rate * times[i])
if i > 0 else 1 - np.exp(-hazard_rate * dt)
for i in range(n_steps)
])
# 4. 计算 CVA
discount_factors = np.exp(-r * times)
cva = -lgd * np.sum(epe * default_probs * discount_factors) * dt
return cva, epe, times
# 参数
notional = 100_000_000 # 1 亿
r = 0.03
hazard_rate = 0.02 # 对手方违约强度 2%
lgd = 0.60
cva, epe, times = calculate_cva(notional, r, hazard_rate, lgd)
print("=" * 50)
print("CVA(信用估值调整)计算")
print("=" * 50)
print(f"名义本金: {notional/1e8:.0f} 亿")
print(f"对手方违约强度: {hazard_rate:.2%}")
print(f"违约损失率: {lgd:.0%}")
print(f"无风险利率: {r:.2%}")
print(f"\nCVA = {cva:,.0f} 元")
print(f"CVA 占名义本金比例 = {abs(cva)/notional:.4%}")
print()
print("白话解释:")
print(f" 因为对手方可能违约,你的衍生品合约需要打折")
print(f" CVA = {abs(cva)/1e4:.0f} 万元")
print(f" 这就是对手方风险的'价格'")
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:EPE 随时间变化
axes[0].plot(times, epe / 1e6, 'b-', lw=2)
axes[0].fill_between(times, 0, epe / 1e6, alpha=0.2, color='blue')
axes[0].set_xlabel('时间(年)')
axes[0].set_ylabel('EPE(百万元)')
axes[0].set_title('预期正暴露(EPE)随时间变化')
axes[0].grid(True, alpha=0.3)
# 右图:CVA 组成
survival = np.exp(-hazard_rate * times)
default_density = hazard_rate * np.exp(-hazard_rate * times)
ax2 = axes[1]
ax2.plot(times, survival * 100, 'g-', lw=2, label='生存概率')
ax2_twin = ax2.twinx()
ax2_twin.plot(times, default_density * 100, 'r--', lw=1.5, label='违约密度')
ax2.set_xlabel('时间(年)')
ax2.set_ylabel('生存概率(%)', color='green')
ax2_twin.set_ylabel('违约密度(%)', color='red')
ax2.set_title('生存概率与违约密度')
# 合并图例
lines1, labels1 = ax2.get_legend_handles_labels()
lines2, labels2 = ax2_twin.get_legend_handles_labels()
ax2.legend(lines1 + lines2, labels1 + labels2, loc='center right')
plt.tight_layout()
plt.show()
# 不同对手方信用质量的 CVA 对比
print("\n不同对手方信用质量下的 CVA 对比")
print("-" * 50)
print(f"{'违约强度':>10} {'CVA(万元)':>12} {'占名义比例':>12}")
print("-" * 40)
for lam in [0.005, 0.01, 0.02, 0.05, 0.10]:
cva_val = calculate_cva(notional, r, lam, lgd)[0]
print(f"{lam:>10.2%} {abs(cva_val)/1e4:>12.2f} {abs(cva_val)/notional:>12.4%}")七、信用衍生品全景
6.1 从简单到复杂的信用风险层次
违约概率(PD) ← 最基础的度量
↓
CDS(违约保险) ← 单个实体的违约保护
↓
Merton 模型 ← 从公司基本面推导违约概率
↓
CDO/CLO ← 一篮子实体的分层违约保护
↓
CVA/DVA ← 衍生品交易中的对手方信用风险调整
↓
FVA/KVA/XVA ← 更全面的风险调整框架
6.2 信用风险 vs 市场风险
| 维度 | 市场风险 | 信用风险 |
|---|---|---|
| 来源 | 价格波动(利率、股价、汇率) | 交易对手违约 |
| 频率 | 每天都在波动 | 违约是稀有事件 |
| 分布 | 近似正态(但厚尾) | 严重偏斜(大部分时间 0,违约时巨大损失) |
| 对冲 | 容易(Delta、Gamma 对冲) | 困难(需要 CDS 或担保品) |
| 度量 | VaR、Greeks | PD、LGD、EAD、CVA |
八、关键概念回顾
| 概念 | 一句话解释 |
|---|---|
| 违约概率(PD) | 未来某时间内不还钱的概率 |
| 违约损失率(LGD) | 违约时损失的本金比例 |
| 信用利差 | 企业债收益率 - 国债收益率 = 违约风险补偿 |
| 信用评级 | AAA ~ D 的分级体系;BBB- 是投资级分界线 |
| 迁移矩阵 | 描述评级随时间变化的概率;多年迁移 = 矩阵幂次 |
| CDS | 违约保险,每年付保费,违约时获赔付 |
| 违约强度() | 瞬时违约发生率,类似泊松过程参数 |
| Merton 模型 | 把公司看作期权,从资产/负债结构推导违约概率 |
| 违约距离(DD) | 资产价值偏离违约临界点的标准差个数 |
| CDO | 打包再分拆——底层资产损失按层级吸收 |
| 高斯 Copula | 用相关因子模型描述多资产联合违约 |
| 相关性悖论 | 相关性上升对高级层更危险(分散化失效) |
| CVA | 因为对手方可能违约而给衍生品打的折扣 |
| DVA | 因为”我”可能违约而给衍生品做的调整 |
推荐阅读
| 书名 | 章节 | 核心价值 |
|---|---|---|
| Options, Futures, and Other Derivatives (Hull) | Ch.24-25 | CDS、CDO、CVA 的权威参考 |
| Credit Risk Modeling (Lando) | Ch.2-5 | 违约强度模型的严格推导 |
| The XVA Challenge (Gregory) | 全书 | CVA/DVA/FVA 的实务指南 |
版本信息
- 创建日期:2026-03-28
- 最后更新:2026-03-28