Skip to contentResearch

FCM-2 · Foil Card Model

A price, and how much to believe it.

Two models. One estimates what a graded card is worth and which way it is going, built around a mechanism most of this field treats as a constant: certified population is not a fixed scarcity number, it is a flow that answers price with a lag. The other turns that into a price for a prediction market, starting from the term everyone omits — that a card market can fail to resolve at all. The method is written down, the code is in the repository, and the checks that validate it are a command anyone can run. It has not been fitted to market data and claims no results about any real card.

What this page is not

The Foil Card Model is a published specification. It has not been fitted to licensed market data, it has produced no results, and no figure derived from it is a forecast, a valuation, a track record or investment advice.

Thresholds on this page are pre-registered: they state what the model must clear to be published as fitted, and were written before any result existed. They are targets, not achieved values.

What it estimates

Three questions.

  1. 01

    How likely is this card to gain more than 10% in six months?

    The headline. A probability, from a fitted return distribution with heavy tails, stretched for how rarely the card actually trades.

  2. 02

    What is it worth now, and how far is the last sale from that?

    A fair value with an interval, combining comparable sales with the card's own price history, and the gap between that and the most recent print.

  3. 03

    What is the whole distribution of outcomes?

    Expected return, volatility, quantiles and the loss expected in the worst cases — not a single number presented as though the future were known.

Structure

Two models.

Estimating what a card is worth and quoting a market on it are different problems, and the second is not the first with a percentage sign on the end. They are built and validated separately.

Model A

Valuation and direction

What is this card worth, and which way is it going?

Comparable sales anchored to the card's own price history, a repeat-sales index, conditional volatility, and a composite signal whose weights are fitted rather than asserted. Runs in three segment-specific forms, because sports cards, trading card games and memorabilia have different mechanics rather than different parameters.

Model B

Line setting

What should the market be quoted at, and how wide?

Takes Model A's distribution and turns it into a two-sided quote. Prices the chance the contract never resolves at all, widens for adverse selection it can name, sizes against a market price, and refuses to quote under stated conditions.

Segments

Three asset classes, not one with a dummy.

These segments do not merely have different parameters. They have different mechanics, and a single model with a category flag will be confidently wrong about at least two of them.

SegmentWhat makes it differentSupply lagExcluded
Sports cardsA living subject drives demand, and thousands of ungraded copies sit ready to be submitted the moment a price move makes grading worthwhile. Supply answers demand within a couple of quarters.5 monthsNothing
Trading card gamesPrint runs closed decades ago, but sealed product still exists. Supply arrives in lumps when a case is opened, which is slower to start and larger when it lands. Demand moves on anniversaries and cultural cycles rather than on news about a person.9 monthsNothing
MemorabiliaOne of the object exists. There is no population to count, no grade above it to compress its premium, and no second sale of the same item to build an index from. Value rests on provenance and on a handful of loosely comparable lots.0 monthsGrade ladder, Supply response, Repeat-sales index

Memorabilia is not the same model with different numbers. It has no ladder to model jointly, no census to measure supply against, and no second sale of the same object to build an index from. It gets a comparables fit, a much wider interval floor, and the three excluded estimators named on its face.

Inputs

What the model needs.

None of it is licensed today. The status column is the honest state of the inputs, and it is the reason this page publishes a protocol rather than results.

FeedFieldsCadenceStatus
Verified salesCard, date, price, venue, fee basisDailyNot licensed
Grading censusCertified population at grade and above, as-of dateMonthlyNot licensed
Marketplace completionsTitle, sold price, sold date, venueDailyNot licensed
Search interestMention and search volume by subjectWeeklyNo feed identified

The model imports nothing from the site's own content files. That boundary is deliberate and enforced by the code: the market data this site already displays is licensed from a third party, and it is not the model's to fit.

Inside Model A

Seven layers, and where each one lives.

  1. 01Comparable salesWhat a card with these characteristics is worth this month.lib/model/hedonic.ts
  2. 02The indexWhat the market did, measured from cards that sold twice.lib/model/repeatSales.ts
  3. 03Risk and returnWhich part of a return is exposure, and which part is skill.lib/model/factors.ts
  4. 04VolatilityHow wide the distribution is, and how that width decays.lib/model/volatility.ts
  5. 05ProbabilityA drift and a width become an answer, with heavy tails.lib/model/horizon.ts
  6. 06ConfidenceHow much the estimate deserves to be believed, published beside it.lib/model/confidence.ts
  7. 07The supply channelPopulation that a price move has already provoked but the census has not printed.lib/model/supply.ts

01 — Fair value

What a card like this is worth.

A regression of log price on the things that make a slab worth what it is. Logs rather than levels, because prices here span four orders of magnitude and the market talks in multiples: a PSA 10 trades at a multiple of the 9, not at a fixed premium above it.

logPit  =  α  +  xiβ  +  θt  +  εit\log P_{it} \;=\; \alpha \;+\; x_i'\beta \;+\; \theta_t \;+\; \varepsilon_{it}
Log price of card i in month t: the characteristics explain the cross-section, and what is left over month by month is the index.
wit  =  2(Tt)/h,h=18 monthsw_{it} \;=\; 2^{-(T - t)/h}, \qquad h = 18 \text{ months}
Every comparable sale enters weighted by its age. A half-life rather than a cut-off, so a sale's influence fades instead of vanishing on a date that has nothing to do with the card.

Each characteristic below carries the sign it is expected to take, stated before any fit, so a result that contradicts the hypothesis is visible rather than absorbed. The two population terms are separate on purpose. How many exist at this grade is ordinary scarcity; how many exist above it is what actually prices a slab, because a PSA 9 with two hundred 10s above it is a different asset from one with none.

CharacteristicExpectedWhy
logPopAtGrade-More certified copies at this grade, lower price.
logPopAbove-Copies in better condition cap what this one can fetch.
topGrade+Being at the ceiling of the scale is worth a premium of its own.
topGrade:logPopAbove+The ceiling premium is convex; nothing above means nothing above.
grade+Condition, as a level.
gradeSquared+The gap from 9 to 10 is worth far more than 4 to 5.
rookie+Rookie cards carry a persistent premium within a subject.
logPrintRun-A stated print run, where one exists. Zero when it does not.
serialNumbered+Separates 'no stated run' from 'a run of one', which logPrintRun cannot.
vintage+Printed before 1980, when survival rates collapse.
logAge+Age in years at the reference date, in logs.

Two details decide whether the published number means what it appears to. Exponentiating a log-scale fit returns a median rather than a mean, and the gap between them grows with the residual variance — so the correction is computed and both figures are reported.

E^[P]  =  exβ^+θ^T1wiwieε^ismearing\widehat{\mathbb{E}}[P] \;=\; e^{x'\hat\beta + \hat\theta_T} \cdot \underbrace{\tfrac{1}{\sum w}\textstyle\sum_i w_i e^{\hat\varepsilon_i}}_{\text{smearing}}
Exponentiating a log-scale fit gives a median, not a mean. Duan's smearing factor is the correction, and the model reports both figures rather than quietly publishing one as the other.

And the band published beside the estimate is a prediction interval, not a confidence interval. The question a reader has is where the next sale will print, not where the average of cards like this sits. The second interval narrows toward nothing as the sample grows; the first never narrows past the residual scatter, because an individual sale is a draw around fair value rather than the value itself. Publishing the narrow one would be wrong in the direction that flatters us.

Comparables are only the prior. Where a card has sold before, those prints are evidence about that card, and the two are combined by precision in log space.

v^  =  τ2vcomps+ω1vˉownτ2+ω1,ω=σprint2neff+σdrift2aˉ\hat{v} \;=\; \frac{\tau^{-2} v_{\text{comps}} + \omega^{-1} \bar{v}_{\text{own}}}{\tau^{-2} + \omega^{-1}}, \qquad \omega = \frac{\sigma^2_{\text{print}}}{n_{\text{eff}}} + \sigma^2_{\text{drift}}\,\bar{a}
Verified sales first, comparables where they do not exist. The comparables surface is a prior and the card's own prints are evidence about this card. Note that only the print-noise term shrinks with the number of sales: the drift since they happened is common to all of them, so a thousand sales from three years ago cannot pin down today's price better than three years of drift allows.

02 — The index

Cards that sold twice.

When the same card sells in March and again in November, the change in its price measures the market over those months and owes nothing to what kind of card it is. Grade, subject, population, print run — every characteristic differences away, because it is the same object both times. That is a stronger identification than any comparables fit can offer, and it is why an average of listings is not an index.

log ⁣(Pi,sPi,b)  =  θsθb  +  ηi\log\!\left(\frac{P_{i,s}}{P_{i,b}}\right) \;=\; \theta_s - \theta_b \;+\; \eta_{i}
A card bought in month b and sold in month s. Every characteristic differences away, because it is the same object both times.

Three things go wrong with this estimator on real data. Two are handled here; the third cannot be, and is stated instead.

Sparsity. A month in which no card happened to sell twice carries no information, and the design is rank deficient. The usual fix is to merge months until every bucket is populated, which silently changes the reporting frequency. A second-difference penalty regularises and identifies at once, and says something defensible: the index may bend, but it should not zig-zag between adjacent months on the strength of two sales.

θ^  =  argminθ  yXθW2  +  λD2θ2\hat\theta \;=\; \arg\min_\theta \; \lVert y - X\theta \rVert_W^2 \;+\; \lambda \lVert D_2\theta \rVert^2
The second-difference penalty. It regularises and identifies at once: a month in which no card happened to sell twice still gets a value, and the index may bend without zig-zagging on the strength of two sales.

Holding period. A card held eight years has a noisier return than one flipped in a quarter, simply because more can happen. Treating both as equally informative lets long holds dominate.

E ⁣[ηi2]  =  2σprint2noise in one sale  +  σdrift2Δtiwander since\mathbb{E}\!\left[\eta_i^2\right] \;=\; \underbrace{2\sigma^2_{\text{print}}}_{\text{noise in one sale}} \;+\; \underbrace{\sigma^2_{\text{drift}}\,\Delta t_i}_{\text{wander since}}
Regressing squared changes on the holding period separates the two: the intercept is the noise in a single print, which does not grow, and the slope is the drift that accumulates. The second stage reweights every pair by the result, so a long hold no longer counts as though it were as informative as a quick flip.

Selection. Only cards that sold twice are in the sample at all, and those are the liquid ones. This cannot be estimated away. It is why the model also carries a comparables index and combines the two rather than trusting either alone.

03 — Risk and return

What is exposure, and what is skill.

A return decomposes into exposure to things many cards share and a residual belonging to that card alone. The shared part is risk and is not evidence of anything. The residual mean is alpha, and it is shrunk toward zero before it reaches anything downstream — alpha estimated from a handful of irregular sales is mostly noise, and the noisiest estimates are systematically the most extreme, so an unshrunk ranking picks out precisely the cards it knows least about.

FactorDefinitionExpectedStatus
MarketExposure to the category index.+Repeat-sales index
ScarcityNegative log population at grade, standardised.+Grading census
QualityGrade relative to the population's grade distribution.+Grading census
VintageAge of the card, standardised within category.?Card attributes
MomentumTwelve-month log price change, skipping the last month.+Verified sales history
LiquidityRecency-decayed sales per year, standardised.-Verified sales history
AttentionSearch and mention volume for the subject, standardised.+No feed identified

04 · 05 — Volatility and probability

The width is the answer.

A probability that a card gains ten per cent in six months is a statement about a distribution, and the width of that distribution decides whether the answer is fifteen per cent or forty-five. Getting the drift slightly wrong moves it a little; getting the volatility wrong moves it enormously.

σt2  =  ω+αrt12+βσt12,α+β<1\sigma^2_{t} \;=\; \omega + \alpha\, r_{t-1}^2 + \beta\, \sigma^2_{t-1}, \qquad \alpha + \beta < 1
Conditional variance. Omega is pinned so the model's unconditional variance matches the sample's, and alpha and beta are images of unbounded parameters under transforms that keep their sum below one, so an explosive model cannot be proposed.

Volatility clusters. A violent period is not a run of independent surprises, it is a spell in which surprises are large — so a forecast must decay back toward the long-run level rather than projecting today’s figure across the whole horizon. That matters most exactly when it is hardest to remember: after a shock.

E ⁣[σt+k2]  =  σˉ2+(α+β)k1 ⁣(σt+12σˉ2)\mathbb{E}\!\left[\sigma^2_{t+k}\right] \;=\; \bar\sigma^2 + (\alpha+\beta)^{\,k-1}\!\left(\sigma^2_{t+1} - \bar\sigma^2\right)
Variance over a horizon is not h times today's. It decays toward the long-run level, which matters most exactly after a shock, when multiplying today's figure would project the panic across the whole window.

Four things contribute to the uncertainty over a horizon, and they are published separately rather than summed into one band. A wide interval because the market is volatile and a wide interval because nobody has bought this card since 2021 are different situations calling for different decisions, and a reader can only tell them apart if the model says which.

β2Vhidxcategory  +  Vhidiothis card  +  Vparestimation  +  Vobswhere the price is  =  Vh\underbrace{\beta^2 V^{\text{idx}}_{h}}_{\text{category}} \;+\; \underbrace{V^{\text{idio}}_{h}}_{\text{this card}} \;+\; \underbrace{V^{\text{par}}}_{\text{estimation}} \;+\; \underbrace{V^{\text{obs}}}_{\text{where the price is}} \;=\; V_h
The four sources of uncertainty over the horizon, published separately. The last is the one usually left out: fair value is never observed, only sales are, and a sale is a draw around the value rather than the value. Omitting it is the commonest way a model like this becomes overconfident.

The fourth term is the one usually left out, and leaving it out is the commonest way a model like this becomes overconfident: it amounts to treating the last print as the current value.

Pr(Rh>x)  =  1Fν ⁣(log(1+x)μhsh),sh=Vhν2ν\Pr(R_h > x) \;=\; 1 - F_\nu\!\left(\frac{\log(1+x) - \mu_h}{s_h}\right), \qquad s_h = \sqrt{V_h\tfrac{\nu-2}{\nu}}
The headline. Student-t rather than normal, and the threshold converted through log(1+x) rather than treated as symmetric — gaining a fifth and losing a fifth are different distances.

Student-t rather than normal, with degrees of freedom estimated from the data. This is not a refinement. The tails are the entire reason anyone asks the model for a probability, and a normal assigns far too little mass to exactly the moves that decide whether a position worked. The horizon is also stretched for illiquidity before any of this is computed: a six-month call on a card that trades twice a year is a longer bet than the calendar says.

Heavy tails, and four kinds of not knowing

Where the probability comes from

Split the uncertainty

5.8%

P(move beyond 60%)

Student-t, as the model computes it — 1.5σ out

6.4%

Under a normal

overstates it by 10%

−67%0+200%

Both curves have the same variance. The dashed one is a normal; the solid one is what the model uses. At 1.5 standard deviations this is not yet a tail question, and the Student-t gives the lower answer — it is more peaked, so it holds less mass in the shoulders than a normal does. Keep dragging the threshold out and the two cross over.

What the width is made of

  • 30% Category index
  • 39% This card
  • 4% Estimation
  • 26% Where the price is

Rendered from lib/model/horizon.ts and the model's own Student-t. The shaded regions are the moves being asked about, in either direction.

Indicators

Signals without a regular bar.

Every standard technical indicator assumes a regular bar, and none of them survive contact with an asset that might print four times in a year and then not at all for eighteen months. Resampling to a monthly grid and carrying the last price forward produces a series that looks regular and is mostly fabricated: long flat stretches that suppress measured volatility, then a single step that reads as a shock.

IndicatorDefinitionMinimum evidence
MomentumLog change between a recent and an earlier window, skipping the latest month because the last print is the noisiest observation in the series.Two sales spanning the lookback.
Realised volatilitySquared log returns divided by their gaps, net of the noise in a single sale, annualised.Three consecutive pairs, and the panel's print variance.
Value gapLast sale minus the comparable-sales estimate, in log points.One sale and a fitted model.
Sale intensityRecency-decayed sales per year.None; zero is a meaningful answer.
Relative strengthRecency-weighted gains over gains plus losses, gap-normalised.Four consecutive pairs.
DrawdownLatest observation below the highest observed, in log points.Three sales.
Population growthLog change in certified count at grade over a year.Two census snapshots a year apart.

Each returns nothing at all when the evidence falls below its minimum, rather than a number. Momentum from two sales eighteen months apart is a decorative figure, and the model has to be able to tell weak momentum from nobody knowing.

06 — Confidence

How much to believe it.

Not every estimate deserves equal weight, and the model publishes a score saying so beside each one. It is a plain weighted sum because it has to be explainable: each component’s contribution is returned alongside the total, so a card scoring Low can be shown why rather than handed a number with no account of itself.

C  =  jwjgj(evidence)jwj,wj0,  gj non-decreasingC \;=\; \frac{\sum_j w_j\, g_j(\text{evidence})}{\sum_j w_j}, \qquad w_j \ge 0,\; g_j \text{ non-decreasing}
Non-negative weights on components that are individually non-decreasing in evidence, so the score is monotone by construction: more sales, more recent sales, more venues or a tighter interval can never lower it.

Monotonicity is a construction rather than a hope. More sales, more recent sales, more venues or a tighter interval can never lower the score, because every component is non-decreasing in its input and every weight is non-negative. It is property-tested over sixty thousand comparisons anyway, since this is the failure nobody would notice: a score that occasionally moved the wrong way would mis-rank a page and never raise a complaint.

BandWhat it means
HighFrequent verified sales across multiple independent venues.
MediumPeriodic sales, or comparables that require more adjustment.
LowSparse or dated evidence. Estimates carry wide uncertainty.

07 — The supply channel

Population is a flow, not a column.

A share count does not rise because the share price rose. A certified population does. When a card's price climbs, grading a copy becomes worth the fee, raw copies come out of collections, and months later the census prints a higher number, which caps the price. Demand pulls, supply answers late, and the answer is what breaks the move.

Almost every model of this asset class treats population as a static scarcity number: a column in a cross-sectional regression. It is not static. It is endogenous to price, and because the lag runs to several months, part of it is knowable in advance. A price move you can already see implies a submission wave that has not yet reached the census.

That is the forecastable part, and it is what this model estimates.

A price shock, and the supply that answers it — and stays

The mechanism, running

Peak to trough
68.8%
Population added
71.6%
Settles against start
-14.6%

Price

Certified population

month 0shockmonth 96

The price rises, and 6 months later submissions answer. The census prints a larger population, and the scarcity term takes back 30.1% of the move. Nobody had to sell for that to happen — supply simply arrived.

And it does not leave. A price fall does not un-certify a card, so every copy graded during the rally stays in the population for good: the price settles at -14.6% against where it began, not back at it. Supply is a ratchet, which is why a card that has already had its run is a different asset from one that has not.

A difference equation, not a fit: the same feedback lib/model/simulate.ts generates, reduced to the two series that matter.

ΔlogNi,t;=;αi+τt+(γ0+γ1(logNilogN))ri,tL;+;ui,t\Delta \log N_{i,t} ;=; \alpha_i + \tau_t + \big(\gamma_0 + \gamma_1 (\log N_i - \overline{\log N})\big)\, r_{i,\,t-L} ;+; u_{i,t}
Population growth on the card's own return lagged by the submission lag, measured within card and within month. The interaction is the part that matters: the response is larger for cards with more ungraded copies still out there.

Three things had to be right before that was worth claiming, and each of them changed the answer.

  1. 01

    Two-way fixed effects, or the sign reverses

    Cards differ in how readily they are submitted. A card with a high baseline submission rate has both faster population growth and weaker prices, for reasons unrelated to either causing the other. Pooled across cards, that produced a confident negative response in simulated data generated with no supply channel at all.

  2. 02

    The reduced form is not evidence of supply

    A lagged price move predicts weaker future returns even where submissions ignore price entirely, because an unusually high print reverts. Any model presenting that coefficient as a supply effect is claiming a mechanism it has not identified.

  3. 03

    Heterogeneity is what separates them

    Reversion does not care how many ungraded copies exist. Submissions do. So the part of the response that scales with population is attributable to supply in a way the common part is not, and that interaction is the test.

Model B — prediction markets

A market can fail to resolve.

A contract on whether a card is above a price by a date settles off a sale. For a card that trades four times a year, the chance that no qualifying sale happens inside the window is not a rounding error. It is frequently the largest single term in the answer.

A model that computes the probability the price clears the threshold has answered a question nobody can trade. It has priced the card, not the contract.

What happens when nothing prints is decided by the rulebook rather than the model, and it changes the answer completely. If the contract voids, the tradeable number is the conditional probability and the position carries settlement risk. If it settles NO, illiquidity is itself a bet against, and a thinly traded card becomes a structurally cheap NO. That asymmetry disappears entirely if you only model the price.

Pr(YES);=;(1eλM)something printsm=1M(1eλ)eλ(Mm)1eλMlast print lands in month mPr ⁣(Pm>K)\Pr(\text{YES}) ;=; \underbrace{\big(1 - e^{-\lambda M}\big)}_{\text{something prints}} \cdot \sum_{m=1}^{M} \underbrace{\frac{(1-e^{-\lambda})\,e^{-\lambda(M-m)}}{1-e^{-\lambda M}}}_{\text{last print lands in month } m} \cdot \Pr\!\big(P_m > K\big)
Sales arrive as a Poisson process, so both halves are available in closed form. The resolving print is the last sale inside the window rather than a uniformly chosen one, so the weights lean late, and the price distribution at that date is wider than a midpoint assumption would give.

Set a line on a card

Model B, live

Settles on
If nothing prints

26.9%

Fair

What the contract is worth

31.1%

Clears, if it prints

What a price-only model would quote

13.5%

Never resolves

No qualifying sale in the window

A price-only model quotes 31.1%. The contract is worth 26.9%, because 13.5% of the time nothing prints and it settles NO. That gap is 4.2 points of free money for whoever read the rulebook.

When the resolving sale lands

month 1month 6never

The bars lean late because the resolving print is the last sale in the window, not a uniformly chosen one — so the price has longer to move than the window’s midpoint would suggest. Expected at month 4.4 of 6.

0.22

Bid

0.32

Ask

10.1

Spread, pts

  • +7.0How unsure we are where the price is today
  • +1.1Chance the market never gets a qualifying sale

What the answer is made of

  • 27% Prints and clears
  • 60% Prints and misses
  • 14% Never prints, settles NO

Computed in your browser by lib/model/line.ts — the same function the backtest calls. Nothing here describes a real card or a real market.

Why the spread is as wide as it is

A quote is a probability plus a spread, and the spread exists because whoever takes the other side may know something. On cards, the informed flow is identifiable in advance. Every reason comes back attached to the quote, so a human can overrule it.

ReasonWhat it prices
How unsure we are where the price is todayThe valuation interval, carried straight into the quote.
Chance the market never gets a qualifying saleWider on a voiding contract than on one that settles NO, because the position may simply not exist.
A print months oldA counterparty holding a private bid knows more than the tape does.
A census update inside the windowA population report moves the whole grade ladder, and its date is public.
A scheduled auctionResolution concentrates on one lot, in one room, on one night.

When it declines to quote

  • No observed trading at all, so resolution cannot be modelled.
  • Void is more likely than settlement, which makes the quote a bet on the rulebook rather than on the card.
  • A valuation interval too wide to support a two-sided market.
  • A last print over two years old.

Evidence

What the checks actually measured.

These are measurements, not claims. Each comes from running the model against a market whose true parameters are known because the simulator chose them, and every figure is written out by the same command a reader can run.

Figures marked “log” are log points — the unit the model works in. Read 0.17 as roughly a 17% error; unlike percentages, they add up cleanly across periods.

Does it find what is actually there?

Each estimator is given data generated from parameters we chose, and asked to recover them knowing only the observations.

Fitted index against the path that generated it (1.00 is perfect)
0.9927
Typical index error
0.0221log
Repeat-sales index error, averaged over six runs
0.0486log
GARCH alpha recovered (true 0.10)
0.1006
GARCH beta recovered (true 0.85)
0.8486
Degrees of freedom recovered (true 6)
6.3658
Population growth that follows a price rise — mechanism switched on
0.3390
Share of population growth the price move explains — switched on
0.1941
Instrument strength (anything above 10 is usable)
917
A predictor handed the answer scores this — so the harness can see skill
1.0000
And this on the ranking measure — 0.50 would be a coin flip
1.0000

Does it refuse to find what is not?

The harder half, and the one that decides whether any of the rest means anything. The same estimators are pointed at data generated with the mechanism switched off, and at a predictor fed pure noise. A model that scores well here is broken in the way that matters most, because it would score well on anything.

Population growth that follows a price rise — mechanism switched off
0.0027
Share of population growth the price move explains — switched off
2.0e-4
Pure noise, ranking measure — bottom of the interval (0.50 is chance)
0.4782
Pure noise, ranking measure — top of the interval
0.5339
Pure noise, signal measure — bottom of the interval (0 is none)
-0.0490
Pure noise, signal measure — top of the interval
0.0281
A past move predicts weaker returns with NO supply channel at all — so this is reversion, not supply
-0.2265

Does it know how sure it is?

An estimate without an honest band is a guess with decoration. These check that the intervals are the size they claim and that stated probabilities happen as often as stated.

80% prediction interval, realised coverage
79.9%
Same interval measured against true value, across 400 cards
87.5%
Index band against the error actually realised (1.0 is exact)
0.9633
Stated probability against 300,000 simulated outcomes, error
0.1%
Times more evidence was checked never to lower the confidence score
60,000

Is it better than the obvious alternative?

Every part of the model has to earn its place against the simpler thing it replaced.

Valuation error from comparable sales alone
0.3239log
Valuation error using the card's own sales
0.1726log
How much harder a common card responds than a scarce one
0.0764

What this establishes is that the estimators are correct: given data produced by a known process, each recovers what produced it, inside the uncertainty it reports for itself. It establishes nothing about whether card prices are predictable. That is a separate question, it needs licensed market data, and the pre-registered thresholds below are how it will be answered.

Robustness

Being wrong about the world.

Every recovery figure above shares a weakness, and it is the first thing a sceptic should say: the simulator generates prices from very nearly the process the model assumes. Handed data built to its own specification, of course the estimator recovers the parameters. That result says the arithmetic is right. It does not say the model is.

So the same cards, the same seed and the same arrival times are run through markets that break those assumptions one at a time. The useful question is not whether the model works when we are right about the world. It is how wrong we can be before the answers stop being usable.

What is measured is not whether the model still recovers the truth — under a characteristic it cannot see, it cannot, and should not pretend to. It is whether it still knows how wrong it is. An estimator that degrades and widens to match is usable. One that degrades while still claiming precision is dangerous, and that is what this battery exists to catch.

What is brokenInstead ofError80% interval covers
None — the model's own worldNothing is broken. The control.0.12890.3%
Prices jumpRare large jumps arrive on top of the diffusion, up and down, with no warning in the preceding path.0.18486.9%
Bull and bear regimesThe market switches between a calm regime and a violent one with different drift and volatility, and the switch is unobservable.0.14689.7%
Sale prices are skewed, not lognormalThe draw is right-skewed: most sales print a little below value, a few print far above when two bidders want the same lot.0.13993.1%
A characteristic the model cannot seeA large persistent per-card effect the model has no column for, worth more than any feature it does have.0.20886.0%
Sellers time the marketA card is far likelier to be listed when it is already trading above its own recent average.0.11687.1%
Cheap cards print noisierPrint noise scales with the inverse of price: a $200 card's sales scatter far more than a $200,000 card's.0.09686.9%
The mechanism changes halfwayThe submission response and the market's volatility both change sharply at the midpoint and never change back.0.12790.3%

Error is the median distance from the true value in log points; read 0.13 as roughly 13%. Coverage is the figure that matters — a model whose error grows but whose interval grows with it has told the truth about a harder problem.

What the battery found, and what it cost to fix

  1. 01

    It caught something real

    Under a market whose volatility and drift change halfway through and never change back, an interval promising 80% delivered 49%. Not because the estimate drifted, which is expected, but because the band stayed narrow while it drifted, which is not.

  2. 02

    The first two fixes were wrong

    Widening the index variance did nothing. Widening the drift rate did nothing. Measuring instead of guessing showed why: the failure was almost pure bias — a 32% systematic offset with the spread completely unchanged. No amount of widening fixes a bias, and widening to hide one would have been worse than leaving it.

  3. 03

    The cause was a gap in the model

    The model builds a price index and then did not use it to age the card's own past sales. A print from two years ago is evidence about what the card was worth two years ago; treating it as evidence about today ignores everything the market has done since. Carrying each print forward through the index took the bias from 0.324 to 0.035 and the error from 0.318 to 0.127.

  4. 04

    What is still not right

    Every scenario now covers at or above the 80% it promises — between 86% and 93%. That is the safe direction and it is still a miscalibration: the intervals are wider than they claim to be, so the model is leaving sharpness on the table. Narrowing them is only worth doing against real data, because on simulated data it would be fitting the simulator.

These are still simulated markets. What the battery establishes is that the model's honesty about its own uncertainty survives seven specific ways of being wrong about the world — including the one that is most obviously true of cards, that sellers list into strength. It does not establish that the list is complete, and the eighth thing nobody thought of is the one that will matter.

Verification

Run it yourself.

The model is in the repository and the checks are a command, not a claim. 454 cases at the last clean run.

npm run model:validate
  1. Distributions against closed forms

    Normal and Student-t against known values and their own inverses, including the far tail where a probability of exactly zero would make a single confident miss cost an infinite log loss.

  2. Look-ahead defences

    No estimator may see a sale after its as-of month, and no census snapshot may be attached to a sale that predates it. Enforced by the panel builder refusing to return the rows at all, rather than by anyone remembering.

  3. Fold hygiene

    No training observation's label window may reach into its test window, asserted arithmetically for every fold rather than assumed from the schedule.

  4. The leak detector

    A predictor fed pure noise must produce a ranking interval containing a coin flip and a signal interval containing zero — and the same harness, handed a predictor that has seen the answer, must catch it. Either half alone proves nothing: a harness that always reports no skill passes the first perfectly.

Every case is seeded by name, so a failure is reproducible by anyone who runs it, and the evidence table above is written out by the same command — only on a run where everything passes.

Pre-registration

Written before the results.

A backtest is easy to pass if the rules are set after the answer is known. These were set before, and the reason for publishing them now — while the model cannot be fitted — is precisely that they cannot be adjusted later without the adjustment being visible.

  1. 01

    Walk forward

    Fit on history, predict the following window, step forward, repeat. A minimum of twenty-four months of training before the first fold.

  2. 02

    Purge

    A sale in month m is labelled by what happened by month m plus the horizon. Every training observation whose label window reaches into the test window is removed. Over a twelve-month horizon that cuts a full year from the end of each training set, and it must.

  3. 03

    Embargo

    Extra margin between the end of training and the start of the test window, on top of the purge. A repeat-sales index level near the boundary is still being revised as new pairs arrive, so the model is kept away from the least settled part of its own index. It widens the gap; it does not reach backwards to delete earlier test windows, which with six-month windows on six-month steps would tile the whole timeline and leave later folds with too little data to estimate even a base rate.

  4. 04

    Assert

    Before each fold the harness checks that the data it is about to hand over contains nothing at or past the test window, and throws if it does. A result that trips this assertion is discarded, not explained.

What each fold was allowed to see

The protocol, drawn

Usable folds
15
Gap per fold
17 months
Evidence minimum
met
fits on this thrown away scored on this

The hatched band is the cost of honesty: 17 months per fold that the model may not fit on, because a sale in there is labelled by something that happens inside the test window. Widen the horizon and watch it grow — 15 folds still clear the pre-registered minimum of eight.

Generated by lib/model/backtest.ts. Every bar is a real fold from makeFolds at the settings on the left.

What it must clear

MetricTargetResult
Median absolute error of fair value, out of sampleAt or below 25%, and below the last-sale-carried-forward benchmark— not yet run
80% prediction interval coverageBetween 75% and 85%— not yet run
Brier skill against the base rate, 12-month P(move > 20%)At least 0.05, with a block-bootstrap 95% interval excluding zero— not yet run
Log loss against the index-only benchmarkLower in at least 60% of folds— not yet run
Calibration slopeBetween 0.85 and 1.15, with no reliability bin off by more than 0.10— not yet run
AUC, directionAt least 0.55, interval excluding 0.50— not yet run
Spearman information coefficient, 12-month returnAt least 0.03 mean, IR at least 0.30, interval excluding zero— not yet run
Top-minus-bottom decile spread, net of a 20% round tripAbove zero, interval excluding zero— not yet run
Annual turnoverAt or below 40%— not yet run
Minimum evidenceAt least 5 years, 25,000 verified sales and 8 walk-forward folds— not yet run
GARCH persistenceBelow 0.999 in every fold— not yet run

No threshold above may be amended once a dataset has been unblinded, except by publishing a dated amendment alongside the original wording. If the evidence minimum is not met, the model ships labelled not validated rather than with softened thresholds.

What it is measured against

BenchmarkWhat it does
Base ratePredicts the historical frequency of the move for every card. Beating this is the minimum bar for a probability forecast.
Last sale carried forwardValues every card at its most recent print and forecasts no change. What a careful person with no model would do.
Index onlyMoves every card with the category index and claims no card-specific information. Isolates whether the per-card work adds anything.
Random walkZero expected return, volatility from the card's own history. The null hypothesis for the return distribution.

Limitations

What it cannot do.

It has not been fitted

No licensed sales data is in hand. Every number the model can currently produce comes from its own simulator and describes no real card.

Repeat sales sees only cards that trade

Cards that sell twice are the liquid ones, and that selection cannot be estimated away. It is why the model also carries a comparables index rather than trusting either alone.

A card is not a constant asset

Certified populations only grow, so the same slab is a slightly commoner thing each year and its price drifts below the market for that reason alone. A repeat-sales index measures the card, not the category.

Attention has no feed

Search and mention volume is defined as a factor, hypothesised to be positive, and unpopulated. No source has been identified, and saying so is more useful than filling the column with a proxy.

Thin evidence stays thin

A card that trades twice a decade cannot be valued precisely, and the model's answer for it is a wide interval and a low confidence band rather than a number that looks like the others.

Indices are revised

Adding sales changes past index values. A figure published today may not be the figure for that month a year from now, and the revision policy has to be stated wherever the index is shown.

The data is the missing piece.

FCM-1 is finished and cannot be fitted. When licensed sales data is in hand, the protocol above runs as written and the results column fills in — whatever it says.

How it works

Statements describing Foil's intended structure, products and timelines are forward looking and remain subject to change, to applicable securities laws, and to the determinations of qualified counsel and regulated partners.