4. 多品种数据获取与清洗
CTA多品种实盘交易的第一道工程门槛,不是策略信号,而是数据。数据质量直接决定回测是否可信、实盘是否可复现。本节围绕“取数—合成—拼接—清洗—对齐”五个环节,建立一套可落地的多品种数据流水线。
一、数据源选择:先定用途,再定来源
数据源没有绝对优劣,只有是否匹配用途。日内高频策略依赖tick级成交与盘口,趋势策略更依赖连续、复权后的日线或分钟线。选择时重点评估:覆盖品种范围、历史深度、更新频率、字段完整性、复权因子、费用与稳定性。
| 数据源类型 | 典型内容 | 适用场景 | 主要风险 |
|---|---|---|---|
| 交易所直连/授权行情 | tick、盘口、成交明细 | 日内、高频、实盘执行 | 接入成本高,历史需自建 |
| 商业数据商 | tick、分钟、日线、复权因子 | 多品种回测、研究 | 字段口径差异、费用 |
| 开源/社区数据 | 日线、部分分钟线 | 教学、原型验证 | 缺失多、复权不规范 |
| 自建采集 | 实时tick落库 | 实盘、私有因子 | 运维与存储压力 |
二、tick与K线合成:从原始流到可计算序列
tick数据是带时间戳的成交快照,K线是按时间窗口聚合的结果。合成核心是“时间对齐+聚合规则”。常见聚合字段包括开盘价、最高价、最低价、收盘价、成交量、持仓量。合成时必须明确:时间戳用交易所时间还是本地时间;窗口是左闭右开还是左开右闭;集合竞价与夜盘如何归属。
# 以1分钟K线合成为例(伪代码)
for tick in ticks:
bar_time = floor(tick.time, '1min')
if bar_time not in bars:
bars[bar_time] = init_bar(tick)
else:
bars[bar_time].high = max(high, tick.price)
bars[bar_time].low = min(low, tick.price)
bars[bar_time].close = tick.price
bars[bar_time].volume += tick.volume
bars[bar_time].oi = tick.open_interest
关键细节:夜盘与日盘属于同一交易日时,需按交易所交易日历归并;涨跌停时成交量可能为0,不能简单丢弃;集合竞价产生的开盘价要单独处理,避免污染连续竞价窗口。
三、主力合约拼接:构造连续可交易序列
期货合约有到期日,单合约无法长期回测。主力合约拼接的目标是:在任一交易日,选择当时流动性最好的合约,并在换月时平滑衔接。常见方法有“成交量/持仓量最大法”“固定换月法”“平滑价差法”。
换月处理是核心难点。若直接拼接价格,换月日会出现跳空,导致虚假盈亏。常用做法是记录换月价差,对历史价格做后复权调整,使序列连续。但要注意:复权后的价格不再等于真实成交价,回测成交与保证金计算需用原始合约价格。
四、缺失值处理与复权对齐
多品种数据常见缺失包括:节假日、停牌、涨跌停无成交、夜盘缺失、字段为空。处理原则是“先判断原因,再决定填充”。
- 节假日/非交易日:直接剔除,不填充。
- 涨跌停无成交:保留价格,成交量置0,标记状态。
- 短暂缺失:前向填充仅用于价格,成交量不填充。
- 长期缺失:该品种该时段不参与回测,避免引入偏差。
复权分为前复权与后复权。前复权适合观察当前价格,后复权适合回测收益计算。多品种对齐时,需统一时间轴:以交易日历为基准,将各品种K线按时间戳对齐,缺失品种用空值标记,而非用0填充。
# 多品种对齐示例
calendar = get_trading_calendar('2020-01-01', '2024-12-31')
aligned = {}
for symbol in symbols:
df = load_bars(symbol)
df = df.reindex(calendar) # 对齐交易日
df['close'] = df['close'].ffill() # 价格前向填充
df['volume'] = df['volume'].fillna(0)
aligned[symbol] = df
五、案例复盘:一次换月导致的回测偏差
某策略在螺纹钢上回测表现优异,实盘却持续亏损。排查发现:回测使用的主力连续合约未做换月价差调整,换月日价格跳空被策略误判为趋势信号,产生大量虚假开仓。修正后,换月日加入价差调整与状态标记,回测收益回归合理区间。
建议建立数据质量检查清单:每日检查缺失率、换月标记、复权因子、成交量连续性、多品种时间对齐一致性。只有数据链路稳定,CTA多品种实盘才具备可复现的基础。