信用组合的风险,不能只看”现在违约率多少”。一只 AAA 债今天几乎不会违约,但它五年后落在 CCC 或 D 的概率并不低;一只 BBB 债明年有 6.5% 的概率滑到 BB 及以下、变成高收益。评级是会”迁移”的,而迁移的累积效应,决定了你持有信用组合三年、五年后的真实损失分布。
结论先放这:用一张年度转移矩阵 + 蒙特卡洛模拟,可以把信用组合的 1/3/5 年损失分布完整算出来。在我们的偏投资级组合(A 30% / BBB 35% / BB 10% / B 5%)下,5 年期望损失约 5.86%、99% VaR 约 7.47%、99% ES 约 7.73%、累计违约率约 3.99%。反直觉的是,组合层面的多期损失近似线性(因分散化平滑),而真正的非线性藏在累计违约率里——它随期限超线性上升,因为边际违约率逐年走高。 附完整 Python 与四张真实计算图(高阶)。

一、评级迁移矩阵:信用世界的”状态转移”#
评级迁移矩阵(Transition Matrix)描述”今年评级为 X 的债券,明年评级变成 Y 的概率”。它是一个行随机矩阵:每行和为 1。状态通常取 {AAA, AA, A, BBB, BB, B, CCC, D},其中 D(违约)是吸收态——一旦违约,就永远停在 D,不再迁移。
我们用一组合成的年度转移概率(百分比)作为例子:
import numpy as np
states = ["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "D"]
raw = np.array([
[89.0, 8.5, 1.4, 0.6, 0.3, 0.2, 0.0, 0.0], # AAA
[2.0, 89.0, 7.5, 0.9, 0.3, 0.2, 0.0, 0.1], # AA
[0.3, 3.5, 88.0, 6.5, 1.0, 0.5, 0.1, 0.1], # A
[0.1, 0.6, 4.5, 86.0, 6.5, 1.8, 0.3, 0.2], # BBB
[0.1, 0.3, 1.0, 6.0, 80.0, 9.5, 1.5, 1.6], # BB
[0.1, 0.2, 0.6, 1.2, 5.5, 81.0, 7.5, 3.9], # B
[0.2, 0.3, 0.6, 1.5, 3.0, 12.0, 56.0, 26.4], # CCC
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0], # D (吸收态)
], dtype=float)
P = raw / raw.sum(axis=1, keepdims=True) # 行归一化到和为 1python矩阵里三个结构一眼可见:对角线最厚(多数债券评级不变)、越往下迁移幅度越大(CCC 一年内有 26.4% 直接违约)、D 行全是 100%(吸收态)。这张图就是我们后续所有计算的”引擎”。
二、组合评级分布随时间的漂移#
把初始组合权重 左乘转移矩阵的幂 ,就能得到 T 年后的确定性评级分布:
下图用这个确定性演化,展示我们偏投资级组合在 5 年内如何逐步向低评级”渗漏”——注意 BBB 那一档的占比先升后降:它既接收从 A 滑下来的,也把持仓漏给 BB/B,是迁移流的”中转站”。

init_mix = np.array([0.05, 0.15, 0.30, 0.35, 0.10, 0.05, 0.0, 0.0])
det = init_mix.copy()
for y in range(1, 6):
det = det @ P # 每年推进一次
print(f"{y}年: ", np.round(det, 4))python这个确定性视角回答的是”平均而言组合会变成什么样”。但它看不到不确定性——有些路径 BBB 集体滑向 B,有些路径平稳得多。要算损失分布,必须上蒙特卡洛。
三、蒙特卡洛模拟多期损失分布#
对每个 obligor 独立地按转移矩阵逐年抽样(向量化实现),跑 5000 条路径、每条 1000 个 obligor,等权组合,最终把”评级变了”和”违约了”都折算成损失:
- 迁移的盯市损益(MTM):评级下降→点差走阔→债券净价下跌。损失 = 久期 × (期末点差 − 期初点差) / 10000。
- 违约损失:落入 D 的 obligor 直接损失 LGD(违约损失率,本文取 60%)。
P_cum = np.cumsum(P, axis=1)
def step_states(cur):
out = np.empty_like(cur)
for k in range(len(states)):
idx = np.where(cur == k)[0]
if idx.size == 0:
continue
u = rng.random(idx.size)
out[idx] = np.clip(np.searchsorted(P_cum[k], u, side="right"), 0, n - 1)
return out
def simulate(n_paths, n_obligors, horizon):
init = rng.choice(n, size=(n_paths, n_obligors), p=init_mix).ravel()
s = init.copy()
for _ in range(horizon):
s = step_states(s)
sp_init = spread[init].reshape(n_paths, n_obligors)
sp_final = spread[s].reshape(n_paths, n_obligors)
mtm = duration * (sp_final - sp_init) / 10000.0
defaulted = (s == D).reshape(n_paths, n_obligors).astype(float) * LGD
return (mtm + defaulted).mean(axis=1) * 100.0 # 等权组合损失%python跑完 1/3/5 年,我们得到每条路径的组合损失(% 面值),再取分位数。核心结果(种子 20260827):
| 期限 | 期望损失 EL | 95% VaR | 99% VaR | 99% ES | 累计违约率 |
|---|---|---|---|---|---|
| 1 年 | 0.93% | 1.35% | 1.55% | 1.68% | 0.47% |
| 3 年 | 3.23% | 4.06% | 4.43% | 4.60% | 2.00% |
| 5 年 | 5.86% | 6.99% | 7.47% | 7.73% | 3.99% |

四、反直觉:组合层近似线性,非线性藏在违约率里#
直觉上,“持有 5 年的风险应该是持有 1 年的 5 倍”。我们的数据拆穿了这层想当然,而且拆出两个方向相反的效应:
效应一(组合层略低于线性): 99% VaR 从 1 年 1.55% 到 5 年 7.47%。如果严格线性,5 年该是 7.73%。差的那 0.26% 来自分散化——1000 个独立 obligor 的极端尾部被平均掉了,组合损失不会像单名那样剧烈。这是信用组合相对股票组合最大的福利:分散化真的有用,因为违约是低频、相对独立的事件。
效应二(累计违约率超线性,这才是真凸性): 累计违约率从 1 年 0.47% 到 3 年 2.00% 到 5 年 3.99%。如果按 1 年线性外推,5 年只该 2.35%,真实却达 3.99%——高出近一倍。原因不是统计巧合,而是数学结构:边际违约率逐年上升。第 1 年只有最差的 CCC 会违约;第 3、5 年,那些逐年从 A→BBB→BB→B 滑下来的债券也加入了违约行列,违约”漏斗”越往后越宽。

一个自洽性检验:模拟得到的 5 年组合累计违约率 3.99%,与用初始权重对”各单名 5 年→D 概率”做加权平均(3.98%)几乎完全一致——说明我们的蒙特卡洛与矩阵解析口径对齐,引擎没 bug。值得注意的单名对比:BBB 单名 5 年→D 概率是 3.17%,而含 BB/B 倾斜的组合整体违约率 3.99% 反而更高——因为组合里那一小块 BB/B 的边际违约率远高于 BBB,拉高了整体。
五、为什么”组合层线性、违约率超线性”重要#
这个看似技术性的结论,直接改写了风险管理的话术:
- 用 1 年 VaR 简单乘期限数来估算多年资本占用,会系统性高估组合层风险(因为忽略了分散化),但会系统性低估违约暴露(因为忽略了边际违约率上升)。
- 监管资本通常按 1 年 horizon 算,但你持有信用组合可能是 3–5 年。真正的”长持风险”不在损失金额的线性放大,而在违约率的复利式累积——这恰恰是很多信用产品(如 CLO、信用债基金)在危机里”前几年没事、第五年突然集体出事”的数学根源。
- 对择时与久期管理的启示:迁移风险主要在中段期限(2–4 年)累积最快。1 年太短、违约漏斗还没打开;5 年以上则主要风险已是”已知会违约的那批”,反而可被定价。
六、模型边界:本文没做的与必须补的#
诚实的边界比漂亮的曲线更重要:
- 独立性假设:本文每个 obligor 独立抽样。真实信用组合有系统性因子(宏观、行业),危机会让评级同时下调,相关性在尾部飙升。正确做法是在转移矩阵上叠加系统因子(如信用周期状态),或用 copula 把单名迁移耦合起来。
- 点差-违约的联合动态:本文把点差变动和违约当成独立两笔账。危机里点差走阔和违约是同一件事的两面,应联合建模。
- 回收率的不确定性:本文 LGD 固定 60%。真实回收率高度分散且危机期骤降,应作为分布而非常数。
- 矩阵本身的估计误差:真实转移矩阵来自有限历史样本,尤其 CCC→D 这种低频格子的估计极不稳定。落地时务必对矩阵做置信区间,而不是把点估计当真理。
七、小结与可复现#
本文用一张 8 状态年度转移矩阵,把一只偏投资级信用组合的 1/3/5 年损失分布完整算了出来,并揭示了一个反直觉、但数学上必然的结构:组合层面的多期损失近似线性(分散化在起作用),而累计违约率随期限超线性上升(边际违约率逐年走高)——真正的非线性风险藏在违约率里,不在损失金额里。 关键数字(种子 20260827):
- 5 年期望损失 5.86%、99% VaR 7.47%、99% ES 7.73%、累计违约率 3.99%
- 1 年线性外推 5 年 VaR 应为 7.73%,真实 7.47%(分散化平滑 −0.26pp)
- 1 年线性外推 5 年违约率应为 2.35%,真实 3.99%(超线性 +1.64pp)
- 自洽性:模拟违约率 3.99% vs 加权单名 3.98%(引擎对齐)
一句话收尾:评级迁移矩阵给了我们一张”信用会怎么变老”的地图,而多期损失分布的真正形状,取决于你有没有把”违约是吸收态、且越往后越容易掉进去”这件事算进去。 完整代码与四张图均来自本文脚本,欢迎改矩阵、改组合、改期限复现。
本文图表均由 Python(numpy + matplotlib)实时计算生成,非占位图。模型为教学用合成矩阵,参数可改;真实落地请用实际历史估计的转移矩阵、行业/系统性耦合与回收率分布替换文中的受控假设。