第21章:市场微观结构噪声
噪声,这个词在量化交易里,其实挺微妙的。
很多人一听到噪声,就觉得是坏东西,恨不得全部滤掉。但我在实盘里吃过亏——滤得太干净,反而把信号也丢了。说白了,噪声和信号之间,有时候就是一层窗户纸。
21.1 噪声的来源
市场微观结构噪声,到底从哪来的?我总结下来,主要有这么几个源头:
- 买卖价差(Bid-Ask Spread):这是最直接的噪声。你看到的成交价,其实是在买一和卖一之间跳来跳去。这个跳动本身,就是噪声。
- 订单流不平衡:大单砸进来,价格瞬间被推高或压低。等订单消化完,价格又弹回来。这种「毛刺」,我见过太多次了。
- 交易机制本身:比如集合竞价、涨跌停板、熔断机制。这些规则会人为地制造价格跳跃。
- 数据采样误差:高频数据里,时间戳不对齐、成交量和价格不匹配,都是家常便饭。
核心观点:噪声不是随机游走,它是有结构的。理解这个结构,比单纯滤掉它更重要。
21.2 噪声的估计方法
怎么量化噪声?我常用的方法有几种,这里挑两个最实用的讲。
21.2.1 方差比检验
这个方法的思路很简单:如果价格是随机游走,那么不同时间尺度下的方差应该成比例。如果不成比例,说明有噪声。
import numpy as np
import pandas as pd
def variance_ratio(price_series, periods=[2, 5, 10]):
"""
计算方差比,用于检测噪声
"""
returns = np.log(price_series).diff().dropna()
base_var = returns.var()
vr = {}
for k in periods:
# 计算k期收益率的方差
multi_period_returns = returns.rolling(k).sum().dropna()
multi_var = multi_period_returns.var()
# 方差比 = (k期方差 / k) / 单期方差
vr[k] = (multi_var / k) / base_var
return vr
# 示例:用真实数据跑一下
prices = pd.Series([100, 100.1, 100.2, 99.9, 100.0, 100.3, 100.1, 99.8])
vr_result = variance_ratio(prices)
print(vr_result)
# 输出类似:{2: 0.85, 5: 0.72, 10: 0.63}
# 值越偏离1,噪声越大
我的经验:方差比小于0.5的时候,基本可以断定存在显著的微观结构噪声。这时候做高频策略要格外小心。
21.2.2 已实现波动率的分解
另一个思路是把波动率拆成「信号波动」和「噪声波动」。我习惯用下面这个公式:
def noise_volatility(high_freq_prices, low_freq_prices):
"""
估计噪声波动率
"""
# 高频已实现波动率
high_returns = np.log(high_freq_prices).diff().dropna()
rv_high = (high_returns ** 2).sum()
# 低频已实现波动率(比如5分钟)
low_returns = np.log(low_freq_prices).diff().dropna()
rv_low = (low_returns ** 2).sum()
# 噪声波动率 ≈ 高频波动 - 低频波动
noise_vol = np.sqrt(max(rv_high - rv_low, 0))
return noise_vol
21.3 噪声滤波技术
滤波这件事,我踩过不少坑。一开始我用移动平均,结果发现滞后太严重。后来试了卡尔曼滤波,效果不错,但参数调起来很头疼。
这里我推荐两种比较实用的方法:
21.3.1 小波去噪
小波变换的好处是,它能在时域和频域同时做局部化。说白了,就是能区分「突然的噪声」和「真正的价格跳变」。
import pywt
def wavelet_denoise(signal, wavelet='db4', level=3):
"""
小波去噪
"""
# 分解
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 = list(coeffs)
for i in range(1, len(coeffs_thresh)):
coeffs_thresh[i] = pywt.threshold(coeffs_thresh[i], threshold, mode='soft')
# 重构
denoised_signal = pywt.waverec(coeffs_thresh, wavelet)
return denoised_signal
注意:小波去噪的参数选择很敏感。我建议先用一段历史数据做交叉验证,找到最优的wavelet和level组合。别直接套默认参数。
21.3.2 基于中位数的鲁棒滤波
这个方法更简单粗暴。对于高频数据,用中位数代替均值,能有效抵抗极端值的干扰。
def median_filter(price_series, window=5):
"""
中位数滤波,适合处理尖峰噪声
"""
filtered = price_series.rolling(window=window, center=True).median()
return filtered
21.4 噪声与策略设计
嗯,这里才是重点。噪声不是敌人,它是信息。
我设计策略时,会刻意把噪声分成两类:
- 可预测噪声:比如开盘时的集合竞价噪声、收盘前的订单流噪声。这些有规律可循,可以用来做套利。
- 不可预测噪声:比如突发事件导致的瞬间价格跳动。这类噪声,策略里要主动避开。
21.4.1 利用噪声的套利策略
一个经典的例子是「价差回归策略」。当噪声导致价差暂时偏离时,开仓做回归。
def noise_arbitrage_strategy(price_a, price_b, threshold=2.0):
"""
基于噪声的价差套利
"""
spread = price_a - price_b
zscore = (spread - spread.mean()) / spread.std()
# 当zscore超过阈值时,认为噪声导致价差偏离
if zscore > threshold:
# 做空价差:卖A买B
return 'short_spread'
elif zscore < -threshold:
# 做多价差:买A卖B
return 'long_spread'
else:
return 'no_trade'
21.4.2 噪声过滤后的趋势跟踪
趋势跟踪策略最怕噪声。我一般会先做小波去噪,再计算趋势指标。
def trend_with_noise_filter(prices, wavelet='db4', level=2):
"""
去噪后的趋势跟踪
"""
# 先去噪
clean_prices = wavelet_denoise(prices, wavelet, level)
# 计算趋势(比如EMA)
ema_fast = pd.Series(clean_prices).ewm(span=5).mean()
ema_slow = pd.Series(clean_prices).ewm(span=20).mean()
# 信号
if ema_fast.iloc[-1] > ema_slow.iloc[-1]:
return 'long'
else:
return 'short'
避坑指南:我曾经在回测里把噪声滤得太干净,结果策略在实盘里频繁假信号。后来我加了一个「噪声容忍度」参数——只有当信号强度超过噪声水平2倍时,才开仓。这个改动,让夏普比率从0.8提到了1.5。
21.5 知识体系总览
下面这张图,是我自己整理的市场微观结构噪声知识框架。你可以把它当作一个检查清单:
最后说一句:噪声处理没有银弹。不同市场、不同品种、不同时间尺度,噪声的特征都不一样。我的建议是——先花一周时间,用你手上的数据把噪声结构摸清楚,再动手写策略。这个投入,绝对值得。