4、基金持仓数据获取:使用Tushare/AkShare获取公募基金持仓数据、数据清洗与预处理
做图神经网络分析,第一步就是搞数据。
这话听起来简单,但我在实际项目中踩过的坑,十有八九都出在数据获取和清洗上。你想想看,模型再漂亮,喂进去的是脏数据,出来的结果能看吗?
这一章,我们就来聊聊怎么拿到公募基金的持仓数据,并且把它收拾得干干净净,为后续的图构建打好基础。
4.1 数据源选型:Tushare vs AkShare
国内能拿到基金持仓数据的接口,主流就两个:Tushare 和 AkShare。我个人习惯两个都备着,一个挂了换另一个,做工程嘛,得有冗余。
| 对比项 | Tushare | AkShare |
|---|---|---|
| 数据质量 | 高,字段规范 | 中等,偶尔有乱码 |
| 获取门槛 | 需要注册 + 积分 | 免费,pip 即用 |
| 更新频率 | 季度数据较稳定 | 依赖爬虫,有时延迟 |
| 适合场景 | 正式研究、论文 | 快速原型、教学 |
我的建议: 如果你只是跑通流程,用 AkShare 就够了。但如果是做严肃的量化分析,我建议你花点积分搞个 Tushare Pro 账号,数据干净很多。
4.2 使用 AkShare 获取基金持仓数据
我们先从 AkShare 开始,因为它最简单。你只需要一行 pip install akshare 就能搞定。
下面这段代码,是我在项目中常用的获取公募基金前十大持仓的脚本:
import akshare as ak
import pandas as pd
# 获取所有公募基金列表
def get_fund_list():
"""获取公募基金基本信息"""
fund_df = ak.fund_name_em()
return fund_df
# 获取单只基金的持仓数据
def get_fund_holdings(fund_code):
"""获取指定基金的持仓明细"""
try:
# 这里我习惯用 'quarter' 参数,按季度拿数据
df = ak.fund_portfolio_hold_em(symbol=fund_code, date="2024")
return df
except Exception as e:
print(f"获取 {fund_code} 失败: {e}")
return None
# 示例:拿一只明星基金的数据
fund_code = "000001" # 华夏成长
holdings = get_fund_holdings(fund_code)
print(holdings.head())
注意: AkShare 的接口有时会变。我曾经遇到过某次升级后,字段名从 'stock_name' 变成了 'name',导致下游代码全崩。建议每次运行前先打印一下列名,确认一下。
4.3 使用 Tushare 获取数据(更稳定)
如果你有 Tushare 的 token,我推荐用这个。数据质量确实高一个档次。
import tushare as ts
# 设置 token(记得换成你自己的)
ts.set_token('your_token_here')
pro = ts.pro_api()
# 获取基金持仓数据
def get_fund_holdings_tushare(fund_code, date="20240331"):
"""使用 Tushare 获取基金持仓"""
df = pro.fund_portfolio(ts_code=fund_code, date=date)
return df
# 获取基金列表
fund_list = pro.fund_basic(market='E')
print(fund_list.head())
这里有个小细节:Tushare 的日期格式是 YYYYMMDD,而且季度末的数据通常要等到下个月中旬才能拿到。我刚开始做的时候,总是着急查最新数据,结果发现接口返回空,后来才明白是数据还没发布。
4.4 数据清洗与预处理
数据拿到手,别急着用。先看看长什么样。我一般会做这几步:
4.4.1 缺失值处理
基金持仓数据里,最常见的缺失是「持仓市值」和「占净值比例」。有些基金只披露了股票名称,没披露比例。
def clean_holdings(df):
"""清洗持仓数据"""
# 删除关键字段为空的行
df = df.dropna(subset=['stock_name', 'hold_amount'])
# 填充比例字段:如果缺失,用 0 填充
df['hold_percent'] = df['hold_percent'].fillna(0)
# 去重:同一只基金在同一个季度可能重复披露
df = df.drop_duplicates(subset=['fund_code', 'stock_code', 'quarter'])
return df
核心原则: 宁可删掉一条数据,也不要让脏数据混进模型。图神经网络对噪声特别敏感,一条错误的边可能带偏整个社区发现的结果。
4.4.2 数据标准化
不同数据源的字段名不一样,我习惯统一成一套标准命名:
| 原始字段(AkShare) | 原始字段(Tushare) | 统一字段 | 说明 |
|---|---|---|---|
| fund_code | ts_code | fund_id | 基金代码 |
| stock_name | name | stock_name | 股票名称 |
| hold_amount | mkv | market_value | 持仓市值(元) |
| hold_percent | ratio | weight | 占净值比例(%) |
4.4.3 构建基金-股票关联矩阵
这一步是为图神经网络做准备。我们需要把数据转成「基金-股票」的二部图结构。
def build_edge_list(df):
"""构建边列表:基金 -> 股票"""
edges = []
for _, row in df.iterrows():
edges.append({
'source': row['fund_id'],
'target': row['stock_name'],
'weight': row['weight'] # 边权重就是持仓比例
})
return pd.DataFrame(edges)
# 使用示例
edge_df = build_edge_list(clean_df)
print(f"共构建了 {len(edge_df)} 条边")
4.5 本章知识体系
下面这张图,是我自己总结的数据获取与预处理流程。你可以把它当作一个检查清单:
4.6 避坑指南
最后,分享几个我踩过的坑:
- 数据延迟问题: 公募基金季报通常在季度结束后15个工作日内披露。你如果在4月1号查3月31号的数据,大概率是空的。别急,等两周。
- 基金代码格式: Tushare 的基金代码是 6 位数字 + ".OF",比如 "000001.OF"。AkShare 直接用 6 位数字就行。我一开始没注意这个,查了半天没数据。
- 持仓比例之和不为100%: 公募基金只披露前十大持仓,所以比例之和通常在20%-60%之间。这是正常的,别以为是数据错了。
- 股票停牌: 有些股票停牌了,市值还是按停牌前价格算。这在图里会形成一些「假连接」,建议在构建图之前,先过滤掉停牌股票。
一个小技巧: 我习惯把清洗后的数据存成 Parquet 格式,比 CSV 快很多,而且自带压缩。尤其是当你处理几千只基金、上万只股票的时候,这个差异非常明显。
好了,数据准备好了。下一章,我们就用这些干净的数据,来构建真正的图结构。到时候你会发现,前面这些清洗工作,每一分钟都是值得的。
公众号:蓝海资料掘金营,微信deep3321