Skip to main content

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.

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 via the Order Router
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.

Step 1 — Pull matched pairs

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.
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.
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)
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)

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']}"
    )
The Trading API supports Polymarket, Predict.fun, Opinion, Limitless, and Hyperliquid for execution — not Kalshi. Kalshi-leg execution requires Kalshi’s native API 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.
For Predexon-supported pairs, use the Order Router — it automatically picks the cheaper venue for each leg, no venue_preference needed:
TRADE = "https://trade.predexon.com"
ACCOUNT_ID = "your-account-id"

def execute_arb_predexon_supported(pair, pairs_to_trade=10):
    """Both legs through Predexon. Router auto-picks the cheaper venue per leg."""
    # buy YES — router picks the venue with the lowest YES price
    requests.post(
        f"{TRADE}/api/accounts/{ACCOUNT_ID}/router/orders",
        headers={**H, "Content-Type": "application/json"},
        json={
            "predexonId": pair["predexon_id_yes"],
            "side": "buy", "type": "market", "size": str(pairs_to_trade),
        },
    )
    # buy NO — router picks the venue with the lowest NO price (i.e. highest YES on the other side)
    requests.post(
        f"{TRADE}/api/accounts/{ACCOUNT_ID}/router/orders",
        headers={**H, "Content-Type": "application/json"},
        json={
            "predexonId": pair["predexon_id_no"],
            "side": "buy", "type": "market", "size": str(pairs_to_trade),
        },
    )
For mixed Polymarket-Kalshi arbs, place the Polymarket leg via Predexon’s venue-specific Place Order, then call Kalshi’s native API for the Kalshi leg — Predexon doesn’t proxy Kalshi trading.

Operational realities

RiskRealityMitigation
Spread collapses between legsThe first leg fills, the second leg moves — you’re left holding one sidePlace both legs in parallel; use market orders; accept some leg-risk
Venue restrictionsKalshi requires KYC + US person statusRun KYC’d accounts only for Kalshi-touching strategies
Resolution timing differsPolymarket and Kalshi resolve the same question on different schedulesCapital is locked until both sides resolve — model as carry cost
Both legs partial-fillYou end up with mismatched sizesAfter both leg responses come back, place a corrective leg for the difference
Match is wrongSimilarity score is high but the questions actually differAt 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