← Developer Blog

Match a Polymarket Market to a Live Tennis Match

Map a Polymarket tennis market to the right live match in Python: normalize player names, survive accents and initials, and refuse to guess.

· · By the Live Tennis API team

To match a Polymarket tennis market to a live match, normalize both player names — strip accents, lowercase, collapse "F. Lastname" and "Lastname F." to a canonical form — then require both players to align before you pair a market with a live score. Never pair on a single name.

This is the join that every Polymarket tennis trading bot needs and the step that quietly breaks most of them. Polymarket's Gamma API gives you market prices keyed by human-typed player names ("Jiri Lehecka vs Arthur Fils"). A live-score feed keys the same match by its own player records. Nothing shares an ID across the two systems, so you have to resolve identity from names — and names are messy.

Why name matching is the hard part

The same player shows up written many different ways:

A naive market_name == feed_name comparison misses almost everything; a loose substring match creates false pairs (matching the wrong Fils). The safe rule is stricter than either: canonicalize each name, then only accept when both sides of the market map to the two players in one live match. If it is ambiguous, return nothing.

Prerequisites

export LIVETENNIS_API_KEY=ltapi_...

Step 1 — Pull the two sides

Fetch open tennis markets from Gamma (tennis carries tag id 864) and live matches from the Live Tennis API. See the exact response fields in the docs.

import os, httpx

GAMMA = "https://gamma-api.polymarket.com"
LTA   = "https://api.livetennisapi.com/api/public/v1"
KEY   = os.environ["LIVETENNIS_API_KEY"]

def gamma_tennis_markets():
    r = httpx.get(f"{GAMMA}/markets",
                  params={"tag_id": 864, "closed": "false", "limit": 200})
    r.raise_for_status()
    return r.json()

def live_matches():
    r = httpx.get(f"{LTA}/matches",
                  params={"status": "live"},
                  headers={"X-API-Key": KEY})
    r.raise_for_status()
    return r.json()

A per-match moneyline market's two outcomes are the two player names — that pair is what you match against a live match's two players.

Step 2 — Canonicalize a name

Reduce every name to a comparable key: drop accents, lowercase, and normalize initials to firstinitial + lastname.

import unicodedata, re

def strip_accents(s: str) -> str:
    return "".join(c for c in unicodedata.normalize("NFKD", s)
                   if not unicodedata.combining(c))

def canon(name: str) -> str:
    """'Jiří Lehečka' -> 'jlehecka'; 'Fils A.' -> 'afils'."""
    s = strip_accents(name).lower()
    s = re.sub(r"[^a-z\s.]", " ", s)
    parts = [p.strip(".") for p in s.split() if p.strip(".")]
    if not parts:
        return ""
    # Move a lone initial to the front: 'fils a' -> ['a','fils']
    if len(parts[-1]) == 1 and len(parts[0]) > 1:
        parts = [parts[-1]] + parts[:-1]
    first, last = parts[0], parts[-1]
    return (first[0] + last) if last != first else last

canon("Jiří Lehečka"), canon("J. Lehecka") and canon("Lehecka J.") all collapse to jlehecka. This is deliberately conservative — first initial plus last name — because full first names are the field most likely to differ between sources.

Step 3 — Pair only when BOTH players agree

Build a canonical key for each live match (a frozenset of its two players), then match a market only when its two outcome names produce the identical set. The frozenset makes order irrelevant, and requiring an exact two-name set is what stops false pairs.

def match_key(name_a: str, name_b: str):
    return frozenset({canon(name_a), canon(name_b)})

def index_live(matches):
    idx = {}
    for m in matches:
        a, b = m["home"]["name"], m["away"]["name"]   # see docs for fields
        idx[match_key(a, b)] = m
    return idx

def match_market(market, live_index):
    outcomes = market.get("outcomes")
    if isinstance(outcomes, str):
        import json; outcomes = json.loads(outcomes)
    if not outcomes or len(outcomes) != 2:
        return None                     # futures / non-moneyline: skip
    key = match_key(outcomes[0], outcomes[1])
    if len(key) != 2:
        return None                     # both names collapsed the same: ambiguous
    return live_index.get(key)          # None = no live counterpart, don't guess

live_index = index_live(live_matches())
for mkt in gamma_tennis_markets():
    hit = match_market(mkt, live_index)
    if hit:
        print(mkt["question"], "->", hit["id"])

Returning None for anything ambiguous is the whole point: a trading bot that acts on a wrong pairing reads the wrong score for the wrong market. Silence beats a confident mismatch.

Step 4 — Use the maintained matcher

The edge cases multiply — doubles teams, mid-tournament name variants, futures markets with no live match, players whose surnames collide within one event. The open-source polymarket-tennis toolkit (MIT) packages this resolution so you don't re-derive it. It discovers tennis markets via Gamma, matches them to live matches, and refuses to guess when a pairing is ambiguous.

pip install "polymarket-tennis @ git+https://github.com/livetennisapi/polymarket-tennis"
from polymarket_tennis import (
    GammaClient, LiveTennisClient,
    discover_tennis_markets, match_market, build_view,
)

with GammaClient() as gamma, LiveTennisClient() as lta:
    markets = discover_tennis_markets(gamma, market_types={"moneyline"},
                                      matches_only=True)
    candidates = lta.live_matches() + lta.fixtures()
    for market in markets:
        decision = match_market(market, candidates)
        if decision is None:
            continue                      # ambiguous or no live counterpart
        print(build_view(market, decision.match).render())

Or inspect one market's matching decision from the CLI:

pmtennis match atp-lehecka-fils-2026-08-17

Wrap-up

Identity resolution is the join that makes market price and live score meaningful together. Canonicalize names, require both players to agree, and treat ambiguity as "no match" rather than a coin flip. The toolkit above is observe-only — it reads public market data and live scores and never places orders. Order execution, wallets and keys are yours to build on Polymarket's own official interfaces. Next, join the matched price and score into one view in the pillar build guide.

Frequently asked questions

How do you match a Polymarket market to a live tennis match?

Canonicalize both player names on each side — strip accents, lowercase, and normalize initials to first-initial-plus-surname — then pair a market with a live match only when both players agree. Treat any ambiguous case as no match rather than guessing, since a wrong pairing feeds the wrong score to the wrong market.

Why is tennis player-name matching so hard?

The same player appears written many ways across sources: accents and transliteration (Lehecka vs Lehečka), initial formats (Arthur Fils, A. Fils, Fils A.), and reversed name order. There is no shared ID between Polymarket's Gamma API and a live-score feed, so identity must be resolved from these inconsistent name strings.

Do Polymarket and a live tennis feed share a match ID?

No. Polymarket's Gamma API keys markets by human-typed player names and slugs, while a live-score feed keys matches by its own player records. You must resolve identity from names by normalizing both sides and requiring both players to align.

Is there a library that matches Polymarket tennis markets to live matches?

Yes. The open-source polymarket-tennis toolkit (MIT) discovers tennis markets via Polymarket's Gamma API and matches them to live matches from the Live Tennis API, refusing to guess when a pairing is ambiguous. It is observe-only and handles doubles, futures and surname collisions.

What data do I need to match markets to live matches?

Two feeds: Polymarket's keyless Gamma API for the market and its player names (tennis is tag id 864), and a live-score feed for live matches. The Live Tennis API free tier (30 req/min, 100 req/day, no card) covers live matches and players, which is enough for the matching step.

Built with the Live Tennis API — real-time scores, players, odds and model win-probability for ATP, WTA, Challenger and ITF.

API reference SDKs on GitHub Plans from $9.99/mo