4. 数据获取与清洗:从 Tushare/akshare 获取行情数据、数据缺失值处理、复权价格计算

量化回测的基石是高质量、连续、无偏的行情数据。本章聚焦于从国内主流数据源(Tushare Pro 与 akshare)获取原始数据,并完成清洗与复权处理,确保回测引擎输入的数据口径一致、逻辑正确。

4.1 数据源选型对比

特性 Tushare Pro akshare
接口类型 RESTful API(需 token) 直接 HTTP 请求 + 解析
数据质量 高(交易所授权合作) 中高(聚合多源)
复权支持 原生参数(qfq/hfq) 需手动计算或调用复权因子
频率覆盖 日/周/月/分钟 日/分钟/实时 tick
免费额度 积分制(基础 200 分) 无限制(开源)
适用场景 专业回测、因子研究 快速原型、实时数据
选型建议: 生产级回测优先使用 Tushare Pro 的复权接口;研究阶段或需要高频 tick 数据时,akshare 的免费特性更具优势。两者可互补使用。

4.2 数据获取流程

以下代码演示从 Tushare Pro 获取沪深 300 成分股日线数据,并同时展示 akshare 的等效调用方式。

4.2.1 Tushare Pro 获取日线

import tushare as ts
import pandas as pd

# 初始化(请替换为你的 token)
pro = ts.pro_api('your_tushare_token')

# 获取平安银行(000001.SZ)日线,前复权
df_ts = pro.daily(
    ts_code='000001.SZ',
    start_date='20230101',
    end_date='20231231',
    adj='qfq'  # 前复权
)

# 字段重命名与排序
df_ts.rename(columns={
    'trade_date': 'date',
    'vol': 'volume'
}, inplace=True)
df_ts['date'] = pd.to_datetime(df_ts['date'])
df_ts.sort_values('date', inplace=True)
df_ts.reset_index(drop=True, inplace=True)

print(df_ts.head())

4.2.2 akshare 获取日线

import akshare as ak

# 获取平安银行日线(未复权)
df_ak = ak.stock_zh_a_hist(
    symbol="000001",
    period="daily",
    start_date="20230101",
    end_date="20231231",
    adjust=""  # 空字符串表示未复权
)

# 字段标准化
df_ak.rename(columns={
    '日期': 'date',
    '开盘': 'open',
    '收盘': 'close',
    '最高': 'high',
    '最低': 'low',
    '成交量': 'volume',
    '成交额': 'amount'
}, inplace=True)
df_ak['date'] = pd.to_datetime(df_ak['date'])
df_ak.sort_values('date', inplace=True)
df_ak.reset_index(drop=True, inplace=True)

print(df_ak.head())
注意: akshare 返回的字段名为中文,需手动映射为英文标准字段。Tushare Pro 返回的字段名已是英文,但需注意 vol 代表成交量(手),amount 代表成交额(元)。

4.3 数据缺失值处理

行情数据常见的缺失场景包括:停牌日、新股上市前、退市后、节假日非交易日。处理策略需根据回测逻辑选择。

缺失类型 处理方法 适用场景
停牌日(无交易) 前向填充(ffill) 持仓市值计算、因子值延续
新股上市前 删除或填充 NaN 回测起始日之后才纳入样本
退市后 截断至最后交易日 避免未来信息泄露
非交易日(周末/节假日) 不填充,保持自然缺失 仅交易日对齐
# 示例:停牌日前向填充
df = df.set_index('date')
# 生成完整交易日日历
all_dates = pd.date_range(start='2023-01-01', end='2023-12-31', freq='B')
df = df.reindex(all_dates)

# 前向填充停牌日数据
df[['open', 'high', 'low', 'close', 'volume']] = df[[
    'open', 'high', 'low', 'close', 'volume'
]].fillna(method='ffill')

# 成交量缺失(停牌)填充为 0
df['volume'] = df['volume'].fillna(0)

# 删除仍为 NaN 的行(如上市前)
df.dropna(subset=['close'], inplace=True)
关键原则: 回测中绝不允许使用未来数据填充缺失值(如后向填充)。前向填充是唯一符合信息流的处理方式。

4.4 复权价格计算

复权是为了消除分红、送股、配股等事件对价格序列造成的非交易性跳变。回测必须使用复权价格,否则收益率计算会失真。

4.4.1 复权类型对比

类型 计算方式 回测适用性
前复权(qfq) 调整历史价格,使当前价格不变 最常用,收益率序列连续
后复权(hfq) 调整当前价格,使历史价格不变 适合长期走势分析
不复权 原始价格,存在跳空缺口 仅用于除权除息日分析

4.4.2 手动复权计算(以 akshare 为例)

当数据源未提供复权数据时,可通过复权因子自行计算。

# 获取复权因子(akshare)
factor_df = ak.stock_zh_a_adjust(symbol="000001", start_date="20230101", end_date="20231231")
# 假设 factor_df 包含 'date' 和 'adj_factor' 列

# 合并到行情数据
df = df.merge(factor_df[['date', 'adj_factor']], on='date', how='left')

# 前复权计算:最新复权因子为基准
latest_factor = df['adj_factor'].iloc[-1]
df['close_adj'] = df['close'] * df['adj_factor'] / latest_factor
df['open_adj']  = df['open']  * df['adj_factor'] / latest_factor
df['high_adj']  = df['high']  * df['adj_factor'] / latest_factor
df['low_adj']   = df['low']   * df['adj_factor'] / latest_factor

# 成交量不需要复权
df['volume_adj'] = df['volume']
验证方法: 复权后的价格序列应单调连续,除权除息日前后价格不会出现跳空。可绘制复权前后对比图进行目视检查。

4.5 数据清洗与复权流程总览

下图展示了从原始数据到回测可用数据的完整处理链路。

数据获取与清洗流程 数据获取 → 清洗 → 复权 → 回测引擎 数据源 Tushare Pro akshare 原始行情数据 缺失值处理 复权价格计算 回测引擎 关键检查点 • 无未来信息泄露 • 复权后价格连续 • 成交量未复权

4.6 数据口径校验清单

在将数据送入回测引擎前,建议逐项检查以下口径。

  • 时间对齐: 所有股票使用同一交易日历,避免因停牌导致长度不一致。
  • 字段统一: 强制使用 date、open、high、low、close、volume、amount 标准命名。
  • 复权标记: 在 DataFrame 的 attrs 或列注释中记录复权类型(qfq/hfq/none)。
  • 极端值过滤: 检查涨跌幅是否超过交易所限制(A 股主板 ±10%,科创/创业板 ±20%)。
  • 成交量单位: 确认是“股”还是“手”,回测中统一转换为“股”。
最佳实践: 将数据获取、清洗、复权封装为独立的 DataLoader 类,支持多数据源切换,并输出统一的 OHLCV 格式。这样回测引擎无需关心底层数据来源,只需消费标准接口。