A quoted gap is the beginning of the check

For a conventional binary market, a matched pair of YES and NO shares is associated with the market's combined payout mechanics. A scanner can compare the cost of buying the pair with that amount. The comparison requires the same market, compatible outcome identifiers and the actual rules. It cannot be inferred from two similar headlines.

The positions and tokens documentation explains the underlying mechanics. Check the market and protocol version you are using. Multi-outcome and negative-risk structures need their own mapping and constraints; summing an arbitrary list of prices is not enough.

Use executable asks and costs at your size

When buying both outcomes, inspect the ask side of both order books. A midpoint, last trade or YES bid is not the price of buying the pair. The official pricing reference describes the available reads; our API guide connects discovery to the outcome identifiers.

A useful calculation for a proposed quantity is: combined payout amount minus the total ask-side acquisition cost, applicable fees and other execution costs. Calculate acquisition cost across the price levels needed for that quantity. If either book lacks the required depth, label the candidate unavailable rather than filling the missing quantity at the best price.

For illustration, asks of $0.46 and $0.52 sum to $0.98. The gross difference from $1 is $0.02 per matched pair before costs. At 100 pairs that is $2 before costs, provided both quantities are actually available. Those are invented numbers for explaining the calculation, not a current market opportunity.

Read current fee parameters from the venue and the market. Polymarket's fee documentation describes category-dependent taker fees. Do not use a universal fixed fee or assume every order has a constant blockchain cost. If a cost is unknown, leave the net calculation unknown.

Run a bounded, read-only scanner

The downloadable Python scanner retrieves a small Gamma sample, selects a binary market with CLOB token identifiers, and reads each outcome's CLOB order book. It uses no private key and includes no order request. Save it as polymarket-pair-scanner.py and run python3 polymarket-pair-scanner.py.

"""Inspect top-of-book quotes for one Gamma CTF binary market; never trades."""
import json
import time
from datetime import datetime, timezone
from decimal import Decimal
from urllib.parse import urlencode
from urllib.request import Request, urlopen

def get(url):
    with urlopen(Request(url, headers={"User-Agent": "NickAI-pair-scanner-example/1.0"}), timeout=20) as response:
        return json.load(response)

def array(value):
    return json.loads(value) if isinstance(value, str) else value

markets = get("https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=20")
selected = None
for market in markets:
    labels = array(market.get("outcomes") or [])
    tokens = array(market.get("clobTokenIds") or [])
    if market.get("enableOrderBook") and market.get("acceptingOrders") and len(tokens) == 2 and set(labels) == {"Yes", "No"}:
        selected = market, dict(zip(labels, tokens))
        break
if selected is None:
    raise SystemExit("No compatible CTF binary market in this bounded sample.")
market, tokens = selected
started = time.monotonic()
quotes = {}
for outcome in ("Yes", "No"):
    book = get("https://clob.polymarket.com/book?" + urlencode({"token_id": tokens[outcome]}))
    asks = [(Decimal(level["price"]), Decimal(level["size"])) for level in book.get("asks", [])]
    asks = [(p, s) for p, s in asks if p.is_finite() and s.is_finite() and 0 < p < 1 and s > 0]
    if not asks:
        raise SystemExit(f"No executable ask levels for {outcome}; skip.")
    best_price = min(p for p, _ in asks)
    size_at_price = sum(s for p, s in asks if p == best_price)
    quotes[outcome] = (best_price, size_at_price)
quote_sum = quotes["Yes"][0] + quotes["No"][0]
print(json.dumps({
    "observed_at": datetime.now(timezone.utc).isoformat(),
    "condition_id": market["conditionId"], "question": market["question"],
    "quotes": {side: {"ask": str(p), "size_at_ask": str(s)} for side, (p, s) in quotes.items()},
    "gross_gap_before_costs": str(Decimal("1") - quote_sum),
    "top_level_pair_size": str(min(quotes["Yes"][1], quotes["No"][1])),
    "read_elapsed_seconds": round(time.monotonic() - started, 3),
    "net_gap": None, "note": "Fees unknown; sequential snapshots; not an execution signal."
}, indent=2))

It explicitly selects the lowest ask rather than assuming the first array element is the best price. It sums size at that price and limits the displayed pair quantity to the smaller side. This is a top-level observation; extending to larger quantities requires walking both books.

We ran the scanner on October 7, 2026. The sampled asks were $0.027 and $0.974, producing a gross difference of minus $0.001 before costs. The scanner correctly kept net value unknown. This records a read-only test, not a validated strategy, and the market values will change.

The two books are fetched sequentially. The elapsed time is part of the report because the quotes are not an atomic snapshot. Production monitoring needs an explicit freshness rule and synchronized handling of incoming data. Even a fresh snapshot can change before an order reaches the venue.

Why fill-or-kill does not protect both legs together

The order-placement documentation describes fill-or-kill behavior for an individual order. If your YES order fills and your NO order is rejected, the first fill still exists. Sending both with FOK does not create a transaction spanning both books.

Keep a separate state for every submitted order and reconcile it with the venue using the order-management endpoints. A network timeout is an unknown result until checked, not permission to submit the same order again blindly. Define what happens to unmatched exposure before enabling an automated order path.

  • Both legs confirmed: record actual quantities, prices and costs.
  • One leg confirmed: stop new entries and follow a predefined exposure policy.
  • Order result unknown: query and reconcile before retrying.
  • Market status or rules changed: invalidate the candidate and require review.

Cross-venue comparison is a separate problem

A price difference between Polymarket and Kalshi is a research signal to investigate. Compare the exact outcome, cutoff, timezone, settlement source, cancellation provisions and account access. Two contracts about the same subject can settle differently. Play-money markets are not interchangeable with cash-settled exposure.

Use the Kalshi monitor to collect a second venue's data, then store a reviewed contract mapping. An LLM can help summarize rules, but its summary should link to the originals and must not silently establish equivalence.

Use NickAI for the monitoring and review workflow

Ask Nick to create an inactive workflow that finds a defined market set, reads quotes, calculates a clearly labeled gross gap and sends a report with identifiers and timestamps. Use a Function node for deterministic arithmetic and inspect the actual data shape before connecting it. Keep fees unknown until you have a verified source.

The Polymarket workflow guide covers the data-to-report path, while the prediction-market report template provides a starting design. Review any generated logic. A reporting workflow is useful on its own; adding execution requires the state handling described above.