3. 数据获取与预处理:期货行情数据源、数据清洗、缺失值处理、数据对齐

做量化风控,有一句老话我一直记着:「垃圾进,垃圾出」。模型再漂亮,数据一塌糊涂,结果就是白忙活。今天咱们聊聊期货行情数据的获取和预处理,这块我踩过的坑,比写过的代码还多。

3.1 期货行情数据源:从哪里拿数据?

我个人习惯把数据源分成三类:官方交易所、第三方数据商、开源数据。各有各的脾气。

数据源类型 代表 优点 缺点
官方交易所 上期所、大商所、郑商所、中金所 数据权威,无版权问题 接口复杂,历史数据不全,收费
第三方数据商 Wind、Tushare、RiceQuant、JoinQuant 数据清洗过,接口友好 收费或有限制,延迟可能高
开源数据 AKShare、yfinance(期货少) 免费,社区活跃 数据质量参差不齐,需自行校验

我在项目中遇到过最头疼的事——用开源数据做回测,结果发现某天的收盘价是前一天的复制粘贴。嗯,从那以后,我养成了一个习惯:任何数据源,到手先做一次「合理性校验」

3.2 数据清洗:脏数据长什么样?

数据清洗说白了就是「找茬」。期货行情数据常见的脏数据,我列几个典型的:

  • 价格异常:比如螺纹钢突然出现0.01元/吨的成交价,明显是输入错误。
  • 成交量/持仓量为负:这属于数据源bug,我见过不止一次。
  • 涨跌停板数据:涨停或跌停时,价格可能被「封死」,但成交量可能为0,这不算脏数据,但需要特殊处理。
  • 合约换月跳空:主力合约切换时,价格会跳空,这不是错误,但预处理时必须处理。

核心原则:不要轻易删除数据。先标记,再判断。我曾经因为删掉「看起来异常」的涨跌停数据,导致回测结果严重失真。

举个例子,我常用的清洗逻辑:

def clean_futures_data(df):
    # 1. 去除价格异常(比如价格 <= 0)
    df = df[df['close'] > 0]
    
    # 2. 去除成交量异常(比如成交量 < 0)
    df = df[df['volume'] >= 0]
    
    # 3. 标记涨跌停(价格不变但成交量极低)
    df['is_limit'] = (df['close'] == df['prev_close']) & (df['volume'] < 10)
    
    # 4. 去除重复索引(时间戳重复)
    df = df[~df.index.duplicated(keep='first')]
    
    return df

你想想看,如果不清洗,模型学到的可能是「价格可以为零」这种荒谬规律。那还做什么风控?

3.3 缺失值处理:补还是不补?

期货数据缺失的原因很多:节假日、合约不活跃、数据源断供。处理方式我分三种:

  • 向前填充(ffill):用上一个非缺失值填充。适用于非交易时段(比如夜盘收盘到日盘开盘)。
  • 线性插值:适用于短期缺失,比如某分钟数据丢失。
  • 直接删除:如果缺失比例太高(比如超过20%),我建议直接删掉该时间段。

我的经验:对于期货主力连续合约,缺失值往往出现在合约换月前后。这时候用ffill会引入「伪数据」,我一般会先检查换月日期,再决定填充策略。

代码示例:

# 向前填充(适用于非交易时段)
df.fillna(method='ffill', inplace=True)

# 线性插值(适用于分钟级数据)
df.interpolate(method='linear', inplace=True)

# 删除缺失比例过高的列
df.dropna(thresh=len(df) * 0.8, axis=1, inplace=True)

为什么会这样?因为期货市场有夜盘,日盘和夜盘之间数据是断开的。如果你用ffill,相当于把夜盘收盘价「复制」到日盘开盘,这明显不合理。我建议:按交易时段分组填充

3.4 数据对齐:多合约、多品种怎么对齐?

做组合风险管理,你不可能只盯着一个合约。螺纹钢、铁矿石、焦炭……它们交易时间、交易单位、最小变动价位都不一样。数据对齐,就是让它们「站在同一个时间点上」。

我常用的对齐方式:

  • 按时间戳对齐:以某个基准时间(比如交易所时间)为准,把其他数据对齐过来。
  • 重采样:把不同频率的数据统一到同一频率(比如把分钟数据重采样为日数据)。
  • 向前看/向后看:对于非同步数据,用「最近可用数据」填充。

注意:对齐时最容易犯的错误是「未来数据泄露」。比如你用当天的收盘价去对齐当天的开盘价,这在回测中是致命的。我建议:对齐时只使用历史数据,不要使用未来数据

代码示例:

# 假设有两个合约的数据:rb_continuous 和 i_continuous
# 按日对齐
aligned = pd.merge(rb_continuous, i_continuous, 
                   left_index=True, right_index=True, 
                   how='outer', suffixes=('_rb', '_i'))

# 向前填充缺失值(只使用历史数据)
aligned.fillna(method='ffill', inplace=True)

# 删除仍然缺失的行(比如两个合约都缺失)
aligned.dropna(inplace=True)

说白了,数据对齐就是「让不同品种的数据在时间轴上握手」。握不好手,模型就会「左右互搏」。

3.5 知识体系图:数据预处理全流程

下面这张图,是我自己总结的数据预处理流程。每次做新项目,我都会先过一遍这个流程。

期货行情数据预处理全流程 数据获取 数据清洗 价格异常检测 成交量/持仓量校验 缺失值处理 向前填充(ffill) 线性插值 数据对齐 按时间戳对齐 重采样 清洗后数据

这张图把整个流程串起来了。你想想看,从数据获取到最终可用,每一步都有坑。我刚开始做的时候,经常在「缺失值处理」这一步翻车——以为ffill万能,结果回测曲线漂亮得不像话,实盘一跑就崩。后来才明白:预处理不是技术问题,是认知问题

避坑指南:我曾经用开源数据做跨品种套利策略,结果发现两个品种的日期索引不一致——一个用「YYYY-MM-DD」,另一个用「YYYY/MM/DD」。对齐后数据全乱了。从那以后,我每次拿到数据,第一件事就是检查索引格式和时区。

好了,数据预处理这块就聊到这儿。记住一句话:花80%的时间处理数据,花20%的时间建模。这不是夸张,是血泪教训。


专注资料整理