经典降噪方法(一):移动平均与指数平滑、中值滤波、小波阈值降噪、卡尔曼滤波
各位同学,今天我们来聊聊订单簿降噪的「基本功」。
说实话,我刚开始做高频交易那会儿,拿到的订单簿数据简直没法看。价格一跳一跳的,成交量忽大忽小,噪声比信号还猛。那时候我就在想——有没有办法把这些毛刺去掉,把真正的买卖意图给揪出来?
嗯,今天这四种方法,就是我当年踩坑踩出来的经验。咱们一个一个说。
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)听起来高大上,其实原理不复杂。它把信号分解成不同频率的成分,然后对高频部分(噪声所在区域)进行「阈值处理」——小于阈值的直接干掉,大于阈值的保留或压缩。
为什么适合订单簿?因为订单簿的噪声往往是高频的、小幅的波动,而真正的买卖压力变化是低频的、持续的。小波变换天然能把这两者分开。
核心步骤:
- 选择小波基(比如db4、sym8)
- 对信号进行多层分解
- 对高频系数进行阈值处理
- 重构信号
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,整整调了一周才找到感觉。
记住,降噪不是目的,目的是让信号更干净、预测更准。下次你看到订单簿上那些乱七八糟的报价时,心里应该有个谱了——该用哪把刀,切哪块肉。