← 返回编程题库
coding-es-exceedance-ratio-backtest中等免费版1000ms未尝试

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.00.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_forecastscore == 0(校准完美)。每天比率恰为 1,1 - 1 == 0

所有破口都满足 realized_loss == 2 * es_forecastscore == 1(模型低估尾损 100%)。每天比率恰为 2,2 - 1 == 1

破口轻微(实现亏损刚刚越过 VaR、远低于 ES):每日贡献为。例如 realized_loss = var_forecast + epsilon = 100.0 + 0.01es_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 可见样例;服务端提交会运行可见样例和隐藏测试。

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

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

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%。