第三章 数据获取与清洗:金融数据API与数据预处理实战

做量化投研的朋友都知道,数据是策略的命根子。我见过太多人花几个月写策略,结果数据源一换,回测结果直接翻车。今天我们就聊聊数据获取与清洗这档子事。

3.1 金融数据API的选择:Tushare vs AKShare

国内金融数据API,目前最主流的就两个:Tushare和AKShare。我个人两个都用过,各有千秋。

核心观点:Tushare适合专业机构,AKShare适合快速原型开发。但别急着选,先看你的需求。

Tushare Pro

Tushare是老牌数据平台了。我2018年刚开始做量化时就在用。它的特点是数据质量高,字段规范,但需要积分才能获取高级数据。

# 安装与初始化
pip install tushare

import tushare as ts
# 设置token(需要注册获取)
ts.set_token('your_token_here')
pro = ts.pro_api()

# 获取日线数据
df = pro.daily(ts_code='000001.SZ', start_date='20230101', end_date='20231231')
print(df.head())

我的经验:Tushare的token一定要放在配置文件里,别硬编码。我曾经把token直接写在代码里,结果上传到GitHub被公开了...嗯,那之后我养成了用环境变量的习惯。

AKShare

AKShare是后来崛起的开源项目。它最大的优势是免费、接口丰富。但数据格式偶尔会变,需要留意更新。

# 安装
pip install akshare

import akshare as ak

# 获取A股日线数据
df = ak.stock_zh_a_hist(symbol="000001", period="daily", 
                        start_date="20230101", end_date="20231231", adjust="qfq")
print(df.head())
对比维度 Tushare AKShare
数据质量 高,字段规范 中等,偶有变动
费用 积分制,部分收费 完全免费
接口稳定性 稳定 偶尔更新
适合场景 生产环境 研究原型

3.2 数据清洗:从原始数据到可用数据

数据拿到手,别急着用。你想想看,原始数据里什么妖魔鬼怪都有。我遇到过停牌数据、复权数据没对齐、甚至还有日期格式不统一的问题。

3.2.1 数据对齐

做行业轮动,最怕的就是时间戳对不上。比如你拿到的银行板块数据和地产板块数据,交易日可能不一样。

import pandas as pd

# 假设有两个板块的数据
bank_data = pd.DataFrame({
    'date': ['2023-01-03', '2023-01-04', '2023-01-05'],
    'bank_return': [0.01, -0.02, 0.03]
})

estate_data = pd.DataFrame({
    'date': ['2023-01-03', '2023-01-04', '2023-01-05', '2023-01-06'],
    'estate_return': [0.02, -0.01, 0.01, -0.005]
})

# 对齐到共同交易日
merged = pd.merge(bank_data, estate_data, on='date', how='inner')
print(merged)

避坑指南:我曾经用'outer'合并,结果回测时发现有些日期只有单边数据,策略信号直接乱了。记住,行业轮动必须用'inner'对齐。

3.2.2 缺失值处理

缺失值在金融数据里太常见了。停牌、节假日、数据源问题都会导致缺失。处理方式有三种:

  1. 向前填充(ffill):用上一个交易日的数据填充。适合价格数据。
  2. 向后填充(bfill):用下一个交易日的数据填充。适合某些财务指标。
  3. 插值法:线性插值,适合连续型指标。
# 模拟有缺失的数据
import numpy as np

data = pd.DataFrame({
    'date': pd.date_range('2023-01-01', periods=10, freq='D'),
    'price': [100, np.nan, 102, 103, np.nan, 105, 106, np.nan, 108, 109]
})

# 向前填充
data['price_ffill'] = data['price'].ffill()

# 线性插值
data['price_interp'] = data['price'].interpolate()

print(data)

我的建议:对于收益率数据,千万别用插值。收益率是离散事件,插值会引入未来信息。我一般用ffill,然后标记哪些是填充的,回测时单独分析。

3.3 数据清洗的完整流程

好了,我们把上面这些串起来。一个完整的数据清洗流程应该是这样的:

def clean_market_data(raw_data):
    """
    清洗市场数据
    """
    # 1. 去除重复行
    data = raw_data.drop_duplicates()
    
    # 2. 排序
    data = data.sort_values('date')
    
    # 3. 处理缺失值
    data['return'] = data['return'].ffill()
    
    # 4. 去除异常值(比如涨跌幅超过20%)
    data = data[data['return'].between(-0.2, 0.2)]
    
    # 5. 重置索引
    data = data.reset_index(drop=True)
    
    return data

核心原则:清洗数据时,宁可少用,不可错用。一条错误数据可能毁掉整个回测。

3.4 知识体系总览

下面这张图,是我做数据清洗时脑子里始终绷着的一根弦。它把整个流程串起来了:

数据获取与清洗核心流程 金融数据API 数据获取 Tushare AKShare 数据清洗与对齐 去重 对齐 缺失值处理 ffill bfill 插值

这张图把整个流程分成了三层:数据源层、清洗层、缺失值处理层。每一层都有对应的处理策略。我个人习惯在代码里用函数封装每一层,这样调试起来方便。

3.5 实战中的坑与技巧

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

  • 数据源切换:别在策略运行中切换数据源。我试过,结果两个API的复权方式不一样,回测结果差了10%。
  • 缓存机制:每次请求都去拉数据太慢了。我一般用本地SQLite缓存,每天更新一次。
  • 数据校验:清洗完后,一定要做一次校验。比如检查收益率是否在合理范围内,日期是否连续。

重要提醒:千万别相信任何数据源是100%正确的。我见过Tushare和AKShare对同一只股票的同一天数据有差异。所以,交叉验证是必须的。

好了,数据获取与清洗就聊到这里。记住,数据质量决定了策略的上限。花80%的时间在数据上,剩下20%写策略,这个比例我觉得很合理。

专注资料整理