数据获取与清洗:数据源选择与质量把控

做量化回测,第一步就是搞数据。这事听起来简单,但我在这个坑里摔过不止一次。数据不对,后面所有策略分析都是白搭——垃圾进,垃圾出,这话在量化领域尤其扎心。

今天咱们就聊聊数据获取和清洗那些事。我会把常用的数据源、清洗套路、质量检查方法都过一遍,顺便分享一些我踩过的坑。

数据源选择:三大主流渠道

国内做量化,常用的数据源就三个:Yahoo Finance、东方财富、Tushare。各有各的脾气,我一个个说。

数据源 覆盖范围 更新频率 免费程度 稳定性
Yahoo Finance 全球股票、ETF、指数 实时/日线 免费 中等(偶尔断连)
东方财富 A股、港股、基金 实时/分钟/日线 免费 较高
Tushare A股、期货、期权、基金 日线/分钟/实时 部分免费,高级需积分 高

Yahoo Finance:全球数据的好选择

我个人习惯用 yfinance 库来拉 Yahoo 的数据。代码简单,几行就能拿到历史行情。

import yfinance as yf

# 获取苹果股票数据
aapl = yf.download('AAPL', start='2020-01-01', end='2023-12-31')
print(aapl.head())

但这里有个坑——Yahoo 的数据偶尔会缺字段,尤其是调整收盘价。我记得有一次做回测,发现策略收益异常高,查了半天才发现是 Yahoo 的调整因子没更新,导致复权数据全是错的。

注意:Yahoo Finance 在国内访问不稳定,经常被墙。建议搭配代理使用,或者干脆别把它当主力数据源。

东方财富:A股数据最全

东方财富的数据接口,说白了就是 akshare 库。它把东方财富的网页接口封装好了,用起来挺顺手。

import akshare as ak

# 获取贵州茅台日线数据
stock_zh_a_hist_df = ak.stock_zh_a_hist(symbol="600519", period="daily", 
                                         start_date="20200101", end_date="20231231")
print(stock_zh_a_hist_df.head())

东方财富的数据更新快,基本收盘后几分钟就能拿到。但有个问题——它的字段命名比较随意,比如「日期」有时候叫 date,有时候叫 trade_date。你想想看,如果代码里写死了字段名,换一只股票可能就崩了。

小技巧:拉完数据后,先打印列名看一眼,确认字段名再往下写。别问我怎么知道的——我吃过这个亏。

Tushare:专业玩家的首选

Tushare 是我现在的主力数据源。它数据质量高,字段规范,而且支持分钟级数据。不过需要注册获取 token,高级接口要积分。

import tushare as ts

# 设置 token
ts.set_token('你的token')

# 初始化接口
pro = ts.pro_api()

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

Tushare 的数据结构很清晰,每个字段都有文档说明。但它的免费额度有限,一天只能调 200 次。如果你要拉全 A 股的历史数据,积分不够的话得等好几天。

我的建议:小规模回测用 Tushare 免费版就够了。如果要做全市场回测,可以考虑买点积分,或者用东方财富做补充。

数据清洗:去重、填充、对齐

数据拉回来,别急着用。原始数据通常有各种问题——重复行、缺失值、时间戳不对齐。我一般按三步走:去重、填充、对齐。

1. 去重

数据重复的原因很多:接口重试、网络波动、手动追加。重复数据会导致回测结果失真,尤其是计算收益率的时候。

# 按日期去重,保留第一条
df = df.drop_duplicates(subset=['trade_date'], keep='first')

# 重置索引
df = df.reset_index(drop=True)

我曾经遇到过一个问题:某只股票的数据重复了三天,导致移动平均线计算完全走样。嗯,从那以后我每次拉数据都会先跑一遍去重。

2. 填充缺失值

缺失值处理要分情况。如果是非交易日导致的缺失,直接向前填充就行。如果是数据本身缺失,那就得小心了。

# 向前填充(非交易日)
df = df.fillna(method='ffill')

# 如果开盘价缺失,用前一天的收盘价代替
df['open'] = df['open'].fillna(df['close'].shift(1))
注意:不要盲目填充。如果连续缺失超过 5 个交易日,建议直接删除这段数据,或者标记为无效区间。

3. 时间对齐

多只股票一起回测时,时间戳必须对齐。A 股有涨跌停、停牌、退市等情况,不同股票的交易日期可能不一样。

# 生成完整交易日历
trade_cal = ak.tool_trade_date_hist_sina()

# 对齐到交易日历
df_aligned = df.set_index('trade_date').reindex(trade_cal['trade_date'])
df_aligned = df_aligned.fillna(method='ffill')

说白了,对齐就是让所有股票站在同一条时间线上。不然你算出来的组合收益,时间点都对不上。

数据质量检查:别让脏数据毁了回测

数据清洗完,还得检查质量。我一般做这几项检查:

  • 边界值检查:价格不能为负,成交量不能为 0(停牌除外)
  • 跳空检查:单日涨跌幅超过 20% 的,大概率是数据错误
  • 复权一致性:前复权和后复权的价格序列应该逻辑自洽
  • 时间连续性:交易日之间不能有超过 5 天的间隔(长假除外)
# 简单质量检查函数
def check_data_quality(df):
    issues = []
    
    # 检查负价格
    if (df['close'] < 0).any():
        issues.append('存在负价格')
    
    # 检查跳空
    returns = df['close'].pct_change()
    if (returns.abs() > 0.2).any():
        issues.append('存在超过20%的跳空')
    
    # 检查缺失
    if df.isnull().sum().sum() > 0:
        issues.append('存在缺失值')
    
    return issues
经验之谈:我每次跑回测前,都会先跑一遍质量检查脚本。发现问题就标记出来,不急着修。有时候数据本身没错,是股票发生了拆股或分红,需要单独处理。

数据获取与清洗流程图

下面这张图概括了从数据获取到清洗完成的完整流程。你可以把它贴在工位旁边,每次做数据准备时对照着走一遍。

数据获取与清洗流程图 数据获取与清洗流程图 数据源选择 Yahoo Finance 东方财富 Tushare 数据获取(API调用) 数据清洗 去重 填充 对齐 数据质量检查 通过 → 入库 不通过 → 重新清洗 关键说明 1. 数据源选择:根据需求选 Yahoo:全球数据,但不稳定 东方财富:A股全,更新快 Tushare:专业,质量高 2. 数据清洗三步走 去重:drop_duplicates 填充:ffill / 逻辑填充 对齐:reindex 到交易日历 3. 质量检查要点 边界值、跳空、复权一致性 时间连续性 4. 常见坑 Yahoo 被墙、字段名不一致 缺失值盲目填充、停牌未处理 5. 我的习惯 每次回测前跑质量检查脚本 发现问题先标记,不急着修

避坑指南:我踩过的那些坑

做数据准备这些年,我踩过的坑能写一本书。挑几个典型的说说:

  • 复权数据不一致:我曾经用 Yahoo 的前复权数据和东方财富的后复权数据做对比,结果发现价格差了好几个点。后来才意识到,不同数据源的复权算法不一样,不能混用。
  • 停牌数据没处理:某只股票停牌一个月,复牌后直接跌停。我的回测模型没处理停牌,结果把停牌期间的缺失值填充成了前一天的收盘价,导致策略信号完全错位。
  • 节假日对齐问题:A 股和港股的节假日不一样。我做跨市场回测时,没对齐交易日历,结果两个市场的日期对不上,组合收益算出来是乱的。
核心原则:数据准备花的时间,永远值得。宁可多花一天清洗数据,也不要花一周调试一个基于脏数据的策略。