# Action Network Arbitrage & +EV Signals (`automation-lab/action-network-sportsbook-arbitrage-signals`) Actor

Compare Action Network sportsbook odds and export best prices, implied probabilities, arbitrage margins, and candidate positive-EV signals.

- **URL**: https://apify.com/automation-lab/action-network-sportsbook-arbitrage-signals.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Sports
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.23 / 1,000 item extracteds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## Action Network Arbitrage & +EV Signals

Compare public Action Network sportsbook quotes and export source-attributed best prices, implied probabilities, cross-book arbitrage margins, and candidate positive-EV signals.

This Actor turns the Action Network scoreboard into analysis-ready game-market records. Each record compares the same moneyline, spread, or total across sportsbooks instead of returning one disconnected row per quote.

> **Important:** outputs are analytical candidates, not betting advice or guaranteed executable opportunities. Sportsbook prices can be delayed, suspended, limited, or unavailable by the time a run finishes.

### What does this Action Network Actor do?

The Actor requests public Action Network scoreboard data for selected leagues and dates, groups compatible quotes, and calculates:

- the best displayed American and decimal price for every outcome;
- raw implied probability for each quote;
- median same-book no-vig probability for each outcome;
- theoretical equal-payout arbitrage return from the best prices;
- candidate expected value at each displayed price;
- an illustrative stake allocation for equal theoretical payout;
- source URLs, API URLs, book IDs, timestamps, and game identity.

Moneylines are compared event by event. Spreads are compared only when books offer the same home/away line. Totals are compared only when books offer the same total. That prevents unlike markets from being presented as an arbitrage pair.

### Who is it for?

Use this Actor if you are a:

- **odds analyst** comparing displayed prices across books;
- **sports data engineer** building a normalized monitoring feed;
- **quantitative researcher** studying implied probability and overround;
- **content or trading-operations team** reviewing line dispersion;
- **developer** feeding Action Network data into a database, dashboard, or alerting workflow.

Choose the related raw line-movement Actor when you need one flat row per sportsbook snapshot without cross-book calculations.

### Why use this instead of a raw odds export?

A raw odds export leaves the buyer to match events, align spread or total lines, convert American odds, remove vig, and rank prices.

This Actor performs those steps in one run while retaining the underlying source-attributed quotes. You can audit every calculated signal against `quotes`, `bestPrices`, `apiUrl`, and `sourceUrl`.

The output deliberately says **candidate** arbitrage and **candidate** positive EV. It does not claim that a displayed line is still executable or that the consensus estimate is a true probability.

### Supported leagues and markets

Supported Action Network league slugs are:

- `mlb`
- `nba`
- `nfl`
- `nhl`
- `ncaab`
- `ncaaf`

Supported market types are:

| Market | Comparison rule | Outcomes |
| --- | --- | --- |
| `moneyline` | Same Action Network game | home, away, and draw when exposed |
| `spread` | Same game and exact home spread | home, away |
| `total` | Same game and exact total | over, under |

Coverage depends on the events, books, and prices currently exposed by Action Network. The Actor does not log in and does not access private account data.

### Input parameters

| Field | Type | Default | Purpose |
| --- | --- | --- | --- |
| `leagues` | string array | `['mlb']` | League scoreboards to analyze |
| `dates` | string array | current UTC date | Dates in `YYYY-MM-DD` or `YYYYMMDD` |
| `bookIds` | string array | common US book IDs | Action Network sportsbooks to compare |
| `marketTypes` | string array | all three markets | `moneyline`, `spread`, and/or `total` |
| `includeAllMarkets` | boolean | `true` | Keep all compatible comparisons, not only positive candidates |
| `minArbitragePercent` | number | `0` | Arbitrage ROI threshold used in filtered mode |
| `minPositiveEvPercent` | number | `0` | Consensus-based EV threshold used in filtered mode |
| `includeLive` | boolean | `true` | Keep games marked live or in progress |
| `includeCompleted` | boolean | `false` | Keep completed games when quotes remain exposed |
| `maxItems` | integer | `50` | Maximum saved game-market records, from 1 to 1,000 |

When `includeAllMarkets` is `false`, a market is retained if it meets either the arbitrage threshold or the positive-EV threshold.

### Getting started

1. Open the Actor in Apify Console.
2. Keep `mlb` for a small first run, or select another in-season league.
3. Choose the market types you need.
4. Leave **Include all analyzed markets** enabled to inspect calculation coverage.
5. Set a modest `maxItems`, such as `20`.
6. Start the run.
7. Open the default dataset and select the **Sportsbook signals** view.
8. Expand `bestPrices` and `quotes` to audit each calculation.
9. Schedule the Task only after confirming that the selected league and dates regularly return useful markets.

A practical first input is:

```json
{
  "leagues": ["mlb"],
  "marketTypes": ["moneyline", "spread", "total"],
  "includeAllMarkets": true,
  "maxItems": 20
}
```

### Output fields

The default dataset contains one item per compatible game-market-line comparison.

| Field | Meaning |
| --- | --- |
| `signalId` | Stable hash for league, game, market, and line |
| `league`, `gameId` | Source event identity |
| `awayTeam`, `homeTeam` | Matched teams |
| `marketType`, `line` | Compared market and shared line |
| `candidateArbitrage` | Whether the displayed best prices imply positive theoretical ROI |
| `arbitrageRoiPercent` | `100 × (1 / impliedProbabilitySum − 1)` |
| `candidatePositiveEv` | Whether a best price has positive consensus-based EV |
| `bestPositiveEvPercent` | Highest calculated EV among best outcome prices |
| `bestPrices` | Best source-attributed quote and stake percentage per outcome |
| `quotes` | All complete quotes used in the comparison |
| `sportsbookCount` | Distinct contributing book IDs |
| `sourceUrl`, `apiUrl` | Public source references |
| `observedAt` | Calculation timestamp |
| `methodology` | Concise calculation and risk note |

Nullable source fields remain `null`; the Actor does not invent missing timestamps or labels.

### Example dataset item

This trimmed example shows the emitted shape. Team names and identifiers are illustrative; actual runs contain current public source values.

```json
{
  "signalId": "a73e9f029451be120a4d",
  "league": "mlb",
  "gameId": 123456,
  "awayTeam": "Sample City Hawks",
  "homeTeam": "Example City Bears",
  "marketType": "moneyline",
  "line": null,
  "candidateArbitrage": false,
  "arbitrageRoiPercent": -1.284,
  "impliedProbabilitySum": 1.01301,
  "candidatePositiveEv": true,
  "bestPositiveEvPercent": 1.72,
  "sportsbookCount": 6,
  "bestPrices": [
    {
      "outcome": "home",
      "bookId": 15,
      "bookName": "DraftKings",
      "americanOdds": 125,
      "decimalOdds": 2.25,
      "fairProbability": 0.4521,
      "expectedValuePercent": 1.722,
      "stakePercent": 43.88
    }
  ],
  "sourceUrl": "https://www.actionnetwork.com/mlb/odds",
  "observedAt": "2026-08-19T20:15:00.000Z"
}
```

The full row contains one `bestPrices` entry per outcome and every valid underlying quote.

### How are arbitrage and positive EV calculated?

American prices are converted to decimal odds and raw implied probabilities.

For arbitrage analysis, the Actor selects the highest decimal price for each compatible outcome. It sums those implied probabilities and calculates equal-payout theoretical ROI:

```text
ROI % = 100 × (1 / sum(best implied probabilities) - 1)
```

For positive-EV analysis, each complete same-book outcome set is normalized to remove that book's displayed overround. The Actor takes the median no-vig probability by outcome across books, then evaluates each quote:

```text
candidate EV % = 100 × (fair probability × decimal odds - 1)
```

This is an explainable consensus model, not a predictive model. It does not include account-specific limits, taxes, latency, slippage, settlement differences, promotions, or sportsbook fees.

### How much does it cost to compare Action Network sportsbook markets?

The Actor uses pay-per-event pricing:

- a one-time **run started** charge of **$0.00005**;
- an **item** charge for each game-market comparison saved to the dataset;
- no separate charge for nested quotes or calculations within an item.

The BRONZE item price is **$0.002052 per saved market comparison**. The six canonical volume tiers decrease from FREE through DIAMOND.

At the BRONZE rate:

| Saved market records | Example charge |
| ---: | ---: |
| 1 | about 0.002102 USD including start |
| 20 | about 0.04109 USD including start |
| 100 | about 0.20525 USD including start |

The final amount follows the active pricing shown in Apify Console. A run that finds fewer compatible markets charges for fewer items. Platform compute charges may be handled according to your Apify plan.

### Recurring monitoring workflows

Create an Apify Task with a stable input and schedule it for the cadence your analysis needs.

Useful patterns include:

1. **Broad comparison feed** — keep `includeAllMarkets: true`, export all compatible markets, and compare snapshots in your database.
2. **Candidate-only feed** — set `includeAllMarkets: false` and retain only records meeting either configured threshold.
3. **Moneyline monitor** — request only `moneyline` to produce one compact comparison per qualifying event.
4. **Date-specific research** — pass explicit historical or future scoreboard dates when Action Network exposes them.
5. **Webhook pipeline** — send the dataset-ready run event to an automation that validates freshness before generating an alert.

Store the `signalId`, `observedAt`, best prices, and inserted timestamps if you need change detection between runs.

### Integrations and exports

The default dataset works with:

- Apify schedules and webhooks;
- CSV, JSON, Excel, XML, and RSS dataset exports;
- Google Sheets or cloud-storage integrations;
- Python or JavaScript analytics pipelines;
- databases and BI dashboards;
- alerting systems that apply their own freshness and execution checks.

For large recurring jobs, consume dataset items through the API rather than copying expanded nested arrays manually from Console.

### Run with the Apify API

Replace `YOUR_TOKEN` with your Apify API token.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~action-network-sportsbook-arbitrage-signals/runs?token=YOUR_TOKEN&waitForFinish=120" \
  -H "Content-Type: application/json" \
  -d '{"leagues":["mlb"],"marketTypes":["moneyline"],"maxItems":20}'
```

#### JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/action-network-sportsbook-arbitrage-signals').call({
  leagues: ['mlb'],
  marketTypes: ['moneyline', 'spread', 'total'],
  includeAllMarkets: true,
  maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/action-network-sportsbook-arbitrage-signals').call(
    run_input={
        'leagues': ['mlb'],
        'marketTypes': ['moneyline'],
        'includeAllMarkets': True,
        'maxItems': 20,
    }
)
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Apify MCP server in Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/action-network-sportsbook-arbitrage-signals"
```

#### Claude Desktop

Add this server object to the `mcpServers` section of Claude Desktop's MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/action-network-sportsbook-arbitrage-signals"
    }
  }
}
```

#### Cursor

Open **Settings → Tools & Integrations → MCP**, add a remote server, and use the same Apify MCP URL shown above.

#### VS Code

Add an HTTP MCP server to your workspace MCP configuration using the same URL, then select the Actor tool from the available Apify tools.

Example prompts:

- “Run the Action Network Actor for today's MLB moneylines and summarize the best displayed price per team.”
- “Compare spread and total markets, but clearly label candidate calculations and source timestamps.”
- “Create a scheduled monitoring input that keeps only non-negative arbitrage or consensus EV candidates.”

An AI agent should not turn a candidate flag into a wagering instruction without independent price, eligibility, and risk checks.

### Limits and failure behavior

- The Actor depends on an undocumented public Action Network web endpoint that may change.
- The source may expose no games or no complete multi-book quotes for a league/date.
- Book names are best-effort; unknown IDs appear as `Book <id>`.
- Prices may be stale, suspended, geo-restricted, account-specific, or unavailable at execution time.
- Spread and total comparisons require identical lines.
- A successful no-result run is possible for an off-season league or empty date.
- If every requested scoreboard call fails or returns an unrecognized shape, the run fails instead of returning a misleading empty success.
- Transient network, HTTP 429, and server errors receive bounded retries.
- There is no browser, login, proxy, or residential fallback mode.

Use smaller league/date batches when diagnosing a source change.

### Responsible use and legality

Only collect and use data you are authorized to access. Review Action Network's terms, applicable sportsbook terms, database rights, local gambling laws, and your organization's compliance obligations.

Do not use the Actor to bypass login, geographic controls, age restrictions, responsible-gambling controls, account limits, or platform security. The Actor reads public source data and does not place bets.

Calculated probabilities and returns can be wrong because of source errors, stale quotes, differing settlement rules, or model assumptions. Treat the output as research data, not financial advice, betting advice, or a promise of profit.

### Troubleshooting

#### The dataset is empty

Confirm that the league is in season and that the selected date has games with at least two complete quotes from your selected `bookIds`. Enable `includeAllMarkets` and include all three market types while diagnosing.

#### The run fails with an Action Network HTTP error

Retry later with one league and one date. If every request fails after bounded retries, inspect the run log for the status and league/date. The Actor intentionally does not hide a source-wide failure as zero results.

#### A sportsbook name appears as `Book 79`

Action Network may expose book IDs that are not in the best-effort label map. The numeric `bookId` remains the source identity. Do not infer a brand name without validating it against current source metadata.

#### A candidate arbitrage return looks unusually large

Inspect every best quote's `insertedAt`, confirm the event and line, open the source, and verify both prices directly. One stale or suspended quote can create a theoretical candidate that is not executable.

#### Why are similar spread records separate?

A home spread of `-1.5` is not the same market as `-2.0`. Separate records prevent the Actor from combining unlike outcomes.

### FAQ

#### Does the Actor place bets?

No. It only exports public source data and calculations.

#### Is candidate positive EV a prediction?

No. It is based on median same-book no-vig probabilities across currently displayed quotes. It is an explainable consensus estimate, not a trained forecasting model.

#### Are odds guaranteed to be live?

No. `observedAt` records calculation time and quote `insertedAt` values show source timestamps when available, but the Actor cannot guarantee execution-time availability.

#### Can I request one sportsbook only?

You can, but signal records require at least two compatible sportsbook rows. Use the raw Action Network line-movement Actor for single-book snapshot extraction.

#### Can I monitor line movement?

Schedule repeated runs and compare records in your own storage. This Actor does not maintain a cross-run history dataset automatically.

### Related Automation Lab Actors

- [Action Network Sports Odds Line Movement Scraper](https://apify.com/automation-lab/action-network-sports-odds-line-movement-scraper) — raw current sportsbook snapshots by game, date, and book.
- [Bovada Sportsbook Odds Scraper](https://apify.com/automation-lab/bovada-sportsbook-odds-scraper) — source-specific Bovada odds extraction.
- [Covers Sports Odds and Consensus Scraper](https://apify.com/automation-lab/covers-sports-odds-consensus-scraper) — Covers odds and betting-consensus data.
- [Polymarket Kalshi Arbitrage Monitor](https://apify.com/automation-lab/polymarket-kalshi-arbitrage-monitor) — cross-venue prediction-market matching and candidate spread calculations.

These Actors solve adjacent jobs; they do not silently contribute data to this Actor's calculations.

# Actor input Schema

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

Action Network league slugs to analyze.

## `dates` (type: `array`):

Dates in YYYY-MM-DD or YYYYMMDD format. Empty uses the current UTC date.

## `bookIds` (type: `array`):

Action Network book IDs to compare. The defaults cover common US books exposed by the source.

## `marketTypes` (type: `array`):

Market families to compare. Spread and total records are grouped only when books quote the same line.

## `minArbitragePercent` (type: `number`):

Minimum theoretical equal-payout return used when filtering to signals. Does not include execution costs.

## `minPositiveEvPercent` (type: `number`):

Minimum expected value based on median same-book no-vig probabilities when filtering to signals.

## `includeAllMarkets` (type: `boolean`):

Keep market comparisons without a positive signal. Turn off to retain only markets meeting either configured threshold.

## `includeLive` (type: `boolean`):

Include events Action Network marks live or in progress.

## `includeCompleted` (type: `boolean`):

Include completed events when quotes remain available.

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

Maximum ranked game-market comparisons saved to the dataset.

## Actor input object example

```json
{
  "leagues": [
    "mlb"
  ],
  "dates": [
    "2026-08-19"
  ],
  "bookIds": [
    "15",
    "21",
    "30",
    "68",
    "69",
    "71",
    "75",
    "76",
    "123"
  ],
  "marketTypes": [
    "moneyline",
    "spread",
    "total"
  ],
  "minArbitragePercent": 0,
  "minPositiveEvPercent": 0,
  "includeAllMarkets": true,
  "includeLive": true,
  "includeCompleted": false,
  "maxItems": 20
}
```

# Actor output Schema

## `overview` (type: `string`):

Open the ranked sportsbook signal overview in the 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 = {
    "leagues": [
        "mlb"
    ],
    "dates": [
        "2026-08-19"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/action-network-sportsbook-arbitrage-signals").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 = {
    "leagues": ["mlb"],
    "dates": ["2026-08-19"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/action-network-sportsbook-arbitrage-signals").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 '{
  "leagues": [
    "mlb"
  ],
  "dates": [
    "2026-08-19"
  ],
  "maxItems": 20
}' |
apify call automation-lab/action-network-sportsbook-arbitrage-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/action-network-sportsbook-arbitrage-signals"
        }
    }
}

```

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/8RIC5syjZhdovQEih/builds/qKLFdsxVZtSXa4A4X/openapi.json
