← 返回编程题库
coding-piecewise-flat-hazard-survival-at-horizons中等免费版2000ms未尝试

分段扁平 hazard 期限结构下多视野存活概率

Survival Probability at Multiple Horizons under a Piecewise-Flat Hazard Term Structure

开始编码

实现 solution(hazards: list[float], period_lengths: list[float], query_horizons: list[float]) -> list[float]。某信用风险口对一个债务人持有一组分段扁平的 hazard 期限结构——hazards[t] 是第 t 个 tenor 桶上的恒定 hazard 速率(单位 1/年),period_lengths[t] 是该桶的长度(单位年)。各桶从左到右无缝拼接:第 t 个桶覆盖 [endpoint[t-1], endpoint[t]),其中 endpoint[t] = sum_{k<=t} period_lengths[k]。对每个查询视野 h(来自 query_horizons),返回存活概率 Q(h) = exp(-integral_0^h lambda(s) ds)——债务人在 h 年前未违约的概率。

在分段扁平假设下,积分可拆为若干完整桶之和,加上 h 所在桶的部分期贡献。若 endpoint[i-1] < h <= endpoint[i],则

cumulative_hazard_to_h = sum_{k<i} (hazards[k] * period_lengths[k])
                       + hazards[i] * (h - endpoint[i-1])
Q(h)                   = exp(-cumulative_hazard_to_h)

第一个求和是上一个桶端点处的累计 hazard;第二项是 h 所在桶的部分期贡献。

solution([0.01, 0.02, 0.04], [1.0, 1.0, 1.0], [0.5, 1.0, 2.0, 3.0]) 返回 [0.9950124791926823, 0.9900498337491681, 0.9704455335485082, 0.9323938199059483]。三个桶分别为 0..1(hazard 0.01)、1..2(hazard 0.02)、2..3(hazard 0.04)。h = 0.5 时累计 hazard 为 0.01 * 0.5 = 0.005(桶 0 的部分期),Q = exp(-0.005) ≈ 0.99501h = 1 时刚走完桶 0,cum_haz = 0.01,Q = exp(-0.01) ≈ 0.99005h = 2 时 cum_haz = 0.01 + 0.02 = 0.03h = 3 时 cum_haz = 0.01 + 0.02 + 0.04 = 0.07Q = exp(-0.07) ≈ 0.93239

易错点

第一处是部分期贡献。查询 h 落在桶 i 内部(严格在 endpoint[i-1]endpoint[i] 之间)时,桶 i 的贡献为 hazards[i] * (h - endpoint[i-1])——只有 h 左侧那部分。作者若误用整桶 period_lengths[i],对任何不在桶端点的 h 都会高估 hazard。具体校验:hazards = [0.01, 0.50]period_lengths = [1.0, 1.0]h = 1.7,正确 cum_haz = 0.01 + 0.50 * 0.7 = 0.36(Q ≈ 0.6977);错误的整桶 cum_haz = 0.01 + 0.50 * 1.0 = 0.51(Q ≈ 0.6005)。单测大约差 0.1,桶 hazard 越大、部分期占比越小,错得越厉害。

第二处是保序。输出必须与 query_horizons 顺序完全一致——重复也按输入顺序输出。作者若内部对 query 排序(为单调指针扫描的便利)后忘了把排列还原回去,会得到值正确但槽位错误的输出。参考解直接按 query_horizons 顺序遍历、对每个 h 独立计算 Q(h)T <= 30K <= 30 之下无需任何排序优化。query_horizons 的重复按输入顺序逐一输出;不要去重。

第三处是 h = 0 边界。Q(0) = 1.0 恒成立——零时间流逝即必然存活,与 hazard 期限结构无关。在做任何部分期算术 之前 就对 h <= 0 短路。作者若依赖线性扫描跑到 i = 0、再算 hazards[0] * (0 - 0) = 0 会侥幸算对, T = 0 时(无桶可索引)hazards[0] 立即抛 IndexError

第四处是恰好落在桶端点。h = endpoint[i] 时,部分期项 hazards[i] * (h - endpoint[i-1]) 正好等于整桶 hazards[i] * period_lengths[i]——累计 hazard 恰是 cum_haz[i](桶 i 端点处的累计 hazard)。作者若特判"仅当 h 严格小于端点时才加部分期"、在端点处跳过这一项,会把最后一个桶整体漏掉。干净的规则:只要 endpoints[i-1] < h <= endpoints[i],就有 cum_haz_to_h = cum_haz[i-1] + hazards[i] * (h - endpoints[i-1]),等号情况落在右边。

第五处是存活概率形式。Q(h) = exp(-cum_haz_to_h)不是 1 - cum_haz_to_h、也 不是 prod_t (1 - hazards[t] * period_lengths[t])。当 lambda * dt 较大时离散线性近似会发散:单桶 hazards = 3.0period_lengths = 1.0h = 1.0,线性 1 - 3.0 = -2.0(非法——负的存活概率),正确 exp(-3.0) ≈ 0.0498。一律用 math.exp 与指数形式。

第六处是 T = 0 哨兵。无桶定义时,从 0 到任意正 h 的积分未定义(无 hazard 可积)。T = 0 时:h = 0 返回 1.0(空积分为零),h > 0 返回 NaN。用 float('nan')。作者若抛异常或返回 0.0 都违反契约;评测器开启 nan_equals_nan = trueNaN 与期望输出里的 NaN 精确匹配。

哨兵与边界

len(query_horizons) == 0 时立即返回 []len(hazards) == 0 时:任何 query_horizons[k] > 0 的项为 NaN;任何 query_horizons[k] == 0 的项为 1.0。否则算法为:先 O(T) 一遍预计算累计端点与累计 hazard 前缀;再对每个 query 用线性扫描或二分定位 h 所在桶,套累计-hazard-加部分期公式即可。T <= 30K <= 30 之下,每查询线性扫描即可。

实现细节由 stubs/stub.py 提供。

实践背景

信用风险建模中违约强度的期限结构常被参数化为分段扁平——每个 tenor 桶(典型的 CDS 标准网格 1y、3y、5y、7y、10y)一个常数 hazard lambda_t。任意视野 h 处的累计存活概率 Q(h)lambda(s) 的分段积分给出:完全落在 h 左侧的桶贡献整段,h 所在桶贡献部分期。该函数回答"债务人活到 h 年的概率是多少?"——是终生预期损失合成、CDS 定价、压力情景下资本预测的输入列。把部分期项写错(h 落在桶中间却用整桶长度)会高估 hazard、低估存活;同一个错误公式落到每个 obligor 上时,整个组合的偏差会同向叠加。

姊妹题

  • coding-cumulative-default-prob-from-marginal-hazard-rates——离散时间 边际 PD 期限结构;本题用 连续时间 分段扁平 hazard。
  • coding-cds-implied-flat-hazard-cumulative-pd——单 tenor par-CDS 利差到累计 PD。
  • coding-cds-curve-flat-hazard-survival-curve——CDS 曲线下逐 tenor 独立 扁平-hazard 存活(不做链式合成)。
  • coding-marginal-pd-from-cumulative-term-structure——反向(累计 → 边际)。

本题是分段连续 hazard 累计存活的姊妹题:给定分段扁平 hazard 期限结构与一组任意查询视野,返回每个视野上与期限结构一致的存活概率。

约束条件

  • 0 <= len(hazards) == len(period_lengths) <= 30
  • 0 <= len(query_horizons) <= 30
  • 0.0 <= hazards[t] <= 5.0(年化 hazard;5.0 ≈ 1y 内 99% 违约)
  • 0.001 <= period_lengths[t] <= 30.0(正数)
  • 0.0 <= query_horizons[k] <= sum(period_lengths)(在期限结构覆盖范围内)
  • 输出为长度 K 的 list[float];每项落在 [0, 1] 或为 NaN(仅当 T = 0 且视野为正时);按 rel_tol=1e-9、abs_tol=1e-9 比对;nan_equals_nan = true

样例

Case 1 · statement-example: 3-bucket flat hazard at front-end, mid, late horizons

输入: [[0.01,0.02,0.04],[1,1,1],[0.5,1,2,3]]

期望: [0.9950124791926823,0.9900498337491681,0.9704455335485082,0.9323938199059483]

cum_haz: 0.5y -> 0.005 (partial); 1y -> 0.01; 2y -> 0.03; 3y -> 0.07. Q = exp(-cum_haz).

Case 2 · visible: partial-period contribution mid-bucket (h=1.5 sits inside bucket 1)

输入: [[0.05,0.1],[1,1],[0.5,1,1.5,2]]

期望: [0.9753099120283326,0.951229424500714,0.9048374180359595,0.8607079764250578]

0.5y: 0.025 (partial of bucket 0); 1y: 0.05; 1.5y: 0.05+0.10*0.5=0.10; 2y: 0.15.

Case 3 · visible: empty query_horizons returns empty list

输入: [[0.05,0.1,0.15],[1,1,1],[]]

期望: []

K=0 -> [] (no horizons asked).

Case 4 · visible: h=0 returns 1.0 (no time elapsed = certain survival)

输入: [[0.5,1,2],[1,1,1],[0,0,0]]

期望: [1,1,1]

h=0 short-circuits to Q=1; duplicates preserve order.

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · statement-example: 3-bucket flat hazard at front-end, mid, late horizons

输入: [[0.01,0.02,0.04],[1,1,1],[0.5,1,2,3]]

期望: [0.9950124791926823,0.9900498337491681,0.9704455335485082,0.9323938199059483]

cum_haz: 0.5y -> 0.005 (partial); 1y -> 0.01; 2y -> 0.03; 3y -> 0.07. Q = exp(-cum_haz).

Case 2 · visible: partial-period contribution mid-bucket (h=1.5 sits inside bucket 1)

输入: [[0.05,0.1],[1,1],[0.5,1,1.5,2]]

期望: [0.9753099120283326,0.951229424500714,0.9048374180359595,0.8607079764250578]

0.5y: 0.025 (partial of bucket 0); 1y: 0.05; 1.5y: 0.05+0.10*0.5=0.10; 2y: 0.15.

Case 3 · visible: empty query_horizons returns empty list

输入: [[0.05,0.1,0.15],[1,1,1],[]]

期望: []

K=0 -> [] (no horizons asked).

Case 4 · visible: h=0 returns 1.0 (no time elapsed = certain survival)

输入: [[0.5,1,2],[1,1,1],[0,0,0]]

期望: [1,1,1]

h=0 short-circuits to Q=1; duplicates preserve order.