4、滤波器设计:移动平均线(SMA/EMA/WMA)、卡尔曼滤波简介、小波去噪、巴特沃斯滤波器

说到量化交易里的信号处理,滤波器绝对是个绕不开的话题。

我刚开始做策略研究那会儿,总想着怎么把价格数据里的噪音去掉。说白了,K线图上那些毛刺,看着就让人心烦。你想想看,如果连原始数据都处理不干净,后面那些复杂的模型怎么可能跑得稳?

这一章,咱们就聊聊几种最实用的滤波器。从最简单的移动平均线,到稍微高级点的卡尔曼滤波,再到小波去噪和巴特沃斯滤波器。嗯,都是我在实战中反复用过的家伙。

4.1 移动平均线:最朴素的信号平滑器

移动平均线这东西,估计每个做交易的人都见过。但你真的用对了吗?

我个人习惯把移动平均线分成三类:简单移动平均(SMA)、指数移动平均(EMA)和加权移动平均(WMA)。它们的目标都一样——把价格序列里的高频噪音滤掉,留下趋势。

4.1.1 简单移动平均(SMA)

SMA 是最老实巴交的滤波器。它把过去 N 个周期的价格加起来,再除以 N。公平对待每一个数据点,谁也不偏心。

def sma(price, window):
    """简单移动平均"""
    return price.rolling(window=window).mean()

但这里有个坑。我曾经在回测一个趋势策略时,发现 SMA 的滞后性特别严重。为什么?因为它对最近的价格和 10 天前的价格一视同仁。市场都转向了,它还在原地踏步。

注意:SMA 的滞后性随窗口长度增加而加剧。短线交易慎用长周期 SMA。

4.1.2 指数移动平均(EMA)

EMA 就聪明多了。它给最近的价格更高的权重,越老的数据权重越小。说白了,EMA 更「记仇」——它更在意刚刚发生的事情。

def ema(price, span):
    """指数移动平均"""
    return price.ewm(span=span, adjust=False).mean()

我记得有一次做高频策略,SMA 死活跟不上节奏,换成 EMA 之后,信号延迟直接降了 30%。

小技巧:EMA 的平滑系数 α = 2/(span+1)。α 越大,对近期价格越敏感。

4.1.3 加权移动平均(WMA)

WMA 是 SMA 和 EMA 的折中方案。你可以自己定义权重,想怎么偏就怎么偏。

def wma(price, weights):
    """加权移动平均,weights 为自定义权重列表"""
    return price.rolling(window=len(weights)).apply(
        lambda x: np.dot(x, weights) / sum(weights)
    )

我一般用线性递减权重,比如 [5,4,3,2,1]。这样既照顾了近期数据,又没完全抛弃历史信息。

4.2 卡尔曼滤波:动态系统的状态估计器

卡尔曼滤波,听起来很高大上对吧?其实没那么玄乎。

它本质上是一个递归算法。每一时刻,它先根据上一时刻的状态做个预测,然后用当前观测值去修正这个预测。预测和修正,反复迭代。

我在做期货跨期套利时用过卡尔曼滤波。当时要估计两个合约的价差均值,传统移动平均根本搞不定,因为价差均值本身就在漂移。卡尔曼滤波却能实时跟踪这个漂移。

import numpy as np

class KalmanFilter:
    def __init__(self, process_variance, measurement_variance):
        self.process_variance = process_variance      # 过程噪声
        self.measurement_variance = measurement_variance  # 测量噪声
        self.estimate = 0.0
        self.error_estimate = 1.0

    def update(self, measurement):
        # 预测
        self.error_estimate += self.process_variance
        # 卡尔曼增益
        kalman_gain = self.error_estimate / (self.error_estimate + self.measurement_variance)
        # 修正
        self.estimate += kalman_gain * (measurement - self.estimate)
        self.error_estimate = (1 - kalman_gain) * self.error_estimate
        return self.estimate
核心参数:process_variance 控制模型对变化的敏感度,measurement_variance 控制对观测值的信任度。调参时,我建议先用历史数据做网格搜索。

4.3 小波去噪:多尺度的信号分解

小波去噪,说白了就是把信号拆成不同频率的成分,然后把噪音所在的成分砍掉,再拼回去。

为什么用小波?因为傅里叶变换只能告诉你信号里有哪些频率,但不知道这些频率出现在什么时候。小波变换能同时告诉你「什么频率」和「什么时候」。

我曾经在股指期货的 tick 数据上试过小波去噪。效果确实好,但计算量也大。实盘时要注意性能问题。

import pywt

def wavelet_denoise(signal, wavelet='db4', level=1):
    """小波去噪"""
    coeffs = pywt.wavedec(signal, wavelet, level=level)
    # 软阈值处理
    sigma = np.median(np.abs(coeffs[-1])) / 0.6745
    threshold = sigma * np.sqrt(2 * np.log(len(signal)))
    coeffs_thresh = [pywt.threshold(c, threshold, mode='soft') for c in coeffs]
    # 重构
    return pywt.waverec(coeffs_thresh, wavelet)
注意:小波基函数的选择很关键。db4 适合金融数据,但不同品种可能需要微调。别偷懒,多试试。

4.4 巴特沃斯滤波器:最平坦的通带响应

巴特沃斯滤波器,是数字信号处理里的经典。它的特点是通带内最平坦,没有纹波。

在量化交易里,我主要用它来做低频趋势提取。比如你想保留 20 周期以上的趋势,滤掉 5 周期以下的噪音,巴特沃斯低通滤波器就很好用。

from scipy.signal import butter, filtfilt

def butter_lowpass(data, cutoff, fs, order=4):
    """巴特沃斯低通滤波器"""
    nyquist = 0.5 * fs
    normal_cutoff = cutoff / nyquist
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    # 使用 filtfilt 实现零相位延迟
    return filtfilt(b, a, data)
关键点:用 filtfilt 而不是 lfilter。filtfilt 是双向滤波,能消除相位延迟。我当年因为这个细节,策略信号整整优化了 15%。

4.5 实战对比:选哪个?

滤波器 优点 缺点 适用场景
SMA 简单、直观 滞后严重 长周期趋势判断
EMA 响应快、滞后小 对噪音敏感 短线交易、信号触发
WMA 灵活、可定制 需要手动调权重 特殊需求场景
卡尔曼滤波 自适应、实时跟踪 参数调优复杂 动态均值回归、套利
小波去噪 多尺度、效果好 计算量大 离线分析、tick 数据
巴特沃斯 通带平坦、零相位 边界效应 趋势提取、低频滤波

说实话,没有哪个滤波器是万能的。我自己的经验是:先用 EMA 快速验证想法,如果效果不理想,再上卡尔曼或小波。别一上来就整复杂的,容易把自己绕进去。

嗯,这一章就到这儿。下一章咱们聊聊特征工程,那才是真正拉开策略差距的地方。