← 返回编程题库
coding-survival-weighted-ee-profile-for-cva中等免费版2000ms未尝试

用于 CVA 聚合的存活加权期望敞口剖面

Survival-Weighted Expected-Exposure Profile for CVA Aggregation

开始编码

实现 solution(ee_profile: list[float], marginal_pd: list[float]) -> list[float]。某衍生品交易对手的 CVA 聚合器需要按"交易对手在该期开始时还活着"为条件的 per-period 敞口——只有当交易对手进入t 期时还活着,银行才会在第 t 期蒙受违约损失。给定 per-period 无条件期望敞口(EE)剖面和 per-period 边际违约概率期限结构(条件于存活的 hazard),返回喂给 CVA 求和的存活加权 EE 剖面。

形式上,定义运行存活概率

Q[-1] := 1.0                              (满初始存活)
Q[t]  := Q[t-1] * (1 - marginal_pd[t])    for t = 0, 1, ..., T-1

以及存活加权 EE

weighted_ee[t] := ee_profile[t] * Q[t-1]   for t = 0, 1, ..., T-1

注意:weighted_ee[t] 用的是 Q[t-1](到第 t-1 期末/第 t 期开始时的累计存活),不是 Q[t]。返回长度 T 的列表 weighted_ee

这个剖面是从原始 EE/PD 曲线走到一份合理存活加权的 CVA 美元费用过程的"后半段"计算。一旦拿到 weighted_ee,标准 CVA 聚合即为

CVA = LGD * sum_t (weighted_ee[t] * marginal_pd[t] * DF[t])

(姊妹题 coding-unilateral-cva-independence 在已加权 EE 剖面与无条件边际 PD 曲线下完成美元 CVA 聚合;本题完成存活加权这一步)。

算例

考虑 ee_profile = [1_000_000, 900_000, 800_000, 700_000, 600_000]marginal_pd = [0.02, 0.018, 0.016, 0.014, 0.012]。逐期维护 Q

  • t=0Q = 1.0weighted_ee[0] = 1,000,000 * 1.0 = 1,000,000。更新 Q = 1.0 * (1 - 0.02) = 0.98
  • t=1weighted_ee[1] = 900,000 * 0.98 = 882,000。更新 Q = 0.98 * (1 - 0.018) = 0.96236
  • t=2weighted_ee[2] = 800,000 * 0.96236 = 769,888。更新 Q = 0.96236 * (1 - 0.016) = 0.94696...
  • t=3weighted_ee[3] = 700,000 * 0.94696... ≈ 662,873.57。更新 Q ≈ 0.93371...
  • t=4weighted_ee[4] = 600,000 * 0.93371... ≈ 560,222.86

所以 solution(...) 返回 [1_000_000, 882_000, 769_888, 662_873.568, 560_222.861...]

五个易错点

第一个易错点是循环内 **Q[t-1]Q[t] 的顺序**。weighted_ee[t] 用的是第 t起始时刻的存活概率—— Q[t-1],到 t-1 期末为止的累计存活——不是t 期默认更新后的 Q[t]。具体地,循环必须 (1) 先用当前 Q 计算 weighted_ee[t] = ee_profile[t] * Q然后 (2) 才更新 Q *= (1 - marginal_pd[t])。把顺序反过来(先更新 Q 再加权)会让每一期都被多乘一个 (1 - mp[t]) 因子。在算例中,错序写法会得到 weighted_ee[0] = 1,000,000 * 0.98 = 980,000 而不是 1,000,000——第一期就错,且偏差逐期累计放大。

第二个易错点是 **Q[-1] = 1 的初值**。在 t=0 时存活权重是 Q[-1] = 1.0(满初始存活——按假设交易对手在交易开始时还活着),因此 weighted_ee[0] = ee_profile[0] * 1.0 = ee_profile[0]。把 Q = 1 - marginal_pd[0](提前一步)作初值会让第 0 期被低估 (1 - mp[0]) 倍。干净的实现以 Q = 1.0 作为初值,先计算再更新——初值陷阱自动消失。

第三个易错点是乘性而非加性的存活演化。条件存活递推式是 Q[t] = Q[t-1] * (1 - marginal_pd[t])不是 Q[t] = Q[t-1] - marginal_pd[t]。把每期存活率 (1 - mp[t]) Q 才能让 Q 解释为 prod_{s<=t} (1 - mp[s]) = P(过完第 t 期后还活着)。加性减法在累计 mp 超过 1 时(per-period bound mp[t] <= 1 是允许的)会让 Q 变负,即便没变负也不对应任何概率意义上的存活路径。以 mp = [0.3, 0.3, 0.3, 0.3] 为例,正确 Q 序列是 [0.7, 0.49, 0.343, 0.2401];而加性减法会得到 [0.7, 0.4, 0.1, -0.2](出现负值!)——只有乘性形式正确。

第四个易错点是 **marginal_pd[t] = 1 是吸收边界**。若任意 mp[t] = 1.0,运行 Q 将在更新步骤坍塌为 0,且所有 k >= 1weighted_ee[t+k] 都为 0。但 weighted_ee[t] 本身是在更新之前计算的,仍使用未坍塌的 Q[t-1](通常非 0)。参考实现通过乘性更新 Q * (1 - 1.0) = 0 自然处理——无需特例。但要小心:在计算当期贡献之前提前 if mp[t] == 1.0: break,会错误地把当期也置零。

第五点是 CVA 聚合恒等式——一个有用的自检。sum_t (weighted_ee[t] * marginal_pd[t]) 等于期限 T 内的期望违约时刻敞口(即 LGD=1、所有 DF=1 时的 CVA)。等价地,姊妹题 coding-unilateral-cva-independenceLGD=1DF[t]=1 时给出的 CVA 与该和相等。规范作者可以用这个恒等式独立交叉验证存活加权剖面。

边界

  • T == 0:空输出 [](无任何期)。
  • 所有 marginal_pd[t] == 0.0Q 始终为 1.0weighted_ee == ee_profile 逐元素相等(视野内零违约风险的交易对手)。
  • marginal_pd = [1.0, *, *, ...]weighted_ee[0] = ee_profile[0]weighted_ee[t] = 0 for t >= 1(交易对手在第 0 期违约——Q 自第 1 期起坍塌为 0)。
  • 所有 ee_profile[t] == 0.0:无论 marginal_pd 取值,weighted_ee 全为 0(无敞口可加权)。
  • T == 1weighted_ee = [ee_profile[0]](用 Q[-1] = 1marginal_pd[0] 不影响唯一输出值——只对超出区间的期才会起作用)。

本函数不抛异常——调用方保证两个列表长度一致、所有输入在声明范围内。

期望算法:单遍 O(T)——维护单个运行存活概率 Q(初值 1.0);在每一期 t计算 weighted_ee[t] = ee_profile[t] * Q然后更新 Q *= (1 - marginal_pd[t])。这个顺序就是易错点 #1 的全部内容;反过来每一期都会少加权。T <= 60 足够小,浮点累加顺序在 1e-9 容差下无关紧要。

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

实践背景

衍生品交易台的 CVA 系统把每个交易对手交易的信用费用算成"交易对手违约时未来预期损失的现值"。标准流程分三步:(1) 模拟或计算 per-period 无条件期望敞口剖面 ee_profile[t](不发生违约时的期望正向重置价值);(2) 做本题 solution(...) 完成的存活加权——把每期敞口按"在该期开始时还活着"为条件加权;(3) 聚合 LGD * sum_t (weighted_ee[t] * marginal_pd[t] * DF[t]) 得到美元 CVA。第 (2) 步在数学上是必需的,因为第 t 期违约只会在交易对手进入该期时还活着的情形下造成损失——银行不会再损失给已不存在的交易对手。跳过存活加权(即把原始 ee_profile 直接代入 CVA 求和)会高估 CVA,期限越长、违约率越高,偏差越大。本题做存活加权剖面构造;姊妹题 coding-unilateral-cva-independence 做美元 CVA 聚合;coding-cumulative-default-prob-from-marginal-hazard-rates 产出累计 PD 期限结构(相关但不同的量);coding-marginal-pd-from-cumulative-term-structure 是反向的期限结构分解;coding-portfolio-expected-loss-by-bucket 是单期 EL(无时间剖面、无存活加权)。存活加权 EE 剖面也是 IFRS 9 衍生品头寸 stage-2/3 终身 ECL 计算的一个构件,亦是融资成本 (FVA)、资本 (KVA) 等同类(hazard-conditioned 现金流结构)调整的基础。

约束条件

  • 0 <= T == len(ee_profile) == len(marginal_pd) <= 60
  • 0.0 <= ee_profile[t] <= 1e9(per-period 无条件期望敞口、美元、非负)
  • 0.0 <= marginal_pd[t] <= 1.0(per-period **边际**违约概率——给定存活到 0..t-1 期之后、第 t 期违约的概率)
  • 输出为长度 T 的 `list[float]`,非负,按 rel_tol=1e-9、abs_tol=1e-9 比对
  • 函数名为 `solution`,复杂度 O(T) 时间、O(T) 空间

样例

Case 1 · statement-example: T=5 swap-like profile

输入: [[1000000,900000,800000,700000,600000],[0.02,0.018,0.016,0.014,0.012]]

期望: [1000000,882000,769888,662873.568,560222.8611839999]

T=5:Q[-1]=1,weighted_ee[0]=1e6;Q[0]=0.98,weighted_ee[1]=900000*0.98=882000;以此类推。

Case 2 · typical: T=1 single period, weighted_ee=[ee[0]]

输入: [[1000000],[0.05]]

期望: [1000000]

T=1:Q[-1]=1,weighted_ee[0]=ee[0]*1=1e6(输出与输入 EE 同样、与 PD 无关)。

Case 3 · boundary: T=0 empty deal returns []

输入: [[],[]]

期望: []

T=0:空合约、空输出 []。

Case 4 · boundary: all marginal_pd=0 (riskless) returns ee_profile unchanged

输入: [[100,200,300],[0,0,0]]

期望: [100,200,300]

所有 mp=0:Q 始终为 1,weighted_ee==ee_profile。

Case 5 · boundary: marginal_pd[0]=1 absorbs (Q drops to 0 from t=1)

输入: [[100,200,300],[1,0.5,0.5]]

期望: [100,0,0]

mp[0]=1:weighted_ee[0]=ee[0]*Q[-1]=100*1=100;之后 Q=0,weighted_ee[t]=0 for t>=1。

Case 6 · adversarial: marginal_pd[1]=1 absorbs mid-stream (Q drops at t=1->t=2)

输入: [[100,200,300,400],[0.1,1,0.5,0.5]]

期望: [100,180,0,0]

mp[1]=1:t=0,1 正常计算(weighted_ee[0]=100、weighted_ee[1]=200*0.9=180),update 后 Q=0,t>=2 weighted_ee=0。

Case 7 · boundary: all ee_profile=0 returns all zeros

输入: [[0,0,0],[0.1,0.2,0.3]]

期望: [0,0,0]

所有 ee=0:weighted_ee 全 0、与 PD 无关。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · statement-example: T=5 swap-like profile

输入: [[1000000,900000,800000,700000,600000],[0.02,0.018,0.016,0.014,0.012]]

期望: [1000000,882000,769888,662873.568,560222.8611839999]

T=5:Q[-1]=1,weighted_ee[0]=1e6;Q[0]=0.98,weighted_ee[1]=900000*0.98=882000;以此类推。

Case 2 · typical: T=1 single period, weighted_ee=[ee[0]]

输入: [[1000000],[0.05]]

期望: [1000000]

T=1:Q[-1]=1,weighted_ee[0]=ee[0]*1=1e6(输出与输入 EE 同样、与 PD 无关)。

Case 3 · boundary: T=0 empty deal returns []

输入: [[],[]]

期望: []

T=0:空合约、空输出 []。

Case 4 · boundary: all marginal_pd=0 (riskless) returns ee_profile unchanged

输入: [[100,200,300],[0,0,0]]

期望: [100,200,300]

所有 mp=0:Q 始终为 1,weighted_ee==ee_profile。

Case 5 · boundary: marginal_pd[0]=1 absorbs (Q drops to 0 from t=1)

输入: [[100,200,300],[1,0.5,0.5]]

期望: [100,0,0]

mp[0]=1:weighted_ee[0]=ee[0]*Q[-1]=100*1=100;之后 Q=0,weighted_ee[t]=0 for t>=1。

Case 6 · adversarial: marginal_pd[1]=1 absorbs mid-stream (Q drops at t=1->t=2)

输入: [[100,200,300,400],[0.1,1,0.5,0.5]]

期望: [100,180,0,0]

mp[1]=1:t=0,1 正常计算(weighted_ee[0]=100、weighted_ee[1]=200*0.9=180),update 后 Q=0,t>=2 weighted_ee=0。

Case 7 · boundary: all ee_profile=0 returns all zeros

输入: [[0,0,0],[0.1,0.2,0.3]]

期望: [0,0,0]

所有 ee=0:weighted_ee 全 0、与 PD 无关。