news 2026/9/1 12:03:21

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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()可以快速计算增长率。如果传入参数periods=4,代表对比当前季度与四个季度之前的数据,也就是季度数据下的“同比”。

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", index=False, encoding="utf-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"], freq="Q") df = df.sort_values("quarter").reset_index(drop=True) print(df)

PeriodIndex可以保证季度数据按时间排序,freq="Q"表示季度频率。排序后再重置索引,后续使用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(periods=4) * 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(periods=4) * 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(periods=4) * 100 pd.set_option("display.float_format", "{:.2f}".format) print(result.to_string())

这里的pct_change()返回的是小数,乘以 100 后得到百分比。periods=4是因为季度数据一年有 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"], color="steelblue", alpha=0.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"], color="orange", marker="o", linewidth=2, label="持股比例(%)") ax2.set_ylabel("机构持股比例(%)") fig.legend(loc="upper left", bbox_to_anchor=(0.1, 0.9)) plt.title("机构持仓变化趋势:数量与持股比例") plt.tight_layout() plt.savefig("result/holding_trend.png", dpi=150) plt.show()

折线图和柱状图放在同一个坐标系里,可以同时看到机构数量、持股比例的变化趋势。这里使用twinx()生成第二个 y 轴,因为机构数量和持股比例的单位不同,如果共用一个 y 轴,折线会被严重压扁。

运行后,result/holding_trend.png会生成一张趋势图。你可以很清楚地看到,机构数量和持股比例在 2024 年呈同步上升趋势。

4.5 输出分析结论

最后,把关键结论汇总成一个简洁表格。

summary = result[result["quarter"] >= pd.Period("2023Q4", freq="Q")][ ["quarter", "institution_num", "num_yoy", "holding_shares", "shares_yoy", "float_ratio", "ratio_yoy"] ] print(summary.to_string(index=False))

在真实报告中,我建议把这类汇总结果输出为 CSV 或 Excel,方便复查。

summary.to_csv("result/holding_summary.csv", index=False, encoding="utf-8-sig")

到这里,一次完整的机构持仓数据分析就结束了。整体流程是:构造数据 → 检查质量 → 清洗排序 → 计算指标 → 可视化 → 输出结论。

5. 扩展:接入真实数据源的思路

如果你希望获取真实年报数据,可以把 CSV 读取部分替换成开源数据接口。不过由于这类接口变更较快,下面只给出伪代码思路,实际参数要以官方文档为准。

# 伪代码:以 akshare 为例,接口名称随版本变化 # import akshare as ak # df_raw = ak.stock_gdfx_free_top_10_em(symbol="SH600309", date="20241231") # print(df_raw.head())

使用真实数据时,需要特别注意以下几点:

  • 接口返回的字段名称可能与演示数据不一致,需要先打印columns做字段映射。
  • 有些数据源返回的持股数量是“股”,有些是“万股”,换算关系要提前确认。
  • 定期报告数据存在更新延迟,不同披露批次之间可能出现前后不一致,需要以最新披露为准。

6. 常见问题与排查思路

问题现象常见原因解决思路
pct_change(periods=4)前几行是 NaN数据不足以计算同比NaN 是正常的,代表这些报告期没有更早一年的历史数据,展示时忽略即可
图表中文显示为方块系统中没有匹配的中文字体安装中文字体,或使用plt.rcParams["font.sans-serif"]切换字体
排序后季度顺序仍然错乱quarter 字段是普通字符串使用pd.PeriodIndexpd.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(level=logging.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(periods=COMPARE_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"], freq="Q") df = df.sort_values("quarter").reset_index(drop=True) 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(periods=freq) * 100 return df

这样做的好处是,当数据源或指标口径变化时,只需要修改对应的函数,而不需要改动主流程脚本。

8. 总结

这篇文章从一个“机构持仓翻倍”的热点标题出发,拆解了背后的数据口径和分析方法。核心收获有三点:

第一,看到“翻倍”类结论先问口径,是机构数量、持股总量,还是持股比例?是同比还是环比?不搞清口径就下结论,很容易被误导。

第二,掌握了用 pandas 计算绝对变化、环比、同比的方法,尤其是pct_change(periods=4)在季度数据中的用法,这是很多新手容易忽略的细节。

第三,完整的分析流程应该包括数据质量检查、时间排序、指标计算、可视化和结果输出,而不是只贴一张图表就算完成。

如果你接下来想深入,可以继续学习polars加速实践、金融时间序列的复权处理、以及如何使用数据源获取全市场股东数据。这些都是把“一次性分析”升级为“可复用数据分析系统”的必经之路。

最后再说一点:年报里的每一组数字背后都有一套统计口径,拿到数据之后先做口径核对,再谈分析规律。把它当成一次严肃的数据工程任务,你会比大多数只看新闻标题的人走得更远。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 12:02:26

基于SpringBoot的旧衣服捐赠系统微信小程序毕业设计项目源码

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/1 12:01:58

用Python搭建新闻质量监控工具:RSS聚合、标题党识别与可读性评分

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/1 12:01:21

SQL Server 2016更改sa用户名

1:sa是数据库的默认用户名,出于安全考虑,最好更改默认的用户名。2:运行SQL Server管理工具,连接数据库。3:在“安全性”下的“登录名”中找到sa。4:右键选择sa,重命名,改…

作者头像 李华
网站建设 2026/9/1 12:01:02

JAVA各种加密与解密方式

一、凯撒加密 在密码学中,凯撒加密是一种最简单且最广为人知的加密技术。它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。这个加密方法是以罗马共和时期恺撒的名字命…

作者头像 李华