3、基差数据的获取与清洗:数据源选择、对齐、异常值与缺失值处理

做基差交易,数据是命根子。这话一点都不夸张。

我见过太多人,模型建得漂漂亮亮,回测曲线跟艺术品似的,结果一上实盘就崩。为什么?数据源没选对,或者清洗没做到位。基差数据这东西,比单纯的价格数据要敏感得多——它本身就是两个价格相减的结果,任何一方的噪声都会被放大。

今天咱们就聊聊,怎么把基差数据这块硬骨头啃下来。

3.1 数据源选择:Wind vs Tushare

选数据源,说白了就是选「靠谱」和「灵活」之间的平衡点。

3.1.1 Wind(万得)

Wind是机构标配。我最早在私募做量化时,公司买的Wind终端,一年好几万。贵是贵,但数据质量确实稳。

  • 优点:数据覆盖全,期货、现货、指数、ETF都有。历史数据回溯时间长,字段标准化做得好。
  • 缺点:贵。而且API调用有频率限制,你写个循环批量拉数据,搞不好就被封IP了。
  • 适用场景:机构交易、正式策略、需要高频数据时。

3.1.2 Tushare(Tushare Pro)

Tushare是开源界的良心。我自己业余研究时,基本都用它。

  • 优点:免费(积分制,注册就有基础积分)。接口设计清晰,Python直接调,适合快速原型开发。
  • 缺点:数据偶尔有延迟,历史数据深度不如Wind。有些字段需要积分兑换。
  • 适用场景:个人研究、策略验证、教学演示。

我的建议:如果你做的是高频基差交易(比如日内套利),老老实实用Wind。如果是做日频以上的统计套利,Tushare完全够用。我自己的习惯是——Wind做生产环境,Tushare做研究环境。

3.2 数据对齐:基差计算的前提

基差 = 现货价格 - 期货价格。听起来简单吧?但坑全在「对齐」上。

你想想看,期货和现货的交易时间不一样。股指期货下午3点收盘,但现货ETF可能3点后还有集合竞价。如果你直接用收盘价相减,那基差里就混进了时间错位的噪声。

3.2.1 时间对齐的三种方式

对齐方式 说明 适用场景
精确时间戳对齐 取同一毫秒/秒级别的数据 高频交易、日内套利
收盘价对齐 取同一自然日的收盘价 日频策略、统计套利
滚动窗口对齐 用前一个有效值填充 数据缺失较多时

我个人习惯用「收盘价对齐」做日频基差。但要注意——有些品种的期货收盘时间比现货晚15分钟,这时候就得用期货的15:00价格去对现货的15:00价格,而不是直接用期货的15:15收盘价。

避坑指南:我曾经在螺纹钢基差上吃过亏。螺纹钢期货有夜盘,但现货没有。如果你把夜盘收盘价和第二天早上的现货价格去算基差,那基差里就包含了隔夜信息,跟你的交易逻辑对不上。我的做法是——只取日盘数据做基差计算,夜盘数据单独处理。

3.3 异常值处理:别让一个坏数据毁了整个模型

基差数据里的异常值,往往比价格数据更隐蔽。为什么?因为基差是两个价格相减,如果其中一个价格出现跳空,基差就会瞬间变大或变小。这种「假信号」最容易骗过模型。

3.3.1 常见的异常值类型

  • 数据录入错误:比如小数点错位,1000变成10000。
  • 市场极端事件:比如涨停跌停、交割日临近时的基差剧烈波动。
  • 数据源问题:比如Wind或Tushare某一天的数据推送延迟,导致价格被错误记录。

3.3.2 我的异常值检测流程

  1. 3σ原则:计算基差序列的均值和标准差,超出均值±3倍标准差的值标记为异常。
  2. IQR方法:用四分位距(Q3 - Q1),超出Q1-1.5IQR或Q3+1.5IQR的值标记为异常。
  3. 人工复核:标记出来的异常值,我会去核对当天的市场新闻。如果是交割日或重大政策发布,那这个异常可能是真实的,保留;如果是数据源问题,直接剔除。

注意:不要机械地删除所有异常值。我曾经在铁矿石基差上,用3σ删掉了一堆「异常值」,结果发现那些正是临近交割日的真实基差收敛信号。删完之后,模型回测效果变差了。所以——异常值要「处理」而不是「删除」,该保留的保留,该修正的修正。

3.4 缺失值填充:别让NaN卡住你的模型

缺失值在基差数据里太常见了。节假日、数据源断更、某个合约暂停交易……都会导致缺失。

3.4.1 填充方法对比

方法 原理 优缺点
前向填充(ffill) 用上一个有效值填充 简单,但会引入滞后
后向填充(bfill) 用下一个有效值填充 会引入未来信息,回测时慎用
线性插值 用前后两个值做线性拟合 平滑,适合缓慢变化的基差
模型预测填充 用ARIMA等模型预测缺失值 精度高,但计算量大

我个人的经验是——日频基差用前向填充就够了。为什么?因为基差本身有自相关性,今天的基差跟昨天的基差差别不会太大。前向填充引入的误差,远小于模型预测填充带来的过拟合风险。

小技巧:如果你用的是Tushare,拉数据时可以直接用adj=‘qfq’参数做复权处理,这样能减少一部分因分红送股导致的缺失值。嗯,这个小细节很多人不知道。

3.5 基差数据清洗的完整流程(SVG流程图)

下面这张图,是我自己总结的基差数据清洗流程。每次做新品种之前,我都会按这个步骤走一遍。

基差数据清洗流程图 数据源选择 Wind / Tushare 数据对齐 时间戳 / 收盘价 / 滚动窗口 异常值检测 3σ / IQR / 人工复核 缺失值填充 ffill / bfill / 线性插值 基差计算 现货 - 期货 关键要点 • 数据源:机构用Wind,个人用Tushare • 对齐:注意期货与现货交易时间差异 • 异常值:不要机械删除,要人工复核 • 缺失值:日频用ffill,高频用插值 • 基差计算:确保数据已对齐 • 最终输出:干净的基差时间序列

3.6 代码示例:用Python实现基差数据清洗

光说不练假把式。下面这段代码,是我实际项目中用过的清洗流程,你拿去改改就能用。

import pandas as pd
import numpy as np

def clean_basis_data(spot_df, futures_df, method='ffill'):
    """
    基差数据清洗函数
    :param spot_df: 现货DataFrame,包含'date'和'price'列
    :param futures_df: 期货DataFrame,包含'date'和'price'列
    :param method: 缺失值填充方法,默认前向填充
    :return: 清洗后的基差DataFrame
    """
    # 1. 数据对齐:按日期合并
    merged = pd.merge(spot_df, futures_df, on='date', suffixes=('_spot', '_futures'))
    
    # 2. 异常值检测(3σ)
    merged['basis'] = merged['price_spot'] - merged['price_futures']
    mean = merged['basis'].mean()
    std = merged['basis'].std()
    merged['outlier'] = np.abs(merged['basis'] - mean) > 3 * std
    
    # 3. 处理异常值:用前后均值替换
    merged.loc[merged['outlier'], 'basis'] = np.nan
    
    # 4. 缺失值填充
    if method == 'ffill':
        merged['basis'].fillna(method='ffill', inplace=True)
    elif method == 'linear':
        merged['basis'].interpolate(method='linear', inplace=True)
    
    # 5. 返回清洗后的数据
    return merged[['date', 'basis']]

# 使用示例
spot = pd.DataFrame({'date': pd.date_range('2024-01-01', periods=10), 'price': np.random.randn(10)*100 + 5000})
futures = pd.DataFrame({'date': pd.date_range('2024-01-01', periods=10), 'price': np.random.randn(10)*100 + 4950})
clean_data = clean_basis_data(spot, futures)
print(clean_data.head())

个人经验:这段代码里,我特意把异常值先转成NaN,再用填充方法处理。为什么?因为直接删除异常值会破坏时间序列的连续性,而转成NaN后统一填充,能保持序列长度不变。这个细节,是我在回测时发现「数据长度不一致导致模型报错」后才加上的。

3.7 小结

基差数据的获取与清洗,说白了就是三个字:稳、准、狠。

  • :选对数据源,别三天两头断更。
  • :对齐时间戳,别让时间错位坑了你。
  • :异常值和缺失值,该处理就处理,别手软。

数据干净了,模型才能跑得稳。下一节咱们聊基差回归模型的具体构建,到时候你会发现——数据清洗这一步做得好,后面建模简直事半功倍。


公众号:蓝海资料掘金营,微信deep3321