W76 vs W78
My dad and I have gone to games together for as long as I can remember. The best $100 I've ever spent was a ticket to the Broncos game against the Patriots in the snow in 2015. My ticket strategy was always the same. Refresh the resale listings until my nerves won. That is not much of a strategy.
This time the ticket is for the World Cup, and I want to take my dad. I built a model to tell me when to buy.
The price depends on the teams
I'm targeting the July 5 game. I didn't appreciate how much the bracket matters until I started looking at the possible teams. Some have a much better chance of reaching my match than others, and the teams can move the ticket price by hundreds of dollars. Brazil is the expensive outcome I'm worried about. Its supporters travel in large numbers, and the team has a good chance of reaching this game.
My ticket doesn't list two teams. It says "W76 vs W78," the winners of two games that haven't happened yet. The teams won't be set until the group stage ends, but I have to pay today. Buying now means betting on who plays.
That turns "when should I buy?" into three questions:
- Who is going to play?
- What is each possible matchup worth?
- Given those, buy now or wait?
The dashboard works through those questions in that order.

All the screenshots here are from the live dashboard.
There was no dataset, so I built one
The data I needed didn't exist. There is no free API for historical resale prices. The aggregators charge about $499 a month for real-time data and don't include history. SeatGeek's API needs approval. The listings pages return a wall of 403s and captchas. I don't blame them. They don't want to be scraped.
So I built the dataset. Every three hours, a job records the lowest all-in price on TickPick, the tournament odds on Polymarket, and the prices of all eight Round of 16 matches. After a few weeks, I have the price history no aggregator would sell me, with one observation every three hours.
Reading a public listings page is a gray area. I keep it personal and low volume, identify the scraper in its user agent, and tag every price with its source. The dashboard is a Next.js app on Vercel. The data is in Vercel Blob, and a GitHub Action runs the scheduled job.
Stage one estimates the teams
Every possible team in "W76 vs W78" comes from groups C, E, F, or I. That is about sixteen teams, few enough to compute directly. I need the chance that each team finishes first or second in its group, then the chance it wins the next knockout game.
For the group standings, I use betting-market prices as each team's chance of finishing first. I use Plackett-Luce to estimate who finishes second, which is the other slot I care about. In plain English, take the winner off the board and calculate who comes next. That gives me a full finishing order from the first-place odds I already have.
For the knockout games, I use Bradley-Terry. Each team gets a strength score, and the stronger team wins more often. I get those scores from the overall "who wins the World Cup" market.
The four group slots are independent, so I do not need a simulation. I can multiply the probabilities and get an exact result for every possible matchup. As of June 12, Brazil has about a 49% chance of reaching my game. There is a 72% chance that Brazil, France, Germany, or the Netherlands makes it. The most likely matchup is Brazil vs Norway at about 14%.

Stage two prices each matchup
Brazil vs France and Tunisia vs Iraq are not the same ticket. I need a price for each possible matchup.
This is the part I trust least. The demand model scores each team by its global interest, based on the betting markets, and its likely draw in New York. Ignoring the local audience would be a dumb mistake. The metro area has a huge Ecuadorian community, so an Ecuador game should sell better here than its world ranking suggests.
I anchor the model to today's price of $1,469. Buyers already know that the matchup is uncertain, so that price mixes together every possible game. The model separates them again. It puts Brazil vs France around $1,899 and Tunisia vs Iraq around $876.
These numbers are a prior, not a calibrated model. That caveat matters. I picked demand scores that seemed reasonable, but I have not tested them against a single real outcome.

The call, with no edge yet
For each matchup, I multiply its probability by its expected ticket price. The result is a kickoff price between roughly $1,198 and $1,899, depending on the teams. The model gives the ticket a 19% chance of rising at least 15% and a 29% chance of falling at least 10%.
Should I buy right now? Not yet. We're weeks out, and the market has already priced in the uncertainty. I'd love a decisive "BUY NOW," but the model doesn't find an edge today. Pretending otherwise would defeat the point. Given what I know on June 12, I should wait.
The decision changes around July 2, when the matchup locks. A Brazil game could send the price up. A sleepy matchup could drag it down. Waiting gives me the choice to act once I know the teams. If I had to attend and couldn't stomach a Brazil vs France spike, I'd buy now. I have some flexibility, so I'll wait for the bracket.
The built-in backtest
I got lucky with the schedule. My match is the last of eight Round of 16 games. The first seven kick off from July 1 through July 4, and their matchups lock days earlier. By the time I decide, I'll have seen how prices moved when seven games went from unknown teams to known teams.
That is an out-of-sample test. I will compare the demand model's price for each matchup with the actual price. Then I will grade its probability forecasts with proper scoring rules and compare the result with a dumb baseline that always uses today's price.
I want the prediction on the record before the results arrive:
As of June 12, 2026, the model gives Brazil a 49% chance of reaching the match. Brazil vs Norway is the single most likely matchup. The expected price at kickoff is about $1,469, with a range of roughly $1,198 to $1,899 depending on the teams. 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, this is what the model cannot do:
- I started collecting data three weeks before the game. That is not much history.
- If TickPick changes its page, the feed breaks until I update the scraper.
- The model assumes today's price is a sound anchor. A mispriced ticket gives me a bad starting point.
- Every Round of 16 game is in a different stadium, so I cannot cleanly compare prices across venues.
- The demand model is still an untested prior.
The forecast is still a sketch. The first seven games will tell me whether the demand model deserves any trust before I buy the eighth.
Either way, my dad and I will be there.