← 返回编程题库
coding-basel-var-traffic-light-zone-and-multiplier中等免费版1000ms未尝试

Basel VaR 回溯检验:红绿灯分区与资本乘子加成

Basel VaR Backtest — Traffic-Light Zone and Capital Multiplier Add-On

开始编码

按 Basel 1996 Market Risk Amendment,使用内部 VaR 模型计提交易账簿资本的银行,必须用 250 个交易日的回测窗口,对其 99% 一日 VaR 预测与实现 PnL 做回溯检验,并以「红绿灯」方式向监管报告。请实现 solution(realized_pnls: list[float], var_forecasts: list[float]) -> list[float],返回 [exceedance_count, zone_id, multiplier_addon]。第 i 天判定为破口的充要条件是严格不等式 realized_pnls[i] < -var_forecasts[i]:实现亏损严格超过当日 99% VaR 阈值。var_forecasts[i] 给的是正的亏损量级——50000.0 含义为「99% 信心明日亏损不超过 $50,000」,因此实现 PnL = -60000.0 对应破口(-60000 < -50000)。统计完破口数后映射到分区:0..4 个破口为绿区(zone_id = 0.0,无加成),5..9 个为黄区(zone_id = 1.0,按下文非均匀查表给出资本加成),10 个及以上为红区(zone_id = 2.0,加成 1.0 并伴随监管介入)。Basel 黄区加成表(Annex 10a)为 5 -> 0.40, 6 -> 0.50, 7 -> 0.65, 8 -> 0.75, 9 -> 0.85,以及 10+ -> 1.00 —— 注意此表不是线性插值,必须显式查表。当 len(realized_pnls) == 0 时返回 [0.0, 0.0, 0.0](空窗口视作绿区)。

算法是单次线性扫描计数加 O(1) 查表。同步遍历两个列表,对每一天 i 检查 realized_pnls[i] < -var_forecasts[i],若成立则 count 加一。最后分支:count <= 4 时返回 [float(count), 0.0, 0.0]count <= 9 时从五元素元组 (0.40, 0.50, 0.65, 0.75, 0.85)count - 5 取值得到加成,返回 [float(count), 1.0, addon];其余返回 [float(count), 2.0, 1.0]。整体时间 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 成立,破口;第 1 天 12000 < -50000 不成立;第 2 天 -8000 < -50000 不成立;第 3 天 -55000 < -50000 成立,破口;第 4 天 5000 < -50000 不成立。总 count = 2,落在 0..4(绿区),故 zone_id = 0.0multiplier_addon = 0.0。最终 solution(...) 返回 [2.0, 0.0, 0.0]

实践背景

实现这道题最常见的五个坑都很值得注意。第一是*严格 vs 非严格不等式*:规约写的是 < 而不是 <=。实现亏损恰好等于阈值意味着模型刚好猜准,不算破口——而漏判一个相等日就可能把银行从绿区推到黄区,对应的是真实的资本上浮。第二是*符号约定*:var_forecasts[i] 是正的亏损量级,所以比较时一定要给它取负号(r < -v);写成 r < v 会把每一天都判错。第三是*分区边界*:绿区是闭区间到 4,黄区是闭区间到 9;写 < 4/< 9 都差一——破口数恰好为 4 必须留绿区,恰好为 9 必须留黄区。第四是*乘子表非均匀*:从 6 到 7 跳了 0.15(不是 0.10),用 0.4 + 0.1*(count-5) 的作者在 count=7、8、9 处会得到 0.60/0.70/0.80,而监管表是 0.65/0.75/0.85。请显式查表。第五是*输出类型*:三个返回值都要 cast 到 float,否则浮点容差比较器可能在 int/float 混用上失败。下游监管仪表盘会把返回的加成与 3.0 基准乘子相加(黄区给出 3.4 到 3.85,红区给出 4.0),以此对市场风险资本计提进行倍乘。

约束条件

  • `0 <= len(realized_pnls) == len(var_forecasts) <= 250`。两个列表保证等长(250 是 Basel 标准的 1 年回测窗口)。
  • `|realized_pnls[i]| <= 1e6`;值为有符号有限浮点数(正为盈利,负为亏损)。
  • `0.0 <= var_forecasts[i] <= 1e6`;VaR 预测是**正的亏损数值**(非负)。
  • 当 `len(realized_pnls) == 0` 时,返回 `[0.0, 0.0, 0.0]`(空窗口视作绿区,无加成)。
  • 输出为长度 3 的 `list[float]`:`[exceedance_count, zone_id, multiplier_addon]`。`exceedance_count` 在 `[0, N]` 区间内(转 float);`zone_id` 取 `0.0`、`1.0`、`2.0` 之一;`multiplier_addon` 取 `0.0`、`0.40`、`0.50`、`0.65`、`0.75`、`0.85`、`1.00` 之一。
  • 比较使用浮点容差 `rel_tol=1e-9, abs_tol=1e-9`。

样例

Case 1 · statement-example two exceedances green zone

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

期望: [2,0,0]

第 0 日和第 3 日均为破口(-60000 < -50000 与 -55000 < -50000);count=2 落在 0..4,绿区(0),零加成。输出 [2.0, 0.0, 0.0]。

Case 2 · visible empty backtest window

输入: [[],[]]

期望: [0,0,0]

无数据:空窗口按绿区处理,破口数 0、加成 0,输出 [0.0, 0.0, 0.0]。

Case 3 · visible equality day is not an exceedance

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

期望: [0,0,0]

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

Case 4 · visible boundary count 4 stays green

输入: [[-100,-200,-300,-400,50,10,25],[50,50,50,50,50,50,50]]

期望: [4,0,0]

四个破口日(0..3)和三个非破口日。count=4 是绿区上边界(闭区间 0..4),故 zone=0、加成 0.0。输出 [4.0, 0.0, 0.0]。

Case 5 · visible boundary count 5 yellow add-on 0.40

输入: [[-100,-100,-100,-100,-100,50,50,50,50,50],[50,50,50,50,50,50,50,50,50,50]]

期望: [5,1,0.4]

前 5 天均破口,count=5 进入黄区(闭区间 5..9);Basel 给出的 5 破口加成为 0.40。输出 [5.0, 1.0, 0.4]。

Case 6 · visible yellow count 7 add-on 0.65 not 0.60

输入: [[-100,-100,-100,-100,-100,-100,-100,10,10,10],[50,50,50,50,50,50,50,50,50,50]]

期望: [7,1,0.65]

七个破口,count=7。Basel 表从 6 的 0.50 跳到 7 的 0.65,而**不是** 0.60——线性插值 `0.4 + 0.1*(count-5)` 在此处会错。输出 [7.0, 1.0, 0.65]。

Case 7 · visible boundary count 9 yellow add-on 0.85

输入: [[-100,-100,-100,-100,-100,-100,-100,-100,-100,10],[50,50,50,50,50,50,50,50,50,50]]

期望: [9,1,0.85]

九个破口,count=9 落在黄区上边界(闭区间 5..9);Basel 给出 9 破口的加成为 0.85。输出 [9.0, 1.0, 0.85]。

Case 8 · visible boundary count 10 red add-on 1.0

输入: [[-100,-100,-100,-100,-100,-100,-100,-100,-100,-100],[50,50,50,50,50,50,50,50,50,50]]

期望: [10,2,1]

十个破口,count=10 进入红区(10+);加成封顶 1.00,伴随监管介入。输出 [10.0, 2.0, 1.0]。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · statement-example two exceedances green zone

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

期望: [2,0,0]

第 0 日和第 3 日均为破口(-60000 < -50000 与 -55000 < -50000);count=2 落在 0..4,绿区(0),零加成。输出 [2.0, 0.0, 0.0]。

Case 2 · visible empty backtest window

输入: [[],[]]

期望: [0,0,0]

无数据:空窗口按绿区处理,破口数 0、加成 0,输出 [0.0, 0.0, 0.0]。

Case 3 · visible equality day is not an exceedance

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

期望: [0,0,0]

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

Case 4 · visible boundary count 4 stays green

输入: [[-100,-200,-300,-400,50,10,25],[50,50,50,50,50,50,50]]

期望: [4,0,0]

四个破口日(0..3)和三个非破口日。count=4 是绿区上边界(闭区间 0..4),故 zone=0、加成 0.0。输出 [4.0, 0.0, 0.0]。

Case 5 · visible boundary count 5 yellow add-on 0.40

输入: [[-100,-100,-100,-100,-100,50,50,50,50,50],[50,50,50,50,50,50,50,50,50,50]]

期望: [5,1,0.4]

前 5 天均破口,count=5 进入黄区(闭区间 5..9);Basel 给出的 5 破口加成为 0.40。输出 [5.0, 1.0, 0.4]。

Case 6 · visible yellow count 7 add-on 0.65 not 0.60

输入: [[-100,-100,-100,-100,-100,-100,-100,10,10,10],[50,50,50,50,50,50,50,50,50,50]]

期望: [7,1,0.65]

七个破口,count=7。Basel 表从 6 的 0.50 跳到 7 的 0.65,而**不是** 0.60——线性插值 `0.4 + 0.1*(count-5)` 在此处会错。输出 [7.0, 1.0, 0.65]。

Case 7 · visible boundary count 9 yellow add-on 0.85

输入: [[-100,-100,-100,-100,-100,-100,-100,-100,-100,10],[50,50,50,50,50,50,50,50,50,50]]

期望: [9,1,0.85]

九个破口,count=9 落在黄区上边界(闭区间 5..9);Basel 给出 9 破口的加成为 0.85。输出 [9.0, 1.0, 0.85]。

Case 8 · visible boundary count 10 red add-on 1.0

输入: [[-100,-100,-100,-100,-100,-100,-100,-100,-100,-100],[50,50,50,50,50,50,50,50,50,50]]

期望: [10,2,1]

十个破口,count=10 进入红区(10+);加成封顶 1.00,伴随监管介入。输出 [10.0, 2.0, 1.0]。