第二章:价差数据获取——交易所数据接口介绍、历史价差数据下载、实时价差数据订阅、数据清洗与对齐
做跨期套利,说白了就是跟价差打交道。但价差这东西不会自己从天上掉下来,你得自己去拿。我见过太多新手,策略写得天花乱坠,结果数据源一塌糊涂,回测跑得飞起,实盘直接翻车。嗯,数据获取是地基,地基不稳,楼盖得再高也得塌。
这一章,我就把数据获取这摊子事给你捋清楚。从交易所接口到历史数据下载,从实时订阅到数据清洗,咱们一步步来。
2.1 交易所数据接口:你得知道去哪拿数据
国内期货交易所主要有四家:上期所、大商所、郑商所、中金所。每家都有自己的数据接口,但说实话,直接对接交易所的CTP(综合交易平台)接口,是咱们做套利最常用的路子。
CTP接口的特点:
- 实时行情推送:Tick级数据,毫秒级延迟。做高频套利,这个必须得有。
- 历史数据查询:支持查询历史K线,但一般只保留最近几个交易日的数据。想拿更久远的?得自己存。
- 多合约支持:一个接口就能拿到所有合约的行情,省事。
除了CTP,还有FIX协议、各交易所的专线接口。不过对于咱们做跨期套利,CTP基本够用了。你想想看,价差套利对时效性要求高,CTP的Tick数据完全能满足。
2.2 历史价差数据下载:回测的命根子
做策略回测,没有历史数据就是巧妇难为无米之炊。我个人习惯把历史数据存在本地数据库里,用的时候直接查。
下载历史数据的步骤:
- 确定合约对:比如螺纹钢RB2410和RB2501,这两个合约的价差就是你要研究的对象。
- 选择时间范围:至少一年以上的数据,才能覆盖不同的市场环境。我建议至少两年。
- 调用接口下载:用CTP的QueryHistoryData接口,或者第三方API。
下面是一个用Python下载历史数据的示例,我用的是聚宽的API,简单易懂:
import jqdatasdk as jq
import pandas as pd
# 登录聚宽
jq.auth('你的账号', '你的密码')
# 定义合约对
contract1 = 'RB2410.XSGE'
contract2 = 'RB2501.XSGE'
# 下载日线数据
df1 = jq.get_price(contract1, start_date='2023-01-01', end_date='2024-01-01', frequency='daily')
df2 = jq.get_price(contract2, start_date='2023-01-01', end_date='2024-01-01', frequency='daily')
# 计算价差
spread = df1['close'] - df2['close']
# 保存到本地
spread.to_csv('rb_spread_historical.csv')
print('历史价差数据下载完成!')
2.3 实时价差数据订阅:实盘交易的生命线
实盘交易跟回测完全是两码事。回测你可以慢慢算,实盘你得实时盯着价差的变化。实时数据订阅,就是让你能第一时间捕捉到价差的异常波动。
实时订阅的实现方式:
- CTP行情订阅:通过MdApi订阅合约的Tick数据,然后自己计算价差。
- WebSocket推送:一些第三方服务商提供WebSocket接口,实时推送行情。
- 本地缓存+计算:把实时数据缓存到内存里,每次新数据来了就更新价差。
我个人比较推荐CTP的行情订阅,因为它延迟最低。下面是一个简单的CTP订阅示例(伪代码,实际CTP接口比较复杂):
# 伪代码示例
class CTPMdSpi:
def OnRtnDepthMarketData(self, pDepthMarketData):
# 收到行情数据
if pDepthMarketData.InstrumentID == 'RB2410':
self.last_price_1 = pDepthMarketData.LastPrice
elif pDepthMarketData.InstrumentID == 'RB2501':
self.last_price_2 = pDepthMarketData.LastPrice
# 计算实时价差
if self.last_price_1 and self.last_price_2:
spread = self.last_price_1 - self.last_price_2
print(f'实时价差: {spread}')
2.4 数据清洗与对齐:别让脏数据坑了你
数据拿到手,你以为就能直接用了吗?太天真了。我见过太多人,回测跑得漂亮,实盘一塌糊涂,最后发现是数据没对齐。价差数据最怕的就是时间戳对不上。
常见的数据问题:
- 缺失值:某个合约在某时刻没有成交,导致价差计算不出来。
- 异常值:比如价格突然跳到一个离谱的数字,可能是交易所的撮合错误。
- 时间戳不对齐:两个合约的Tick数据到达时间不一样,直接相减会算出错误的价差。
清洗与对齐的步骤:
- 去除异常值:用3倍标准差法,或者设定合理的价格范围。比如螺纹钢价格不可能突然从3000跳到10000。
- 填充缺失值:用前向填充(ffill)或者插值法。我个人习惯用前向填充,因为期货市场流动性好,缺失值很少。
- 时间对齐:把两个合约的数据按时间戳对齐。比如都对齐到最近的秒级时间戳,然后取同时刻的数据计算价差。
下面是一个数据清洗的Python示例:
import pandas as pd
import numpy as np
# 假设df1和df2是两个合约的Tick数据
df1 = pd.read_csv('rb2410_tick.csv')
df2 = pd.read_csv('rb2501_tick.csv')
# 1. 去除异常值
df1 = df1[(df1['price'] > 3000) & (df1['price'] < 5000)]
df2 = df2[(df2['price'] > 3000) & (df2['price'] < 5000)]
# 2. 时间戳对齐到秒
df1['time_sec'] = pd.to_datetime(df1['time']).dt.floor('S')
df2['time_sec'] = pd.to_datetime(df2['time']).dt.floor('S')
# 3. 合并数据
merged = pd.merge(df1, df2, on='time_sec', how='inner', suffixes=('_1', '_2'))
# 4. 计算价差
merged['spread'] = merged['price_1'] - merged['price_2']
# 5. 填充缺失值(如果有的话)
merged['spread'].fillna(method='ffill', inplace=True)
print('数据清洗完成,共{}条有效价差数据'.format(len(merged)))
知识体系总览
下面这张图,把数据获取的整个流程串起来了。你可以把它当作一个检查清单,做数据准备时对照着来:
数据获取这件事,看起来简单,但细节决定成败。我见过太多人在这上面栽跟头。记住一句话:数据质量决定了策略的天花板。数据是脏的,再好的策略也白搭。
好了,这一章的内容就到这里。数据拿到手、洗干净了,下一步就是分析价差的统计特征,看看它到底有没有规律可循。