尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

用Python分析机构持仓:从年报数据采集到可视化验证

用Python分析机构持仓:从年报数据采集到可视化验证 持仓数据里藏着哪些信息最近不少做股票数据分析的朋友都在讨论年报中机构持仓的变化。以罗曼股份为例2024年年报披露后机构持股数量相比前期出现了明显增长这种变化背后反映了什么又该如何用可复现的技术手段去追踪和验证如果只用肉眼去读年报PDF再手动对比往期数据效率低且容易遗漏。更稳妥的做法是通过标准数据接口采集机构持仓明细再用 Python 做清洗、计算和可视化把“机构持仓翻倍”这类结论从主观感受变成可核验的数据结果。这篇文章就围绕这条技术路线展开先讲清楚机构持仓相关概念再给出从数据获取到分析展示的完整实现过程最后补充常见坑和工程建议。无论你是做量化投研的开发者还是刚接触金融数据分析的工程师这篇文章都能帮你建立一套可复用的持仓分析流程。更重要的是它能让你在看待任何一家公司的年报持仓变化时不再是只看新闻标题而是能亲手验证数据。1. 机构持仓分析到底在分析什么机构持仓简单理解就是基金、券商、保险、社保、QFII 等专业投资机构持有上市公司股票的数量和比例。年报和季报中披露的十大股东、十大流通股东信息是普通投资者能拿到的相对规范的机构持仓数据来源。这里容易混淆的概念有两个一是“机构持股数量”二是“机构持股比例”。数量是绝对值比例是相对值。一家公司总股本不变的情况下机构持股数量增长持股比例大概率同步提升但如果公司总股本因为定增、转增发生变化只看数量就可能误判。比如一家公司总股本扩大了 50%机构持股数量增加 30%实际机构持股比例反而是下降的。所以分析机构持仓变化时不能只看单一指标必须同时看数量、比例、股本变动三个维度。另一个需要区分的概念是“报告期持仓”和“实时持仓”。年报披露的是截至 12 月 31 日的股东持仓情况季报披露的是季度末时点数据。这些数据存在滞后性年报通常次年 4 月底前披露完毕从 12 月 31 日到披露日之间机构可能已经调仓。因此年报机构持仓数据更适合用来分析中长期趋势不适合用来做短期交易决策。在技术实现层面分析机构持仓的核心流程包括数据采集从公开接口或定期报告获取机构持仓明细。数据清洗统一股东名称、机构类型、持股数量单位。指标计算计算持股数量变动、持股比例变动、机构数量变化。可视化通过图表呈现持仓变化的趋势和结构。这套流程不依赖人工读 PDF也不依赖新闻摘要全部基于结构化数据完成结果可复核、可追溯。从工程角度来说它本质上是一个轻量级的数据分析管道输入是财报数据源输出是指标变化结果。这里需要强调一个重要观点机构持仓变化只是一个分析维度不能单独作为买卖依据。机构也可能判断失误机构持仓数据本身也有滞后性。真正有价值的是把机构持仓变化与公司基本面、行业趋势、估值水平放在一起做交叉验证。技术手段能帮我们高效完成“数据获取、清洗、计算、展示”这四步但最终的分析判断仍然需要人来完成。2. 获取机构持仓数据的技术方案对比要做机构持仓分析第一步是搞定数据源。目前常用的方式有四类直接读取定期报告 PDF、使用财经数据接口、爬取财经网站、购买商业数据库。下面逐个分析。2.1 定期报告 PDF 解析直接在巨潮资讯网下载年报 PDF然后用 Python 的 pdfplumber、camelot 等库解析表格。优点是数据源头权威缺点也很明显不同年份、不同公司的 PDF 格式不完全一致表格可能跨页股东名称可能换行。解析脚本的维护成本很高适合一次性研究不适合做常态化监控。2.2 财经数据接口目前国内开发者常用的有 akshare、tushare、baostock 等。akshare 免费、接口丰富、无需注册即可使用一部分功能tushare 需要积分积分越高可调用的接口越多baostock 免费但数据范围相对有限。这类接口适合做个人研究和原型验证也是本文示例采用的方式。使用数据接口时要注意接口返回的字段名、单位可能和预期不一致必须先用小样本验证接口有访问频率限制批量获取时要控制请求速度免费接口的数据质量总体可靠但遇到异常值要人工核对。2.3 爬取财经网站从东方财富、同花顺等网站抓取股东持仓数据。这种方式灵活但存在反爬风险而且网页结构随时可能调整爬虫维护成本高。从合规角度看个人学习研究使用问题不大但生产环境需要确认数据使用条款。2.4 商业数据库Wind、Choice、iFind 等商业终端数据质量高、字段全但价格不菲适合机构用户。个人开发者没必要为了分析一家公司的持仓专门购买商业数据库。从工程实践来看我推荐组合使用日常研究用免费接口关键数据用定期报告人工核对有条件再采购商业数据库做交叉验证。没有唯一正确的数据源只有更适合当前场景的方案。3. 环境准备与前置条件3.1 运行环境本文示例基于 Python 编写操作系统使用 Windows、macOS 或 Linux 均可。建议 Python 版本不低于 3.9需要安装以下依赖库akshare获取股票财务数据和股东数据。pandas数据清洗和指标计算。matplotlib基础可视化。numpy数值计算。安装命令如下pip install akshare pandas matplotlib numpy需要注意akshare 更新比较频繁接口名称和参数偶尔会调整。如果运行时报错提示找不到某个接口可以先用以下命令升级到最新版本pip install akshare --upgrade3.2 确定分析标的本文以罗曼股份作为演示案例。罗曼股份是一家专注于景观照明和城市夜游文旅规划的公司。之所以选择它是因为它在最近披露的年报中机构持仓出现了明显变化适合用来演示“持仓翻倍”如何从数据层面验证。先引入关键工具库并确认 akshare 版本import akshare as ak import pandas as pd import numpy as np import matplotlib.pyplot as plt print(akshare 版本, ak.__version__)运行这段代码能正常输出版本号说明环境基本可用。4. 核心数据采集与清洗实操4.1 获取股票基础信息首先确认股票代码和名称。罗曼股份在上海证券交易所上市股票代码为 605289。使用 akshare 拉取最新交易快照确认代码有效# 获取A股实时行情快照确认代码可用 spot_df ak.stock_zh_a_spot_em() # 筛选罗曼股份 target spot_df[spot_df[代码] 605289] print(target[[代码, 名称, 最新价, 总市值]].to_string(indexFalse))正常输出会显示“605289 罗曼股份”以及对应的行情数据。如果这部分返回空说明 akshare 的数据源接口有调整或者代码输入有误需要先解决数据源问题再继续后续步骤。4.2 获取十大流通股东数据akshare 提供了获取个股十大流通股东的接口stock_gdfx_free_holding_detail_em。在调用前建议先查看接口文档或直接尝试运行确认参数格式# 获取罗曼股份十大流通股东数据 # 该接口返回最近报告期的股东持股明细 try: holder_df ak.stock_gdfx_free_holding_detail_em(symbol605289, date20250331) print(holder_df.head(20).to_string(indexFalse)) except Exception as e: print(接口调用失败, e)这里date20250331表示 2025 年一季报的截止日期。如果要分析年报数据应该将日期调整为年报对应的时点比如 20241231。具体可用日期范围可以在调用前先用其他接口获取报告期列表或者参考 akshare 的文档说明。接口返回的数据字段通常包括股东名称。持股数量。占流通股比例。股份变动数量。股份变动比例。拿到原始数据后还需要做两件事统一单位、清洗名称。持股数量有的接口返回“股”有的返回“万股”必须统一成“万股”或“股”否则后续计算会出现数量级错误。# 清洗确认数据的列名和类型 holder_df.columns [col.strip() for col in holder_df.columns] print(holder_df.dtypes)4.3 获取历史报告期机构持仓数据只会看一个报告期远远不够要判断“机构持仓翻倍”必须对比至少两个报告期。建议获取最近 4 到 8 个报告期的数据用于观察趋势变化。以下代码演示批量获取多个报告期的十大流通股东数据# 定义需要获取的报告期列表 report_dates [20240331, 20240630, 20240930, 20241231, 20250331] holder_dict {} for date in report_dates: try: df ak.stock_gdfx_free_holding_detail_em(symbol605289, datedate) df[报告期] date holder_dict[date] df print(f{date} 获取成功共 {len(df)} 条股东记录) except Exception as e: print(f{date} 获取失败{e}) # 合并所有报告期数据 all_holders pd.concat(holder_dict.values(), ignore_indexTrue) print(all_holders.shape)这一步执行成功后我们就有了一个包含多个报告期股东明细的 DataFrame后续所有分析都基于这份数据。4.4 筛选机构股东并计算合计持仓股东名称中包含了个人股东和机构股东。常见的机构名称特征包括“基金”“资管”“保险”“社保”“信托”“证券”等。先用关键词筛选出疑似机构股东再按报告期分组汇总。# 筛选机构股东 def is_institution(name): if not isinstance(name, str): return False keywords [基金, 资管, 保险, 社保, 信托, 证券, 银行, QFII] return any(k in name for k in keywords) all_holders[是否机构] all_holders[股东名称].apply(is_institution) # 按报告期汇总机构持股数量 institution_by_date ( all_holders[all_holders[是否机构]] .groupby(报告期) .agg( 机构股东数量(股东名称, count), 合计持股数量(持股数量, sum), 合计持股比例(占流通股比例, sum) ) .reset_index() ) print(institution_by_date.to_string(indexFalse))这段代码有几点需要注意。第一“占流通股比例”字段在不同接口中可能不是数值类型需要先用pd.to_numeric做转换。第二十大流通股东之外的机构持仓不在统计范围内所以这里的“合计持股比例”是“十大流通股东中机构股东的合计比例”不是全量机构持仓比例。第三同一家机构可能通过多个产品持股上面代码按股东名称简单计数更严谨的做法是按“机构母公司”维度做归并这需要维护一张机构名称映射表。# 确保比例字段是数值 for col in [持股数量, 占流通股比例, 股份变动数量]: if col in all_holders.columns: all_holders[col] pd.to_numeric(all_holders[col], errorscoerce)4.5 计算持仓翻倍的核心指标现在进入关键环节计算相邻报告期的机构持仓变化。核心指标有三个机构持股数量变化率、机构持股比例变化、机构股东数量变化。# 计算环比变化 institution_by_date institution_by_date.sort_values(报告期).reset_index(dropTrue) institution_by_date[持股数量环比变化率] institution_by_date[合计持股数量].pct_change() * 100 institution_by_date[持股比例环比变化] institution_by_date[合计持股比例].diff() institution_by_date[机构数量环比变化] institution_by_date[机构股东数量].diff() # 保留两位小数便于查看 institution_by_date institution_by_date.round(2) print(institution_by_date.to_string(indexFalse))输出结果中“持股数量环比变化率”如果超过 100说明机构持股数量相比上一报告期翻了一倍以上。这正是“机构持仓翻倍”这一结论的数据来源。如果最新报告期的数值是 120说明机构持股数量是上一期的 2.2 倍而不是 120 倍。看完这个结果你就能理解为什么我之前强调要同时看数量和比例如果总股本变化不大数量和比例的变化方向应该一致如果两者出现背离就需要检查总股本是否发生了变动。5. 持仓结构可视化让数据自己说话表格数据适合精细查看但人类对图形的感知更直观。这里用 matplotlib 画两组图一是机构合计持股数量随报告期的变化趋势二是机构持股结构的变化。5.1 绘制机构持股数量趋势图# 设置中文字体防止乱码 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, PingFang SC] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(10, 6)) ax.plot( institution_by_date[报告期].astype(str), institution_by_date[合计持股数量], markero, linewidth2, color#d62728, label机构合计持股数量 ) ax.set_xlabel(报告期) ax.set_ylabel(机构合计持股数量) ax.set_title(罗曼股份十大流通股东中机构持股数量变化) ax.legend() ax.grid(True, linestyle--, alpha0.6) plt.xticks(rotation45) plt.tight_layout() # 保存图片方便后续复盘和报告复用 fig.savefig(institution_holding_trend.png, dpi150) plt.show()这段代码的关键点在于中文字体设置。如果系统缺少 SimHei 或 Microsoft YaHei图形中的中文会显示为方框。macOS 下可以改用 PingFang SCLinux 服务器上则需要额外安装中文字体。5.2 绘制机构持仓占比条形图折线图适合看趋势条形图适合看各报告期之间的结构对比fig2, ax2 plt.subplots(figsize(10, 6)) bars ax2.bar( institution_by_date[报告期].astype(str), institution_by_date[合计持股比例], color#1f77b4, alpha0.8, label机构合计持股比例 ) ax2.set_xlabel(报告期) ax2.set_ylabel(合计持股比例%) ax2.set_title(罗曼股份十大流通股东中机构合计持股比例变化) ax2.legend() # 在柱子上方添加数值标签 for bar in bars: height bar.get_height() ax2.text( bar.get_x() bar.get_width() / 2, height 0.3, f{height:.2f}%, hacenter, vabottom, fontsize10 ) ax2.grid(True, axisy, linestyle--, alpha0.6) plt.xticks(rotation45) plt.tight_layout() fig2.savefig(institution_holding_ratio.png, dpi150) plt.show()可视化到这里我们已经完成了从数据采集到指标计算、再到图表输出的所有环节。整个流程跑通后你就能非常直观地看到机构持仓在几个报告期之间的变化轨迹。6. 运行结果与验证方法6.1 预期结果与判断标准正常情况下程序会输出包含多个报告期的数据表每个报告期都有机构股东数量、合计持股数量、合计持股比例等字段。判断结果是否合理可以从三个方面入手。从数据完整性角度每个报告期都应该有至少一条机构股东记录如果某个报告期机构股东数量为 0大概率是筛选关键词漏掉了某些机构名称或者接口数据本身缺失。从数据合理性角度机构合计持股比例不应该大于 100%如果出现超过 100% 的情况说明“占流通股比例”字段的单位或口径有问题需要返回去检查数据源。从趋势一致性角度机构持股数量变化率如果出现暴涨暴跌比如一个报告期增长 300%下一个报告期又下降 80%先不要急着下结论建议打开年报原文核实对应报告期的股东名单和持股数量。6.2 失败的排查路径如果某一步运行报错可以按以下顺序排查。第一步确认 akshare 是否最新版。akshare 的接口变更频率较高旧版本调用新接口很容易报错。第二步确认日期参数格式。akshare 的日期参数通常要求YYYYMMDD格式且必须是合法的报告期截止日。传了20250330这种非标准日期接口可能返回空数据。第三步检查网络和接口限流。免费接口对单 IP 的访问频率有限制频繁请求可能触发限流。建议在循环请求之间加time.sleep(1)或更长的间隔。第四步核对字段名。不同版本的 akshare 返回的列名可能不同建议先print(df.columns)确认字段名再对列进行操作。7. 机构持仓分析的常见问题与解决思路问题现象可能原因排查方式解决方案接口返回空数据报告期参数不正确打印请求参数尝试相邻报告期调整日期参数确认使用合法报告期中文字体显示为方框系统缺少中文字体检查 matplotlib 字体列表安装中文字体或指定可用的字体名机构持股比例合计异常字段单位或口径不一致对比同报告期定期报告原文确认是否为十大流通股东口径换算单位筛选出的机构数量偏少关键词覆盖不全查看全部股东名称列表扩充关键词或使用人工标注补充金额或数量级明显异常单位未统一检查原始数据的单位说明统一转为“万股”或“股”访问频率受限短时间内请求过多查看返回错误信息增加 sleep 间隔或分散请求时间上面这些坑我在实际分析中几乎都遇到过。尤其是在字段单位上不同的接口返回的持股数量单位可能是“股”也可能是“万股”稍不注意就会得出偏差几十倍的错误结论。保险的做法是把计算结果与定期报告中的原始披露数据抽查核对一两个股东确认无误后再继续往下做分析。8. 机构持仓分析的工程化最佳实践8.1 数据落库不要每次现拉免费接口的稳定性和速度都不适合反复拉取。建议把每次采集到的原始数据、清洗后的数据、计算结果都落入本地数据库SQLite 足够。这样下次分析时直接读库既快又能保留历史快照。import sqlite3 conn sqlite3.connect(stock_analysis.db) all_holders.to_sql(holder_detail, conn, if_existsreplace, indexFalse) institution_by_date.to_sql(institution_summary, conn, if_existsreplace, indexFalse) conn.close()8.2 命名规范与注释分析代码往往是一次性脚本但就算是一次性脚本也建议使用清晰的命名和必要注释。比如holder_df、institution_by_date这样的名字一周后再看还能快速理解而df1、df2这种名字三天后就很难分清哪个是股东数据、哪个是汇总数据。8.3 固定随机种子与输出可复现虽然这个分析过程不涉及随机采样的内容但如果后续加入机器学习模型或者抽样逻辑一定要固定随机种子确保每次运行结果一致。8.4 合规与免责意识做股票数据分析有两个合规底线需要始终牢记。第一不能以“荐股”为目的发布分析结果数据分析报告应该是客观的信息呈现不是投资建议。第二不能误导读者必须明确说明数据口径、报告期时点、滞后性等限制条件。在公开的文章和报告中建议加上“本文内容仅供技术研究和数据分析学习不构成投资建议”之类的声明。8.5 逻辑分层采集、清洗、计算、展示分离即使是个人分析项目也建议按照数据采集、数据清洗、指标计算、可视化展示四层组织代码。每一层之间通过 DataFrame 传递数据方便单独测试和替换实现。比如今天用 akshare 免费接口明天换成商业数据库只需要替换数据采集层后续清洗、计算、展示代码完全不用动。9. 总结数据分析能力比“翻倍”这个结论更重要回到罗曼股份这个案例。通过上面的完整流程我们已经具备了把“机构持仓翻倍”从一个新闻标题变成一套可验证数据结果的能力。你会知道数据来自哪个报告期统计口径是什么机构数量和持股比例各自变化了多少与上一报告期的差异有多大。这些信息比单纯的“翻倍”二字有价值得多因为它可以被复核、被比较、被质疑。更值得沉淀的是这套分析方法论本身。它完全适用于其他任何一家上市公司。你只需要修改股票代码和报告期列表就能在十几分钟内完成新标的的机构持仓分析。把采集、清洗、计算、可视化四个环节标准化之后分析一只新股票的成本会显著降低。如果要继续深入有几个方向可以关注一是把分析范围从十大流通股东扩展到全部机构持仓这需要更完整的数据源二是加入股东户数变化、筹码集中度等辅助指标三是研究机构持仓变化与后续股价表现的统计关系但这一点不要轻易下因果结论。对开发者来说把这套流程封装成一个可复用的分析工具模块是一个不错的实战练手项目。最后再提醒一次年报机构持仓数据是滞后信息判断机构意图本质上是事后分析。真正可靠的分析永远是把多维度数据交叉验证之后再做判断而技术工具能做的是帮你高效、准确地完成数据层面的准备工作。对于一个程序员来说把数据分析链路跑通比追逐一个热点结论更有长期价值。
返回列表