Core Team Stats Required✓ Ready
Season totals off the league table — the model's main input.
Form Check Optional✓ Ready
Recent results, home and away mixed (usually the last 5). Powers Form Check on the Verdict tab.
📊 Model Comparison
What PP predicts for the current fixture vs. what the Market expects, read from the de-vigged odds in the EV Finder card on the Verdict tab. The Market side needs odds entered there: all three 1X2 for the score picture and 1X2 tiles, Over/Under's own pair, BTTS's own pair.
1X2
Over / Under
Both Teams to Score
Top correct scores
1X2
Over / Under
Both Teams to Score
Top correct scores
Calculate probabilities on the Inputs tab to see the data analysis.
📊 Market Split
Same probabilities as Model Comparison, drawn instead of tabulated.
🎯 Score Read
How concentrated the correct-score matrix is on a few scorelines vs. spread thin across many.
🎯 Market Score Read
Same concentration read as Score Read above, but for the bookies' own de-vigged score picture instead of the model's — needs all three 1X2 odds entered in Bookmaker Odds (Inputs tab).
⚽ Expected Goals
Each side's λ (expected goals this match), split against the total.
🥅 Goal Total Split
Same score matrix as above, grouped by total goals in the match instead of a single Over/Under line.
🥅 Market Goal Total Split
Same bands as Goal Total Split above, computed from the bookies' de-vigged odds instead of the model — needs all three 1X2 odds entered in Bookmaker Odds (Inputs tab).
🔢 Score Matrix
The full correct-score grid behind every market on this tab — darker cells are more probable scorelines, and the ringed cell is the single most likely score.
✨ Setup Notes
Plain-language read of the numbers above — describes the match setup, not a pick.
Calculate probabilities on the Form tab to see your Match Verdict.
Match Verdict
💰 EV Finder
Positive-EV markets only, ranked highest first — compares the odds you enter in the Bookmaker Odds card (Inputs tab) against PitchPulse's own fair probability. EV% = (fair probability × odds) − 1.
🎯 Bookmaker De-Vig
Uses the same odds you typed in above — no separate entry needed. Strips the bookmaker's margin out of each group to reveal their true fair probabilities, then works out the exact scoreline picture those odds imply.
📋 Match Log
No matches logged yet. Calculate a fixture, then hit "Add to Match Log".
💡 How To Use PitchPulse
Each side's season standings, one league baseline, and PitchPulse turns it into a full set of match probabilities in one tap.
Step 1 — League Baseline: Pick a Country and League to auto-fill the average home/away goals for that competition, or type your own. Defaults are Home 1.50 / Away 1.20 — a generic mid-table read. Switching Advanced back off restores these defaults.
Step 2 — Team names: Name your Home and Away side.
Step 3 — Season Tally: Copy each team's season-to-date record straight off the league standings table (home rows only for the Home side, away rows only for the Away side) — matches played, goals for, goals against — into the three MP / GF / GA boxes.
Step 4 — Elo rating (optional): Each team-name row has an Elo pill. Leave both blank for a form-only read, or fill in both (e.g. from clubelo.com or eloratings.net) to shift expected goals toward the stronger side (the fixture's total expected goals stays exactly as the model computed it — Elo only changes how it's split) — useful when the two teams aren't from the same league or division, so their Season Tally numbers alone don't capture the quality gap. It only takes effect once both sides have a value.
Step 5 — Calculate: Hit "⚡ Calculate Probabilities". The Model Comparison tab's PP column fills in with 1X2, Over/Under, BTTS, and the top correct scores; the stats strip up top shows Lambdas (H/A), PitchPulse's own top correct score (PP Exp. Score), the model's Best Bet — the market that wins the probability/excess hybrid ranking among markets that clear 50% raw probability, shimmering gold whenever all 3 of the top 3 most probable correct scores agree with it — and Top EV, the single positive-EV market ranked highest once odds are entered on the Inputs tab.
Step 6 — Track it: On the Verdict tab, hit "+ Add to Match Log" to save the pick, then fill in the real final score once the match is played. Signal Check and Calibration build up as your log grows. The log archives automatically each day — use 🗄 Archive or the ⬇ CSV/JSON buttons to export for analysis.
📊 Where To Get Your Team Stats
PitchPulse doesn't pull live data — you feed it the numbers, which keeps it fast, offline-friendly, and usable for any league on earth.
Official league standings tables — most league sites publish separate home/away standings splits (e.g. premierleague.com, laliga.com) with MP/GF/GA columns ready to copy straight in, season-to-date.
FlashScore.com & WhoScored.com — the standings tab usually has a Home/Away split alongside the overall table.
FBref.com — free standings and squad stats for almost every senior league worldwide, including home/away splits.
Use the Country/League selector on the Form tab first — it auto-fills from a built-in database. If your league isn't listed, search "[league name] goals per game average" for the current season, or just leave the defaults (1.50 home / 1.20 away) as a generic baseline.
⚙️ Good To Know
Inputs, theme, and the Match Log are saved in your browser's local storage only. There's no backend and nothing is ever sent anywhere.
Served over HTTPS, PitchPulse installs as a standalone app via the "Install App" button (or your browser's install prompt) and keeps working offline once installed.
Toggle the sun/moon icon in the header any time — your choice is remembered on next visit.
❓ Methodology & FAQ
A Poisson model built on each team's season-to-date record. Each team's season MP-GF-GA — matches played, goals for, goals against (home-only for the Home side, away-only for the Away side) — is blended with a league-average prior via adaptive shrinkage, so a small early-season sample doesn't overreact to a handful of results. Those shrunk rates become attack/defence strength relative to the league baseline, which convert into expected goals (λ) for each side — from there, independent Poisson distributions build the score matrix, with an adaptive Dixon-Coles / bivariate-Poisson correlation blend applied before it's renormalized (see the next question). Every market on the Model Comparison tab's PP column is derived from that final matrix.
Independent Poisson treats home and away goals as statistically unrelated, but real match goals are mildly coupled by things like game state and tactics — and that shows up specifically in the low-scoring cells. The Dixon & Coles (1997) tau correction adjusts exactly those four cells (0–0, 1–0, 0–1, 1–1) before the matrix is renormalized. Rather than a single fixed literature constant, ρ here is adaptive: it scales with the league's own average goals, growing more negative for defensive/low-scoring leagues and lighter for high-scoring ones — anchored so a league at the app's default baselines (1.50/1.20) reproduces the original literature-standard ρ = -0.13 exactly. It's derived purely from the League Baseline inputs above (never from either team's own GF/GA form) and runs automatically in the background — there's nothing to configure.
Dixon-Coles only touches four cells — the low-score corner it was built for. Everywhere else in the matrix, independent Poisson is still uncorrelated, which matters more as a league's scoring rate climbs and more of the probability mass sits outside that corner. A bivariate Poisson model (two independent legs plus a small shared "common-shock" term, λ3, that couples every cell) fills that gap without changing either side's expected goals — λ3 is carved out of both λHome and λAway, not added on top. Which correction actually runs is itself adaptive to League Baseline: leagues at or below the app's own reference average (≈1.34 goals) run pure Dixon-Coles, leagues at 2.0+ run pure bivariate Poisson, and anything in between blends the two score matrices on a straight linear ramp. Same as Dixon-Coles above — nothing to configure, it retunes itself off the League Baseline inputs.
An earlier version read the last 5 matches and dropped the single best and worst to keep one outlier scoreline from dominating the read — that worked, but it also capped how much real evidence the model could ever use: a team's read stayed pinned to roughly 3 match-equivalents of real data all season, no matter how many games it actually played. Season MP-GF-GA uses everything played so far instead, with adaptive shrinkage (see "How are λ calculated?" below) doing the small-sample protection early on — and that protection fades naturally as the season goes on and real MP grows, rather than staying fixed.
HomeAttack = Home team's shrunk home goals-for ÷ league average home goals. HomeDefence = shrunk home goals-against ÷ league average away goals. Away strengths mirror this using the team's away form. Before that division, each side's raw season GF/GA rate (goals ÷ matches played) is blended with a few "average" matches worth of the league baseline — so a genuine 0 across a handful of real matches doesn't collapse to a guaranteed clean sheet, while a team with a deep season sample is barely pulled off its own real rate. That blend strength is adaptive on two axes: it scales with the league's scoring level (goals follow a Poisson distribution, so a small sample is proportionally noisier in a low-scoring context than a high-scoring one — a defensive league at 0.8 goals/game gets pulled toward the baseline more firmly, around 46% weight, than a high-scoring one at 2.5 goals/game, around 33%), and it fades as real matches played (MP) grows — a team 2 games into its season leans heavily on the baseline, the same team 30 games in barely does. λHome = HomeAttack × AwayDefence × league average home goals, and λAway = AwayAttack × HomeDefence × league average away goals. A strength of 1.00× means exactly league average; 1.25× means 25% above it.
It sits between Model Comparison and Verdict, deliberately, as a "read the match setup" step before Best Bet is revealed — nothing on it names or hints at which market Best Bet actually picks. Market Split draws the same 1X2/O-U/BTTS probabilities Model Comparison already tabulates, just as donut charts. Score Read shows how much of the correct-score matrix sits in the top scoreline and the next four vs. everything else, with a Concentrated/Moderate/Wide-open tier — that's about the shape of the matrix as a whole, not a hint at any one scoreline. Expected Goals shows λHome vs λAway as a split bar against this league's typical total, and Goal Total Split regroups that same matrix into 0–1 / 2–3 / 4+ total goals instead of a single Over/Under cut. Score Matrix lays out the full correct-score grid itself — each team's goal counts bucketed 0, 1, 2, 3, 4, and 5+ along its own axis, shaded by probability, with the single most likely scoreline ringed — so the shape described by Score Read and Goal Total Split is visible cell by cell too. Setup Notes is a short plain-language bullet list generated from everything above (expected-goals gap, total vs league baseline, Elo gap, and the most extreme attack/defence multiplier), purely descriptive rather than a recommendation. (Recent Form and Form Strip, which used to chart per-match goals for sides on the old ⇄ 5 scores entry mode, no longer appear — season MP-GF-GA tallies don't carry per-match detail to chart.)
1.50 home goals, 1.20 away goals per match — a generic mid-table baseline. Raw goal counts only mean something relative to context: 1.8 goals a game is modest in a high-scoring league and strong in a defensive one. Pick your league from the Country/League selector to auto-fill the real numbers, or enter them yourself for the sharpest read.
Best Bet scores all seven direct markets (Home/Draw/Away, Over/Under 2.5, BTTS Yes/No) by edge over the league baseline (what a coin-flip fixture in this league, using only the league's home/away goal averages and no team form, would show for that same market) — but only markets that clear 50% raw probability are eligible to win at all. As of v3.7, edge is the whole ranking: the eligible market with the biggest edge wins outright, with raw probability used only to break an exact tie. (An earlier version blended 80% raw probability with 20% edge; backtesting against logged match results showed edge-led ranking calling more matches correctly, so the blend was dropped in favor of ranking on edge directly.) That 50% floor is still a hard gate, not a tiebreaker: a market that's more likely to lose than win can never be Best Bet, no matter how large its edge over baseline is. When the winner beats the outright probability leader on a genuinely large excess edge (15+ points), it's flagged 🔥 Value Signal in the Verdict card — that's a badge, not a gate: it doesn't need to clear any extra bar to win, the flag just makes the pick's shape visible rather than letting a probability-defying pick look identical to a safe favorite win. Because edge now drives the ranking directly, expect to see this badge more often than in earlier versions — that reflects the new ranking rule working as intended, not a bug. If no market at all clears 50% — possible on a tight three-way 1X2 split, though Over/Under and BTTS are near-complementary pairs so one side of each almost always clears it — Best Bet falls back to the single highest-probability market instead, and the Verdict card says so explicitly (↩ Fallback). Draw is the market this floor hits hardest — it's genuinely rare for Draw to clear 50% outright in a 3-way split, so when Draw is still this fixture's single most likely result, just not eligible, the Verdict card says so explicitly too (⚖ Draw Watch) rather than Draw silently losing out. Separately, the top 3 most probable correct scores from the match's score matrix each imply a result, an Over/Under 2.5 read, and a BTTS read at once — e.g. a 2–1 implies Home Win + Over 2.5 + BTTS Yes — and cast a probability-weighted vote on those three axes. That vote never decides the winner; it's a corroborating signal only, sorted into a confirmation tier — 3/3 or 2/3 scorelines land on the pick ("AGREE", green), 1/3 is "NEUTRAL" (amber), 0/3 is "DIVERGE" (red, an explicit divergence warning) — shown as a badge next to the pick and referenced in the reason text below it. Separately from that scoreline-count tier, if another market is backed by meaningfully more vote-weight than the winner, the reason text flags that explicitly too rather than staying silent, and if the winner wasn't the outright probability leader (or the pick was a no-qualifiers fallback), a separate note says so and shows the margin. The "Why this pick?" breakdown under the Verdict card lists all seven markets with their probability, edge over baseline, and top-3 vote weight — markets at or below the 50% floor are dimmed and marked with a ⛔ (ineligible to win, shown for context), and markets whose edge barely clears (or doesn't clear) the baseline are separately marked with a ↓ (thin edge, informational only) — neither marker removes a row from the table.
It's a caution flag, separate from the divergence warning above. Best Bet's excess figure comes from each side's real season-to-date matches played, blended with the league-average baseline (see "How are λ calculated?" below) — a small excess built mostly on that baseline prior, rather than the form you actually entered, is weaker evidence than the same-sized excess would be with a deeper sample. It only appears when both are true: the winning market's edge over baseline is small, and the thinner-evidence side of this fixture (whichever team has fewer matches played) still leans mostly on the prior rather than its entered matches (a defensive/low-scoring league shrinks harder than an attacking one, and an early-season fixture shrinks harder than a late-season one — see the adaptive shrinkage note above). A large excess is trusted regardless of shrinkage, since it had to survive the pull toward baseline to still be large. This flag doesn't change the pick itself — it's purely informational, telling you the confidence behind this particular edge is thinner than usual.
It shimmers when the pick is unanimous — all 3 of the top 3 correct scores agree on it. The Verdict tab always shows a confirmation badge next to the pick — 3/3 or 2/3 scorelines backing it shows green "AGREE", 1/3 shows amber "NEUTRAL", and 0/3 shows red "DIVERGE" (an explicit warning that none of the model's own top-3 scorelines land on the pick) — so you can see the strength of that corroboration at a glance even when it isn't full 3/3. Only the exact 3/3 case shimmers; 2/3 is still shown as agreement, just not the unanimous version of it.
When a Best Bet is confirmed this way and you log it, the Match Log entry itself is marked with a gold "✨ CONFIRMED" badge and the same shimmer, so it stands out from the rest of your log.
Best Bet answers "which market does the model trust most" — it never looks at odds. The EV Finder answers a completely different question: "given the odds I'm actually being offered, is this a good bet?" You type in the decimal odds for each of the same seven markets, and it compares them against the model's own fair probability: EV% = (fair probability × odds) − 1. Positive EV means the price is better than the model's fair value; negative means it isn't — regardless of whether that market happens to be the current Best Bet. A market can be the model's top confidence pick and still be poor value if the odds are short, and a market the model rates lower can still be +EV if the price is generous. Odds you type in persist while you keep recalculating the same fixture, but aren't saved to local storage — a fresh page load starts blank.
Bookmaker De-Vig reads those same odds a different way, running quietly in the background rather than as its own visible card: instead of comparing them to the model, it strips the bookmaker's own margin (the "overround") out of each group of odds — 1X2, Over/Under 2.5, BTTS — to reveal the bookmaker's true fair probabilities. A group needs every one of its odds filled in before it can be de-vigged exactly, since the margin can't be isolated from a partial set. Once the three 1X2 odds are in, it goes a step further and searches for the λHome/λAway pair whose own score matrix would reproduce those de-vigged prices, surfacing the bookies' own implied most-likely scorelines — the same kind of read the Verdict tab gives for the model's numbers, but built entirely from the market's, so the Model Comparison tab's "Market Implied" column can show where the model and the market disagree on the shape of the match, not just the price of one outcome. If you only type one side of Over/Under 2.5 or BTTS (say just Over, or just BTTS Yes), that price still helps the λ fit: its margin is estimated from the 1X2 margin rather than the price being ignored. Only the fit (and the scorelines built from it) uses that estimate — the Over/Under and BTTS tiles, the market notes and the Match Log MKT badge still need both sides, so type both when you want the exact de-vig.
A 🟢/🟡/🔴/⚪ tier for the specific bet category behind your current Best Bet (e.g. "Home Win"), computed from your own logged history using a Wilson score interval lower bound rather than a raw hit rate — so a 2-for-2 record doesn't get overrated. It needs 5+ resolved picks in that category before it rates anything; below that it shows ⚪ UNPROVEN.
The 📊 Calibration button opens a running Brier score (0–1 scale, lower is better) split by outcome — Home Win, Draw, Away Win, Over 2.5, Under 2.5, BTTS Yes, BTTS No — comparing your all-time average against your last 10 resolved matches for each, so you can see which specific side is improving or slipping instead of one blended number per market. Where a market has 5+ resolved picks on each side, it also splits Home Win / Draw / Away Win into an Elo ON vs. Elo OFF comparison — a higher Elo ON score means the Elo adjustment is hurting that market's calibration, a lower one means there's room to lean on it more. This is independent of Signal Check: it grades the model's raw probabilities directly, rather than just whether the top pick won.
Hit "+ Add to Match Log" on the Verdict tab after calculating to save a prediction. Once the match is played, enter the real final score — it's marked ✅ HIT or ❌ MISS against the Best Bet pick, and rolls into the Hit Rate shown in the stats bar. Best Bet, the Match Log, and the Model Comparison tab all grade against the same fixed Over/Under 2.5 line, so a logged "Over 2.5" always stays comparable.
The MP / GF / GA boxes in the Core Team Stats section — matches played, goals for, goals against, typed straight off the league standings table (home rows for Home, away rows for Away), season-to-date. It's the main model's only input now; there's no trim or per-match entry to do by hand. Each box holds up to two digits and auto-advances as soon as it's full (MP → GF → GA), and after Home's GA the cursor hops straight to Away's MP — same as the Form Check tally below it. Backspace on an empty box steps back. A blank tally just means the model falls back to the pure league baseline until you fill it in — nothing errors or needs clearing first.
Form Check compares Best Bet's pick against each side's general (unsplit home/away) recent form — the raw tally you type on the Team Form card as mp-gf-ga (e.g. 6-11-8; not always 5 matches, mp is read straight off whatever's typed). It's fully automatic — no research step, nothing to pick — it recomputes live the moment both sides have a valid tally, and shows its own verdict right there on its own card: "✅ Agrees" or "⚠ Diverges", naming whichever market it favors instead. On that card, Form's own pick turns green (▲) once its probability is above 60%. The same read is also echoed right under Match Verdict itself, on the Verdict tab's own card, the moment it has a valid read: a green "✅ Form confirms" note when it agrees — two independent reads landing on the same pick — or an amber "⚠ Form diverges" note otherwise. No note at all (in either place) means Form Check had no valid tally on one or both sides.
Form Check itself never logs anything — there's no button dedicated to it. But whichever way a match DOES get logged (the ordinary "+ Add to Match Log" button), the entry is tagged with whatever Form Check's own read was at that exact moment: a green "📋 Form: Agree" badge if it favored the same pick, or an amber "📋 Form: Diverge" badge if it favored something else. No badge at all means Form Check had no valid tally on one or both sides when the match was logged. The entry's meta line also shows Form Check's top scoreline (teal "Form 1–1") and the bookies' de-vigged top scoreline (gold "Mkt 2–1") next to the main λ, each only when that read existed at log time.
That's the match's kickoff time — entirely optional, and it never touches the model: λ, Best Bet, Form Check, none of it is read from or changed by what's typed there. It exists purely for the Match Log. Once a pick carrying a kickoff time is logged, it shows up as a small "🕐 15:00" chip next to the team names, and — on the live Match Log specifically, not the Archive — entries with a kickoff time are sorted latest-first, above any entry that doesn't have one; entries without a time keep their normal most-recently-logged-first order among themselves. Handy on a day with several fixtures logged out of order, so the Match Log reads top-to-bottom with the day's next-up fixture at the top. It's saved with the rest of the fixture inputs, so "↺ Clear Fields" blanks it and "↺ Reload Fixture" on a past entry restores whatever was typed for that one.
It archives automatically. The first time the app checks after the calendar date changes — on load, or when you come back to an open tab — everything logged "today" moves into a dated archive bucket, and the Match Log starts fresh for the new day's fixtures. Nothing is deleted: open 🗄 Archive to browse every past day (with its HIT/MISS split) and export it as CSV or JSON. The ⬇ CSV / ⬇ JSON buttons next to "Clear all" export everything at once — today's log plus the full archive — as one file for spreadsheet or offline analysis.
No. Everything — inputs, theme, your Match Log, and its archive — lives in your browser's local storage. There's no backend and no data ever leaves your device unless you tap an export button yourself.
Yes — served over HTTPS, PitchPulse is an installable PWA. Use the "Install App" button in the header, or your browser's own install prompt, and it'll keep working offline afterward.
PitchPulse produces a probability estimate from recent form — not a guarantee. It doesn't account for injuries, motivation, or opponent quality beyond the last-5-match window (an Elo rating pair, entered on the Team Form card, can correct for a cross-league quality gap, but there's no read on things like a relegation six-pointer or a dead rubber), and Signal Check / Calibration are only as meaningful as how many matches you've logged and resolved. Please gamble responsibly. — by Victor Korir
Questions about the model, licensing, or custom builds? Reach out directly: