Pokemon 25 PSA 10 Index — Methodology Specification v4.0-draft
The flagship index and the template all other variants derive from. Written to be implementable directly by the index engine (Piece 2) and publishable, lightly edited, as the public methodology doc.
1. Index Objective
Measure the price performance of the 25 most valuable Pokemon TCG single cards in PSA 10 condition, value-weighted with a 10% cap, rebalanced quarterly under fixed published rules.
2. Universe (what is eligible at all)
A product is in the eligible universe if ALL of the following hold:
- U1 — Category:
products.category = 'pokemon'in our database. - U2 — Single cards only. Sealed products (booster boxes, ETBs, packs, decks) are excluded from this index (they get their own variant). Implementation note: PriceCharting does not cleanly flag sealed vs. single; exclusion is by name-pattern filter matching each pattern as a whole word, case-insensitively (“Booster”, “Box”, “Pack”, “Collection”, “Tin”, “Deck”, “Bundle” — so “Splitting” does not match “Tin” and “Boxer” does not match “Box”). The filter list is part of this methodology and versioned with it. Engine must log everything excluded by this filter at each rebalance for human review.
- U3 — PSA 10 price exists. The product has a non-null, non-zero price in the
psa_10condition bucket on the selection reference date. - U4 — Data freshness. The product has PSA 10 snapshots on at least 80% of the trailing 60 calendar days (i.e., its price is being actively maintained, not stale). Until we have 60 days of history, substitute: snapshot present on ≥80% of all days since our data inception.
- U5 — Language scope (console registry): The flagship includes English-language
cards only. Language classification lives in a
consolesregistry table (one row per PriceCharting console/set name: language ‘en’/‘ja’/‘zh’/‘ko’/‘unknown’, a reviewed flag, and first-seen date), seeded once from existing data by word-boundary marker match (“Japanese” → ‘ja’, “Chinese” → ‘zh’, “Korean” → ‘ko’, else ‘en’). Index universes join through this registry: the flagship takes ‘en’, the Pokemon Japan variant takes ‘ja’; ‘zh’, ‘ko’, and ‘unknown’ belong to no index (Chinese/Korean variants may come later — snapshots keep accumulating for them regardless). A console name never seen before is registered by the daily pipeline as ‘unknown’ and reported loudly for human review — the pipeline never silently auto-classifies. Additionally, ‘en’-classified consoles whose names match a versioned suspect-keyword list (currently: Carddass, Sealdass, Topsun, Amada, Banpresto, Meiji, Marumiya, Menko — Japanese vintage/vending markers that hide under English-looking names) are flagged in every run’s report for human review; suspects are never auto-reclassified. The registry contents and the suspect-keyword list are versioned parameters of this methodology, per G3. - U6 — Minimum sales volume. The product’s most recent PriceCharting
sales-volume(yearly units sold) must be at least the per-indexmin_volumeparameter: flagship 50, Pokemon Japan 25 variant 10 (MTG / One Piece first looks also 10). Note the volume figure is per product across all grades/conditions (PriceCharting reports one yearly-units number per product), so this is a product-liquidity proxy, not PSA-10-specific liquidity. Products with no volume data fail the screen (liquidity cannot be verified). Hysteresis: incumbent constituents remain eligible at ≥80% of min_volume (the ratio is a parameter); entrants need the full threshold. This stops churn from cards oscillating around the volume boundary.
3. Selection (which 25 get in)
- S1 — Reference price: the trailing 5-day median of each product’s
psa_10price ending on the selection reference date. (Median of ≤5 daily values; robust to a single-day glitch in the feed.) - S2 — Rank all eligible products by reference price, descending.
- S3 — Select top 25, with a rank buffer for incumbents: a current constituent remains selected if it ranks in the top 30; a non-constituent must rank in the top 25 to enter. This reduces churn from cards that hover around the boundary. Net constituent count is always exactly 25 (if buffers create an overflow, lowest-ranked incumbents beyond position 25 are dropped last-in-first-out by rank).
- S4 — Tie-break: equal reference prices are broken by higher data-freshness ratio (U4 metric), then alphabetically by product name (deterministic, auditable).
4. Weighting
- W1 — Dollar-turnover target weight: each constituent’s target weight is its dollar-turnover share: (reference price × yearly units sold) ÷ Σ of the same across all 25 constituents. Rationale: weight should reflect the economic activity in a card — the dollars actually changing hands — not price level alone; a five-figure card trading hundreds of times a year carries more market weight than a six-figure card trading five times. Selection is unaffected: U1–U6 and S1–S4 are unchanged and ranking stays by reference price; turnover determines weight only.
- W2 — 10% cap, iterative redistribution: any constituent above 10% is set to exactly 10%; the excess is redistributed pro-rata across uncapped constituents; repeat until no constituent exceeds 10% (standard capped-index algorithm; always converges since 25 × 10% > 100%).
- W3 — Integer quantities (smallest faithful basket): the index holds whole cards. Notional value V = max over constituents of (reference price ÷ capped target weight); the maximizing constituent (the anchor) gets exactly quantity 1, and every other card gets Q_i = round(w_i × V ÷ P_i) (half-up), with a minimum of 1. Realized weights (P_i × Q_i ÷ basket cost) are published beside the targets; a single-constituent deviation beyond ±3 percentage points is a review flag, never auto-fixed. Quantities are frozen between rebalances.
- W4 — Weights are set only at rebalance. Between rebalances, realized weights drift with prices (no continuous re-capping, no quantity changes). If drift pushes a card past 10% intra-quarter, that is reported but not acted on until the next rebalance. (This is how S&P capped indexes behave and keeps the index a pure price measure between rebalances.)
5. Index Calculation (daily)
- C1 — The index is its basket: the integer quantities from W3 are the index —
there is no separate notional or unit-basket construct. Store Q_i in
constituents(a quantities column). Each index publishes, daily, both its level (C2–C4) and its total basket cost in dollars (Σ P_i,t × Q_i). Basket-cost steps at rebalances (quantity changes) are recorded as they occur, never smoothed — the level, via the divisor, is the continuous series; the cost is the honest price of holding the basket. - C2 — Daily level:
Level_t = Σ(P_i,t × Q_i) / Divisor. - C3 — Base: Level = 1000.00 on the inception date. Divisor is initialized as
Σ(P_i,0 × Q_i,0) / 1000. - C4 — Divisor adjustment at rebalance: on each rebalance effective date, the new
divisor is
Σ(P_i,new × Q_i,new) / Level_(effective date, pre-rebalance)— i.e., the level is continuous through constituent and weight changes; only subsequent returns reflect the new basket. - C5 — Daily price input: that day’s snapshot price for each constituent’s
psa_10bucket. Missing snapshot for a constituent → carry forward its last known price and mark the day’s levelis_stale = trueinindex_levels. If >20% of constituents are carry-forward on a given day, the level is computed but flagged for review (log +partialstatus), not suppressed. - C6 — Rounding: levels published to 2 decimals; internal math at full precision; prices remain integer cents end-to-end.
6. Rebalance Schedule
- R1 — Frequency: quarterly.
- R2 — Selection reference date: the last calendar day of March, June, September, December.
- R3 — Effective date: the 5th calendar day of the following month (buffer for review of the mechanical output).
- R4 — Human review, no discretion: between reference and effective dates, the mechanically generated constituent list is reviewed for data errors only (e.g., a mispriced product, a sealed item that slipped the U2 filter). The review packet includes cross-grade monotonicity flags: for any candidate where multiple grade buckets have prices, a higher grade priced below a lower grade (PSA 10 below grade 9, graded below loose, etc.) is flagged as a likely data error. The PSA 10 / loose price ratio is reported as informational context only, not an error flag (vintage grails legitimately exceed 50×). Any override is logged with a reason and disclosed in the methodology changelog. Overrides should be rare; recurring overrides mean the rules need a version bump, not more discretion.
- R5 — Extraordinary removal: if a constituent’s data goes stale beyond 15 consecutive days intra-quarter, it is removed at the next daily calculation, its weight redistributed pro-rata, and the divisor adjusted for continuity. Removals are disclosed.
7. Inception
- I1 — Inception date = the first date with a computed level, using the first available rebalance-style selection run on accumulated data (earliest practical: once U4’s provisional freshness rule has ~2 weeks of snapshots to chew on).
- I2 — No backfilled history in v1.0. The chart starts at inception at 1000. “Extended historical returns coming soon” per the business plan.
8. Governance & Versioning
- G1 — This document is versioned (v1.0, v1.1, …). Any rule change bumps the version and is listed in a changelog with effective date.
- G2 — Because all snapshots are retained, a methodology version bump MAY restate history (recompute all levels under new rules) — if so, both series are published during a transition note. Silent restatement is never allowed.
- G3 — The name-pattern exclusion list (U2), console registry contents and suspect-keyword list (U5), minimum-volume thresholds and hysteresis ratio (U6), cap level (W2), realized-deviation flag threshold (W3), buffer ranks (S3), and staleness thresholds (U4, C5, R5) are all parameters of this spec — the engine should read them from per-index config and the registry table, not hardcode them, since every variant index reuses this template with different parameters.
9. Variant Template
Every other index is this spec with different parameters:
| Variant | Changes vs. flagship |
|---|---|
| Pokemon 25 Raw | Bucket = loose instead of psa_10 |
| Pokemon Vintage 25 | Universe filter: release era ≤ 2003 (WotC era); requires era metadata from pokemontcg.io joined via product matching |
| Pokemon Sealed 10 | U2 inverted (sealed only), N=10, bucket = new_sealed |
| Pokemon 25 Equal Weight | W1 replaced: w_i = 1/25; same cap logic unnecessary |
| Pokemon Japan 25 | U5 takes registry language ‘ja’; min_volume 10; otherwise identical |
| MTG / One Piece flagships | category filter swapped; min_volume 10; same everything else |
10. Open Items (decide before publishing v4.0 final)
- The U2 exclusion filter list: finalize from the real data — run the selection, eyeball the excluded and included lists, tune patterns.
- Registry review: all seeded
consolesrows carry reviewed = false; confirm the seeded classifications (and any ‘unknown’ arrivals) and flip reviewed = true.
Implementation checklist for Piece 2 (engine)
- Selection module: universe filters → ranking → buffer logic → outputs proposed
constituent list (dry-run mode that prints, and commit mode that writes
constituentswith effective_from). - Weighting module: turnover target weights → iterative cap → integer basket (anchor rule) → realized weights + deviation flags.
- Daily calc: read snapshots → carry-forward logic → level → write
index_levels. - Divisor continuity: unit tests proving level is unchanged across a rebalance boundary and across an extraordinary removal.
- Unit tests against a tiny hand-computed 3-card example: selection, capping (force one card >10%), divisor init, one rebalance, one stale-carry-forward day.
- All parameters (N, cap, buffer, thresholds, bucket, category, exclusion patterns)
from per-index config rows in the
indexestable or a config file — one engine, many indexes.
Changelog
- v4.0-draft (2026-07-03): Weighting overhaul plus registry/eligibility
refinements:
- W1 replaced — dollar-turnover target weights (reference price × yearly units sold, share of basket total). Rationale: weight follows the dollars actually trading in a card, not its price level alone — a six-figure card with five sales a year no longer outweighs a five-figure card with hundreds. Selection rules and ranking are unchanged; turnover affects weight only.
- New W3 — integer-quantity basket (“smallest faithful basket”): V = max(P_i ÷ capped weight); the anchor holds exactly 1 card, others Q_i = round(w_i × V ÷ P_i), min 1. Realized weights published beside targets; single-constituent deviation beyond ±3pp is a review flag, never auto-fixed. Quantities frozen between rebalances. Old W3 (drift) renumbered to W4.
- Unit-basket duality removed (C1): each index is its integer basket; daily publication adds total basket cost in dollars, with rebalance cost steps recorded, never smoothed.
- U6 hysteresis: incumbents stay eligible at ≥80% of min_volume; entrants need the full threshold.
- U5 suspect keywords: ‘en’ consoles matching a versioned Japanese-market marker list (Carddass, Sealdass, Topsun, Amada, Banpresto, Meiji, Marumiya, Menko) are flagged for review each run; never auto-reclassified.
- v3.0-draft (2026-07-02): Four changes from the first selection dry run:
- U5 console-registry classification: language now lives in the
consolestable instead of query-time string matching. ‘zh’ and ‘ko’ consoles (the dry run found ~3,900 such products classified as English) are excluded from all indexes until dedicated variants exist; never-seen consoles register as ‘unknown’ (also in no index) and queue for human review. - U2 word-boundary fix: patterns match whole words, not substrings (“Splitting” no longer hits “Tin” — restoring e.g. Sky-Splitting Deoxys to the Japan universe; a “Boxer” card no longer hits “Box”).
- New U6 minimum-volume screen: flagship ≥ 50 yearly units sold, Pokemon Japan ≥ 10 (MTG / One Piece first looks ≥ 10). Volume is per product across all grades — a product-liquidity proxy, not PSA-10-specific.
- Anomaly check replaced: the “PSA 10 > 50× loose” error flag (which flagged legitimate vintage grails) is replaced by the R4 cross-grade monotonicity check; the PSA10/loose ratio remains as informational output only.
- U5 console-registry classification: language now lives in the
- v2.0-draft (2026-07-02): U5 replaced — the flagship is now English-language cards only; Japanese-printing products (classified by console-name, a versioned G3 parameter) form the universe of the new Pokemon Japan 25 variant (added to Section 9). Former open item “Japanese cards (U5)” resolved and removed from Section 10.
- v1.0-draft: initial specification.