ManticDocs
Example workflows

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 20
Polymarket 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 Yes outcome, 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:

market_pairs.csv
mantic_question_id,polymarket_market_slug,polymarket_yes_token_id
123,example-market-slug,12345678901234567890

3. 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 questions

GET /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.md

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

On this page