0 · The number we refuse to fake
“This market is up 40%” is the single most abused sentence in collectibles. A median sale price jumps because a marquee quarter over-sampled expensive lots; a cohort index rounds and drifts; a partial quarter reports half its sales and prints a spike. None of those is price movement. Price movement is what a like-for-like object did over time — the same reference, the same form, the same size — with the changing mix of what happened to sell held constant. Everything below is one apparatus for estimating that one quantity, and refusing to state it when we can’t.
1 · The data underneath — from raw lot to labeled object
The index this whole post describes leans on controls — a reference, a form, a size, a decade — that don’t arrive labeled. A crawler pulls a string: PATEK PHILIPPE. A fine stainless steel automatic wristwatch, Ref. 5711/1A, circa 2015. Before any of the math below can hold quality constant, that string becomes a structured object — maker patek-philippe, reference 5711, form wristwatch, decade 2010s, its native price a fact and its USD a derivation. Quality has to be labeled before it can be held constant.
Every field the rest of this post relies on is parsed at crawl time (scripts/ray-crawl.ts) and hardened over the whole corpus at build time (scripts/lib/corpus-normalize.ts):
| Raw signal | Structured label | What consumes it |
|---|---|---|
| title / creator line | maker · vertical (the router) | which market; the per-maker index |
| reference / model line | reference · modelKey (Daytona ≠ Datejust) | the within-maker mix control |
| catalogue wording | form (26-form taxonomy) · size · medium · decade | the hedonic controls + the comp gate |
| card title | _card fingerprint: player·year·set·cardNo·grade | the repeat-sales index key |
| native price + sale year | USD via a dated FX table | every money figure on the site |
The discipline is the one the engine itself runs on: a label is a gate, not a guess. A field we can’t read confidently is left null, never invented — unknown comps nothing, a Daytona reference matches only another Daytona, and a lot whose form we can’t pin never enters a comp pool at all. A wrong label is worse than a missing one, so we carry the blank.
And labeling is never a one-time migration. Every night a normalization pass re-derives the corpus end to end — it clamps impossible years, reroutes mislabeled lots (a blue-chip print misfiled under science), recovers references the parser missed, reconciles fallback sale dates, and re-keys the card fingerprints — so a single fix to how we read a title heals the entire back-catalogue at once. One object, labeled once, then feeds everything downstream: the hedonic regresses on its controls, the comps engine matches on its identity, the repeat-sales index keys on its fingerprint, the backtest replays it. The rest of this post is what the engine does with a labeled object — the label is where the honesty starts.
2 · The hedonic price-movement index
The engine is a hedonic log-price regression, fit per maker in scripts/hedonic-index.ts. We regress the log of the realized price on a set of quality controls plus a dummy for every calendar quarter. The quarter coefficients are the pure time effect — the price movement of a like-for-like lot — because the quality dummies absorb the mix:
For a per-maker index the maker is constant, so its dummy drops and the within-maker drivers carry the mix: a watch reference (a Daytona vs a Datejust), a furniture model (a Conoid bench vs a slab table), the form, the size, the object’s decade, and the auction house. That is exactly what a naive median can’t give you — if a quarter happens to sell more Daytonas, the median jumps even when nothing appreciated; the hedonic τ doesn’t, because the reference dummy already ate it.
Auction tails are heavy — a handful of whale lots would drag ordinary least squares — so the fit is Huber IRLS: five reweighting passes that trust residuals inside a robust band and down-weight everything outside it, with a tiny ridge on the non-intercept coefficients for numerical stability. The design matrix is sparse (each lot lights up ~6 dummies out of a few hundred columns), so we never materialize the dense n×p matrix; we accumulate X’WX and X’Wy over each row’s nonzeros and solve the small system by Cholesky (via ml-matrix, eigen-pseudo-inverse fallback):
3 · Confidence gating — the part that abstains
Here is the whole thesis. A horizon — 1Y, 3Y, 5Y — publishes a return only when its 95% confidence interval resolves the sign. If the interval straddles zero, we don’t say “roughly flat” and we don’t round toward a story. We abstain: the horizon is marked not-publishable with a human-readable reason, and the site says so out loud.
The sign gate is the headline, but it’s the last of a stack. A horizon also has to end on a complete, dense quarter (we never end on the current stub quarter, and the endpoint’s volume must be ≥60% of its trailing-4-quarter median, which catches a half-reported latest quarter); it needs enough distinct references or forms that the within-maker mix is actually controlled; and it fails on a composition break — any single source or house that is >40% of one endpoint but <12% of the other, which is how an archive backfill or a house entering/leaving would otherwise leak into the “time” effect. Clear all of them, and only then does the CI get a vote.
The consequence is deliberate and severe. The market-level index abstains almost everywhere. Most per-maker indices abstain. Every composite we assemble bottom-up from makers abstains when it can’t seat enough measurable components. We publish less, on purpose — because a confident-looking wrong number is worse than an honest blank.
4 · The verified movers
The makers that clear the bar are the entire product. As of tonight’s refit there are three, each surfaced at the longest horizon whose CI resolves (5Y > 3Y > 1Y) — the numbers below are read straight out of makerIndex in market.json at build time, so this table is always the current engine’s:
| Maker | horizon | change | 95% CI | lots |
|---|---|---|---|---|
| Cartier | 5Y | +53.7% | [21, 95] | 5,772 |
| Rolex | 5Y | +25.0% | [12, 39] | 15,235 |
| Patek Philippe | 3Y | −14.3% | [-22, -6] | 15,522 |
Even the makers that publish abstain at their shorter windows wherever those intervals still span zero — the site holds them. Everything else — every artist, every design name, every sports and science bucket — abstains, and the “verified movers” panel simply says so where a market has none. A handful of intervals that resolve the sign, standing behind real money, is the honest yield of a very large corpus.
5 · Demand vs ROI: two reads, and the divergence flag
The hedonic index is one of two market reads, and they check each other. Demand (app/lib/demand.ts) is the median percent a lot sold over its own estimate midpoint, trailing twelve months. It’s mix-proof by construction — a $900 print and a $3M canvas each beating ask by 60% count identically — which makes it a fast, wide signal. But it is a relative beat, and the houses set the bar. A market can run “hot” on demand while the specialists quietly trim estimates: the lots keep beating ask, but ask keeps falling.
That is exactly what the hedonic ROI is the absolute check for. When demand is positive but the appreciation trend is meaningfully negative, the lander flags it — roiFlag = ‘beating soft estimates’ (IndexHero.tsx) — so a buyer reads the heat correctly: it’s clearing a softening bar, not real strength. Demand answers “are lots beating the ask?”; the hedonic index answers “is the ask worth beating?” The divergence is the interesting part.
6 · Sub-markets: the strongest honest read
Not everything is a maker, and not every maker earns an index. So every tracked slug is a “sub-market” (scripts/sub-markets.ts) that carries the strongest honest read its data supports, and no stronger:
| read | when | what it shows |
|---|---|---|
index | a real maker with a publishable CI’d horizon | the verified price move + interval (the movers in §4) |
demand | the slug carries estimates across enough quarters | quarterly median %-over-estimate (hammer basis) |
descriptive | no estimates, or not a maker | typical price · all-time record · volume — no appreciation |
The last row is the discipline. A collectible bucket — meteorites, game-used, tickets-passes, trophies — is tagged entityClass:‘category’ in the type system: its lots are mutually incomparable objects under one thematic slug, so a like-for-like index is impossible (it posts +900% artifacts). The maker index refuses those at the door and they fall to a descriptive read: a typical price and a record, and no appreciation number at all. There is no honest way to say a “meteorites index is up 30%,” so we don’t.
7 · Why not a naive market index
This engine replaced a legacy cohort/appreciation index, and the replacement is a reaction to specific artifacts that index produced — the reasons a marketplace-wide “the whole market is up X%” number is a lie waiting to happen:
| Artifact | What it did |
|---|---|
| Seasonal cohort swing | A marquee-sale quarter over-samples expensive makers; the index jumps with the mix, not the market |
| Partial-quarter endpoints | A half-reported latest quarter prints a spike or crater that reverses when the rest of the sales land |
| Integer rounding / mean-pinning | Rounding and mean sensitivity manufacture motion out of a thin, heavy-tailed quarter |
| Backfill composition breaks | An archive backfill floods one endpoint with one source; the “change” is the flood, not price |
| Card-dominated ‘all’ | Sports-cards are 66–88% of the whole-corpus quarters under one slug — a shift in which cards sold masquerades as market movement |
The honest engine answers each of these by abstaining rather than emitting a confident number. That card-dominance case is live: the market-wide ‘all’ index refuses every horizon because a single maker-slug (sports-cards) dominates the endpoint quarters outright, so the hedonic control can’t hold quality constant within it. The art market shows the subtler version: even where its market-level fit points down, the bottom-up composite still abstains whenever no art maker publishes a defensible per-maker horizon. Picasso is the reason in miniature — his recent quarters are overwhelmingly prints with no reference-level control, so a shift in which prints sold would leak straight into the time effect. The gate catches it and the maker abstains.
8 · What this costs, and what it buys
The cost is coverage: three verified movers across a corpus of 1,129,691 lots from 18 sources. Most verticals never seat enough measurable components to assemble a composite at all. That is a small published surface for a very large amount of data — and it is the point. Every appreciation figure the site shows has a 95% interval behind it that resolves the sign, ends on a complete quarter, survives a composition-break check, and holds the within-maker mix constant. Where it can’t, it says so. We publish less, on purpose — and everything we publish, we can defend.
