# Tennis Odds Dataset - ATP & WTA Closing Odds, Results & Elo (`crawlplant/tennis-odds-dataset`) Actor

ATP and WTA results with closing odds in tennis-data.co.uk's column names (B365W/L, MaxW/L, AvgW/L, opening average, fair probability), next to Jeff Sackmann's match columns and pre-match Elo. By season or date range, bookmakers of your country. Export as CSV.

- **URL**: https://apify.com/crawlplant/tennis-odds-dataset.md
- **Developed by:** [CrawlPlant](https://apify.com/crawlplant) (community)
- **Categories:** Sports, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 matches

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Tennis Odds Dataset - ATP & WTA Closing Odds, Results & Elo

*Independent tool, not affiliated with, endorsed by or connected to tennis-data.co.uk, Jeff Sackmann, Tennis Abstract,
Flashscore, the ATP, the WTA, the ITF or any bookmaker. It reads the public match and odds data that flashscore.com shows
to every visitor.*

**Closing odds next to the result, one row per match, in the column names of tennis-data.co.uk.** Every row is a played
ATP, WTA, Challenger or ITF singles match in the layout of Jeff Sackmann's `atp_matches` / `wta_matches` files (winner and
loser, rank, seed, hand, height, age, score, round, minutes, serve statistics, **pre-match Elo**) with the
**closing prices** appended: `B365W` / `B365L`, `MaxW` / `MaxL`, `AvgW` / `AvgL`, `OpenAvgW` / `OpenAvgL`, `ProbW` /
`ProbL`, `BookmakerCount` and `OddsCountry`. Code that reads those columns keeps working.

### Related runnable examples

Explore the same tennis research workflows in our example repository. These recipes currently call the broader Flashscore Tennis Scraper or Match Dataset Actor; they are companion examples, not direct calls to this specialized Actor.

- [Compute Elo-versus-market probability differences](https://github.com/CrawlPlant/examples/blob/main/docs/recipes/tennis-elo-market.md)
- [Export winner/loser rows to JSON, CSV and SQLite](https://github.com/CrawlPlant/examples/blob/main/docs/recipes/tennis-dataset.md)

Follow the [Python setup and `.env` guide](https://github.com/CrawlPlant/examples/blob/main/docs/LIVE-QUICKSTART.md) with your own `APIFY_TOKEN`, or browse the [complete catalog](https://github.com/CrawlPlant/examples/blob/main/docs/CATALOG.md) and [AI assistant instructions](https://github.com/CrawlPlant/examples/blob/main/docs/AI-AGENTS.md). Actor runs follow Apify pricing; the quickstart explains per-run spending limits.

### Why this one

- **The columns you already use.** `B365W` / `B365L` (bet365 closing prices of the winner and the loser), `MaxW` / `MaxL`
  (best price across the bookmakers), `AvgW` / `AvgL` (average), `OpenAvgW` / `OpenAvgL` (average at the opening),
  `ProbW` / `ProbL` (win probability without the bookmaker margin), `BookmakerCount`, `OddsCountry`.
- **Result and odds in one row.** No join between a results file and an odds file: the score, rank, serve statistics and Elo
  sit next to the prices.
- **Your bookmakers.** Odds come from the bookmakers licensed in the country you pick (`oddsCountry`): about 20 in the
  UK, plus Germany, Poland, Italy, Spain, France, the US, Australia and others.
- **Elo included.** Both players' Elo before the match, to test your edge against the market.
- **Pay for odds only where they exist.** The odds price is charged only on matches that carry odds, never on the rest.
- **ATP, WTA, Challenger and ITF.** Whole seasons or the last few days.
- **Current.** This week's matches are in the dataset within the hour.
- **See it live.** The size of the database and the Elo leaders of both tours, from the same database your runs read:
  [crawlplant.com/tennis-odds-dataset](https://crawlplant.com/tennis-odds-dataset/).

### What can you use it for?

- **Betting models**: test a model against closing prices, the benchmark for a tennis edge.
- **Market research**: how closing prices moved from the opening average, favourites' hit rate by tour and surface.
- **Replacing a stale file**: refresh your results-and-odds table weekly with one scheduled task.
- **AI agents**: structured results with closing odds as JSON or CSV.

### Quick start

1. Click **Try for free** with the default input (ATP and WTA results of the last 60 days with their closing odds, oldest
   first, up to 200 matches).
2. Set **Years** for whole seasons, or **From / To day** for a date range; pick **Tours** and **Bookmakers of country**.
3. Export the dataset as **CSV**: the columns come in Jeff Sackmann's order, the odds columns last.

#### Copy to your AI assistant

Paste this into ChatGPT, Claude or any agent so it knows how to use the Actor:

```
crawlplant/tennis-odds-dataset on Apify: ATP / WTA / Challenger / ITF singles results in the columns of Jeff Sackmann's
atp_matches files + match_id, tour, winner_elo, loser_elo + closing odds in tennis-data.co.uk's columns: B365W, B365L,
MaxW, MaxL, AvgW, AvgL, OpenAvgW, OpenAvgL, ProbW, ProbL (fair win probability), BookmakerCount, OddsCountry.
Input: years (e.g. [2024, 2025]; empty = the dates), dateFrom / dateTo (e.g. "-7" and "today"), tours (atp, wta,
challenger-men, challenger-women, itf-men, itf-women), oddsCountry (GB default; DE, PL, US, AU...), maxItems (default 200).
Price: $1.00 per 1,000 matches plus $1.50 per 1,000 matches that have odds on the Free plan.
```

### Ready-to-use examples

**1. ATP and WTA results of the last 60 days with closing odds, oldest first (up to 200 matches, $0.50 at most; the default)**

```json
{ "dateFrom": "-60", "dateTo": "today", "tours": ["atp", "wta"] }
```

**2. A whole ATP season with closing odds (4,285 matches in 2025, up to $10.71)**

```json
{ "years": [2025], "tours": ["atp"], "maxItems": 5000 }
```

**3. The first 3,000 Challenger matches of the 2025 season with closing odds (up to $7.50)**

```json
{ "years": [2025], "tours": ["challenger-men"], "maxItems": 3000 }
```

**4. Last week's ATP and WTA closing odds from German bookmakers (up to 500 matches, $1.25 at most)**

```json
{ "dateFrom": "-7", "dateTo": "today", "tours": ["atp", "wta"], "oddsCountry": "DE", "maxItems": 500 }
```

**5. One day of WTA results with closing odds (up to 200 matches, $0.50 at most)**

```json
{ "dateFrom": "yesterday", "dateTo": "yesterday", "tours": ["wta"] }
```

### How to…

#### Download tennis closing odds with results

Run the default input for the last 60 days (example 1), or give `years` for whole seasons (examples 2 and 3). Set
`maxItems` above the number of matches you expect: rows come oldest first.

#### Get the odds of another country's bookmakers

Set `oddsCountry` to a two-letter code (example 4). `OddsCountry` in each row says whose bookmakers the prices are from.

#### Keep a results-and-odds table current

Schedule a task with `dateFrom` `-7` and `dateTo` `today` weekly and upsert the rows by `match_id`.

### Input options

| Option | Default | Description |
|---|---|---|
| `years` | `[]` | Seasons, e.g. `[2024, 2025]` (from 1990); empty = the days below |
| `tours` | `atp`, `wta` | `atp`, `wta`, `challenger-men`, `challenger-women`, `itf-men`, `itf-women` |
| `dateFrom` | `-60` | First day when `years` is empty: `2026-01-01`, `yesterday`, `-7` |
| `dateTo` | `today` | Last day, inclusive; empty = only the From day |
| `oddsCountry` | `GB` | Whose bookmakers the odds come from |
| `maxItems` | `200` | Maximum matches in total |

### Odds columns

| Column | Description |
|---|---|
| `B365W`, `B365L` | bet365 closing price of the match winner and the loser |
| `MaxW`, `MaxL` | Best closing price across the bookmakers |
| `AvgW`, `AvgL` | Average closing price |
| `OpenAvgW`, `OpenAvgL` | Average price at the opening |
| `ProbW`, `ProbL` | Win probability of the winner and the loser without the bookmaker margin |
| `BookmakerCount` | How many bookmakers priced the match |
| `OddsCountry` | The country whose bookmakers these are |

Every other column is Jeff Sackmann's: `tourney_id`, `tourney_name`, `surface`, `draw_size`, `tourney_level`,
`tourney_date`, `match_num`, `winner_*` and `loser_*` (id, seed, entry, name, hand, ht, ioc, age, rank, rank_points),
`score`, `best_of`, `round`, `minutes`, the `w_*` / `l_*` serve statistics, plus `match_id`, `tour`, `winner_elo` and
`loser_elo`. A match without odds has the odds columns empty.

### How much does tennis closing odds data cost?

Pay per result: platform usage is included. A match is one price; the closing odds are a second price charged only on the
matches that have odds.

| Event | No discount (Free plan) | Bronze (Starter) | Silver (Scale) | Gold (Business) |
|---|---|---|---|---|
| Match (per 1,000) | $1.00 | $0.90 | $0.80 | $0.70 |
| Closing odds (per 1,000) | $1.50 | $1.35 | $1.20 | $1.05 |
| Actor start (per run) | $0.00005 | $0.00005 | $0.00005 | $0.00005 |

| Example on the Free plan | Rows | Cost |
|---|---|---|
| Default run: the last 60 days, up to 200 matches | up to 200 | up to ~$0.50 |
| Last week, ATP + WTA, up to 500 matches | up to 500 | up to ~$1.25 |
| A full ATP season (4,285 matches in 2025) | 4,285 | ~$4.29 + $1.50 per 1,000 with odds, up to ~$10.71 |

Set a **maximum cost per run** in the run options to stop a large run at your budget.

### Run it through the API

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('crawlplant/tennis-odds-dataset').call({ dateFrom: '-7', dateTo: 'today', tours: ['atp', 'wta'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("crawlplant/tennis-odds-dataset").call(run_input={"years": [2025], "tours": ["atp"], "maxItems": 5000})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["tourney_name"], row["winner_name"], row["loser_name"], row["MaxW"], row["MaxL"])
```

### Use with AI agents

The Actor works as a tool in the [Apify MCP server](https://mcp.apify.com) for Claude, ChatGPT, Cursor and others:

```
https://mcp.apify.com?tools=crawlplant/tennis-odds-dataset
```

Try *"which ATP favourites at the closing price lost last week?"*.

**Use it when** you need results with closing odds in tennis-data.co.uk's columns.

**Not for** live scores or the odds of matches not yet played (use the [Flashscore Tennis Scraper](https://apify.com/crawlplant/flashscore-tennis)).

### More tennis data

- [Flashscore Tennis Scraper](https://apify.com/crawlplant/flashscore-tennis): every match of any day with odds from about
  20 bookmakers, head-to-head, form, rankings, player statistics and Elo lists.
- [Tennis Live Scores](https://apify.com/crawlplant/tennis-live-scores): matches in play now and any day's scores, ATP, WTA, Challenger and ITF.
- [Tennis Match History](https://apify.com/crawlplant/tennis-match-history): a player's whole career, match by match, with Elo and serve statistics.
- [ATP & WTA Tennis Rankings](https://apify.com/crawlplant/tennis-rankings): the current lists, every weekly list back to 1973 (ATP) and 2001 (WTA), and the player lists.
- [ATP & WTA Tennis Match Dataset](https://apify.com/crawlplant/tennis-match-dataset): whole seasons in Jeff Sackmann's CSV columns.

### FAQ

#### Does every match have odds?

No. Matches without odds have the odds columns empty, and you are not charged the odds price for them.

#### How far back does it go?

Results from 1990. Odds exist for the matches the bookmakers priced; ask for a season with `years` and compare
`BookmakerCount` to see how well it is covered.

#### Which date range do I give for one day?

Set `dateFrom` and `dateTo` to the same day (example 5; the default `dateTo` is `today`). With `dateFrom` `-7` and `dateTo` `today` you get the
last 8 days.

#### Is it legal?

The data is public sporting results read without logging in, at a polite pace. How you use and publish them is your
responsibility, so check the source's terms for your use case.

Questions, a missing field or a source you need? Open an issue on this Actor: we reply within a day. If it saved you time, a short review helps others find it.

### Privacy

The data is about professional players' public sporting results and the prices bookmakers showed for them. Each run sends the developer anonymous feature-usage
statistics (the options used, never the players you asked for); your Apify account id is replaced by a one-way hash.

# Actor input Schema

## `years` (type: `array`):

The seasons, e.g. \[2024, 2025] (from 1990). Empty = the From / To days below.

## `tours` (type: `array`):

atp / wta = singles of the main tours, challenger-men / challenger-women = the Challenger level, itf-men / itf-women = ITF events. Empty = ATP and WTA.

## `dateFrom` (type: `string`):

Used when Years is empty: the first day, e.g. 2026-01-01 or -7 (seven days ago).

## `dateTo` (type: `string`):

Used when Years is empty: the last day, inclusive, e.g. today. For a single day, set both days to it (the default is today and the 60 days before).

## `oddsCountry` (type: `string`):

Whose bookmakers: bookmakers differ by country (licences). Two-letter code: GB (about 20 bookmakers), DE, PL, IT, ES, FR, US, AU, NG and others. It sets the prices in the odds columns and is written to OddsCountry.

## `maxItems` (type: `integer`):

Maximum matches to return. A full ATP season is about 4,300 matches with qualifying, WTA about 3,800. Odds are charged only on matches that have them.

## Actor input object example

```json
{
  "years": [],
  "tours": [
    "atp",
    "wta"
  ],
  "dateFrom": "-60",
  "dateTo": "today",
  "oddsCountry": "GB",
  "maxItems": 200
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlplant/tennis-odds-dataset").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("crawlplant/tennis-odds-dataset").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call crawlplant/tennis-odds-dataset --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlplant/tennis-odds-dataset"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/QKOqX77UvclRT66my/builds/n0AMtX6bLutGGixNa/openapi.json
