Check a backtest
你手上这份证据,实际值多少?
What is this evidence actually worth?
一个问题,在三条轴上各问一次:几份源真的是独立的;一段回测里有几个月是模型没读过的; 这个结论是从输入读出来的,还是背出来的。三条轴共用同一件事—— 把「你以为有多少证据」折算成「实际有多少」。
每一条都在你自己的浏览器里算完,不上传任何东西,也不需要注册。
换一个单位问同一件事:你的判断质量,需要多少次决策才看得出来 →
One question, asked on three axes: how many of your sources are actually independent; how many months of a backtest the model had not already read; whether a conclusion was read off the inputs or recited from memory. All three do the same job — they convert what you think you have into what you actually have.
Each runs entirely in your own browser. Nothing is uploaded and nothing is signed up for.
Same question, different unit: how many decisions before your judgement can be judged →
DECISION-CONFIDENCE · 有效源数EFFECTIVE SOURCES

你这几列,实际上值几票?

How many votes are your columns actually worth?

把一张评分表贴进来——每行一个标的,每列一个已经打好的分。这个页面回答三个问题: 这几列实际提供了几份独立证据、哪两列在重复、砍掉哪一列不损失什么。

它不预测涨跌,不替你选股,不产生任何新的分数。它只量你手上已有的东西。

全部计算在你的浏览器里完成,数据不发送到任何地方。 这个页面没有后端,断网也能跑——你可以现在就断网试。
Paste in a table of scores — one row per subject, one column per rating you already have. This page answers three questions: how many independent pieces of evidence those columns actually supply, which two of them are duplicates, and which one you could drop for free.

It predicts nothing, picks nothing, and produces no new score. It only measures what you already brought.

Everything runs in your browser; no data is sent anywhere. There is no backend — pull your network cable and it still works. Try it now.
第一步 · 贴表STEP 1 · PASTE

从 Excel、Numbers、Google Sheets 直接复制粘贴即可

Copy straight out of Excel, Numbers or Google Sheets

第一行是列名。第一列如果是标的名称(非数字),会被自动识别为名称列,不参与计算。
支持制表符、逗号分隔。空格分隔的也认,但列名里就不能有空格。
First row is the header. If the first column is non-numeric it is treated as a label column and left out of the maths.
Tabs and commas both work. Spaces work too, but then column names cannot contain spaces.
第二步 · 读数STEP 2 · READ IT
有效源数EFFECTIVE SOURCES · N_EFF
—
这几列两两之间有多重合HOW MUCH EACH PAIR OVERLAPS
第三步 · 该砍哪一列STEP 3 · WHICH TO DROP

去掉这一列,剩下的还剩几票

Drop this column — how many votes are left

逐列移除,看有效源数怎么变。掉得少的那一列,就是账面上占位置、实际不提供独立信息的那一列。 如果移除某列之后反而上升,说明那一列不但没有贡献,还在拉低整体的独立性。
Remove each column in turn and watch the effective count move. The one that costs you least is the one taking up space on the invoice without supplying independent information. If removing a column makes the number go up, that column was not merely useless — it was dragging the rest down.
移除这一列REMOVE 剩下的有效源数N_EFF LEFT 变化CHANGE 读法HOW TO READ IT
第四步 · 那我该怎么办STEP 4 · WHAT TO DO

拿到这个数之后的动作

What follows from the number

口径HOW IT WAS COUNTED
DECISION-CONFIDENCE · 知识窗口KNOWLEDGE WINDOW

另一条轴:你的回测,模型看过多少?

The other axis: how much of your backtest had the model already read?

上面那一半问的是几列证据里有几份是独立的。这一半问同一个问题,只是换到时间上: 一段回测里,有几个月是模型没读过的。

模型有知识截止日,截止日之前发生的事它读过。回测区间若大部分落在截止日之前, 那段区间检验的不是策略能不能赚钱,是模型记不记得。 这一步是纯算术——在算出任何一个收益数字之前就能知道结论作不作数。

同样全部在浏览器里完成,不读取任何行情,不发送任何数据。
The half above asks how many of your columns are independent. This half asks the same question of the calendar: how many months of a backtest the model had not already read.

A model has a knowledge cutoff, and it has read what happened before it. If most of your backtest lies before that date, that stretch is not testing whether the strategy makes money — it is testing whether the model remembers. This step is pure arithmetic: you can know whether the result counts before computing a single return.

Also entirely in the browser. It reads no market data and sends nothing.
三个日期THREE DATES
格式 YYYY-MM。回测区间含首尾两个月。目标 Sharpe 是你声称要检验的强度, 不是回测跑出来的结果——它决定干净区间要多长才够。
Format YYYY-MM; the range includes both end months. Target Sharpe is the strength you claim to be testing for, not the number the backtest printed — it decides how long the clean stretch has to be.
「筛选过几个变体」=这个策略是从多少个候选里挑出来的。留空不是中性默认, 它断言你一次成型。「其中独立几次」是折扣,必须是量出来的—— 用 tools/record_neff.py --table 对那些变体的收益序列量出来。 不要用本页上半部分:数有效源时把方向相反的变体当成重复是保守的,数挑选次数时却会让惩罚变小。
"Variants screened" is how many candidates this strategy was picked out of. Leaving it blank is not a neutral default — it asserts you got it right first time. "Of those, independent" is the discount, and it has to be measured: run tools/record_neff.py --table on those variants' return series. Not the top half of this page: treating a mirror-image variant as a duplicate is cautious when counting sources and flattering when counting tries.
干净区间 · 可用于检验CLEAN MONTHS · USABLE FOR TESTING
—
红色部分=模型已读过的月份Red = months the model has already read
这么长够不够支撑一个推断IS THAT LONG ENOUGH TO INFER ANYTHING
变体筛选校正SCREENING CORRECTION

这个策略是从多少个候选里挑出来的

How many candidates was this one picked out of

那我该怎么办WHAT TO DO

拿到这个判决之后的动作

What follows from the verdict

战绩 · 没有模型的那种TRACK RECORD · NO MODEL INVOLVED

跟单榜上的钱包、金库、基金:分界点是你选中它的那天

A leaderboard wallet, a vault, a fund: the split is the day you picked it

没有知识截止日,但有同样的分界:选中之前的战绩,是让它被选中的那段; 只有选中之后的才检验这个选择。日期写 YYYY-MM 或 YYYY-MM-DD, 全部带「日」才按天算。「从多少个里挑」对排行榜来说是榜单排过的全部钱包,不是 1。
No knowledge cutoff, but the same split: the record up to the pick is the one that got it picked; only what came after tests the choice. Dates are YYYY-MM or YYYY-MM-DD, counted in days only when all of them have a day. "Chosen from" for a leaderboard is every wallet the board ranked, not 1.
「其中独立几个」必须是量出来的:tools/record_neff.py 用这些钱包的日收益算。 对冲的两面(一个做多、一个做空同一样东西)算两次机会,不算重复。
"Of those, independent" has to be measured: tools/record_neff.py computes it from the wallets' daily returns. Two wallets long and short the same thing count as two chances, not one.
DECISION-CONFIDENCE · 反事实扰动COUNTERFACTUAL PERTURBATION

第三条轴:这个结论是读出来的,还是背出来的?

The third axis: was the conclusion read, or recited?

前两条轴问的是你手上的证据够不够——几列里有几份独立,几个月是模型没读过的。 这一条问的是更前面的一件事:那个给出结论的 agent,到底有没有在看输入。

做法是反事实扰动:改一个事实,重问一次,看结论跟不跟着动。 已发表的测量给出的数字比多数人预期的糟——扰动关键市场输入之后, 最差的模型有 82.13% 的情况预测原封不动 (Li et al., Profit Mirage, arXiv:2510.07920)。

但单看一个翻转率不构成判决,这一节存在的理由就在这里。扰动分两类,期待相反: 实质扰动(利好改利空、政策方向翻转)结论本该变; 表面扰动(换代码名、平移日期、改数量级)结论本不该变。 两类平均成一个数,恰好把唯一有分辨力的部分丢掉了。

同样纯前端,不调用任何模型,不发送任何数据。 扰动是你自己跑的,本页只给那张表打分。
The first two axes ask whether the evidence you hold is enough — how many of your columns are independent, how many months the model had not read. This one asks something earlier: was the agent that produced the conclusion reading its inputs at all?

The method is counterfactual perturbation: change a fact, re-ask, and see whether the conclusion follows. The published measurement is worse than most people expect — perturbing key market inputs left the worst model's prediction unchanged 82.13% of the time (Li et al., Profit Mirage, arXiv:2510.07920).

A flip rate on its own is not a verdict, which is the whole reason for this section. Perturbations come in two kinds with opposite expectations: material ones (good news to bad, a policy reversed) should move the conclusion; cosmetic ones (a renamed ticker, shifted dates, rescaled magnitudes) should not. Averaging the two into one number throws away the only part that discriminates.

Pure front-end again: it calls no model and sends nothing. You run the perturbations; this page scores the table.
逐条录入ONE ROW PER RUN
一行=你已经跑过的一次。「翻转」指结论变了—— 同一个判断换个说法不算翻转。描述会原样回显, 因为这些标注要能被别人回看:把实质扰动标成表面就能通过,这是本页唯一的逃生舱。
两类都要填:只有实质扰动时,「从不翻转」和「根本没读」是同一个观察。
One row per run you have already made. "Flipped" means the conclusion changed — a different wording of the same call does not count. The descriptions are echoed back, because these labels have to be inspectable: mislabel a material change as cosmetic and the audit passes, which is the one escape hatch on this page.
Both kinds are required: with material ones alone, "never flips" and "read nothing" are the same observation.
判决VERDICT
—
两类扰动各翻了几次HOW OFTEN EACH KIND FLIPPED IT
你标了什么WHAT YOU LABELLED

这份清单,是一个持怀疑态度的人会看的东西

This is the list a sceptic would read

判决的可信度不高于这张表的标注质量。本页无法核验其中任何一格—— 「实质还是表面」和「算不算翻转」都是你给的。回显它,是为了让它可以被质疑。
The verdict is no better than the labels in this table. This page cannot check a single cell of it — material-versus-cosmetic and flipped-versus-not are both yours. It is echoed here so that it can be argued with.
那我该怎么办WHAT TO DO

拿到这个判决之后的动作

What follows from the verdict

反事实扰动 · 方法与限制COUNTERFACTUAL PERTURBATION · METHOD AND LIMITS
方法:把实质扰动与表面扰动排成 2×2 列联表,做单边 Fisher 精确检验。 精确检验而非卡方,是因为这里的计数全是个位数——正是近似最差的地方。

对照系是 agent 自己的表面扰动翻转率,不是 0.5,也不是任何假定值。 「一个真在读输入的 agent 应该翻转多少次」事先无从得知; 但「它对无意义改动反应多少次」是可测的,而且恰好是正确的基线。 这和上半部分 carry_cost 是同一个动作:一个对照案例,把拒答变成测量。

六次是下限:3 实质 + 3 表面完美分离,p 恰好等于 0.0500。 低于这个数,任何形状的结果都过不了 α = 0.05—— 那时给出的是 no_power,不是「通过」。 「这次审计分辨不了」和「这个 agent 没问题」是两件事。

为什么 memorised 和 unstable 按「有没有表面翻转」分,而不是比两个比率: 比率法是第一版,它把两个极端都判反了——0% 对 0% 会触发「表面 ≥ 实质」而把一个 完全无反应的 agent 判成 unstable,100% 对 100% 反而落到 memorised。 给「多不稳算不稳」设一个阈值,就是这个仓库在别处一直拒绝的那种没标定的数字; 而一次表面翻转本身就是问题,所以门槛是一。

无法工程化消除的限制:「结论是否翻转」和「这次改动算实质还是表面」都由你给出, 本页核验不了。把实质扰动标成表面,审计就会通过—— 这和不申报筛选次数是同一类逃生舱。所以清单被原样回显在判决旁边。

responsive 不是「结论正确」:它只说明 agent 会对实质改动作出反应。 三条轴各测各的,一条通过不替另外两条说话。

给 agent 用:同一条轴有 MCP 工具 counterfactual_audit。 它的工具说明里写着一句给模型的指令:扰动要你自己跑—— 这个工具只给表打分,不能替你生成或评判那些运行。

与命令行同源:算法与 tools/perturb.py 一致,文案与 src/messages.py 同源, 同样由 python tools/check_js_parity.py 抽出本页 JS 用 Node 跑, 与 Python 逐条比对判决、p 值与处方文本(两种语言)。 页面载入时的那组(实质 1/4、表面 0/4)在两边都应给出 p = 0.5000 · memorised,命令行对应 python tools/perturb.py --material 1/4 --cosmetic 0/4。
Method: arrange material and cosmetic runs as a 2×2 table and apply a one-sided Fisher exact test. Exact rather than chi-square because the counts here are single digits — precisely where the approximation is worst.

The baseline is the agent's own cosmetic flip rate, not 0.5 and not any assumed rate. How often a genuinely reading agent ought to flip is not knowable in advance; how often it responds to meaningless changes is measurable, and is exactly the right comparison. This is the same move carry_cost makes in the sources axis: a control case is what turns a refusal into a measurement.

Six is the floor: three material and three cosmetic, split perfectly, give p = 0.0500 exactly. Below that no result of any shape clears α = 0.05, and what comes back is no_power rather than a pass. "This audit cannot tell" and "the agent is fine" are different statements.

Why memorised and unstable split on whether any cosmetic flip happened rather than on a comparison of the two rates: comparing rates was the first attempt, and it inverted both extremes — 0% against 0% fires the "cosmetic ≥ material" test and calls a completely unresponsive agent unstable, while 100% against 100% falls through to memorised. A threshold on "too unstable" would be an uncalibrated number of exactly the kind this repository refuses elsewhere, and one cosmetic flip is a real problem on its own, so the bar is one.

The limit that cannot be engineered away: whether a conclusion flipped, and whether a change was material or cosmetic, are both supplied by you and cannot be checked here. Mislabel a material change as cosmetic and the audit passes — the same kind of escape hatch as an undeclared trial count. That is why the list is echoed back beside the verdict.

Responsive is not correct: it says the agent reacts to material changes, and nothing else. The three axes measure three different things, and passing one does not speak for the other two.

For agents: the same axis is exposed as the MCP tool counterfactual_audit, whose description carries one instruction aimed at the model: you run the perturbations yourself. The tool scores a table; it cannot generate or judge the runs for you.

Same source as the command line: the arithmetic matches tools/perturb.py and the wording is generated from src/messages.py. python tools/check_js_parity.py lifts this page's JavaScript, runs it under Node, and diffs verdicts, p-values and remedy texts against the Python in both languages. The set loaded on arrival (1/4 material, 0/4 cosmetic) must give p = 0.5000 · memorised on both sides; python tools/perturb.py --material 1/4 --cosmetic 0/4 is the same case.
知识窗口 · 方法与限制KNOWLEDGE WINDOW · METHOD AND LIMITS
方法:所需月数 = ceil((t / SR)² × 12),来自 t ≈ SR·√T(Lo 2002,T 以年计)。 截止日当月算作已读——「知识截止 2024 年 10 月」意味着 2024 年 10 月它读过。

这一个月不是小事:同一组配置,含当月是 87.9%,不含是 86.4%。 两种读法都站得住,而引用的人通常不说自己用的是哪一种。 口径在这里被写死,正是为了它不能在两次讲述之间漂移。

两条会让这个数好看的限制,跟着结果一起给:
  ① t ≈ SR·√T 假设收益 i.i.d.。月度收益通常正自相关,会抬高朴素 t 值—— 所以这里要求的长度是下限,真实需求只多不少。
  ② 它假设策略在看数据之前就定好了。若曾在开卷区间里筛选过变体, 选中这一个本身就用了被污染的数据,t=2.0 门槛偏低(多重检验下 Harvey & Liu 主张约 3.0)。 上面的 t 门槛可以自己调高。

够长不等于有效:sufficient 只说明「长度」这一条不再是限制, 它不构成策略有效的任何证据。

变体筛选怎么算:Bonferroni。把你已经接受的错误率按独立试验数分摊—— α = 1 − Φ(t),α' = α / n,t' = Φ⁻¹(1 − α')。 α 从你自己设的 t 反推、而不是固定成 0.05,是为了让「只试过 1 次」成为恒等: 申报一次不应该让所需长度动一个月。

为什么是「独立试验数」而不是「试验数」:同一个策略的 50 组参数不是 50 次独立检验, 就像五家供应商读同一个链上字段不是五份独立证据。这和本页上半部分是同一种折扣, 但方向相反:数源时少算是保守,数试验次数时少算是美化。 所以「独立几次」要用 tools/record_neff.py --table 量—— 它是一个可自由填写的字段,如果凭感觉填,它就不是校正而是逃生舱。

给 agent 用:同一条轴有一个 MCP 工具 knowledge_window, 让 agent 在引用任何回测数字之前先自己问一句。它的工具说明里写着一句给模型的指令: 不知道试了几个变体就去问用户,不要省略—— 省略不是缺省,是替用户断言了他一次成型。

与命令行同源:算法与 tools/window.py 一致,文案与 src/messages.py 同源。 默认那组在两边都应给出 58 / 8 / 48 / underpowered。 这不靠肉眼比对——python tools/check_js_parity.py 会把本页的 JS 抽出来用 Node 跑, 与 Python 比对 28 组数值、57 条文案(两种语言)、48 组处方文本,所需月数必须完全相等。
一处已知的精度差:浏览器没有内置正态分布,本页的 Φ 用 Abramowitz & Stegun 7.1.26 近似(|ε| < 1.5e-7),Φ⁻¹ 用 Acklam。 实测 t 门槛与 Python 最大差 1.5e-5,显示到两位小数看不出来; 但它原则上可能在临界点让所需月数进位差一个月,所以那个比对脚本卡的是月数,不是 t。
Method: months = ceil((t / SR)² × 12), from t ≈ SR·√T with T in years (Lo 2002). The cutoff month counts as read — "knowledge cutoff: October 2024" means October 2024 was in the training data.

That one month is not a rounding detail: on the same configuration, including it gives 87.9% and excluding it gives 86.4%. Both readings are defensible, and people quoting either one rarely say which. The convention is fixed in code precisely so the figure cannot drift between tellings.

Two limits that would make this number look better, printed with it:
  (1) t ≈ SR·√T assumes i.i.d. returns. Monthly returns are typically positively autocorrelated, which inflates the naive t — so the length demanded here is a floor, never generous.
  (2) It assumes the strategy was specified before anyone looked. If you screened variants on the open-book stretch, picking this one already used the contaminated data and t = 2.0 is too low a bar (under multiple testing, Harvey & Liu argue for roughly 3.0). Raise the t bar above yourself.

Long enough is not the same as valid: sufficient says only that length has stopped being the binding constraint. It is not evidence of anything about the strategy.

How screening is charged: Bonferroni. Divide the error rate you already accepted by the number of independent trials — α = 1 − Φ(t), α' = α / n, t' = Φ⁻¹(1 − α'). Deriving α from your own t rather than fixing it at 0.05 is what makes "I tried one thing" an exact identity: declaring a single attempt must not move the requirement by even a month.

Why independent trials and not just trials: fifty parameter settings of one strategy are not fifty independent tests, any more than five vendors reading one on-chain field are five independent reads. It is the same discount as the top half of this page, pointed the other way: undercounting sources is cautious, undercounting tries is flattering. So measure the independent count with tools/record_neff.py --table — it is a free-text field, and a number filled in by feel is not a correction, it is an escape hatch.

For agents: the same axis is exposed as an MCP tool, knowledge_window, so an agent can ask before quoting any backtest figure. Its description carries one instruction aimed at the model: if you do not know how many variants were screened, ask the user — do not omit it. Omitting it is not a default; it asserts on the user's behalf that they got it right first time.

Same source as the command line: the arithmetic matches tools/window.py and the wording is generated from src/messages.py. The defaults above must produce 58 / 8 / 48 / underpowered on both sides. That is not left to the eye — python tools/check_js_parity.py lifts this page's JavaScript, runs it under Node and diffs it against the Python across 28 numeric cases, 57 message entries in both languages, and 48 remedy texts, with the months requirement compared exactly.
One known precision gap: browsers have no normal distribution, so Φ here is the Abramowitz & Stegun 7.1.26 approximation (|ε| < 1.5e-7) and Φ⁻¹ is Acklam. The measured gap against Python on the t bar is at most 1.5e-5, invisible at the two decimals shown — but it could in principle flip a ceiling by one month, which is why the parity check is strict on months and tolerant on t.
有效源数 · 方法与限制EFFECTIVE SOURCES · METHOD AND LIMITS
方法:列内取秩 → 两两皮尔逊相关(即斯皮尔曼秩相关)→ n_eff = n² / (n + 2Σ|ρ|)。 区间由对标的(不是对单元格)有放回重抽 1000 次得到,取 2.5% / 97.5% 分位。 种子固定,同一张表每次跑出同一个数。

为什么对相关系数取绝对值:Kish 有效样本量的原式不取绝对值,因为它来自方差推导—— 两个负相关的估计量平均起来噪音互相抵消,平均值反而更准,所以「等效独立样本数」可以超过列数。 那在估计精度的语境下是对的。但这里问的不是精度,是这几列在不在回答同一个问题。 两列相关 −0.9,不是两个独立的问题,是同一个问题的正反两面——知道一列就知道另一列。 作为证据它该算一票多一点,不是两票。所以这里正负同等计入重合。 代价是这个数不再等于方差意义上的有效样本量;好处是它不会给出「4 列值 6 票」这种读不通的结果。

零方差列:一整列的值全都一样时,它跟任何列的相关都无法定义。把它记作 0(=完全独立) 会让一条毫无区分度的列伪装成满分独立票——这是最坏的失败方向。所以这类列在进入计算之前 就被剔除,并单独列出。一列分不出任何东西,不等于它提供了独立信息。
缺失值:整行删除(listwise)。任何一列缺分,该行不参与计算,删了多少行会在「口径」里报出来。

它不是什么:不是因子研究。因子研究问的是「这个信号能不能预测收益」;这里问的是 「这几列是不是在回答同一个问题」。两者正交——一个因子可以毫无预测力却仍是独立构念, 两个因子可以都很赚钱却其实是同一个东西。
也不是风险模型:风险模型问你承担了几种金融风险,这里问你有几份证据支持结论。

一个必须说清的边界:这里用的是原始秩相关,没有条件化在任何结局变量上。 如果你的表里有可测的结局(涨跌、是否兑现),控制住它之后再算,数字通常会更低。 所以这个数是相关性的上界读法,不是「控制标签后」的读法——真实的独立性只会比它更差,不会更好。
还有一件它看不出来的事:有效源数只能量现有的列有多重合, 它看不出你漏掉了什么。四列算得再干净,要是该测的第五件事根本没有数据源, 这个总分依然不成立。那一步得靠你自己列。

这个数不必采信,可以自行核验:方法全部公开,纯前端,右键查看源代码即可读完。 MIT · 无依赖 · 无网络请求
Method: rank within each column → pairwise Pearson on the ranks (i.e. Spearman) → n_eff = n² / (n + 2Σ|ρ|). The interval comes from resampling subjects (not cells) with replacement 1000 times, taking the 2.5% / 97.5% quantiles. The seed is fixed, so the same table gives the same number every time.

Why the correlations are taken in absolute value: Kish's original does not, because it comes from a variance derivation — two negatively correlated estimators cancel each other's noise, so the "equivalent independent sample size" can legitimately exceed the number of columns. That is correct when the question is precision. It is not the question here. Here the question is whether these columns are answering the same question, and two columns correlated at −0.9 are not two independent questions — they are two sides of one, and knowing either tells you the other. As evidence that is worth a little more than one vote, not two. So sign is ignored and magnitude counted. The cost is that this number is no longer an effective sample size in the variance sense; the benefit is that it never reports "4 columns, 6 votes".

Zero-variance columns: when every value in a column is identical, its correlation with anything is undefined. Recording that as 0 (= perfectly independent) would let a column with no discriminating power masquerade as a full independent vote — the worst direction to fail in. So such columns are removed before the calculation and listed separately. A column that distinguishes nothing is not a column that adds information.
Missing values: listwise deletion. Any row with a gap sits out entirely, and the count of dropped rows is reported under "how it was counted".

What this is not: it is not factor research. Factor research asks whether a signal predicts returns; this asks whether several columns are answering one question. The two are orthogonal — a factor can have no predictive power and still be an independent construct, and two factors can both make money while being the same thing.
Nor is it a risk model: a risk model asks how many kinds of financial risk you are carrying. This asks how many pieces of evidence stand behind your conclusion.

A boundary worth stating plainly: these are raw rank correlations, not conditioned on any outcome variable. If your table has a measurable outcome (it went up, it was delivered) and you control for it first, the number usually falls. So this is the upper-bound reading of independence, not the controlled-for-label reading — the truth is only ever worse, never better.
And one thing it cannot see at all: an effective-source count measures how much the columns you have overlap. It cannot see what you left out. Four columns can come back beautifully independent, and if the fifth thing that needed measuring has no source at all, the total still does not hold. That part is yours to enumerate.

You do not have to take this number on trust: the method is fully open, the page is pure front-end, and view-source is the whole of it. MIT · no dependencies · no network requests