> ## Documentation Index
> Fetch the complete documentation index at: https://docs.predexon.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-venue arbitrage

> Find matched markets across Polymarket, Kalshi, and Limitless, and surface live spreads net of fees.

The same prediction-market question often trades at different prices on different venues. When the spread exceeds fees, it's an arb. Build a scanner that surfaces them and (optionally) executes both legs.

**You'll build**:

1. A spread scanner across pre-matched venue pairs
2. A profitability filter (net of fees and round-trip cost)
3. Two-leg execution, one venue-specific order per leg

**Endpoints used**: 2 REST + 2 Trading API. Free + Dev plan (matched pairs is gated).

***

## How matching works

Predexon maintains LLM-curated equivalence between markets across venues — same question, normalized outcome labels, similarity score. You don't have to re-discover this from scratch.

```mermaid theme={null}
flowchart LR
  PAIRS[matched-pairs<br/>~all active pairs]
  PRICES[per-venue prices<br/>poll each side]
  SPREAD[Compute spread<br/>net of fees]
  GATE{|spread| > 2%?}
  SURFACE[Alert / dashboard]
  EXECUTE[Two-leg execution<br/>per venue]

  PAIRS --> PRICES
  PRICES --> SPREAD
  SPREAD --> GATE
  GATE -- yes --> SURFACE
  GATE -- yes --> EXECUTE
  GATE -- no --> PAIRS
```

***

## Step 1 — Pull matched pairs

```python theme={null}
import os, requests, time

BASE = "https://api.predexon.com"
H = {"x-api-key": os.environ["PREDEXON_API_KEY"]}

def all_pairs(min_similarity=98):
    return requests.get(
        f"{BASE}/v2/matching-markets/pairs",
        headers=H,
        params={"min_similarity": min_similarity, "limit": 200},
    ).json()["pairs"]
```

`min_similarity=98` filters to near-exact matches. Drop to 90 for more pairs at the cost of more false positives.

***

## Step 2 — Quote each side

For each pair, get current YES-side prices on each venue. Polymarket gives prices 0–1, Kalshi gives cents 0–100 — normalize.

```python theme={null}
def polymarket_yes(token_id):
    return float(requests.get(
        f"{BASE}/v2/polymarket/market-price/{token_id}",
        headers=H,
    ).json()["price"])

def kalshi_yes(ticker):
    # Kalshi prices are in cents; divide by 100
    md = requests.get(
        f"{BASE}/v2/kalshi/markets",
        headers=H,
        params={"ticker": ticker, "limit": 1},
    ).json()["markets"][0]
    return md["yes_bid"] / 100.0

def quote(pair):
    poly_token = pair["polymarket"]["yes_token_id"]
    kalshi_tkr = pair["kalshi"]["ticker"]
    return {
        "pair": pair,
        "poly_yes": polymarket_yes(poly_token),
        "kalshi_yes": kalshi_yes(kalshi_tkr),
    }
```

***

## Step 3 — Compute spread net of fees

The naive spread is `|poly_yes - kalshi_yes|`. The real number includes round-trip fees on both sides.

```python theme={null}
POLYMARKET_FEE_BPS = 0    # Polymarket itself charges 0; partner fees if you set them
KALSHI_FEE_BPS = 0        # Kalshi maker is 0; taker varies

def net_edge(q):
    spread = abs(q["poly_yes"] - q["kalshi_yes"])
    fee_cost = (q["poly_yes"] + q["kalshi_yes"]) * (POLYMARKET_FEE_BPS + KALSHI_FEE_BPS) / 10_000
    return spread - fee_cost
```

If Polymarket YES is at 0.62 and Kalshi YES is at 0.58, the gross edge is 0.04 (4¢). Net of fees that's roughly your profit per share if you buy Kalshi and sell (short) Polymarket — but **shorting Polymarket means buying the NO side**.

The two-leg trade:

* **Buy NO on the venue trading higher** (you profit if it resolves NO at \$1)
* **Buy YES on the venue trading lower** (you profit if it resolves YES at \$1)
* Total cost: `(higher_no_price + lower_yes_price)` per pair
* Total payout: \$1 regardless of outcome
* Profit per pair: `$1 - (higher_no_price + lower_yes_price)`

```python theme={null}
def arb_profit_per_pair(q):
    # higher venue: sell YES = buy NO; lower venue: buy YES
    higher_yes = max(q["poly_yes"], q["kalshi_yes"])
    lower_yes  = min(q["poly_yes"], q["kalshi_yes"])
    cost = (1 - higher_yes) + lower_yes  # NO on higher + YES on lower
    return 1.0 - cost  # net of $1 payout at resolution
```

Any pair where `arb_profit_per_pair > $0.02` is worth a closer look.

***

## Step 4 — Surface (and optionally execute)

```python theme={null}
def scan():
    opportunities = []
    for pair in all_pairs():
        try:
            q = quote(pair)
            profit = arb_profit_per_pair(q)
            if profit > 0.02:
                opportunities.append({**q, "profit_per_pair": profit})
        except Exception as e:
            print(f"quote failed: {e}")
    return sorted(opportunities, key=lambda x: -x["profit_per_pair"])

for op in scan():
    print(
        f"${op['profit_per_pair']:.3f}/pair  "
        f"poly={op['poly_yes']:.2f} kalshi={op['kalshi_yes']:.2f}  "
        f"{op['pair']['polymarket']['question']}"
    )
```

<Warning>
  **The Trading API supports Polymarket, Predict.fun, Opinion, Limitless, and Hyperliquid for execution — not Kalshi.** Kalshi-leg execution requires [Kalshi's native API](https://trading-api.readme.io/) directly. The full Predexon-only execution path works for Polymarket ↔ Predict.fun, Polymarket ↔ Limitless, Polymarket ↔ Opinion, and any other combination of the 5 trading-supported venues.
</Warning>

For Predexon-supported pairs, place each leg with a [venue-specific Place Order](/trading-api/accounts/place-order): buy YES on the venue showing the cheapest YES, and buy NO on the venue showing the cheapest NO. Use [Get Canonical Outcome](/api-reference/canonical/outcome) to resolve a `predexon_id` to each venue's native listing (`token_id` / `market_ticker`) for the order's `market` bag.

For mixed Polymarket-Kalshi arbs, place the Polymarket leg via Predexon's [venue-specific Place Order](/trading-api/accounts/place-order), then call Kalshi's native API for the Kalshi leg — Predexon doesn't proxy Kalshi trading.

***

## Operational realities

| Risk                          | Reality                                                                  | Mitigation                                                                               |
| ----------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| Spread collapses between legs | The first leg fills, the second leg moves — you're left holding one side | Place both legs in parallel; use market orders; accept some leg-risk                     |
| Venue restrictions            | Kalshi requires KYC + US person status                                   | Run KYC'd accounts only for Kalshi-touching strategies                                   |
| Resolution timing differs     | Polymarket and Kalshi resolve the same question on different schedules   | Capital is locked until both sides resolve — model as carry cost                         |
| Both legs partial-fill        | You end up with mismatched sizes                                         | After both leg responses come back, place a corrective leg for the difference            |
| Match is wrong                | Similarity score is high but the questions actually differ               | At `min_similarity=98` this is rare but possible — verify high-conviction trades by hand |

For systematic arb, set `min_similarity=99` and add a manual review step for any new pair before letting the system trade it.

***

## Reference

* [Matched Pairs](/api-reference/matching/matched-pairs) — pre-computed pairs with similarity scores
* [Find Matching Markets](/api-reference/matching/find-matches) — programmatic pair discovery for a specific market
* [Polymarket Price](/api-reference/markets/market-price) and [Kalshi Markets](/api-reference/kalshi/list-markets) — for live quotes
* [Place Order](/trading-api/accounts/place-order) — venue-specific order placement
* [Canonical Markets](/api-reference/canonical/markets) — to see all venues holding a canonical outcome
