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 我的异常值检测流程
- 3σ原则:计算基差序列的均值和标准差,超出均值±3倍标准差的值标记为异常。
- IQR方法:用四分位距(Q3 - Q1),超出Q1-1.5IQR或Q3+1.5IQR的值标记为异常。
- 人工复核:标记出来的异常值,我会去核对当天的市场新闻。如果是交割日或重大政策发布,那这个异常可能是真实的,保留;如果是数据源问题,直接剔除。
注意:不要机械地删除所有异常值。我曾经在铁矿石基差上,用3σ删掉了一堆「异常值」,结果发现那些正是临近交割日的真实基差收敛信号。删完之后,模型回测效果变差了。所以——异常值要「处理」而不是「删除」,该保留的保留,该修正的修正。
3.4 缺失值填充:别让NaN卡住你的模型
缺失值在基差数据里太常见了。节假日、数据源断更、某个合约暂停交易……都会导致缺失。
3.4.1 填充方法对比
| 方法 | 原理 | 优缺点 |
|---|---|---|
| 前向填充(ffill) | 用上一个有效值填充 | 简单,但会引入滞后 |
| 后向填充(bfill) | 用下一个有效值填充 | 会引入未来信息,回测时慎用 |
| 线性插值 | 用前后两个值做线性拟合 | 平滑,适合缓慢变化的基差 |
| 模型预测填充 | 用ARIMA等模型预测缺失值 | 精度高,但计算量大 |
我个人的经验是——日频基差用前向填充就够了。为什么?因为基差本身有自相关性,今天的基差跟昨天的基差差别不会太大。前向填充引入的误差,远小于模型预测填充带来的过拟合风险。
小技巧:如果你用的是Tushare,拉数据时可以直接用adj=‘qfq’参数做复权处理,这样能减少一部分因分红送股导致的缺失值。嗯,这个小细节很多人不知道。
3.5 基差数据清洗的完整流程(SVG流程图)
下面这张图,是我自己总结的基差数据清洗流程。每次做新品种之前,我都会按这个步骤走一遍。
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