Pokemon 25 PSA 10 Index — Methodology Specification v5.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, MTG, and One Piece variants 30 (raised from 10 in v5 — thin-trading grail cards produce unreliable reference prices at low sale counts; see the v5 changelog for the motivating cases). 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. - U7 — Crossover / licensed-IP exclusion (v5, per-index). Products whose
set/console name matches a versioned crossover pattern list are excluded
from the index universe. Matching is word-boundary and case-insensitive
(same matcher as U2, but against set names, not product names). The list is
a per-index G3 parameter and is empty for every index except
mtg-25-psa10, whose scope is Magic’s own IP only: crossover / Universes Beyond sets (Final Fantasy, Lord of the Rings, Marvel, Warhammer 40,000, etc.) are reserved for a future dedicated crossover index — their snapshots keep accumulating regardless (this is a selection screen, not an ingest change). The list also excludes generic collab/promo consoles where crossover cards hide under a set name no IP pattern can catch (Secret Lair Drop, Promo — reviewed 2026-07-05). Masterpiece-style Magic-IP sets (Kaladesh Inventions / Masterpiece series, Mystery Booster, 30th Anniversary) are Magic IP and deliberately NOT excluded. Every selection run over an index with U7 patterns emits the full set classification (crossover vs in-scope) plus review flags for ambiguous names (a versioned review-keyword list — e.g. Secret Lair, Promo consoles, where collab cards can hide under a generic set name): review flags are for humans, same pattern as the console registry — nothing is auto-excluded by a review flag.
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.
- R4-A — Auto-rebalance with safety gates (v5 amendment to R4): rebalances
execute automatically on the effective date — the daily run detects an
open window ([effective date, effective date + 5 calendar days], the window
extension is a G3 parameter) with no commit yet, runs the selection, and:
- auto-commits (identically to a manual
--commit: divisor continuity per C4, integer quantities, basket-cost step recorded) when no safety gate trips, logging arebalance_auto_committedevent alongside the rebalance itself; - withholds the commit for human review when ANY gate trips, writing
the dry-run report, logging a
rebalance_gatedevent naming the tripped gates, and alerting loudly in the run summary. The gates:- any entering constituent carries an anomaly flag on the reference date — a cross-grade monotonicity violation involving its PSA 10 or grade 9 bucket, or a >30% single-day move within the trailing 5 snapshot days (both thresholds are G3 parameters);
- turnover above ceiling: more than 10 of 25 constituents changing (G3 parameter);
- the reference-date ingest was
partialorfailedfor the index’s category; - any proposed realized weight deviates more than ±3pp from target (the existing W3 review flag).
- A gated window is alerted once and never auto-retried (the
rebalance_gatedevent is the marker); the human reviews the report and runsrebalance --commitmanually inside the window. R3’s grace period still governs manual commits. - The zero-change case (selection identical to incumbents, no quantity
changes) records a
rebalance_no_changeevent — the window always ends in a terminal recorded state (auto-committed, gated, or no-change). This amendment removes the human from the happy path only; every non-clean outcome still demands the same R4 human review as before.
- auto-commits (identically to a manual
- 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), crossover pattern and review-keyword lists (U7), cap level (W2), realized-deviation flag threshold (W3), buffer ranks (S3), staleness thresholds (U4, C5, R5), and the auto-rebalance gate parameters (R4-A: window extension days, day-move ratio and trailing window, turnover ceiling, monotonicity bucket set) 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 30; otherwise identical |
| MTG flagship | category filter swapped; min_volume 30; U7 exclusion active (Magic-IP-only scope; generic collab/promo consoles also excluded) |
| One Piece flagship | category filter swapped; U5 takes registry language ‘en’; min_volume 30 |
| One Piece Japan (future) | U5 takes ‘ja’ — the registry keeps classifying and data keeps accumulating |
| MTG Crossover (future) | dedicated crossover/Universes Beyond universe (roughly the complement of MTG’s U7 screen) |
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
- v5.0-draft (2026-07-05): Eligibility tightening, MTG scope decision,
re-inception of the three unpublished variants, and rebalance automation:
- U6
min_volumeraised 10 → 30 for the Pokemon Japan, MTG, and One Piece variants, effective via re-inception on 2026-07-05. Rationale: exclude thin-trading grail cards whose reference prices are unreliable at low sale counts. Motivating cases: Illustrator Pikachu (~12 sales/yr) carried a grade-7 price below its loose price — the classic cross-grade monotonicity artifact of near-zero liquidity — while anchoring the Japan basket at 10% weight; Portgas.D.Ace [Championship 2023 Finalist] P-028 (14/yr) showed a 517% “day move” ($2,025 → $12,500) that was an n=2 median artifact, not a price change. The screen is rules-based — no hand-picking; the flagship is unchanged at 50. Exit lists with per-card volumes are in the re-inception reports (reports/inception-*-v5.md) and the Phase-1 comparison (reports/u6-threshold-comparison-2026-07-05-v4.md). - New U7 — crossover/licensed-IP exclusion for
mtg-25-psa10: the MTG index covers Magic’s own IP only. Sets classified as crossover (a versioned pattern list in per-index config: Final Fantasy, TMNT, Avatar: The Last Airbender, Marvel, Spider-Man, Lord of the Rings, Doctor Who, Warhammer, 40,000, Fallout, Assassin’s Creed, Jurassic, Transformers, Street Fighter, Walking Dead, Stranger Things, Hatsune Miku, Secret Lair x) are excluded from its universe; they are reserved for a future dedicated crossover index and their data keeps accumulating. Kaladesh Inventions / Masterpiece series, Mystery Booster, and 30th Anniversary are Magic IP — not excluded. Ambiguous consoles (Secret Lair, Promo) are review-flagged every run, never auto-excluded. - Re-inception disclosure (per G2):
pokemon-japan-25-psa10,mtg-25-psa10, andone-piece-25-psa10were re-incepted under v5 on 2026-07-05 at level 1000.00, with their prior levels (two days, 2026-07-03 → 2026-07-04, never published anywhere) discarded, not restated. G2 permits restatement when disclosed; this changelog entry is the disclosure, and each index carries are_inceptionevent recording the reason and old/new methodology versions. The flagship’s history is untouched. - R4-A — auto-rebalance with safety gates (amendment to R4, above): scheduled rebalances execute automatically on the effective date unless a safety gate (entrant anomaly, >10-of-25 turnover, partial/failed reference-day ingest, ±3pp realized-weight deviation) withholds the commit for human review. Gated windows alert once and wait for a manual commit; every window ends in a recorded terminal state.
- Same-day draft amendments (2026-07-05, post U7-scan review by Mason):
- U7 pattern list extended for
mtg-25-psa10: the generic collab/promo consolesMagic Secret Lair DropandMagic Promoare excluded outright — crossover cards hide under these set names where no IP pattern can catch them. The four smaller Secret Lair consoles (30th Anniversary, Ultimate Edition, Showdown, Exclusives) remain in-scope review flags. No current constituent was affected, so no re-selection was needed; the screen governs all future selections. - One Piece flagship scope decided: English-only. U5 applies with
registry language ‘en’ (previously no split); each language gets its
own index later, and non-‘en’ consoles belong to no index until then.
one-piece-25-psa10was re-incepted 2026-07-05 at 1000.00 under this scope — five Japanese-language constituents left the universe; the prior v5 levels (2026-07-04 → 05, never published) were discarded, disclosed here per G2.
- U7 pattern list extended for
- U6
- 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.