Source confidence: high

Polymarket 99c Limit Order Arbitrage — Scalping the Resolution Tail

In short: Decide, with data rather than a story, whether resting 97-99c bids on near-certain Polymarket contracts earns more than the resolution-against tail it is paid to absorb.

Key fact Detail
What it covers automation, finance/trading, prediction markets
Tools Polymarket
Posts covered 1 post · top: Polymarket 99c limit-order edge — 75 automated…
Creators @beinginvested
Sources dated Jun 2026
Prompt system Run-it-yourself prompts
Last verified 2026-10-22

There's a specific, dumb, beautiful trade on Polymarket: post resting 99¢ bids on contracts that are almost certainly going to resolve to $1. When someone panics, needs liquidity, or fat-fingers a market sell, you eat their order at 99¢, wait a day or two, and collect $1. One cent of edge times 30-50 basis points of fee drag nets ~0.5% per cycle — but the cycles are short, the risk is well-characterized, and the capital turnover is extreme.

This is not alpha in the "predict the future" sense. It is liquidity provision at the tail of a binary distribution. The edge is structural.


1. What it is

Polymarket contracts are binary — they resolve to either $1 (YES wins) or $0 (NO wins). In the hours and days before resolution, many contracts have a near-certain outcome. A political contract where the result is already declared but not yet resolved. A sports contract past the final whistle. A weather contract where the day's high has already been printed. The "true" price is $1, but the order book has people selling at 98, 97, 96 for various reasons: exit-to-redeploy, position-close, loss-harvesting, or panic.

The trade: post resting limit buy orders at 99¢ (or 98, 97 laddered) and let liquidity takers hit you. When the contract resolves, you redeem for $1.


2. How it works — the mechanics

Polymarket's order book

Polymarket runs a CLOB (Central Limit Order Book) on Polygon. Orders are signed messages held off-chain; fills settle on-chain via the Conditional Tokens Framework (CTF). Key facts:

Why resting 99¢ bids get filled

Even on a contract "everyone knows" will resolve YES, sellers show up because:

  1. Capital rotation. Someone with a $10k position wants to redeploy before resolution rather than wait two days — they'll take 99¢ to move on.
  2. Tax harvesting. Realizing a gain at 99¢ vs waiting for $1 has cleaner bookkeeping.
  3. Panic / FUD. Rumors of oracle disputes, delays, or manipulation. Occasionally right, usually wrong.
  4. Fat fingers. Market sells by people who meant to market buy, or by scripts with broken sign logic.
  5. Resolution risk tail. Actual non-zero probability of a dispute or adverse resolution. This is the one real risk you are being paid for.

The math

Round-trip PnL on a single fill:

PnL = (payout − entry) − fees
    = ($1.00 − $0.99) − (2 × fee_rate × $0.01)   [fees on both sides capped by min(p, 1−p)]
    ≈ $0.01 − $0.0002
    ≈ $0.0098 per share ≈ 0.99% gross return

But capital turnover is high. If you cycle every 24 hours, that's ~250% annualized simple, compounded. With laddering (bids at 97, 98, 99) you catch more fills at higher gross margin:

Expected weighted return per capital deployed depends entirely on fill distribution, which depends entirely on what kind of "almost-certain" contracts you target.


3. Key mechanics — what to target

Not every contract is tradeable this way. The sweet spot:


4. Automation — how to replicate

The Polymarket CLOB has a Python client (py-clob-client) and a REST/WebSocket API. Full automation takes maybe 300 lines of Python.

Minimum viable bot

from py_clob_client.client import ClobClient
from py_clob_client.clob_types import OrderArgs, OrderType

client = ClobClient(host="https://clob.polymarket.com", key=PRIVATE_KEY, chain_id=137)
client.set_api_creds(client.create_or_derive_api_creds())

# 1. Scan all markets with end_date within 72h
markets = client.get_markets()
targets = [m for m in markets if is_near_resolution(m) and is_clean_source(m)]

# 2. For each target's YES token, post a laddered bid
for m in targets:
    token_id = m["tokens"][0]["token_id"]  # YES
    for price, size in [(0.99, 100), (0.98, 50), (0.97, 25)]:
        order = client.create_order(OrderArgs(
            price=price, size=size, side="BUY",
            token_id=token_id
        ))
        client.post_order(order, OrderType.GTC)

# 3. Watch fills via WS, redeem after resolution

Sizing and risk

Existing tools


5. Risks — the honest list

  1. Oracle disputes. UMA can rule against the "obvious" outcome. Rare but has happened (contested election calls, ambiguous sports outcomes). This is the real risk you're being paid 1¢ for. If UMA rules NO on a contract where you bought YES at 99¢, you lose ~$0.99 per share. One bad resolution can erase months of edge. Mitigation: diversification + hard dispute filter + never touch contested markets.
  2. Smart contract risk. CTF contract bugs, Polygon bridge issues, Polymarket operator actions.
  3. Resolution delays. Your capital sits longer than modeled, compressing annualized return. Not catastrophic, just disappointing.
  4. Front-running / adverse selection. The 1¢ of panic sellers knows something you don't about 5% of the time. That's priced in.
  5. Regulatory. US persons technically can't trade Polymarket. This is your call, not mine to advise on.
  6. Competition from other bots. This strategy is known. Depth at 99¢ has thinned as more bots run it. You need to be either faster, or post at 97-98¢ and accept more fill variance.

6. How this plugs into your trading-agent-team

This is a capital-efficient, low-touch, low-correlation strategy that fits the Unicron multi-strategy arena model well. It doesn't compete with the fast-cycler or Oracle Commander — it runs in parallel on Polymarket collateral while the perp strategies run on Hyperliquid collateral.

Concrete integration ideas:

  1. Pure-rules execution, zero AI in the hot path — matches your rule from feedback_rules_vs_agents.md. The selection logic ("clean source, <72h to resolution, not disputed") is 100% deterministic. No LLM, no ML. The AI layer only runs at market-discovery time: nightly, an agent scans new markets and classifies them as eligible / not eligible with reasoning. Humans review the classification before markets go live.

  2. Watchdog pattern is a perfect fit. Unicron's oversight spec already has a watchdog layer. For this strategy, the watchdog monitors UMA dispute events per open market. Dispute detected → cancel + exit via YES/NO merge immediately. This is the highest-leverage line of code in the whole bot.

  3. Challenge-game sweep. Parameters: bid ladder shape [97, 98, 99] weights, per-market cap, time-to-resolution threshold, source-cleanliness score. Fitness: Sharpe of simulated PnL on replayed Polymarket historical data (available via their Subgraph). Cheap to evaluate, clear optimum surface.

  4. Paired with the weather bot. The weather strategy (polymarket-weather-trading.md) builds a stable of positions it wants to exit close to resolution. The 99c bot is the counterparty to those exits. Running both strategies internally means the weather bot's exit cost goes to zero against your own book — that's a genuine operational synergy.

  5. Low capital to validate. $500-$2000 is enough to run this live and get statistically meaningful fill data in a week. Much lower barrier than the perp strategies.

Where to file it in the repo: /unicron/strategies/polymarket_99c/ with a shared polymarket_clob.py module also used by the weather strategy. Backtest harness uses Polymarket's Subgraph for historical order-book snapshots — same data layer as the weather bot.


7. TL;DR

You are selling insurance against Polymarket resolution tail risk at ~1% per cycle. The edge is structural (Polymarket's fee curve + forced liquidity providers on almost-resolved markets). The risk is oracle disputes, which you diversify away by spreading across 30+ concurrent markets and filtering out anything contested. Automation is ~300 lines. Annualized return is in the 30-100% range depending on fill-rate and dispute variance. It is not "free money" — the 1¢ is the premium you're collecting for taking a real, small, well-understood tail.

Sources


Prompt system — run this yourself

Everything below is pasteable into Claude Code, Claude, or any agent. Use the one-shot when you want the whole result in one run; use the step prompts when you want to steer. Every step carries its own audit, and the gap audit at the end makes the agent find and fill what it missed.

Mission. Decide, with data rather than a story, whether resting 97-99c bids on near-certain Polymarket contracts earns more than the resolution-against tail it is paid to absorb. Running this produces a historical tail-calibration study, a fill and adverse-selection backtest against a control, and a read-only paper bot with an UMA-dispute watchdog. Done means a written go or no-go: no real money moves until the historical loss rate's upper confidence bound sits below break-even and 30 days of paper trading beat the control after fees.

1 · Deeper, not bigger

Before running anything, make the task deeper rather than larger. Ask: what would a top professional do here that I am not asking for; what is the rawest material; which methods and tools; what, other than the model or me, proves it right. Then ask for exactly that.

Rewritten prompt — ask for exactly that:

Do not build me a Polymarket 99c bot yet. First tell me whether the edge exists after the tail.

1. From Polymarket docs and py-clob-client source, confirm the current fee schedule (the guide says fees scale as min(price, 1-price) times fee_rate), tick sizes, price bounds 0.01-0.99, CTF redeem and merge, and the UMA dispute flow. Check the Terms of Service for my jurisdiction and stop if live trading is not permitted.
2. Build a dataset of resolved markets with end dates, categories, resolution source text, price histories, on-chain fills at 0.97-0.99 in the final 72 hours, and final outcomes.
3. Compute the loss rate (resolved against a 0.97-0.99 buyer) by price band, category, and hours-to-end with Wilson 95% intervals. Compute break-even per band after fees.
4. Backtest laddered resting bids at 97/98/99 with a queue-position fill model, 2% per-market cap, 20-50 concurrent markets, dispute exits. Compare against a control that buys at the ask at a random time in the same window. Report return per capital-day, not the guide's 250% annualized.
5. Run a read-only paper bot for 30 days with a UMA dispute watchdog.

Gate: no real money until the upper confidence bound of the loss rate is below break-even and paper beats the control after fees. If it passes, the pilot is capped at $500 total, $25 per market, and halts on the first adverse resolution. Never promise returns.

2 · One-shot prompt

ROLE: You are a market-microstructure researcher evaluating a liquidity-provision strategy on Polymarket. Your default verdict is no-go; the data has to earn a go. You never place a real order in this run.

CONTEXT:
- Strategy from the guide: post resting limit bids at 0.99 (laddered 0.98, 0.97) on YES tokens of contracts whose outcome is effectively known but not yet resolved; redeem at $1 after resolution via the Conditional Tokens Framework (CTF). Targets: resolution within 24-72 hours, clean sources (weather station, election commission, final sports score), post-certainty windows, no oracle controversy.
- Claims to test: fees scale as min(price, 1-price) times fee_rate so near-$1 fees are near zero; about 0.98c net per share at 99c; about 250% annualized if capital cycles daily; depth at 99c has thinned as bots compete; UMA disputes are rare.
- Tools: Polymarket CLOB and Gamma APIs, py-clob-client, CTF Exchange OrderFilled events on Polygon, UMA Optimistic Oracle events, direkturcrypto/polymarket-terminal (plumbing only).
- Break-even arithmetic: at entry p, a win pays (1-p) minus fees and a loss costs p. Loss rate must be below roughly (1-p)/1 after fees, about 1% at 0.99, 2% at 0.98, 3% at 0.97.

HARD CONSTRAINTS:
- No order signing, no private key loaded, no wallet funded in this run. Check key presence with a boolean only, never print it.
- Read Polymarket's Terms of Service; if the user's jurisdiction is excluded, stop the live-path work and say so plainly.
- All analysis uses only information available at each simulated timestamp.
- Report returns per capital-day with confidence intervals; never promise returns.
- Any proposed pilot after a pass: $500 total cap, $25 per market cap, 2% of strategy capital per market maximum, halt on the first adverse resolution or any dispute fill, manual review before restart.

DELIVERABLES, in order:
1. mechanics.md: current fees, tick sizes, price bounds, order types, redeem and merge calls, UMA liveness period and dispute steps, ToS jurisdiction finding, each with a doc URL.
2. data/: parquet tables of resolved markets (end date, category, resolution source text, outcome), price histories, and on-chain fills at 0.97-0.99 within 72 hours of end.
3. tail_study.md: loss rate by price band x category x hours-to-end bucket with counts and Wilson 95% intervals; break-even line per band after fees; the cells where the upper bound is below break-even.
4. backtest.md: laddered resting-bid simulation with queue-position fills, per-market and total caps, dispute exits via cancel and merge; control arm (buy at ask at a random time in the same window); net PnL per capital-day with bootstrap intervals; worst single loss; realized time-to-redemption distribution.
5. paper_bot/: read-only WebSocket book watcher that logs hypothetical fills only when real sell volume crosses the price after your queue position, plus a UMA dispute watchdog that cancels hypothetical orders on any dispute.
6. gate.md: pass or fail against the oracles, and the pilot spec only if passed.

GAP ANALYSIS (built in): maintain gaps.md. For each deliverable record what you could not get (fill data incomplete before a date, resolution text missing, dispute history unavailable) and how it biases the result (missing disputes make the tail look safer). Fill what you can from on-chain data; state the rest.

SELF-QUESTIONS before finishing:
- Is my loss rate measured on fills, or on prices, and how different are they?
- How many adverse resolutions are in the sample; is a zero just a small sample?
- Did I include markets that were voided or resolved 50-50, and how did I treat them?
- Does the queue model assume I am first in line when real bots are ahead of me?
- Is the fee formula current for these market types?
- What is the realized days-to-redemption, including disputes?
- Does the strategy beat the control, or just the market's average drift to $1?
- Which categories carry the losses, and does excluding them overfit the sample?

STOP CONDITIONS: stop at the gate. Stop early if ToS excludes the jurisdiction, if fewer than 300 qualifying fills exist (insufficient to bound a 1% tail), or if the upper confidence bound of the loss rate exceeds break-even in every band.

RETURN FORMAT: a markdown report: Verdict (go or no-go, one line), Mechanics, Data coverage, Tail study table, Backtest vs control table, Paper bot status, Gate, Gaps ranked by impact, file paths.

3 · Step-by-step prompts

Step 1 — Ground truth: mechanics, fees, oracle, terms

Goal: Pin every mechanical fact the strategy depends on to a primary source.

From Polymarket's official docs and the py-clob-client source (github.com/Polymarket/py-clob-client), document: current fee schedule per market type (test the guide's claim that fees scale as min(price, 1-price) times fee_rate; note any taker-only fees and any fee-free markets); tick sizes (0.01 vs 0.001) and price bounds 0.01-0.99; order types (GTC, GTD, FOK) and how cancels work; the exact client methods for creating, posting, and cancelling orders; CTF redeemPositions and mergePositions; the UMA Optimistic Oracle flow: proposal, liveness window length, dispute, escalation to DVM vote, typical timelines.

Read the Terms of Service and state which jurisdictions are excluded. Write mechanics.md with one row per fact: fact, value, source URL or source file path:line, date checked. Add a break-even table: for entries 0.97, 0.98, 0.99, the maximum loss rate at which expected PnL per share is zero after fees.

Expect: mechanics.md with 12+ sourced rows, a jurisdiction finding, and a three-row break-even table.

Audit before moving on: Every row needs a URL or code path; rows without one are marked unverified. Recompute break-even by hand for 0.99: win (0.01 minus fee) times (1-L) equals loss 0.99 times L. If the jurisdiction is excluded, mark all live-trading steps blocked and continue only with research and paper simulation.

Step 2 — Historical dataset

Goal: Build a point-in-time dataset of near-resolution markets, fills, and outcomes.

Using the Gamma API, pull all closed binary markets for the longest window available (target 12+ months): market id, question, category or tags, end date, resolution timestamp, resolution source text, outcome, YES and NO token ids. Using CLOB prices-history, pull price series for the final 7 days of each. From the CTF Exchange OrderFilled events on Polygon (via a public subgraph, Dune, or a Polygon RPC with eth_getLogs in block ranges), pull every fill at price 0.97 or higher on the eventual-winner token and on the eventual-loser token in the final 72 hours.

From UMA Optimistic Oracle events, flag every market that had a dispute and its timeline. Save parquet: markets.parquet, prices.parquet, fills.parquet, disputes.parquet, with a coverage.md listing date ranges, row counts, and known holes.

Expect: Four parquet files, coverage.md with counts per month and known gaps, a fills count at 0.97-0.99 sufficient to bound a 1% tail (target 300+ in the 0.99 band).

Audit before moving on: Reconcile 20 random markets: the outcome in markets.parquet must match the on-chain resolution. Check that fills on the losing side at 0.97+ exist at all; if zero, your sample is suspicious (survivorship or a query bug). Fail if any fill timestamp is after the market's resolution; fix the join.

Step 3 — Tail calibration study

Goal: Measure how often near-certain contracts resolve the other way, and where.

From the step 2 parquet, define the event: a buy fill at price p in {0.97, 0.98, 0.99} on a token, within H hours of end (buckets 0-24, 24-48, 48-72). Loss = that token resolved to 0. Compute loss counts, fill counts, loss rate, and Wilson 95% intervals by price band, category (sports, politics, weather, crypto, other), and H bucket. Separately compute the unconditional rate from prices (the token's last price at 0.97+ at each hour boundary) to show the adverse-selection gap between price-conditional and fill-conditional loss rates.

Treat voided or 50-50 resolutions explicitly: list them and score them at their actual payout. Mark markets with any UMA dispute and report their loss rate separately. Write tail_study.md with the full table and a short list of cells where the upper confidence bound is below break-even from mechanics.md.

Expect: tail_study.md with a table (band x category x H) including n, losses, rate, Wilson CI, break-even, pass flag; an adverse-selection comparison; a dispute subsection.

Audit before moving on: Check that the total number of cells you mark as passing is not the result of mining many small cells: count how many cells you tested and apply a Bonferroni or Holm correction to the pass flags. Fail any cell with fewer than 100 fills; small cells cannot bound a 1% tail. Spot-check 5 losses by reading the market question and resolution text.

Step 4 — Backtest with queue model and control

Goal: Simulate the actual strategy with realistic fills, caps, and a control.

Simulate resting bids at 0.99, 0.98, 0.97 with ladder weights as parameters, on markets passing the guide's filters: end within 72 hours, category in the clean-source set, no dispute at entry. Queue model: when you post, record resting size ahead of you at that price from the book snapshot (or assume the full displayed size if snapshots are missing); your order fills only after that much sell volume trades at your price. Caps: 2% of strategy capital per market, 20-50 concurrent markets, time-based cancel after the expected window. Dispute rule: on a UMA dispute event, cancel resting orders and exit open positions at the then-prevailing price or by merge if you hold both sides.

Control arm: same markets, same capital, buy at the best ask at a uniformly random time in the same window and hold to resolution. Report per arm: fills, net PnL after fees, PnL per capital-day with bootstrap 95% CI, worst single loss, max drawdown, realized days-to-redemption distribution. Do not fit ladder weights on the reporting period: tune on the first 60% of months, report on the last 40%.

Expect: backtest.md with strategy versus control table on the held-out period, parameter choices from the tuning period, and a PnL histogram showing the tail.

Audit before moving on: Re-run with the queue model set to worst case (you are last in line) and best case (first in line); if the conclusion flips between them, the result is not robust and the gate fails. Confirm no future information: entry filters must use only data before each order timestamp.

Step 5 — Read-only paper bot with dispute watchdog

Goal: Collect 30 days of forward evidence without risking money.

Build paper_bot/ in Python: a market scanner on the Gamma API running hourly that applies the step 4 filters; a WebSocket subscriber to the CLOB book and trade channels for each selected token; a ledger that places hypothetical resting bids and records a hypothetical fill only when real trade volume at or below your price exceeds the resting size ahead of you at post time. No private key is loaded; the client runs in read-only mode.

Add a watchdog that polls UMA Optimistic Oracle status for every market with an open hypothetical position every 5 minutes; on dispute it cancels hypothetical orders, records the exit at the prevailing bid, and logs an alert. Run a control ledger in parallel (random-time ask buys on the same markets). Write daily rows to paper_ledger.parquet and a daily summary line: fills, open exposure, realized PnL, control PnL, disputes seen. Run for 30 days; keep a systemd user timer or cron entry and a heartbeat file.

Expect: paper_bot/ code with a README, a running process with a heartbeat, paper_ledger.parquet growing daily, and a dispute-alert log.

Audit before moving on: Grep the code for any order-signing or private-key call; any hit fails the step. Reconcile 10 hypothetical fills against the public trade tape. If the heartbeat goes stale for more than 1 hour, treat the day as missing in results, not as zero PnL.

Step 6 — Validate against oracles, gate, gap report

Goal: Decide go or no-go with evidence, and list what remains unknown.

Evaluate the gate: (1) in at least one tradeable cell after multiple-comparison correction, the Wilson 95% upper bound of the fill-conditional loss rate is below break-even; (2) the held-out backtest beats the control on PnL per capital-day with a bootstrap 95% CI excluding zero, under the worst-case queue model; (3) 30 days of paper trading beat the paper control after fees, with at least 100 hypothetical fills; (4) the ToS permits the user's jurisdiction; (5) a second reviewer re-runs tail_study from parquet and reproduces the loss rates. All five must pass. Otherwise the verdict is no-go and no wallet is funded.

If go: write the pilot spec: $500 total, $25 per market, 2% per market maximum, halt on first adverse resolution or dispute, daily reconciliation, kill switch that cancels all orders. Write gap_report.md: every claim from the guide and every result, marked verified / unverified / false with evidence, ranked by impact on capital at risk. Fix the top three now.

Expect: gate.md with five pass/fail rows and evidence, verdict line, pilot spec only if all pass, gap_report.md ranked.

Audit before moving on: If any oracle is unverified, the verdict is no-go. Check that the verdict line contains no return projection. Confirm the pilot caps appear in code constants, not just prose, if a pilot is proposed.

4 · Gap analysis and audit

Run this after the build. It makes the agent enumerate its own claims, mark what is verified, rank the gaps and fix the top ones.

You are an independent risk reviewer for a Polymarket 99c resting-bid study. You did not build it. Read mechanics.md, coverage.md, tail_study.md, backtest.md, the paper ledger, gate.md, and gap_report.md.

1. Enumerate every claim: fee formula, break-even rates, loss rates and intervals, adverse-selection gap, backtest versus control, paper results, jurisdiction finding. Mark each verified, unverified, or false. Recompute the 0.99 band loss rate and Wilson interval from fills.parquet yourself.
2. List missing pieces: unmodeled disputes, missing early fills, voided markets ignored, no worst-case queue run, tuning and reporting on the same months, paper fills not reconciled to the tape.
3. Rank by impact on capital at risk: anything that understates the tail first, then anything that overstates fills, then cost and timing.
4. Fix the top three now and show the before and after numbers.
5. State what you could not fix and why. If any fix moves the verdict from go to no-go, say so in the first line.
Return a claim table (claim, status, evidence, fix) and the ranked gap list. Never project returns.

Questions the agent must ask itself before it says done:

5 · What proves it right

6 · Failure modes

7 · Agent direction (unattended runs)

Research first, code second, money never in this run. Reach for the Gamma API and on-chain logs before the CLOB client; cache every pull as parquet under data/polymarket_99c/ and never re-query a cached range. Spawn a sub-agent for the on-chain fill extraction (block-range pagination is slow and self-contained) and another to independently recompute tail_study from parquet as the reproduction oracle. Never load or print a private key; the paper bot runs read-only. Stop and ask Victor if the Terms of Service exclude his jurisdiction, if qualifying fills are under 300, or before any step that would sign an order or fund a wallet. Write outputs to docs/polymarket_99c/: mechanics.md, coverage.md, tail_study.md, backtest.md, gate.md, gap_report.md, and paper_bot/. Return the verdict line first, then the gate table, then ranked gaps. Never state an expected return as a promise.

Prompt system updated 2026-10-22 · all prompt systems · JSON