Skip to content
Prepaid, no subscription trap. Top up a balance and we draw the monthly fee from it.
tcggraph

Portfolio and analytics

A portfolio marked at retail is a portfolio marked wrong

Build card portfolio and valuation tools on daily price history, dealer exit prices and per-printing identity across eight games.

The problem

Where this project usually goes wrong

Cards are traded as assets and increasingly held as them. That raises the standard for the numbers you show: a user deciding whether to sell is making a financial decision on your figure, and a figure that overstates the exit by half is not a rounding error.

Retail is the wrong mark for a position because it is the acquisition price, and nobody exits at it. The realisable number is the dealer bid, and the gap between the two is not a constant — it widens sharply as cards get cheaper, so a portfolio full of mid-value cards is overstated by a different amount than one holding a few expensive ones.

History is the other half. A single current price cannot tell a user whether they are up or down, and a series stitched together from whatever you happened to record is full of gaps exactly where the interesting movements were.

What it needs

The fields that decide it

Daily price history
A dense daily series per printing, so a position curve does not depend on when your job happened to run.
listType: buylist
The realisable exit price, which is what a position is genuinely worth rather than what it would cost to replace.
Per-printing identity
A position is in a specific printing. Marking it against a card-level average destroys the entire point.
Both markets
The same card is a different asset in Europe and North America, and a holder should be marked against the one they can sell into.
Webhooks on price thresholds
Alert a holder when a position crosses a level, instead of asking them to check.

In practice

The two queries that matter most

Written for investment, valuation and analytics products. Both work the same way for every game we carry.

Chart a position over a year

Chart a position over a year
curl -G "https://api.tcggraph.com/v1/cards/mtg_f6555d1f-d4c/history" \
  -d source=cardmarket \
  -d from=2025-09-01 \
  -d to=2026-09-01 \
  -d interval=DAY \
  -H "Authorization: Bearer $TCGGRAPH_KEY"

The series comes back dense: a day with no recorded change carries the last known price forward, so you never have to fill gaps yourself or explain a chart that drops to zero.

Mark a portfolio to exit rather than to retail

Mark a portfolio to exit rather than to retail
{
  cards(filter: { ids: ["mtg_f6555d1f-d4c", "mtg_5465a1b2-9cd"] }) {
    nodes {
      name
      retail: prices(listType: RETAIL)  { market currency }
      exit:   prices(listType: BUYLIST) { market currency }
    }
  }
}

Aliasing both sides in one query gives you the spread per position without a second request or a second reconciliation.

Avoid these

Three mistakes that are expensive to undo

Each of these is cheap to get right at the start and painful to retrofit once you have user data shaped the wrong way.

  • Applying one discount to model the exit

    The realisable share of retail varies by nearly four times across price bands. A flat haircut flatters cheap positions and penalises expensive ones.

  • Building history from your own polling

    Your series will have gaps wherever your job failed, and those gaps cannot be backfilled from anything you hold. Read a series that was recorded properly.

  • Ignoring which market the holder is in

    A European holder marked against American prices is looking at an asset they cannot sell at that number.

Questions

Portfolio tracker, answered

Ship your card app this weekend

$19 a month for 25,000 credits, no sales call and no contract. Pick a plan and your first query runs in under a minute.