# Polymarket Kalshi Arbitrage Finder & Monitor (`antndev/polymarket-kalshi-arbitrage-finder`) Actor

Finds matched markets across Polymarket and Kalshi and computes fee-adjusted, order-book-depth-aware arbitrage edges. Monitor mode emits only new or changed opportunities and can push webhook alerts.

- **URL**: https://apify.com/antndev/polymarket-kalshi-arbitrage-finder.md
- **Developed by:** [Anton König](https://apify.com/antndev) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 0.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/platform/actors/running/actors-in-store#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

## Polymarket Kalshi Arbitrage Finder & Monitor

Find **matched markets across Polymarket and Kalshi** and compute **fee-adjusted, order-book-depth-aware arbitrage edges**. Run it once for a snapshot, or on a schedule in monitor mode to get only **new / widened / gone** opportunities, optionally pushed to your **webhook**.

Built for people who actually trade prediction-market arbitrage, not for screenshots: every number is computed the way your fill would really happen.

### Why this one (what incumbents get wrong)

- **Fee-correct.** Polymarket now charges taker fees on many markets (`fee = shares x rate x p x (1-p)`, rate 0.07 crypto / 0.05 sports / 0.04 finance & politics, read per-market from the API). Kalshi charges `0.07 x C x P x (1-P)` rounded up per fill. Most scanners compare raw prices and call it "arbitrage". This actor outputs the **edge after both venues' real fees**.
- **Depth-aware.** Order books are walked with your configured stake (default $100 per leg) to get **executable** average prices, not top-of-book fantasy. Polymarket's CLOB returns asks in descending order; naive scanners read the worst price as the best. This actor sorts correctly.
- **Match honesty.** Cross-venue matching runs fuzzy scoring behind four hard gates: resolution-date window, numeric strike overlap, month-deadline consistency, opposite-direction words (`increase` vs `cut`, `above` vs `below`) and event stage (`on the ballot` vs `passes` are different events). Every row carries its `match_confidence`. Suspiciously large edges on imperfect matches are flagged `match_review_recommended`, because a 20% "arb" is almost always a wrong pairing rather than free money. Known-bad pairs can be suppressed via `excludePairIds`.

### What you get (dataset row)

```json
{
  "pair_id": "516923__KXUSAIRANAGREEMENT-27-26SEP",
  "question": "US-Iran Final Nuclear Deal by August 31, 2026?",
  "match_confidence": 89,
  "pm_url": "https://polymarket.com/market/us-iran-final-nuclear-deal-by-august-31",
  "kalshi_url": "https://kalshi.com/markets/KXUSAIRANAGREEMENT",
  "direction": "buy_kalshi_yes__buy_pm_no",
  "exec_price_a": 0.093, "exec_price_b": 0.899,
  "shares": 105.3, "cost_usd": 104.50,
  "gross_spread_pct": 0.8,
  "fee_adjusted_edge_pct": 0.73,
  "edge_usd": 0.77,
  "annualized_return_pct": 8.0,
  "days_to_resolution": 33.4,
  "change_type": "new",
  "match_review_recommended": false
}
```

Both directions are evaluated for every pair: `buy_pm_yes + buy_kalshi_no` and `buy_kalshi_yes + buy_pm_no`. If the fee-adjusted sum of executable prices is below $1.00, the difference is locked in at resolution (assuming the two markets resolve identically, which is what `match_confidence` and your review are for).

The full matched-pair table (all cross-venue pairs with confidence scores and URLs) is stored in the key-value store under `matched_pairs`.

### Monitor mode

Schedule the actor (e.g. every 10 minutes) with `monitorMode: true`:

- remembers the previous run's opportunities in the key-value store,
- emits only `new`, `widened` (>= +0.25pp), `narrowed`, `gone` rows,
- POSTs `new`/`widened` rows to your `webhookUrl` as JSON, so you can alert into Discord, Slack (via relay), or your own bot.

### Input

| Field | Default | Meaning |
|---|---|---|
| `minEdgePct` | 0 | only output rows at/above this fee-adjusted edge |
| `stakeUsd` | 100 | stake per leg used for the depth walk |
| `minMatchConfidence` | 88 | fuzzy-match threshold (the four gates always apply on top); lower it to 80 to widen the net and review pairs yourself |
| `monitorMode` | false | emit only changes vs previous run |
| `webhookUrl` | none | push alerts to this URL |
| `excludePairIds` | \[] | suppress known-bad pairs |

### Data sources

Official public read APIs only, no scraping, no login: Polymarket Gamma + CLOB, Kalshi trade-api v2. The actor is read-only and never places orders.

### Honest limitations

- An "arb" is only riskless if the two markets truly resolve identically. Read both rule pages before trading; the actor gives you both URLs and a confidence score, not legal advice.
- Executable size is limited by the thinner book; `shares` tells you the size the stake could actually fill at the quoted prices.
- Settlement timing differs between venues; capital can be locked until both resolve.

### Keywords

polymarket arbitrage, kalshi arbitrage, prediction market arbitrage, polymarket kalshi matched markets, cross-market spread scanner, prediction market scanner, polymarket api, kalshi api, arbitrage monitor, prediction market alerts

# Actor input Schema

## `minEdgePct` (type: `integer`):

Only output opportunities with at least this fee-adjusted edge in percent. Use 0 to see everything at or above break-even; use -100 to also see negative (no-arb) rows for research.

## `stakeUsd` (type: `integer`):

Order books are walked with this stake to compute REAL executable prices, not top-of-book fantasy prices.

## `minMatchConfidence` (type: `integer`):

Fuzzy-match threshold for pairing equivalent markets across venues. Higher = fewer, safer pairs. Deadline, month, direction-word and event-stage gates are always applied on top. Lower it to 80 to widen the net, but review the pairs yourself.

## `monitorMode` (type: `boolean`):

For scheduled runs: remembers previous opportunities and emits only NEW, WIDENED, NARROWED or GONE rows instead of a full snapshot.

## `webhookUrl` (type: `string`):

POSTs new/widened opportunities as JSON to this URL (Discord/Slack-compatible endpoints work with a relay, generic JSON receivers work directly).

## `maxPolymarketMarkets` (type: `integer`):

Top active binary markets by 24h volume.

## `maxKalshiMarkets` (type: `integer`):

Open Kalshi markets (multivariate parlay collections are always excluded).

## `maxBookPairs` (type: `integer`):

Order books are fetched for the top matched pairs by confidence and volume.

## `outputMatchedPairs` (type: `boolean`):

Stores the full matched-pair table (with confidence scores) in the key-value store under 'matched\_pairs'.

## `excludePairIds` (type: `array`):

pair\_id values to ignore (known-bad matches you want to suppress).

## Actor input object example

```json
{
  "minEdgePct": 0,
  "stakeUsd": 100,
  "minMatchConfidence": 88,
  "monitorMode": false,
  "maxPolymarketMarkets": 1500,
  "maxKalshiMarkets": 8000,
  "maxBookPairs": 120,
  "outputMatchedPairs": true
}
```

# 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 = {
    "minEdgePct": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("antndev/polymarket-kalshi-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 = { "minEdgePct": 0 }

# Run the Actor and wait for it to finish
run = client.actor("antndev/polymarket-kalshi-arbitrage-finder").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 '{
  "minEdgePct": 0
}' |
apify call antndev/polymarket-kalshi-arbitrage-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=antndev/polymarket-kalshi-arbitrage-finder",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/wS8kdmAhe0n2IN7CF/builds/3BSYlmuvU3ksldvIM/openapi.json
