← Back to Blog

W76 vs W78

My dad and I have been going to sporting events together for as long as I can remember; the best $100 I've ever spent was a ticket to the Broncos–Patriots snow game in 2015. My ticket-buying strategy has always been the same: refresh the resale prices and trust my gut on when to buy. It's worked well enough, but "trust my gut" isn't a strategy. So this time, for one World Cup ticket, I'm replacing the gut with a model.

The price is a bet on who's playing

I'm targeting the July 5 game. What I didn't appreciate at first: the structure of the tournament means some teams are far more likely to land in my match than others, and who plays swings the price enormously. Brazil, for instance, travels with a huge, loyal fanbase, and they're fairly likely to be in this one.

But here's the catch: my match isn't listed with two teams. It's listed as "W76 vs W78," the winners of two games that haven't happened yet. The teams aren't set until the group stage ends, but I'm asked to pay today. So the price isn't really a price. It's a bet on who plays.

Once you see it that way, "when should I buy?" stops being one question and becomes three:

  1. Who is actually going to play? (P(matchup))
  2. What is each possible matchup worth? (E[price | matchup])
  3. Given those, buy now or wait?

That's the whole project: a little pipeline for each piece.

The live dashboard: the current price and buy/wait signal up top, then the five-stage forecast pipeline: prediction markets and the bracket, simulate the path, P(matchup), demand model, price forecast.

All the screenshots here are from the live dashboard.

There was no dataset, so I built one

First problem: the data didn't exist. There's no free API for historical resale prices, the aggregators that have it charge ~$499 a month for real-time only (no history), and SeatGeek's API needs approval. Read their pages directly and you hit a wall of 403s and captchas. Fair enough; they don't want to be scraped.

So I built the dataset myself. A job runs every three hours and writes down three things: the lowest ticket price (from TickPick, which shows the all-in price you'd actually pay), the prediction-market odds for the tournament (Polymarket), and the prices of all eight Round-of-16 matches, not just mine. A few weeks of that and I have something nobody is selling: a time series of how this market moves.

Reading a public listings page is a gray area, so I'll say it plainly: personal-use, low-volume, honest user-agent, every price tagged with its source. (For the curious: a Next.js app on Vercel, data in Vercel Blob, a GitHub Action firing the cron.)

Stage one: who will we be watching?

Follow the bracket back from "W76 vs W78" and every possible opponent comes from just four groups: C, E, F, and I. That's about sixteen teams, small enough to just compute. I need two things: how likely each team is to finish first or second in its group, and who wins the knockout games after that.

For the group standings I lean on the betting markets: the "who wins Group C" prices are each team's chance of finishing first. For who finishes second (the runner-up slots I care about), I use Plackett-Luce, a clean way of saying "take the winner off the board, and here's who's next." It turns the first-place odds I already have into the full finishing order, no extra data needed.

For the knockout games I use Bradley-Terry, the idea behind chess and sports rating systems: give each team a strength number and the stronger team wins proportionally more often. I pull those strengths from the overall "who wins the World Cup" market.

The nice part: the path to my match factorizes (the four group slots are independent), so I don't simulate anything. I multiply it out exactly and get the probability of every possible matchup. Today that's about a 49% chance Brazil is in my game, a 72% chance of at least one marquee team (Brazil, France, Germany, or the Netherlands), and a single most likely matchup of Brazil vs Norway at ~14%.

Bracket-path diagram: the Group C winner (Brazil) and the Group F runner-up feed Match 76; the Group E and Group I runner-ups feed Match 78; the two winners meet in Match 91. Below, the matchup distribution and a callout noting France is only ~23% to appear.

Stage two: what is each matchup worth?

Brazil vs France and Tunisia vs Iraq are not the same ticket, so I need to know what each matchup is worth.

This is the part I'm least sure about. I built a demand model that scores each team on two things: how much global interest they pull (again from the markets) and how much they'd draw locally in New York specifically. That second piece matters more than you'd think: the metro area has a huge Ecuadorian community, so an Ecuador game sells better here than its world ranking suggests.

I anchor it to today's price. The market already knows the matchup is uncertain, so $1,469 is the blended average across all the possible games; the model just splits that average back out. A marquee matchup like Brazil vs France lands around $1,899, a Tunisia vs Iraq around $876.

One caveat, and it matters: these numbers are a prior, not a calibrated model. I picked the demand scores because they seemed reasonable, not because I've tested them against a single real outcome.

Price forecast: all eight Round-of-16 venues priced today (Mexico City highest at $2,119, mine mid-pack at $1,469, Seattle lowest at $909), then the forecast range from $876 to $1,899 with a "now" marker at $1,469 and conditional prices for the most likely matchups.

The call (and why "no edge yet" is the honest answer)

Put it together (probability of each matchup times what it's worth) and you get a forecast for the price at kickoff: roughly $1,198 to $1,899, depending on who arrives, with about a 19% chance of a 15%+ spike and a 29% chance of a 10%+ dip.

So should I buy right now? Not yet. We're weeks out, and the market has already priced in how little anyone knows about who's playing. I'd love to end this with a decisive "BUY NOW," but the model doesn't see an edge today, and faking one would defeat the whole point. The honest call, given what I know today, is to wait.

The interesting part is timing. All that uncertainty resolves the moment the matchup locks, around July 2: a Brazil game and the price jumps, a sleepy matchup and it sags. Waiting is holding an option on that resolution. If I had to go and couldn't stomach a Brazil-vs-France spike, I'd lock in now to cap the downside. I have some flexibility, so I'll wait and let the bracket tell me more.

The experiment: a built-in backtest

Here's how I find out whether the prior is any good, and the schedule hands me a perfect experiment. My match is one of eight Round-of-16 games, and seven kick off before mine, July 1–4. Their matchups lock days earlier, so by the time I decide I'll have watched seven other matches go from "unknown teams" to "known teams" and seen exactly how their prices moved. A genuine out-of-sample test. I take the demand model, ask what each matchup should have been worth, and grade it with proper scoring rules (the honest way to score a probabilistic forecast) against dumb baselines like "just use today's price."

So let me put a real prediction on the record, today:

As of June 12, 2026, the model says: ~49% chance Brazil is in the match; the single most likely matchup is Brazil vs Norway; expected price at kickoff is about $1,469, with a range of roughly $1,198–$1,899 depending on who arrives. Check back after July 2.

If the prior is bad, those seven matches will say so, loudly, and I'll recalibrate before my own game.

What I'm not claiming

To keep myself honest, what this is not:

  • Not much data yet: I started three weeks out.
  • The scraper is fragile; if TickPick changes their page, the feed breaks until I fix it.
  • The market-anchor assumption fails if today's price is itself mispriced.
  • Cross-venue comparison is confounded: every Round-of-16 match is at a different stadium with its own baseline.
  • And, again, the demand model is an untested prior.

None of these are fatal. They're the difference between a fun forecast and a finished one, and the next post is about closing that gap.

Either way, my dad and I will be there.