← 返回编程题库
coding-inverse-percentile-rank-by-pnl中等免费版2000ms未尝试

日 PnL 在历史分布中的反向百分位排名

Inverse Percentile Rank of Daily PnL Against Reference Distribution

开始编码

按策略的回测指标:研究台希望对每个新交易日的日 PnL,计算它在固定历史参考分布中的反向百分位排名,从而把尾部异常(rank 低于 5 或高于 95)单独标记出来。请实现 solution(pnl_samples: list[float], query_pnls: list[float]) -> list[float]:先把 pnl_samples 排序一次,然后对 query_pnls 中每条 query 在 [0.0, 100.0] 之间用线性插值给出它在排序后参考序列上的百分位排名,按输入顺序返回 Q 个 float。

排序后参考序列 s[0..N-1] 上的反向百分位排名公式如下。(1) 若 q <= s[0] 返回 0.0;若 q >= s[N-1] 返回 100.0。(2) 否则用二分查找定位下界邻居,找到 i 使 s[i] <= q <= s[i+1],返回 100.0 * (i + (q - s[i]) / (s[i+1] - s[i])) / (N - 1)。(3) 当 s[i] == s[i+1] 时分母为零 —— 触发重复值规则,返回 100.0 * i / (N - 1),确定性地取较小一侧的 LOWER 索引。分母用 N - 1 而不是 N,所以 query 等于最小值时排名为 0、等于最大值时排名为 100。

pnl_samples = [3.0, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0]query_pnls = [3.0, 5.0, 7.0]。一次排序得 s = [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9],N = 11,denom = N - 1 = 10。q = 3 时二分落在重复簇 s[3] = s[4] = 3 内部,gap 为零,按下界规则取 i = 3,rank = 100 * 3 / 10 = 30.0q = 5 时落在更大的重复簇 s[6] = s[7] = s[8] = 5 内部,下界规则取 i = 6,rank = 100 * 6 / 10 = 60.0q = 7 时无平局:s[9] = 6s[10] = 9frac = (7 - 6) / (9 - 6) = 1/3,rank = 100 * (9 + 1/3) / 10 ≈ 93.3333。整体成本:一次 O(N log N) 排序加三次 O(log N) 二分查找,输出 [30.0, 60.0, 93.333…]

实践背景

这是 numpy.percentile(..., method="linear") 约定的「值到百分位」反方向,与「百分位到值」的题(题库中另一题)刚好互补。研究员先用历史回测窗口构造参考分布,然后用每个新交易日的实际 PnL 去查询:rank < 5 标记为左尾(异常糟糕),rank > 95 标记为右尾(异常好)。在生产里重复值规则尤其重要:止损紧的策略 PnL 往往会聚集在整数刻度附近,如果在重复点出现非确定性的 NaN,标记逻辑会被静默污染。摊销成本同样关键:同一份参考分布通常会服务很多天的查询,所以「参考分布只排序一次、每条 query 一次二分」是正确的工程取舍。

约束条件

  • 1 ≤ N ≤ 5000,N = len(pnl_samples)。N ≥ 1 是调用方先验保证的前提条件,不要求实现处理空参考分布。
  • 0 ≤ Q ≤ 5000,Q = len(query_pnls)。Q = 0 是合法输入 —— 返回空列表,除排序之外不做额外工作。
  • |pnl_samples[i]|、|query_pnls[k]| ≤ 1e6。取值为含正负号的有限浮点数,可包含重复值。
  • 输出是一个长度为 Q 的 float 列表,按输入顺序对应每条 query,每个值落在闭区间 [0.0, 100.0] 内。
  • 浮点比较容差:rel_tol=1e-9,abs_tol=1e-9,用于与参考实现做干净比对。

样例

Case 1 · typical 11-sample distribution, three queries spanning the body

输入: [[3,1,4,1,5,9,2,6,5,3,5],[3,5,7]]

期望: [30,60,93.33333333333334]

11 个样本升序为 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]。query=3 时 s[3]=3、s[4]=3 出现重复,按下界规则取 i=3,结果 = 100 * 3 / 10 = 30.0;query=5 时 s[6]=5、s[7]=5 重复,i=6,结果 = 100 * 6 / 10 = 60.0;query=7 时落在 s[9]=6 与 s[10]=9 之间,i=9、frac=(7-6)/3=0.333…,结果 = 100 * (9 + 0.333…) / 10 = 93.333…。

Case 2 · typical mid-distribution query, no duplicates

输入: [[-50,-10,0,5,12,25,60,80,100,250],[10]]

期望: [41.26984126984127]

10 个样本升序为 [-50, -10, 0, 5, 12, 25, 60, 80, 100, 250],N=10、N-1=9。query=10 落在 s[3]=5 与 s[4]=12 之间,i=3、frac=(10-5)/(12-5)=5/7≈0.7143,结果 = 100 * (3 + 5/7) / 9 ≈ 41.2698。

Case 3 · typical batch: tail-flag style queries against 8-sample reference

输入: [[-5,-2,0,1,2,3,4,6],[-10,-3.5,1.5,5,10]]

期望: [0,7.142857142857143,50,92.85714285714286,100]

N=8、denom=7。query=-10 ≤ s[0]=-5 → 0.0;query=10 ≥ s[7]=6 → 100.0。query=-3.5 落在 s[0]=-5 与 s[1]=-2 之间,frac=1.5/3=0.5,结果 100*(0+0.5)/7≈7.1429;query=1.5 落在 s[3]=1 与 s[4]=2 之间,frac=0.5,结果 100*(3+0.5)/7=50.0;query=5 落在 s[6]=4 与 s[7]=6 之间,frac=0.5,结果 100*(6+0.5)/7≈92.857。

Case 4 · typical quartile-aligned queries on integer-spaced reference

输入: [[10,20,30,40,50,60,70,80,90,100],[25,55,85]]

期望: [16.666666666666668,50,83.33333333333333]

N=10、denom=9。query=25 落在 s[1]=20 与 s[2]=30 之间,frac=0.5,结果 100*1.5/9≈16.6667;query=55 落在 s[4]=50 与 s[5]=60 之间,frac=0.5,结果 100*4.5/9=50.0;query=85 落在 s[7]=80 与 s[8]=90 之间,frac=0.5,结果 100*7.5/9≈83.3333。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · typical 11-sample distribution, three queries spanning the body

输入: [[3,1,4,1,5,9,2,6,5,3,5],[3,5,7]]

期望: [30,60,93.33333333333334]

11 个样本升序为 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]。query=3 时 s[3]=3、s[4]=3 出现重复,按下界规则取 i=3,结果 = 100 * 3 / 10 = 30.0;query=5 时 s[6]=5、s[7]=5 重复,i=6,结果 = 100 * 6 / 10 = 60.0;query=7 时落在 s[9]=6 与 s[10]=9 之间,i=9、frac=(7-6)/3=0.333…,结果 = 100 * (9 + 0.333…) / 10 = 93.333…。

Case 2 · typical mid-distribution query, no duplicates

输入: [[-50,-10,0,5,12,25,60,80,100,250],[10]]

期望: [41.26984126984127]

10 个样本升序为 [-50, -10, 0, 5, 12, 25, 60, 80, 100, 250],N=10、N-1=9。query=10 落在 s[3]=5 与 s[4]=12 之间,i=3、frac=(10-5)/(12-5)=5/7≈0.7143,结果 = 100 * (3 + 5/7) / 9 ≈ 41.2698。

Case 3 · typical batch: tail-flag style queries against 8-sample reference

输入: [[-5,-2,0,1,2,3,4,6],[-10,-3.5,1.5,5,10]]

期望: [0,7.142857142857143,50,92.85714285714286,100]

N=8、denom=7。query=-10 ≤ s[0]=-5 → 0.0;query=10 ≥ s[7]=6 → 100.0。query=-3.5 落在 s[0]=-5 与 s[1]=-2 之间,frac=1.5/3=0.5,结果 100*(0+0.5)/7≈7.1429;query=1.5 落在 s[3]=1 与 s[4]=2 之间,frac=0.5,结果 100*(3+0.5)/7=50.0;query=5 落在 s[6]=4 与 s[7]=6 之间,frac=0.5,结果 100*(6+0.5)/7≈92.857。

Case 4 · typical quartile-aligned queries on integer-spaced reference

输入: [[10,20,30,40,50,60,70,80,90,100],[25,55,85]]

期望: [16.666666666666668,50,83.33333333333333]

N=10、denom=9。query=25 落在 s[1]=20 与 s[2]=30 之间,frac=0.5,结果 100*1.5/9≈16.6667;query=55 落在 s[4]=50 与 s[5]=60 之间,frac=0.5,结果 100*4.5/9=50.0;query=85 落在 s[7]=80 与 s[8]=90 之间,frac=0.5,结果 100*7.5/9≈83.3333。