3. 数据源差异处理:回测数据 vs 实盘数据的对齐
做量化交易的朋友,十有八九都经历过这种崩溃:回测曲线漂亮得像艺术品,一上实盘就变成心电图。我当年第一次实盘时,三天亏掉回测半年的利润。后来复盘才发现,问题出在数据源上——回测和实盘用的根本不是同一套数据。
说白了,回测数据是「历史回放」,实盘数据是「现场直播」。两者之间的差异,比你想象的大得多。今天我就把这块硬骨头啃干净。
3.1 数据源差异的核心矛盾
回测数据有几个典型特征:
- 无延迟:历史数据是静止的,你什么时候取都一样
- 无缺失:交易所的历史数据通常很完整
- 无噪声:撮合记录是最终结果,没有中间状态
但实盘数据呢?
- 有延迟:行情推送有网络延迟、交易所处理延迟
- 有缺失:断线、重启、数据订阅失败都会丢数据
- 有噪声:异常Tick、重复数据、乱序到达
我见过最夸张的案例:有人用1分钟K线回测,年化收益30%。实盘时换成Tick级数据,直接变成负收益。为什么?因为回测时用的K线是「收盘价对收盘价」,而实盘交易是在「盘中某个瞬间」成交的。这两个价格可能差很远。
核心原则:回测数据必须模拟实盘数据的「不完美」特性。否则你的回测就是自欺欺人。
3.2 数据对齐的四个维度
我个人习惯从四个维度来做数据对齐。少一个都不行。
3.2.1 时间维度对齐
回测时我们通常用「交易所时间戳」。但实盘时,你的本地时间、券商时间、交易所时间可能都不一样。
举个例子:
- 回测数据:2024-01-01 09:30:00.000(交易所时间)
- 实盘数据:2024-01-01 09:30:00.123(本地收到时间)
这123毫秒的差异,在高频策略里足以让交易逻辑完全失效。
我的做法:
# 时间戳对齐示例
def align_timestamp(local_ts, exchange_ts, offset):
"""
local_ts: 本地收到时间
exchange_ts: 交易所原始时间
offset: 本地与交易所的时钟偏差
"""
# 优先使用交易所时间戳
if exchange_ts is not None:
return exchange_ts
# 否则用本地时间减去偏差
return local_ts - offset
小技巧:实盘时,我会在每天开盘前做一次时钟同步。用NTP服务器校准本地时间,然后记录与交易所时间的偏差。这个偏差在交易时段内基本稳定。
3.2.2 价格维度对齐
回测数据里的价格,通常是「成交价」。但实盘时你看到的是「买卖盘口价」。这两个价格能差多少?
我记得有一次做股指期货策略,回测时用成交价,滑点设了1个tick。实盘时发现,大单冲击下,实际成交价和盘口价差了3-5个tick。策略直接崩了。
对齐方法:
- 回测时不要用成交价,用「对手盘价格」
- 买开仓用卖一价,卖平仓用买一价
- 加上合理的滑点模型(固定滑点 + 比例滑点)
# 价格对齐示例
def get_trade_price(side, bid_price, ask_price, slippage=0.01):
if side == 'buy':
# 买入用卖一价 + 滑点
return ask_price * (1 + slippage)
else:
# 卖出用买一价 - 滑点
return bid_price * (1 - slippage)
3.2.3 成交量维度对齐
回测时我们假设「想买就能买到」。实盘呢?
- 小盘股:挂100手可能只成交20手
- 涨停板:根本买不到
- 流动性枯竭:买卖价差巨大
我曾在回测里用「成交量加权平均价」做成交判断,结果实盘时发现,策略信号出现时,盘口深度根本不够。后来我加了一个「成交量限制因子」:
# 成交量对齐示例
def check_liquidity(volume_needed, bid_volume, ask_volume, depth_levels=5):
"""
检查盘口深度是否足够
depth_levels: 检查几档盘口
"""
total_volume = sum(bid_volume[:depth_levels]) + sum(ask_volume[:depth_levels])
return volume_needed <= total_volume * 0.3 # 最多吃30%的盘口
注意:回测时一定要设置「成交量限制」。我建议用实盘历史数据统计出「平均盘口深度」,然后按这个深度来限制回测的成交数量。否则你的回测收益会被严重高估。
3.2.4 数据质量维度对齐
实盘数据有个特点:脏。常见问题包括:
- 重复数据:同一笔Tick推送两次
- 缺失数据:某段时间没有行情
- 异常数据:价格突然跳变(比如从100跳到1000)
- 乱序数据:后发的数据先到
回测时用的都是「干净数据」。但实盘时,你必须处理这些脏数据。我的做法是:
# 数据清洗示例
def clean_tick_data(tick):
# 去重:检查时间戳和价格是否重复
if is_duplicate(tick):
return None
# 异常检测:价格超过3个标准差
if abs(tick.price - mean_price) > 3 * std_price:
return None
# 乱序处理:时间戳比上一个还早
if tick.timestamp < last_timestamp:
return None
return tick
3.3 数据对齐的实战框架
下面这张图是我自己用的数据对齐框架。说白了就是「回测时模拟实盘的脏,实盘时模拟回测的净」。
3.4 避坑指南
做数据对齐这几年,我踩过的坑能写本书。挑几个最要命的说说:
坑一:回测用「后复权」数据,实盘用「前复权」
我曾经有个策略,回测时用后复权数据,收益曲线漂亮。实盘时发现,每次除权日策略就乱开仓。后来才发现,后复权数据把历史价格调整了,但实盘价格是真实的。两个数据源的价格根本对不上。
解决方案:回测和实盘用同一套复权规则。我个人习惯用「前复权」,因为实盘交易时看到的价格就是前复权后的。
坑二:忽略「数据频率」差异
回测用1分钟K线,实盘用Tick数据。这两个频率下的策略表现可能完全不同。我见过一个趋势策略,1分钟K线回测胜率60%,换成Tick数据后胜率降到40%。
原因:K线平滑了价格波动,Tick数据包含了更多噪声。策略在K线上看到的「趋势」,在Tick上可能只是随机波动。
坑三:不处理「数据断点」
实盘时,网络断连、交易所维护、数据订阅过期都会导致数据中断。回测时数据是连续的,但实盘不是。我建议在策略里加一个「数据健康检查」:如果超过N秒没有新数据,就暂停交易。
3.5 总结
数据源差异处理,说白了就是一句话:回测时把数据弄「脏」,实盘时把数据弄「净」。两者最终要在一个标准上对齐。
我个人习惯在回测框架里内置一个「数据污染模块」,自动给回测数据加上延迟、缺失、噪声。这样跑出来的结果,才敢放心上实盘。
嗯,数据对齐这块就聊到这儿。记住:回测赚的钱,有一半是数据差异给的。把这部分去掉,剩下的才是你的真实能力。