How to Detect Arbitrage Opportunities in Prediction Markets
Learn how arbitrage detection works in prediction markets, use the Propheseer API to find cross-platform price discrepancies, and build your own Python arbitrage scanner.
Introduction
Arbitrage — buying low on one exchange and selling high on another — is one of the oldest trading strategies in finance. In prediction markets, arbitrage detection opportunities arise when the same event is priced differently across platforms like Polymarket, Kalshi, and Gemini.
For example, if Polymarket prices "Will the Fed cut rates in June?" at 42% Yes, but Kalshi prices the same question at 48% Yes, there's a 6-percentage-point spread. A trader could theoretically buy Yes on Polymarket at $0.42 and buy No on Kalshi at $0.52 (1 - 0.48), guaranteeing a profit regardless of the outcome.
In this guide, we'll cover how arbitrage detection works, how to use the Propheseer API's built-in arbitrage endpoint, and how to build your own custom scanner in Python.
What Is Prediction Market Arbitrage?
Arbitrage in prediction markets exploits price discrepancies between platforms trading on the same event. Here's the basic mechanics:
Binary Market Arbitrage
For a binary (Yes/No) market, arbitrage exists when you can buy both sides for less than $1.00 total:
Platform A: Yes = $0.42 → No = $0.58
Platform B: Yes = $0.48 → No = $0.52
Strategy: Buy Yes on A ($0.42) + Buy No on B ($0.52) = $0.94
Guaranteed payout: $1.00
Profit: $0.06 per contract (6.4% return)
The trade is profitable regardless of the outcome because one of your positions always pays out $1.00.
Why Do Price Discrepancies Exist?
Several factors create and maintain price differences between platforms:
- Different user bases — Polymarket's crypto-native traders and Kalshi's fiat traders may have different information or biases
- Liquidity differences — A market with $5M in volume will price more efficiently than one with $50K
- Timing gaps — News breaks hit platforms at slightly different times
- Fee structures — Different fees affect the effective price traders are willing to pay
- Regulatory constraints — US restrictions on Polymarket mean some traders can only access Kalshi, creating segmented markets
How Arbitrage Detection Works
Detection follows a simple three-step process:
Step 1: Match Markets Across Platforms
The hardest part is identifying that two markets on different platforms refer to the same event. "Will Bitcoin hit $150K by 2026?" on Polymarket and "Bitcoin above $150,000 on December 31?" on Kalshi are the same question, but the text differs.
Matching approaches range from simple (keyword overlap) to sophisticated (NLP semantic similarity). The Propheseer API handles this matching automatically using normalized market data.
Step 2: Compare Prices
Once matched, compare the Yes probabilities from each platform. The spread is the absolute difference:
Spread = |Platform_A_Yes - Platform_B_Yes|
A spread of 0.05 (5 percentage points) or higher is generally considered actionable after accounting for fees and execution risk.
Step 3: Calculate Expected Return
The potential return depends on the spread and the prices:
Cost = min(Yes_A, Yes_B) + min(No_A, No_B)
Profit per contract = 1.00 - Cost
Return = Profit / Cost
For example, if you buy Yes at $0.42 and No at $0.52:
Cost = $0.42 + $0.52 = $0.94
Profit = $1.00 - $0.94 = $0.06
Return = $0.06 / $0.94 = 6.4%
Using the Propheseer Arbitrage API
The easiest way to find arbitrage opportunities is the Propheseer /v1/arbitrage endpoint, which handles market matching and spread calculation automatically.
Basic Request
curl -s "https://api.propheseer.com/v1/arbitrage?min_spread=0.05" \
-H "Authorization: Bearer YOUR_API_KEY" | python3 -m json.tool
Response Format
{
"data": [
{
"question": "Will the Fed cut rates in June 2026?",
"spread": 0.062,
"potentialReturn": "6.6%",
"markets": [
{
"source": "polymarket",
"yesPrice": 0.418,
"url": "https://polymarket.com/markets?_q=Fed%20cut%20rates%20June"
},
{
"source": "kalshi",
"yesPrice": 0.48,
"url": "https://kalshi.com/events?search=Fed%20rate%20June"
}
]
}
],
"meta": { "total": 3 }
}
Filtering by Category
Focus on specific market categories:
# Only political markets
curl -s "https://api.propheseer.com/v1/arbitrage?min_spread=0.03&category=politics" \
-H "Authorization: Bearer YOUR_API_KEY"
Python Client
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.propheseer.com/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}
def find_arbitrage(min_spread=0.03, category=None):
params = {"min_spread": min_spread}
if category:
params["category"] = category
response = requests.get(f"{BASE_URL}/arbitrage", headers=headers, params=params)
response.raise_for_status()
return response.json()["data"]
# Find all opportunities with 5%+ spread
opportunities = find_arbitrage(min_spread=0.05)
for opp in opportunities:
print(f"\n{opp['question']}")
print(f" Spread: {opp['spread']:.1%} | Return: {opp['potentialReturn']}")
for m in opp["markets"]:
print(f" {m['source']:>12}: Yes @ {m['yesPrice']:.1%}")
The arbitrage endpoint requires a Pro plan or higher. See pricing for details.
Building Your Own Arbitrage Scanner
For more control — custom matching logic, alerting, or integration with trading systems — you can build a scanner on top of the /v1/markets endpoint.
Complete Python Scanner
import requests
import time
from collections import defaultdict
from difflib import SequenceMatcher
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.propheseer.com/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}
def fetch_all_markets():
"""Fetch all open markets across platforms."""
markets = []
offset = 0
limit = 200
while True:
response = requests.get(f"{BASE_URL}/markets", headers=headers, params={
"status": "open",
"limit": limit,
"offset": offset,
})
response.raise_for_status()
data = response.json()
batch = data["data"]
markets.extend(batch)
if len(batch) < limit:
break
offset += limit
return markets
def similarity(a, b):
"""Calculate text similarity between two market questions."""
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
def find_matches(markets, threshold=0.75):
"""Group markets that refer to the same event."""
by_source = defaultdict(list)
for m in markets:
by_source[m["source"]].append(m)
sources = list(by_source.keys())
matches = []
# Compare markets across different sources
for i in range(len(sources)):
for j in range(i + 1, len(sources)):
for m1 in by_source[sources[i]]:
for m2 in by_source[sources[j]]:
sim = similarity(m1["question"], m2["question"])
if sim >= threshold:
matches.append((m1, m2, sim))
return matches
def calculate_arbitrage(market_a, market_b):
"""Calculate arbitrage opportunity between two matched markets."""
yes_a = market_a["outcomes"][0]["probability"]
yes_b = market_b["outcomes"][0]["probability"]
spread = abs(yes_a - yes_b)
# Calculate the cost of buying both sides
if yes_a < yes_b:
# Buy Yes on A, No on B
cost = yes_a + (1 - yes_b)
strategy = f"Buy Yes on {market_a['source']} @ {yes_a:.1%}, No on {market_b['source']} @ {1-yes_b:.1%}"
else:
# Buy Yes on B, No on A
cost = yes_b + (1 - yes_a)
strategy = f"Buy Yes on {market_b['source']} @ {yes_b:.1%}, No on {market_a['source']} @ {1-yes_a:.1%}"
profit = 1.0 - cost
return_pct = (profit / cost) * 100 if cost > 0 else 0
return {
"question": market_a["question"],
"spread": spread,
"cost": cost,
"profit": profit,
"return_pct": return_pct,
"strategy": strategy,
"markets": [
{"source": market_a["source"], "yesPrice": yes_a, "url": market_a.get("url")},
{"source": market_b["source"], "yesPrice": yes_b, "url": market_b.get("url")},
],
}
def scan(min_spread=0.03, min_return=1.0):
"""Run a full arbitrage scan."""
print("Fetching markets...")
markets = fetch_all_markets()
print(f"Found {len(markets)} open markets")
print("Matching markets across platforms...")
matches = find_matches(markets)
print(f"Found {len(matches)} cross-platform matches")
opportunities = []
for m1, m2, sim in matches:
arb = calculate_arbitrage(m1, m2)
if arb["spread"] >= min_spread and arb["return_pct"] >= min_return:
opportunities.append(arb)
opportunities.sort(key=lambda x: x["spread"], reverse=True)
print(f"\n{'='*60}")
print(f"Found {len(opportunities)} arbitrage opportunities")
print(f"{'='*60}")
for opp in opportunities:
print(f"\n{opp['question']}")
print(f" Spread: {opp['spread']:.1%}")
print(f" Return: {opp['return_pct']:.1f}%")
print(f" Strategy: {opp['strategy']}")
for m in opp["markets"]:
print(f" {m['source']:>12}: Yes @ {m['yesPrice']:.1%}")
return opportunities
if __name__ == "__main__":
scan(min_spread=0.05, min_return=2.0)
Running Continuously
To monitor for opportunities in real-time, wrap the scanner in a loop:
import time
SCAN_INTERVAL = 60 # seconds
while True:
try:
opportunities = scan(min_spread=0.05)
if opportunities:
# Send alerts (email, Slack, Discord, etc.)
for opp in opportunities:
send_alert(opp) # Implement your notification logic
except requests.exceptions.RequestException as e:
print(f"API error: {e}")
time.sleep(SCAN_INTERVAL)
Be mindful of your rate limits when polling continuously. The Free plan allows 100 requests per day — for continuous scanning, you'll need the Pro plan (10,000/day) or higher.
Risks and Limitations
Arbitrage in prediction markets isn't risk-free. Here are the key considerations:
Execution Risk
The spread you see may disappear by the time you execute both legs of the trade. High-liquidity markets are more reliable; thin markets can move against you quickly.
Fee Impact
Platform fees eat into your profit margin:
| Platform | Fee Structure | Impact |
|---|---|---|
| Polymarket | ~2% on winning positions | Reduces payout by ~$0.02 |
| Kalshi | 3-7% maker/taker fees | Can eliminate small spreads |
| Gemini | ~1.5% trading fee | Moderate impact |
A 5% gross spread may only yield 1-2% net after fees. Always calculate net returns before executing.
Settlement Timing
Prediction markets don't settle instantly. If a market resolves in 6 months, your capital is locked for that duration. The annualized return on a 5% arbitrage with 6-month settlement is roughly 10% — decent, but not extraordinary.
Resolution Risk
Markets on different platforms may resolve differently due to different resolution sources or criteria. Read the resolution rules carefully for both sides of any trade.
Capital Requirements
You need funded accounts on multiple platforms. For Polymarket, this means USDC in a connected wallet. For Kalshi, USD in your account. Managing capital across platforms adds operational complexity.
Advanced Strategies
Multi-Leg Arbitrage
Some opportunities involve three or more platforms. With Polymarket, Kalshi, and Gemini all pricing the same event, you can find the best Yes price and the best No price across all three:
def find_multi_leg_arbitrage(matched_markets):
"""Find optimal prices across 3+ platforms."""
yes_prices = [(m["source"], m["outcomes"][0]["probability"]) for m in matched_markets]
no_prices = [(m["source"], 1 - m["outcomes"][0]["probability"]) for m in matched_markets]
best_yes = min(yes_prices, key=lambda x: x[1])
best_no = min(no_prices, key=lambda x: x[1])
total_cost = best_yes[1] + best_no[1]
if total_cost < 1.0:
profit = 1.0 - total_cost
return {
"buy_yes_on": best_yes[0],
"yes_price": best_yes[1],
"buy_no_on": best_no[0],
"no_price": best_no[1],
"profit": profit,
"return_pct": (profit / total_cost) * 100,
}
return None
Temporal Arbitrage
Monitor how spreads evolve over time. Some spreads are persistent (structural differences between platforms), while others are temporary (news-driven). Temporary spreads close faster and require quick execution.
Combining with Unusual Trade Detection
Large trades on one platform often precede price corrections. Use the unusual trades endpoint to detect whale activity that may create or close arbitrage windows:
# Check for whale activity that might signal price movement
unusual = requests.get(f"{BASE_URL}/unusual-trades",
headers=headers,
params={"min_score": 80, "limit": 10}
)
For a complete trading bot that combines arbitrage detection with alerting, see our Python trading bot tutorial.
Getting Started
- Sign up for a Pro account — the arbitrage endpoint requires Pro or higher
- Get your API key — follow our 5-minute quickstart
- Try the
/v1/arbitrageendpoint to see current opportunities - Build a custom scanner using the code above for more control
- Start small and validate your approach before scaling capital
For background on the platforms themselves, see our Polymarket vs Kalshi comparison. If you're new to prediction markets entirely, start with our complete guide.
Ready to find arbitrage opportunities? Get your Pro API key and start detecting cross-platform price discrepancies in minutes. The /v1/arbitrage endpoint does the heavy lifting — or build your own scanner with the code above.