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

成交 PnL 的线性插值百分位

Linear-Interpolation Percentile of Trade PnL

开始编码

TCA(成交质量分析)报表常常要回答这样一个问题:在最近一批成交里,第 p 百分位的单笔 PnL 是多少?例如「95 百分位的尾部亏损」用来度量极端情形下的滑点暴露。请实现 solution(pnls: list[float], p: float) -> float:给定一组单笔成交 PnL(货币单位与输入一致)和一个 [0, 100] 区间内的百分位 p,返回线性插值的第 p 百分位。

算法分三步。(1) 把 pnls 升序排序,得到顺序统计量序列 sorted[0..n-1]。(2) 计算分数排名 r = (n - 1) * (p / 100.0),注意分母用 n - 1 而不是 n —— 这样 p=0 落在下标 0、p=100 落在下标 n-1,正好覆盖整段排序数组。(3) 设 lo = floor(r)hi = ceil(r)frac = r - lo,结果 = sorted[lo] * (1 - frac) + sorted[hi] * frac。当 r 是整数(例如 p=0、p=100,或 n=11、p=70 时 r=7)时,lo == hi,公式自动退化为直接读取该下标,不需要特判。

举一个具体例子:solution([3.0, 1.0, 4.0, 1.0, 5.0, 9.0, 2.0, 6.0, 5.0, 3.0, 5.0], 70.0) 升序后为 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9],n=11,r=(11-1)*0.7=7.0 是整数;lo = hi = 7,frac=0;结果直接读 sorted[7] = 5.0。再换 p=33.3:r=(11-1)*0.333=3.33,lo=3, hi=4, frac=0.33,结果 = 3 * 0.67 + 3 * 0.33 = 3.0。两次中间样本恰好相等,所以插值结果也是 3。

这道题有两个常见陷阱。第一,分母:用 n * p / 100 而不是 (n - 1) * p / 100 会让 p=100 时的 r 等于 n,下标越界;这是 numpy 把这一约定单独命名为 "linear" 的根源——区别于 "lower"、"higher"、"nearest" 等其他方法。第二,端点处理:p=0 与 p=100 都是合法输入(不是异常路径),分别返回 sorted[0] 与 sorted[n-1];nearest-rank 实现常常把 p=100 翻译成下标 n(越界)或 p=0 翻译成下标 -1,而线性插值实现只要按公式走就自然落在两端。再加一条:千万不要在排序之前用 nearest-rank 取整再插值——必须先排序再按分数排名取邻居。

约束条件

  • 1 ≤ n ≤ 200000,n = len(pnls)。n ≥ 1 是调用方先验保证的前提条件,不要求实现处理空输入。
  • 0 ≤ p ≤ 100,作为调用方先验保证的前提条件。端点 p=0 与 p=100 必须按定义返回最小值与最大值。
  • PnL 取值在 ±1e9 内的有限浮点数,可以含负值与重复值,不保证已排序。
  • 百分位约定:线性插值,公式为 `r = (n - 1) * p / 100`,匹配 `numpy.percentile(..., method="linear")` 的语义。
  • 浮点比较容差:rel_tol=1e-9,abs_tol=1e-12(输出是单个 float,所以容差非常紧)。

样例

Case 1 · typical 10-trade pnl set, p=95 tail

输入: [[-120.5,-50,-10,5.5,12,25,60,80,100,250],95]

期望: 182.49999999999986

10 笔成交 PnL 升序为 [-120.5, -50, -10, 5.5, 12, 25, 60, 80, 100, 250]。p=95 时 r=(10-1)*0.95=8.55,落在 sorted[8]=100 与 sorted[9]=250 之间,权重 frac=0.55;结果 = 100*(1-0.55) + 250*0.55 = 45 + 137.5 = 182.5。常见 bug:用 n=10 而不是 n-1=9 算位置,会得到 r=9.5,索引出界。

Case 2 · median (p=50) of 4 pnls — averages middle two

输入: [[-3,-1,2,4],50]

期望: 0.5

n=4 的 p=50 中位数:r=(4-1)*0.5=1.5,落在 sorted[1]=-1 与 sorted[2]=2 之间;frac=0.5,结果 = -1*0.5 + 2*0.5 = 0.5。即偶数样本的中位数等于中间两个的平均。

最近提交

还没有提交记录。

编码区

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

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

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

Case 1 · typical 10-trade pnl set, p=95 tail

输入: [[-120.5,-50,-10,5.5,12,25,60,80,100,250],95]

期望: 182.49999999999986

10 笔成交 PnL 升序为 [-120.5, -50, -10, 5.5, 12, 25, 60, 80, 100, 250]。p=95 时 r=(10-1)*0.95=8.55,落在 sorted[8]=100 与 sorted[9]=250 之间,权重 frac=0.55;结果 = 100*(1-0.55) + 250*0.55 = 45 + 137.5 = 182.5。常见 bug:用 n=10 而不是 n-1=9 算位置,会得到 r=9.5,索引出界。

Case 2 · median (p=50) of 4 pnls — averages middle two

输入: [[-3,-1,2,4],50]

期望: 0.5

n=4 的 p=50 中位数:r=(4-1)*0.5=1.5,落在 sorted[1]=-1 与 sorted[2]=2 之间;frac=0.5,结果 = -1*0.5 + 2*0.5 = 0.5。即偶数样本的中位数等于中间两个的平均。