Label basis
When a cohort label was computed: labelBasis, grading, the lookahead that breaks a backtest, and the endpoints that refuse or fix a basis.
A cohort label is a fact about a wallet at some moment. labelBasis and grading choose which moment and which ladder, and a wrong choice lets a backtest see the future. The ladders themselves are on Cohorts and axes.
labelBasis: when the label was computed
current (resolved from the wallet dictionary at query time) or point_in_time (the label as it
stood in that row's own hour). Three axes are fixed and reject a conflicting value rather than
silently overriding it: size, tier and pnl_unrealized are always point_in_time; pnl_abs
is always current.
grading: which PnL ladder does the labelling
realized | realized-pit | unrealized, on
/coins/{coin}/cohort-metrics only, and
refused on the axes that rank no PnL (size, tier, pnl_abs).
grading and labelBasis are independent
A grading name is neutral by design — realized does not say whether the label is today's or the
hour's. That is why every response carries a label_computed block on the envelope and on every
row: the name is not allowed to travel alone.
The default is lookahead for a backtest
The default labelling is lookahead for a backtest
By default the pnl cohort is resolved from the dictionary at query time, so a historical row
carries today's grading. That is correct for "who is smart right now" and wrong for a backtest,
because a wallet under the 10-closed-trade floor back then may be graded now. Pass
labelBasis=point_in_time for each row's label as it was that hour.
The point-in-time response then omits five fields, listed in unavailable_fields:
position_count, position_count_long, total_unrealized_pnl, total_position_size and
total_position_size_long — the frozen source does not carry them.
label_computed.lookahead on the envelope states which of the two you got. Do not infer it.
What each grading selects
grading=realized(the default onaxis=pnl) — the percentile ladder over cumulative realized PnL. With the defaultlabelBasisthis is resolved at query time andlabel_computed.lookaheadis true. WithlabelBasis=point_in_timeit is the frozen ladder, andlabel_computednames a 2026-08-06 seam: before it the labels come from the 2026-08-17 point-in-time rebuild (daily resolution), from it they come from the row's own hourly column. Both halves are honest, but a series crossing the seam changes label resolution — a turnover count will read that as a regime change that is an artefact.grading=realized-pit— the same ladder graded point-in-time across the whole window (day D on the cumulative as of the end of D−1), which is what makes it the control for a study crossing that seam.grading=unrealized— "currently onside": hour H's rank over the wallet's last snapshot strictly before H.axis=pnl_unrealizedis the legacy spelling of exactly this and keeps working; both reach one branch.
realized-pit and unrealized refuse labelBasis=current rather than silently overriding it.
An endpoint that refuses point-in-time
/coins/{coin}/metrics refuses labelBasis=point_in_time by name
Its cohort label is resolved from the wallet dictionary at query time, so every historical hour
it returns carries today's grading. Measured 2026-04-20T12:00Z on BTC's 3x smart bucket:
938,495,920.6 of notional against 861,706,562.3 under the point-in-time labelling of the same hour
— 8.91% inflated. The 400 names
/coins/{coin}/cohort-metrics with
axis=pnl&labelBasis=point_in_time, which reads the frozen ladder.
It refuses rather than serving it because the frozen column is 0% filled before 2026-08-06 and this endpoint's whole value is the long window: answering recent hours only would silently truncate some windows and not others.
Before 2026-08-19 /coins/{coin}/metrics accepted no labelBasis at all
An unrecognised query
parameter was ignored rather than refused, so ?labelBasis=point_in_time returned HTTP 200 with
lookahead rows. A caller who explicitly asked for the honest labelling was told yes and given the
contaminated answer. If you have stored results from that period, they carry today's grading
regardless of what you asked for. The refusal exists so that can no longer happen silently.
Label provenance on a row
On /positions every row declares its own label provenance: pnl_cohort_basis (point_in_time | current) and
pnl_cohort_as_of (the ISO hour the label was computed at, or null). Historical rows carry the
point-in-time label from the hourly label table — not today's dictionary standing — with the
current unlabelled tail (the newest hour or two) falling back to the dictionary and saying so per
row. The envelope's label_computed block states the mode's source once.
A current label is sometimes right
On /hype/holders, pnl_cohort is resolved at query time and the envelope says so (label_basis: "current" plus
label_computed). That is correct rather than a fallback: the leaderboard has no time axis, so
there is no earlier row for a current label to contaminate.