经典降噪方法(一):移动平均与指数平滑、中值滤波、小波阈值降噪、卡尔曼滤波

各位同学,今天我们来聊聊订单簿降噪的「基本功」。

说实话,我刚开始做高频交易那会儿,拿到的订单簿数据简直没法看。价格一跳一跳的,成交量忽大忽小,噪声比信号还猛。那时候我就在想——有没有办法把这些毛刺去掉,把真正的买卖意图给揪出来?

嗯,今天这四种方法,就是我当年踩坑踩出来的经验。咱们一个一个说。

3.1 移动平均——最简单,也最容易犯错

移动平均(Moving Average, MA)说白了就是「取个平均值」。你拿过去N个价格点,加起来除以N,得到一个新的平滑值。

核心公式:

SMA(t) = (P(t) + P(t-1) + ... + P(t-N+1)) / N

我在项目中遇到过一个问题:用移动平均处理订单簿的买卖价差。窗口选小了,噪声还在;窗口选大了,信号滞后严重。有一次我选了50个tick的窗口,结果行情突变时,我的信号还停留在5秒前——那笔交易亏得我心疼。

我的建议:订单簿数据用5-20个tick的窗口比较合适。太短了没效果,太长了反应慢。你可以先试试10个tick。

import numpy as np

def moving_average(data, window=10):
    """简单移动平均"""
    return np.convolve(data, np.ones(window)/window, mode='valid')

你想想看,移动平均有个致命问题——它对所有历史数据一视同仁。最新的价格和10个tick前的价格权重一样,这合理吗?

3.2 指数平滑——给「新鲜」的数据更多权重

指数平滑(Exponential Smoothing, ES)就聪明多了。它给最近的数据更大的权重,越老的数据权重越小,呈指数衰减。

核心公式:

ES(t) = α × P(t) + (1-α) × ES(t-1)

其中 α 是平滑因子,0 < α < 1

α越大,对最新数据越敏感。我个人习惯用α=0.3左右,对于订单簿的中间价来说,这个值能较好地平衡噪声和滞后。

避坑指南:我曾经把α设成0.9,想着「越灵敏越好」。结果呢?噪声被完美保留了,降噪了个寂寞。α不是越大越好,要根据数据频率来调。

def exponential_smoothing(data, alpha=0.3):
    """指数平滑"""
    result = [data[0]]
    for i in range(1, len(data)):
        result.append(alpha * data[i] + (1-alpha) * result[-1])
    return result

指数平滑比移动平均好在哪里?它只需要记住上一个平滑值,计算量小,适合实时处理。做量化交易的朋友应该深有体会——延迟就是金钱。

3.3 中值滤波——对付「毛刺」的利器

中值滤波(Median Filter)不是取平均,而是取中间值。你把窗口内的数据排序,取中间那个数。

为什么要用中值滤波?因为订单簿里经常出现「异常报价」——某个tick突然出现一个离谱的价格,然后又消失。移动平均会被这个异常值拉偏,但中值滤波不会。

举个例子:

价格序列:[100, 101, 102, 500, 101, 100, 99]

移动平均(窗口3):(100+101+102)/3=101, (101+102+500)/3=234.3 ... 被带偏了

中值滤波(窗口3):排序后取中值,[100,101,102]→101, [101,102,500]→102 ... 稳得很

from scipy.signal import medfilt

def median_filter(data, kernel_size=5):
    """中值滤波,kernel_size必须是奇数"""
    return medfilt(data, kernel_size=kernel_size)

我的经验:处理订单簿的买卖价差时,中值滤波的窗口用3或5就够了。窗口太大,会把真正的价格跳变也滤掉——那可就得不偿失了。

3.4 小波阈值降噪——信号处理界的「瑞士军刀」

小波阈值降噪(Wavelet Threshold Denoising)听起来高大上,其实原理不复杂。它把信号分解成不同频率的成分,然后对高频部分(噪声所在区域)进行「阈值处理」——小于阈值的直接干掉,大于阈值的保留或压缩。

为什么适合订单簿?因为订单簿的噪声往往是高频的、小幅的波动,而真正的买卖压力变化是低频的、持续的。小波变换天然能把这两者分开。

核心步骤:

  1. 选择小波基(比如db4、sym8)
  2. 对信号进行多层分解
  3. 对高频系数进行阈值处理
  4. 重构信号
import pywt

def wavelet_denoise(data, wavelet='db4', level=3, threshold=0.1):
    """小波阈值降噪"""
    # 分解
    coeffs = pywt.wavedec(data, wavelet, level=level)
    # 对高频系数做阈值处理
    coeffs[1:] = [pywt.threshold(c, threshold, mode='soft') for c in coeffs[1:]]
    # 重构
    return pywt.waverec(coeffs, wavelet)

避坑指南:我曾经选错了小波基,用haar小波处理订单簿数据,结果重构出来的信号全是锯齿。后来换成db4,效果好了很多。小波基的选择需要根据数据特征来试,没有万能解。

3.5 卡尔曼滤波——动态系统的「最优估计」

卡尔曼滤波(Kalman Filter)是这四种方法里最「聪明」的一个。它不光是降噪,还能做预测。它把订单簿的价格变化建模成一个动态系统,然后根据观测值和系统模型,不断修正对真实价格的估计。

说白了,卡尔曼滤波在问一个问题:「根据我过去的估计和现在的观测,真实价格最可能是什么?」

核心思想:

预测 + 观测 → 更新估计

每一步都在「相信模型」和「相信数据」之间做权衡

class KalmanFilter:
    def __init__(self, process_variance=1e-5, measurement_variance=1e-3):
        self.x = 0  # 状态估计
        self.P = 1  # 估计误差协方差
        self.Q = process_variance  # 过程噪声
        self.R = measurement_variance  # 测量噪声
        
    def update(self, measurement):
        # 预测
        self.P += self.Q
        # 更新
        K = self.P / (self.P + self.R)  # 卡尔曼增益
        self.x += K * (measurement - self.x)
        self.P = (1 - K) * self.P
        return self.x

我的建议:卡尔曼滤波的两个参数——过程噪声Q和测量噪声R——是关键。Q越大,模型越相信观测数据;R越大,模型越相信历史估计。对于订单簿的中间价,我一般把Q设得很小(1e-5),R稍大(1e-3),这样平滑效果比较好。

3.6 四种方法对比——什么时候用哪个?

方法 优点 缺点 适合场景
移动平均 简单、直观、计算快 滞后严重、对异常值敏感 快速预览、低频数据
指数平滑 计算量小、实时性好 参数α需要调优 实时行情、流式数据
中值滤波 抗异常值能力强 会丢失部分细节 有异常报价的订单簿
小波阈值降噪 多尺度分析、效果好 参数多、计算量大 离线分析、研究阶段
卡尔曼滤波 动态估计、可预测 需要建模、参数敏感 高频交易、实时预测

我个人习惯的做法是:先用中值滤波把明显的异常报价干掉,然后用卡尔曼滤波做实时平滑和预测。如果做回测分析,我会用小波阈值降噪,效果更细腻。

记住一句话:没有最好的降噪方法,只有最合适的。关键是要理解你的数据特征——噪声是什么样的?信号是什么样的?然后对症下药。

订单簿降噪方法选择流程图 原始订单簿数据 有异常报价? 中值滤波 实时处理? 卡尔曼滤波 需要预测? 小波阈值降噪 移动平均/指数平滑

嗯,以上就是四种经典降噪方法的全部内容。每种方法都有它的脾气,你得跟它磨合。别指望一次就能找到最优参数——我当年调卡尔曼滤波的Q和R,整整调了一周才找到感觉。

记住,降噪不是目的,目的是让信号更干净、预测更准。下次你看到订单簿上那些乱七八糟的报价时,心里应该有个谱了——该用哪把刀,切哪块肉。

专注资料整理