简介:面向金融数据分析、学术研究与投研建模人员,提供一套上市公司员工持股计划与高管薪酬数据集的网盘索引文档。资源包内仅1个docx文件,大小12KB,打开即可获取百度网盘链接与提取码,网盘压缩包约50多MB,收纳员工持股计划变动明细、持有人信息、基础信息、进度明细、行业统计、资管信息,以及高管人数、员工结构与薪酬水平等十余个zip表格。数据基于上市公司年报、公告及相关省、市数据整理,覆盖沪深北证A股主板、中小企业板、创业板、科创板、北交所服务板块,超过5000只个股,时间跨度覆盖员工持股计划2014-2025、员工结构2006-2024、高管与员工薪酬1990-2024等区间,可用于员工持股参与度、董监高认购比例、薪酬激励效应等专题分析。目前已有87人学习,适合需要结构化金融数据并希望省去手工整理的投资者、高校师生与券商研究员。
1. 员工持股计划数据为什么值得做一次全量分析
上市公司员工持股计划从2014年集中试点到现在已超过十年,刚好覆盖完整的政策周期与市场周期。做金融领域的数据分析,只看单一年份或单一公司容易得出偏差结论,而把2014到2025年的公告数据拉通后,可以回答几个业务方真正关心的问题:哪些行业的员工持股参与度最高、高管与普通员工的持股成本差多少、锁定期结束后股价表现如何、薪酬与持股之间是否存在对冲关系。这篇文章就以证券监督管理机构披露的上市公司员工持股计划草案、实施公告和定期报告为数据基础,带着你用可复现的方式走一遍从采集、清洗到统计建模的完整流程。无论你是做金融数据分析、企业治理研究还是薪酬绩效设计,这篇内容都能直接落成一套可运行的本地分析脚手架。
2. 2014-2025员工持股计划数据的采集来源与数据库建模
2.1 为什么数据源选择交易所官网与定期报告交叉验证
员工持股计划数据在金融领域的公开渠道主要有四类:证券交易所官网的披露公告、上市公司年报与半年报、Wind或Choice这类商用终端、以及第三方数据商的API接口。商用终端虽然字段齐全,但历史数据存在口径调整和复权问题,尤其是早期计划中经常出现“认购价格待定”这类非标字段。我一般选择交易所官网披露的原始PDF与巨潮资讯的XBRL结构化数据做交叉验证,再用年报中的“股份变动情况表”校对实际过户数量。这样做的原因是员工持股计划从董事会预案到非交易过户完成,中间存在至少三到六个月的窗口期,如果只抓预案数据,会导致持股规模明显高估。
采集层我用Python的requests加BeautifulSoup来抓取公告列表页,再用pdfplumber解析PDF中的关键信息。交易所官网的公告列表接口反爬策略比较温和,每两次请求间隔两到三秒即可,但需要注意分页参数的偏移量不是简单的页数乘以条数,部分年份的公告编号存在跳号。对于这部分原始采集逻辑,建议维护一张增量表记录已抓取的公告编号,避免重复解析。
2.2 SQLite建模:把非结构化公告转成可统计的宽表
拿到原始文本后,需要设计存储结构。金融领域的员工持股数据分析通常要做三类查询:按时间维度看计划数量趋势、按公司维度看持股与薪酬关系、按员工类型看参与结构。用SQLite作为本地分析库足够支撑这些场景,且零运维成本。我先建三张核心表,schema如下:
CREATE TABLE plan ( plan_id TEXT PRIMARY KEY, company_code TEXT NOT NULL, company_name TEXT NOT NULL, announce_date TEXT, plan_status TEXT, total_shares INTEGER, total_amount REAL, source_price REAL, employee_count INTEGER ); CREATE TABLE participant ( plan_id TEXT, category TEXT, person_name TEXT, position TEXT, shares INTEGER, amount REAL, FOREIGN KEY (plan_id) REFERENCES plan(plan_id) ); CREATE TABLE salary ( company_code TEXT, year INTEGER, exec_name TEXT, position TEXT, total_salary REAL, stock_shares INTEGER, PRIMARY KEY (company_code, year, exec_name) );plan表存放每个计划的主体信息,participant表记录董监高及核心员工的认购明细,salary表从定期报告的“董事、监事和高级管理人员报酬情况”章节解析而来。需要说明的是,participant表中的shares字段在部分公告里是以“万股”为单位披露的,解析时我会统一换算为股;数据处理中这个单位不一致问题在2016年前后的公告中尤其常见,同一家公司前后年份口径都会变化,所以在ETL的清洗环节必须先做单位标准化,否则后续聚合结果会差几个数量级。
3. 员工结构统计:用Python与Excel透视拆解参与率与认购集中度
3.1 员工持股计划的参与结构分析维度
员工结构分析是衡量计划激励效果的第一层指标。常见做法是先把计划的总参与人数与公司当年年报披露的员工总数做比对,算出覆盖率。这个指标在金融行业尤其值得关注——券商和银行的员工基数大、分支机构多,但一线业务人员与总部中后台的参与意愿差异明显,单纯看覆盖率会掩盖结构性问题。我把员工结构拆成四个维度进行数据分析:人数占比、人均认购金额、高管认购占比、以及认购金额的基尼系数。最后一个指标能有效识别出“高管拿大头、普通员工陪跑”的失衡计划。
要实现这四个维度的计算,最直接的方式是用pandas做分组聚合:
import pandas as pd import numpy as np # 读取participant表 participant_df = pd.read_sql("SELECT * FROM participant", conn) plan_df = pd.read_sql("SELECT * FROM plan", conn) # 合并计划信息 merged = participant_df.merge(plan_df[['plan_id', 'company_code', 'announce_date']], on='plan_id', how='left') # 计算高管认购占比和普通员工人均金额 category_stats = merged.groupby(['company_code', 'category']).agg( person_count=('person_name', 'count'), total_shares=('shares', 'sum'), total_amount=('amount', 'sum') ).reset_index() # 计算每家公司的高管认购集中度 exec_stats = category_stats[category_stats['category'] == '高管'] normal_stats = category_stats[category_stats['category'] == '普通员工'] pivot_result = pd.pivot_table(merged, index='company_code', columns='category', values='amount', aggfunc='sum', fill_value=0) pivot_result['exec_ratio'] = pivot_result.get('高管', 0) / pivot_result.sum(axis=1)这段代码的核心逻辑是先按公司和人员类别分组统计人数与金额,再用pivot_table生成宽表计算高管认购占比。参数说明:aggfunc='sum'表示对金额做加总,fill_value=0解决部分公司不存在高管认购记录时产生的空值问题。这里有一个金融数据处理的细节——部分2020年后的公告不再区分“高管”和“普通员工”,而是按“董事、监事、高级管理人员”和“其他员工”披露,所以category字段在建表时需要用正则表达式做一次归一化映射,把这两种口径统一成同一种标签。
3.2 基于Excel的交叉验证方法
数据库层面的计算结果还必须回到原始Excel表单中做抽样核对。通常操作是打开原始公告表格,用Excel的数据透视表按“员工类型”和“认购金额区间”拉交叉表,与SQL查询结果响应比对。这里有个经验值:抽5家公司,每家核对三个字段——参与人数、员工持股总数、高管认购比例,如果全部一致则视为ETL流程正确。如果发现SQL结果与Excel不一致,优先检查participant表中有没有同一名高管因职务变动在年内被披露两次,这会造成人数虚增。
4. 高管持股薪酬水平统计:锁定价格、杠杆倍数与信息披露口径
4.1 高管持股与薪酬的联动分析框架
员工持股计划中高管持股的特殊性在于资金来源。从计划草案披露的信息看,员工持股计划的资金来源一般有四种:合法薪酬、自筹资金、股东借款、以及杠杆结构化资金。在金融领域的上市公司中,使用杠杆资金的比例要高于一般制造业企业,这与金融机构本身对资本运作工具更熟悉有关。这部分数据直接反映在计划的“资金来源”字段里,做统计分析时我会生成这样一张汇总表:计划代码、公司简称、公告年份、员工资金总额、杠杆资金比例、高管认购占比。通过相关性矩阵可以识别杠杆比例与高管认购之间是否存在正向联动。
更进一步,把高管在员工持股计划中的获授股份与年报披露的薪酬水平放在同一张表里,可以计算“薪酬持股比”这个指标——即高管年度薪酬总额与当年参与持股计划认购金额的倍数关系。这个指标用来衡量激励的长期绑定效应:比值越低,说明高管越倾向于通过持股来获取收益而非依赖短期薪酬。金融监管机构在审查计划合理性时,也常常关注高管认购金额是否显著超过其年度薪酬水平。
4.2 薪酬数据批量提取与比例计算
薪酬数据存放于年报的PDF与XBRL实例文件中,很难直接读到便捷格式。我通常采用跳过PDF解析、直接抓取巨潮资讯的XBRL数据包的方式,因为交易所要求上市公司从2020年起同步报送标准化数据,而XBRL在“报酬情况”节点下具有固定的元素名。下面是一个从XBRL提取高管薪酬的简化实现:
import requests import xml.etree.ElementTree as ET # XBRL文件的命名空间 ns = {'xbrl': 'http://www.xbrl.org/2003/instance', 'link': 'http://www.xbrl.org/2003/linkbase'} def parse_salary_from_xbrl(xbrl_url, company_code, year): resp = requests.get(xbrl_url, timeout=15) root = ET.fromstring(resp.text) salary_data = [] # 遍历所有context为当年高管的薪酬节点 for elem in root.iter(): tag = elem.tag.split('}')[-1] if tag in ['SalaryOfDirectors', 'RemunerationOfTopManagers']: context_ref = elem.get('contextRef') # 通过contextRef过滤出目标年度与目标公司 if company_code in context_ref and str(year) in context_ref: salary_data.append({ 'name': elem.attrib.get('name', ''), 'value': float(elem.text), 'unit': elem.attrib.get('unitRef', '') }) # 按人汇总年度薪酬 salary_df = pd.DataFrame(salary_data) salary_summary = salary_df.groupby('name').agg( annual_salary=('value', 'sum'), paid_currency=('unit', 'first') ).reset_index() return salary_summary这段代码里,contextRef是XBRL中关联上下文的关键属性,代表了某个数据点所属的期间与实体。需要注意年度过滤逻辑——有些公司披露的是“报告期内从公司获得的报酬总额”,而有些公司额外披露了“期末持股数”,这两个概念不可直接加总。在做“高管薪酬与持股”这类统计时,正确方式是把年度薪酬和计划认购金额视为两个独立变量,分别做描述性统计并计算Pearson相关系数,而不是相除得到一个误导性的倍数。
5. Spark大数据分析员工持股与薪酬数据的进阶能力
5.1 为什么当数据量增长时需要迁移到Spark
当把时间跨度拉长到十年以上、且同时纳入全市场五千余家上市公司的高管薪酬明细后,单机pandas在应对全内存聚合时会明显力不从心。尤其在做“多家公司连续多年持股计划与薪酬数据”的关联分析时,数据规模会从几十万条膨胀到千万级。此时把数据导入Spark做分布式处理是标准的进阶路径。相比数据库与单机脚本,Spark的强项在于多阶段转换和宽表关联时能够将中间结果缓存于内存,避免频繁读写磁盘。
金融领域做员工持股计划和薪酬数据时,我通常用Spark完成两部分工作:第一步是十亿行级别的大宽表构建,把员工明细、薪酬、股价行情三张事实表做全量左连接;第二步是滚动窗口聚合,计算“过去三年高管股权激励累计值”。
5.2 用Spark SQL完成全量关联统计的关键步骤
Spark支持直接读取SQLite的表文件,但为了获得最佳性能,我会先把数据目录指定为Parquet格式存储。下面的代码演示了如何在Spark环境中计算每年各行业高管薪酬与持股比例的汇总值:
from pyspark.sql import SparkSession from pyspark.sql.functions import col, year, sum as spark_sum spark = SparkSession.builder \ .appName("ESOP_Executive_Analysis") \ .config("spark.sql.shuffle.partitions", "200") \ .getOrCreate() # 读取Parquet格式的历史数据 salary_sdf = spark.read.parquet("data/salary/*.parquet") plan_sdf = spark.read.parquet("data/plan/*.parquet") employee_sdf = spark.read.parquet("data/participant/*.parquet") # 关联计划与参与人,计算高管份额 exec_participation = employee_sdf.filter(col("category") == "高管") \ .join(plan_sdf.select("plan_id", "company_code", "announce_date"), "plan_id") \ .withColumn("year", year("announce_date")) # 关联薪酬和持股数据,按年度与行业聚合 analytics_result = salary_sdf.alias("s") \ .join(exec_participation.alias("e"), [col("s.company_code") == col("e.company_code"), col("s.year") == col("e.year")]) \ .groupBy(col("s.year"), col("s.industry")) \ .agg( spark_sum("e.amount").alias("exec_purchase_amount"), spark_sum("s.total_salary").alias("total_salary") ) analytics_result.orderBy("year", "industry").show(50, truncate=False)关键参数的说明:spark.sql.shuffle.partitions控制shuffle阶段的并行度,默认200对金融数据这种千万级规模是合理的起点,如果集群内存紧张可调低到120。两表关联时使用复合键[company_code, year]能够避免把同一公司不同年度的数据错误关联;由于历史原因,同一股票代码在2014到2025年间存在极少数退市后重新上市的情况,因此复合键不要只用公司代码。
5.3 用Spark弥补报表层面的统计空白
Spark计算结果通过write.mode("overwrite").parquet(...)落到分析服务器上的共享目录,这样Excel与Python脚本都可以读取,同时避免了单机内存溢出的问题。在金融数据分析场景中,这条链路比直接在关系型数据库里跑大JOIN要快得多——磁盘读写的瓶颈不会成为约束。
6. 验证与进阶技巧:用净收益还原法校验统计口径
员工持股计划在锁定到期后的实际表现,是回溯检验整个数据分析流程质量的最有效手段。常见的验证算法是做一次“净收益还原法”:以计划公告时的认购价格加杠杆成本为基准,计算锁定期结束后按市价计算的总收益率,扣除资金成本后得到员工的实际激励水平。这个结果反过来可以验证前面采集的source_price字段是否准确——如果批量计算出的收益率分布中出现极端值(比如年化超过500%),基本可以判定原始价格字段存在单位错误或复权遗漏。用Python实现的核心逻辑如下:
# 净值还原法验证 def validate_esop_profile(plan_row, market_prices): cost = plan_row['employee_total_amount'] # 杠杆型计划需计算融资成本(设定7%年化) leverage_ratio = plan_row.get('leverage_ratio', 1.0) years_held = (plan_row['unlock_date'] - plan_row['announce_date']).days / 365.0 if leverage_ratio > 1: borrowed = cost * (leverage_ratio - 1) / leverage_ratio finance_cost = borrowed * (pow(1.07, years_held) - 1) else: finance_cost = 0.0 total_invest = cost + finance_cost # 解锁日均价为参考卖出价 unit_price = market_prices.loc[plan_row['unlock_date'], 'close'] proceeds = plan_row['total_shares'] * unit_price net_return = (proceeds - total_invest) / total_invest return net_return plan_df.apply(lambda r: validate_esop_profile(r, price_table), axis=1)这段脚本中的关键参数是年化融资成本7%,它应当根据当年市场利率水平做动态调整,2021年后建议使用同期贷款市场报价利率加200个基点。当net_return分布呈现双峰形态时,前一个峰对应完全自有资金的计划,后一个峰对应高杠杆计划,这种结构本身就是金融领域员工持股计划运营特征的直接证据。将验证结果与薪酬数据合并画散点图,还能观察出一个盈利能力强的公司是否倾向于设计更高杠杆的持股计划——这是判断长期激励与公司业绩真实关联度的重要视角。
本文还有配套的精品资源,点击获取