← 返回编程题库
coding-unilateral-cva-independence中等免费版2000ms未尝试

独立性假设下的单边 CVA:折现时间剖面求和

Unilateral CVA Under PD-EE Independence: Discounted Time-Profile Sum

开始编码

实现 solution(expected_exposure_profile: list[float], marginal_pd_per_period: list[float], lgd: float, discount_factors: list[float]) -> float。某衍生品交易台对每个交易对手每日计算信用估值调整(CVA):交易对手违约时未来预期信用损失的现值。在 PD 与敞口独立(无错向风险)的假设下,简单单边 CVA 公式为

total_cva = sum_{t=0..T-1} LGD * marginal_pd_per_period[t] * expected_exposure_profile[t] * discount_factors[t]
         = LGD * sum_{t=0..T-1} marginal_pd_per_period[t] * expected_exposure_profile[t] * discount_factors[t]

给定四组平行输入:expected_exposure_profile 是长度为 T 的 per-period 美元 Expected Exposure 列表(如果不发生违约则每期的预期正向重置价值),marginal_pd_per_period 是长度为 T无条件边际违约概率列表(违约t 期发生的概率——等价于 survival(t-1) - survival(t),所以存活到 t-1 的累计权重已经内嵌进去),lgd 是违约损失率(跨期常数),discount_factors 是长度为 T 的 per-period 折现因子列表。返回总单边 CVA 的美元数值(float)。

因为 PD 已经是无条件边际(不是裸条件 hazard),CVA 循环直接对四因子 LGD * pd_t * EE_t * DF_t 相乘、循环内不再乘任何存活因子。如果你的输入恰好是条件 hazard 期限结构 h_t = P(default in t | alive at t-1),则求和前需要乘上存活到 t-1 的累计因子——但本题接收的是无条件边际

算例

考虑一笔 3 期合约:expected_exposure_profile = [1_000_000, 900_000, 800_000]marginal_pd_per_period = [0.02, 0.018, 0.016]lgd = 0.6discount_factors = [exp(-0.05*1), exp(-0.05*2), exp(-0.05*3)] ≈ [0.95123, 0.90484, 0.86071](连续复利、固定 5% 利率)。逐期:

  • t=0:贡献 = 0.02 * 1,000,000 * 0.95123 ≈ 19,024.59
  • t=1:贡献 = 0.018 * 900,000 * 0.90484 ≈ 14,658.37
  • t=2:贡献 = 0.016 * 800,000 * 0.86071 ≈ 11,017.06

三项相加 ≈ 44,700.02,乘以 LGD = 0.6≈ 26,820.01。所以 solution(...) 返回 ≈ 26820.01

五个易错点

第一个易错点是单期四因子乘积。每期 CVA 贡献是 LGD * PD_t * EE_t * DF_t —— 个因子的乘积。漏掉任意一个(漏 DF、漏 EE、把 LGD 当作求和后单独缩放进而误写成 LGD ** t 等)都会出错。最简单的正确一行式 lgd * sum(pd[t] * ee[t] * df[t] for t in range(T));LGD 是常数可提到求和外,但绝不能取幂、也绝不能漏掉。

第二个易错点是求和、不是 max。总 CVA 是各期求和、不是 MAX 或别的聚合器。每期都贡献自己的预期损失,总 CVA 是合约存续期内预期信用损失的累计现值。算例中,取最大单期贡献再乘 LGD 给出 ~11,414,与正确的 ~26,820 差了一倍多。直觉:违约可能发生在 T 期中的任意一期,每期的期望损失叠加。

第三个易错点是无条件边际 PD per-period——不是累计、也不是裸条件 hazard。输入 PD 是无条件边际违约概率(违约t 期发生的概率,存活到 t-1 的累计因子已内嵌),不是累计 PD、也不是裸条件 hazard。CVA 公式直接把它们代入四因子乘积、不再乘任何存活因子。把输入按累计 PD 处理会重复计入违约事件;按裸条件 hazard h_t = P(default in t | alive at t-1) 处理而漏乘 survival(t-1) 则把后段时期权重抬高。算例中,把 [0.02, 0.018, 0.016] 替换为对应的累计期限结构会得到 ~51,715 而不是正确的 ~26,820。姊妹题 coding-marginal-pd-from-cumulative-term-structure 负责由累计反算无条件边际;本题直接给定无条件边际。

第四个易错点是逐期折现因子。每期 CVA 必须通过 discount_factors[t] 折回今天。CVA 是现值,所以每期的美元期望损失必须折回 0 时刻。漏掉折现得到的是未折现 CVA —— 比真实现值更大。算例中,去掉 DF 得到 ~29,400 而正确值为 ~26,820 —— 虚高约 10%,期限越长缺口越大。

第五点是独立性假设——它是简单公式得以成立的前提。分解 E[loss_t] = LGD * E[1{第 t 期违约} * EE_t] = LGD * PD_t * EE_t 要求 PD 和 EE 独立——即无错向风险。如果两者相关(例如某 FX 互换的敞口恰在交易对手本币信用恶化时上升,或某信用衍生品参考标的与交易对手一同违约),需要加错向调整因子。本题不建模错向风险,独立性假设正是 per-period 乘积形式的依据。

边界

  • T == 0 返回 0.0(空合约——没有期可求和)。
  • lgd == 0.0 返回 0.0(全额回收——无信用损失,与 PD/EE 无关)。
  • 所有 marginal_pd_per_period[t] == 0.0 返回 0.0(视野内零违约概率交易对手)。
  • 所有 expected_exposure_profile[t] == 0.0 返回 0.0(无敞口——满质押或 MtM 为零)。
  • T == 1 退化为一期含折现的 EL:lgd * pd[0] * ee[0] * df[0]

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

期望算法:单遍 O(T)——一次扫过四条平行数组、把 pd[t] * ee[t] * df[t] 累加到求和器、最后乘一次 lgd、返回。没有 off-by-one、没有嵌套循环、也没有超出普通浮点乘法的数值陷阱;T <= 60 足够小,求和顺序在 1e-9 容差下无关紧要。

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

实践背景

每个工作日衍生品交易台的 CVA 团队都要重算每个交易对手交易的信用费用:交易对手违约时未来预期信用损失的现值。标准简单单边 CVA 公式 LGD * sum_t PD_t * EE_t * DF_t 是地基:每期期望损失瀑布在合约存续期内求和、再折现回今天。结果作为信用费用从交易的 mark-to-market 中扣除——它在交易员屏幕上表现为 P&L 减项,在风险官面板上表现为监管资本数字。CVA 是无风险 MtM 的信用面镜像、也是 Basel III 与 IFRS 13 下新交易定价的一阶驱动因子。本题简单公式假设 PD 与敞口独立——即无错向风险;更精细的 CVA 模型会加错向调整因子、可能还要做随机敞口模拟,但独立形式的单边 CVA 是合适的起点,也正是 solution(...) 的计算目标。它与单期预期损失(姊妹题 coding-portfolio-expected-loss-by-bucket)的区别在于本题是带折现的时间剖面求和;与累计 PD 构造(coding-cumulative-default-prob-from-marginal-hazard-rates)的区别在于本题直接消费边际 PD 进入折现期望损失聚合、而不是产出累计 PD 期限结构。

约束条件

  • 0 <= T == len(expected_exposure_profile) == len(marginal_pd_per_period) == len(discount_factors) <= 60
  • 0.0 <= expected_exposure_profile[t] <= 1e9(per-period EE 美元、非负)
  • 0.0 <= marginal_pd_per_period[t] <= 1.0(**无条件边际**违约概率——存活到 t-1 已内嵌、非累计、非裸条件 hazard;sum_t pd_t <= 1)
  • 0.0 <= lgd <= 1.0(违约损失率,跨期保持常数)
  • 0.0 < discount_factors[t] <= 1.0(per-period 折现因子,例如 exp(-r*t))
  • 输出为非负 float(美元 CVA),按 rel_tol=1e-9、abs_tol=1e-9 比对

样例

Case 1 · statement-example: T=3 swap-like, LGD=0.6, r=0.05

输入: [[1000000,900000,800000],[0.02,0.018,0.016],0.6,[0.951229424500714,0.9048374180359595,0.8607079764250578]]

期望: 26820.01005626254

T=3:每期贡献 0.02*1e6*0.95123=19024.59、0.018*9e5*0.90484=14658.37、0.016*8e5*0.86071=11017.06;和 ≈ 44700.02;乘 LGD=0.6 ≈ 26820.01。

Case 2 · typical: T=1 single period collapses to EL with discounting

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

期望: 28536.882735021423

T=1:CVA = 0.6 * 0.05 * 1e6 * exp(-0.05) ≈ 28536.88。

Case 3 · boundary: T=0 empty deal returns 0.0

输入: [[],[],0.6,[]]

期望: 0

T=0:没有期可求和、返回 0.0。

Case 4 · boundary: lgd=0 returns 0.0 (full recovery, no CVA)

输入: [[1000000,900000],[0.05,0.05],0,[0.95,0.9]]

期望: 0

LGD=0:完全回收、CVA=0、与 PD/EE/DF 无关。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · statement-example: T=3 swap-like, LGD=0.6, r=0.05

输入: [[1000000,900000,800000],[0.02,0.018,0.016],0.6,[0.951229424500714,0.9048374180359595,0.8607079764250578]]

期望: 26820.01005626254

T=3:每期贡献 0.02*1e6*0.95123=19024.59、0.018*9e5*0.90484=14658.37、0.016*8e5*0.86071=11017.06;和 ≈ 44700.02;乘 LGD=0.6 ≈ 26820.01。

Case 2 · typical: T=1 single period collapses to EL with discounting

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

期望: 28536.882735021423

T=1:CVA = 0.6 * 0.05 * 1e6 * exp(-0.05) ≈ 28536.88。

Case 3 · boundary: T=0 empty deal returns 0.0

输入: [[],[],0.6,[]]

期望: 0

T=0:没有期可求和、返回 0.0。

Case 4 · boundary: lgd=0 returns 0.0 (full recovery, no CVA)

输入: [[1000000,900000],[0.05,0.05],0,[0.95,0.9]]

期望: 0

LGD=0:完全回收、CVA=0、与 PD/EE/DF 无关。