以 ETF 跟踪的指数代码为维度,汇总统计 A股场内 ETF 的份额变化,输出可按变化幅度排序的 HTML 报告。
份额数据本地优先:本地缺失时自动到沪深交易所官方报表补全历史日频数据,无需第三方数据库或付费数据源。
- 指数维度汇总:把全部 ETF 按跟踪指数归并,同一指数的多只 ETF(如 300ETF、300增强ETF)份额合并统计
- 多窗口变化:最近 1日 / 1周 / 1月 / 3月 / 6月 份额变化,单位「亿份」,同时给出变化率
- 下钻明细:点击任意指数行,弹出该指数成分 ETF 明细(代码 / 名称 / 份额总数 / 各窗口增减),明细表同样可点击表头排序,底部有合计行
- 本地增量存储:SQLite 落库,每次运行只补缺失部分;首次运行自动回溯约 6 个月历史
- 官方数据源:份额取自上交所/深交所官方披露报表(权威口径),非估算值
- 可排序报告:HTML 表格支持点击表头按任意列升降序排序,默认按最近 1日变化降序(无数据的行恒排末尾)
- 不重复抓取:记录上次成功抓取的时间与数据版本,当日数据已拿到就跳过网络抓取(可直接
--force覆盖) - 一键看报告:默认运行会自动判断「抓数据 → 生成报告 → 打开报告」,报告已是最新则直接打开
- 零第三方依赖:仅用 Python 标准库(含自行实现的 xlsx 解析),
python main.py即可运行
| 项 | 要求 |
|---|---|
| Python | >= 3.8(已在 CPython 3.13 / 3.14 实测通过;仅用到 f-string、dict 保序等 3.6/3.7 特性) |
| 第三方包 | 无。requirements.txt 仅含说明注释,pip install -r requirements.txt 是空操作 |
| 其他 | 无需虚拟环境、无需 C 编译工具链;仅需可访问上交所/深交所/天天基金/中证/国证等公开接口 |
pip install -r requirements.txt # 空操作,仅为兼容 CI:本项目无第三方依赖
python main.py # 日常使用:按需抓数据 → 按需生成报告 → 自动打开报告
python main.py update # 强制抓最新数据并重建报告
python main.py report # 仅用本地数据重建报告(不打开)
python main.py open # 仅打开已有报告
python main.py index # 仅修复跟踪指数映射(补缺失 + 纠正错误)
python main.py --force # 忽略「今日已抓取」,强制重新抓数据后打开报告
python main.py --no-open # 做完但不打开报告(适合定时任务)首次运行需抓取 6 个月历史与全部指数映射,耗时较长(约 10~20 分钟,取决于网络);之后的日常运行通常几秒内完成(命中缓存直接打开报告)。
报告输出:output/etf_index_share_report.html
程序把「上次成功抓取」的时间与数据版本记在本地库的 run_state 表中,默认运行(python main.py)按下面的顺序判断:
| 条件 | 行为 |
|---|---|
| 今日已成功抓取,且本地快照已包含今日数据 | 跳过抓取 |
| 今日为非工作日 | 跳过抓取(无新增数据) |
当前未到份额日报发布时点(config.DATA_READY_HOUR,默认 18:00) |
跳过抓取 |
| 今日已在发布时点之后抓取过 | 跳过抓取 |
| 其他情况(今日没抓过 / 上次核心数据抓取失败 / 上午抓过但已过发布时点) | 联网抓取 |
跳过抓取后,只有在报告缺失或报告所依据的数据快照已落后于本地数据时才会重新生成报告,否则直接打开现有报告。
只有核心数据(沪深交易所日频份额)本轮都成功获取才会被记为「今日已抓取」;任一失败则下次运行自动重试。要强制刷新,用 python main.py --force 或 python main.py update。
| 列名 | 说明 |
|---|---|
| 指数代码 | ETF 跟踪指数的代码 |
| 指数名称 | 指数名称 |
| 份额总数 | 该指数下全部 ETF 的最新总份额(亿份) |
| 最近1日 / 1周 / 1月 / 3月 / 6月 | 各窗口的份额变化(亿份)与变化率 |
点击任意指数行可弹出该指数的成分 ETF 明细(ETF 代码、名称、份额总数、各窗口增减),明细支持排序并有合计行。
| 用途 | 来源 |
|---|---|
| ETF 全量名单 | 天天基金 fundcode_search.js(按场内代码规则过滤) |
| 沪市日频份额 | 上交所 query.sse.com.cn ETFGM 报表(按 STAT_DATE 单日查询,可回溯) |
| 深市日频份额 | 深交所 scsj_fund_jjgm 报表(按日期区间导出 xlsx,单次最长 6 个月) |
| 深市拟合指数 | 深交所 ETF 列表(CATALOGID=1945),含拟合指数代码与名称 |
| 指数名称字典 | 中证指数官网全量清单(exportExcel/indexAll/CH,约 3000 条)国证指数官网全量清单( index/indexList,约 1480 条) |
| 跟踪指数代码 | 东方财富基金详情接口 INDEXCODE 字段(覆盖境外/债券/主题指数) |
| 兜底份额快照 | 腾讯财经行情(总市值 ÷ 最新价),仅用于交易所报表未覆盖的代码 |
| 历史季度份额 | 天天基金 F10「规模变动」期末总份额(补足更早历史) |
data/index_alias.json手工别名映射(境外/债券指数,优先级最高)- 官方指数字典(中证/国证官网全量清单)精确匹配 → 归一化匹配
- 东方财富基金详情接口
INDEXCODE(权威字段,带限速与重试) - 东方财富指数搜索接口兜底
etf_share_monitor/
├── config.py # 全局配置:路径、请求头、统计窗口、并发与限速参数
├── storage.py # SQLite 封装(etf_meta 元数据表 + shares_daily 份额快照表 + run_state 运行状态表)
├── datasource.py # 全部外部数据源接口封装
├── index_dict.py # 中证/国证官网全量指数字典(含本地缓存与名称归一化)
├── updater.py # 数据更新:本地优先,缺失自动补全 + 指数映射纠错
├── report.py # 按指数维度汇总计算 + HTML 报告渲染
├── main.py # 命令行入口(智能跳过重复抓取 / 按需生成报告 / 自动打开)
├── requirements.txt # 依赖清单(无第三方依赖,仅含说明注释)
├── data/
│ ├── index_alias.json # 手工别名映射(跟踪标的名称 → 指数代码)
│ ├── index_dict.json # 指数字典缓存(自动生成)
│ └── etf_shares.db # 本地 SQLite 库(自动生成)
├── skills/
│ └── etf-share-monitor/
│ └── SKILL.md # Agent Skill:数据源/接口/坑位清单(见下节)
└── output/
└── etf_index_share_report.html # 报告产物
skills/etf-share-monitor/SKILL.md 是本工程的配套 Agent Skill,把踩过的坑固化成了可复用文档:
- 全部数据源接口清单:精确 URL、参数、请求头、返回格式与字段映射(含「必须 POST」「仅支持 xlsx」「限流极严」等关键约束)
- 采集策略:沪市逐日回溯 vs 深市区间请求,以及各窗口基期的容限规则
- 跟踪指数映射的五级优先级与名称归一化规则、补全与纠错必须分开的原因(同名不同机构指数)
- 已知坑位:入库元组顺序、货币型代码过滤、f-string 内嵌 JS 的转义、东财接口限流退避等
- 浏览器端验证方法:用
playwright-core+ 本地缓存的 chromium 无头实例做交互断言
安装到 WorkBuddy:把 skills/etf-share-monitor/ 目录放到 ~/.workbuddy/skills/ 下即可(用户级),或放到项目的 .workbuddy/skills/ 下(项目级)。
各窗口的基准份额选取规则:在 [目标日 - 窗口天数 - 容限, 目标日 - 窗口天数 + 容限] 区间内取距目标日最近的记录,且基期须早于当前快照 窗口天数/2 天以上,避免基期与当前过于接近导致变化被低估。容限天数见 config.py 的 WINDOWS 配置。
本项目数据均来自各官网公开接口,仅供研究与学习使用,不构成任何投资建议。数据口径与准确性请以官方披露为准。