← 返回编程题库
coding-lifetime-el-with-survival-weighting中等免费版2000ms未尝试

含存活加权与逐期 LGD/EAD 剖面的生命期期望损失

Lifetime Expected Loss with Survival Weighting and Per-Period LGD/EAD

开始编码

实现 solution(marginal_pd: list[float], lgd_per_period: list[float], ead_profile: list[float], discount_factors: list[float]) -> float

某银行的 IFRS 9 / CECL 减值准备引擎需要某金融工具的生命期期望信用损失:该金融工具存续期内所有未来期望损失的现值,按到达每一期的存活概率加权。给定 per-period 边际违约概率期限结构、per-period LGD 剖面(允许变化——例如由于抵押品估值变化)、per-period EAD 剖面、以及 per-period 折现因子,返回美元生命期 EL 标量。

形式上,定义运行存活概率与每期 EL 贡献:

survival[0] := 1.0
For t in 0..T-1:
    el_t = survival[t] * marginal_pd[t]
                       * lgd_per_period[t]
                       * ead_profile[t]
                       * discount_factors[t]
    survival[t+1] = survival[t] * (1 - marginal_pd[t])

lifetime_el = sum(el_t for t in 0..T-1)

注意:在第 t 期,survival[t] 是存活到第 t开始(即到第 t-1 期末)的概率。然后第 t 期内可能以概率 marginal_pd[t] 发生违约、产生损失 el_t。本期结束后,survival 乘以 (1 - marginal_pd[t]) 得到下一期开始时的存活概率。

返回 lifetime_el(一个 float)。

算例

考虑 marginal_pd = [0.02, 0.018, 0.016, 0.014, 0.012]lgd_per_period = [0.40, 0.42, 0.44, 0.46, 0.48]ead_profile = [1_000_000, 900_000, 800_000, 700_000, 600_000]discount_factors = [0.99, 0.98, 0.97, 0.96, 0.95]。逐期维护 survival

  • t=0survival = 1.0el_0 = 1.0 * 0.02 * 0.40 * 1_000_000 * 0.99 = 7_920.0。更新 survival = 1.0 * (1 - 0.02) = 0.98
  • t=1el_1 = 0.98 * 0.018 * 0.42 * 900_000 * 0.98 ≈ 6_535.66。更新 survival = 0.98 * (1 - 0.018) = 0.96236
  • t=2el_2 = 0.96236 * 0.016 * 0.44 * 800_000 * 0.97 ≈ 5_257.55。更新 survival ≈ 0.94697
  • t=3el_3 ≈ 0.94697 * 0.014 * 0.46 * 700_000 * 0.96 ≈ 4_096.36。更新 survival ≈ 0.93371
  • t=4el_4 ≈ 0.93371 * 0.012 * 0.48 * 600_000 * 0.95 ≈ 3_065.69

求和:lifetime_el ≈ 26_875.27(测试比对的是精确 float 值)。

五个易错点

第一个易错点是循环内的 survival-update 顺序。在每一期 t当前运行的 survival(代表"存活到第 t 期开始"的概率)计算 el_t然后才更新 survival *= (1 - marginal_pd[t]) 供下一次迭代使用。把顺序反过来(先更新 survival,再用更新后的值给第 t 期加权)会让每一期都被多乘一个 (1 - mp[t]) 因子。在算例中,错序写法会得到 el_0 = 0.98 * 0.02 * 0.40 * 1_000_000 * 0.99 = 7_761.6 而不是 7_920.0——第一期就错,且偏差逐期累计放大。

第二个易错点是 **survival[0] = 1 的初值**。在第 0 期开始时,按假设债务人还活着(尚未发生违约),所以 survival[0] = 1.0el_0 = 1 * mp[0] * lgd[0] * ead[0] * DF[0]。把 survival = 1 - marginal_pd[0](提前一步)作为初值会让第 0 期被低估 (1 - mp[0]) 倍。干净的实现以 survival = 1.0 作为初值,先计算再更新——初值陷阱自动消失。

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

第四个易错点是每期的四因子乘积。每一期对生命期 EL 的贡献是 (survival * mp * lgd * ead * DF) 的乘积——把 survival 算上是五个因子,但必须逐期相乘的四个 per-period 输入是 mp[t]lgd[t]ead[t]DF[t]。把任何一个因子加起来或漏掉(例如忘记乘 DF、把 LGD 单独求和、丢掉存活权重)都会得到错误结果。特别注意:本题使用逐期变化的 LGD(抵押品估值变化、回收曲线随时间老化、违约后 cure-rate 动态),使用单一常数 LGD 标量会丢失 per-period 异质性。这是与常 LGD 的 coding-unilateral-cva-independence 姊妹题的关键区别。

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

边界

  • T == 0:生命期 EL = 0.0(无任何期、没有损失可言)。
  • 所有 marginal_pd[t] == 0.0survival 始终为 1.0;每期 el_t = 0(每项都有 0 因子);生命期 EL = 0.0(视野内零违约风险的债务人)。
  • 所有 ead_profile[t] == 0.0:每期 el_t = 0(每项都有 0 因子);生命期 EL = 0.0(无敞口可损失)。
  • marginal_pd = [1.0, 1.0, ...]survival = [1, 0, 0, ...];只有第 0 期贡献 el_0 = 1 * 1 * lgd[0] * ead[0] * DF[0];后续期均为 0。
  • T == 1lifetime_el = marginal_pd[0] * lgd_per_period[0] * ead_profile[0] * discount_factors[0](使用 survival[0] = 1)。
  • 常数 lgdeadDF 与常数 mp:标准闭式 lifetime_el = lgd * ead * DF * (1 - (1-mp)^T)(几何级数)。

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

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

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

实践背景

在 IFRS 9(国际)与 CECL(美国 GAAP)会计准则下,银行须对划入 stage-2 / stage-3(IFRS 9)或全部贷款(CECL)的金融工具计提生命期期望信用损失减值准备。生命期 ECL 定义为:金融工具合同存续期内所有未来期望损失的现值,按到达每一期的存活概率加权。函数 solution(...) 是规范的生命期 EL 聚合器,把 per-period 边际 PD、LGD、EAD、折现因子组合成单个美元标量费用——该标量随后进入银行总账的减值计提分录。

上游有多条流水线为这些输入提供值:评级迁移矩阵 + 宏观经济叠加产出 marginal_pd 期限结构;抵押品重估 + 回收曲线建模产出时变 lgd_per_period;摊还表 + 提前还款模型产出 ead_profile;银行融资曲线产出 discount_factors。本题的四输入聚合器位于该堆栈底部——不做任何上游建模,只完成存活加权求和。这里的错误(特别是 survival-update 顺序或漏掉某个因子)会造成准备金的系统性错报,属于监管报表合规问题。

与姊妹题的区别:

  • coding-portfolio-expected-loss-by-bucket 是跨债务人桶的单期 EL 聚合——无时间剖面、无存活加权。
  • coding-unilateral-cva-independence 做衍生品交易对手 CVA——假设 LGD 为常数,且使用不同结构的聚合器(更简单的形式中无 per-period EAD 变化)。
  • coding-survival-weighted-ee-profile-for-cva 产出存活加权的期望敞口剖面——一个中间对象、不是标量。
  • coding-cumulative-default-prob-from-marginal-hazard-ratescoding-marginal-pd-from-cumulative-term-structure 是期限结构转换,产出本题消费的 marginal_pd 曲线。
  • coding-lgd-from-collateral-recovery-waterfall 构造本题消费的 per-period lgd_per_period

本题就是 IFRS 9 / CECL 流水线末端的"美元费用标量"——含 per-period 输入的生命期 EL 聚合器

约束条件

  • 0 <= T == len(marginal_pd) == len(lgd_per_period) == len(ead_profile) == len(discount_factors) <= 60
  • 0.0 <= marginal_pd[t] <= 1.0(per-period **边际**违约概率——给定存活到 0..t-1 期之后、第 t 期违约的概率)
  • 0.0 <= lgd_per_period[t] <= 1.0(per-period 违约损失率;**允许逐期变化**)
  • 0.0 <= ead_profile[t] <= 1e9(per-period 违约时敞口、美元、非负)
  • 0.0 < discount_factors[t] <= 1.0(per-period 折现因子;严格为正)
  • 输出为单个非负 `float`——美元生命期 EL;按 rel_tol=1e-9、abs_tol=1e-9 比对
  • 函数名为 `solution`,复杂度 O(T) 时间、O(1) 额外空间

样例

Case 1 · statement-example: T=5 amortizing loan, rising LGD, falling EAD, decreasing DF

输入: [[0.02,0.018,0.016,0.014,0.012],[0.4,0.42,0.44,0.46,0.48],[1000000,900000,800000,700000,600000],[0.99,0.98,0.97,0.96,0.95]]

期望: 26875.661817602046

5 期摊还贷款:每期先用当前 survival 计算 el_t = survival*mp*lgd*ead*DF,再更新 survival *= (1-mp)。survival 序列为 [1.0, 0.98, 0.962360..., 0.946962..., 0.933704...]。逐期 el_t 累加,给出生命期 EL。

Case 2 · typical: T=1 single period, lifetime_el = mp*lgd*ead*DF

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

期望: 19400.000000000004

T=1:survival[0]=1,lifetime_el = 1*0.05*0.40*1e6*0.97 = 19400.0。

Case 3 · boundary: T=0 empty inputs return 0.0

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

期望: 0

T=0:无任何期,生命期 EL = 0.0。

Case 4 · boundary: all marginal_pd zero -> EL = 0

输入: [[0,0,0,0],[0.5,0.5,0.5,0.5],[1000000,1000000,1000000,1000000],[0.95,0.95,0.95,0.95]]

期望: 0

所有 marginal_pd = 0:每期 el_t = survival*0*... = 0;survival 保持为 1;生命期 EL = 0。

Case 5 · boundary: all EAD zero -> EL = 0

输入: [[0.05,0.05,0.05,0.05],[0.4,0.4,0.4,0.4],[0,0,0,0],[0.97,0.97,0.97,0.97]]

期望: 0

所有 EAD = 0:四因子乘积每期均为 0;生命期 EL = 0。

Case 6 · boundary: marginal_pd starts with 1.0 - only period 0 contributes (absorbing default)

输入: [[1,1,1],[0.4,0.5,0.6],[500000,400000,300000],[0.99,0.97,0.95]]

期望: 198000

mp[0]=1:survival 序列为 [1, 0, 0]。只有第 0 期贡献 el_0 = 1*1*0.40*500000*0.99 = 198000。后续期 el_t = 0。

Case 7 · typical: constant inputs T=10 - geometric survival decay

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

期望: 152479.963089416

常数 mp=0.05、lgd=0.40、ead=1e6、DF=0.95,T=10:survival[t] = 0.95^t;闭式 EL = sum_{t=0}^{9} 0.95^t * 0.05 * 0.40 * 1e6 * 0.95。

Case 8 · boundary: T=1 with mp=1.0 - certain default

输入: [[1],[0.5],[2000000],[0.96]]

期望: 960000

T=1, mp=1:survival[0]=1,lifetime_el = 1*1*0.50*2e6*0.96 = 960000。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · statement-example: T=5 amortizing loan, rising LGD, falling EAD, decreasing DF

输入: [[0.02,0.018,0.016,0.014,0.012],[0.4,0.42,0.44,0.46,0.48],[1000000,900000,800000,700000,600000],[0.99,0.98,0.97,0.96,0.95]]

期望: 26875.661817602046

5 期摊还贷款:每期先用当前 survival 计算 el_t = survival*mp*lgd*ead*DF,再更新 survival *= (1-mp)。survival 序列为 [1.0, 0.98, 0.962360..., 0.946962..., 0.933704...]。逐期 el_t 累加,给出生命期 EL。

Case 2 · typical: T=1 single period, lifetime_el = mp*lgd*ead*DF

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

期望: 19400.000000000004

T=1:survival[0]=1,lifetime_el = 1*0.05*0.40*1e6*0.97 = 19400.0。

Case 3 · boundary: T=0 empty inputs return 0.0

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

期望: 0

T=0:无任何期,生命期 EL = 0.0。

Case 4 · boundary: all marginal_pd zero -> EL = 0

输入: [[0,0,0,0],[0.5,0.5,0.5,0.5],[1000000,1000000,1000000,1000000],[0.95,0.95,0.95,0.95]]

期望: 0

所有 marginal_pd = 0:每期 el_t = survival*0*... = 0;survival 保持为 1;生命期 EL = 0。

Case 5 · boundary: all EAD zero -> EL = 0

输入: [[0.05,0.05,0.05,0.05],[0.4,0.4,0.4,0.4],[0,0,0,0],[0.97,0.97,0.97,0.97]]

期望: 0

所有 EAD = 0:四因子乘积每期均为 0;生命期 EL = 0。

Case 6 · boundary: marginal_pd starts with 1.0 - only period 0 contributes (absorbing default)

输入: [[1,1,1],[0.4,0.5,0.6],[500000,400000,300000],[0.99,0.97,0.95]]

期望: 198000

mp[0]=1:survival 序列为 [1, 0, 0]。只有第 0 期贡献 el_0 = 1*1*0.40*500000*0.99 = 198000。后续期 el_t = 0。

Case 7 · typical: constant inputs T=10 - geometric survival decay

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

期望: 152479.963089416

常数 mp=0.05、lgd=0.40、ead=1e6、DF=0.95,T=10:survival[t] = 0.95^t;闭式 EL = sum_{t=0}^{9} 0.95^t * 0.05 * 0.40 * 1e6 * 0.95。

Case 8 · boundary: T=1 with mp=1.0 - certain default

输入: [[1],[0.5],[2000000],[0.96]]

期望: 960000

T=1, mp=1:survival[0]=1,lifetime_el = 1*1*0.50*2e6*0.96 = 960000。