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 →
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.
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.
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.
"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
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.
"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.
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 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.
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
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.
两条会让这个数好看的限制,跟着结果一起给:
① t ≈ SR·√T 假设收益 i.i.d.。月度收益通常正自相关,会抬高朴素 t 值——
所以这里要求的长度是下限,真实需求只多不少。
② 它假设策略在看数据之前就定好了。若曾在开卷区间里筛选过变体,
选中这一个本身就用了被污染的数据,t=2.0 门槛偏低(多重检验下 Harvey & Liu 主张约 3.0)。
上面的 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.
这个数不必采信,可以自行核验:方法全部公开,纯前端,右键查看源代码即可读完。
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