Kupiec POF 似然比统计量:VaR 回溯检验
Kupiec POF Likelihood-Ratio Statistic for VaR Backtesting
开始编码实现 solution(exceedance_count: int, total_obs: int, alpha: float) -> float。某风险台每日回溯检验看板把这一例程与 Basel 红绿灯分区分类器并列运行,作为 VaR 无条件覆盖性的正式假设检验。给定观测到的 VaR 突破次数 exceedance_count(= N)、回溯窗口长度 total_obs(= T)、VaR 置信度 alpha,返回 Kupiec (1995) Proportion-of-Failures 似然比统计量
LR_uc = 2 * [ N * ln(q / p) + (T - N) * ln((1 - q) / (1 - p)) ]其中 p = 1 - alpha 是覆盖正确零假设下的理论突破率,q = N / T 是实现突破率。在零假设下 LR_uc 渐近服从 chi-square(1) 分布;当 LR_uc > 3.84 时按 95% 置信度拒绝模型。统计量恒非负(KL 散度非负 / Jensen 不等式应用于二项对数似然的结论)。
实现是常数时间的:从 alpha 算 p、相除得 q、两次 math.log、求和、乘 2。难点在 0 * ln(0) := 0 的边界约定。
算例
solution(3, 250, 0.99) 返回 0.09494012266442553。逐步:p = 1 - 0.99 = 0.01(理论突破率);250 天窗口下模型期望 T*p = 2.5 个突破。观测 q = 3 / 250 = 0.012(轻微超)。第一项 3 * ln(0.012 / 0.01) = 3 * ln(1.2) ≈ 0.5470。第二项 247 * ln(0.988 / 0.99) ≈ -0.4995。求和 ≈ 0.0475,乘 2 得 ≈ 0.0949。远低于 chi-square(1) 95% 临界值 3.84——模型未被拒绝。
三个易错点
第一,**0 * ln(0) 边界**。每当 N = 0(未观测到突破)时 q = 0,第一项 N * ln(q / p) 是 0 * ln(0 / p) = 0 * (-inf)。数学上由 lim_{x->0} x * ln(x) = 0 应取 0,但直接让 Python 算就得到 0 * (-inf) = nan 把整体答案污染。须在 N == 0 时显式跳过该项。对称地,当 N = T(每天都突破)时 q = 1,第二项 (T - N) * ln((1 - q) / (1 - p)) 变为 0 * ln(0 / (1 - p)) = 0 * (-inf);须在 N == T 时跳过。两个边界可见测试(N = 0 与 N = T)就是为抓不分支的写法而设。
第二,系数 2。统计量是 LR_uc = 2 * [ ... ],来自标准 -2 * [ln L(p) - ln L(q)] 似然比定义(负号与 ln(q/p) 方向相消)。写成 1 * [ ... ] 得到一半——符号正确但 chi-square 阈值按双倍标定;写成 -2 * [ ... ] 翻转符号、产生负值,违反 LR_uc >= 0 不变量。
第三,p 与 alpha。理论突破率是 p = 1 - alpha,不是 p = alpha。99% VaR(alpha = 0.99)下期望 1% 突破率,故 p = 0.01。混淆二者会按错零假设计算,得出与模型充分性无关的数字。相邻易错点:写 q = p(把观测率写死等于理论率,结果恒为 0)或 math.log10(差 1/ln(10) ≈ 0.4343 倍)——任一 bug 都会在重度超出突破的隐藏测试上明显失败。
哨兵与边界
T = 0 在合约下不可达(total_obs >= 1),但参考解防御性地对该输入返回 float('nan')。
q == p(观测率恰等理论率)数学上给 LR_uc = 0——模型与零假设完美一致。浮点下结果是接近 0 的极小数(约 1e-14),落在比较器 abs_tol = 1e-9 容差内。
重度突破不足(如大 T 下 N = 0)可检测,但产生的统计量比对称的过度突破要小,因为二项对数似然在 q = 0 附近偏斜。T = 250、alpha = 0.99 下:观测零突破得 LR_uc ≈ 5.03(95% 拒绝),观测 4 个突破(2*期望)得 LR_uc ≈ 0.77(不拒绝)。
实践背景
Kupiec POF 检验是一支典型风险台 VaR 回溯检验看板上的正式假设检验支柱。它与本题库三道姊妹题互补:Basel 红绿灯分区分类器(绿/黄/红分类,无统计检验)、突破集群计数(连续突破最长游程,用于标记聚集 / 序列相关)、与更朴素的突破计数指标(喂入 Basel 分区但不考虑样本量)。Kupiec 把(次数、样本量、期望率)三元组化为单一统计量并把对真实突破率的不确定性纳入考量,这正是它属于真正似然比检验、而非 desk 启发式的原因。Christoffersen (1998) 的独立性检验在 Kupiec 无条件覆盖性检验上加了条件覆盖性分量;那是另一道题(也是另一个 LR 统计量)。
约束条件
- 1 <= total_obs <= 5000(T >= 1)
- 0 <= exceedance_count <= total_obs(N ∈ [0, T])
- 0.5 < alpha < 1.0(VaR 置信度;p = 1-alpha ∈ (0, 0.5))
- 输出:float —— 非负 LR_uc 统计量,或 `float('nan')` 当 total_obs == 0(合约保证 T >= 1,此为防御性哨兵);rel_tol=1e-9、abs_tol=1e-9;NaN 与 NaN 视为相等
样例
Case 1 · statement-example: 250 obs, N=3, alpha=0.99
输入: [3,250,0.99]
期望: 0.09494012266442553
p=1-0.99=0.01,期望 2.5 个突破;观测 q=3/250=0.012(轻微超)。LR_uc = 2*[3*ln(0.012/0.01) + 247*ln(0.988/0.99)] ≈ 0.0949。远低于 chi-square(1) 95% 临界值 3.84,模型不被拒绝。
Case 2 · visible: q==p exact match yields zero (1/100, alpha=0.99)
输入: [1,100,0.99]
期望: -1.7763568394002513e-15
q = 1/100 = 0.01 恰等于 p = 1 - 0.99 = 0.01,两个 ln 项均为零。LR_uc 数学上为 0(此处显示的是 abs_tol=1e-9 容差内的浮点噪声)。
Case 3 · visible: N=0 boundary, T=250, alpha=0.99
输入: [0,250,0.99]
期望: 5.025167926750753
N=0 触发 0*ln(0)=0 的极限约定,第一项为 0。LR_uc = 2*(T-0)*ln((1-0)/(1-p)) = -2*250*ln(0.99) ≈ 5.0252。直接让 Python 计算 0*ln(0) 会得到 NaN(0 * -inf)。
Case 4 · visible: N=T boundary all days exceed
输入: [250,250,0.99]
期望: 2302.5850929940457
N=T 触发第二项的 0*ln(0)=0 约定(q=1、T-N=0)。LR_uc = 2*T*ln(1/p) = -2*250*ln(0.01) ≈ 2302.585。模型被强烈拒绝。
Case 5 · visible: heavy over-exceedance N=20, T=250, alpha=0.99 rejects
输入: [20,250,0.99]
期望: 49.445276047840615
观测率 q=0.08,期望 p=0.01(高 8 倍)。LR_uc ≈ 49.45 >> 3.84,强烈拒绝。漏掉系数 2 的写法只得到一半(约 24.7)。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: 250 obs, N=3, alpha=0.99
输入: [3,250,0.99]
期望: 0.09494012266442553
p=1-0.99=0.01,期望 2.5 个突破;观测 q=3/250=0.012(轻微超)。LR_uc = 2*[3*ln(0.012/0.01) + 247*ln(0.988/0.99)] ≈ 0.0949。远低于 chi-square(1) 95% 临界值 3.84,模型不被拒绝。
Case 2 · visible: q==p exact match yields zero (1/100, alpha=0.99)
输入: [1,100,0.99]
期望: -1.7763568394002513e-15
q = 1/100 = 0.01 恰等于 p = 1 - 0.99 = 0.01,两个 ln 项均为零。LR_uc 数学上为 0(此处显示的是 abs_tol=1e-9 容差内的浮点噪声)。
Case 3 · visible: N=0 boundary, T=250, alpha=0.99
输入: [0,250,0.99]
期望: 5.025167926750753
N=0 触发 0*ln(0)=0 的极限约定,第一项为 0。LR_uc = 2*(T-0)*ln((1-0)/(1-p)) = -2*250*ln(0.99) ≈ 5.0252。直接让 Python 计算 0*ln(0) 会得到 NaN(0 * -inf)。
Case 4 · visible: N=T boundary all days exceed
输入: [250,250,0.99]
期望: 2302.5850929940457
N=T 触发第二项的 0*ln(0)=0 约定(q=1、T-N=0)。LR_uc = 2*T*ln(1/p) = -2*250*ln(0.01) ≈ 2302.585。模型被强烈拒绝。
Case 5 · visible: heavy over-exceedance N=20, T=250, alpha=0.99 rejects
输入: [20,250,0.99]
期望: 49.445276047840615
观测率 q=0.08,期望 p=0.01(高 8 倍)。LR_uc ≈ 49.45 >> 3.84,强烈拒绝。漏掉系数 2 的写法只得到一半(约 24.7)。