第二章:金融数据基础——股票、指数、期货数据获取与清洗对齐

各位同学,欢迎来到第二章。说实话,做量化交易这么多年,我见过太多人一上来就研究各种高大上的策略,结果数据源都没搞定,回测跑出来全是幻觉。数据是量化模型的「米」,没有米,再好的厨子也做不出饭。

这一章,我们就来搞定数据获取与清洗。我会用 Tushare 和 AkShare 这两个国内最主流的库,带你走一遍完整流程。

2.1 数据源的选择:Tushare vs AkShare

先说说我的个人习惯。Tushare 是老牌选手,数据质量高,但部分接口需要积分。AkShare 是完全免费的,数据源来自东方财富、新浪财经等,更新快,但偶尔会有字段不一致的情况。

我建议你两个都装上。平时用 AkShare 做快速验证,正式回测用 Tushare 做交叉校验。嗯,这里有个坑——千万不要只依赖一个数据源。我曾经在实盘前发现 AkShare 某天的期货数据少了一条,幸好用 Tushare 补上了,不然策略信号就错了。

核心原则:数据源要冗余,清洗要严格,对齐要精确。

2.2 安装与基础配置

先装包,这个不用多说:

pip install tushare akshare pandas numpy

Tushare 需要注册获取 token,AkShare 直接调用即可。我个人习惯把 token 写在环境变量里,而不是硬编码在代码中:

import tushare as ts
import akshare as ak
import os

# Tushare 初始化
ts.set_token(os.getenv('TUSHARE_TOKEN'))
pro = ts.pro_api()

# AkShare 无需初始化,直接调用函数
小技巧:如果你只是学习,Tushare 的免费积分够用。但做项目的话,建议充点积分,省得被限流。

2.3 股票数据获取

股票数据是最基础的。我们拿贵州茅台(600519)来举例:

# Tushare 获取日线
df_ts = pro.daily(ts_code='600519.SH', start_date='20230101', end_date='20231231')

# AkShare 获取日线
df_ak = ak.stock_zh_a_hist(symbol="600519", period="daily", 
                           start_date="20230101", end_date="20231231", adjust="qfq")

你想想看,这两个返回的字段名不一样。Tushare 用 trade_date,AkShare 用 日期。这就是为什么我们要做统一清洗。

2.4 指数数据获取

指数数据,说白了就是大盘的体温计。沪深300(000300)是最常用的基准:

# Tushare 获取沪深300
df_index_ts = pro.index_daily(ts_code='000300.SH', start_date='20230101', end_date='20231231')

# AkShare 获取沪深300
df_index_ak = ak.stock_zh_index_daily(symbol="sh000300")

这里要注意,指数的复权方式和个股不同。指数本身已经考虑了分红,所以不需要前复权。我刚开始做的时候没注意这个,回测结果差了0.5%,排查了半天。

2.5 期货数据获取

期货数据比股票复杂,因为有主力合约切换的问题。以螺纹钢(RB)为例:

# Tushare 获取期货日线
df_fut_ts = pro.fut_daily(ts_code='RB9999.XSGE', start_date='20230101', end_date='20231231')

# AkShare 获取期货日线
df_fut_ak = ak.futures_main_sina(symbol="RB0")

注意 RB9999 是 Tushare 的主力合约代码,RB0 是 AkShare 的连续合约代码。两者逻辑略有不同,Tushare 按持仓量换月,AkShare 按成交量换月。我建议你两个都拉下来对比一下,看看换月点是否一致。

避坑指南:期货数据一定要检查是否有跳空。主力合约切换时,新旧合约之间可能有价差,这个价差如果不处理,会严重影响回测结果。我曾经因为这个原因,一个趋势策略的回测年化收益从15%变成了8%。

2.6 数据清洗:统一格式

数据拉下来之后,你会发现字段名、日期格式、缺失值处理都不一样。我们需要做三件事:

  1. 统一日期格式:全部转为 YYYY-MM-DD
  2. 统一字段名:比如 openhighlowclosevolume
  3. 处理缺失值:停牌日、节假日等
def clean_data(df, source='tushare'):
    """统一清洗函数"""
    if source == 'tushare':
        df = df.rename(columns={
            'trade_date': 'date',
            'vol': 'volume'
        })
        df['date'] = pd.to_datetime(df['date'])
    elif source == 'akshare':
        df = df.rename(columns={
            '日期': 'date',
            '开盘': 'open',
            '收盘': 'close',
            '最高': 'high',
            '最低': 'low',
            '成交量': 'volume'
        })
        df['date'] = pd.to_datetime(df['date'])
    
    # 按日期排序
    df = df.sort_values('date').reset_index(drop=True)
    # 只保留需要的列
    cols = ['date', 'open', 'high', 'low', 'close', 'volume']
    df = df[cols]
    return df

2.7 数据对齐:多品种时间序列

多因子模型需要多个品种的数据在同一个时间轴上。比如股票和指数,交易日不同,必须对齐。

我常用的方法是:

def align_data(df_list, freq='D'):
    """对齐多个DataFrame"""
    # 找到所有日期的并集
    all_dates = pd.DatetimeIndex([])
    for df in df_list:
        all_dates = all_dates.union(df['date'])
    
    # 创建完整的时间轴
    full_calendar = pd.date_range(
        start=all_dates.min(), 
        end=all_dates.max(), 
        freq=freq
    )
    
    # 对齐每个df
    aligned = []
    for i, df in enumerate(df_list):
        df_aligned = df.set_index('date').reindex(full_calendar)
        # 前向填充缺失值(比如停牌日)
        df_aligned = df_aligned.ffill()
        aligned.append(df_aligned)
    
    return aligned

你想想看,为什么用 ffill() 而不是 bfill()?因为停牌日的数据,用前一天的收盘价填充更合理。用后一天的数据填充,就相当于「预知未来」了,这在回测中是致命的。

2.8 知识体系总览

下面这张图,是我做这个章节时画的框架。它把数据获取、清洗、对齐的整个流程串起来了:

金融数据获取与处理流程 Tushare AkShare 其他数据源 数据获取:股票 / 指数 / 期货 数据清洗:统一字段 / 处理缺失值 / 日期格式化 数据对齐:多品种时间序列对齐 多因子择时模型数据基础

2.9 实战中的常见问题

最后,分享几个我踩过的坑:

  • 日期不一致:股票和期货的交易日不同,期货夜盘也算在第二天。对齐时一定要用交易日历,而不是自然日。
  • 复权问题:股票分红送股后,价格会跳空。一定要用前复权数据,否则回测会失真。
  • 数据延迟:Tushare 和 AkShare 的数据更新有延迟,实盘时建议用券商接口。
我的习惯:每次拉完数据,先画个简单的 K 线图看看,有没有异常值。肉眼扫一遍,比跑一百个统计检验都管用。

好了,这一章的内容就到这里。数据是量化模型的基石,基础打牢了,后面的路才好走。下一章我们会正式进入因子构建,到时候这些数据就派上用场了。


专注资料整理