第三章 季节性数据获取:国内外数据源介绍与数据库构建

做跨品种套利,数据是命根子。

这话我常说,也常被新人当成废话。直到有一次,我花了整整两周搭建的套利模型,回测曲线漂亮得像教科书,结果一上实盘就崩了。查来查去,问题出在数据上——两个品种的结算价时间戳差了15分钟,我根本没发现。

嗯,从那以后,我对数据源的选择和清洗,再也不敢马虎了。

3.1 国内外主流数据源:各有各的脾气

先说说我这些年用过的数据源。每个都有它的长处,也都有坑。

3.1.1 Wind(万得)——国内量化标配

说实话,Wind在国内金融数据这块,地位确实稳。我个人习惯把它当作主力数据源,尤其是做商品期货跨品种套利时,Wind的日频数据质量相当不错。

  • 覆盖范围:国内四大期货交易所(上期所、郑商所、大商所、中金所)全品种,包括主力合约、连续合约、指数合约
  • 数据频率:Tick、分钟、日、周、月,基本够用
  • 获取方式:Wind API(Python/C++/Excel),我常用WindPy库
  • 坑在哪:节假日数据对齐有时会出问题。比如春节前后,国内休市但外盘还在交易,Wind的连续合约可能会跳空。我踩过这个坑,后来加了手动校验逻辑。
小技巧:用Wind获取主力合约时,建议用"主力合约连续"代码(比如RB.SHF),而不是自己拼接。Wind内部已经做了换月处理,省心很多。

3.1.2 Bloomberg——国际品种的权威来源

做跨品种套利,尤其是涉及内外盘联动(比如沪铜与LME铜、豆粕与CBOT大豆),Bloomberg几乎是绕不开的。

  • 优势:数据历史长、质量高、字段全。我记得有一次需要回溯2005年的LME铜库存数据,Bloomberg轻松搞定
  • 获取方式:Bloomberg Terminal + API(blpapi Python库)
  • 注意:Bloomberg的合约代码规则比较特殊,比如"CO1 Comdty"代表铜连续合约。刚开始用的时候,我经常搞混代码格式,后来干脆写了个映射表。
警告:Bloomberg的Tick数据是按交易所原始时间戳存储的,没有做时区转换。如果你同时拉取国内夜盘和LME日盘数据,一定要手动对齐时区。我曾经因为忽略这个,导致价差序列出现了虚假的"跳空"。

3.1.3 交易所官网——免费但费手

说实话,交易所官网的数据是免费的,但获取起来确实麻烦。我一般只在两种情况下用:一是验证商业数据源的准确性,二是做小规模研究不想花钱。

交易所 官网地址 可用数据 获取方式
上期所 www.shfe.com.cn 日行情、持仓、仓单 手动下载/爬虫
郑商所 www.czce.com.cn 日行情、仓单、交割数据 手动下载/爬虫
大商所 www.dce.com.cn 日行情、持仓、交割 手动下载/爬虫
中金所 www.cffex.com.cn 股指期货、国债期货 手动下载/爬虫
个人经验:交易所官网的数据格式经常变。我去年写的一个爬虫,因为郑商所改了一次页面结构,直接废了。建议定期检查,或者用商业数据源兜底。

3.2 数据清洗与对齐:90%的时间花在这

你想想看,数据源拿回来,直接就能用吗?

太天真了。我做过统计,一个完整的跨品种套利项目,数据清洗和对齐至少占掉90%的时间。剩下的10%才是建模和回测。

3.2.1 常见的数据脏问题

  • 缺失值:节假日、交易所故障、数据源漏发。我遇到过某天LME铜的收盘价全部为0,原因是Bloomberg那天的数据推送出了问题
  • 异常值:价格跳空、成交量突变。比如某天螺纹钢的成交量突然放大10倍,后来发现是数据源把"手"和"吨"搞混了
  • 时间戳不一致:国内夜盘是21:00-23:00(或次日02:30),LME是北京时间16:00-次日01:00。两个品种的收盘时间不同,直接对齐会出问题
  • 合约换月:主力合约切换时,价格会有跳空。如果不处理,套利信号会失真

3.2.2 数据对齐的核心逻辑

说白了,对齐就两件事:时间对齐和价格对齐。

时间对齐:我一般用"最近可用价格"原则。比如国内夜盘收盘是23:00,LME还在交易,那就用LME 23:00的实时价。如果LME在23:00没有交易(比如休市),就用前一个有效价格。

价格对齐:跨品种套利通常用"指数合约"或"连续合约"。我个人习惯用指数合约,因为它平滑了换月跳空。但要注意,指数合约是加权计算的,不同数据源的权重算法可能不同。

核心原则:数据对齐的精度,决定了套利信号的可靠性。宁可少一个数据点,也不要用一个错误的数据点。

3.3 构建季节性数据库:从数据到资产

数据清洗完了,怎么存?怎么用?

我建议建一个专门的季节性数据库。不是随便扔几个CSV文件就完事,而是要有结构、有索引、可复用的。

3.3.1 数据库结构设计

我常用的结构是这样的:

数据库:seasonal_arbitrage_db
├── 表1:daily_price(日行情)
│   ├── date(日期)
│   ├── symbol(品种代码)
│   ├── open(开盘价)
│   ├── high(最高价)
│   ├── low(最低价)
│   ├── close(收盘价)
│   ├── volume(成交量)
│   └── open_interest(持仓量)
├── 表2:seasonal_stats(季节性统计)
│   ├── symbol(品种代码)
│   ├── month(月份)
│   ├── avg_return(平均收益率)
│   ├── std_return(收益率标准差)
│   ├── win_rate(胜率)
│   └── sample_count(样本数)
└── 表3:pair_metrics(套利对指标)
    ├── pair_id(套利对ID)
    ├── date(日期)
    ├── spread(价差)
    ├── zscore(Z分数)
    └── signal(信号:1做多/ -1做空/ 0观望)
我的习惯:用SQLite做本地数据库,轻量、便携、不需要装服务器。如果数据量超过100GB,再考虑迁移到PostgreSQL。

3.3.2 数据入库流程

我一般按这个流程走:

  1. 数据拉取:每天定时从Wind/Bloomberg拉取增量数据
  2. 清洗校验:检查缺失值、异常值,标记可疑数据
  3. 对齐处理:按品种、按时间对齐,生成统一格式的日行情表
  4. 季节性计算:按月统计收益率、胜率等指标,写入seasonal_stats表
  5. 套利对计算:计算价差、Z分数,生成交易信号

3.3.3 一个简单的数据清洗示例

用Python写个简单的清洗函数,你感受一下:

import pandas as pd
import numpy as np

def clean_price_data(df):
    """
    清洗日行情数据
    df: 包含date, symbol, close, volume的DataFrame
    """
    # 1. 删除缺失值超过50%的列
    df = df.dropna(thresh=len(df)*0.5, axis=1)
    
    # 2. 填充少量缺失值(用前一个交易日数据)
    df['close'] = df['close'].fillna(method='ffill')
    
    # 3. 标记异常值(价格变动超过5个标准差)
    df['return'] = df['close'].pct_change()
    mean_ret = df['return'].mean()
    std_ret = df['return'].std()
    df['outlier'] = np.abs(df['return'] - mean_ret) > 5 * std_ret
    
    # 4. 删除异常值(或者标记后人工审核)
    df_clean = df[~df['outlier']].copy()
    
    return df_clean
注意:上面的代码只是示例。实际项目中,异常值不能简单删除。比如2015年股灾期间,很多品种的价格变动都超过了5个标准差,但那是真实的市场行为。我一般会加一个"人工审核"步骤,把标记的异常值打印出来,手动判断。

3.4 本章知识体系总览

说了这么多,我画个图帮你理一理思路。

季节性数据获取与数据库构建 数据源获取 数据清洗 数据对齐 数据源类型 • Wind(国内主力) • Bloomberg(国际品种) • 交易所官网(免费) 清洗内容 • 缺失值处理 • 异常值标记 • 合约换月处理 对齐要点 • 时间戳统一 • 时区转换 • 指数合约选择 季节性数据库构建 套利信号生成 → 回测 → 实盘

这张图把整个流程串起来了。从数据源获取,到清洗对齐,再到数据库构建,最后输出套利信号。每一步都有坑,但每一步也都有方法。

一句话总结:数据是套利的地基。地基不稳,楼盖得再高也得塌。花80%的精力在数据上,剩下的20%自然水到渠成。

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