CCG Index

Methodology

Version 1.2.0 · Last updated

This page explains exactly how CCG Index is calculated so you can evaluate whether the numbers mean what you think they mean. Every number elsewhere on the site is derived from the rules below — if a number doesn't match what you expect, this is the page to consult first.

1. Data source — tier hierarchy

Pricing data flows from a strict tier hierarchy. Each product receives the highest-tier price available; lower tiers only fill gaps when the tier above is silent. This is deliberate — the tiers reflect data quality, not convenience. Every tick stored in our database carries asource field naming its tier so downstream calculations and external citations can audit provenance.

  1. Tier 1 — TCGPlayer market price (canonical). Sourced via two parallel streams:
    • TCGCSV mirror — a public hourly snapshot of TCGPlayer's full catalog. Refresh-rate-friendly, handles the bulk of our coverage. Tagged source="tcgcsv".
    • TCGPlayer direct API — gap-filler for products TCGCSV missed plus the weekly historical backfill that establishes 1Y change percentages. Tagged source="tcgplayer".
    Market price is TCGPlayer's computed mid-market estimate from recent sold listings — not the lowest current listing. The two sources are interchangeable for calculation purposes and produce identical numbers within rounding.
  2. Tier 2 — Mana Pool. For products carried by Mana Pool, we mirror their inventory + recent sales feed and extract a market reference. Used as a cross-check on Tier 1 and as a primary signal for any product where Mana Pool has tighter recent volume than TCGPlayer. Tagged source="manapool".
  3. Tier 3 — eBay sold + active. For products with no Tier 1/2 tick in the last 14 days (rare, low-volume, out-of-print), an eBay Browse API fallback computes a trimmed median of recent sold + active listing prices. Tagged source="ebay_sold" or source="ebay_active" so consumers can distinguish.
  4. Tier 4 — PriceCharting. For graded singles and a handful of deep-history sealed products, PriceCharting provides multi-year sales-by-grade data unavailable elsewhere. Tagged source="pricecharting" /source="pricecharting_graded".

When a downstream feature (e.g. an index calculation, an EV estimate) needs a price, it asks for "the latest market price" — internally that resolves to the highest-tier non-stale tick available. Stale = older than 14 days for sealed products, 30 days for singles.

2. Price refresh cadence

  • Primary TCGCSV sync runs every hour
  • Gap-fill for stale products runs every 24 hours
  • Weekly historical backfill runs every 24 hours, processing 500 products per day to build a full year of history
  • eBay fallback runs every 24 hours for the highest-priority stale products

3. Index value — chain-linked, base 100

Each index is a chain-linked index rebased to 100 at the start of the window — the same technique the Dow and S&P use to handle a basket whose membership changes over time. It measures the actual price growth of the basket, not the basket getting bigger as new products are added.

Each day, the index moves by the value-weighted return of the overlapping basket — only the products that have a price both the prior day and today:

index(day 0) = 100
each day:  basket = products priced BOTH yesterday and today
           return = Σ(price_today  over basket) / Σ(price_yesterday over basket)
           index(today) = index(yesterday) × return

Adding a new product never moves the index. A product that first appears today isn't in yesterday's basket, so it contributes nothing to that day's move — no jump just from being listed. From the next day on it's a normal constituent, so its own price changes (e.g. a $400 box that runs up to $800) flow into the index like everything else. The index is value-weighted: a product's percentage move counts in proportion to its share of the basket's total value.

We show the same series two ways: the base-100 value (pure growth — 137 means +37%) and a USD view that scales the growth curve so the most recent point equals the basket's current total dollar value.

4. Change percentage calculation (the subtle part)

For 24H, 1W, 1M, and 1Y change percentages, we use a paired-sum approach rather than simple(today - then) / then:

# For each product, find its price NOW and its price at timestamp T.
# Only count products that have BOTH a current and a historical price.

paired_products = [p for p in category if has_price_at(p, now) and has_price_at(p, T)]
sum_current     = Σ latest_price(p)   for p in paired_products
sum_historical  = Σ price_at(p, T)    for p in paired_products
change_pct      = (sum_current - sum_historical) / sum_historical * 100

Why this matters: a naive calculation would compare today's index value (using all N products) against the historical index value (using only the M products that existed or had prices then). When M < N, you get phantom growth that's actually just coverage expansion. The paired-sum approach rejects that artifact by only counting products that have data at both points in time.

As a consequence, 1Y change percentages for newer products are often missing until they accumulate a full year of history. You'll see "—" in place of the change chip in that case.

5. Product categorization

Products are classified into categories by a rule-based name matcher. For example:

  • "Modern Horizons 3 — Collector Booster Display Case" → Magic Collector Booster Case Index
  • "Wilds of Eldraine Commander Decks [Set of 4]" → Magic Commander Deck Set Index
  • "Mew V Box" → Pokémon Collection Box Index

The matcher is conservative — specific patterns beat generic ones (so "Collector Booster Case" is never miscategorized as a box). Products that don't match any rule fall into the per-game "Other" bucket. If you spot a miscategorization, let us know and we'll add a rule.

6. What's tracked

CCG Index tracks both sealed products (booster boxes, cases, bundles, preconstructed decks, tins, ETBs, collection boxes, specialty products) and singles (individual cards) for the supported games. Sealed coverage is the headline use case — that's where the indexes live — but every printed card has a detail page carrying the same multi-tier price history described above.

Two games in scope: Magic: The Gathering and Pokémon TCG. Sealed coverage is comprehensive across both. Singles coverage uses the full Scryfall mirror for MTG and the Pokémon TCG API mirror for Pokémon.

7. Sealed Expected Value (EV)

For a subset of sealed Magic products (boxes, packs, bundles where WotC publishes hit-rate data), CCG Index computes Expected Value: the probabilistic dollar value of the cards inside, given current single-card market prices.

# For each slot in the product's print sheet:
EV(slot) = Σ  P(card_i in this slot) × current_market_price(card_i)
         i

# Product EV is the sum of slot EVs:
EV(product) = Σ  EV(slot_j) × n_slots_j(product)
            j

Slot probabilities come from each set's published booster-construction data, which encodes WotC's rarity/foil/special-treatment slot frequencies. Card prices use the multi-tier hierarchy from §1, with a last-30-day staleness cutoff. Cards without recent prices are excluded and the slot's expected value is computed only over priced cards then scaled up — this avoids artificially deflating EV for sets with thin single-card data.

Pokémon Pack EV uses the same shape but with finish probabilities (holo / reverse holo / 1st edition holo / etc.) and Pokémon TCG API rarity tables. See /research/pokemon-pack-ev.

EV is a statistical expectation. Actual results from opening a single product vary widely — variance grows with rare-slot diversity, especially in Collector Booster slots. Treat EV as a long-run average over many openings, not as a forecast for one box.

8. Top movers / market-report calculations

On the homepage, on each set hub, and in auto-generated Market Reports, we surface top movers — products with the largest percent price change over a window. The window varies (24H on home, 7D on weekly reports, calendar-month on monthly reports) but the calculation is identical:

# For every product with at least 2 ticks in the window:
start_price   = first tick in window (chronologically earliest)
current_price = last tick in window  (chronologically latest)
change_pct    = (current_price − start_price) / start_price × 100

# Sort by abs(change_pct) desc; cap to top N.

Products with a single tick (or zero) in the window are excluded — we cannot compute a percent change from one observation. This means newly-added products and products with thin recent data won't appear in mover lists for a few cycles.

9. Reproducibility + citation

If you cite CCG Index numbers in research, articles, podcasts, or social posts, please:

  • Link to the specific URL (e.g. /research/indexes or /item/12345) — that's the canonical record of what we showed at that moment.
  • Note the date you accessed it. Hourly refresh means a number you cite today may not match what's on screen tomorrow.
  • Link back to this methodology page so readers can audit the calculation.
  • Mention the data source tier (mostly TCGPlayer) so readers know whose market is being represented.

For programmatic access, contact us — there is no public API at this time, but non-commercial research use is something we'd like to support.

10. Caveats and limitations

  • This is not a valuation service. Market prices are noisy, especially for low-volume products. A single high outlier listing can move a thinly-traded product's "price" significantly.
  • Not all products have full history. We're continuously backfilling but newer and rarer products may show gaps in the 1M or 1Y change columns.
  • TCGPlayer is US-centric. If you buy in EUR or GBP, prices here may not reflect your local market.
  • Indices are unweighted. A $5 booster pack and a $500 case contribute equally to the count but very differently to the dollar value. Read the index value as "cost to buy one of each", not as a market-cap-weighted portfolio.

Questions about the methodology? Contact info on the About page.