含存活加权与逐期 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=0:survival = 1.0。el_0 = 1.0 * 0.02 * 0.40 * 1_000_000 * 0.99 = 7_920.0。更新survival = 1.0 * (1 - 0.02) = 0.98。t=1:el_1 = 0.98 * 0.018 * 0.42 * 900_000 * 0.98 ≈ 6_535.66。更新survival = 0.98 * (1 - 0.018) = 0.96236。t=2:el_2 = 0.96236 * 0.016 * 0.44 * 800_000 * 0.97 ≈ 5_257.55。更新survival ≈ 0.94697。t=3:el_3 ≈ 0.94697 * 0.014 * 0.46 * 700_000 * 0.96 ≈ 4_096.36。更新survival ≈ 0.93371。t=4:el_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.0 且 el_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.0:survival始终为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 == 1:lifetime_el = marginal_pd[0] * lgd_per_period[0] * ead_profile[0] * discount_factors[0](使用survival[0] = 1)。- 常数
lgd、ead、DF与常数mp:标准闭式lifetime_el = lgd * ead * DF * (1 - (1-mp)^T)(几何级数)。
本函数不抛异常——调用方保证四个列表长度一致、所有输入在声明范围内。
期望算法:单遍 O(T)、O(1) 额外空间——维护单个运行存活概率(初值 1.0);在每一期 t:先把 el_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-rates与coding-marginal-pd-from-cumulative-term-structure是期限结构转换,产出本题消费的marginal_pd曲线。coding-lgd-from-collateral-recovery-waterfall构造本题消费的 per-periodlgd_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 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
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。