Skip to content

About

The seeded bivariate Poisson Monte Carlo engine behind the World Cup 2026 final model on touchline.watch. ~550 lines of dependency-free TypeScript. Change a number, re-run, watch the probabilities move.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Touchline model

The seeded, bivariate Poisson Monte Carlo engine behind the World Cup 2026 final model on touchline.watch. The whole thing, the maths and the calibration, in about 550 lines of dependency-free TypeScript. Change a number, re-run, watch the probabilities move.

Spain versus Argentina, the 2026 World Cup final

Star on GitHub License: MIT TypeScript No runtime dependencies

If the model is useful or interesting, star the repo so more people find it.

What this is

Touchline turns a fixture into a brief you can read, a model you can run, and a set of receipts you can check. This repo is the model: the exact engine that produced the numbers on the Spain v Argentina final page, lifted out of the site and made to run on its own.

It is not an AI, not a betting tip, and not a black box. It is classical statistics, the same family the analytics world uses, with a fixed seed so the same inputs always give the same result. You can read every line, run it yourself, and move the assumptions to see the numbers shift. That is the whole idea.

The method

  • 90 minutes. Each side's goals come from a Poisson draw at its expected-goals rate, plus a small shared component (the bivariate Poisson piece, cov, default 0.10, the Karlis–Ntzoufras club-football estimate). The shared piece adds the positive goal correlation a pure independent-Poisson model lacks, open games swing both ways.
  • Extra time. If it is level after 90, extra time runs at roughly a third of the 90-minute rates, tilted by a fatigue term (etTilt) toward the fresher side.
  • Penalties. Level after 120, and a shootout is drawn, weighted by penArg for pedigree.
  • Scorers. Each goal is assigned to a player by a blend: half the verified tournament goal share, half a pre-tournament prior. Seven games is too small a sample to trust raw shares outright.
  • Live mode. Given the minute, the current score, and who is on top, the engine simulates only the remainder of the match, with a chase adjustment (trailing teams open up, leaders counter).

Run it

npm install
npm start           # the base case, 100,000 runs
npm start -- 500000 # a different iteration count

Output (identical every time, that is the point):

Base case (the site's read)  (n=100,000)
  Spain to lift      55.5%
  Argentina to lift  44.5%
  Extra time         29.1%
  Penalties          15.7%
  Over 2.5 goals     42.3%
  Both teams score   49.0%
  Likely scores      1–0 14.3%, 0–1 11.6%, 2–1 11.3%, 1–2 8.8%
  Likely scorers     Messi 35.5%, Oyarzabal 35.4%, Yamal 18.0%, Lautaro 17.3%

Poke it

Everything you would want to change lives at the top of src/engine.ts:

export const DEFAULT_PARAMS: SimParams = {
  lamEsp: 1.3,   // Spain expected goals, 90 min
  lamArg: 1.1,   // Argentina expected goals, 90 min
  penArg: 0.56,  // P(Argentina win a shootout)
  etTilt: 0.1,   // extra-time fatigue tilt toward Spain
  cov: 0.1,      // shared goal component (0 = independent Poisson)
  n: 20000,
  seed: 20260719,
};

Push Spain's rate up, drop the shared component to zero, hand the shootout to whoever you fancy, and re-run. The PRESETS array holds the calibrations for the markets and the published models, so you can line the base case up against Polymarket and Opta:

Presets, 100,000 runs each: Spain to lift
  The site's read          55.5%
  Polymarket, live         59.8%
  Opta supercomputer       59.4%
  La Roja masterclass      70.2%
  The stoppage-time script 37.4%

The base case sits about four points under the markets and Opta on purpose: the model thinks money and machine both underprice how Argentina steal finals late (all four of their knockout wins were sealed at 90+2 minutes or later). Whether that read was right is on the receipts, kept either way.

How it maps to the site

How the model works, on touchline.watch

On touchline.watch, this machinery is copy-adapted per match, each fixture gets its own calibrated rates and scorer weights, and the same engine drives the live win probability, the sensitivity table, and the post-match receipts. The full method, with the interactive version, is at touchline.watch/how-it-works. The journal takes the pieces apart one at a time:

A note on what this is for

Analysis to argue with, not betting advice. The output is a probability and the working behind it, never a recommendation and never a guarantee. Football is played, not solved.

License

MIT, see LICENSE. If you build something with it, a link back to touchline.watch is appreciated but not required.

About

The seeded bivariate Poisson Monte Carlo engine behind the World Cup 2026 final model on touchline.watch. ~550 lines of dependency-free TypeScript. Change a number, re-run, watch the probabilities move.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages