一条见过未来的平滑线
滤波器把输入序列 xₜ 变成输出 yₜ,输出是近期输入和近期输出的加权和。指数移动平均最简单:yₜ=αxₜ+(1-α)yₜ₋₁。在 scipy.signal 中,滤波器是一对系数列表,b 作用于输入,a 作用于输出;这里 b = [alpha]、a = [1, -(1 - alpha)] 时 yₜ=b₀xₜ-a₁yₜ₋₁。signal.lfilter(b, a, x) 像实时系统一样沿时间向前运行,所以 yₜ 只依赖 s≤t 的 xₛ。它默认从静止(零状态)开始,对接近 100 的价格会使最初的几个值失真;传入 zi = signal.lfilter_zi(b, a) * x[0] 即可从第一个值开始。结果与 pd.Series(x).ewm(alpha=alpha, adjust=False).mean() 在机器精度内一致。
signal.filtfilt(b, a, x) 把同一个滤波器先向前再向后运行。滞后被抵消,平滑线正好贴在数据上,在图上看起来完美,但现在每个输出都依赖于它之后的输入。用它构造交易信号就是泄漏。取一个趋势信号,即对数价格减去其平均,把它与次日收益做相关:对 SPY 的 2,501 天、α=0.1,因果信号的相关系数为 -0.055(微弱的均值回归),零相位信号为 -0.33;α=0.3 时分别为 -0.076 和 -0.54。没有交易员能赚到零相位的这些数字,因为滤波后的值里已经包含了明天的价格。检验信号很简单:在某个日期截断数据,确认该日期之前的值不变。
lfilter是实时的,filtfilt是事后诸葛:只有前者能在交易台上运行。- 设定初始状态:
lfilter_zi(b, a) * x[0]消除启动瞬态。 - 截断测试:在某日期截断数据;有效信号在此之前不会改变。
实验:评分内容
- 滤波器系数10 分
- 因果平均20 分
- pandas 版本10 分
- 零相位平均15 分
- 信号与次日收益25 分
- SPY 研究20 分