由 CDS 利差期限结构构造逐期限扁平 hazard 存活曲线
Per-Tenor Flat-Hazard Survival Curve from a CDS Spread Term Structure
开始编码实现 solution(tenors: list[float], spreads_bps: list[float], recovery_rate: float) -> list[float]。某信贷交易台从经纪人单读取按期限报出的 CDS 平价利差(典型为 1y/3y/5y/7y/10y 标准格点,外加任何定制期限),希望快速看一眼暗含的存活曲线。对每个市场观察到的期限,按信用三角扁平 hazard 假设反推该期限处的存活概率——最朴素的读数:每个期限的存活率仅由该期限自己的利差决定(不做 bootstrap、不做链式计算)。所得曲线 Q(t) 喂给下游工具如 CDS 定价、CVA 聚合。
公式按期限独立:bps 转十进制 s = spreads_bps[i] / 10000.0;信用三角反推 hazard lambda = s / (1.0 - recovery_rate)(CDS 平价利差每年补偿期望损失 lambda * (1 - R),反解得 lambda = s / (1 - R));视野 tenors[i] 上的存活概率 Q[i] = exp(-lambda * tenors[i])(连续时间扁平 hazard 存活率)。
示例
solution([1.0, 3.0, 5.0, 7.0, 10.0], [150.0, 150.0, 150.0, 150.0, 150.0], 0.4) 返回 [0.9753099120283326, 0.9277434863285529, 0.8824969025845955, 0.8394570207692074, 0.7788007830714049]。利差曲线平在 150 bp,每个期限用同样的 lambda = 0.015 / (1 - 0.4) = 0.025;存活率为 exp(-0.025 * t) 在每个 t 处的值。t=5 时 Q = exp(-0.125) = 0.882496...——与单期限姊妹题在 150 bp、5y、R=0.4 给出的数字一致。t=10 时存活率衰减到 exp(-0.25) ≈ 0.7788。
五个易错点
第一,bps 到十进制:1 bp = 1/10000,不是 1/100。把 bps 当作百分比会把每个暗含 hazard 放大 100x,给一个普通信用名义算出几乎为 0 的存活率。务必除以 10000.0,与 coding-cds-implied-flat-hazard-cumulative-pd 约定一致。
第二,信用三角的方向:正确是 lambda = s / (1 - R),分母是违约损失率(LGD)。常见错写有 lambda = s * (1 - R)(乘代替除)和 lambda = s / R(除以回收率而非 LGD)。三种公式的量纲一致,但只有一个对。记忆口诀:利差每年补偿期望损失,期望损失等于 PD * LGD = lambda * (1 - R),反解 lambda 就要除以 (1 - R)。
第三,存活率的形式:扁平 hazard lambda 在视野 t 上的存活率为 exp(-lambda * t)(连续时间)。线性近似 1 - lambda * t 仅当 lambda * t << 1 时准;扭曲名义(5000 bp、R=0.4、t=5y)下 lambda * t = 4.17,线性给 -3.17(非法——负存活率),而正确指数给 exp(-4.17) ≈ 0.0155。请使用 math.exp 与 Q = exp(-lambda*t)。
第四,逐期限独立性:每个 Q[i] 仅由 spreads_bps[i] 决定,假设从 t=0 到 tenors[i] 全程使用这一 hazard。这是最朴素的扁平 hazard 解释,与逐段常 hazard 的 bootstrap 不同。bootstrap 会把每个相邻利差当作前后两期限之间那段的 hazard,存活率作乘积链式计算——那是另一个问题,给出不同曲线。本题合约要求不链式的逐期限读数:单期限姊妹题的向量化扩展。
第五,recovery_rate == 1.0 哨兵:LGD=0 时分母为 0,任何 lambda 都给出零期望损失利差;现实中 R=1 的 CDS 平衡时 spread = 0。返回 [1.0] * len(tenors)(无 hazard,每个期限存活率均为 1.0)。在除法之前检测 recovery_rate == 1.0,否则 s / (1 - 1.0) 会抛 ZeroDivisionError,违反合约。注意顺序保留:不要对 tenors 排序;输出位置必须对应输入。tenors[i] == 0 与 spreads_bps[i] == 0 都是公式的自然情形(exp(-lambda*0) = 1 与 exp(-0*t) = 1 自动处理)——除 R == 1 哨兵和 T = 0 空列表短路外无需额外特判。
哨兵与边界
若 len(tenors) == 0 直接返回 []。若 recovery_rate == 1.0 直接返回 [1.0] * len(tenors)。tenors[i] == 0 与 spreads_bps[i] == 0 都由公式自然给出 Q[i] = 1.0,无需特判,但要注意:进入函数后先一次性检查 R == 1(不要先按 spreads_bps[i] == 0 短路)。合约要求输出顺序与输入 tenors(与 spreads_bps)一致;不要排序。
stubs/stub.py 提供函数骨架。
实践背景
报价或对冲单名 CDS 与指数 CDS 的信贷交易台,从经纪人单与做市商屏幕读取标准期限(1y、3y、5y、7y、10y)外加任何定制期限上的平价利差。对这些利差最朴素的读数是:每个期限独立处理,把该期限上的利差当作 t=0 到该期限的扁平 hazard,反推出该期限处的存活概率。所得曲线 Q(t) 是 desk 早间看板上的"快速合理性检查"那一列,紧挨着单期限姊妹题给出的累计违约概率列以及更精细的 bootstrap 逐段扁平 hazard 曲线。本题计算的是不链式的逐期限读数。
姊妹题
coding-cds-implied-flat-hazard-cumulative-pd——单期限累计违约概率(本题返回多期限上的存活率Q)。约定一致;本题返回列表,并把顺序保留与逐期限独立性也作为易错点。coding-cds-par-spread-from-cumulative-pd——逆向(PD -> spread)。coding-cumulative-default-prob-from-marginal-hazard-rates——离散时间边际 PD 期限结构。
本题是单期限累计违约概率题的多期限存活曲线姊妹版。
约束条件
- 0 <= len(tenors) == len(spreads_bps) <= 30
- 0.0 <= tenors[i] <= 30.0(允许 0 用于哨兵测试)
- 0.0 <= spreads_bps[i] <= 10000.0(10000 bp = 100% 利差,扭曲名义区间)
- 0.0 <= recovery_rate <= 1.0
- 输出:list[float],长度 = len(tenors);每项 ∈ [0.0, 1.0];rel_tol=1e-9、abs_tol=1e-9
样例
Case 1 · typical: 5y curve at uniform 150bp R=0.4
输入: [[1,3,5,7,10],[150,150,150,150,150],0.4]
期望: [0.9753099120283326,0.9277434863285529,0.8824969025845955,0.8394570207692074,0.7788007830714049]
每个 tenor 独立用 lambda = 0.015/0.6 = 0.025,Q[i] = exp(-0.025*tenor[i])。10y 处 exp(-0.25) ≈ 0.7788。
Case 2 · typical: upward-sloping IG curve R=0.4
输入: [[1,3,5,7,10],[50,80,100,120,150],0.4]
期望: [0.991701292638876,0.9607894391523232,0.9200444146293233,0.8693582353988059,0.7788007830714049]
spreads 不同 tenor 不同,需独立计算每个 Q。例如 5y 处 lambda=0.01/0.6≈0.01667,Q≈exp(-0.0833)≈0.9200。
Case 3 · typical: 200bp flat-hazard 5-tenor read R=0.4
输入: [[1,2,3,5,10],[200,200,200,200,200],0.4]
期望: [0.9672161004820059,0.9355069850316178,0.9048374180359595,0.8464817248906141,0.7165313105737893]
lambda=0.02/0.6≈0.0333;Q(t)=exp(-0.0333*t)。5y 处 ≈0.8465,10y 处 ≈0.7165。
Case 4 · boundary: empty input -> empty output
输入: [[],[],0.4]
期望: []
T=0:直接返回空列表,不调用 exp。
Case 5 · boundary: single tenor T=1
输入: [[5],[150],0.4]
期望: [0.8824969025845955]
T=1 退化为单期限信用三角;lambda=0.025,Q=exp(-0.125)≈0.8825。
Case 6 · boundary: all spreads zero -> all Q = 1.0
输入: [[1,5,10],[0,0,0],0.4]
期望: [1,1,1]
spread=0 -> lambda=0 -> Q=exp(0)=1。指数形式天然处理,无需特判。
Case 7 · adversarial: R=1.0 sentinel returns all-ones
输入: [[1,5,10],[150,150,150],1]
期望: [1,1,1]
R=1 -> LGD=0,公式分母为 0;按合约返回 [1.0]*T。直接做除法会抛 ZeroDivisionError。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · typical: 5y curve at uniform 150bp R=0.4
输入: [[1,3,5,7,10],[150,150,150,150,150],0.4]
期望: [0.9753099120283326,0.9277434863285529,0.8824969025845955,0.8394570207692074,0.7788007830714049]
每个 tenor 独立用 lambda = 0.015/0.6 = 0.025,Q[i] = exp(-0.025*tenor[i])。10y 处 exp(-0.25) ≈ 0.7788。
Case 2 · typical: upward-sloping IG curve R=0.4
输入: [[1,3,5,7,10],[50,80,100,120,150],0.4]
期望: [0.991701292638876,0.9607894391523232,0.9200444146293233,0.8693582353988059,0.7788007830714049]
spreads 不同 tenor 不同,需独立计算每个 Q。例如 5y 处 lambda=0.01/0.6≈0.01667,Q≈exp(-0.0833)≈0.9200。
Case 3 · typical: 200bp flat-hazard 5-tenor read R=0.4
输入: [[1,2,3,5,10],[200,200,200,200,200],0.4]
期望: [0.9672161004820059,0.9355069850316178,0.9048374180359595,0.8464817248906141,0.7165313105737893]
lambda=0.02/0.6≈0.0333;Q(t)=exp(-0.0333*t)。5y 处 ≈0.8465,10y 处 ≈0.7165。
Case 4 · boundary: empty input -> empty output
输入: [[],[],0.4]
期望: []
T=0:直接返回空列表,不调用 exp。
Case 5 · boundary: single tenor T=1
输入: [[5],[150],0.4]
期望: [0.8824969025845955]
T=1 退化为单期限信用三角;lambda=0.025,Q=exp(-0.125)≈0.8825。
Case 6 · boundary: all spreads zero -> all Q = 1.0
输入: [[1,5,10],[0,0,0],0.4]
期望: [1,1,1]
spread=0 -> lambda=0 -> Q=exp(0)=1。指数形式天然处理,无需特判。
Case 7 · adversarial: R=1.0 sentinel returns all-ones
输入: [[1,5,10],[150,150,150],1]
期望: [1,1,1]
R=1 -> LGD=0,公式分母为 0;按合约返回 [1.0]*T。直接做除法会抛 ZeroDivisionError。