Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
12 家公司的月度经营指标看板:成交量 / 账户 / 资产 / 余额 / 营收,数据取自公司官网 IR 与 SEC 申报,版式仿 Goldman Sachs GIR exhibit
Translation pending
FinancePython · JavaScriptGitHub @hzhan7OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
28 家公司按月披露的经营数据,做成一套交互看板,版式仿 Goldman Sachs GIR exhibit。 数据全部来自公司官网 IR 或 SEC 申报的原始披露,不含任何券商研报的观点或数据。 一行一族(与导航分行一致,顺序照 build/roster.py 的 GROUPS): 横截面七张页各管一件事:/exchanges12/ 12 家交易所总览(定基名义额)、 /exchanges-na/ 北美真份额、/exchanges-eu/ 欧洲现货竞争、/exchanges-apac/ 亚太增长对比、 /exchanges-products/ 标的轴(利率 / 股指 / 单股ETF期权 / 能源 / 农产品 / FX 即期)、 /wealth/ 财富管理组、/semi/ 半导体组(台股 7 家的月营收横截面,2026-09-22 新增)。Read the full README ↗
28 家公司按月披露的经营数据,做成一套交互看板,版式仿 Goldman Sachs GIR exhibit。 数据全部来自公司官网 IR 或 SEC 申报的原始披露,不含任何券商研报的观点或数据。
看板地址: https://hzhan7.github.io/Monthly-OP-Dashboards/
一行一族(与导航分行一致,顺序照 build/roster.py 的 GROUPS):
/ 总览(28 家 + 7 张横截面,含各家新鲜度红点)
/ibkr/ /schw/ /lpla/ /hood/ 券商与财富管理
/cme/ /cboe/ /ice/ /ndaq/ /miax/ /tmx/ /enx/ /db1/ /lseg/ /hkex/ /jpx/ /sgx/ /asx/ 交易所(北美 6 → 欧洲 3 → 亚太 4)
/msci/ /spgi/ 数据与指数
/cost/ /axp/ 消费与信贷
/tsm/ /ase/ /mtk/ /nanya/ /umc/ /alchip/ /guc/ 半导体(台湾月度营收,7 家)
/exchanges12/ /exchanges-na/ /exchanges-eu/ /exchanges-apac/ /exchanges-products/ /wealth/ /semi/ 横截面(同组公司放同一张图比)
横截面七张页各管一件事:/exchanges12/ 12 家交易所总览(定基名义额)、
/exchanges-na/ 北美真份额、/exchanges-eu/ 欧洲现货竞争、/exchanges-apac/ 亚太增长对比、
/exchanges-products/ 标的轴(利率 / 股指 / 单股ETF期权 / 能源 / 农产品 / FX 即期)、
/wealth/ 财富管理组、/semi/ 半导体组(台股 7 家的月营收横截面,2026-09-22 新增)。
/semi/ 与另外六张的口径处境不一样,值得单记一句:它是全仓唯一不需要做任何口径转换的
横截面。七家都按台湾《证券交易法》在次月 10 日前公告同一个法定字段(合并营业收入净额)、
都以新台币申报、发布节奏都落在次月第 7–15 天 —— 所以那一页不折汇率、不造定基名义额。
它的难处在另一头:七家在同一条价值链的不同层(代工 / 封测 / IC 设计 / 存储 / ASIC
设计服务),营收互为上下游,相加是重复计算,所以页上没有任何合计与占比,只有增速、
指数与离散度。另外两条页面级约束也写在那份生成器的文件头里:图表引擎只有 6 个数据色而
本页有 7 家(七家同框只能用热力矩阵,折线按制造 / 设计拆两张),以及南亚科的同比量级
不与代工同框(那是 /nanya/ 页自己定的读法,横截面页遵守它)。
原 /exchanges/(CME / Cboe / HKEX 三家旧横截面)已被 12 家版取代,2026-08-07 删除。
LSEG(/lseg/)本轮只上了单公司页,暂未进任何一张横截面页。 /exchanges12/ 仍是
那 12 家、/exchanges-eu/ 仍是 Euronext / Cboe Europe / Deutsche Börse 三家,
build/exchanges*.py 与 data/exchanges-*.js 这一轮逐字节未动。原因不是漏接:LSEG 的四条腿
(LSE 订单簿 / 一级市场 / Tradeweb / LCH)里没有一条与现有横截面池同口径同量纲,
接进去要先定跨口径规则。四条腿各自的口径边界、能与不能配对的理由见 docs/verify/lseg.md。
/cost/ 与 /ibkr/ 原先是两个独立仓库(costco-monthly-sales / ibkr-monthly-metrics),
已整体并入本仓;那两个仓库连同它们的 Pages 站点已于 2026-08-06 从 GitHub 删除。
两家的数据源没有变,但解析管道的位置变了:原先分别借用 /COST月度销售 与
/IBKR月度指标 两个 skill 的代码,那两个 skill 也已删除,代码逐字搬进本仓
(COST 在 fetch/cost_release.py,IBKR 在 fetch/ibkr_source.py),真值 CSV 落到 series/。
本仓现在自包含 —— 不依赖那两个旧仓、也不依赖 ~/.claude 下的任何文件,页面上不留指向它们的链接。
搬迁的验收标准是逐字节而不是「看起来一样」:把两个 skill 移走后重跑生成器,
data/cost.js 与 data/ibkr.js 与搬迁前 0 字节不同(连首行构建日期都相同)。
旧仓的 mirror 备份在 ~/Backups/deleted-repos-2026-08-06/,
两个 skill 的代码在 ~/.claude/skills 的 git 历史里。
index.html 总览页
<ticker>/index.html 35 个页面外壳 —— 里面没有任何公司专属内容
14 个(11 家老单公司页 + tsm + wealth + ice)由 build/make_shells.py 生成;
22 个(6 张横截面 + build/specs/ 与 build/mrspecs/ 下每一家)
由 build/make_shells12.py 生成
(tsm 两处都在,是接入 mrbase 时留下的历史重叠,去重后 35)
⚠ 两份名单加起来的家数**别照抄**:make_shells12 的单公司那一半是
扫目录得来的(specs ∪ mrspecs),加一份 spec 就多一张壳。
横截面那一半是硬编码的 CROSS —— 新增一张横截面页要手动加进去,
漏了不会报错,只会在下次改版式时静默少铺一张壳
assets/charts.js 手写 SVG 图表引擎,零依赖零构建(17 种 kind)
assets/page.js 通用页面渲染器,全部页面共用一份(导航分行读 roster 的 row)
assets/style.css 版式
series/*.csv 历史序列(唯一真值,入库)[注]
一家可以有不止一份:hood_q / axp_trust / lseg_part_* /
ibkr_pr(IBKR 月度新闻稿的佣金口径,2016-02 起)/
hood_ipo_pit、hood_ipo_q(HOOD 在月度披露开始之前的 IPO 申报期时点值与
季度损益,由 build/basefill/hood_ipo.py 一次性回填、不进抓取链路)等
series/cost_seg_q.csv /cost/ 的 SEC 腿,季度/财年分部收入(US / CA / Other Intl)。
⚠ **口径是 TOTAL REVENUE(净销售额 + 会员费),不是净销售额** ——
FY2025 三段合计 275,235 而 total net sales 是 269,912,差的 5,323
正是当年会员费。**不要和 series/cost.csv 的 net_sales_bn 画在同一根轴上**
series/cost_tkt_q.csv 同上,季度 Sales / Ticket / Traffic 分部表(源是 8-K 的 EX-99.2,
比同季 10-Q 早约一周到)
series/cost_fy.csv 同上,财年利润表 + 分国家仓数 + 面积(10-K)。
⚠ 表里有**两列口径标记**,画分部图之前必须先读:
· `seg_oi_basis` —— 分部经营利润在 FY2020 处有口径断点。公司
FY2022 10-K 起把股权激励摊进分部,且只重述了 FY2020-21,
FY2019 及更早从未重述。混着画会读出一段纯属改分摊的假拐点
(实测其他国际 3.83%→3.76% 像是下滑,同口径下其实是上升)。
**合并口径的 `op_income_mn` 不受影响**,断的只是分部拆法。
· `preopen_src` —— 记的是开办费这一笔的**来路**
(loader-folded / issuer-folded / not-disclosed),不是「含没含」:
`sga_mn` 对**全部年份**都已含开办费,`preopen_mn` 是其中那一笔。
所以要算「不含开办费的 SG&A」,无条件 `sga_mn - preopen_mn` 即可,
不要按标记分支(旧名 `sga_folded_preopen` 正是这么被读错的)。
series/cost_cohort.csv 同上,10-K「Average Sales Per Warehouse」开业年份矩阵
fetch/<t>.py 各家的无人值守抓取器:latest_month() / update()
fetch/cost_sec.py /cost/ 的**第二个数据源**:SEC 申报层(CIK 0000909832 的 10-K/10-Q/8-K),
与 fetch/cost.py 并列、各写各的 CSV、互不读写。季度/年度节奏,
由 monthly_run.cost_sec() 单独一步驱动(**不能挂成 fetch/cost.py 的慢腿**,
理由见 docs/CRON_WIRING.md §2.5)
fetch/tsm_6k.py /tsm/ 的第二个数据源:SEC 月报 6-K(CIK 0001046179)第 3/4 项 →
series/tsm_guarantees.csv / tsm_derivatives.csv,由 monthly_run.tsm_6k()
单独一步驱动(理由见 docs/CRON_WIRING.md §2.6)
fetch/fx.py 月度汇率(10 币种对美元,ECB)—— 横截面页的公共底座,不属于任何一家
build/<t>.py 各家的 payload 生成器:series/*.csv → data/<t>.js
build/single.py 单公司页通用底座:build/specs/<t>.py → data/<t>.js(10 家新交易所里的 9 家走这条;
ice 2026-09 改成手写的 build/ice.py,spec 已删)
build/specs/<t>.py 一家一份配置(见 docs/SINGLE_SPEC.md)
build/mrbase.py 月度营收页通用底座(台湾半导体 7 家):build/mrspecs/<t>.py → data/<t>.js
配置契约写在 mrbase.py 自己的 §1(不在 docs/ 里另开一份,
免得字段清单与 validate() 两处走散)
build/mrspecs/<t>.py 一家一份配置;build/<t>.py 是 7 个薄壳入口
build/mrwin.py mrbase 的窗口左端与排版裁决层,可单测(python3 build/mrwin.py)
build/CONTRACT.md payload 数据契约(写生成器前先读它;§6 是全站同比口径)
build/yoy.py 同比口径的唯一实现,所有生成器算同比一律走这里
build/brief.py 页顶数据总结(brief)的规则库,句子由各家生成器自己拼
build/glossary.py 页顶名词释义(glossary)的版式层与护栏,措辞由各家自己写
build/roster.py 生成 data/roster.js(总览页与导航的目录,含各家披露节奏 LAG)
data/<t>.js 生成物,页面直接 <script src> 加载
cache/ 下载来的原始文件(gitignore)
monthly_run.py 月度入口(cron 跑的就是它)
tools/check_yoy_caliber.py 同比口径判据(机检 CONTRACT §6,改生成器后跑)
tools/visual_qa.py 整站视觉 QA(截图 + 机器判据,页名自动取自 data/roster.js)
tools/prune_cache.py cache/ 分级清理(白名单制,dry-run 默认;见「cache 怎么清」)
docs/CRON_WIRING.md 各家的发布节奏与闸门参数、以及「怎么删掉一家」
2026-09 的一处搬家:IBKR 的 CPT(单笔佣金)与平均订单规模原先由 build/ibkr.py
每次构建现场解析 cache/ibkr/*.pdf,而 cache/ 是 gitignore 的 —— 换一台机器或清一次
缓存,那几张佣金图就从十年缩回一两个月,一声不响。现在数值入库到 series/ibkr_pr.csv
(tracked,2016-02 起,每月往右追一行 —— 期数别写死在这里),
build/ibkr.py 只查表、一个 PDF 数字都不解析;
历史由 build/basefill/ibkr_pr_2016.py 一次性回填,fetch/ibkr.py 每月往右追一行
并回看 6 个月补漏。IBKR 那个下载端点对不存在的文件返回 200 + application/pdf +
0 字节、从不 404,所以「下不到」与「官方没发」在 HTTP 层分不开:2016-01 与 2021-10
实测十几种文件名全是 0 字节(登记在 ibkr_source.PR_ABSENT,页面上画成缺口),
而 2016-02..09 / 2017-12 / 2020-03 这 10 期只在词尾多一个 1 的 token 下才有
(ibkr_source.PR_TOKENS 逐个回落)。
[注] 个别 series/*.csv 只被 fetch/ 读回当台账,不进 build —— 「没有 build 读它」
不等于它是断链孤儿。 目前是 series/spgi.csv:它与 spgi_clean.csv 在
fetch/spgi.py 的同一次 update() 里一起写(中间没有「清洗步骤」这一环),
画图只读 spgi_clean.csv,但 spgi.csv 是该模块的去重台账 + 官方重述体检的唯一基线。
删了 = SPGI 抓取当场报错,且从此再也发现不了官方悄悄重述历史。理由写在
fetch/spgi.py 的 update() docstring 里。做孤儿盘点时,判据要用「fetch 或 build
有没有人读它」,只按 build 的读取面盘点必然误杀这一类文件。
入口是 monthly_run.py,一条 cron 管 28 家 + 2 张公共表(费率、汇率)+ 7 张横截面页。
每天跑一次:28 家的披露日从次月 1 号散到 21 号,覆盖全部窗口只能天天开工;
省下来的是「今天该不该下载」那一层判断(not_due()),够新的那几家一个字节都不下。
各家的闸门参数与「怎么删掉一家」见 docs/CRON_WIRING.md
(删一家 = 3 个文件 + 5 行注册,见其 §4;删一张横截面页照 docs/DELIVERY.md §4.4 的实测清单)。
python3 monthly_run.py # 正常:逐家检查,有新数据才 commit + push
python3 monthly_run.py --only cme,cboe # 只跑指定几家
python3 monthly_run.py --dry-run # 抓取与生成都做,但不 commit/push
python3 monthly_run.py --force # 数据没变也重建并推送(改了图表代码后用)
日常只需要两条命令(2026-09-19 起):
python3 tools/rebuild.py # 离线重建全站 data/*.js(约 4 秒);合并分支之后必跑
python3 tools/gate.py # 下面全部校验一次跑完(约 30 秒),末行 GATE OK / FAILED
data/*.js 在 .gitattributes 里是 merge=binary:两边都重建过同一页时,合并会停在 unmerged、
文件里不插冲突标记 —— 不要手挑哪边,跑 tools/rebuild.py 再 git add data/。
轴刻度的小数位由 build/payload_guard.write_dash 统一过一遍 axisfmt.fix_all,生成器不用再各自记得调。
挪图 = 改一张顺序表(2026-09-19 起,机制在 build/exhibits.py 文件头)。已迁移的页每张图带固定 id,
页头一张 ORDER(single.py / mrbase 的页写在 spec 的 order 里)决定先后,写盘时从 2 起编号、核对表接最后;
正文里的图号一律写占位符 ⟨ex:id⟩(本页)/ ⟨ex:页/id⟩(别的页)/ ⟨ex:table⟩(核对表),兑不出来就构建失败。
所以「把 X 图挪到 Y 图后面」= 在 ORDER 里挪一行 → tools/rebuild.py → tools/gate.py,不用找任何一处「Exhibit N」。
「下一张图」「上一张」「上面那张」这种位置词也要写成占位符 ⟨ex:x@+1:下一张图⟩(-1 前一张、@< / @> 上面 / 下面任意位置):
真挨着就原样印那个词,挪开了自动印成「Exhibit N」。条件图(按数据可得性出不出的)只能在同一判据下面点名,否则构建失败。
当前图序:手写生成器看页头 ORDER;spec 页跑 python3 build/single.py <t> --order 现印(整段抄进 spec 再挪)。
验收工具 tools/exhibit_check.py:compare(改前后 payload 逐字比)、audit(找没走占位符的图号)、
drill <页>(不改文件地对调两张图重建,查图头、正文、跨页引用,再复原)。
哪些页已迁移:python3 tools/exhibit_check.py status(现读 data/*.js,名单不写死在这里;2026-09-19 除 ice 外全部迁移)。
改过生成器或引擎之后,五条校验各管一层,谁都替代不了谁(tools/gate.py 就是把它们并起来跑):
python3 build/verify_pages.py # 结构层:payload 契约 + 页面引用,0 ERROR 才算过
python3 tools/check_yoy_caliber.py # 口径层:同比口径判据(CONTRACT §6 的机检)
python3 tools/visual_qa.py --all # 像素层:整站截图 + 机器判据(轴刻度/越界柱/压字)
python3 test_slow_legs.py # 闸门层:monthly_run.SLOW_LEGS 登记表不变式 + miax 现场重放(离线、秒级;改 SLOW_LEGS 时必跑)
python3 tools/check_doc_gates.py # 文档层:CRON_WIRING §2 的闸门表 vs LAG / EARLY_BY / FACT_GATE 真值
最后一条只在改了 build/roster.py 的 LAG 或 monthly_run.py 的 EARLY /
EARLY_BY / FACT_GATE 时才可能红 —— 那张表是手抄的代码常量,抄漏了页面上看不出
任何异常(2026-08-30 给 umc / ase 加 EARLY_BY 那次就漏了,一周后才发现)。
它不在 cron 路径上,理由见脚本头。
改过 fetch/tsm_6k.py、monthly_run.tsm_6k() 或 audit_manual_series() 之后,另手跑
python3 fetch/test_tsm_6k.py && python3 fetch/tsm_6k.py audit && python3 test_monthly_run_tsm6k.py
(同样不在 cron 路径上,同 fetch/test_cost_sec.py)。在 worktree 里跑时 cache/ 不在:前者要设
TSM6K_CACHE=<主 checkout>/cache(否则真缓存重放那组会 skip),audit 要加 --cache <主 checkout>/cache
(否则一份件都读不到,「0 个月」照样退出 0)。
每家输出一行 <ticker> <状态> <说明>,stdout 最后一行是总状态,调度任务只读这一行:
| 总状态 | 含义 |
|---|---|
NOTHING_TO_DO | 28 家都没有新数据(正常,等下次) |
PUBLISHED <sha> <n> <月份串> | n 家更新并已推送,Pages 约 1 分钟后生效 |
PARTIAL <sha> <n> ok / <m> fail | 有更新也有失败,成功的那部分已发布 |
FAILED <原因> | 一家都没成功,或工作树不干净 / push 失败 |
单公司仓库的老脚本是「一有问题就整体退出」——那时只有一家,退出=什么都不做,代价为零。 扩到 28 家后同样的写法意味着:TSMC 官网当天抽风,CME / Cboe / HKEX 已经抓到的新数据 也一起不发布,一家的故障惩罚了另外二十七家。所以这里逐家隔离:失败的那家跳过 (线上仍是它自己的旧数据,不会变成错数据),成功的照常发布,失败清单打在总状态里。
工作树不干净就拒跑。 提交范围只有 data/ 与 series/;这之外(assets/、
index.html、build/)有任何未提交改动时,脚本在下载之前就退出。这个仓库是
手写手改的,没有这道护栏时改到一半的图表引擎会被 cron 连同数据一起 commit 成
「更新数据」推到公开站。(--dry-run 不 commit/push,故只警告不拦。)
缺列一律失败,绝不静默写 NaN 上线。解析结果少任何一个已有列,该家的 fetch 模块直接抛异常。
未到披露期不下载。 28 家的披露日从次月 1 号散到 21 号,要覆盖全部窗口就得
天天跑;但天天把 28 个源全下一遍是浪费(也给对方站点添堵)。所以先用本地
data_through 对照各家的 LAG 节奏表,够新的直接跳过。
闸门比 LAG 提前 EARLY = 5 天开,不要改成和红点一样的 + GRACE。 两者共用
LAG 表但方向相反:红点早响一天是假警报,闸门晚开一天是公开页面挂旧数据一天。
原先两处都写 + GRACE,实测代价是当时在仓的 12 家 × 常规月/季末月共 24 档全部
晚于实际发布日 5-8 天(Costco 2026-08-05 发的 7 月数据,闸门要等到 8-13 才开)。
改成 -EARLY 后 24 档无一迟到,代价是每家每月多打几个空请求。
校验用的「实测发布日」现在有台账兜底:series/source_dates.csv 每月记下各家
官方自述的发布日,随时可以回头核对这张 LAG 表准不准(SPGI 就是这样被抓出来的 ——
它季末月的实测发布日一度在第 15 到第 41 天之间飘。这条已经翻篇:2026-01
数据起官方改成「每月 15 日或顺延」的固定节奏,季末月不再是例外,LAG 与闸门
都已跟着收,见 fetch/spgi.py 的发布节奏一节与 monthly_run.EARLY_BY)。
上面三条护栏管的是「拦住不该发生的事」,它们失败时会喊。真正难查的是另一类 ——失败时安静退出的环节:日志没有红字,最后一行状态也正常,只是某件事没发生。 判据是一句话:
它连续失败十天,和成功十天,在日志里长得一样吗? 一样,就缺一道护栏。
本仓踩过的五处。机制都写在代码现场,这里只留指路:
| 安静失败的地方 | 为什么看不出来 | 现在靠什么拦 |
|---|---|---|
| push 被拒 | main() 末尾的发布段先 commit 后 push。推失败那个提交留在本地,次日在它上面再 commit、再被拒 —— 本地分支永不收敛,每天安静 FAILED 一次;而 NOTHING_TO_DO 的日子压根走不到 push,看不出区别 | sync_with_origin()(完整推理见其 docstring) |
| 跑在非 main 分支上 | 裸 git push 推的是当前分支 → 数据进了别处 → 而 Pages 从 main 根目录发 → 站点静默停更 | 同上 |
| 解析器认不出某行/某列 | 该行被静默丢弃,latest_month 停在上个月,fetch 干干净净报 NOCHANGE,红点与断档检查全都抓不到。这一条 2026-08-19 一天之内独立复发三次:MSCI 把脚注写成裸文本、cboe/ice 手里有自报月却没拿来对账、AXP 的 Certificates 被申报方手误写成单数(2021-02 那期,少算一家)—— 三次的形状都是读不到不抛错 | 用独立于解析器的外部判据对账:fetch/cboe.py 的 _crosscheck_report_month、fetch/ice.py 的 _crosscheck_workbook_month;词形容错见 fetch/axp.py 里 _series_excess_spread 的 docstring |
| 合并冲突时选「保留我的」 | 机械改写行(import 块、月份标签定义)两边都像对的:改动数不变、测试照样绿,于是重构被静默回退 | 验收方式改成重跑原判据(如死 import 扫描),而不是读 diff |
| 多腿源的一条腿降级或晚发 | fetch/lseg.py 逐路 catch 之后只打一行 ⚠,这一家仍是 NOCHANGE、末行不含它,调度任务 tail -60 看不到(2026-09-05 Tradeweb 实例);一级市场 factsheet 晚到时,头条追平后闸门不再去取,模块也没有逾期判据 | fetch 模块的 DEGRADED → monthly_run.LEG_ALERTS → 末行失败清单 + report_leg_alerts();一级市场腿的 _MAX_PUBLISH_LAG_DAYS 逾期护栏 + SLOW_LEGS['lseg'] 登记 |
这五条的共同点:代价不在「出错」,在出错不会被计入。
而且自检要跑在自己身上,不只跑在别人的分支上。 「引用的符号是否真有定义」
这道检查最初是为了审一个旧分支的 README 才写的(那里有 4 行指向不存在的文件),
第一次跑到自己刚写的东西上,就抓出了同款:一处代码注释把发布段称作 publish(),
而全仓没有这个函数(commit + push 内联在 main() 里),那个名字被复制了六遍、
两个人复核都没发现——因为大家都在核「逻辑对不对」,没在核「这段话提到的东西
存不存在」。同理,注释里不要写行号:加一段代码就会让下游行号全错,写死的
行号下一次改动就成假话。
主 checkout 是共用的,而 guard_dirty_tree() 的粒度是全仓:data/、series/ 之外
任何一处未提交改动,整轮 28 家一起拒跑 —— 一个人的私事会变成全局故障。
三条规矩,都是实战踩出来的:
monthly_run.py」。 不要,就去 worktree 做;
主 checkout 留给 cron。git stash,不要 git checkout。
前者可逆,后者销毁的是没提交过的手写源码。存档类分支(如 backup/axp-2016-preRebase)不要合并,处置结论记在 git notes 里:
git notes show cb77376。
data/*.js 首行是构建日期注释,不是数据。monthly_run.py 按「忽略首行的正文比较」
判断 data/ 是否真变了——没变时报 NOTHING_TO_DO,不会产生只有日期不同的 no-op
commit,也不会把页面的新鲜度信号刷成当天。构建日期不进 payload,进了这套判断就
永久失效。
总览页每家一个圆点,红色=按该家自己的披露节奏已经超期。两条设计:
build/roster.py 的 LAG 是 (常规月, 季末月) 两个值。SCHW / HOOD 的
季末月(3/6/9/12)没有独立月报,数值随当季财报走;LPLA 的季末月机制不同
(要等下一个月的 Historical File,见 LAG 里那条注释),但同样晚 2-4 周。
用同一个日子判断会每季度误报一次红点,而每季度假一次的警报,人很快就学会无视了。LAG['spgi'] 因此从 (16, 46)
收成 (18, 18)。两档不同不是越宽越保险:旧的 46 会让真正缺货的季末月拖到
第 51 天才变红。实测发布日全表在 fetch/spgi.py 的 docstring 里,只增不改。cache/ 是 gitignore 的,但它只增不减:2026-08 首次盘点时 711 MB。
清理走 python3 tools/prune_cache.py(默认 dry-run,加 --apply 才真删)。
判据是「谁会再读它」,不是「它有多老」。 按 mtime 一刀切会同时犯两个方向的错: 删掉再也拿不回来的东西,同时留着明天就会自己长回来的东西。四种性质分开处理:
| 性质 | 是谁 | 体积 | 处理 |
|---|---|---|---|
| 不可重下 | basefill/ | 245 MB | 永不清理,且是唯一需要异地备份的部分 |
| 热工作集 | axp/、*_rates/ | 172 MB | 永不清理:fetch/rates_*.py 的 rows() 每次全量重放做重述对账,删了每月重打几百个 SEC 请求 |
| 删了会回来 | lseg_primary_*.xlsx | 38 MB | 不清理:fetch/lseg_primary.py 的 fetch_rows() 每次跑全月份区间,本地没有就重下 |
| 冷档 | POLICY 表登记的族 | 139 MB | 按期次保留最近 N 期 |
basefill/ 里有三类无人值守拿不回来的东西:只存在于 archive.org 的 CME 规则手册、
CME 2019 每日 SPAN 存档(同期 Settlements API 已返回 empty、HTTPS 镜像恒 400,
那条 FTP 是唯一还活着的通道,见 build/basefill/cme2.py 模块头)、
以及 ICE reCAPTCHA 墙后只能人工导出的报表(build/basefill/ice_enx2.py)。
它的备份在 private 仓 hzhan7/dashboards-data-archive,用那边的 backup.sh 刷新。
POLICY 是白名单,不是黑名单。 新加数据源之后它的 cache 文件族默认不在表里、
不会被动;报告末尾单列「未登记的散文件」体积,超过 50 MB 会警告 —— 看见它涨了,
就去核一遍那个 fetcher 的读取语义(update() 是只处理 CSV 里没有的期次,
还是每次全区间重放),再决定要不要登记进来。登记时必须在表里写下依据那一句。
⚠ 跑完 --apply 之后 cache/ 是 gitignore 的所以工作树不脏,但改这个工具本身要提交 ——
monthly_run.py 的第一道闸是「工作树不干净就拒跑」,留着未提交的改动会让第二天的 cron 直接停摆。
各家的源、发布节奏、口径坑写在 fetch/<t>.py 的模块 docstring 里,那是第一手记录。
⚠ 「一页一个源」不再普遍成立:/cost/ 与 /tsm/ 各有两个。 除了 GlobeNewswire 的月度销售稿
(fetch/cost.py → series/cost.csv),2026-09 起还接了 SEC 申报层
(fetch/cost_sec.py,CIK 0000909832 的 10-K / 10-Q / 8-K),另写五张 series/cost_*.csv:
分部收入、客单与客流、财年单店经济、开业年份矩阵,以及 cost_fy_be.csv(FY2011–FY2015 的
合并损益回填,只喂 Exhibit 16 的两条盈亏平衡线,用的是上一代 XBRL 元素名)——
这几类数字月度稿里一件都没有。
两条腿节奏完全不同(月度 vs 季度/年度),所以在 monthly_run.py 里是两步而不是一步;
接线与「为什么不能合成一步」见 docs/CRON_WIRING.md §2.5。
读这几张表之前先看那里的口径警告:cost_seg_q.csv 是 total revenue,不是净销售额。
/tsm/ 同理:月营收走 fetch/tsm.py(TSMC 官网 xlsx → series/tsm.csv),2026-09 起另接 SEC 月报 6-K
(fetch/tsm_6k.py,CIK 0001046179,与营收新闻稿同一份 6-K 的第 3/4 项),追加
series/tsm_guarantees.csv(背書保證)与 series/tsm_derivatives.csv(衍生性商品);
在 monthly_run.py 里同样是单独一步(tsm_6k()),接线见 docs/CRON_WIRING.md §2.6。
读这两张表或改解析器之前先看三条口径坑(全文在 fetch/tsm_6k.py 的口径坑 a–c):
公司債与董事会核准资本支出两张表仍人工维护,陈旧提示见 monthly_run.audit_manual_series()(只打印)。
几条踩过的坑:
curl / urllib /
requests 会「连上但永远不返回」(不是 403,看日志会误判成网络问题)。可用通道:
curl_cffi(impersonate='chrome')、macOS 自带 /usr/bin/nscurl、node 原生 fetch。
月度数据走 Reg FD 挂 IR 站,不进 8-K,盯 EDGAR 抓不到。investor.spglobal.com 对 curl 一律 Cloudflare 403,但 s29.q4cdn.com
的 CDN 直链可直接下。Billed Issuance 官方只披露同比百分比,从不给绝对面值。cmegroupinc.gcs-web.com/monthly-volume 这个无扩展名地址本身就是 xlsx,
且是稳定别名,每月指向新文件。别去猜带月份的文件名直链,那种链接每月都变。Clients' Daily Average Trades
与 Growth in Client Assets and Accounts 的 Margin loans outstanding。这两个量在
月报里 2026-01 那期才新增、只回填到 2025-01,在季报里却一直有(本地缓存的
8-K Ex-99.1 最老一份印到 2013Q3)。落 series/schw_q.csv(2016Q1 起),画成
schw 页 Exhibit 10 / 11(紧跟各自的月频兄弟 Exhibit 8 / 9)。教训:一条列的「披露边界」要分「哪一列」和「什么频率」
两问,只查月报就断言「任何来源都补不出更早的」,2026-08 到 2026-09 在本仓错了两次。时序图的窗口左端统一为 2016-01(2026-08-18 定)。在此之前各页各定各的:
build/single.py 的 10 家是「近 25 个月」、cboe / cme 各有一个 WIN_LONG = 25、
hkex 是 12 处写死的 .iloc[-25:]、cost 是 2021-01、hood 是近 25 个月,
而 msci 早就写着 WIN0 = '2016-01'。后果是回补了历史也看不见 ——
db1 的序列从 2002-01 起、tmx 从 2001-12 起、ndaq 从 2005-09 起(期数别写死在这里 ——
series/*.csv 每月往右加一行;上一版这里写死了三个期数,2026-09-02 复核时
已经有两个对不上),
页面上除了每页那 1-2 张「全历史」图之外一律只画近两年。
现在各生成器统一用 WIN_FROM = '2016-01'(mrbase 那 7 家走 window.x_from),
取 max(序列首月, 2016-01):只往右让、不往左借,序列比它短的家用序列自己的起点
(2026-08-19 实测:asx / hkex / guc / lseg 等 127 期,hood 67 期 —— 各按各的)。
· 排版不要各写各的:通栏与 x 标签抽稀一律调 build/mrwin.py 的 layout_all()
(台湾半导体 7 家的 127 点图已经用它跑了一年)。全站已经有过三份互相抄来的量边距
算式,再抄第四份的下场是改一处漏三处。
· 窗口一变长,三类错会同时冒出来,都被现有护栏抓到过,改窗口前先知道它们长什么样:
① DENSE 图型(mrwin.DENSE:gs_line / lines_endlabels / stacked_dual)窗口里出现
前导 null → 平滑线(gs_line / lines_endlabels)被 Catmull-Rom 插值画成一条塌到零的假线,
左端标数值时按 fmt 要么抛 TypeError、要么印出一个假的 0(cboe Ex7 栽过,被 verify_pages
当场拦下;它是 lines_endlabels + f0c,真画出来会是后一种,现编号 Ex6);stacked_dual 不是平滑图型,null 段按 0 高画、
柱矮一截,右轴线被拽到 0,都不报错。解法是 mrwin.resolve() 裁左端,不是补 0。
② 派生列(环比 / 同比)在序列首月定义上不存在 → 首格 NaN,被 payload_guard 拦
(hkex Ex3 栽过)。解法是丢掉那一格。
③ 辅助函数的默认参数写死旧窗口(def yoy_rhs(..., win=25))→ 「values 长 25、
x 轴 127 格」,被 verify_pages 拦(hkex、hood 各栽一次)。默认值要跟着窗口走。
· 列里有中间空洞时,横轴绝不能用「有值的月」拼起来 —— 那会把相隔数年的两点画成
相邻格,是 build/CONTRACT.md 规矩 3 说的假时间轴(exchanges-eu Ex12 栽过)。
逐月铺开、缺月留 null,用 lines(doSmooth=false,null 是断笔)。
⚠ 同时注意统计量(min/median/max)要走真有值的月,走全轴会算出 nan。
· 口径断点的竖排标签在长窗口下挂不住:rotate(-90) 的文案长 150-200px,
charts.js 那套沿竖直带找空档的避让在 25 期时够用、127 期时那条带上挤满柱值标签,
一个空档都没有。所以 single.py 的 BREAK_LABEL_MAX = 60:超过就只画红虚线、
文案移进图注(图注本来就按从左到右的顺序逐条点名)。实测这一条把全站 🔴 从 94 降到 1。
全站同比只有一种口径:单月(当月 ÷ 去年同月 − 1)。2026-09 页面所有者定的,
原话是「我就需要直接的月度数据 yoy 同比折线图,不要给我搞 12 月滚动合计同比」。
好处是可核对:拿这根柱除以 12 根柱之前那根,就是次轴金线上的这一点。
(例外全站两张,都在图上自己声明了:cost Ex4 的线是公司报告 y/y、基期是
同样周数的上年错位窗口,对不上的月份里最大的四个正是 53 周财年那四个月
(Jan-18 / Jan-19 / Jan-24 / Jan-25,最大 26.6pp),其余都在 5pp 以内 ——
可比月与对不上的条数每月都在长,别在这里写死;
ibkr Ex2 的线用公司 Notes 还原后的账户增长,三个月对不上。
逐张实测见 build/CONTRACT.md §6.1 第 1 条 —— 没有这句声明的对不上就是 bug。)
代价没有消失,改由每张图自己承担 —— 图注里印出这条序列自己实测的毛刺
(逐月标准差、相邻月最大跳变、与 12 个月滚动口径符号相反的月份数),
由 的 / 现算。
⚠️ (2026-09-02 21:0x 逐条读过当时的 R0–R5、23:5x 连后加的
R7 / R8 一起再读一遍,没有一条查图注有没有印代价),
所以它坏了不会响:2026-09-02 21:0x 实测,回源确定命中流量列的 106 张图里
还有 10 张欠着(9 张 + Ex2),而判据报 。
同日 23:5x 照同一套数法重跑,欠账那两个数一格没动();
判据的输出那一半当晚就变了 —— 23:50 它长出一条 R8(近零基数序列没声明),当次报了
10 张 🟡;23:59 再扫,同一条报 0 张(逐张核过的只有 Ex29:那一页当晚被重建,
图注按 §6.1 第 5 条点了名、右轴也截到 +400%)。
欠账清单、数法与建议的补法都在 §6.1 第 3 条与 §6.6。
,但没有翻掉那次审计的测量 ——
只是那批数:审计是在当时的图窗(近 25 个月)上量的。
2026-09 在审计自己的分母上复算,对得上的是「65% 的可比序列至少有一个月两种口径
符号相反」(复算 138/212 —— ;同日 21:4x 重跑是 138/213,占比同为 65%。审计记 147/225。
引用前照 §6.5 那段配方当场重跑,别拿这里的绝对条数去对今天的仓);
对不上的是「噪声是滚动的 2 倍以上」——
同一批序列的标准差放大倍数中位复算只有 (20:4x 快照 1.87、
同日 21:4x 重跑 1.86 —— 分母是活数;2.08 倍只在「全部 CSV 流量列」
那个另一个分母上成立, 第 ⑦ 节当场印)。图窗 2026-08-19
那次改动拉到 2016-01(到 2026-07 满打满算 127 个月,短序列从自己首月起)之后,
这两个数变成 。逐条复算与「哪个数
复算不出来」记在 §6.5。变的是量级不是方向:单月口径毛刺更大、
单看一个月能把方向读反,这件事没变,所以每张流量同比图都得把这笔账印在旁边。
只是那次审计要解决的真正病根是「同一页混用两种口径」,统一成单月同样解决,
而这是所有者要的那一种。
例外共(都不是折线): Ex5 的三年连排分组柱与 Ex15 的
量价分解桥、 Ex4/7/8;其中 Ex15 是由所有者对 Ex5 的回答、
不是原话( §6.2),所以它是名单上最先该被重新审的一条。
同比仍然只有一份实现()+ 一条契约
( §6);存量序列(OI / AUM / 账户数)走点对点同比、禁止滚动合计。
判据可机检:。
纯 SVG + 原生 JavaScript,零依赖、零构建步骤,不加载任何外部脚本或字体。
<script src> 而非 fetch 加载 → 直接双击 index.html 以 file:// 打开也能跑gs_bar 的可选 stacks[]),但总额仍是那一根柱:
纵轴、柱顶数值、12 个月均线、次轴同比全部照总额走,分色只是把同一根柱的填色拆开。
各段之和必须逐格等于总额 —— 引擎不替数据求和,差额会静默变成柱顶一截空白,
所以这条恒等式在 build/verify_pages.py 里硬校验(见 docs/CHART_KINDS.md §3.6.1)。
没有改用堆叠柱专用图型(stacked_dual)是刻意的:那个图型的右轴下界写死为 0,
而这里的次轴是同比、会转负(单月口径下转负比滚动口径更频繁),负值会被顶到轴外 ——
页面等于宣称「增速从没转负过」,而且不报任何错year_lines)的往年色是同色相的等间距明度阶,最浅一档也压得住网格线;
当年红色高亮。逐年对比是这类图唯一的用途,颜色分不开等于图没画build/single.py 的
groups[].mix,2026-09 起;哪几页在用不在这里存,grep -l "'mix'" build/specs/*.py 现数):第一张是合计的水平值柱 + 次轴同比
(流量走单月同比、存量走点对点同比 —— 口径写进标题,
图注里把单月口径的实测代价一起交代,见 build/CONTRACT.md §6.1 第 3 条),
第二张是分项的 100% 堆叠占比图(stacked_dual,每根柱恒高 100%,只讲结构不讲规模)。
两张回答的是两个问题:柱图答「这门生意有多大、在不在长」,占比图答「结构往哪边走」——
而占比最有价值的场合恰恰是「总量在涨、某一块的占比反而在掉」。
同一个合计若还有第二种成立的加总关系(例如衍生品成交既能按期货 / 期权切、也能按资产类别切),
写进同一条 mix 的 alt_splits(2026-09-12 起):合计柱仍只画一次,每一种切法各出一张占比图、
分母相同,跨图的段不许相加相减 —— 底座只复算每一刀自己的加总关系,不复算刀与刀之间的包含关系。
加总关系由底座逐月复算:分项之和超过合计、或少于合计而没给残差段名字、
或声明了残差段而残差恒为 0,一律硬失败(见 docs/SINGLE_SPEC.md §1.5)——
一张写着「堆叠 = 100%」而实际不是的图,页面上看不出来仅供个人研究使用,不构成投资建议。数值以公司原始披露为准。 页面版式模仿 Goldman Sachs GIR exhibit 风格,仅为视觉版式,不含其研究观点或数据。
build/yoy.pycaliber_diff()describe()heat_matrixspgi🔴 0 🟡 0db1build/CONTRACT.mddata/ 快照
的读数python3 build/yoy.pybuild/CONTRACT.mdexchanges-apacexchanges12build/CONTRACT.mdbuild/yoy.pybuild/CONTRACT.mdpython3 tools/check_yoy_caliber.py量价分解:把成交额(或收入)的增长按恒等式拆成量与价两部分,全仓三类派生量 ——
股数 × 均价、笔数 × 每笔金额、张数 × 每张费率 —— 含义互不相通,图注强制写明
「它不是什么」。横轴口径不统一,逐页由数据决定,每张图的图注自己写明是哪一种:
① 日历年 + 当年 YTD(一格 = 一个完整日历年,对上一年同 12 个月;末格对去年同月窗口)
—— asx / hkex / jpx / lseg / miax / sgx / tmx×3 共 9 张,源数据只有季度或年度费率;
② 逐月(一格 = 一个月,对去年同月)—— cme Ex16、schw Ex5、cboe Ex11、hood Ex6、
lpla Ex6 共 5 张,两条腿都逐月披露才做得成;③ 其余 5 张是季度桶或横截面对比
(cost Ex15、exchanges-apac Ex15、exchanges-eu Ex15、exchanges-na Ex5/Ex8)。
⚠️ 2026-09-04 之前这一行写的是「横轴口径统一为日历年 + 当年 YTD」——
那句话在 cme / schw 改月度桶时就已经不成立,cboe 只是又多了一张;现按 data/*.js
的实际清点重写。缺同口径(金额,数量)配对的页面明说「不具备数据条件」,
不硬拆(db1 / enx / ice / ndaq 页各有一条说明)。
brief:21 页的页顶 ~300 字数据总结 —— 11 家老单公司页 + /ice/ + 台湾半导体 7 家
/wealth/ + /asx/。
规则库在 build/brief.py(只算事实),句子由各家生成器自己拼(build/mrbase.py
也 import brief,7 家共用底座拼出来的那几句 + spec 的 brief_extra 钩子);
刻意不复述图表里已有的数字,只写图表讲不出来的三件事 —— 基数效应、口径背离、所处区间。glossary(名词释义):34 页每页一块,排在 brief 之下、所有 exhibit 之上。
与 brief 分工明确:brief 说「这个月这组读数该怎么读」、每月重写;
glossary 说「这些词是什么意思」、一年到头是同一段 —— 所以释义里不写当月判断,
也不写随月份变的数。收词的判据是页面所有者定的:本页的图与表里出现过、
不看定义就会读错的名词(ADV / SDAV / DDAV / OI / RPC / DARTs / NNA / comp /
池内占比 / 定基名义额……),而不是一本通用金融词典;m/m、y/y、3Y %ile、pp/bp
这类全站通用读图约定不收(每页 summary.note 已经逐条讲过,收进来就是两处各写一份)。
版式与四道护栏在 build/glossary.py(Markdown 星号、非行内标签、dt 过长、
词条重复/空释义,四种都是「页面上看得见、构建期一声不吭」的写法);
build/verify_pages.py 的 check_glossary() 在产物侧再查一遍,
并核「词条是不是本页真有的词」(过半查不到才报,留出「可比零售周」这类
解释别的词用得上、页面上不直接出现的口径概念)。