← 返回编程题库
coding-cds-par-spread-from-cumulative-pd中等免费版2000ms未尝试

由累计违约概率反推平价 CDS 利差:信用三角反向

Par CDS Spread Implied by a Cumulative Default Probability via the Inverse Credit Triangle

开始编码

实现 solution(cumulative_pd_at_tenor: float, tenor_years: float, recovery_rate: float) -> float。某信贷台运行信用三角看板的反向腿:从一条模型暗含的累计违约概率期限点(例如来自评级迁移模型、对方机构的债券收益率等),它想知道为该名义在对应期限上买保护,市场会收什么样的平价 CDS 利差。基于扁平 hazard 信用三角近似,返回该期限的平价 CDS 利差(基点)。

公式三步:把累计 PD 转为存活概率 Q = 1 - cumulative_pd_at_tenor;反推暗含扁平 hazard lambda = -ln(Q) / tenor_years(连续时间存活为 Q = exp(-lambda*T),反解得 lambda = -ln(Q)/T);用信用三角正向恒等式合成十进制平价利差 s = lambda * (1 - recovery_rate),然后转为基点 s_bps = s * 10000.0

示例

solution(0.05, 5.0, 0.4) 返回约 61.55195326506069。逐步:Q = 1 - 0.05 = 0.95。然后 lambda = -ln(0.95) / 5 ≈ 0.05129 / 5 ≈ 0.010259(每年的扁平 hazard 强度,在 5 年后通过 1 - exp(-0.010259 * 5) ≈ 0.05 复现 5% 累计违约率)。s = 0.010259 * (1 - 0.4) = 0.010259 * 0.6 ≈ 0.006155(约 0.6155% 的十进制平价利差),最后 s_bps = 0.006155 * 10000 ≈ 61.55 bps。看板读数直觉:在 40% 回收率假设下,定价隐含 5 年累计违约概率 5% 的名义,与 5 年期约 62 bp 的平价 CDS 利差一致。

四个易错点

第一反向方向。本题反转姊妹题的正向 cum_pd = 1 - exp(-lambda * T)——你从 cum_pd 出发,要算 s。把 cum_pd 直接塞进正向公式不能解题;正确路径是先解 lambdalambda = -ln(Q)/T),再解 ss = lambda * (1 - R))。混淆方向会得到完全错误的函数形式。

第二连续存活形式。信用三角约定用 Q = exp(-lambda*T),故 lambda = -ln(Q)/T。离散近似 lambda = (1 - Q)/T = cum_pd / T 是小 PD 线性化,不是合约。lambda*T 越大两者越散:cum_pd = 0.40, T = 5y 时正解 lambda = -ln(0.60)/5 ≈ 0.1022,而 (1-Q)/T = 0.08——低约 22%。请用 math.log 走连续形式。

第三信用三角方向(正向)。平价利差是 s = lambda * (1 - R)不是 lambda * R 也不是 lambda / (1 - R)。除法形式 lambda / (1 - R) 是反向(姊妹题 coding-cds-implied-flat-hazard-cumulative-pd 用它从 s 反推 lambda);在本题用上会差出 (1 - R)^2 的倍数。记忆口诀:利差补偿每年期望损失 EL = PD * LGD = lambda * (1 - R)

第四十进制到 bps 的转换。1 bp = 0.0001,故乘 10000,不是 100。乘 100 是把十进制当百分比,给出 100x 太小的答案。题面例子下,正解 61.55 bps 在错乘 100 时变成错误的 0.6155

哨兵

recovery_rate == 1.0,则 LGD = 0:没有损失需要补偿,平价利差为 0.0,与暗含 hazard 强度无关(lambda * (1 - R) 公式也给零,但显式分支文档化意图)。返回 0.0 并把该分支放在最前(先于其他分支)。

tenor_years == 0.0,反向信用三角未定义(瞬时违约概率没有对应的视野去摊成 hazard 强度)。返回 float('nan')。请在做除法之前检测——让 Python 抛 ZeroDivisionError 违反合约。

cumulative_pd_at_tenor <= 0.0,则 Q = 1lambda = 0,利差为 0.0。公式天然处理(math.log(1) = 0 除以 T 没问题),但显式短路更清晰。

cumulative_pd_at_tenor >= 1.0,则 Q = 0math.log(0) 在 Python 抛 ValueError。数学极限是 lambda -> +infinity,故 s_bps -> +infinity。返回 float('inf')。请在调用 math.log 之前检测。

哨兵顺序:recovery_rate == 1.0 -> tenor_years == 0.0 -> PD 边界。特别地,solution(0.05, 0.0, 1.0) 必须返回 0.0(R=1 优先),而 solution(0.0, 0.0, 0.4) 必须返回 NaN(T=0 优先于 PD=0)。先按 PD 边界短路的作者会答错后者。

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

实践背景

持有外部累计 PD 视角(来自评级迁移模型、Merton 类结构模型反推的股权暗含 PD、或债券收益率反推 PD 曲线)的信贷交易台,常需要把这类视角换算成等价平价 CDS 利差,与做市商屏幕上挂出的报价比较。信用三角反向 s = -ln(1 - cum_pd) / T * (1 - R) * 10000 提供一行换算。它是更熟悉的正向(姊妹题 coding-cds-implied-flat-hazard-cumulative-pd,从 spread 到 PD)的自然反向;正反两题合在一起,给 desk 在 PD 与 spread 两种"市场方言"之间的双向映射。从一组 CDS 期限做更精细的逐段扁平 hazard bootstrap 是后续问题;单期限的反向读数是正确的起点,也正是 solution(...) 计算的内容。

约束条件

  • 0.0 <= cumulative_pd_at_tenor <= 1.0
  • 0.0 <= tenor_years <= 30.0(T=0 时返回 NaN 哨兵)
  • 0.0 <= recovery_rate <= 1.0
  • 输出:float — 非负 bps 利差,或 0.0(recovery=1 或 PD=0),或 float('inf')(PD=1),或 float('nan')(tenor=0);rel_tol=1e-9、abs_tol=1e-9;NaN 与 NaN 视为相等

样例

Case 1 · statement-example: cum_pd=0.05, T=5y, R=0.4 -> ~61.55 bps

输入: [0.05,5,0.4]

期望: 61.55195326506069

Q=0.95;lambda = -ln(0.95)/5 ≈ 0.01026;s = 0.01026*0.6 ≈ 0.006155;s_bps ≈ 61.55。

Case 2 · typical: cum_pd=0.10 5y R=0.4 (HY benchmark)

输入: [0.1,5,0.4]

期望: 126.43261878939154

Q=0.90;lambda ≈ 0.02107;s_bps ≈ 126.43。

Case 3 · typical: cum_pd=0.02 5y R=0.4 (IG-style)

输入: [0.02,5,0.4]

期望: 24.243248781023357

Q=0.98;lambda = -ln(0.98)/5 ≈ 0.00404;s_bps ≈ 24.24。

Case 4 · boundary: cum_pd=0 -> spread=0

输入: [0,5,0.4]

期望: 0

PD=0 -> Q=1 -> lambda=0 -> s_bps=0。

Case 5 · boundary: R=0 no-recovery -> spread=lambda*10000

输入: [0.05,5,0]

期望: 102.58658877510115

R=0 时 LGD=1,spread = lambda(十进制)-> s_bps = -ln(0.95)/5 * 10000 ≈ 102.59。

Case 6 · large: cum_pd=0.50 1y R=0.4

输入: [0.5,1,0.4]

期望: 4158.883083359672

lambda = -ln(0.5) ≈ 0.6931;s = 0.6931*0.6 ≈ 0.4159;s_bps ≈ 4158.88。

Case 7 · adversarial: R=1.0 sentinel returns 0.0 (no LGD -> no spread)

输入: [0.05,5,1]

期望: 0

R=1 -> LGD=0 -> 任意累计 PD 下平价利差皆为 0。返回 0.0(不是 NaN,也不是 inf)。

Case 8 · adversarial: T=0.0 sentinel returns NaN (undefined)

输入: [0.05,0,0.4]

期望: "NaN"

T=0 -> 除以零未定义;返回 float('nan'),不要触发 ZeroDivisionError。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · statement-example: cum_pd=0.05, T=5y, R=0.4 -> ~61.55 bps

输入: [0.05,5,0.4]

期望: 61.55195326506069

Q=0.95;lambda = -ln(0.95)/5 ≈ 0.01026;s = 0.01026*0.6 ≈ 0.006155;s_bps ≈ 61.55。

Case 2 · typical: cum_pd=0.10 5y R=0.4 (HY benchmark)

输入: [0.1,5,0.4]

期望: 126.43261878939154

Q=0.90;lambda ≈ 0.02107;s_bps ≈ 126.43。

Case 3 · typical: cum_pd=0.02 5y R=0.4 (IG-style)

输入: [0.02,5,0.4]

期望: 24.243248781023357

Q=0.98;lambda = -ln(0.98)/5 ≈ 0.00404;s_bps ≈ 24.24。

Case 4 · boundary: cum_pd=0 -> spread=0

输入: [0,5,0.4]

期望: 0

PD=0 -> Q=1 -> lambda=0 -> s_bps=0。

Case 5 · boundary: R=0 no-recovery -> spread=lambda*10000

输入: [0.05,5,0]

期望: 102.58658877510115

R=0 时 LGD=1,spread = lambda(十进制)-> s_bps = -ln(0.95)/5 * 10000 ≈ 102.59。

Case 6 · large: cum_pd=0.50 1y R=0.4

输入: [0.5,1,0.4]

期望: 4158.883083359672

lambda = -ln(0.5) ≈ 0.6931;s = 0.6931*0.6 ≈ 0.4159;s_bps ≈ 4158.88。

Case 7 · adversarial: R=1.0 sentinel returns 0.0 (no LGD -> no spread)

输入: [0.05,5,1]

期望: 0

R=1 -> LGD=0 -> 任意累计 PD 下平价利差皆为 0。返回 0.0(不是 NaN,也不是 inf)。

Case 8 · adversarial: T=0.0 sentinel returns NaN (undefined)

输入: [0.05,0,0.4]

期望: "NaN"

T=0 -> 除以零未定义;返回 float('nan'),不要触发 ZeroDivisionError。