PitchPulse

Every match has a pulse!
λ Lambdas (H/A) —/—
PP Exp. Score —
🔥 Best Bet —
Top EV —
λ Lambdas (H/A) —/—
PP Exp. Score —
🔥 Best Bet —
Top EV —

League Baseline

The league's average goals per match — converts raw form into attack and defence strength. Pick a league to auto-fill, or set the numbers yourself.

Advanced — pick a league or override
Using defaults: Home 1.50  |  Away 1.20 (toggle Advanced to change)

Team Form

Enter each side's name, then their season-to-date MP-GF-GA (home venue for Home, away venue for Away) — copy it straight off the league standings.

HOM
AWA

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.

Bookmaker Odds (Optional)

Type the bookies' decimal odds and they feed the EV Finder card and Model Comparison tab automatically, de-vigged behind the scenes. Same odds as the EV Finder card's own ranking on the Verdict tab — edit here and it stays in sync.

📊 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.

PP
⚽ Most Likely Score
Home
– – –
Away
Top correct score probability
Expected Goals
—
Over 2.5
—
BTTS Yes
—
HT Score (Est)
—

1X2

Home
– –
–
Draw
– –
–
Away
– –
–
Most likely score –
Vs league baseline –

Over / Under

Over
––
–
Under
––
–
Most likely Over score –
Vs league baseline –

Both Teams to Score

Yes
––
–
No
––
–
Most likely BTTS score –
Vs league baseline –

Top correct scores

    Market Implied
    ⚽ Bookies' Most Likely Score
    Home
    – – –
    Away
    Enter odds in the EV Finder card on the Verdict tab
    Expected Goals
    —
    Over 2.5
    —
    BTTS Yes
    —
    Overround
    —

    1X2

    Home
    – –
    –
    Draw
    – –
    –
    Away
    – –
    –
    Most likely score –
    1X2 overround –

    Over / Under

    Over
    ––
    –
    Under
    ––
    –
    Most likely Over score –
    O/U overround –

    Both Teams to Score

    Yes
    ––
    –
    No
    ––
    –
    Most likely BTTS score –
    BTTS overround –

    Top correct scores

      Calculate probabilities on the Inputs tab to see the data analysis.

      Calculate probabilities on the Form tab to see your Match Verdict.

      📋 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.

      Season MP-GF-GA — matches played, goals for, goals against

      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.

      League averages

      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

      Everything stays on your device

      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.

      Installing PitchPulse

      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.

      Light / dark mode

      Toggle the sun/moon icon in the header any time — your choice is remembered on next visit.

      ❓ Methodology & FAQ

      What model does PitchPulse use?

      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.

      What is the Dixon-Coles correction, and why is it adaptive?

      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.

      What's the bivariate Poisson blend, and when does it kick in?

      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.

      Why season-to-date instead of last 5 matches?

      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.

      How are λ (expected goals) calculated?

      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.

      What's on the Data Analysis tab?

      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.)

      What are the default league averages, and why do they matter?

      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.

      What is Best Bet, and how is it different from "highest probability"?

      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.

      What does the 🧪 "Thin data" note mean?

      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.

      Why does Best Bet sometimes shimmer gold?

      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.

      What is the EV Finder, and how is it different from Best Bet?

      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.

      What is Signal Check?

      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.

      What is Calibration (the Brier score)?

      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.

      How does the Match Log work?

      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.

      What's the Season Tally (League/Cup tally) field?

      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.

      What is Form Check, and what do the 📋 Form: Agree / 📋 Form: Diverge badges mean?

      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.

      What's the little time field between the team names for, and does it change the model?

      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.

      What happens to the Match Log after 24 hours?

      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.

      Does PitchPulse store or send my data anywhere?

      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.

      Can I install PitchPulse as an app?

      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.

      Disclaimer

      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

      📬 Contact the Developer

      Questions about the model, licensing, or custom builds? Reach out directly: