3. 增量更新算法基础
逐笔特征计算的核心矛盾在于:行情流是持续到达的,而特征往往需要“最近一段时间”的统计量。如果每来一笔都从头重算窗口内全部数据,计算量会随窗口长度线性增长,在毫秒级延迟约束下不可接受。增量更新的思路是:让每一笔新数据只做常数级或对数级的局部修正,把历史计算结果复用起来。本节围绕四种基础构件展开——滑动窗口聚合、指数加权移动平均、增量方差/协方差、环形缓冲区,并对比它们与全量重算的复杂度差异。
一、滑动窗口聚合:从“重扫”到“加减”
设窗口长度为 N,窗口内需要维护求和 S。全量重算每来一笔都要遍历 N 个元素,单笔复杂度 O(N);增量做法是维护一个累加器,新数据进入时 S += x_new,最老数据离开时 S -= x_old,单笔复杂度降为 O(1)。均值、最大值、最小值同理,但最大值/最小值不能简单相减,需要配合单调队列,单笔摊还复杂度仍为 O(1)。
以成交额窗口均值特征为例,假设窗口为最近 50 笔成交,初始累加器为 0。第 51 笔到达时,先减去第 1 笔的成交额,再加上第 51 笔的成交额,然后除以 50 得到新均值。整个过程只涉及两次浮点加减和一次除法,与窗口长度无关。需要注意浮点累加误差:长时间运行后 S 可能产生漂移,工程上可定期用一次全量重算做校准,或改用 Kahan 求和补偿。
二、指数加权移动平均:用衰减因子替代窗口
EWMA 不保存历史样本,只维护一个状态值:EWMA_t = α · x_t + (1 − α) · EWMA_{t−1},其中 α ∈ (0,1) 为平滑系数。单笔更新为常数次乘加,复杂度 O(1),内存 O(1)。它与滑动窗口的区别在于:窗口聚合有明确的“遗忘边界”,第 N+1 笔之前的样本权重瞬间归零;EWMA 则是渐进遗忘,越老的数据权重按 (1−α)^k 指数衰减。
实际参数选择上,若希望等效观察长度约为 N 笔,常用近似 α ≈ 2/(N+1)。例如 N=20 时取 α≈0.095。EWMA 对突发行情反应更平滑,但也意味着旧状态会“拖尾”,在剧烈跳变时不如硬窗口灵敏。逐笔特征中,EWMA 常用于买卖压力、短期波动率的平滑估计。
三、增量方差与协方差:Welford 式递推
方差若用 Σx² − (Σx)²/N 直接增量维护,数值稳定性差,大均值小方差时容易产生灾难性抵消。更稳妥的是维护均值与二阶中心矩的递推形式。对固定窗口,进入新值 x_new、离开旧值 x_old 时,可先更新和与平方和,再计算方差;若追求数值稳定,可采用“先移除旧值、再加入新值”的两步 Welford 更新,每步都是常数次运算。
协方差用于刻画两个逐笔序列的联动,例如成交额与价格变化的协方差。维护 Σx、Σy、Σxy 三个累加器,窗口滑动时同步加减,单笔 O(1)。但同样存在数值稳定性问题,工程上可对数据做中心化预处理,或定期重算校准。对比全量重算的 O(N),增量协方差在 N=200 的窗口下可将单笔计算量降低两个数量级。
四、环形缓冲区:让“最老元素”可 O(1) 定位
增量聚合的前提是能快速找到并移除窗口中最老的元素,环形缓冲区正是为此设计。它用固定长度数组加头尾指针实现:写入时尾指针前进并覆盖最老位置,读取最老元素时直接访问头指针。入队、出队、访问最老元素均为 O(1),内存预分配、无动态扩容,避免了链表节点分配带来的抖动,非常适合低延迟场景。
设计要点包括:容量取 2 的幂以便用位掩码替代取模;区分“满”与“空”状态(可用计数器或预留一个空位);多线程环境下写指针与读指针的可见性需用内存序或原子操作保证。逐笔特征引擎通常为每个特征维护独立的环形缓冲区,缓冲区长度即窗口长度。
五、复杂度对比与选型判断
把四种构件放在同一张表里看,差异一目了然。下表为示例性对比,N 表示窗口长度,d 表示协方差维度。
| 构件 | 单笔时间复杂度 | 内存 | 主要风险 | 典型用途 |
|---|---|---|---|---|
| 全量重算窗口聚合 | O(N) | O(N) | 延迟随窗口线性增长 | 离线校验、基准对照 |
| 增量滑动窗口聚合 | O(1) | O(N) | 浮点累加漂移 | 窗口均值、成交额和 |
| EWMA | O(1) | O(1) | 旧状态拖尾、α 难调 | 买卖压力、波动率平滑 |
| 增量方差/协方差 | O(1) / O(d²) | O(N) / O(d²) | 数值抵消、需中心化 | 波动率、量价协方差 |
| 环形缓冲区 | O(1) | O(N) 预分配 | 满/空判定、并发可见性 | 为增量聚合提供窗口底座 |
选型时先问三个问题:窗口是否有明确边界?若需要“只看最近 N 笔”,用环形缓冲区加增量聚合;若更看重平滑与低内存,用 EWMA。统计量是否涉及二阶矩?方差、协方差必须用数值稳定的递推形式,不能图省事直接维护平方和。窗口长度是否频繁变化?环形缓冲区容量固定,变长窗口需要重建缓冲区,这一点要在特征配置阶段就确定。
最后强调一个容易忽略的点:增量更新的正确性依赖于“进入与离开严格配对”。逐笔流中若出现乱序、重复或丢包,累加器会与真实窗口脱节。因此环形缓冲区写入前应做序列号校验,发现缺口时标记该特征为“待重建”,而不是继续在错误状态上累加。低延迟不等于牺牲正确性,增量算法的价值恰恰在于用可控的状态维护换取稳定的常数级延迟。