Compare Mantic with prediction markets
Pair Mantic event forecasts with Polymarket markets, compare Mantic's probability with the market midpoint, and rank the largest gaps.
Rank where Mantic's probability differs most from Polymarket's price for the same event. Search suggests pairs and you approve each one, because the gap only means something when both resolve the same way.
The Polymarket price is the midpoint: halfway between the best bid and ask. The scripts find markets with Polymarket's public Gamma market discovery endpoint and read midpoints from its CLOB midpoint price endpoint.
Full scripts: discover_pairs.py and divergence_screen.py.
1. Find candidate pairs
discover_pairs.py lists open Polymarket markets with Gamma's GET /markets/keyset and searches Mantic forecasts for each market's title. It checks the top three search results and keeps forecasts whose latest run has type event:
questions = client.request(
"GET",
"/questions",
params={"search": title, "show_forecasts": "true"},
).json()["questions"]
return [
question
for question in questions[:limit]
if question["latest_forecast"] is not None
and question["latest_forecast"]["type"] == "event"
]Each Polymarket outcome has its own token ID, and the pairs file needs the one that matches Mantic's event. Gamma returns outcomes and clobTokenIds as JSON-encoded strings in matching order, so the script decodes and pairs them:
outcomes = json_list(market.get("outcomes"))
token_ids = json_list(market.get("clobTokenIds"))
if not token_ids:
return [] # Gamma can return null clobTokenIds
if len(outcomes) != len(token_ids):
raise ValueError(f"Market {market.get('slug')} has mismatched outcomes and token IDs")
return list(zip(outcomes, token_ids, strict=True))Run it to print each market with its outcome token IDs and Mantic candidates:
python discover_pairs.py --limit 20Polymarket example-market-slug: Will X happen by 31 December 2026?
outcome 'Yes' token_id 12345678901234567890
outcome 'No' token_id 09876543210987654321
Mantic 123: Will X happen before the end of 2026?2. Review and record pairs
Check each suggested pair against both full definitions, the Mantic forecast's and the market's:
- Mantic's event matches the market's
Yesoutcome, not its opposite; - the deadline and time zone are the same;
- cancellation, postponement and early-resolution rules agree;
- both use the same source of evidence;
- neither has already resolved or stopped trading.
Record only the pairs you approve, with the token ID of the outcome that matches Mantic's event, usually Yes:
mantic_question_id,polymarket_market_slug,polymarket_yes_token_id
123,example-market-slug,123456789012345678903. Fetch Mantic forecasts in batches
divergence_screen.py fetches all paired forecasts with GET /questions and question_ids, which takes up to 100 question_id values per request. The shared paginate() helper reads every page of each batch:
questions: dict[int, dict[str, Any]] = {}
for start in range(0, len(question_ids), 100):
batch = question_ids[start : start + 100]
params = {"question_ids": json.dumps(batch), "show_forecasts": "true"}
for question in paginate(client, "/questions", "questions", params):
questions[question["id"]] = question
return questionsGET /questions leaves out forecasts with no completed run and forecasts you can't access; the script lists those IDs in the output.
4. Compare with the market midpoint
Only event runs have a single probability, so the script skips the rest:
if question is None:
return None
forecast = question["latest_forecast"]
if forecast is None or forecast["type"] != "event":
return None
return float(forecast["probability"])Before it reads a price, it checks that the token belongs to the paired market, so a copied token ID can't silently compare the wrong market:
details = http.get(f"{GAMMA_URL}/markets/slug/{slug}")
details.raise_for_status()
if token_id not in json_list(details.json().get("clobTokenIds")):
raise ValueError(f"Token {token_id} does not belong to market {slug}")
response = http.get(f"{CLOB_URL}/midpoint", params={"token_id": token_id})
response.raise_for_status()
return float(response.json()["mid"])The gap is Mantic's probability minus the market midpoint. Pairs below the margin are left out, and the rest are sorted by the size of the gap.
5. Run the screen
Run it with a margin of 15 percentage points (pp):
python divergence_screen.py market_pairs.csv --margin 0.15 > divergence.mdEach row shows the gap in percentage points:
question = row["question"]
points = 100 * (row["mantic"] - row["market"])
direction = "higher" if points > 0 else "lower"
links = (
f"[{escape_cell(question['title'])}](https://app.mantic.com/question/{question['id']}) "
f"([market](https://polymarket.com/market/{row['slug']}))"
)
lines.append(
f"| {abs(points):.1f}pp {direction} | {row['mantic']:.1%} | {row['market']:.1%} "
f"| {links} | `{question['latest_forecast']['date']}` |"
)"18.0pp higher" means Mantic gives the event 18 percentage points more probability than the market; it does not mean an 18% relative difference.
A midpoint from a thin market can be stale; show the spread or last-trade time beside it.