← 返回编程题库
coding-cds-implied-flat-hazard-cumulative-pd中等免费版2000ms未尝试

由 CDS 利差反推累计违约概率:信用三角近似

CDS-Implied Cumulative Default Probability via the Credit-Triangle Approximation

开始编码

实现 solution(spread_bps: float, recovery_rate: float, horizon_years: float) -> float。某信贷台每天跑一个脚本,从挂出的 CDS 平价利差反推暗含的累计违约概率,把结果写进信用看板的"暗含 PD"那一列。给定平价 CDS 利差 spread_bps(基点)、假设回收率 recovery_rate、视野 horizon_years(年),按标准信用三角扁平 hazard 近似,返回该视野下的累计违约概率。

公式三步:bps 转十进制 s = spread_bps / 10000.0;信用三角反推 hazard lambda = s / (1.0 - recovery_rate)(CDS 平价利差每年补偿期望损失 lambda * (1 - R),反解得 lambda = s / (1 - R));视野累计违约概率 cum_pd = 1 - exp(-lambda * horizon_years)(连续时间扁平 hazard 下存活概率为 exp(-lambda*T),故累计违约为 1 - exp(-lambda*T))。

示例

solution(150.0, 0.4, 5.0) 返回 0.11750309741540454。逐步:s = 150 / 10000 = 0.0150(年化 1.5% 的利差,不是 1.5 也不是 0.0015)。然后 lambda = 0.015 / (1 - 0.4) = 0.015 / 0.6 = 0.025(每年的 hazard,乘以 LGD 0.6 复现 0.015 年化利差)。最后 cum_pd = 1 - exp(-0.025 * 5) = 1 - exp(-0.125) = 1 - 0.882496... ≈ 0.11750。看板读数直觉:150 bp 的 5y CDS、按 40% 回收率假设,市场暗含未来五年的累计违约概率约为 11.75%

三个易错点

第一,bps 到十进制:1 bp = 1/10000,不是 1/100。把 bps 当作百分比会把 hazard 放大 100x,给一个普通信用名义算出接近 1.0 的累计违约概率(明显荒谬)。务必除以 10000.0

第二,信用三角的方向:正确是 lambda = s / (1 - R),分母是违约损失率(LGD)。常见错写有 lambda = s * (1 - R)(乘代替除)和 lambda = s / R(除以回收率而非 LGD)。三种公式的量纲一致,但只有一个对。记忆口诀:利差每年补偿期望损失,期望损失等于 PD * LGD = lambda * (1 - R),反解 lambda 就要除以 (1 - R)

第三,累计违约概率的合成形式:扁平 hazard lambda 在视野 T 上累计违约概率为 1 - exp(-lambda * T)(连续时间)。线性近似 lambda * T 仅当 lambda * T << 1 时准;扭曲名义(利差 5000 bp、R=0.4、T=5y)下 lambda * T = 4.17,线性给出 4.17——非法概率(>1),而正确指数形式给 1 - exp(-4.17) ≈ 0.9846。离散式 1 - (1 - lambda)**T 也错(它回答的是不同的问题——离散年度伯努利,不是连续 hazard),在 lambda * T = 0.83 时偏差 ~3%。请使用 math.exp1 - exp(-lambda*T)

哨兵

recovery_rate == 1.0,信用三角的分母为 0:任何 lambda 都给出零期望损失利差,spread 无法标定 hazard。返回 float('nan')(且必须检测,再做除法——让 Python 抛 ZeroDivisionError 违反合约)。检查顺序很重要:R == 1.0 哨兵优先于其他捷径。特别地,solution(0.0, 1.0, 5.0) 必须返回 NaN,不是 0.0——先按 spread_bps == 0 短路的作者会答错这条对抗用例。

spread_bps == 0.0(且 R != 1.0),则 lambda = 0cum_pd = 1 - exp(0) = 0.0。指数形式天然处理;除 R == 1.0 哨兵外无需特判。

horizon_years 极小(例如一天 1/365),累计 PD 大约等于 lambda * T1 - exp(-x) 在小 x 处的线性化),但仍按 1 - exp(-lambda*T) 计算以保持一致;若 horizon_years 极大(例如扭曲名义的 30 年),累计 PD 饱和到接近 1.0,指数形式自然处理。

stubs/stub.py 提供函数骨架。

实践背景

报价或对冲单名 CDS 的信贷交易台,从经纪人单与做市商屏幕上读取平价利差,需要一个快速读数:这一利差暗示了几个标准视野(1y、3y、5y、10y)的累计违约概率是多少?信用三角近似 lambda = s / (1 - R) 是每本固收信用教材都会教的"快速读数"公式;配上扁平 hazard 累计形式 1 - exp(-lambda*T),就给出该 desk 早间看板上的暗含 PD 期限结构那一列,紧挨着现券到期收益率反推 PD 与评级机构一年期 PD。从一组 CDS 期限(1y、3y、5y、7y、10y)做更精细的逐段扁平 hazard bootstrap 是后续问题;单期限的信用三角读数是正确的起点,也正是 solution(...) 计算的内容。

约束条件

  • 0.0 <= spread_bps <= 10000.0(基点;10000 bp = 100% 利差,扭曲名义区间)
  • 0.0 <= recovery_rate <= 1.0
  • 0.0 < horizon_years <= 30.0
  • 输出:float ∈ [0.0, 1.0],或 float('nan') 当且仅当 recovery_rate == 1.0;rel_tol=1e-9、abs_tol=1e-9;NaN 与 NaN 视为相等

样例

Case 1 · statement-example: 150bp spread, R=0.4, T=5y -> ~0.1175

输入: [150,0.4,5]

期望: 0.11750309741540454

s = 150/10000 = 0.015;lambda = 0.015/(1-0.4) = 0.025;cum_pd = 1 - exp(-0.025*5) = 1 - exp(-0.125) ≈ 0.11750。

Case 2 · typical: 100bp HY-style 5y benchmark

输入: [100,0.4,5]

期望: 0.07995558537067671

s=0.01;lambda=0.01/0.6≈0.01667;cum_pd = 1 - exp(-0.0833...) ≈ 0.07996。注意:写错把 100bp 当 1.0(相当于 100%)会得到接近 1 的结果。

Case 3 · typical: 200bp 5y, R=0.4

输入: [200,0.4,5]

期望: 0.15351827510938587

s=0.02;lambda=0.02/0.6≈0.03333;cum_pd = 1 - exp(-0.16667) ≈ 0.15352。

Case 4 · typical: 50bp IG name 5y, R=0.4

输入: [50,0.4,5]

期望: 0.040810542890861834

s=0.005;lambda≈0.00833;cum_pd ≈ 0.04081。投资级名义的典型量级。

Case 5 · boundary: spread_bps=0 -> cum_pd=0 exactly

输入: [0,0.4,5]

期望: 0

s=0 -> lambda=0 -> cum_pd = 1 - exp(0) = 0。

Case 6 · boundary: R=0.0 no-recovery -> lambda equals s

输入: [100,0,5]

期望: 0.048770575499285984

R=0 时 lambda = s/1 = s = 0.01;cum_pd = 1 - exp(-0.05) ≈ 0.04877。

Case 7 · large: 5000bp (50%) distressed 5y

输入: [5000,0.4,5]

期望: 0.9844961464009907

lambda=0.5/0.6≈0.833;lambda*T≈4.17;cum_pd ≈ 0.98450。注意:错用线性公式 lambda*T 会给出 4.17(>1,非法概率)。

Case 8 · adversarial: R=1.0 sentinel returns NaN (not ZeroDivisionError)

输入: [150,1,5]

期望: "NaN"

R=1 时 LGD=0,从 spread 反推不出 lambda(除以零);按合约返回 NaN,不能让 Python 抛 ZeroDivisionError。

最近提交

还没有提交记录。

编码区

实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。

加载编辑器...
计时0:00

默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。

Case 1 · statement-example: 150bp spread, R=0.4, T=5y -> ~0.1175

输入: [150,0.4,5]

期望: 0.11750309741540454

s = 150/10000 = 0.015;lambda = 0.015/(1-0.4) = 0.025;cum_pd = 1 - exp(-0.025*5) = 1 - exp(-0.125) ≈ 0.11750。

Case 2 · typical: 100bp HY-style 5y benchmark

输入: [100,0.4,5]

期望: 0.07995558537067671

s=0.01;lambda=0.01/0.6≈0.01667;cum_pd = 1 - exp(-0.0833...) ≈ 0.07996。注意:写错把 100bp 当 1.0(相当于 100%)会得到接近 1 的结果。

Case 3 · typical: 200bp 5y, R=0.4

输入: [200,0.4,5]

期望: 0.15351827510938587

s=0.02;lambda=0.02/0.6≈0.03333;cum_pd = 1 - exp(-0.16667) ≈ 0.15352。

Case 4 · typical: 50bp IG name 5y, R=0.4

输入: [50,0.4,5]

期望: 0.040810542890861834

s=0.005;lambda≈0.00833;cum_pd ≈ 0.04081。投资级名义的典型量级。

Case 5 · boundary: spread_bps=0 -> cum_pd=0 exactly

输入: [0,0.4,5]

期望: 0

s=0 -> lambda=0 -> cum_pd = 1 - exp(0) = 0。

Case 6 · boundary: R=0.0 no-recovery -> lambda equals s

输入: [100,0,5]

期望: 0.048770575499285984

R=0 时 lambda = s/1 = s = 0.01;cum_pd = 1 - exp(-0.05) ≈ 0.04877。

Case 7 · large: 5000bp (50%) distressed 5y

输入: [5000,0.4,5]

期望: 0.9844961464009907

lambda=0.5/0.6≈0.833;lambda*T≈4.17;cum_pd ≈ 0.98450。注意:错用线性公式 lambda*T 会给出 4.17(>1,非法概率)。

Case 8 · adversarial: R=1.0 sentinel returns NaN (not ZeroDivisionError)

输入: [150,1,5]

期望: "NaN"

R=1 时 LGD=0,从 spread 反推不出 lambda(除以零);按合约返回 NaN,不能让 Python 抛 ZeroDivisionError。