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.
- 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.
- 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.
- 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.
| Segment | What makes it different | Supply lag | Excluded |
|---|---|---|---|
| Sports cards | A 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 months | Nothing |
| Trading card games | Print 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 months | Nothing |
| Memorabilia | One 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 months | Grade 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.
| Feed | Fields | Cadence | Status |
|---|---|---|---|
| Verified sales | Card, date, price, venue, fee basis | Daily | Not licensed |
| Grading census | Certified population at grade and above, as-of date | Monthly | Not licensed |
| Marketplace completions | Title, sold price, sold date, venue | Daily | Not licensed |
| Search interest | Mention and search volume by subject | Weekly | No 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.
- 01Comparable salesWhat a card with these characteristics is worth this month.lib/model/hedonic.ts
- 02The indexWhat the market did, measured from cards that sold twice.lib/model/repeatSales.ts
- 03Risk and returnWhich part of a return is exposure, and which part is skill.lib/model/factors.ts
- 04VolatilityHow wide the distribution is, and how that width decays.lib/model/volatility.ts
- 05ProbabilityA drift and a width become an answer, with heavy tails.lib/model/horizon.ts
- 06ConfidenceHow much the estimate deserves to be believed, published beside it.lib/model/confidence.ts
- 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.
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.
| Characteristic | Expected | Why |
|---|---|---|
| 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.
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.
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.
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.
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.
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.
| Factor | Definition | Expected | Status |
|---|---|---|---|
| Market | Exposure to the category index. | + | Repeat-sales index |
| Scarcity | Negative log population at grade, standardised. | + | Grading census |
| Quality | Grade relative to the population's grade distribution. | + | Grading census |
| Vintage | Age of the card, standardised within category. | ? | Card attributes |
| Momentum | Twelve-month log price change, skipping the last month. | + | Verified sales history |
| Liquidity | Recency-decayed sales per year, standardised. | - | Verified sales history |
| Attention | Search 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.
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.
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.
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.
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%
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.
| Indicator | Definition | Minimum evidence |
|---|---|---|
| Momentum | Log 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 volatility | Squared 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 gap | Last sale minus the comparable-sales estimate, in log points. | One sale and a fitted model. |
| Sale intensity | Recency-decayed sales per year. | None; zero is a meaningful answer. |
| Relative strength | Recency-weighted gains over gains plus losses, gap-normalised. | Four consecutive pairs. |
| Drawdown | Latest observation below the highest observed, in log points. | Three sales. |
| Population growth | Log 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.
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.
| Band | What it means |
|---|---|
| High | Frequent verified sales across multiple independent venues. |
| Medium | Periodic sales, or comparables that require more adjustment. |
| Low | Sparse 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
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.
Three things had to be right before that was worth claiming, and each of them changed the answer.
- 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.
- 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.
- 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.
Set a line on a card
Model B, live
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
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.
| Reason | What it prices |
|---|---|
| How unsure we are where the price is today | The valuation interval, carried straight into the quote. |
| Chance the market never gets a qualifying sale | Wider on a voiding contract than on one that settles NO, because the position may simply not exist. |
| A print months old | A counterparty holding a private bid knows more than the tape does. |
| A census update inside the window | A population report moves the whole grade ladder, and its date is public. |
| A scheduled auction | Resolution 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 broken | Instead of | Error | 80% interval covers |
|---|---|---|---|
| None — the model's own world | Nothing is broken. The control. | 0.128 | 90.3% |
| Prices jump | Rare large jumps arrive on top of the diffusion, up and down, with no warning in the preceding path. | 0.184 | 86.9% |
| Bull and bear regimes | The market switches between a calm regime and a violent one with different drift and volatility, and the switch is unobservable. | 0.146 | 89.7% |
| Sale prices are skewed, not lognormal | The draw is right-skewed: most sales print a little below value, a few print far above when two bidders want the same lot. | 0.139 | 93.1% |
| A characteristic the model cannot see | A large persistent per-card effect the model has no column for, worth more than any feature it does have. | 0.208 | 86.0% |
| Sellers time the market | A card is far likelier to be listed when it is already trading above its own recent average. | 0.116 | 87.1% |
| Cheap cards print noisier | Print noise scales with the inverse of price: a $200 card's sales scatter far more than a $200,000 card's. | 0.096 | 86.9% |
| The mechanism changes halfway | The submission response and the market's volatility both change sharply at the midpoint and never change back. | 0.127 | 90.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
- 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.
- 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.
- 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.
- 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:validateDistributions 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.
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.
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.
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.
- 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.
- 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.
- 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.
- 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
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
| Metric | Target | Result |
|---|---|---|
| Median absolute error of fair value, out of sample | At or below 25%, and below the last-sale-carried-forward benchmark | — not yet run |
| 80% prediction interval coverage | Between 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 benchmark | Lower in at least 60% of folds | — not yet run |
| Calibration slope | Between 0.85 and 1.15, with no reliability bin off by more than 0.10 | — not yet run |
| AUC, direction | At least 0.55, interval excluding 0.50 | — not yet run |
| Spearman information coefficient, 12-month return | At 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 trip | Above zero, interval excluding zero | — not yet run |
| Annual turnover | At or below 40% | — not yet run |
| Minimum evidence | At least 5 years, 25,000 verified sales and 8 walk-forward folds | — not yet run |
| GARCH persistence | Below 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
| Benchmark | What it does |
|---|---|
| Base rate | Predicts the historical frequency of the move for every card. Beating this is the minimum bar for a probability forecast. |
| Last sale carried forward | Values every card at its most recent print and forecasts no change. What a careful person with no model would do. |
| Index only | Moves every card with the category index and claims no card-specific information. Isolates whether the per-card work adds anything. |
| Random walk | Zero 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.
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.