Expected Shortfall 校准回溯:超额日比率检验
Expected Shortfall Calibration Backtest via Exceedance-Day Ratio
开始编码请实现 solution(realized_pnls: list[float], var_forecasts: list[float], es_forecasts: list[float]) -> float。风险团队的 Expected Shortfall (ES) 校准仪表盘把这条例程与 Kupiec POF(VaR 计数检验)和 Lopez 量级损失函数(VaR 量级感知检验)并列展示,用于评估模型隐含的尾损分布的条件均值。给定三条等长的长度为 T 的序列——每日实现 PnL(有符号;负值代表亏损)、每日 VaR 预测(正的亏损量级)、每日 ES 预测(正的亏损量级;校准良好时通常 es_forecasts[t] >= var_forecasts[t])——返回超额日上 (realized_loss / es_forecast) - 1 的平均值:
exceedance_days = { t : realized_pnls[t] < -var_forecasts[t] } (严格 <)
对每个 t ∈ exceedance_days:
realized_loss_t = -realized_pnls[t] (正)
score_t = realized_loss_t / es_forecasts[t] - 1
score = mean(score_t for t in exceedance_days)校准完美的 ES 给出 score == 0(因为定义上 E[loss | loss > VaR] == ES)。score 持续为正意味着模型低估了尾损(实现的条件均值超过了预测);持续为负意味着模型保守(实现的条件均值不足)。算法是单次 O(T) 线性扫描:用严格不等式找出超额日,累加 realized_loss / es_forecast - 1,最后除以超额计数。
工作样例
solution([50000.0, -120000.0, 30000.0, -180000.0, -60000.0], [100000.0, 100000.0, 100000.0, 100000.0, 100000.0], [150000.0, 150000.0, 150000.0, 150000.0, 150000.0]) 返回 0.0。5 天窗口,VaR 恒为 10 万,ES 恒为 15 万。第 1、3、5 天 PnL 高于 -10 万,不破口。第 2 天(-12 万 < -10 万)破口,实现亏损 12 万,得分贡献 120000/150000 - 1 = -0.2。第 4 天(-18 万 < -10 万)破口,实现亏损 18 万,得分贡献 180000/150000 - 1 = 0.2。两个超额日的均值是 (-0.2 + 0.2) / 2 = 0.0。在这个窗口上,模型平均意义下校准完美(ES 预测 15 万恰好等于实现的条件均值 (12+18)/2 = 15 万)。
六个陷阱
第一个陷阱是取均值的分母。得分只在超额日上取均值,不在 T 天上取均值。把累加和除以 count(超额日数),不是 len(realized_pnls)。1000 天窗口出 5 个破口时,错把分母写成 1000 会让答案小 200 倍,整套校准解释也变了。
第二个陷阱是严格不等式。破口条件是 realized_pnls[t] < -var_forecasts[t](严格 <),与本套题中 Kupiec、Lopez、Christoffersen 的约定一致。等于不算破口。<= 会按等于天数多算,并把条件均值拉向边界值。
第三个陷阱是实现亏损符号。超额日上正亏损量级是 realized_loss = -realized_pnls[t](取负号)。每日得分的分子是这个正数。直接把 realized_pnls[t] 塞进比率,每个超额日分子都是负的,最终得分符号反了——「低估」会被读成「高估」,反之亦然。
第四个陷阱是**-1 项**。每日得分是 (realized_loss / es_forecast) - 1,不是 realized_loss / es_forecast。-1 是让校准良好的 ES 给 0、低估给正、保守给负的关键。漏掉 -1 的写法返回的是以 1.0 为基准的比率,下游仪表盘所有「与 0 比较」的判断都会错。
第五个陷阱是分母用 ES,不是 VaR。ES 是条件均值尾损,VaR 只是阈值;二者不同(校准良好时通常 es_forecasts[t] >= var_forecasts[t])。分母必须是 es_forecasts[t]。误用 var_forecasts[t] 会得到不同的指标(更像 Lopez 风格的比率),而且 var_forecasts[t] 可能为 0(按约束 es_forecasts[t] >= 0.001 严格正),重新引入除零风险。
第六个陷阱是空超额哨兵。如果窗口内零超额日(包括 T == 0),条件均值无定义,返回 float('nan')。框架比较器开启了 nan_equals_nan = true。不要返回 0.0:0.0 是「校准良好且至少存在一个超额日」的得分,与「整窗口无破口」结构上不同。
哨兵与边界情形
T == 0(空窗口):返回 float('nan')(没有超额日)。
整窗口零破口:返回 float('nan')。验证 solution([10.0]*100, [50.0]*100, [60.0]*100) 应返回 NaN。
每天都破口:得分是 mean((loss_t / es_t) - 1),对所有 T 天取均值。
所有破口都满足 realized_loss == es_forecast:score == 0(校准完美)。每天比率恰为 1,1 - 1 == 0。
所有破口都满足 realized_loss == 2 * es_forecast:score == 1(模型低估尾损 100%)。每天比率恰为 2,2 - 1 == 1。
破口轻微(实现亏损刚刚越过 VaR、远低于 ES):每日贡献为负。例如 realized_loss = var_forecast + epsilon = 100.0 + 0.01、es_forecast = 150.0,每日 100.01/150 - 1 = -0.3333。一个全是轻微破口的窗口可能产生负得分——所有破口都是真实破口,但条件均值的解释是模型高估了尾部严重程度。
工程背景
ES(Expected Shortfall)校准回溯比 VaR 校准更难:ES 是 VaR 以下的条件均值,不是分位数阈值;要检验它必须看破口的量级,而不仅仅是次数或独立性。超额日上 (realized_loss / es_forecast) - 1 取均值,是工业界常见的 ES 校准检验,思路与 Acerbi-Szekely Z2 统计量类似。一个分数持续为正的模型在低估尾损;持续为负则保守。Basel FRTB(Fundamentals Review of the Trading Book)已把市场风险资本计提的核心尾风险度量从 VaR 切到 ES,让这一类 ES 校准回溯成为每张市场风险仪表盘上的常驻检验。本题是题库中的 ES 校准姊妹题——与 VaR 系列区分开:Basel 红绿灯分区(分类、计数)、超额聚簇计数(描述、计数+聚簇)、Kupiec POF 似然比(二项 LR、边际频率)、Christoffersen 独立性与条件覆盖 LR(聚簇 / 联合 VaR)、Lopez I 量级损失函数(平方量级 VaR)、pinball 分位数损失(VaR 分位数损失)。ES 超额比率是题库中的第一道 ES 专属回溯,检验条件均值而非分位数。
约束条件
- 0 <= len(realized_pnls) == len(var_forecasts) == len(es_forecasts) <= 1500
- 对每个 t:`|realized_pnls[t]| <= 1e6`(有符号:正为盈利,负为亏损)
- 对每个 t:`0.0 <= var_forecasts[t] <= 1e6`(正的亏损量级)
- 对每个 t:`0.001 <= es_forecasts[t] <= 1e6`(**严格正**——保证任意超额日上分母非零)
- 输出:`float`——条件均值得分(带符号);超额集为空时返回 `float('nan')`。`rel_tol=1e-9, abs_tol=1e-9`,NaN 与 NaN 相等
样例
Case 1 · statement-example: 5-day window perfectly calibrated
输入: [[50000,-120000,30000,-180000,-60000],[100000,100000,100000,100000,100000],[150000,150000,150000,150000,150000]]
期望: 0
5 天窗口:第 2 天(-12 万)与第 4 天(-18 万)破口(VaR=10 万)。每日得分:120000/150000-1=-0.2 和 180000/150000-1=0.2。两个超额日均值 (-0.2+0.2)/2 = 0.0——本窗口上 ES 校准完美。
Case 2 · visible: empty backtest window returns NaN
输入: [[],[],[]]
期望: "NaN"
T=0:无超额日,条件均值无定义,返回 NaN。框架比较器开启 nan_equals_nan=true。
Case 3 · visible: no exceedances over the window returns NaN
输入: [[10,20,-50],[100,100,100],[150,150,150]]
期望: "NaN"
三天,均不破口(-50 > -100)。零超额日,条件均值无定义,返回 NaN。除零会报错;返回 0.0 把「无破口」和「校准良好的破口」混为一谈。
Case 4 · visible: realized PnL exactly equals minus VaR is NOT an exceedance
输入: [[-100,-100,-50],[100,100,100],[150,150,150]]
期望: "NaN"
第 1、2 天恰在 VaR 阈值上(-100 == -100)。严格小于:等于不算破口。第 3 天也未破。零超额日,NaN。
Case 5 · visible: all exceedances at twice the ES forecast give score 1.0
输入: [[-300,-300,-300],[100,100,100],[150,150,150]]
期望: 1
三天每天破口且实现亏损是 ES 的两倍(300/150 = 2)。每日得分 2-1 = 1。3 天均值 = 1.0。模型低估尾损 100%。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: 5-day window perfectly calibrated
输入: [[50000,-120000,30000,-180000,-60000],[100000,100000,100000,100000,100000],[150000,150000,150000,150000,150000]]
期望: 0
5 天窗口:第 2 天(-12 万)与第 4 天(-18 万)破口(VaR=10 万)。每日得分:120000/150000-1=-0.2 和 180000/150000-1=0.2。两个超额日均值 (-0.2+0.2)/2 = 0.0——本窗口上 ES 校准完美。
Case 2 · visible: empty backtest window returns NaN
输入: [[],[],[]]
期望: "NaN"
T=0:无超额日,条件均值无定义,返回 NaN。框架比较器开启 nan_equals_nan=true。
Case 3 · visible: no exceedances over the window returns NaN
输入: [[10,20,-50],[100,100,100],[150,150,150]]
期望: "NaN"
三天,均不破口(-50 > -100)。零超额日,条件均值无定义,返回 NaN。除零会报错;返回 0.0 把「无破口」和「校准良好的破口」混为一谈。
Case 4 · visible: realized PnL exactly equals minus VaR is NOT an exceedance
输入: [[-100,-100,-50],[100,100,100],[150,150,150]]
期望: "NaN"
第 1、2 天恰在 VaR 阈值上(-100 == -100)。严格小于:等于不算破口。第 3 天也未破。零超额日,NaN。
Case 5 · visible: all exceedances at twice the ES forecast give score 1.0
输入: [[-300,-300,-300],[100,100,100],[150,150,150]]
期望: 1
三天每天破口且实现亏损是 ES 的两倍(300/150 = 2)。每日得分 2-1 = 1。3 天均值 = 1.0。模型低估尾损 100%。