ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python量化分析:拆解机构持仓翻倍的数据口径与同比计算

2026/9/2 15:44:03 拓冰建站 浏览量
Python量化分析:拆解机构持仓翻倍的数据口径与同比计算 最近在整理上市公司年报数据时总能看到类似“某某股份机构持仓翻倍”这类标题非常抓眼球。但作为开发者我更关心的是另一个问题这类结论到底是怎么算出来的是机构数量翻倍还是持仓比例翻倍统计口径是同比还是环比如果只看新闻标题很容易被带偏。本文就以“罗曼股份机构持仓翻倍”这个案例作为切入点不讨论具体股票买卖点也不构成任何投资建议而是站在数据分析的角度用 Python 完整走一遍“机构持仓变化”的分析流程包括演示数据生成、数据清洗、指标计算、同比/环比口径拆分、可视化输出和常见坑点排查。看完之后你可以直接套用这套方法来分析其他股票的定期报告数据。1. 背景机构持仓数据为什么值得分析1.1 什么是机构持仓数据机构持仓数据指的是基金、券商、保险、社保、QFII合格境外机构投资者等专业投资机构在上市公司定期报告中披露的持股情况。通常包括三个核心指标机构数量持有该公司股票的机构有多少家。持股总量所有机构合计持有的股份数量。持股占流通股比例机构合计持股数占公司流通股本的比例。这类数据一般随上市公司年报、半年报和季报披露属于公开信息。对于做量化分析或基本面研究的人来说机构持仓变化是观察资金动向的重要参考。1.2 “机构持仓翻倍”的数据口径“翻倍”这个词很模糊。同样是“机构持仓翻倍”可能对应完全不同的统计口径机构数量从 20 家增长到 40 家是翻倍。机构持股总量从 1000 万股增长到 2000 万股也是翻倍。机构持股占流通股比例从 5% 上升到 10%同样可以称为翻倍。更严格一点还要区分“同比”和“环比”同比2024 年年报对比 2023 年年报可以剔除周期性波动。环比2024 年四季度对比 2024 年三季度反映短期变化。本文在实战部分会同时计算多种口径并建议你用表格统一输出避免只凭一个数字下结论。1.3 本文的边界说明这里需要先说明清楚本文所有演示数据均为虚构仅用于展示分析方法。“罗曼股份”作为案例引出不涉及对真实公司数据的任何解读。真实场景中你需要从正规数据源获取经过审计校验的定期报告数据并且分析结论不能直接作为交易依据。2. 环境准备与数据源选择2.1 Python 环境与依赖本文示例以 Python 3.9 环境为例核心依赖如下依赖库用途pandas数据清洗、表格计算numpy数值处理matplotlib可视化绘图jupyter notebook交互式演示环境安装命令pip install pandas numpy matplotlib jupyter如果你是在 Jupyter Notebook 中运行建议使用新版的 notebook 或 JupyterLab如果是在 PyCharm 等 IDE 中运行也可以直接执行 Python 脚本。2.2 数据源说明真实场景下机构持仓数据可以从以下途径获取巨潮资讯网等官方信息披露平台下载上市公司定期报告 PDF 或 Excel 附件。券商金融终端导出的股东研究数据。开源金融数据接口例如 akshare、tushare 等。需要注意的是财金类数据源的接口变动比较频繁而且不同数据源对“机构”的定义可能存在差异。本文主流程优先使用本地 CSV 演示数据这样你可以完全脱离网络环境复现真实数据接入方式会在后面作为扩展方案说明。2.3 项目目录结构建议先创建一个独立的工作目录结构如下institution_holding/ ├── data/ │ └── institution_holding.csv ├── result/ │ └── holding_trend.png ├── 0_generate_demo_data.py ├── 1_analyze_holding.py └── README.md其中data存放原始数据result存放输出图表脚本文件按执行顺序编号方便追踪。3. 核心概念拆解持仓指标与翻倍口径3.1 常用持仓指标在构建分析模型之前先把几个常见指标的定义理清楚。机构数量institution_num报告期内出现在前十大股东、前十大流通股东名单中的机构数量。要注意部分数据源统计的是“全体机构股东”数量部分统计的是“进入前十大名单的机构”数量口径不同结果差异很大。机构持股总量holding_shares机构持有的股票数量单位通常是“股”少数数据源用“万股”。机构持股占流通股比例float_ratio机构持股总量除以公司流通股本再乘以 100%。这个比例可以消除公司总股本规模的影响更适合横向对比。持股集中度类似“前十大股东持股合计比例”“前十大流通股东持股合计比例”可以辅助判断筹码集中程度但本文不把集中度作为主指标只作为参考维度。3.2 “翻倍”的计算口径在数据分析中计算“翻倍”最常用的方法有三个第一绝对变化。公式为变化值 本期值 - 上期值适合回答“增加了多少”的问题。第二同比增长率。公式为同比增长率 (本期值 - 去年同期值) / 去年同期值 × 100%适合回答“一年前到现在涨了多少”的问题。第三环比增长率。公式为环比增长率 (本期值 - 上期值) / 上期值 × 100%适合回答“与上一个报告期相比变化了多少”的问题。在 pandas 中DataFrame.pct_change()可以快速计算增长率。如果传入参数periods4代表对比当前季度与四个季度之前的数据也就是季度数据下的“同比”。3.3 从数据到结论的偏差风险看到机构持仓翻倍时至少还要问三个问题数据是否经过复权调整股票分红送转会导致持股数量表面变化但不代表机构真实买卖行为。统计口径是否一致不同数据源可能把“陆股通”单独列出也可能合并到机构中。是否剔除了新股上市、限售股解禁等事件影响流通股本变化会直接影响持股比例。这些问题在可视化阶段会显得尤其重要。如果不对齐口径画出的趋势图可能完全失真。4. 完整实战Python 分析机构持仓变化4.1 生成演示数据由于真实数据涉及数据源授权和接口波动我们先构造一份符合真实结构的演示数据。假设某公司从 2023 年一季度到 2024 年四季度机构数量、持股总量、持股占流通股比例持续增长其中 2024 年四季度相对 2023 年四季度接近翻倍。创建脚本0_generate_demo_data.py# 文件路径0_generate_demo_data.py import pandas as pd data { quarter: [ 2023Q1, 2023Q2, 2023Q3, 2023Q4, 2024Q1, 2024Q2, 2024Q3, 2024Q4 ], institution_num: [12, 15, 18, 22, 26, 31, 38, 45], holding_shares: [850, 1000, 1200, 1500, 1800, 2200, 2700, 3300], float_ratio: [2.8, 3.4, 4.1, 5.2, 6.3, 7.6, 9.2, 11.5] } df pd.DataFrame(data) df.to_csv(data/institution_holding.csv, indexFalse, encodingutf-8-sig) print(df.head())运行python 0_generate_demo_data.py这里生成的是一个 8 行 4 列的 CSV 文件。为什么要用utf-8-sig编码因为后续如果要用 Excel 打开utf-8-sig可以避免中文乱码。预期输出quarter institution_num holding_shares float_ratio 0 2023Q1 12 850 2.8 1 2023Q2 15 1000 3.4 ...可以看到机构数量从 12 家增加到 45 家持股比例从 2.8% 上升到 11.5%“翻倍”在这个演示数据里是成立的。4.2 加载与清洗数据分析脚本1_analyze_holding.py正式读取数据。读取后第一件事不是算指标而是做基础检查。# 文件路径1_analyze_holding.py import pandas as pd df pd.read_csv(data/institution_holding.csv) print(数据 shape, df.shape) print(缺失值检查) print(df.isnull().sum()) print(重复行数量, df.duplicated().sum()) print(数据类型) print(df.dtypes)预期输出数据 shape (8, 4) 缺失值检查 quarter 0 institution_num 0 holding_shares 0 float_ratio 0 dtype: int64 重复行数量 0 数据类型 quarter object institution_num int64 holding_shares int64 float_ratio float64 dtype: object这里的关键点在于quarter是字符串类型在排序时不能简单按字母顺序排否则会出现2023Q10排在2023Q2前面的问题。虽然本例数据是顺序排列的但在真实场景中建议把季度字段解析成有序的类型例如pandas.PeriodIndex。df[quarter] pd.PeriodIndex(df[quarter], freqQ) df df.sort_values(quarter).reset_index(dropTrue) print(df)PeriodIndex可以保证季度数据按时间排序freqQ表示季度频率。排序后再重置索引后续使用pct_change时才不会出错。4.3 计算持仓变化指标接下来分别计算绝对变化、同比变化和环比变化。# 复制一份用于计算 result df.copy() # 机构数量变化 result[num_abs_change] result[institution_num].diff() result[num_qoq] result[institution_num].pct_change() * 100 result[num_yoy] result[institution_num].pct_change(periods4) * 100 # 持股总量变化 result[shares_abs_change] result[holding_shares].diff() result[shares_qoq] result[holding_shares].pct_change() * 100 result[shares_yoy] result[holding_shares].pct_change(periods4) * 100 # 持股占流通股比例变化 result[ratio_abs_change] result[float_ratio].diff() result[ratio_qoq] result[float_ratio].pct_change() * 100 result[ratio_yoy] result[float_ratio].pct_change(periods4) * 100 pd.set_option(display.float_format, {:.2f}.format) print(result.to_string())这里的pct_change()返回的是小数乘以 100 后得到百分比。periods4是因为季度数据一年有 4 期对比当前季度与第 4 期之前的数据正好是同比。输出结果中重点看 2024Q4 这一行机构数量同比(45 - 22) / 22 * 100 104.55%持股总量同比(3300 - 1500) / 1500 * 100 120%持股比例同比(11.5 - 5.2) / 5.2 * 100 121.15%所以从演示数据来看三个指标都超过了 100%确实属于“翻倍”级别。但在真实分析中你要把这三个数字都列出来而不是只挑一个最大或最小的说。4.4 可视化持仓变化为了让数据更直观我们绘制双轴图柱状图展示机构数量折线图展示机构持股占流通股比例。import matplotlib.pyplot as plt # Windows 常见中文字体 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plc.rcParams[axes.unicode_minus] False注意如果你的环境是 Linux通常没有SimHei字体需要改成plt.rcParams[font.sans-serif] [WenQuanYi Zen Hei, Noto Sans CJK SC]如果没有中文字体可以先安装字体或者在图表中改用英文标签。为了示例稳定下面代码保留中文标签但需要保证本地已安装对应字体。fig, ax1 plt.subplots(figsize(10, 6)) x range(len(result)) ax1.bar(x, result[institution_num], colorsteelblue, alpha0.7, label机构数量) ax1.set_xlabel(报告期) ax1.set_ylabel(机构数量) ax1.set_xticks(x) ax1.set_xticklabels(result[quarter].astype(str)) ax2 ax1.twinx() ax2.plot(x, result[float_ratio], colororange, markero, linewidth2, label持股比例(%)) ax2.set_ylabel(机构持股比例(%)) fig.legend(locupper left, bbox_to_anchor(0.1, 0.9)) plt.title(机构持仓变化趋势数量与持股比例) plt.tight_layout() plt.savefig(result/holding_trend.png, dpi150) plt.show()折线图和柱状图放在同一个坐标系里可以同时看到机构数量、持股比例的变化趋势。这里使用twinx()生成第二个 y 轴因为机构数量和持股比例的单位不同如果共用一个 y 轴折线会被严重压扁。运行后result/holding_trend.png会生成一张趋势图。你可以很清楚地看到机构数量和持股比例在 2024 年呈同步上升趋势。4.5 输出分析结论最后把关键结论汇总成一个简洁表格。summary result[result[quarter] pd.Period(2023Q4, freqQ)][ [quarter, institution_num, num_yoy, holding_shares, shares_yoy, float_ratio, ratio_yoy] ] print(summary.to_string(indexFalse))在真实报告中我建议把这类汇总结果输出为 CSV 或 Excel方便复查。summary.to_csv(result/holding_summary.csv, indexFalse, encodingutf-8-sig)到这里一次完整的机构持仓数据分析就结束了。整体流程是构造数据 → 检查质量 → 清洗排序 → 计算指标 → 可视化 → 输出结论。5. 扩展接入真实数据源的思路如果你希望获取真实年报数据可以把 CSV 读取部分替换成开源数据接口。不过由于这类接口变更较快下面只给出伪代码思路实际参数要以官方文档为准。# 伪代码以 akshare 为例接口名称随版本变化 # import akshare as ak # df_raw ak.stock_gdfx_free_top_10_em(symbolSH600309, date20241231) # print(df_raw.head())使用真实数据时需要特别注意以下几点接口返回的字段名称可能与演示数据不一致需要先打印columns做字段映射。有些数据源返回的持股数量是“股”有些是“万股”换算关系要提前确认。定期报告数据存在更新延迟不同披露批次之间可能出现前后不一致需要以最新披露为准。6. 常见问题与排查思路问题现象常见原因解决思路pct_change(periods4)前几行是 NaN数据不足以计算同比NaN 是正常的代表这些报告期没有更早一年的历史数据展示时忽略即可图表中文显示为方块系统中没有匹配的中文字体安装中文字体或使用plt.rcParams[font.sans-serif]切换字体排序后季度顺序仍然错乱quarter 字段是普通字符串使用pd.PeriodIndex或pd.to_datetime转为有序时间类型数据源返回的持股比例超过 100%单位或口径理解错误检查字段说明确认是“占流通股比例”还是“占总股本比例”计算出的同比涨幅异常大去年同期基数太小对低基数场景单独标注避免误读从接口获取数据时字段名频繁变化数据源接口升级打印df.columns后动态映射并把原始数据保存到本地以便追溯建议养成一个习惯每次分析之前先写“数据质量检查”代码块检查缺失值、重复值、类型和排序。不要急着算指标。7. 最佳实践与工程建议7.1 口径统一与配置管理在做这类周期性数据分析时不要把所有口径写死在业务代码里。建议把指标名称、对比期数、输出路径放到配置文件或配置类中。例如用config.py统一管理参数# 文件路径config.py COMPARE_PERIODS 4 # 季度数据同比 OUTPUT_DIR result DATA_DIR data这样后续修改对比周期时只改一处配置即可。7.2 异常处理与日志记录在脚本开头初始化日志重点记录数据读取时间、清洗前后行数变化、最终输出文件路径。import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logging.info(load data from %s, data/institution_holding.csv) logging.info(after clean shape: %s, df.shape)对于生产环境日志是排查问题的第一入口。建议至少保留INFO级别日志关键异常用logger.exception(e)输出堆栈。7.3 安全与合规边界金融数据分析容易触碰合规风险这里提醒几点获取数据时必须遵守数据源的使用条款不要绕过接口限制批量抓取。分析结论不要以“预言涨跌”的形式发布更不要鼓励读者据此交易。涉及真实上市公司数据时避免用耸动标题误导读者注明数据来源和分析口径。如果数据涉及个人或机构非公开信息禁止传播。7.4 性能优化如果分析对象不只是单一股票而是一篮子股票例如沪深 300 所有成分股的历史持仓数据数据量会迅速膨胀。此时要优先考虑使用polars替代pandas处理大数据量时性能更稳定。把中间数据存储为parquet格式比 CSV 读写更快。计算指标时尽量向量化避免逐行for循环。# 向量化示例一次性计算多列同比 for col in [institution_num, holding_shares, float_ratio]: result[f{col}_yoy] result[col].pct_change(periodsCOMPARE_PERIODS) * 100这段代码比单独对每一列重复写pct_change更简洁也更容易扩展。7.5 构建可复用的分析模块如果团队中多人需要分析不同股票的持仓数据可以把数据加载、清洗、指标计算拆成独立函数放进holding_analyzer.py# 文件路径holding_analyzer.py核心片段 import pandas as pd def load_data(path: str) - pd.DataFrame: df pd.read_csv(path) df[quarter] pd.PeriodIndex(df[quarter], freqQ) df df.sort_values(quarter).reset_index(dropTrue) return df def calc_mom_yoy(df: pd.DataFrame, value_col: str, freq: int 4) - pd.DataFrame: df df.copy() df[f{value_col}_mom] df[value_col].pct_change() * 100 df[f{value_col}_yoy] df[value_col].pct_change(periodsfreq) * 100 return df这样做的好处是当数据源或指标口径变化时只需要修改对应的函数而不需要改动主流程脚本。8. 总结这篇文章从一个“机构持仓翻倍”的热点标题出发拆解了背后的数据口径和分析方法。核心收获有三点第一看到“翻倍”类结论先问口径是机构数量、持股总量还是持股比例是同比还是环比不搞清口径就下结论很容易被误导。第二掌握了用 pandas 计算绝对变化、环比、同比的方法尤其是pct_change(periods4)在季度数据中的用法这是很多新手容易忽略的细节。第三完整的分析流程应该包括数据质量检查、时间排序、指标计算、可视化和结果输出而不是只贴一张图表就算完成。如果你接下来想深入可以继续学习polars加速实践、金融时间序列的复权处理、以及如何使用数据源获取全市场股东数据。这些都是把“一次性分析”升级为“可复用数据分析系统”的必经之路。最后再说一点年报里的每一组数字背后都有一套统计口径拿到数据之后先做口径核对再谈分析规律。把它当成一次严肃的数据工程任务你会比大多数只看新闻标题的人走得更远。