4. 数据获取与清洗:多合约数据对齐、处理非同步交易、复权与换月处理

做跨品种套利,数据是地基。地基不稳,楼盖得再漂亮也得塌。

我见过太多人,策略回测曲线漂亮得像艺术品,一上实盘就崩。为什么?数据没处理好。今天咱们就把这块硬骨头啃下来。

4.1 多合约数据对齐:时间戳的战争

跨品种套利,你得同时盯着两个甚至多个合约。但问题来了——每个合约的交易时间不完全一样。

举个例子:螺纹钢和热卷,虽然都在上期所,但它们的盘中停牌、涨跌停触发时间可能有细微差别。更别说跨交易所的品种了,比如螺纹钢(上期所)和铁矿石(大商所),收盘时间都不一样。

核心原则:必须对齐到同一时间轴。我习惯用「最近可用的时间戳」作为对齐基准。

具体怎么做?我分享一个我常用的方法:

  1. 确定基准时间轴:通常选流动性更好的那个合约的时间作为基准。比如螺纹钢和热卷,我一般以螺纹钢的时间为准。
  2. 向前填充(Forward Fill):对于缺失的时间点,用最近的有效数据填充。注意,不能向后填充,那会引入未来信息。
  3. 剔除不匹配的边界:如果一个合约已经收盘,另一个还在交易,那这段时间的数据直接扔掉。别心疼,套利策略最怕的就是「一个动一个不动」。
# 伪代码示例:多合约数据对齐
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%的数据坑。

跨品种套利数据清洗流程 原始多合约数据 时间对齐(向前填充 + 剔除边界) 非同步交易处理(时间窗口匹配) 复权处理(后复权) 换月处理(成交量换月法) 关键点: 剔除停牌时段 关键点: 容忍延迟0.5-1秒 关键点: 避免插值法 关键点: 价差连续拼接

数据清洗这件事,说白了就是「把脏活累活干好」。你想想看,如果连数据都处理不好,策略再牛也是白搭。我个人习惯在数据清洗上花掉整个项目40%的时间,剩下的60%才是策略开发和回测。

嗯,这一章的内容就到这里。记住:数据干净,策略才能赚钱。

专注资料整理