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 本章知识体系

下面这张图,是我自己总结的数据获取与预处理流程。你可以把它当作一个检查清单:

基金持仓数据获取与预处理流程 数据源选择 Tushare(稳定) AkShare(免费) 数据获取 数据清洗 清洗要点 1. 缺失值处理 - 删除关键字段为空的行 - 比例字段用 0 填充 2. 去重 - 按 fund + stock + quarter 去重 3. 标准化 - 统一字段命名 - 统一数值单位 4. 构建边列表 - source: fund_id - target: stock_name - weight: 持仓比例

4.6 避坑指南

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

  • 数据延迟问题: 公募基金季报通常在季度结束后15个工作日内披露。你如果在4月1号查3月31号的数据,大概率是空的。别急,等两周。
  • 基金代码格式: Tushare 的基金代码是 6 位数字 + ".OF",比如 "000001.OF"。AkShare 直接用 6 位数字就行。我一开始没注意这个,查了半天没数据。
  • 持仓比例之和不为100%: 公募基金只披露前十大持仓,所以比例之和通常在20%-60%之间。这是正常的,别以为是数据错了。
  • 股票停牌: 有些股票停牌了,市值还是按停牌前价格算。这在图里会形成一些「假连接」,建议在构建图之前,先过滤掉停牌股票。
一个小技巧: 我习惯把清洗后的数据存成 Parquet 格式,比 CSV 快很多,而且自带压缩。尤其是当你处理几千只基金、上万只股票的时候,这个差异非常明显。

好了,数据准备好了。下一章,我们就用这些干净的数据,来构建真正的图结构。到时候你会发现,前面这些清洗工作,每一分钟都是值得的。


公众号:蓝海资料掘金营,微信deep3321