4. 数据获取与清洗:多合约数据对齐、处理非同步交易、复权与换月处理
做跨品种套利,数据是地基。地基不稳,楼盖得再漂亮也得塌。
我见过太多人,策略回测曲线漂亮得像艺术品,一上实盘就崩。为什么?数据没处理好。今天咱们就把这块硬骨头啃下来。
4.1 多合约数据对齐:时间戳的战争
跨品种套利,你得同时盯着两个甚至多个合约。但问题来了——每个合约的交易时间不完全一样。
举个例子:螺纹钢和热卷,虽然都在上期所,但它们的盘中停牌、涨跌停触发时间可能有细微差别。更别说跨交易所的品种了,比如螺纹钢(上期所)和铁矿石(大商所),收盘时间都不一样。
核心原则:必须对齐到同一时间轴。我习惯用「最近可用的时间戳」作为对齐基准。
具体怎么做?我分享一个我常用的方法:
- 确定基准时间轴:通常选流动性更好的那个合约的时间作为基准。比如螺纹钢和热卷,我一般以螺纹钢的时间为准。
- 向前填充(Forward Fill):对于缺失的时间点,用最近的有效数据填充。注意,不能向后填充,那会引入未来信息。
- 剔除不匹配的边界:如果一个合约已经收盘,另一个还在交易,那这段时间的数据直接扔掉。别心疼,套利策略最怕的就是「一个动一个不动」。
# 伪代码示例:多合约数据对齐
def align_contracts(df1, df2, freq='1min'):
# 以df1的时间为基准
aligned = df1.copy()
# 重采样到统一频率
df2_resampled = df2.resample(freq).ffill()
# 合并
aligned = aligned.join(df2_resampled, how='inner', lsuffix='_a', rsuffix='_b')
# 剔除任何一方为NaN的行
aligned = aligned.dropna()
return aligned
我的小技巧:对齐时,我习惯把两个合约的成交量也带上。如果某个时间点成交量突然为0,那很可能是停牌或数据异常,直接剔除。
4.2 处理非同步交易:别被「假信号」骗了
非同步交易,说白了就是两个合约的成交时间不同步。你看到螺纹钢涨了1%,想开套利单,结果热卷其实早就涨了0.8%,只是数据还没传过来。
嗯,这种情况我踩过坑。有一次回测跑得特别好,实盘却连续亏损。后来一查,发现回测数据是「完美对齐」的,但实盘数据有延迟。那叫一个惨。
怎么处理?我总结了三个要点:
- 使用Tick级数据时,必须做「时间窗口匹配」:比如,把两个合约的Tick数据按100毫秒的时间窗口分组,取窗口内的最后一笔成交价。这样能最大程度减少非同步的影响。
- 警惕「闪崩」和「秒拉」:如果一个合约在1秒内价格波动超过3%,而另一个合约纹丝不动,这大概率是数据问题或极端行情。我建议直接跳过这个时间点。
- 引入「延迟容忍度」:在策略逻辑里,允许两个合约的信号有0.5秒到1秒的延迟。超过这个范围,就不开仓。
# 处理非同步交易的示例逻辑
def check_sync(price_a, price_b, timestamp, max_delay_ms=500):
# 检查两个价格的时间戳是否在允许的延迟范围内
if abs(timestamp_a - timestamp_b) > max_delay_ms:
return False # 不同步,跳过
return True
警告:千万不要用「插值法」去填补非同步的数据点。插值会制造出「完美但虚假」的价格关系,回测时好看,实盘时让你怀疑人生。
4.3 复权处理:让历史数据「说真话」
复权,很多人觉得是股票才需要做的事。其实期货也需要,只是方式不同。
期货的复权,主要针对的是「分红、配股、拆合」这些事件。虽然期货没有股票那种分红,但有些品种会有「现金结算」或「实物交割」带来的价格跳跃。
我个人习惯用「后复权」方式:
- 后复权:把历史价格按当前合约的乘数进行调整。这样看历史走势,不会出现因为合约规则变化导致的「假跳空」。
- 前复权:把当前价格往前调整。适合做技术分析,但做套利时容易失真。
举个例子:某个合约在交割日因为结算规则变化,价格突然跳了2%。如果不复权,你的套利模型会以为这是个套利机会,实际上只是规则导致的「假信号」。
我的经验:做跨品种套利,我建议两个合约都用「后复权」数据。这样价差序列更平滑,更容易识别真正的套利机会。
4.4 换月处理:别让主力合约「坑」了你
期货合约有到期日。你不能一直拿着一个合约做回测,因为主力合约会变。
换月处理,说白了就是「什么时候从旧合约切换到新合约」。这里有个坑:如果换月时间选得不对,价差序列会出现巨大的「假跳空」。
我常用的换月方法有两种:
| 方法 | 说明 | 适用场景 |
|---|---|---|
| 成交量换月法 | 当新合约的成交量连续3天超过旧合约时,切换 | 流动性好的品种(如螺纹钢、铁矿石) |
| 持仓量换月法 | 当新合约的持仓量超过旧合约时,切换 | 机构参与度高的品种(如股指期货) |
| 固定日期换月法 | 在交割月前第N个交易日固定切换 | 流动性一般的品种,或需要统一规则的场景 |
我个人最推荐「成交量换月法」。为什么?因为成交量代表市场的真实参与度。持仓量可能被大资金操纵,但成交量很难造假。
# 换月处理示例
def roll_contract(df, volume_col='volume'):
# 找到成交量最大的合约作为主力
main_contract = df.groupby('contract')['volume'].sum().idxmax()
# 只保留主力合约的数据
return df[df['contract'] == main_contract]
避坑指南:我曾经在换月时犯过一个错误——直接在新旧合约之间做「价格拼接」,没有做价差调整。结果回测的价差序列出现了巨大的「假套利空间」。后来我改用「价差连续法」:先计算新旧合约在换月日的价差,然后把旧合约的价格统一加上这个价差,再拼接。这样价差序列就连续了。
4.5 本章知识体系
下面这张图,是我自己总结的数据处理流程。你照着这个做,至少能避开80%的数据坑。
数据清洗这件事,说白了就是「把脏活累活干好」。你想想看,如果连数据都处理不好,策略再牛也是白搭。我个人习惯在数据清洗上花掉整个项目40%的时间,剩下的60%才是策略开发和回测。
嗯,这一章的内容就到这里。记住:数据干净,策略才能赚钱。