上市公司股本历史查询
何时使用
- 想看一家公司历年底股本怎么变的
- 判断回购到底有没有用(回购注销掉了,还是被股权激励增发吃回去了)
- 判断每股指标是被稀释还是真实增长(净利润涨了 ≠ 每股赚得多了)
- 批量给几十上百家公司出一份股本对比报表
- 还原跨拆股/合股的长周期数据(否则 2015 年的股价和 EPS 跟现在没法直接比)
数据源与前置条件
⚠️ 给安装者的话:这个技能依赖本机 westock-mcp(腾讯自选股)连接器拿数据。
第一次用之前,请确认你的 WorkBuddy 已经连接了「腾讯自选股」连接器,否则所有取数都会失败、脚本只能报"数据不足"。
其他数据源:wind-finance(万得)可用于交叉校验。
这样设计的好处是不需要任何 API Key、不用装 Python 包,连上就能跑。
关键字段地图(照抄,别自己猜)
调用 data_finance 时,不同市场返回的字段名不一样,这是最大的坑,背下来:
type=income(利润表,用来算加权平均股本)
| 用途 | A股字段名 | 港股字段名 |
|---|---|---|
| 归母净利润 | NPParentCompanyOwners |
ProfitToShareholders |
| 基本每股收益 | BasicEPS |
BasicEPS |
| 稀释每股收益 | DilutedEPS |
EPS |
type=balance(资产负债表,用来算期末股本)
| 用途 | A股字段名 | 港股字段名 |
|---|---|---|
| 期末总股本 | PaidInCapital(实收资本) |
需计算:SeWithoutMinority / NetAssetPS |
| 归母净资产 | SEWithoutMI |
SeWithoutMinority |
| 每股净资产 | NAPS |
NetAssetPS |
> ⚠️ A 股别只信 PaidInCapital:回购的股票没注销时挂在库存股里,实收资本纹丝不动,会误判成"零回购"。
> 统一用 SEWithoutMI / NAPS 反推更灵敏也更能跨市场对齐,PaidInCapital 只拿来交叉校验。
> 实测:茅台 2025 两者都对上了(说明回购已注销),腾讯用反推法算出 91.20 亿股,与年报披露一致。
data_dividend(送转折算用)
bonusShareRatio 送股、tranAddShareRatio 转增,单位「每10股X股」。
港股接口返回空 {} —— 不是报错,别反复重试。
三条股本口径,别混用(分子一律是归母净利润,港股叫 ProfitToShareholders,A股叫 NPParentCompanyOwners):
加权平均股本 = 归母净利润 / BasicEPS← 算 EPS 的那个分母,判断回购效果用它最准稀释股本 = 归母净利润 / 稀释EPS← 港股用EPS,A股用DilutedEPS;反映假设期权全转股后的潜在稀释期末总股本← 12月31日那一瞬间的股数,反映累计结果
工作流
Step 0 · 定代码
不确定代码先 data_search(query="腾讯")。格式:sh600519 / sz000001 / hk00700。港股用 hk 前缀。
Step 1 · 拿 EPS 序列(便宜,一次拿好几年)
data_finance(code=XXX, type="income", fields="core", num=20, start="YYYY-12-31", end="YYYY-12-31")
core 窄表已经包含 BasicEPS、稀释EPS、归母净利润(字段名按上面的地图对),一次能覆盖约 5 年。只挑 EndDate 是 12-31 的年报记录,中报半年报丢掉(半年报的 EPS 不能用于全年股本反推)。
要覆盖 10 年就分两段请求(每段 ~20 期),别一次 num=60,返回会炸。
Step 2 · 拿期末股本(逐年逐个来,贵但准)
data_finance(code=XXX, type="balance", fields="all", num=1, start="YYYY-12-31", end="YYYY-12-31")
逐年请求。统一取 SEWithoutMI / NAPS(A股)或 SeWithoutMinority / NetAssetPS(港股)反推期末股本,PaidInCapital 额外记下来做交叉校验。
⚠️ 别用 fields=all + num=N 一次拉多年:报告期是按「全部披露期」倒序数的,num=8 从年末往回数只够覆盖 2 个年报,剩下 6 条全是中报季报,白烧 token。num=1 逐年打点才省。
默认只做近 5 年,用户明确要长周期才拉 10 年——50 家公司 × 10 年 = 500 次调用,会跑很久,先跟用户说一声。
Step 3 · 拿送转明细(A股专属)
data_dividend(code=XXX, years=N)
看 bonusShareRatio(送股)和 tranAddShareRatio(转增),单位都是「每10股X股」。注意同一年度可能有多条记录(年报 + 中报 + 特别分红),按 reportEndDate 归到年度。
港股这个接口返回空(实测确认),别浪费调用。
Step 3.5 · 拿回购明细(可选,但能大幅提升说服力)
data_buyback(code=XXX, start="YYYY-01-01", end="YYYY-12-31")
⚠️ 这个接口输出巨大,务必按下面的方式处理,别硬读:
腾讯这种天天回购的公司,一次请求就是 10 万+字符,工具会自动把结果落盘到 tool-results/*.txt,而不是塞进对话。这时候:
- 不要试图用 Read 工具去读那个文件(大概率被权限拦下)
- 用 python 脚本读它、解析 JSON、按年聚合,只把聚合结果带回对话:
import io, json, collections
p = r"<tool-results 里的 txt 路径>"
d = json.loads(io.open(p, encoding="utf-8").read())
items = d["data"]["items"] # 每条: date + hkBuybackInfo.{BuybackShares,BuybackMoney}
agg = collections.defaultdict(lambda: {"sh": 0.0, "money": 0.0, "n": 0})
for it in items:
b = it.get("hkBuybackInfo") or {}
a = agg[it["date"][:4]]
a["sh"] += float(b.get("BuybackShares") or 0)
a["money"] += float(b.get("BuybackMoney") or 0)
a["n"] += 1
一次把整个区间(10年)拉下来聚合是没问题的,就是别让原文进上下文。记得先打印 items[0]["date"] 和 items[-1]["date"] 确认覆盖范围——接口有返回条数上限,超长的区间可能只覆盖最近若干年。
聚合结果按年填进 JSON 的 buyback_shares / buyback_amount(单位统一为「股」和「元」)。某年确实没回购就填 0,别留 null,否则 script 会误判成"数据缺失"。
Step 4 · 落中间 JSON
边查边写,别攒在脑子里。每查完一家就写进 capital_data.json,格式见 references/schema.md。
这一步很重要:原始接口返回动辄几万字符,全留在对话里会爆上下文。
Step 5 · 出报表
python scripts/build_report.py capital_data.json --output-dir <报表目录>
生成 report.md + report.html(HTML 带股本走势条形图,可直接浏览器打开)。
折算口径(这是本技能的核心价值)
历史发生了送股/转增/拆股,直接比历年股本会得出荒谬结论(比如 10送10 会让股本翻倍,看起来像"疯狂稀释",其实是零和游戏)。
累计折算因子:
cum_factor[t] = Π(k=t+1 → 最新) (1 + (bonus[k] + transfer[k]) / 10)
把第 t 年的股本 × cum_factor[t],就折算成了「如果当年就持有现在这么多股」的最新口径。
同样地,历年 EPS、每股净资产、每股分红全部要除以各自年份的 cum_factor,才能跟最新年份直接比。
缺数据怎么办(港股送转数据):
用突变检测兜底——如果某年期末股本 / 上年 > 1.5 或 < 0.7,且同年 EPS 同比例反向变动,判定为拆股/合股事件,把该年比例当作折算因子用,并在报表里明确标注「⚠ 折算因子为推断值」。
判定标准
| 股本年化变动率(5年 CAGR) | 判定 | 含义 |
|---|---|---|
| < -1%/年 | 🟢 持续净缩股 | 回购注销跑赢了股权激励,股东友好 |
| -1% ~ +1%/年 | ⚪ 基本持平 | 回购被增发抵消,中性 |
| +1% ~ +3%/年 | 🟡 温和稀释 | 股权激励为主,可接受但要盯着 |
| > +3%/年 | 🔴 明显稀释 | 靠增发融资,每股价值被持续摊薄 |
再叠一条 稀释股本 / 加权平均股本 比率:>1.05 说明账上有大量未转股的期权/可转债,未来还有隐性稀释。
有回购数据时,一定要算「隐含股权增发」
这是整个分析里最有杀伤力的一个数:
隐含股权增发 = 当年回购注销股数 + 当年股本净变动
原理:股本变动 = 增发 − 注销。回购注销进去多少、股本最后净变了多少,差额就是公司这一年实际新发给了员工/股东的股票。
它回答的是那个真问题:这家公司每年要给股权激励"交多少保护费"? 回购必须持续超过这个数,股东手上的每股权益才会真正变多。
判读:
- 回购 < 隐含增发 → 回购白干了,只是把股权激励的稀释对冲掉,股东没占到便宜
- 回购持续 > 隐含增发 → 真·每股价值增厚(腾讯 2022 年起就是这个状态)
- 看转折点:回购量首次超过增发量的那一年,往往就是公司现金流成熟、股东回报意识觉醒的分水岭
长跨度要看分段,别只看总年化
8 年以上的序列,脚本会自动算前后半段 CAGR并标出转折点。原因:像腾讯 2016–2025 总年化只有 -0.35%(看着像"基本持平"),但拆开是前 5 年 +0.28%、后 4 年 -1.30%——2021 年是分水岭。只看总数会把这个关键变化抹掉。
实测样例(已验证,可直接复用)
2021–2025 年报,腾讯 + 茅台:
| 公司 | 期末股本变化 | 年化 | 判定 | 净利年化 | EPS年化 |
|---|---|---|---|---|---|
| 腾讯控股 hk00700 | 96.08亿 → 91.20亿 | -1.18% | 净缩股 | -2.46% | -1.29% |
| 贵州茅台 sh600519 | 12.56亿 → 12.52亿 | -0.05% | 基本持平 | +11.92% | +11.98% |
自洽性检验(每次出报告都要看这一眼):腾讯净利润年化 -2.46% 但 EPS 只跌 -1.29%,差额来自股本收缩;茅台 EPS 增速 +11.98% 略高于净利润 +11.92%,同样是股本微降贡献。如果 EPS 增速明显高于净利润增速,一定有股本变动在背后,去查它。
脚本注意事项
build_report.py 只依赖 Python 标准库。改脚本时注意:本机 Python 解析器对 f-string 内嵌套同类引号会报 SyntaxError,写 f"...{VCOL.get(k,'#000')}..." 这种要先提成变量再拼,别省这一步。
坑位清单
- 港股
data_dividend返回空{}—— 不是报错,是接口不支持,别反复重试。 data_buyback输出巨大(一次 11 年能到 10 万+字符)。要用单年窗口请求:start="2025-01-01", end="2025-12-31"。批量场景干脆跳过它,用股本净变动就能倒推出回购的净效果。- 货币单位:港股报表是港币计价。股本计算是「净资产÷每股净资产」,单位上下抵消,所以股数不受币种影响;但引用金额数字时要标币种。
- 别把中报当年报:只取
EndDate后缀12-31的记录。 - 务必以定期报告为准。第三方接口的股本、回购数据存在错漏,本技能算出的股本用于趋势判断足够,涉及交给别人的精确数字要回年报复核。
- 股数单位:接口给的是「股」,报表统一换算成「亿股」保留 2 位小数,别让用户在 9120114321 这种数字上数零。
交付习惯
- 报表放
<工作区>/reports/股本历史_<日期>/ - 最后一定要用
present_files把report.html打开给用户看 - 正文回复给结论:哪些公司在真回购注销,哪些在悄悄稀释,别让用户自己看表