一个结论可不可信,取决于三件事:口径对不对齐、数据有没有读错、结论对假设敏不敏感。本页按这个顺序交代:先定义公式和「可投资利润」口径;再说明数据从哪来、经过了哪几步;然后列出过程中真实遇到的数据问题以及处理方式;最后是校验与敏感性分析——结论在换口径、剔除亏损股之后是否依然成立。
偏离度i = 权重占比i ÷ 可投资利润占比i
可投资利润i = 归母净利润TTMi × 自由流通比例i
行业 i 的权重占比 = 行业内成分股权重之和 ÷ 全部权重;利润占比同理,用可投资利润加总。
沪深300 按自由流通市值加权,而财报净利润是公司 100% 股本对应的利润。两者直接相除,分子分母口径不一致。
| 例子 | 自由流通比例 | 归母净利TTM(亿) | 可投资利润(亿) | 影响 |
|---|---|---|---|---|
| 建设银行 | 2.5% | — | — | 全样本最低,大量股本为国家持有或 H 股 |
| 农业银行 | 8.2% | 2,979.1 | 243.6 | 不调整会把利润放大 12 倍 |
| 工商银行 | 8.8% | 3,741.4 | 327.4 | 同上 |
| 工业富联 | 16.2% | 469.1 | 76.0 | 大股东控股比例高 |
| 伊利股份 | 91.0% | — | — | 全样本最高,几乎不受影响 |
若行业权重占比 ≈ 自由流通市值占比(记 M 为自由流通市值,P 为可投资利润):
偏离度i ≈ (Mi/ΣM) ÷ (Pi/ΣP) = (Mi/Pi) ÷ (ΣM/ΣP) = PEi ÷ PE指数
前提是否成立要实测:本期 300 只个股「权重 ÷ 自由流通市值占比」中位数 1.002,90% 落在 0.85–1.15。行业层面用两种口径算出的偏离度最大差 0.17(通信服务 2.27 vs 2.10),其余均在 0.06 以内。
数据接口返回的是自然语言问答结构,最容易出现「列读错位但不报错」的静默错误。此前在恒生指数系统上吃过亏:兜底逻辑把 PE 读成了股息率。所以这次每一层都要求精确命中:
| 层级 | 检查内容 | 不符时 |
|---|---|---|
| 业务状态 | code == 1 | 报错 |
| data 字段 | 必须是 JSON 字符串,解码后含 answer | 报错并提示「返回结构又改了」 |
| 表头 | 按完整列名精确匹配,不按位置 | 报错并打印实际表头 |
| 行对齐 | 按证券代码对齐;出现重复或未请求的代码 | 报错 |
| 指标口径 | 行业级别 = 一级行业;利润单位 = 元 | 报错 |
| 数值 | 只接受纯数字或「万 / 亿 / 万亿」后缀 | 报错 |
| 自由流通比例 | 必须在 (0,100],且与「自由流通股 ÷ 总股本」相差 ≤ 0.5 个百分点 | 报错,拒绝写入 |
| 缺失值 | 仅「--」「-」、空值等显式标记算缺失 | 记为缺失并列出,不补 0 |
| # | 问题 | 如果没发现会怎样 | 处理 |
|---|---|---|---|
| 1 | 最初拿到的「指数成分股数据.xlsx」只有 50 行,仅前 10 只有权重(合计 23.66%),行业为申万三级 | 样本严重不完整,行业口径也不对 | 弃用,改从中证官网下载正式权重文件 |
| 2 | 中证列名写作「成份券」而非「成分券」,权重列为小写 weight | 找不到列 | 补充列名别名;删除会同时匹配中英文名称两列的歧义别名 |
| 3 | 原配置把「交易所英文名称」列为行业列的第一候选 | 「Shanghai Stock Exchange」被当成行业 | 删除该候选;行业改由 iFinD 补齐 |
| 4 | 接口的 data 字段是 JSON 字符串(需二次解码),业务数据在 markdown 表格里 | 典型的「'str' object has no attribute 'get'」 | 显式两层解码 + 表格解析 |
| 5 | 同一接口两次返回的列顺序不同,行顺序也与请求顺序无关 | 按位置取值会列错位——正是恒指事故的同类问题 | 只按表头名取列、按证券代码对齐行 |
| 6 | 自由流通比例以百分数返回(43.27 而非 0.4327) | 可投资利润放大 100 倍 | 除以 100,并用股本数交叉验证 |
| 7 | 批量查询漏行:沪硅产业(TTM 亏损 21.06 亿)在 25 只一批的查询里没有返回,单独查询则有数据 | 若按缺失剔除,亏损股被悄悄移出,信息技术利润被高估 | 脚本显式列出缺失,断点续传单独补取;另换批次组合随机抽检 20 只,数值 20/20 一致 |
| 8 | 走本机代理时连接在第 8 批中途断开 | 取数中断 | 国内接口改为直连;仅对网络层错误重试,业务错误不重试;断点续传保证已取数据不丢 |
| 9 | 只查自由流通市值时,返回表不带日期列 | 无法确认取到的是否为 2026-08-31 的值 | 附带查询收盘价,使返回带「日期」列,并逐行断言日期 |
| 校验 | 判据 | 结果 | 结论 |
|---|---|---|---|
| 利润口径是 TTM | 贵州茅台应在 850 亿量级;若为 200 多亿则误取单季度 | 8.14e10(814.3 亿) | 通过 |
| 自由流通比例是小数 | 全部应在 0–1;出现 35、68 说明是百分数 | 0.025(建设银行)~ 0.910(伊利股份),越界 0 条 | 通过 |
| 隐含 PE 量级 | 可投资利润合计反推,应在 12–18 倍;3 倍或 60 倍说明数据有误 | 试跑 30 只 17.2x;全样本 24.31 万亿 ÷ 1.43 万亿 = 17.05x | 通过 |
| 批量取数一致性 | 换批次组合重查,数值应完全一致 | 随机 20 只:一致 20,不一致 0,漏行 0 | 通过 |
| 权重 vs 自由流通市值 | 两者占比之比应接近 1(中证采用分级靠档,会有偏差) | 中位 1.002;5%–95% 分位 0.85–1.15 | 通过 |
第 1 项:茅台 814 亿比预设的 850 亿低约 4%,属真实利润水平差异;与单季度(约 200 亿)差距明确,口径无误。
三列分别对应:原口径(中证权重);把权重替换为 iFinD 自由流通市值(检验分级靠档误差);剔除全部亏损股后重算(检验亏损股是否扭曲比值)。
| 行业 | 权重% | 原口径 | 自由流通市值口径 | 剔除亏损股 | 亏损股权重% | 结论是否变化 |
|---|---|---|---|---|---|---|
| 科技制造 | 48.68 | 1.88 | 1.87 | 1.89 | 1.52 | 不变 |
| 信息技术 | 22.56 | 2.94 | 2.96 | 3.03 | 0.64 | 不变 |
| 通信服务 | 9.88 | 2.27 | 2.10 | 2.44 | 0.06 | 不变 |
| 工业 | 16.24 | 1.17 | 1.19 | 1.12 | 0.82 | 不变(贴近 1.2 上沿) |
| 金融地产 | 20.17 | 0.44 | 0.46 | 0.44 | 0.22 | 不变 |
| 上游资源品 | 12.28 | 0.93 | 0.92 | 1.01 | 0.05 | 不变 |
| 消费 | 11.79 | 1.11 | 1.10 | 1.09 | 0.77 | 不变 |
| 医药生物 | 4.31 | 1.83 | 1.79 | 1.57 | 0.19 | 仍 ≥1.5,但受亏损股影响明显 |
| 公用事业/交运 | 2.77 | 1.05 | 1.10 | 1.14 | 0.00 | 不变 |
| 行业 | 前 30 名试跑 | 全部 300 只 |
|---|---|---|
| 科技制造 | 2.34 | 1.88 |
| 金融地产 | 0.42 | 0.44 |
| 医药生物 | 1.59 | 1.83 |
| 上游资源品 | 0.80 | 0.93 |
前 30 名里集中了四大行和中国平安,金融拿走了样本内 54% 的利润,其他行业的偏离度被系统性抬高;样本内工业只有宁德时代一家。试跑的用途只是验证数据质量。
# 1. 从中证指数官网下载最新 000300closeweight.xls 放入项目目录
# 2. 清掉上期缓存
del profits_partial.csv industry_partial.csv
# 3. 取数(行业 + 利润TTM + 自由流通比例,分批、可断点续传)
python fetch_profits.py --weights 000300closeweight.xls
# 4. 两层口径报告
python report_levels.py --weights 000300weights_with_industry.csv ^
--profits profits_partial.csv --save detail_full_industry.csv
| 文件 | 作用 |
|---|---|
| weight_profit_gap.py | 计算核心:解析权重、口径对齐、行业聚合、判读与渲染(仅按实际列名补了别名表) |
| ifind_client.py | iFinD 数据接口客户端:两层解码、表格解析、中文数值解析,结构不符即报错 |
| fetch_profits.py | 分批取数、口径参数校验、自由流通比例交叉验证、断点续传 |
| report_levels.py | 同一计算函数输出六大类与中证一级行业两层结果 |