# Odds Arbitrage & Value Signal API (`westerly_breaker/odds-arbitrage-value-signal`) Actor

Cross-book best-price comparison via The Odds API (bring your own key): detect guaranteed-profit arbitrage (negative overround) with a stake-split calculator, and flag value bets against a de-vigged sharp-book (Pinnacle) fair price.

- **URL**: https://apify.com/westerly\_breaker/odds-arbitrage-value-signal.md
- **Developed by:** [Daniel Posztos](https://apify.com/westerly_breaker) (community)
- **Categories:** AI, Agents, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.01 / 1,000 dataset items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Odds Arbitrage & Value Signal API — Cross-Book De-Vig Value Layer

**SEO title:** Odds Arbitrage & Value Signal API — Sports Betting Edge
**SEO description:** Detect guaranteed-profit arbitrage across sportsbooks and flag value bets against a de-vigged Pinnacle fair price, via The Odds API (bring your own key). No scraping, no anti-bot.

Raw sportsbook odds are a commodity — dozens of feeds publish them. The **signal** is the product: this actor pulls odds from [The Odds API](https://the-odds-api.com/) (you bring your own key — zero scraping, zero anti-bot risk), the same BYO-key source and de-vig math as actor #8 (Sports Odds → Fair Probability API), and adds a value layer on top:

1. **Arbitrage detection** — for every (event, market, betting line), find the single best (highest) decimal odds for each outcome across every book you're comparing. If those best-odds imply a combined probability under 100%, a guaranteed-profit arbitrage exists — reported as `arb_pct`, with a `stake_split` telling you exactly what fraction of your stake to place on each outcome (at its best-odds book) for an equal payout no matter who wins.
2. **Value-bet signal** — `sharp_book` (default `"pinnacle"`, the traditional sharp reference in the betting industry) is de-vigged (multiplicative/power/Shin — your choice) to get a fair probability per outcome. Every (book, outcome) pair whose own price implies an edge at or above `min_edge_pct` versus that fair probability is flagged in `value_flags`.

Built for betting modelers, arbitrage hunters, cross-platform arbitrage tooling (pairs naturally with actor #7, Prediction Market Odds API), and AI agents that need a structured "is there money on the table here" signal instead of raw odds tables.

**⚠️ Odds move fast.** Every row here is a snapshot from the moment this run fetched it. This is a research/alert tool for spotting a signal worth checking, **not an execution guarantee** — by the time you place a bet, the price (or the arbitrage window) may already be gone. Always re-check the live price at the actual sportsbook before staking money.

### Why this exists

A raw odds feed just gives you numbers. Turning those numbers into a decision requires two separate calculations most tools don't do for you:

- **Is there a mathematical arbitrage right now?** Comparing the best price for every outcome across several books, then checking whether their combined implied probability dips under 100% — a genuine, bookmaker-agnostic guaranteed return if all legs are placed.
- **Is any individual book's price generous relative to the market's real (de-vigged) view?** This requires a trusted fair-price anchor (a "sharp" book whose own vig has been removed) to compare every other book's price against.

This actor computes both, on every event/market/line it fetches, and never silently drops a row for thin data — a market that can't support one or the other calculation still ships with a documented `note` explaining exactly why.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `odds_api_key` | string (secret) | — | **Required.** Your own The Odds API key ([free tier: 500 requests/month](https://the-odds-api.com/#get-access)). Bring-your-own-key: never logged, never in output. |
| `sport` | string | — | **Required.** A The Odds API sport_key, e.g. `"basketball_nba"`, `"soccer_epl"`, or `"upcoming"` (next games across all sports). Validated against the live `/v4/sports` catalog at run time (free) — an unknown key fails fast with example valid keys. |
| `leagues` | array of strings | `[]` | Additional sport_keys to fetch in the SAME run alongside `sport` (each costs its own separate quota). |
| `books` | array of strings | `[]` | Bookmaker keys to restrict to, e.g. `["draftkings", "fanduel", "pinnacle"]`. If non-empty and `sharp_book` isn't in the list, it's added automatically (free — doesn't change quota cost). Leave empty to compare every bookmaker available in `regions` (recommended for arbitrage — more books = more chance of a real cross-book gap). |
| `regions` | array of strings | `["us"]` | Odds API region codes (`us`, `uk`, `eu`, `au`, ...). **Pinnacle is not always covered by region `"us"`** — see "Getting real value signals" below. Each additional region multiplies quota cost. |
| `markets` | array of strings | `["h2h"]` | `"h2h"` (moneyline/1x2), `"spreads"` (point spread), `"totals"` (over/under). Each requested market multiplies quota cost. |
| `min_edge_pct` | number (0–100) | `2.0` | Minimum edge (percentage points) a book's own price must clear versus `sharp_book`'s de-vigged fair probability to be reported in `value_flags`. |
| `devig_method` | string | `"multiplicative"` | `"multiplicative"`, `"power"`, or `"shin"` — how `sharp_book`'s own margin is stripped to compute the fair-probability anchor. |
| `sharp_book` | string | `"pinnacle"` | The Odds API bookmaker key used as the value-bet anchor. If it doesn't cover a given event/market/line, `value_flags` is left empty for that row with an explicit `note` — never silently wrong. |
| `max_events` | integer (1–200) | `25` | Max DISTINCT events, fair-shared across every requested sport_key. Each event still produces one row per (market × distinct line) it has. |

#### Getting real value signals

`value_flags` needs `sharp_book` (Pinnacle by default) to actually be one of the books returned for a given game. Confirmed live (2026-07-07): Pinnacle is **not** consistently covered by The Odds API's `"us"` region (the input default) for every sport — it shows up reliably for e.g. MLB/NPB/KBO baseball under `"us"`, but was absent for a WNBA slate checked the same day. If your `value_flags` keep coming back empty with the *"Sharp book 'pinnacle' is not among the books..."* note, add `"eu"` to `regions` (Pinnacle is a European-licensed book and shows up there far more consistently), or list `"pinnacle"` explicitly in `books`.

#### Worked example — input

```json
{
  "odds_api_key": "YOUR_THE_ODDS_API_KEY",
  "sport": "upcoming",
  "markets": ["h2h"],
  "regions": ["us", "eu"],
  "devig_method": "multiplicative",
  "sharp_book": "pinnacle",
  "min_edge_pct": 1.0,
  "max_events": 8
}
````

#### Worked example — a real arbitrage (live data, 2026-07-07)

```json
{
  "event": {
    "id": "f0620d5ff3f64875ff71939d62f51d91",
    "sport_key": "tennis_atp_wimbledon",
    "sport_title": "ATP Wimbledon",
    "commence_time": "2026-07-07T06:45:00Z",
    "home_team": "Jiri Lehecka",
    "away_team": "Alexander Zverev"
  },
  "market": "h2h",
  "point": null,
  "devig_method": "multiplicative",
  "sharp_book": "pinnacle",
  "best_odds": {
    "Alexander Zverev": { "book": "betfair_ex_eu", "odds": 1.07, "point": null },
    "Jiri Lehecka": { "book": "onexbet", "odds": 16.0, "point": null }
  },
  "overround": 0.9970794392523364,
  "arb_pct": 0.29291154071471315,
  "stake_split": {
    "Alexander Zverev": 0.9373169302870533,
    "Jiri Lehecka": 0.0626830697129467
  },
  "value_flags": [],
  "books_compared": ["betclic_fr", "betfair_ex_eu", "betmgm", "bovada", "coolbet", "draftkings", "fanduel", "marathonbet", "matchbook", "onexbet", "sport888", "unibet_nl", "unibet_se", "winamax_de", "winamax_fr"],
  "note": "Sharp book 'pinnacle' is not among the books offering this event/market/line (either it doesn't cover this game, or it wasn't included in 'books'/'regions'); value_flags left empty for this row -- see README \"Getting real value signals\".",
  "fetched_at": "2026-07-07T08:06:08.024198+00:00"
}
```

Betfair Exchange's back price on Zverev (1.07) and onexbet's price on Lehecka (16.0) together imply just **99.71%**, not 100% — a real, if thin (0.29%), cross-book arbitrage: staking 93.73% of your bankroll on Zverev at 1.07 and 6.27% on Lehecka at 16.0 returns the exact same amount (100.29% of stake) whichever way the match goes.

#### Worked example — a real value flag (live data, 2026-07-07)

```json
{
  "event": { "sport_key": "baseball_kbo", "sport_title": "KBO", "home_team": "Hanwha Eagles", "away_team": "NC Dinos" },
  "market": "h2h",
  "best_odds": {
    "Hanwha Eagles": { "book": "lowvig", "odds": 2.15, "point": null },
    "NC Dinos": { "book": "onexbet", "odds": 1.85, "point": null }
  },
  "overround": 1.005656819610308,
  "arb_pct": null,
  "stake_split": null,
  "value_flags": [
    { "book": "onexbet", "outcome": "NC Dinos", "edge_pct": 0.909090909090926, "fair_prob": 0.5454545454545455 }
  ],
  "books_compared": ["betclic_fr", "betmgm", "betonlineag", "betrivers", "betsson", "bovada", "coolbet", "draftkings", "everygame", "fanduel", "leovegas_se", "lowvig", "mybookieag", "nordicbet", "onexbet", "pinnacle", "tipico_de", "unibet_fr", "unibet_se", "williamhill"],
  "note": null
}
```

Pinnacle's own price de-vigs to a 54.55% fair probability for NC Dinos; onexbet quotes 1.85 on that same outcome, which is a 0.91% edge over fair — flagged since `min_edge_pct` was `0.1` for this run. No arbitrage here (`overround` is `1.0057`, just above 1.0), but there IS a value edge on one specific book.

### Output schema

One row per `(event, market, line)` — a "line" is the market's own distinct point value(s) (e.g. `-6.5`/`+6.5` for one spread, `155.5` for one total; h2h has no line at all). See `.actor/dataset_schema.json` for the full JSON Schema.

| Field | Type | Notes |
|---|---|---|
| `event` | object | `id`, `sport_key`, `sport_title`, `commence_time` (ISO 8601 UTC), `home_team`, `away_team` |
| `market` | string | `"h2h"`, `"spreads"`, or `"totals"` — always one of the markets you actually requested (see "Known limitations" re: exchange `h2h_lay`) |
| `point` | number or null | The spread/total line magnitude (e.g. `6.5`, `155.5`). `null` for h2h |
| `devig_method` / `sharp_book` | string | Which method/book this run used (uniform across every row) |
| `best_odds` | object | Keyed by outcome name → `{book, odds, point}`: the single best (highest) decimal odds for that outcome across every book compared, and which book offered it. Populated for every outcome that had at least one valid quote, even if others didn't |
| `overround` | number or null | Sum of best-odds implied probabilities. `> 1.0` = normal (no cross-book arb); `< 1.0` = an arbitrage exists. `null` only when not every outcome had any priced quote |
| `arb_pct` | number or null | `(1/overround - 1) x 100`. Only non-null when a real arbitrage exists (`overround < 1.0`) — this field means "an arbitrage was detected", never a negative number for a normal vigged book |
| `stake_split` | object or null | Only present alongside `arb_pct`. Fraction of total stake per outcome (at its `best_odds` book) for an equal guaranteed return. Sums to 1.0 |
| `value_flags` | array | `{book, outcome, edge_pct, fair_prob}` for every (book, outcome) pair clearing `min_edge_pct` versus `sharp_book`'s de-vigged fair probability. Empty (not missing) when `sharp_book` didn't cover this line, or nothing cleared the bar |
| `books_compared` | array of strings | Every bookmaker key that offered this line — the full comparison set, for transparency |
| `note` | string or null | Non-null whenever something was skipped/partial for this row — explains exactly why |
| `fetched_at` | string | When this run fetched this row |

#### How arbitrage and value are computed, and why a row is never dropped

- **`overround`/`arb_pct`/`stake_split`** require every outcome in the (market, line) group to have at least one validly-priced book — a guaranteed-return arbitrage needs a price for every possible outcome, not just some. If any outcome is completely unpriced across every compared book, these three fields are `null` and `note` explains it, but `best_odds` still reports whatever outcomes WERE priced.
- **`value_flags`** is gated independently and more narrowly: it only needs, per (book, outcome) pair, that BOTH that book's own price for that outcome AND `sharp_book`'s de-vigged fair probability for that outcome exist. A book with one suspended outcome still gets flagged on its other, still-priced outcome(s) — value analysis doesn't wait for full best-odds coverage.
- **`sharp_book` is never special-cased out of its own flagging pass.** De-vigging a book's own odds and comparing its own price back against its own resulting fair probability always produces a uniformly *negative* apparent "edge" for every one of its own outcomes (removing a book's margin against itself can't create positive edge for itself) — proven in this actor's test suite across all three de-vig methods, not just assumed.
- **Different lines are never merged.** Confirmed live for actor #8 that a bookmaker's own `spreads`/`totals` market object is already exactly ONE line (e.g. `-6.5`/`+6.5`, never mixed with `-7`/`+7`); this actor groups every bookmaker's market objects by `(market_key, point_signature)` so that comparing "best odds" across books is always apples-to-apples, never accidentally pairing two different point spreads as if they were the same bet.

### Pricing (pay-per-event)

| Event | Price | When it's charged |
|---|---|---|
| `event-row` | $0.003 | Once per `(event, market, line)` row returned |
| `opportunity` | $0.03 | **Premium** — once per row that ACTUALLY contains an arbitrage and/or ≥1 value flag. Never charged for a "clean" row with neither |

Plus Apify's own `apify-actor-start` synthetic event (first 5 seconds of compute free, platform-managed). **Your own Odds API quota is separate** — a free-tier key is capped at 500 requests/month, and `markets`/`regions` multiply how many of those a single run costs.

**Example:** 200 event-rows returned, ~15 of which actually contain an opportunity (a realistic order of magnitude — most rows carry neither by construction): `200 x $0.003 = $0.60`, plus `15 x $0.03 = $0.45`, **total $1.05**.

If a run's cost would exceed your `Max total charge USD`, the actor stops delivering further rows at exactly that point — every delivered row was paid for, nothing paid-for is ever dropped, no crash. The `opportunity` premium is charged *before* a row is pushed (not after): if its budget alone runs out, that row is still delivered as a normal `event-row` with its arbitrage/value content stripped out and a `note` explaining why, rather than shipping premium content for free.

### Quota notes (The Odds API, your own key)

- `GET /v4/sports` (used internally to validate `sport`/`leagues`) is **free** — confirmed live.
- Each odds fetch costs **`(number of markets requested) × (number of regions requested)`** of your quota, regardless of `books` filtering. Requesting all three markets costs 3× a single-market request.
- Each additional `leagues` entry is a separate fetch with its own cost.
- Auto-adding `sharp_book` to a non-empty `books` filter does **not** add to this cost (confirmed live for actor #8: `bookmakers` filtering is free).
- Free-tier keys get 500 requests/month total (shared across every tool using that key).

### Error messages

- **Missing/invalid `odds_api_key`, `sport`, `markets`, `devig_method`, `sharp_book`, `min_edge_pct`, `max_events`:** speaking `ActorInputError` stating the exact allowed values/range — exit 1, **no upstream call made at all**.
- **Unknown `sport`/`leagues` value:** *"Unknown sport\_key(s) \[...] -- not present in The Odds API's live /v4/sports catalog..."* with example valid keys — exit 1. This check itself is free.
- **Invalid/expired `odds_api_key` (caught live):** *"The Odds API rejected this run: ... API key is not valid... (error\_code=INVALID\_KEY)"* — exit 1.
- **A requested sport\_key doesn't support the requested markets** (e.g. an outrights-only league with `markets: ["h2h"]`): logged and that sport\_key is skipped (`INVALID_MARKET_COMBO`, free); other requested sport\_keys still run.
- **0 rows with otherwise valid input:** not an error — the run succeeds, but the log has an explicit `WARNING: 0 results produced despite valid input (...)` line and the run's status message says so.

### How it works

1. Validate structural input — fails fast with **no upstream call** if anything is wrong.
2. `GET /v4/sports` (free) validates your API key live AND every `sport`/`leagues` value against the live catalog.
3. `GET /v4/sports/{sport}/odds` once per requested sport\_key (fair per-key share of `max_events`), with `sharp_book` auto-added to a non-empty `books` filter.
4. Group every (bookmaker, market) entry per event by `(market_key, line)` — filtering out any market key you didn't request (see "Known limitations").
5. For each group: find best odds per outcome, compute overround/arbitrage/stake-split (or document why not), then de-vig `sharp_book`'s own odds and flag every (book, outcome) pair clearing `min_edge_pct`.
6. Charge-and-deliver: `event-row` for every row, plus `opportunity` for rows that actually found something — whatever is charged is delivered, and vice versa.
7. 0 rows with valid input logs an explicit warning instead of looking like a normal empty run.

### Known limitations (documented, not hidden)

- **Only "main" lines are fetched**, not alternate spreads/totals — same MVP scope as actor #8.
- **Exchange bookmakers can return a companion market you didn't ask for.** Confirmed live (2026-07-07): Betfair Exchange (`betfair_ex_eu`) returns an `h2h_lay` market object alongside `h2h` even when only `markets: ["h2h"]` was requested (their back/lay quotes are two separate market objects upstream). This actor filters output to ONLY the market keys you explicitly requested — `h2h_lay` (and any other unrequested companion market) is silently dropped from output, never billed.
- **`sharp_book` coverage varies by region** — see "Getting real value signals" above. An absent sharp book is reported via `note`, never guessed or substituted.
- **`max_events` caps events, not rows.** A run can return far more `event-row`s (and cost far more) than `max_events` suggests, since each event fans out into one row per (market × distinct line).
- **A thin/rare arbitrage can vanish before you can act on it** — see the disclaimer at the top of this README. `arb_pct`/`stake_split` describe the moment this row was fetched, not a standing offer.
- **Shin de-vig falls back to multiplicative (logged) for a book with ~zero/negative overround** — a mathematical edge case, not a "doesn't work for 3-way markets" limitation.
- **Your Odds API quota is shared across every tool using that key** — this actor logs it per-call but doesn't track cross-run cumulative usage.

### Data source

[The Odds API](https://the-odds-api.com/) (`api.the-odds-api.com/v4`) — bring your own key, zero scraping, zero anti-bot risk. This actor never ships with a shared/pooled key.

# Actor input Schema

## `odds_api_key` (type: `string`):

Your OWN The Odds API key (https://the-odds-api.com/ -- free tier: 500 requests/month). This actor is bring-your-own-key: it never ships with a shared/pooled key, and your key is never logged, stored in output, or shared. Get one at https://the-odds-api.com/#get-access.

## `sport` (type: `string`):

The Odds API sport\_key to fetch, e.g. "basketball\_nba", "soccer\_epl", "americanfootball\_nfl". Use the special value "upcoming" for the next games across ALL sports when you don't know a specific league key. Validated against The Odds API's live /v4/sports catalog at run time (a free call) -- an unknown key fails the run immediately with a list of valid examples. Call https://api.the-odds-api.com/v4/sports?apiKey=YOUR\_KEY to see the full current list.

## `leagues` (type: `array`):

Optional list of ADDITIONAL sport\_key values to fetch in the SAME run alongside 'sport' (e.g. sport="basketball\_nba", leagues=\["basketball\_wnba"] fetches both). Each additional key costs its own separate Odds API quota and its own fair share of 'max\_events'. Leave empty to fetch only 'sport'.

## `books` (type: `array`):

Optional list of The Odds API bookmaker keys to restrict results to, e.g. \["draftkings", "fanduel", "pinnacle"]. Maps directly to the Odds API 'bookmakers' param -- does NOT change your quota cost. If non-empty and 'sharp\_book' isn't in this list, it is added automatically (free) so the value-signal anchor is always fetched. Leave empty to get every bookmaker available in the requested 'regions' instead (recommended for arbitrage -- more books compared = more chance of a real cross-book gap).

## `regions` (type: `array`):

Odds API region codes controlling which bookmakers are eligible, e.g. "us", "uk", "eu", "au". Ignored for a book already named explicitly in 'books', but still required by the upstream API in general -- defaults to \["us"]. NOTE: Pinnacle (the default 'sharp\_book') is not always covered by region "us" -- add "eu" (or list "pinnacle" explicitly in 'books') if you want reliable value\_flags. Each additional region multiplies quota cost (cost = markets requested x regions requested).

## `markets` (type: `array`):

Which market type(s) to fetch and analyze: "h2h" (moneyline/1x2), "spreads" (point spread), "totals" (over/under). Each requested market multiplies quota cost (cost = markets requested x regions requested) -- fetching all three in one call is 3x the cost of just "h2h".

## `min_edge_pct` (type: `number`):

Minimum edge (percentage points) a book's own quoted odds must clear versus 'sharp\_book's de-vigged fair probability before it's reported in 'value\_flags'. edge\_pct = (book\_odds x fair\_prob - 1) x 100. Must be between 0 and 100.

## `devig_method` (type: `string`):

How to strip 'sharp\_book's own margin (vig) to compute the fair probability used as the value-bet anchor. "multiplicative" (default): normalize raw implied probabilities to sum to 1. "power": solves for a shared exponent -- shrinks longshot probabilities more aggressively. "shin": Shin's (1992) insider-trading model -- generally considered the most theoretically grounded for sports betting; falls back to multiplicative (logged) for the rare book with ~zero/negative overround.

## `sharp_book` (type: `string`):

The Odds API bookmaker key treated as the fair-value anchor for the value-bet signal, e.g. "pinnacle" (default -- the traditional industry sharp reference). If this book doesn't cover a given event/market/line, 'value\_flags' is left empty for that row with an explicit 'note' -- never silently wrong or guessed.

## `max_events` (type: `integer`):

Maximum number of DISTINCT events to include, fair-shared across every requested sport\_key ('sport' + 'leagues') so one league can't consume the whole budget. NOTE: this caps events, not output rows -- each included event still produces one output row per (market x distinct line) it has, so total billed 'event-row's is usually a multiple of this number. Must be between 1 and 200.

## Actor input object example

```json
{
  "sport": "basketball_nba",
  "leagues": [],
  "books": [],
  "regions": [
    "us"
  ],
  "markets": [
    "h2h"
  ],
  "min_edge_pct": 2,
  "devig_method": "multiplicative",
  "sharp_book": "pinnacle",
  "max_events": 25
}
```

# Actor output Schema

## `event_rows` (type: `string`):

One row per (event, market, line): best cross-book odds, arbitrage detection with stake-split, and sharp-book value flags. Stored in the run's default dataset.

# 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 = {
    "sport": "upcoming",
    "sharp_book": "pinnacle"
};

// Run the Actor and wait for it to finish
const run = await client.actor("westerly_breaker/odds-arbitrage-value-signal").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 = {
    "sport": "upcoming",
    "sharp_book": "pinnacle",
}

# Run the Actor and wait for it to finish
run = client.actor("westerly_breaker/odds-arbitrage-value-signal").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "sport": "upcoming",
  "sharp_book": "pinnacle"
}' |
apify call westerly_breaker/odds-arbitrage-value-signal --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=westerly_breaker/odds-arbitrage-value-signal",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Odds Arbitrage & Value Signal API",
        "description": "Cross-book best-price comparison via The Odds API (bring your own key): detect guaranteed-profit arbitrage (negative overround) with a stake-split calculator, and flag value bets against a de-vigged sharp-book (Pinnacle) fair price.",
        "version": "0.1",
        "x-build-id": "Da8xUWP3elTz4h0Jv"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/westerly_breaker~odds-arbitrage-value-signal/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-westerly_breaker-odds-arbitrage-value-signal",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/westerly_breaker~odds-arbitrage-value-signal/runs": {
            "post": {
                "operationId": "runs-sync-westerly_breaker-odds-arbitrage-value-signal",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/westerly_breaker~odds-arbitrage-value-signal/run-sync": {
            "post": {
                "operationId": "run-sync-westerly_breaker-odds-arbitrage-value-signal",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "odds_api_key",
                    "sport"
                ],
                "properties": {
                    "odds_api_key": {
                        "title": "The Odds API key (bring your own)",
                        "type": "string",
                        "description": "Your OWN The Odds API key (https://the-odds-api.com/ -- free tier: 500 requests/month). This actor is bring-your-own-key: it never ships with a shared/pooled key, and your key is never logged, stored in output, or shared. Get one at https://the-odds-api.com/#get-access."
                    },
                    "sport": {
                        "title": "Sport / league key",
                        "type": "string",
                        "description": "The Odds API sport_key to fetch, e.g. \"basketball_nba\", \"soccer_epl\", \"americanfootball_nfl\". Use the special value \"upcoming\" for the next games across ALL sports when you don't know a specific league key. Validated against The Odds API's live /v4/sports catalog at run time (a free call) -- an unknown key fails the run immediately with a list of valid examples. Call https://api.the-odds-api.com/v4/sports?apiKey=YOUR_KEY to see the full current list."
                    },
                    "leagues": {
                        "title": "Additional leagues",
                        "type": "array",
                        "description": "Optional list of ADDITIONAL sport_key values to fetch in the SAME run alongside 'sport' (e.g. sport=\"basketball_nba\", leagues=[\"basketball_wnba\"] fetches both). Each additional key costs its own separate Odds API quota and its own fair share of 'max_events'. Leave empty to fetch only 'sport'.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "books": {
                        "title": "Bookmakers filter",
                        "type": "array",
                        "description": "Optional list of The Odds API bookmaker keys to restrict results to, e.g. [\"draftkings\", \"fanduel\", \"pinnacle\"]. Maps directly to the Odds API 'bookmakers' param -- does NOT change your quota cost. If non-empty and 'sharp_book' isn't in this list, it is added automatically (free) so the value-signal anchor is always fetched. Leave empty to get every bookmaker available in the requested 'regions' instead (recommended for arbitrage -- more books compared = more chance of a real cross-book gap).",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "regions": {
                        "title": "Regions",
                        "type": "array",
                        "description": "Odds API region codes controlling which bookmakers are eligible, e.g. \"us\", \"uk\", \"eu\", \"au\". Ignored for a book already named explicitly in 'books', but still required by the upstream API in general -- defaults to [\"us\"]. NOTE: Pinnacle (the default 'sharp_book') is not always covered by region \"us\" -- add \"eu\" (or list \"pinnacle\" explicitly in 'books') if you want reliable value_flags. Each additional region multiplies quota cost (cost = markets requested x regions requested).",
                        "default": [
                            "us"
                        ],
                        "items": {
                            "type": "string"
                        }
                    },
                    "markets": {
                        "title": "Markets",
                        "type": "array",
                        "description": "Which market type(s) to fetch and analyze: \"h2h\" (moneyline/1x2), \"spreads\" (point spread), \"totals\" (over/under). Each requested market multiplies quota cost (cost = markets requested x regions requested) -- fetching all three in one call is 3x the cost of just \"h2h\".",
                        "items": {
                            "type": "string",
                            "enum": [
                                "h2h",
                                "spreads",
                                "totals"
                            ],
                            "enumTitles": [
                                "Moneyline / 1x2 (h2h)",
                                "Point spread",
                                "Totals (over/under)"
                            ]
                        },
                        "default": [
                            "h2h"
                        ]
                    },
                    "min_edge_pct": {
                        "title": "Minimum edge % for a value flag",
                        "minimum": 0,
                        "maximum": 100,
                        "type": "number",
                        "description": "Minimum edge (percentage points) a book's own quoted odds must clear versus 'sharp_book's de-vigged fair probability before it's reported in 'value_flags'. edge_pct = (book_odds x fair_prob - 1) x 100. Must be between 0 and 100.",
                        "default": 2
                    },
                    "devig_method": {
                        "title": "De-vig method (for the sharp-book fair price)",
                        "enum": [
                            "multiplicative",
                            "power",
                            "shin"
                        ],
                        "type": "string",
                        "description": "How to strip 'sharp_book's own margin (vig) to compute the fair probability used as the value-bet anchor. \"multiplicative\" (default): normalize raw implied probabilities to sum to 1. \"power\": solves for a shared exponent -- shrinks longshot probabilities more aggressively. \"shin\": Shin's (1992) insider-trading model -- generally considered the most theoretically grounded for sports betting; falls back to multiplicative (logged) for the rare book with ~zero/negative overround.",
                        "default": "multiplicative"
                    },
                    "sharp_book": {
                        "title": "Sharp book (value anchor)",
                        "type": "string",
                        "description": "The Odds API bookmaker key treated as the fair-value anchor for the value-bet signal, e.g. \"pinnacle\" (default -- the traditional industry sharp reference). If this book doesn't cover a given event/market/line, 'value_flags' is left empty for that row with an explicit 'note' -- never silently wrong or guessed.",
                        "default": "pinnacle"
                    },
                    "max_events": {
                        "title": "Max events",
                        "minimum": 1,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Maximum number of DISTINCT events to include, fair-shared across every requested sport_key ('sport' + 'leagues') so one league can't consume the whole budget. NOTE: this caps events, not output rows -- each included event still produces one output row per (market x distinct line) it has, so total billed 'event-row's is usually a multiple of this number. Must be between 1 and 200.",
                        "default": 25
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
