在线累计 Bollinger 带:Welford 递推的带对序列
Online Cumulative Bollinger Bands: Welford-Recurrence Band-Pair Stream
开始编码实现 solution(x: list[float], k_sigma: float) -> list[list[float]]。给定长度为 N 的逐期观测数组 x 与非负带宽倍数 k_sigma,输出长度为 N 的列表,第 k 项(1-indexed)为二元列表 [lower_band_k, upper_band_k],定义为
lower_band_k = mean_k - k_sigma * sd_k
upper_band_k = mean_k + k_sigma * sd_k其中 mean_k 与 sd_k 是 x[0..k-1] 的累计总体均值与标准差(分母 k,不是 k-1),通过 Welford 递推
delta = x[k-1] - mean_old
mean_new = mean_old + delta / k
M2_new = M2_old + delta * (x[k-1] - mean_new)
sd_k = sqrt(M2_new / k)从 mean_0 = 0、M2_0 = 0 起步。这道题的合约是先折入再读出:每个 tick 先把当前观测吸收进运行状态,再从那个状态读 mean 与 sd 来生成带对——不存在 prior-only z 分数那种"自污染分母"问题,因为第 k 个带对就是定义在包含当前观测的 x[0..k-1] 上。
关键工程要点是总体方差 vs 样本方差的分母选择。本题用分母 k,所以 k=1 时方差正好是 0,两条带都退化到 x[0];改用 statistics.stdev(分母 k-1)会在 k=1 时除零,并在其它 k 处统一偏出 sqrt(k/(k-1)) 倍。Welford 是单趟 O(N)、O(1) 状态,且在大均值-小方差体制下保持数值稳定;教科书的两趟 sum(x^2)/n − (sum(x)/n)^2 写法在该体制下会因为两个相近正数的相消而输出 0 或负方差,让 sqrt 在合法输入上报错。
例
solution([1.0, 2.0, 3.0, 4.0, 5.0], 2.0) 返回
[
[1.0, 1.0 ],
[0.5, 2.5 ],
[0.3670068381445479, 3.632993161855452 ],
[0.2639320225002102, 4.736067977499790 ],
[0.1715728752538097, 5.828427124746190 ],
]逐步:k=1 时窗口里只有一个样本,mean=1、sd=0,两条带都是 1。k=2 时前缀 [1, 2],累计均值 1.5、总体方差 ((1-1.5)^2 + (2-1.5)^2)/2 = 0.25,sd = 0.5,带对 1.5 ± 2*0.5 = [0.5, 2.5]。k=3 时前缀 [1, 2, 3],均值 2、总体方差 ((-1)^2 + 0 + 1^2)/3 = 2/3,sd = sqrt(2/3) ≈ 0.81650,带对 2 ± 2*sqrt(2/3) ≈ [0.367, 3.633]。k=5 时前缀 [1..5],均值 3、总体方差 (4+1+0+1+4)/5 = 2、sd = sqrt(2),带对 3 ± 2*sqrt(2) ≈ [0.172, 5.828]。如果用样本方差(分母 k-1),k=5 时会变成 3 ± 2*sqrt(2.5) ≈ [-0.162, 6.162]——判别测试会捕获这个漂移。
需明确若干细节:solution([], 1.5) 返回 [];常数流如 [3.5, 3.5, 3.5, 3.5] 输出 [[3.5, 3.5], [3.5, 3.5], [3.5, 3.5], [3.5, 3.5]](sd 全程为 0);k_sigma == 0 时每个 k 的带对都坍缩到累计均值;k_sigma 最大可达 100、x 量级可至 ±1e6——Welford 保数值稳定,朴素累加器不行。比较器使用绝对容差 1e-9,因此任何合理的 Welford 或等价补偿和实现都通过。
实现细节由 stubs/stub.py 提供。
实践背景
监控台想在某条收益 / 信号流上挂一个最简单的"当 tick 是否落在累计历史均值 ± k_sigma 包络外"的判断,最常见的就是 Bollinger 带几何。野外存在两种口味:滑动窗口形(取最近 W 期的均值与 sd,零售看图常用)和这里的累计历史形(在整段历史上累计均值与 sd)。当桌子希望整段回测——包括深尾——都参与到带的形状里、而不被一个本身可能并不代表性的近期窗口主导时,累计形是首选;它也是流式统计约束下的自然选择——例如低延迟的 on-ack 事后监控对每笔成交相对其全生命周期的统计打分时,维持滚动缓冲是昂贵的。把分母固定为 k(总体)而不是 k-1(样本)这一个细节是下游读者真正在意的:当带对后续要与 stack 别处按总体 sd 标定的预交易阈值比对时,两种分母约定会悄悄差出 sqrt(k/(k-1)),这个差在低 k 时会演变成系统性的阈值漂移。Welford 是承重递推,因为另一种选项——累加 sum(x) 与 sum(x*x) 再用 E[X^2] - E[X]^2 读方差——在 mid 报价、FX 汇率这种大均值-小离散度的流上会失败:两个接近的正数相消可以输出零或负方差,让 sqrt 在合法输入上报错。Welford 累加的是相对运行均值的平方偏差、而不是相对零的,从而避开相消。
约束条件
- 0 <= len(x) <= 5000,其中 x 是 solution(x, k_sigma) 的逐期观测数组
- k_sigma 为非负有限 float,取值在 [0, 100]
- x[i] 为有限带符号 float,绝对值不超过 1e6
- 输出为长度 N 的二元 float 列表 [lower_band, upper_band];空输入返回 []
- 比较器为 float 绝对容差 1e-9(任意数值稳定的 Welford 或等价补偿和实现都通过)
样例
Case 1 · statement-example: rising 5-tick stream with k_sigma=2.0
输入: [[1,2,3,4,5],2]
期望: [[1,1],[0.5,2.5],[0.36700683814454793,3.632993161855452],[0.2639320225002102,4.73606797749979],[0.1715728752538097,5.82842712474619]]
k=1 时 sd=0 故 [1,1];之后随累计样本 mean 与 population sd 演化,输出每一步的 [mean - 2*sd, mean + 2*sd]。
Case 2 · visible: empty stream returns []
输入: [[],1.5]
期望: []
空输入直接返回空列表。
Case 3 · visible: constant stream emits flat bands
输入: [[3.5,3.5,3.5,3.5],2.5]
期望: [[3.5,3.5],[3.5,3.5],[3.5,3.5],[3.5,3.5]]
全等流的 sd 恒为 0,每一步都是 [3.5, 3.5]。
Case 4 · visible: k_sigma=0 collapses both bands onto the mean
输入: [[2,4,6,8],0]
期望: [[2,2],[3,3],[4,4],[5,5]]
k_sigma=0 时带宽为零,每一步两条带都退化为该步 cumulative mean。
Case 5 · visible: single tick emits [v, v]
输入: [[7.25],3]
期望: [[7.25,7.25]]
单元素 N=1 时 sd_1=0,输出 [[7.25, 7.25]]。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: rising 5-tick stream with k_sigma=2.0
输入: [[1,2,3,4,5],2]
期望: [[1,1],[0.5,2.5],[0.36700683814454793,3.632993161855452],[0.2639320225002102,4.73606797749979],[0.1715728752538097,5.82842712474619]]
k=1 时 sd=0 故 [1,1];之后随累计样本 mean 与 population sd 演化,输出每一步的 [mean - 2*sd, mean + 2*sd]。
Case 2 · visible: empty stream returns []
输入: [[],1.5]
期望: []
空输入直接返回空列表。
Case 3 · visible: constant stream emits flat bands
输入: [[3.5,3.5,3.5,3.5],2.5]
期望: [[3.5,3.5],[3.5,3.5],[3.5,3.5],[3.5,3.5]]
全等流的 sd 恒为 0,每一步都是 [3.5, 3.5]。
Case 4 · visible: k_sigma=0 collapses both bands onto the mean
输入: [[2,4,6,8],0]
期望: [[2,2],[3,3],[4,4],[5,5]]
k_sigma=0 时带宽为零,每一步两条带都退化为该步 cumulative mean。
Case 5 · visible: single tick emits [v, v]
输入: [[7.25],3]
期望: [[7.25,7.25]]
单元素 N=1 时 sd_1=0,输出 [[7.25, 7.25]]。