在线累计 Pearson 相关系数(Welford 共动差递推)
Running Cumulative-History Pearson Correlation via Welford-Style Co-Moment Recurrence
开始编码实现 solution(x_stream: list[float], y_stream: list[float]) -> list[float]。给定两条等长配对流 x_stream 与 y_stream,长度 N (≥ 0),维护五个 Welford 风格运行标量 (mean_x, mean_y, M2_x, M2_y, C2_xy)——其中 M2_x = sum_i (x_i − mean_x_n)**2、M2_y = sum_i (y_i − mean_y_n)**2、C2_xy = sum_i (x_i − mean_x_n)(y_i − mean_y_n) 分别是关于运行均值的累计二阶中心矩与累计交叉中心矩。每观测到新的一对 (x_new, y_new) 后,输出累计历史 Pearson 相关系数
corr_k = C2_xy / sqrt(M2_x * M2_y)仅当 M2_x > 0 且 M2_y > 0 同时成立;否则输出 float('nan')。索引 k = 0(n = 1)时两条运行方差按构造严格为 0,因此首项始终为 NaN;任一流的前缀为常数也输出 NaN。空输入返回 []。
第 (k+1) 次观测的 Welford 共动差更新(更新后 n = k + 1)为
delta_x = x_new - mean_x
mean_x_new = mean_x + delta_x / n
delta_y = y_new - mean_y
mean_y_new = mean_y + delta_y / n
M2_x += delta_x * (x_new - mean_x_new)
M2_y += delta_y * (y_new - mean_y_new)
C2_xy += delta_x * (y_new - mean_y_new) # 不对称:旧 mean_x、新 mean_yC2 行是唯一的微妙之处。它通过 delta_x 使用旧 mean_x,通过 y_new − mean_y_new 使用新 mean_y;代数等价对偶 delta_y * (x_new − mean_x_new) 也可。混搭错误的两半(例如 delta_x * (y_new − mean_y) 仍取旧 mean_y)会沉默地偏移轨迹。
例
solution([0.01, -0.02, 0.015, -0.005, 0.03], [0.02, -0.01, 0.018, -0.008, 0.025]) 大致返回 [NaN, 1.0, 0.9816036292717386, 0.9235589222731387, 0.9324346713662166]。在 k = 0 两条方差精确为 0,所以输出 NaN;在 k = 1 两点样本恰好共线,corr = 1 到机器精度(一般地:任意两点都完美共线);从 k = 2 起运行相关系数即为该前缀的样本 Pearson 相关,因为两条流大体同向运动而维持接近 1。总开销 O(N) 时间、O(N) 输出空间、O(1) 额外状态。
朴素替代——每一步从前缀重新计算 (mean(x*y) − mean(x)·mean(y)) / sqrt(var(x)·var(y))——是 O(N²) 的,并且当数据聚集在远离零的位置时会失精度(参见以 1e6 为均值的 adversarial 用例,那里两遍式得到的甚至不再夹在 [−1, 1] 内)。
函数骨架见 stubs/stub.py。
实践背景
跨资产风控监视台上常常实时盯着两条收益流之间的累计历史 Pearson 相关——例如某策略每根 bar 的 PnL 流与一条基准因子流,或者两条本应作为对冲的 book-mid 收益流。这里相关系数不被当作预测,而是用作体制变化哨兵:相关系数突降意味着市场状态变了,或者策略与原本的驱动脱钩了;无论是哪一种,风控系统都希望提前几分钟而不是滞后几分钟知道。Welford 共动差递推让"每个 tick 在每对受监对上更新"足够便宜:每观测一次只更新一次、不需要回看历史、并且在台面数据日常跨越的几个数量级范围(盘中收益 tick 的 1e-4 与跨夜 P&L 的 1e6 并存)下数值稳定。生产中真正会咬人的两条合约正是测试在考的:n = 1 时的 NaN 兜底(出错则要么在第一次观测就崩溃,要么悄悄输出 0/1 而仪表盘以为已经在线),以及常数前缀的 NaN 分支(行情卡住或暂停送出恒值时面板必须显式给出"未定义",而不是 0 或除零异常)。这与 EWMA 衰减协方差不同——这里没有衰减旋钮,每次观测等权贡献——也与秩相关不同——后者根本不使用值层面的 Pearson 公式。
约束条件
- 0 <= len(x_stream) == len(y_stream) <= 4000;每个元素为有限浮点数,绝对值不超过 1e6
- 对每个索引 k(0 起算),第 k 个输出基于 x_stream[0..k] 与 y_stream[0..k],即 n = k + 1 对配对观测
- 若第 k 次更新后 M2_x == 0.0 或 M2_y == 0.0,则输出 float('nan');否则输出 C2_xy / sqrt(M2_x * M2_y)
- n = 1 时两条运行方差按构造为 0,因此 out[0] 必为 float('nan');任一流的前缀为常数都会强制将该索引置为 NaN,直至首次出现差异
- 比对采用浮点容差 rel_tol=1e-9, abs_tol=1e-9,并启用 NaN 与 NaN 相等语义;corr 受舍入影响可能略微突破 1.0(不超过 abs_tol),所以比对器不得做截断
样例
Case 1 · statement-example: 5-step paired return stream — running corr settles near +1 as same-sign moves accumulate
输入: [[0.01,-0.02,0.015,-0.005,0.03],[0.02,-0.01,0.018,-0.008,0.025]]
期望: ["NaN",1,0.9816036292717386,0.9235589222731387,0.9324346713662166]
前两步:k=0 两条方差均为 0 输出 NaN;k=1 两点共线 corr=1。从 k=2 起相关系数即为前缀样本 Pearson 相关,并因两条流大体同向运动维持接近 1。
Case 2 · statement-example: empty inputs return the empty list
输入: [[],[]]
期望: []
空输入直接返回空列表。
Case 3 · statement-example: single pair (n=1) — running variances are 0 by construction, so the only entry is NaN
输入: [[1.5],[-2]]
期望: ["NaN"]
n=1 时 M2_x = M2_y = 0,按合约输出 NaN。
Case 4 · statement-example: two distinct pairs (n=2) — second entry is exactly +1 because two points are always co-linear
输入: [[0,1],[0,0.5]]
期望: ["NaN",1]
k=0 输出 NaN;k=1 两点必然共线,corr 等于 ±1。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: 5-step paired return stream — running corr settles near +1 as same-sign moves accumulate
输入: [[0.01,-0.02,0.015,-0.005,0.03],[0.02,-0.01,0.018,-0.008,0.025]]
期望: ["NaN",1,0.9816036292717386,0.9235589222731387,0.9324346713662166]
前两步:k=0 两条方差均为 0 输出 NaN;k=1 两点共线 corr=1。从 k=2 起相关系数即为前缀样本 Pearson 相关,并因两条流大体同向运动维持接近 1。
Case 2 · statement-example: empty inputs return the empty list
输入: [[],[]]
期望: []
空输入直接返回空列表。
Case 3 · statement-example: single pair (n=1) — running variances are 0 by construction, so the only entry is NaN
输入: [[1.5],[-2]]
期望: ["NaN"]
n=1 时 M2_x = M2_y = 0,按合约输出 NaN。
Case 4 · statement-example: two distinct pairs (n=2) — second entry is exactly +1 because two points are always co-linear
输入: [[0,1],[0,0.5]]
期望: ["NaN",1]
k=0 输出 NaN;k=1 两点必然共线,corr 等于 ±1。