# Polymarket Arbitrage Finder: Dutch-Book Scanner (`mrdoe/polymarket-arbitrage-finder`) Actor

Scans live Polymarket order books for dutch-book arbitrage: outcomes on the same market whose combined best-ask cost is under $1, so buying every outcome locks in a profit before fees regardless of which one resolves true. No login, no API key.

- **URL**: https://apify.com/mrdoe/polymarket-arbitrage-finder.md
- **Developed by:** [MrDoe](https://apify.com/mrdoe) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

![Polymarket Arbitrage Finder: Dutch-Book Scanner - Polymarket arbitrage finder showing a market's outcome legs summing to under $1 with profit percent and max size](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/polymarket-arbitrage-finder--hero.png)

### What does Polymarket Arbitrage Finder: Dutch-Book Scanner do?

Polymarket Arbitrage Finder scans live order books across Polymarket's highest-volume active markets. Every market's outcomes always pay out exactly $1 to whichever one resolves true and $0 to the rest, so if you can buy every outcome's best ask for a combined total under $1, you have locked in a profit before fees no matter what happens. This Actor fetches the real best ask and size for every outcome (not the last-trade price, which is not something you can actually transact at), computes the combined cost and the profit percent, and reports the max number of complete sets the current book supports and the total guaranteed profit at that size. It works on binary Yes/No markets and multi-candidate markets alike.

### Why use Polymarket Arbitrage Finder: Dutch-Book Scanner?

- Uses the real order book (best ask + size), not the last-trade price shown on Polymarket's site - a price you cannot actually transact at is not a real opportunity.
- Generalizes past Yes/No: a multi-candidate market (e.g. an election with 6 runners) is evaluated the same way, summing every outcome's best ask.
- Reports the max size the opportunity actually supports, not just that one exists - a $5-deep edge and a $5,000-deep edge look the same in a last-trade-only view.
- Honest about what it can't see: Polygon gas and execution slippage are not deducted, and this is stated on every row rather than implied away.
- No API key or login - Polymarket's Gamma and CLOB APIs are public. Billed only for opportunities returned.

### What makes this different

Most odds/price tools show you what a market last traded at, which tells you nothing about what you could actually buy right now. This Actor reads the order book itself for every outcome of a market in one pass, so the number it reports - profitPercent, and the size behind it - is the one that determines whether an opportunity is actually worth acting on, not just interesting to look at.

### How it works

![Polymarket Arbitrage Finder: Dutch-Book Scanner workflow: your input, collection, output](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/polymarket-arbitrage-finder--how-it-works.png)

1. **Your input** — the Actor pulls the highest-volume active markets from Polymarket's public Gamma API (optionally filtered to one category).
2. **The Actor collects it** — for every outcome it fetches the live order book and takes the best (lowest) ask and the size available there.
3. **Your output** — when the outcomes' combined best-ask cost is under $1 by at least your minimum profit %, it reports the opportunity with every leg and the max size.

### What data can you extract?

The dataset has 16 fields per row:

| Field | Type | Description |
|---|---|---|
| `type` | string | Always "arbitrage". |
| `question` | string | The market's question text. |
| `url` | string | Best-effort link to the market on polymarket.com. |
| `endDate` | string | The market's resolution date. |
| `volume` | number | The market's total trading volume. |
| `liquidity` | number | The market's current liquidity. |
| `outcomeCount` | number | How many outcomes the market has (2 for Yes/No, more for multi-candidate). |
| `legs` | array | Every outcome's label, best ask price, available size and bookAgeSeconds. |
| `totalCostPerSet` | number | Combined best-ask cost of buying one of every outcome. |
| `profitPerSetUnits` | number | 1 - totalCostPerSet: the guaranteed payout gap per complete set. |
| `profitPercent` | number | profitPerSetUnits as a return on totalCostPerSet. |
| `maxCompleteSets` | number | The largest number of complete sets the current book supports. |
| `maxGuaranteedProfitUnits` | number | maxCompleteSets \* profitPerSetUnits: the total profit at that size. |
| `note` | string | Restates that this is before gas/slippage and the book can move. |
| `demo` | boolean | True when the built-in sample was used. |
| `oldestLegBookAgeSeconds` | number | The largest per-leg bookAgeSeconds (how long since the least-recently-traded leg last changed); high values flag an illiquid market, not stale data. |

### How to use Polymarket Arbitrage Finder: Dutch-Book Scanner

![Polymarket Arbitrage Finder: Dutch-Book Scanner input form](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/polymarket-arbitrage-finder--input.png)

1. Open the Actor and go to the **Input** tab.
2. Leave every field at its default to scan the 200 highest-volume active markets across all categories for at least 1% profit. Set **Category tag** to focus on one vertical (e.g. "politics", "sports", "crypto"), or raise **Minimum profit %** to see only larger edges. With no live markets reachable, it runs on a small built-in demo so you can see the output shape.
3. Optionally set filters and a **Max results** limit.
4. Click **Start**. A default run finishes in under a minute.
5. Open the **Output** tab, then download the dataset or connect it to your tools.

### Input Parameters

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `tagSlug` | string | No | `""` | Only scan markets under one Polymarket category, e.g. "politics", "sports", "crypto". Leave empty to scan the highest-volume markets across every category. |
| `maxMarketsToScan` | integer | No | `200` | How many active markets (highest volume first) to fetch order books for. Set 0 for as many as the API returns. |
| `minProfitPercent` | number | No | `1` | Only report a market whose best-ask outcomes leave at least this much profit per $1 payout, before Polygon gas and execution slippage. profitPercent = (1 / totalCostPerSet - 1) \* 100. |
| `minimumValidPrice` | number | No | `0.001` | An outcome's best ask below this is treated as bad data and excluded. |
| `maximumValidPrice` | number | No | `0.999` | An outcome's best ask above this is treated as bad data and excluded. |
| `maxResults` | integer | No | `20` | Maximum opportunities returned, highest profit % first. Set 0 to return everything found. |

### Output Data

![Polymarket Arbitrage Finder: Dutch-Book Scanner dataset table](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/polymarket-arbitrage-finder--output.png)

`legs` lists every outcome with its label, best ask price and the size available at that price. `totalCostPerSet` is the sum of every leg's best ask - the cost to buy one complete set (guaranteed to be paid exactly $1 back). `profitPerSetUnits` is `1 - totalCostPerSet`; `profitPercent` is the same profit expressed as a return on capital deployed. `maxCompleteSets` is the largest number of complete sets the current book supports (capped by the thinnest leg's size); `maxGuaranteedProfitUnits` is `maxCompleteSets * profitPerSetUnits`. `note` restates that this is before Polygon gas and execution slippage, and that the book can move between the snapshot and your fill. `bookAgeSeconds` on each leg (and `oldestLegBookAgeSeconds` on the row) is how long since that outcome last traded, not data staleness - every ask here was just fetched live from the order book, so an old value means a quiet, illiquid market, not a stale price.

![Polymarket Arbitrage Finder: Dutch-Book Scanner field map of one record](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/polymarket-arbitrage-finder--fields.png)

A real dataset item:

```json
{
  "type": "arbitrage",
  "question": "Will Team Alpha win the championship?",
  "url": "https://polymarket.com/event/demo-team-alpha-championship",
  "endDate": "2030-01-01T00:00:00Z",
  "volume": 500000,
  "liquidity": 20000,
  "outcomeCount": 2,
  "legs": [
    {
      "outcome": "Yes",
      "askPrice": 0.46,
      "availableShares": 300,
      "bookAgeSeconds": 0
    },
    {
      "outcome": "No",
      "askPrice": 0.48,
      "availableShares": 150,
      "bookAgeSeconds": 0
    }
  ],
  "totalCostPerSet": 0.94,
  "profitPerSetUnits": 0.06,
  "profitPercent": 6.383,
  "maxCompleteSets": 150,
  "maxGuaranteedProfitUnits": 9,
  "oldestLegBookAgeSeconds": 0,
  "note": "Before Polygon gas and execution slippage; the book can move between this snapshot and your fill. bookAgeSeconds is how long since that out…",
  "demo": true
}
```

### Usage Examples

#### Scan everything, default 1% threshold

```json
{
  "maxMarketsToScan": 200,
  "minProfitPercent": 1,
  "maxResults": 20
}
```

#### Politics markets only

```json
{
  "tagSlug": "politics",
  "maxResults": 20
}
```

#### Sports prediction markets only

```json
{
  "tagSlug": "sports",
  "maxResults": 20
}
```

#### Crypto markets only

```json
{
  "tagSlug": "crypto",
  "maxResults": 20
}
```

#### Larger edges only (5%+)

```json
{
  "minProfitPercent": 5,
  "maxResults": 0
}
```

### Tips for Best Results

- profitPercent is a return on capital, not an annualized rate - a market can resolve in a day or a year, and this Actor does not estimate time-to-resolution.
- A high bookAgeSeconds does not mean the price is stale - the ask was fetched live - it means that outcome has not traded in a while, which usually also means less real depth than the size shown suggests.
- Check maxGuaranteedProfitUnits, not just profitPercent - a huge percent on a $2 max size is not worth acting on after gas.
- Thin, low-volume markets show wider (and less real) apparent edges because their books are shallow - check liquidity and volume alongside profitPercent.
- Run this on a schedule (every few minutes) rather than once, since opportunities are transient by nature.
- Default is 5 results for a fast test. Set the max to 0 to return everything available.

### Known Limitations

- Reports the best ask and size at one snapshot; by the time you place real orders, the book may have moved or the size may be gone.
- Polygon gas cost and any execution slippage from partially filling deeper into the book are not deducted from profitPercent.
- Only markets Polymarket's Gamma API lists as active and not closed, with a live order book on every outcome, are evaluated - a market missing a book for any outcome is skipped, not guessed at.
- This is market analysis, not an execution service: it does not place orders, and does not require or store a wallet or API key.
- Polymarket's own matching (fee-free limit orders as of writing) can change; this Actor does not model taker fees if Polymarket introduces them.

### Integrations

Run it from the Apify API, on a schedule, or from a webhook. Send results straight to Google Sheets, Make, Zapier, Slack or your own database with Apify's built-in integrations.

### Export Formats

Download the dataset as JSON, CSV, Excel, XML, HTML table or RSS from the **Output** tab or the API.

### Frequently Asked Questions

#### What is a dutch-book / arbitrage opportunity here?

A prediction market always pays exactly $1 to whichever outcome resolves true. If you can buy the best ask of every outcome for a combined total under $1, you lock in the $1 - total gap as profit no matter which one wins, before fees.

#### Does this use the price shown on Polymarket's website?

No. That is usually the last-trade price, which you cannot necessarily transact at. This Actor reads the live order book and uses the actual best ask you could buy at right now.

#### Does it work on markets with more than 2 outcomes?

Yes. Multi-candidate markets are evaluated the same way: summing every outcome's best ask against $1.

#### Is the profit really guaranteed?

Before Polygon gas and execution slippage, and only if you can fill every leg at the reported price and size before the book moves. This Actor states that caveat on every row rather than implying a guarantee.

#### Does this place trades for me?

No, it only reports the math. Trading on Polymarket is up to you, on your own wallet.

#### Am I charged for markets with no opportunity?

No, only for opportunities returned.

#### Do I need an account or login?

No. Polymarket's Gamma and CLOB APIs are public; no wallet, account or API key is used or required.

#### Am I charged for failed runs or empty results?

You are only charged for results that are actually written to the dataset.

#### Can I run it on a schedule?

Yes. Create a Task with your input and add a schedule in Apify Console. With monitor mode on, each scheduled run returns only what changed since the previous one.

### Support

Questions or a missing field? Open an issue from the **Issues** tab on this Actor's page and it will be looked at.

### Legal / Responsible Use

This Actor reads Polymarket's public market and order-book APIs. You are responsible for how you use it and for any trading you do. It is not financial advice or an execution service, and is not affiliated with Polymarket.

# Actor input Schema

## `tagSlug` (type: `string`):

Only scan markets under one Polymarket category, e.g. "politics", "sports", "crypto". Leave empty to scan the highest-volume markets across every category.

## `maxMarketsToScan` (type: `integer`):

How many active markets (highest volume first) to fetch order books for. Set 0 for as many as the API returns.

## `minProfitPercent` (type: `number`):

Only report a market whose best-ask outcomes leave at least this much profit per $1 payout, before Polygon gas and execution slippage. profitPercent = (1 / totalCostPerSet - 1) \* 100.

## `minimumValidPrice` (type: `number`):

An outcome's best ask below this is treated as bad data and excluded.

## `maximumValidPrice` (type: `number`):

An outcome's best ask above this is treated as bad data and excluded.

## `maxResults` (type: `integer`):

Maximum opportunities returned, highest profit % first. Set 0 to return everything found.

## Actor input object example

```json
{
  "tagSlug": "",
  "maxMarketsToScan": 200,
  "minProfitPercent": 1,
  "minimumValidPrice": 0.001,
  "maximumValidPrice": 0.999,
  "maxResults": 20
}
```

# Actor output Schema

## `results` (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 = {
    "maxMarketsToScan": 200,
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrdoe/polymarket-arbitrage-finder").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 = {
    "maxMarketsToScan": 200,
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("mrdoe/polymarket-arbitrage-finder").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 '{
  "maxMarketsToScan": 200,
  "maxResults": 20
}' |
apify call mrdoe/polymarket-arbitrage-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mrdoe/polymarket-arbitrage-finder"
        }
    }
}
```

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/7BXbrE33ufy3PnOKZ/builds/NYvnE96f3LlnQldUi/openapi.json
