第二章:订单簿数据噪声来源
做量化交易这些年,我踩过最大的坑,就是以为订单簿数据是「干净」的。
刚入行那会儿,我拿着原始数据直接跑策略,回测曲线漂亮得不行。结果一上实盘,直接被打脸。后来我才明白——订单簿数据里藏着各种各样的噪声,不处理干净,策略就是空中楼阁。
这一章,咱们就来聊聊噪声到底从哪来。
核心观点:订单簿噪声不是单一来源,而是多种因素叠加的结果。理解噪声来源,是降噪的第一步。
2.1 微观结构噪声
微观结构噪声,说白了就是交易机制本身带来的「毛刺」。
你想想看,订单簿不是连续流动的,它是离散的——每一笔报价、每一次成交,都是一个个独立的事件。这些事件之间,存在大量的随机波动。
我个人习惯把微观结构噪声分成三类:
- 买卖价差噪声:买一和卖一之间的价差,本身就是一种噪声。价差越大,信号越模糊。
- 报价跳动噪声:价格在买一和卖一之间来回跳,尤其是流动性差的品种,跳得你眼花。
- 订单簿深度波动:挂单量忽大忽小,有时候几秒钟内变化几十倍。
我的经验:我在做股指期货订单簿分析时,发现微观结构噪声在开盘和收盘前特别严重。那段时间的订单簿数据,我建议直接打折扣使用。
举个例子,你看下面这段代码,能直观感受到微观结构噪声的「威力」:
# 模拟微观结构噪声
import numpy as np
# 真实价格信号
true_price = 100.0 + np.cumsum(np.random.randn(1000) * 0.01)
# 微观结构噪声:买卖价差导致的随机跳动
noise = np.random.choice([-0.02, 0, 0.02], size=1000, p=[0.3, 0.4, 0.3])
# 观测到的价格 = 真实价格 + 噪声
observed_price = true_price + noise
# 你看,观测价格比真实价格多了多少毛刺
print(f"真实价格波动: {np.std(np.diff(true_price)):.4f}")
print(f"观测价格波动: {np.std(np.diff(observed_price)):.4f}")
2.2 跳跃与异常值
跳跃噪声,是订单簿数据里最「要命」的一种。
为什么?因为它不是随机波动,而是突然的、大幅的跳变。这种跳变往往来自:
- 大单冲击:突然有人砸了一笔大单,价格瞬间跳了好几个档位。
- 数据错误:交易所数据推送偶尔会出问题,比如价格少了一个小数点。
- 流动性枯竭:某个价位上没人挂单,价格直接跳到下一个有挂单的位置。
避坑指南:我曾经因为没处理好异常值,导致策略在回测中「捡漏」了很多不存在的套利机会。实盘时这些机会根本不存在,白白亏了手续费。记住:异常值必须识别并处理,不能直接扔进模型。
识别跳跃噪声,我常用一个简单的方法——看价格变化是否超过N个标准差:
def detect_jumps(price_series, threshold=3):
"""
检测价格序列中的跳跃噪声
threshold: 标准差倍数阈值
"""
returns = np.diff(price_series) / price_series[:-1]
mean = np.mean(returns)
std = np.std(returns)
jump_indices = np.where(np.abs(returns - mean) > threshold * std)[0]
return jump_indices
# 使用示例
jumps = detect_jumps(observed_price, threshold=4)
print(f"检测到 {len(jumps)} 个跳跃点")
2.3 买卖盘不平衡
买卖盘不平衡,这个我得多说两句。
很多人以为订单簿上的买量和卖量是「真实」的供需信号。其实不然——挂单可以随时撤掉,你看到的买一可能下一秒就没了。
买卖盘不平衡带来的噪声,主要体现在:
- 虚假挂单:有人挂大单但不成交,只是为了制造假象。
- 订单流不对称:买单和卖单的到达速度不一样,导致价格偏移。
- 深度分布不均:某个价位上挂单特别多,但其他价位很稀疏。
我习惯用一个指标来衡量买卖盘不平衡——订单簿斜率。它比单纯的买卖量比更稳定:
def order_book_slope(bids, asks, levels=5):
"""
计算订单簿斜率
bids: 买单价格和数量 [(price, volume), ...]
asks: 卖单价格和数量 [(price, volume), ...]
"""
bid_prices = np.array([b[0] for b in bids[:levels]])
bid_volumes = np.array([b[1] for b in bids[:levels]])
ask_prices = np.array([a[0] for a in asks[:levels]])
ask_volumes = np.array([a[1] for a in asks[:levels]])
# 买单斜率:价格越低,量越大
bid_slope = np.polyfit(bid_prices, bid_volumes, 1)[0]
# 卖单斜率:价格越高,量越大
ask_slope = np.polyfit(ask_prices, ask_volumes, 1)[0]
return bid_slope, ask_slope
关键点:买卖盘不平衡噪声,本质上是「信息不对称」的体现。你看到的订单簿,是别人想让你看到的。
2.4 数据采样频率的影响
采样频率这个问题,我纠结了很久。
你想想看,采样频率越高,数据越「真实」吗?不一定。高频采样会引入更多的微观结构噪声。采样频率太低,又会丢失重要的市场信息。
我做过一个对比实验,结果很有意思:
| 采样频率 | 噪声水平 | 信号保留度 | 适用场景 |
|---|---|---|---|
| Tick级(每笔) | 极高 | 100% | 高频做市 |
| 1秒 | 高 | ~95% | 高频趋势 |
| 10秒 | 中等 | ~80% | 日内交易 |
| 1分钟 | 低 | ~60% | 中低频策略 |
你看这个表,采样频率和噪声水平是正相关的。但信号保留度是负相关的。怎么选?
我个人习惯是:先高频采样,再降噪,最后重采样到目标频率。这样既保留了高频信息,又控制了噪声。
一个小技巧:如果你做的是中低频策略(比如持仓几小时),直接用1分钟数据就够了。别去碰tick级数据,那是给自己找麻烦。
最后总结一下这章的核心:
订单簿噪声不是「有」或「没有」的问题,而是「多少」和「怎么处理」的问题。微观结构噪声、跳跃异常值、买卖盘不平衡、采样频率——这四个来源,你每处理一个,信号质量就提升一截。
下一章,我会讲具体的降噪方法。但在这之前,我建议你先把自己的数据拿出来,看看这四种噪声分别占多大比例。知己知彼,才能对症下药。
本章要点:
- 微观结构噪声来自交易机制的离散性
- 跳跃异常值必须识别并处理,不能直接使用
- 买卖盘不平衡反映的是信息不对称
- 采样频率的选择是噪声与信号的权衡