4、财务数据获取:常用数据源(Wind、Choice、Tushare、AkShare等)、财务报表数据结构与清洗要点
财务因子选股的起点是可靠、完整、口径一致的财务数据。无论是构建多因子模型、进行单因子测试,还是执行量化回测,数据质量直接决定研究结论的可信度。本章围绕“从哪里拿数据”和“拿到数据后如何处理”两个核心问题展开,系统梳理国内主流财务数据源的特点、财务报表的数据结构,以及量化场景下必须掌握的数据清洗方法论。
4.1 常用数据源对比与选型
国内量化研究可用的财务数据源大致分为四类:商业终端(Wind、Choice)、开源Python接口(Tushare Pro、AkShare)、交易所/监管机构官方披露(巨潮资讯、上交所/深交所官网)、以及专业数据库(如CSMAR、RESSET)。不同数据源在覆盖度、更新时效、字段丰富度、使用成本和稳定性上差异显著。
| 数据源 | 覆盖范围 | 更新频率 | 接口方式 | 成本 | 适用场景 |
|---|---|---|---|---|---|
| Wind | A股/港股/美股/债券/商品 | 实时/日频 | API(Python/Excel) | 高(机构年费) | 机构级研究、高频因子、多资产配置 |
| Choice | A股为主,含港美股 | 日频/分钟 | API(Python/Excel) | 中(个人/机构) | 中小机构、个人量化 |
| Tushare Pro | A股/基金/期货/宏观 | 日频(积分制) | Python SDK | 低(积分制) | 个人研究、策略原型验证 |
| AkShare | A股/港股/美股/宏观/另类 | 日频(爬虫聚合) | Python(requests) | 免费 | 快速原型、教学演示、数据探索 |
| 巨潮资讯 | A股定期报告/临时公告 | 公告日同步 | PDF/网页 | 免费 | 原始报表核对、PDF解析 |
4.2 财务报表数据结构:宽表与长表
财务数据在数据库中通常以两种形式存储:宽表(Wide Format)和长表(Long Format)。理解这两种结构是数据清洗的前提。
宽表:每一行代表一个“股票-报告期”组合,每一列代表一个财务字段(如总资产、净利润、ROE)。这种结构便于直接计算因子,但新增字段时需要修改表结构。
# 宽表示例(DataFrame)
ts_code end_date total_assets net_profit roe
0 000001.SZ 20231231 5,320,000,000 138,000,000 12.5
1 000001.SZ 20230930 5,180,000,000 105,000,000 10.2
2 600000.SH 20231231 8,450,000,000 210,000,000 11.8
长表:每一行代表一个“股票-报告期-字段名-字段值”的记录。这种结构便于存储和扩展,但计算因子时需要进行透视(pivot)操作。
# 长表示例
ts_code end_date field_name field_value
0 000001.SZ 20231231 total_assets 5,320,000,000
1 000001.SZ 20231231 net_profit 138,000,000
2 000001.SZ 20231231 roe 12.5
在量化回测中,通常需要将长表转换为宽表。使用Pandas的pivot_table或groupby+unstack即可完成转换。需要注意:转换前必须确保ts_code和end_date的组合唯一,否则透视结果会出现重复列。
4.3 数据清洗核心要点
财务数据清洗是因子构建中最耗时、最易出错的环节。以下是五个必须处理的要点:
4.3.1 去重与唯一性校验
同一股票同一报告期可能出现多条记录(例如数据源重复推送、不同报表类型并存)。清洗第一步是确保(ts_code, end_date)唯一。若出现重复,需根据报表类型优先级(合并报表优先于母公司报表)或公告日期(取最新公告)进行去重。
# 去重示例:保留每个股票每个报告期最后一条记录
df = df.sort_values(['ts_code', 'end_date', 'ann_date'])
df = df.drop_duplicates(subset=['ts_code', 'end_date'], keep='last')
4.3.2 缺失值处理
财务数据缺失常见原因:新股上市不足一年、停牌期间未披露、数据源采集遗漏。处理策略需结合因子计算逻辑:
- 剔除:若某股票在因子计算区间内关键字段缺失,直接剔除该股票(适用于截面回归)。
- 填充:对于非关键字段,可使用行业均值或前值填充(需谨慎,避免引入偏差)。
- 标记:在因子值中增加缺失标记列,供后续模型处理。
4.3.3 极值处理(去极值)
财务数据常存在极端值(如净利润为负导致的ROE异常大、资产重组导致的资产规模突变)。常用方法:
- MAD法(中位数偏差):将超出中位数±3倍MAD的观测值替换为边界值。
- 分位数法:将超出1%和99%分位数的值截断。
- 标准差法:将超出均值±3倍标准差的观测值替换为边界值(对正态分布假设敏感)。
4.3.4 口径统一:合并报表 vs 母公司报表
同一公司可能同时披露合并报表和母公司报表。量化因子计算应统一使用合并报表数据,因为其反映了公司整体经营成果。若数据源同时提供两种口径,需设置筛选条件:report_type = '合并'。此外,还需注意新旧会计准则切换(如2007年新准则、2019年金融工具准则修订)导致的历史数据口径不一致,需进行可比性调整。
4.3.5 行业中性化与标准化
财务因子(如ROE、毛利率)受行业属性影响显著。在截面选股时,需进行行业中性化处理:将因子值对行业虚拟变量回归,取残差作为中性化后的因子值。标准化则常用Z-Score或百分位排名,使不同量纲的因子可比较。
# 行业中性化示例(使用statsmodels)
import statsmodels.api as sm
# 假设df包含因子值factor、行业代码industry
dummies = pd.get_dummies(df['industry'], prefix='ind')
X = sm.add_constant(dummies)
model = sm.OLS(df['factor'], X).fit()
df['factor_neutral'] = model.resid
4.4 实战案例:构建ROE因子数据管道
以下案例演示从Tushare Pro获取财务数据、清洗并构建ROE因子的完整流程。假设我们需要构建一个基于“扣非ROE”的选股因子,用于月度调仓回测。
背景与约束:回测区间为2018年1月至2023年12月,股票池为沪深300成分股。数据源使用Tushare Pro(积分2000+),日频更新。关键约束:避免未来函数——只能使用截至调仓日已公告的财务数据。
操作步骤:
- 获取股票列表:从Tushare获取沪深300成分股列表(
index_weight接口)。 - 获取财务数据:调用
fina_indicator接口,获取roe_dt(扣非ROE)字段,同时获取end_date、ann_date(公告日期)。 - 数据对齐:将财务数据与调仓日对齐——仅保留
ann_date <= 调仓日且end_date为最近报告期的记录。 - 清洗处理:去重、剔除缺失值、按行业(申万一级)进行MAD去极值、行业中性化、Z-Score标准化。
- 因子输出:生成月度因子值表,供回测框架调用。
# 关键代码片段(Tushare Pro)
import tushare as ts
pro = ts.pro_api('your_')
# 获取财务指标
df = pro.fina_indicator(ts_code='000001.SZ',
start_date='20180101',
end_date='20231231',
fields='ts_code,end_date,ann_date,roe_dt')
# 过滤:仅保留公告日期在调仓日之前的数据
df = df[df['ann_date'] <= '20230630']
结果与复盘:清洗后的ROE因子IC均值约为0.04,ICIR为0.35,分层回测显示多空组合年化收益差约8%。但发现两个问题:一是部分股票在年报披露前使用三季报数据,导致因子更新滞后;二是金融行业ROE普遍高于非金融行业,行业中性化后金融股因子值被压缩,需结合行业配置权重调整。
ann_date字段,并在调仓日进行动态过滤。
4.5 常见误区与注意事项
- 误区一:直接使用报告期数据而不考虑公告延迟。年报披露截止日为次年4月30日,若在1月调仓时使用上年年报数据,实际上该数据尚未公开,属于未来函数。
- 误区二:忽略报表类型差异。部分数据源默认返回母公司报表,若直接用于因子计算,会低估合并范围内的子公司贡献。
- 误区三:对财务数据直接进行标准化而不做行业中性化。这会导致因子值主要反映行业属性而非公司经营差异。
- 误区四:数据更新时未处理历史修正。上市公司可能对历史报表进行追溯调整(如会计差错更正),若数据源已更新,需同步刷新历史因子值。
财务数据获取与清洗是量化研究的“地基工程”。建议在正式回测前,建立一套自动化的数据质量校验流程,包括:字段完整性检查、报告期连续性检查、与原始公告抽样核对等。只有确保数据管道稳定可靠,后续的因子分析和策略回测才有意义。