vike
Concepts

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 on axis=pnl) — the percentile ladder over cumulative realized PnL. With the default labelBasis this is resolved at query time and label_computed.lookahead is true. With labelBasis=point_in_time it is the frozen ladder, and label_computed names 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_unrealized is 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.

On this page