在固定 PnL 阈值处的经验 CDF 尾部概率
Empirical CDF Tail Probability at a Fixed PnL Threshold
开始编码风控团队在每日 VaR 回溯检验中追踪一个固定 PnL 阈值处的经验尾部概率:给定历史 PnL 样本和一个阈值 t(风控部门定的 VaR 切点,单位与 PnL 一致,负值代表亏损),实测 PnL 严格小于 t 的频率是多少?这与基于分位数的标准 VaR 例程方向相反:不是「找到能覆盖最差 α% 的阈值」,而是「在这个固定切点之下、实测样本占比是多少?」。这个输出服务于三条工作流:(1)交通灯回溯检验(统计实测样本相对固定每日 VaR 限额的突破次数),(2)监管异常报告(实测亏损突破模型 VaR 的天数),以及(3)压力测试影响评估(在阈值 t 处历史样本中会触发止损的比例)。
请实现 solution(pnl_samples: list[float], threshold: float) -> float,返回
其中 N = len(pnl_samples)。测试套主要围绕三个正确性陷阱。**严格 <,不是 <=**——恰好等于 t 的观测被排除在尾部之外(采用经验 CDF 的「P(X < t)」形式,不是「P(X ≤ t)」)。**分母是 N**——除以完整样本量,不是 N - 1。统计下尾——严格小于而不是严格大于;方向反了就会返回 1 - P_hat。当 N == 0 时经验概率未定义,规范返回 float('nan') 作为哨兵。
算法
一次线性扫描,一次除法。
- 若
N == 0,立即返回float('nan')。 - 初始化
count = 0。遍历pnl_samples中的每个v,若v < threshold则count += 1。 - 返回
count / N。
算例
solution([-3.0, -1.0, 0.5, 2.0], -2.0) 返回 0.25。严格小于的集合是 {-3.0}(只有第一个样本 < -2.0;-1.0 反而更大);计数为 1,N = 4,结果 1/4 = 0.25。如果反过来统计 >= -2.0,会得到 3/4 = 0.75——上尾计数,是「1 减原值」的反向结果。
solution([-1.0, -1.0, 0.0], -1.0) 返回 0.0。两个 -1.0 等于而不是小于 threshold = -1.0,严格小于的计数是 0。<= 的 bug 在这里会返回 2/3——典型的「严格 vs 非严格」破绽。
solution([5.0, 5.0, 5.0], 10.0) 返回 1.0。三个样本全部 < 10.0,计数 3,N = 3,结果 3/3 = 1.0(阈值高于整个样本,整个样本都落在下尾内)。
solution([], -1.0) 返回 float('nan')。没有样本就没有经验分布可查;nan 哨兵避免下游仪表盘把「无数据」与「零突破率」混淆。0.0 的 bug 会把一个未定义值悄悄误报为「实测为零」。
量化背景
经验 CDF 尾部概率是历史 VaR 的正向反例。历史 VaR 固定置信度 α、求阈值(损失分布的 α 分位数);本例程固定阈值、求尾部质量——也就是该阈值对应的经验置信度。两者是同一经验分布上的反函数:把它们组合起来就能还原输入(在 α 的插值选择和阈值上的等值处理上有微小差异)。这让该例程成为巴塞尔交通灯框架下 VaR 回溯检验的天然基元:每天报一个固定的模型 VaR t,在回溯窗口里数 #{i : pnl_i < t},再与二项分布期望 N * (1 - α) 比较,给出绿黄红打分。监管异常报告字面上算的也是这个值(实测亏损突破模型亏损上限的天数);压力测试团队也用它来估算某条候选止损线在历史上会被触发多少次。
严格不等式(< vs <=)的选择虽小但在规模化时很关键:当阈值与四舍五入后的伪迹或重复 PnL 值(例如零、或日终标价上限)重合时,<= 形式会过计等值并夸大上报的突破率。本规范选择 < 与经验 CDF「P(X < t)」的标准定义保持一致,并保证在阈值上恰有等值观测(按定义这些观测并未触发下尾事件)时结果不变。
约束条件
- 0 <= len(pnl_samples) <= 1500
- 每个 `pnl_samples[i]` 是有限浮点数,`|pnl_samples[i]| <= 1e6`(带符号;负数表示亏损、正数表示盈利)
- `threshold` 是有限浮点数,`|threshold| <= 1e6`(带符号)
- 输出是 Python `float`,落在 `[0.0, 1.0]` 内;当 `len(pnl_samples) == 0` 时输出 `float('nan')`
- 浮点容忍:`rel_tol = 1e-9`,`abs_tol = 1e-9`;`nan` 在本题中视为与 `nan` 相等
- 复杂度目标:O(N) 时间,O(1) 空间
样例
Case 1 · statement-example: pnl=[-3,-1,0.5,2] threshold=-2 -> 0.25
输入: [[-3,-1,0.5,2],-2]
期望: 0.25
严格小于 -2 的样本只有 -3.0;count=1,N=4,结果 1/4=0.25。
Case 2 · typical: half the sample below threshold
输入: [[-2,-1,0,1],0]
期望: 0.5
严格 < 0 的有 -2.0 和 -1.0,count=2,N=4,结果 0.5;0.0 等于阈值不计入。
Case 3 · typical: positive threshold above all samples
输入: [[-5,-3,-1,0],1]
期望: 1
全部 4 个样本都严格 < 1.0;count=4,N=4,结果 1.0。
Case 4 · typical: mixed signs around zero threshold
输入: [[-5,0,5],0]
期望: 0.3333333333333333
严格 < 0 的只有 -5.0;count=1,N=3,结果 1/3。如果方向反了得到 2/3,是上尾计数。
Case 5 · typical: ten samples with threshold mid-range
输入: [[-10,-8,-5,-3,-1,1,3,5,8,10],0]
期望: 0.5
10 个样本中有 5 个严格 < 0;result=5/10=0.5。
Case 6 · typical: 8 samples with one breach at -100
输入: [[-100,1,2,3,4,5,6,7],0]
期望: 0.125
8 个样本中只有 -100.0 严格 < 0;count=1,N=8,结果 1/8=0.125。
Case 7 · typical: small fractional probability 1/7
输入: [[-0.01,0.5,0.6,0.7,0.8,0.9,1],0]
期望: 0.14285714285714285
只有 -0.01 严格 < 0;count=1,N=7,结果 1/7。
Case 8 · boundary: N=0 returns nan
输入: [[],-1]
期望: "NaN"
样本为空时经验概率未定义,返回 nan 哨兵;不要崩溃也不要返回 0.0。
Case 9 · boundary: N=0 with positive threshold returns nan
输入: [[],1000000]
期望: "NaN"
N=0 与阈值无关,恒返回 nan。
Case 10 · boundary: single sample, threshold equals it -> 0.0
输入: [[5],5]
期望: 0
单样本时 threshold 恰等于样本:严格 < 不计入;count=0,N=1,结果 0.0(<= bug 会得到 1.0)。
Case 11 · boundary: single sample, threshold above it -> 1.0
输入: [[5],10]
期望: 1
单样本严格 < threshold;count=1,N=1,结果 1.0。
Case 12 · boundary: single sample, threshold below it -> 0.0
输入: [[5],-10]
期望: 0
单样本不小于阈值;count=0,N=1,结果 0.0。
Case 13 · boundary: threshold smaller than all samples -> 0.0
输入: [[1,2,3,4,5],-100]
期望: 0
阈值低于全部样本;空尾,结果 0.0。
Case 14 · boundary: threshold larger than all samples -> 1.0
输入: [[1,2,3,4,5],100]
期望: 1
阈值高于全部样本;整段都是尾部,结果 1.0。
Case 15 · boundary: threshold equals minimum sample -> 0.0
输入: [[1,2,3,4,5],1]
期望: 0
阈值等于最小样本:严格 < 不计入,结果 0.0;<= bug 返回 1/5=0.2。
Case 16 · boundary: threshold equals maximum sample -> (N-1)/N
输入: [[1,2,3,4,5],5]
期望: 0.8
阈值等于最大样本:除最大值外全部严格 <;count=4,N=5,结果 0.8;<= bug 返回 1.0。
Case 17 · boundary: all samples equal to threshold -> 0.0
输入: [[3,3,3,3],3]
期望: 0
全部样本等于阈值;严格 < 全部排除;结果 0.0。<= bug 返回 1.0。
Case 18 · boundary: all samples equal, threshold above -> 1.0
输入: [[3,3,3,3],3.0001]
期望: 1
阈值刚好高出微小一档;全部样本严格 <;结果 1.0。
Case 19 · boundary: all samples equal, threshold below -> 0.0
输入: [[3,3,3,3],2.9999]
期望: 0
阈值刚好低出微小一档;无样本严格 <;结果 0.0。
Case 20 · boundary: threshold at zero with PnL straddling
输入: [[-1e-9,0,1e-9],0]
期望: 0.3333333333333333
极小负数 -1e-9 严格 < 0 计入;0.0 等值不计入;1e-9 不计入;count=1,N=3,结果 1/3。
Case 21 · boundary: ties at threshold strict-less-than excludes
输入: [[-1,-1,-1,0,0,0,0,0,1,1],0]
期望: 0.3
三个 -1.0 严格 < 0;五个 0.0 等值排除;两个 1.0 在右侧;count=3,N=10,结果 0.3。<= bug 返回 0.8。
Case 22 · boundary: ties at threshold = -1, none below
输入: [[-1,-1,-1,0,0,0,0,0,1,1],-1]
期望: 0
阈值 -1 与最小观测值并列;严格 < 不计入;结果 0.0。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: pnl=[-3,-1,0.5,2] threshold=-2 -> 0.25
输入: [[-3,-1,0.5,2],-2]
期望: 0.25
严格小于 -2 的样本只有 -3.0;count=1,N=4,结果 1/4=0.25。
Case 2 · typical: half the sample below threshold
输入: [[-2,-1,0,1],0]
期望: 0.5
严格 < 0 的有 -2.0 和 -1.0,count=2,N=4,结果 0.5;0.0 等于阈值不计入。
Case 3 · typical: positive threshold above all samples
输入: [[-5,-3,-1,0],1]
期望: 1
全部 4 个样本都严格 < 1.0;count=4,N=4,结果 1.0。
Case 4 · typical: mixed signs around zero threshold
输入: [[-5,0,5],0]
期望: 0.3333333333333333
严格 < 0 的只有 -5.0;count=1,N=3,结果 1/3。如果方向反了得到 2/3,是上尾计数。
Case 5 · typical: ten samples with threshold mid-range
输入: [[-10,-8,-5,-3,-1,1,3,5,8,10],0]
期望: 0.5
10 个样本中有 5 个严格 < 0;result=5/10=0.5。
Case 6 · typical: 8 samples with one breach at -100
输入: [[-100,1,2,3,4,5,6,7],0]
期望: 0.125
8 个样本中只有 -100.0 严格 < 0;count=1,N=8,结果 1/8=0.125。
Case 7 · typical: small fractional probability 1/7
输入: [[-0.01,0.5,0.6,0.7,0.8,0.9,1],0]
期望: 0.14285714285714285
只有 -0.01 严格 < 0;count=1,N=7,结果 1/7。
Case 8 · boundary: N=0 returns nan
输入: [[],-1]
期望: "NaN"
样本为空时经验概率未定义,返回 nan 哨兵;不要崩溃也不要返回 0.0。
Case 9 · boundary: N=0 with positive threshold returns nan
输入: [[],1000000]
期望: "NaN"
N=0 与阈值无关,恒返回 nan。
Case 10 · boundary: single sample, threshold equals it -> 0.0
输入: [[5],5]
期望: 0
单样本时 threshold 恰等于样本:严格 < 不计入;count=0,N=1,结果 0.0(<= bug 会得到 1.0)。
Case 11 · boundary: single sample, threshold above it -> 1.0
输入: [[5],10]
期望: 1
单样本严格 < threshold;count=1,N=1,结果 1.0。
Case 12 · boundary: single sample, threshold below it -> 0.0
输入: [[5],-10]
期望: 0
单样本不小于阈值;count=0,N=1,结果 0.0。
Case 13 · boundary: threshold smaller than all samples -> 0.0
输入: [[1,2,3,4,5],-100]
期望: 0
阈值低于全部样本;空尾,结果 0.0。
Case 14 · boundary: threshold larger than all samples -> 1.0
输入: [[1,2,3,4,5],100]
期望: 1
阈值高于全部样本;整段都是尾部,结果 1.0。
Case 15 · boundary: threshold equals minimum sample -> 0.0
输入: [[1,2,3,4,5],1]
期望: 0
阈值等于最小样本:严格 < 不计入,结果 0.0;<= bug 返回 1/5=0.2。
Case 16 · boundary: threshold equals maximum sample -> (N-1)/N
输入: [[1,2,3,4,5],5]
期望: 0.8
阈值等于最大样本:除最大值外全部严格 <;count=4,N=5,结果 0.8;<= bug 返回 1.0。
Case 17 · boundary: all samples equal to threshold -> 0.0
输入: [[3,3,3,3],3]
期望: 0
全部样本等于阈值;严格 < 全部排除;结果 0.0。<= bug 返回 1.0。
Case 18 · boundary: all samples equal, threshold above -> 1.0
输入: [[3,3,3,3],3.0001]
期望: 1
阈值刚好高出微小一档;全部样本严格 <;结果 1.0。
Case 19 · boundary: all samples equal, threshold below -> 0.0
输入: [[3,3,3,3],2.9999]
期望: 0
阈值刚好低出微小一档;无样本严格 <;结果 0.0。
Case 20 · boundary: threshold at zero with PnL straddling
输入: [[-1e-9,0,1e-9],0]
期望: 0.3333333333333333
极小负数 -1e-9 严格 < 0 计入;0.0 等值不计入;1e-9 不计入;count=1,N=3,结果 1/3。
Case 21 · boundary: ties at threshold strict-less-than excludes
输入: [[-1,-1,-1,0,0,0,0,0,1,1],0]
期望: 0.3
三个 -1.0 严格 < 0;五个 0.0 等值排除;两个 1.0 在右侧;count=3,N=10,结果 0.3。<= bug 返回 0.8。
Case 22 · boundary: ties at threshold = -1, none below
输入: [[-1,-1,-1,0,0,0,0,0,1,1],-1]
期望: 0
阈值 -1 与最小观测值并列;严格 < 不计入;结果 0.0。