Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
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 新增)。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() 在产物侧再查一遍,
并核「词条是不是本页真有的词」(过半查不到才报,留出「可比零售周」这类
解释别的词用得上、页面上不直接出现的口径概念)。