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

VaR 回溯检验:破口数与最长连续段

VaR Backtest Exceedance Count and Longest Cluster

开始编码

风控部门按规要求每日维护 VaR 回溯检验日志:今天先把次日的 VaR 预测值落账,第二天再把当日实际 PnL 补上。合规需要从这份日志中读出两个数字——总破口数与最长连续破口段长度。请实现 solution(realized_pnls: list[float], var_forecasts: list[float]) -> list[int],返回 [exceedance_count, max_consecutive_run]。第 i 天判定为破口的充要条件是 realized_pnls[i] < -var_forecasts[i],即实现亏损严格超过当日 VaR 阈值。var_forecasts[i] 给的是正的亏损量级——例如 50000.0 含义是「99% 信心明日亏损不超过 $50,000」,因此当日实现 PnL = -60000.0 即为破口(-60000 < -50000)。这两个指标对应模型的两类互补失效:总破口数走二项覆盖检验(事先承诺的尾部概率是否真的兑现),最长连续段走独立性检验(破口是否密集出现在模型没刻画的波动状态切换上)。当 len(realized_pnls) == 0 时返回 [0, 0]

算法是一次线性扫描配三个计数器 countcur_runbest_run。对每一天 i 检查 realized_pnls[i] < -var_forecasts[i]:成立则 countcur_run 都加 1,并更新 best_run = max(best_run, cur_run);否则把 cur_run 清零,其他不变。结束时返回 [count, best_run]。整体时间 O(N),附加空间 O(1)。

五天的日志:realized_pnls = [-60000.0, 12000.0, -8000.0, -55000.0, 5000.0]var_forecasts = [50000.0, 50000.0, 50000.0, 50000.0, 50000.0]。第 0 天 -60000 < -50000 成立,于是 count=1, cur_run=1, best_run=1;第 1 天 12000 < -50000 不成立,cur_run 清零;第 2 天 -8000 < -50000 不成立,cur_run 仍为 0;第 3 天 -55000 < -50000 成立,count=2, cur_run=1, best_run = max(1, 1) = 1;第 4 天 5000 < -50000 不成立,再次清零。最终 solution(...) 返回 [2, 1]——两次破口、不相邻。

实践背景

实现这道题最常见的三个坑都很值得注意。第一是*严格 vs 非严格不等式*:规约写的是 < 而不是 <=。实现亏损恰好等于阈值意味着模型刚好猜准,不是破口。把 < 写成 <= 会按等于天数多算几个破口,监管抽查时一定挑出来。第二是*符号约定*:var_forecasts[i] 是正的亏损量级,所以比较时一定要给它取负号,写成 realized_pnls[i] < -var_forecasts[i]。把 var_forecasts[i] 当成带符号的 PnL 下限来写 realized_pnls[i] < var_forecasts[i],会把每一天都判错。第三是*清零逻辑*:非破口日 cur_run 必须回到 0;忘了清零,下一段破口接着累积,best_run 最终就等于 count,整段回测会被汇报成一个超长的连续段。

约束条件

  • `0 <= len(realized_pnls) == len(var_forecasts) <= 1500`。两个列表保证等长。
  • `|realized_pnls[i]| <= 1e6`;值为有符号有限浮点数(正为盈利,负为亏损)。
  • `0.0 <= var_forecasts[i] <= 1e6`;VaR 预测是**正的亏损数值**(非负)。
  • 当 `len(realized_pnls) == 0` 时,返回 `[0, 0]`。
  • 输出为长度为 2 的 `list[int]`:`[exceedance_count, max_consecutive_run]`,两值均在 `[0, N]` 内。
  • 输出列表按整数精确比较(答案是计数对,不需要浮点容忍)。

样例

Case 1 · statement-example two singletons

输入: [[-60000,12000,-8000,-55000,5000],[50000,50000,50000,50000,50000]]

期望: [2,1]

第 0 日和第 3 日均为破口(-60000 < -50000 与 -55000 < -50000),二者不相邻,最长连续段长度为 1,故输出 [2, 1]。

Case 2 · visible empty backtest window

输入: [[],[]]

期望: [0,0]

无数据:破口数为 0,最长连续段长度也为 0,输出 [0, 0]。

Case 3 · visible equality is not exceedance

输入: [[-50000,-50000,-50000],[50000,50000,50000]]

期望: [0,0]

每天实现亏损恰好等于 VaR 阈值(-50000 == -50000),按严格小于规则不计为破口,输出 [0, 0]。

Case 4 · visible four-day all exceedance run

输入: [[-101,-200,-300,-400],[100,100,100,100]]

期望: [4,4]

四日实现亏损均超过 VaR 阈值,破口数=4,最长连续段长度=4,输出 [4, 4]。

Case 5 · visible cluster lengths 3 then 5

输入: [[-2,-2,-2,5,5,-2,-2,-2,-2,-2,5],[1,1,1,1,1,1,1,1,1,1,1]]

期望: [8,5]

两个独立连续段:长度分别为 3 和 5,中间被盈利日打断。破口总数=8,最长连续段长度=5,输出 [8, 5]。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · statement-example two singletons

输入: [[-60000,12000,-8000,-55000,5000],[50000,50000,50000,50000,50000]]

期望: [2,1]

第 0 日和第 3 日均为破口(-60000 < -50000 与 -55000 < -50000),二者不相邻,最长连续段长度为 1,故输出 [2, 1]。

Case 2 · visible empty backtest window

输入: [[],[]]

期望: [0,0]

无数据:破口数为 0,最长连续段长度也为 0,输出 [0, 0]。

Case 3 · visible equality is not exceedance

输入: [[-50000,-50000,-50000],[50000,50000,50000]]

期望: [0,0]

每天实现亏损恰好等于 VaR 阈值(-50000 == -50000),按严格小于规则不计为破口,输出 [0, 0]。

Case 4 · visible four-day all exceedance run

输入: [[-101,-200,-300,-400],[100,100,100,100]]

期望: [4,4]

四日实现亏损均超过 VaR 阈值,破口数=4,最长连续段长度=4,输出 [4, 4]。

Case 5 · visible cluster lengths 3 then 5

输入: [[-2,-2,-2,5,5,-2,-2,-2,-2,-2,5],[1,1,1,1,1,1,1,1,1,1,1]]

期望: [8,5]

两个独立连续段:长度分别为 3 和 5,中间被盈利日打断。破口总数=8,最长连续段长度=5,输出 [8, 5]。