Skip to content

Guides

How do I compare ATP closing odds with the result and Elo in Python?

One call returns a match's score, both players' Elo before it and every sportsbook's open and close price. This removes the margin and sets the market beside Elo.

Code verified October 12, 2026. One call to get_match, 2 credits.

The call

match_id comes from get_matches or get_tournament. Odds are in the odds array, one row per book, and a match without odds returns an empty array. Pass league: "wta" for the women's tour.

import requests
import pandas as pd

API_KEY = "YOUR_API_KEY"

resp = requests.post(
    "https://mcp.acesapi.com/v1/get_match",
    headers={"x-api-key": API_KEY},
    json={
        "match_id": "157774",
    },
    timeout=60,
)
resp.raise_for_status()
data = resp.json()["data"]

def implied(odds):
    return 100 / (odds + 100) if odds > 0 else -odds / (-odds + 100)

m = data["match"]
p1, p2 = data["players"]
winner = m["player1"] if m["winner_id"] == m["player1_id"] else m["player2"]
print(m["tournament"], m["round"], "|", m["player1"], "v", m["player2"], m["score"], "| winner:", winner)

odds = pd.DataFrame(data["odds"])
odds["p1_fair"] = odds["p1_close"].map(implied) / (odds["p1_close"].map(implied) + odds["p2_close"].map(implied))
odds["p1_fair_open"] = odds["p1_open"].map(implied) / (odds["p1_open"].map(implied) + odds["p2_open"].map(implied))
print(odds[["book", "p1_open", "p1_close", "p2_close", "p1_fair_open", "p1_fair"]].round(3).to_string(index=False))

elo_p1 = 1 / (1 + 10 ** ((p2["rating"]["before"] - p1["rating"]["before"]) / 400))
surface_p1 = 1 / (1 + 10 ** ((p2["rating"]["surface_before"] - p1["rating"]["surface_before"]) / 400))
print(m["player1"], "win probability: market", round(odds["p1_fair"].median(), 3),
      "| overall Elo", round(elo_p1, 3), "| surface Elo", round(surface_p1, 3))

Replace YOUR_API_KEY with a key from your dashboard. Python needs requests and pandas (pip install requests pandas). JavaScript runs on Node 18 or newer, or in the browser.

What comes back

The data object of the response, trimmed to the first rows. Every field is real output from the run above.

{
  "tour": "ATP",
  "match": {
    "match_id": "157774",
    "tournament_id": "188-2025",
    "tournament": "Wimbledon",
    "season": 2025,
    "surface": "grass",
    "indoor": false,
    "starts_at": "2025-07-13T15:20:00.000Z",
    "match_date": "2025-07-13",
    "draw": "main",
    "round": "Final",
    "round_order": 22,
    "court": "Centre Court",
    "player1_id": "3623",
    "player1": "Jannik Sinner",
    "player1_country": "ITA",
    "seed1": 1,
    "player2_id": "3782",
    "player2": "Carlos Alcaraz",
    "player2_country": "ESP",
    "seed2": 2,
    "winner_id": "3623",
    "status": "final",
    "outcome": "completed",
    "score": "4-6 6-4 6-4 6-4",
    "sets1": 3,
    "sets2": 1,
    "games1": 22,
    "games2": 18,
    "note": "(1) Jannik Sinner (ITA) bt (2) Carlos Alcaraz (ESP) 4-6 6-4 6-4 6-4",
    "sets": [
      {
        "set_no": 1,
        "games1": 4,
        "games2": 6,
        "tiebreak1": null,
        "tiebreak2": null,
        "winner": 2
      },
      {
        "set_no": 2,
        "games1": 6,
        "games2": 4,
        "tiebreak1": null,
        "tiebreak2": null,
        "winner": 1
      },
      {
        "set_no": 3,
        "games1": 6,
        "games2": 4,
        "tiebreak1": null,
        "tiebreak2": null,
        "winner": 1
      },
      {
        "set_no": 4,
        "games1": 6,
        "games2": 4,
        "tiebreak1": null,
        "tiebreak2": null,
        "winner": 1
      }
    ]
  },
  "players": [
    {
      "player_id": "3623",
      "name": "Jannik Sinner",
      "seed": 1,
      "sets_won": 3,
      "games_won": 22,
      "tiebreaks_won": 0,
      "tiebreak_points_won": 0,
      "sets_won_6_0": 0,
      "rating": {
        "before": 2325,
        "after": 2335,
        "surface_before": 1995,
        "surface_after": 2037
      }
    },
    {
      "player_id": "3782",
      "name": "Carlos Alcaraz",
      "seed": 2,
      "sets_won": 1,
      "games_won": 18,
      "tiebreaks_won": 0,
      "tiebreak_points_won": 0,
      "sets_won_6_0": 0,
      "rating": {
        "before": 2278,
        "after": 2267,
        "surface_before": 2176,
        "surface_after": 2135
      }
    }
  ],
  "stats": {
    "sets_played": 4,
    "total_games": 40,
    "tiebreaks": 0,
    "went_the_distance": false,
    "winner_lost_first_set": true,
    "serve_stats": null,
    "serve_stats_note": "ESPN publishes no serve statistics for tennis (aces, double faults, break points), so none are served here."
  },
  "odds": [
    {
      "source": "the_odds_api",
      "book": "888sport",
      "p1_open": 105,
      "p1_close": -110,
      "p2_open": -120,
      "p2_close": -105,
      "games_spread1": null,
      "games_total": null
    },
    {
      "source": "the_odds_api",
      "book": "Betfair Exchange",
      "p1_open": 104,
      "p1_close": -115,
      "p2_open": -106,
      "p2_close": 112,
      "games_spread1": null,
      "games_total": null
    },
    "...4 more"
  ]
}

What to do next

  • get_head_to_head (3 credits). How the two players have done against each other, by surface and in finals.
  • get_surface_splits (3 credits). Each player's record on the surface they meet on.

More guides

Start with 500 free credits every month

No card. REST or MCP.