Season simulation
Two season engines
posterior_simulation and chronological_simulation answer different questions.
Static posterior. simulate_seasons_from_posterior samples one coherent strength state for each season path and holds it fixed. This is useful for synthetic examples, comparisons, and lower-level analysis.
Chronological. simulate_seasons_chronologically keeps one latent true-strength state per path and lets it drift between fixture dates. A separate model estimate learns from the simulated scores and xG after each date. Use it when future observations should change later strength estimates.
Do not describe a static-posterior output as a chronological forecast. See Scale and performance for API defaults, operational path counts, and timing evidence.
One chronological path
At the first league date, the engine draws one coherent true-strength state from the model's posterior. It retains that truth throughout the season rather than redrawing it from later model estimates. Each path also owns a separate copy of the model, which estimates strength from the observations generated by that truth.
For each future date, the chronological engine:
- advances the copied model's uncertainty;
- advances the persistent truth using the model's random-walk drift for team strengths and, when active, home advantage;
- computes motivation from that path's table and remaining calendar when a policy is supplied and its loaded gates apply;
- applies relocation, derby, and motivation offsets;
- samples every score on the date from the shared state;
- samples synthetic Gamma xG around the scoring rates, including caller-configured future observation variance;
- updates the copied model with the complete date batch; and
- updates the table before proceeding to the next date.
A club that is stronger in a sampled path remains stronger in later fixtures, subject to the configured drift. Observing a simulated match changes the model's estimate, not the truth that generated the match. The source model is copied, so simulation paths do not mutate it or one another.
Redrawing truth from the updated posterior on every date creates an artificial scoring feedback loop. Exponential scoring rates turn repeated uncertainty draws into observations whose average exceeds the filter's mean-strength rate. Those observations push estimated defences downward, inflating later scoring and reducing draws. Persistent truth avoids that repeated-resampling bias. The configured drift can still change scoring over long horizons; removing the bias does not validate the drift assumption.
Fixtures are sorted by date and match ID. All matches on a date use the same pre-date state. Teams may not appear twice in one observed batch.
Outputs and reproducibility
Both season APIs return:
- expected final points, one value per team;
- a team-by-place matrix of finish percentages; and
- 10th/90th percentile final-point intervals.
Expected points can be fractional because they are means across paths. With one simulation, place values are still encoded as 0 or 100.
Chronological multi-path seeds follow seed + path * 1_000_003 and remain stable across worker counts. A stable seed supports debugging and comparison; it does not establish adequate Monte Carlo precision. Small path counts are smoke tests only. Correcting the path-generation algorithm changes fixed-seed chronological results; output shapes, units, and the static-posterior engine are unchanged.
Table ranking
The supplied Ekstraklasa ranking sequence uses:
- points;
- head-to-head points;
- head-to-head goal difference;
- overall goal difference;
- goals scored;
- wins; and
- away wins.
A random draw resolves a remaining tie because discipline and fair-play data are outside the model. Adapt league_table before applying the package to competitions with different rules.
Starting partway through a season
The static simulator accepts a starting_table. Chronological simulation instead accepts completed matches and reconstructs the table used by each path. Remaining and completed match IDs must not overlap, and all matches must belong to the requested season and team set.
Known point adjustments are filtered by season and applied causally. Callers are responsible for supplying a complete, internally consistent schedule and observation cutoff.
Polish Cup machinery
The export can calculate regulation and advancement probabilities and simulate a caller-defined staged knockout tournament. It models extra time, a supplied shootout probability, lower-tier strength, unknown draws, and persistent uncertainty through a Cup path.
See Polish Cup model for the full mechanics and limitations. The private joint league–Cup calendar is not exported. In that larger orchestrator, an Ekstraklasa entrant reads its current league-path strength, but a simulated Cup result advances only the bracket and never updates league ratings, table state, motivation, or the cross-tier bridge.
Monte Carlo interpretation
Simulation error is additional to model uncertainty. Increase paths until relevant reported probabilities are stable enough for the intended decision. Report outer paths and, when motivation is enabled, inner paths separately. The resulting interval covers variation represented by the model and simulator; it does not include every source of football uncertainty.