Mincer-Zarnowitz OLS Regression for VaR-Forecast Unbiasedness
Mincer-Zarnowitz OLS Regression for VaR-Forecast Unbiasedness
开始编码实现 solution(realized_pnls: list[float], var_forecasts: list[float]) -> list[float]。风险团队的日度 VaR 研究仪表盘把 Mincer-Zarnowitz (1969) OLS 回归 作为预测无偏性的正式检验,与基于次数的 Kupiec POF 似然比检验和分位数损失(pinball)回测一起使用。给定一个长度为 T 的实现带符号 PnL 序列(realized_pnls[t])以及对应的日度正数损失 VaR 预测(var_forecasts[t]),做以下回归:
y[t] = realized_pnls[t]
x[t] = -var_forecasts[t] # 取负以对齐符号:进入“带符号 PnL 空间”
slope_beta = sum_t (x[t] - x_bar) * (y[t] - y_bar) / (T - 1)
----------------------------------------------------
sum_t (x[t] - x_bar)^2 / (T - 1)
intercept_alpha = y_bar - slope_beta * x_bar返回 [intercept_alpha, slope_beta](α 在前,β 在后)。在无偏 VaR 预测下应有 α ≈ 0 且 β ≈ 1;偏离 (0, 1) 即为偏差证据。当 T < 2(数据不足)或所有 var_forecasts 完全相同(var(x) = 0,回归未定义)时,返回 [NaN, NaN]。比较器为 float,rel_tol = abs_tol = 1e-9、nan_equals_nan = true。
例子
solution([-0.01, -0.02, -0.015], [0.01, 0.02, 0.015]) 返回 [0.0, 1.0]。逐步推导:x = [-0.01, -0.02, -0.015],y = [-0.01, -0.02, -0.015],所以 y = x 完全相等。x_bar = y_bar = -0.015。dx = dy = [0.005, -0.005, 0.0]。sum(dx*dy) = 0.000025 + 0.000025 + 0 = 5e-5。sum(dx*dx) = 5e-5。代入 (T-1) = 2:cov_xy = 2.5e-5,var_x = 2.5e-5。slope_beta = 2.5e-5 / 2.5e-5 = 1.0。intercept_alpha = y_bar - 1 * x_bar = -0.015 - 1*(-0.015) = 0.0。输出 [0.0, 1.0]——这就是“完美无偏预测”的标准答案。再举一例:solution([-0.015, -0.035, -0.055], [0.01, 0.02, 0.03]) 返回约 [0.005, 2.0]。这里 realized = 0.005 + 2 * (-VaR) 完全成立,OLS 恢复出斜率 2、截距 0.005——也就是预测系统性低估损失幅度(β > 1)。
三个常见陷阱
第一个陷阱是 **符号约定取负 x = -var_forecasts**。var_forecasts 是按正数损失约定的(2% VaR 是 0.02),而 realized_pnls 是带符号的(2% 实现损失是 -0.02)。如果直接对 y = realized_pnls 关于 x = var_forecasts 做回归(不取负),斜率会反号——完美无偏预测(realized = -VaR + noise)此时给出 slope = -1 而非 +1,导致 β ≈ 1 的无偏性检验在所有干净输入上都失败。务必取负。
第二个陷阱是 截距公式 α = y_bar - β * x_bar。斜率修正项是必需的;如果写成 α = y_bar,会得到一个退化的“仅均值”估计,与 OLS 在所有非零均值输入上都不一致。验证方法:在完美无偏序列 realized = -var_forecasts 上,必须得到 α = 0(因为 y_bar = x_bar 且 β = 1,所以 α = x_bar - 1*x_bar = 0)。
第三个陷阱是 **输出顺序 [α, β]**:α 在前,β 在后。无偏性的基准是 (α, β) = (0, 1)。颠倒顺序在完美无偏输入上会返回 [1, 0],让仪表盘的阈值检查把无偏判定成“失败”(它在看 output[0] ≈ 0 和 output[1] ≈ 1,但你给的是 [1, 0])。
哨兵与边界情况
T < 2(空输入或单点输入)返回 [NaN, NaN]——少于两个观测点的回归没有自由度。(T-1) 分母会变成除以 0 或除以 1,但更本质的原因是斜率在数学上未定义。
所有 var_forecasts 相同(导致 var(x) = 0)返回 [NaN, NaN]。这是“预测无变化”的退化情形:所有 dx = 0,斜率的分子分母同时为零。注意这与 T < 2 哨兵不同;常数预测情形可以有任意大的 T。
realized_pnls 恒为 0、var_forecasts 有变化:y_bar = 0,所有 dy = 0,所以 cov_xy = 0、slope_beta = 0。然后 intercept_alpha = 0 - 0 * x_bar = 0。结果 [0.0, 0.0](预测对一条平坦的实现序列完全有偏且毫无解释力)。
realized = -var_forecasts 完全成立、且至少有两个不同的预测值:返回 [0.0, 1.0]——这就是“完美无偏预测”的标准输出。
参见 stubs/stub.py 中的函数骨架。
实务背景
Mincer 与 Zarnowitz (1969) 引入了用 OLS 回归把实现值回归到预测值上来检验预测无偏性的方法:预测无偏当且仅当回归 realized = α + β * forecast + ε 满足 α = 0 且 β = 1。应用到 VaR 回测,它与另外两个回测支柱相互补充:Kupiec POF 似然比检验(基于次数的无条件覆盖率检验)和分位数损失/pinball 评分检验(在所选尾分位上的拟合检验)。Mincer-Zarnowitz 的优势在于它检验预测是否捕捉到损失分布的中心趋势,而不仅仅是突破次数。一份典型的 VaR 研究仪表盘会把这三个检验并列展示:通过 Kupiec 但不通过 Mincer-Zarnowitz 的预测,通常意味着突破率正确但存在系统性的水平偏差(例如在非突破日上一致地低估损失幅度)。这里返回的点估计 [α, β] 是联合 Wald 检验 H_0: (α, β) = (0, 1) 的输入;检验统计量本身留给下游例程处理。
约束条件
- 0 <= len(realized_pnls) == len(var_forecasts) <= 1500
- 每个 realized_pnls[t] 是有限浮点数,|realized_pnls[t]| <= 1e6(带符号:正数为盈利,负数为损失)
- 每个 var_forecasts[t] 是有限浮点数,0.0 <= var_forecasts[t] <= 1e6(正数损失约定:0.02 代表 2% 的损失 VaR;回归内部会取负以对齐 realized_pnls 的带符号约定)
- 输出:长度为 2 的浮点列表 `[intercept_alpha, slope_beta]`,对 realized_pnls 关于 (-var_forecasts) 做样本 (T-1) 约定的 OLS 回归得到;比较器为 float,rel_tol = 1e-9,abs_tol = 1e-9,ordered = true,nan_equals_nan = true
- 哨兵输出:当 T = len(inputs) < 2(数据不足以做回归)或所有 var_forecasts 完全相同导致 var(x) = 0(斜率未定义)时返回 `[NaN, NaN]`;函数对任何输入都不抛出异常
样例
Case 1 · statement-example: 3-point perfectly unbiased
输入: [[-0.01,-0.02,-0.015],[0.01,0.02,0.015]]
期望: [0,1]
realized = -var_forecasts 完全成立:y=x,斜率 1,截距 0。[0.0, 1.0]。
Case 2 · statement-example: slope=2 intercept=0.005
输入: [[-0.015,-0.035,-0.055],[0.01,0.02,0.03]]
期望: [0.0049999999999999975,2]
realized = 0.005 + 2*(-VaR):斜率 2、截距 0.005。
Case 3 · boundary: T=0 empty -> [NaN, NaN]
输入: [[],[]]
期望: ["NaN","NaN"]
T=0:未定义。
Case 4 · boundary: T=1 single point -> [NaN, NaN]
输入: [[0.005],[0.02]]
期望: ["NaN","NaN"]
T=1:自由度不足。
Case 5 · boundary: all forecasts identical -> [NaN, NaN]
输入: [[0.01,-0.02,0.005,-0.015],[0.02,0.02,0.02,0.02]]
期望: ["NaN","NaN"]
所有预测相同 -> var_x=0。
Case 6 · boundary: realized = 0 -> [0, 0]
输入: [[0,0,0,0],[0.01,0.02,0.015,0.025]]
期望: [0,0]
realized=0 -> 斜率=0,截距=0。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: 3-point perfectly unbiased
输入: [[-0.01,-0.02,-0.015],[0.01,0.02,0.015]]
期望: [0,1]
realized = -var_forecasts 完全成立:y=x,斜率 1,截距 0。[0.0, 1.0]。
Case 2 · statement-example: slope=2 intercept=0.005
输入: [[-0.015,-0.035,-0.055],[0.01,0.02,0.03]]
期望: [0.0049999999999999975,2]
realized = 0.005 + 2*(-VaR):斜率 2、截距 0.005。
Case 3 · boundary: T=0 empty -> [NaN, NaN]
输入: [[],[]]
期望: ["NaN","NaN"]
T=0:未定义。
Case 4 · boundary: T=1 single point -> [NaN, NaN]
输入: [[0.005],[0.02]]
期望: ["NaN","NaN"]
T=1:自由度不足。
Case 5 · boundary: all forecasts identical -> [NaN, NaN]
输入: [[0.01,-0.02,0.005,-0.015],[0.02,0.02,0.02,0.02]]
期望: ["NaN","NaN"]
所有预测相同 -> var_x=0。
Case 6 · boundary: realized = 0 -> [0, 0]
输入: [[0,0,0,0],[0.01,0.02,0.015,0.025]]
期望: [0,0]
realized=0 -> 斜率=0,截距=0。