← Back to tutorials

Data

Audit prediction accuracy on Champions League results

Compare match_winner predictions to the last ten Champions League results and flag correct calls.

Published on October 29, 2025 · 9 min read
Scoreboard style layout summarising past fixtures.

Audit how well match winner predictions performed on the last ten finished Champions League games and compute a quick success rate.

Step 1 — Set up the recap table

Drop the following markup into index.html. The status block will show the running accuracy percentage.

index.html
<!DOCTYPE html><html lang="en">  <head>    <meta charset="utf-8" />    <title>GameForecastAPI accuracy recap</title>    <link rel="stylesheet" href="styles.css" />  </head>  <body class="dashboard">    <main class="card">      <h1 class="card__title">Champions League accuracy</h1>      <p id="status" class="status"></p>      <div class="table-wrapper">        <table>          <thead>            <tr>              <th>Kickoff</th>              <th>Home</th>              <th>Away</th>              <th>Score</th>              <th>Winner</th>              <th>Prediction</th>              <th>Outcome</th>            </tr>          </thead>          <tbody id="results-body"></tbody>        </table>      </div>    </main>    <script type="module" src="accuracy.js"></script>  </body></html>

Feel free to include league branding or sponsor callouts around the table. Accuracy recaps convert well for newsletters and Discord communities because they demonstrate transparency on every bet recommendation.

Step 2 — Compute accuracy with vanilla JavaScript

This script fetches the ten most recent finished matches (league id 8), compares the most probable outcome against the actual winner, and highlights correct calls.

accuracy.js
const RAPID_API_HOST = "game-forecast-api.p.rapidapi.com";const CHAMPIONS_LEAGUE_ID = 8;const RAPID_API_KEY = "YOUR_RAPIDAPI_KEY";const headers = {  "X-RapidAPI-Key": RAPID_API_KEY,  "X-RapidAPI-Host": RAPID_API_HOST,};const resultsBody = document.getElementById("results-body");const status = document.getElementById("status");if (!resultsBody || !status) {  throw new Error("Required DOM nodes are missing. Check your HTML markup.");}function setStatus(message) {  status.textContent = message;}function createCell(text, className) {  const cell = document.createElement("td");  cell.textContent = text;  if (className) {    cell.className = className;  }  return cell;}function resolveWinner(event) {  const finalScore = event.score && event.score.final ? event.score.final : {};  const rawWinner = typeof finalScore.winner === "string" ? finalScore.winner.trim().toLowerCase() : null;  if (rawWinner === "home" || rawWinner === "away" || rawWinner === "draw") {    return rawWinner;  }  const homeGoals = typeof finalScore.home === "number" ? finalScore.home : null;  const awayGoals = typeof finalScore.away === "number" ? finalScore.away : null;  if (homeGoals !== null && awayGoals !== null) {    if (homeGoals > awayGoals) return "home";    if (awayGoals > homeGoals) return "away";    return "draw";  }  return null;}function getPredictedWinner(event) {  const prediction =    event.predictions && event.predictions[0] && event.predictions[0].match_result;  if (!prediction) {    return { label: "-", isCorrect: false };  }  const homeProb = Number(prediction.home ?? 0);  const drawProb = Number(prediction.draw ?? 0);  const awayProb = Number(prediction.away ?? 0);  let outcome = "home";  let bestScore = homeProb;  if (drawProb > bestScore) {    outcome = "draw";    bestScore = drawProb;  }  if (awayProb > bestScore) {    outcome = "away";    bestScore = awayProb;  }  const resolvedWinner = resolveWinner(event);  if (outcome === "draw") {    const isCorrect = resolvedWinner === "draw";    return { label: "Draw", isCorrect };  }  const predictedTeam = outcome === "home" ? event.team_home.name : event.team_away.name;  const isCorrect =    resolvedWinner !== null && outcome === resolvedWinner;  return { label: predictedTeam, isCorrect };}function renderMatches(matches) {  resultsBody.innerHTML = "";  let correct = 0;  matches.forEach((event) => {    const row = document.createElement("tr");    row.appendChild(createCell(new Date(event.start_at).toLocaleString(), "nowrap"));    row.appendChild(createCell(event.team_home.name));    row.appendChild(createCell(event.team_away.name));    const finalScore = event.score && event.score.final ? event.score.final : {};    const scoreText =      String(finalScore.home ?? 0) + " - " + String(finalScore.away ?? 0);    row.appendChild(createCell(scoreText, "numeric"));    const resolvedWinner = resolveWinner(event);    const winnerLabel =      resolvedWinner === "home"        ? event.team_home.name        : resolvedWinner === "away"        ? event.team_away.name        : "Draw";    row.appendChild(createCell(winnerLabel));    const prediction = getPredictedWinner(event);    row.appendChild(createCell(prediction.label, "muted"));    const outcomeCell = createCell(prediction.isCorrect ? "Correct" : "Miss");    outcomeCell.className = prediction.isCorrect ? "is-success" : "is-error";    row.appendChild(outcomeCell);    if (prediction.isCorrect) {      correct += 1;    }    resultsBody.appendChild(row);  });  const successRate =    matches.length > 0 ? Math.round((correct / matches.length) * 100) : 0;  setStatus(    "Success rate on the last " + matches.length + " matches: " + successRate + "%."  );}async function loadMatches() {  setStatus("Loading finished matches…");  try {    const url = new URL("https://" + RAPID_API_HOST + "/events");    url.searchParams.set("league_id", String(CHAMPIONS_LEAGUE_ID));    url.searchParams.set("status_code", "FINISHED");    url.searchParams.set("page_size", "10");    url.searchParams.set("start_at_start", "2025-10-15");    const response = await fetch(url, { headers });    if (!response.ok) {      throw new Error("Failed to load finished matches");    }    const payload = await response.json();    const matches = payload.data || [];    if (matches.length === 0) {      resultsBody.innerHTML = "";      setStatus("No finished matches found.");      return;    }    renderMatches(matches);  } catch (error) {    console.error(error);    setStatus("Unable to fetch finished matches.");  }}loadMatches();

The helper getPredictedWinner reads the match_result probabilities generated once per day by our in-house model. When the API marks a draw as the likeliest outcome, the script cross-checks it against score.final.winner to avoid false positives.

Want to go deeper? Use the recommended_bets array for parlays, or incorporate total_goals predictions to benchmark multi-market strategies from the same payload.

Step 3 — Next experiments

  • Group matches by stage to see where predictions excel or struggle.
  • Blend match odds from the previous tutorial to understand where the market disagreed with our model.
  • Translate the accuracy recap into a weekly newsletter for your subscribers.

Turn accuracy into subscriptions

When you upgrade on RapidAPI, you gain priority access to fresh predictions at midnight UTC, letting you publish verified records before your competitors wake up. Add a CTA alongside this recap that links subscribers straight to your premium picks powered by GameForecastAPI.
Get your RapidAPI key