① A market is a $1 coin
Every binary Polymarket question splits into two tokens, YES and NO (here: side A and side B). At resolution exactly one is worth $1 and the other $0. You never need to know which.
Almost everything a Polymarket arbitrage bot does collapses into one rule and a few guards around it. This guide walks that rule outward, through the data feeds, the decision engine, capital recycling, risk controls, the configuration profiles, and the validation ladder, roughly in the order they fire inside a live system. It explains the strategy in general and the features it can incorporate, not any single bot.
…and only to (a) build toward a hedged pair or (b) average down a side you already hold. Bound directional risk with a max-imbalance cap. Pace capital with a soft budget. Act on dips, not every tick.
Before any code, the whole strategy fits in one idea. If you understand this section, every step after it is just plumbing around it. No prediction-market background assumed.
Every binary Polymarket question splits into two tokens, YES and NO (here: side A and side B). At resolution exactly one is worth $1 and the other $0. You never need to know which.
Because one side always pays $1, the market prices them so price(A) + price(B) ≈ $1. If A trades at $0.60, B trades near $0.40. They move as a seesaw.
If you hold one A and one B, resolution pays you exactly $1 no matter the outcome. So if the pair cost you less than $1 to assemble, the difference is risk-free profit, already locked.
askA + askB > $1 (if it dipped below, it would be free money and get taken in seconds). The edge does not live in the spread. It lives in time. Because A and B seesaw, their dips never line up: A bottoms out exactly when B peaks. So you buy A only during A's dips and B only during B's dips, at different moments. Each side's blended average reflects only its own cheap moments, and two bargain-hunted averages can add up to well under $1.Buying a side cheaply pulls down its average, which raises the price you can still afford on the other side while staying under the ceiling. The two sides ratchet each other down. Start balanced at $0.50 / $0.50 (combined $1.00), buy B at $0.30 on a dip, then A at $0.30 when the seesaw flips, and the matched pair now costs $0.80. Keep repeating that the whole market long.
You already hold 100 of B at an average of $0.45. With ceiling C = 0.97, the most you may pay for A is C − avg(B) = 0.97 − 0.45 = 0.52. A's ask is $0.49, which clears, so you buy. The pair now costs 0.49 + 0.45 = $0.94 and locks $0.06 of profit per matched share.
violate it: force A at $0.80 in a blowout → pair $1.25 → −$0.25 locked loss$1 − (cost of the pair).avg(A) + avg(B). The single number that decides everything: below $1 means every matched pair is profit.price(S) ≤ C − avg(other side).cap − price. The locked profit that buy adds per pair.|qA − qB|. The hard bound on one-sided, directional risk.pUSD) before resolution, freeing capital to redeploy.One stage feeds the next: data in, a decision out, the risk manager vets it, execution places it, and the fills loop back. Scroll any section below and its box lights up here.
Reads the live position and calls the on-chain CTF contract: Merge matched pairs to pUSD before resolution to recycle capital, Redeem leftovers after. Freed cash flows back into the deployable budget.
The same pure engine runs against three data sources: replay on recorded books, shadow on the live feed with paper fills, then live. What you test is exactly what you run.
Five sources feed the decision engine. Two WebSocket channels carry the live truth; REST and the on-chain contract handle metadata and capital. A sound engine reads both token order books directly. It never synthesizes one side's price from the other.
In plain termsA live feed of who wants to buy and sell each side, and at what price, updating moment to moment.
It streams the order book over a WebSocket (a persistent connection that pushes updates instantly, with no repeated polling). The best ask is the lowest price someone will sell at, the bid is the highest someone will buy at, and the mid sits halfway between.
best_ask · ask_size · bid · midIn plain termsWhat you actually own, based on trades that truly executed rather than what you hoped would happen.
A fill is an order that actually traded, in full or in part. Your position and average cost are rebuilt from real fills, never from assumptions, so the bot always knows its true exposure.
trade → qty · cost · avgIn plain termsA lookup service that hands you the basic facts about a market before you trade it.
It returns the token ids to subscribe to (each outcome is its own token), the tick (the smallest price step a market allows, for example one cent), and the neg_risk flag that selects the correct on-chain Merge path.
token1/2 · tick · neg_riskIn plain termsThe calls that place, cancel, and batch your orders, plus one easy-to-forget setup step.
CLOB V2 (the exchange's order-matching system) needs a /balance-allowance/update call to sync on boot, and again after funding, before any order is accepted. Skipping it is the top cause of silent V2 failures.
/order · /balance-allowance/updateIn plain termsThe on-chain tool that turns your matched shares back into spendable cash so you can keep trading.
On Polygon, you Merge a pair of opposite shares back into $1 to recycle capital early, and you Redeem after the market resolves to collect the winning side.
mergePositions · redeemPositionsIn plain termsExtra information a bot can bolt on to trade smarter, though none of it is required for a first working build.
Examples include a live score or game-clock feed, or a reference book from another venue like Pinnacle or Binance to compare prices against.
score feed · external bookaskB ≈ 1 − askA). Live, each token has its own independent order book, and at every instant askA + askB > $1. Always read both books directly. Never synthesize one side's price from the other.The engine is one pure function, decide(book, position, config) → Intent | null. It does no I/O and is fully deterministic, which is exactly what lets the same code run in simulation, offline replay, and live trading. Drag the sliders below to watch the gate accept or reject a buy in real time.
In plain termsIt refuses any buy that would make a completed pair cost more than your target, so a finished pair always profits.
If price > C − avg(other) the buy is rejected, where avg(other) is what you have already paid per share on the opposite side.
In plain termsEvery buy needs a real reason: either to even out a lopsided position, or to genuinely lower your average cost.
A buy must build toward balance (move the two sides closer to equal) or be a true average-down, priced below your current average by at least the edge you require. No reason means no buy.
In plain termsIt stops the bot from piling onto one side and quietly turning into a directional bet.
A buy that does not move toward balance is refused once |qA − qB| ≥ imb, where qA and qB are your share counts on each side and imb is the cap on how far apart they may drift.
The gate in Step 2 only decides whether a buy is allowed. It does not say a buy is a good idea right now. A trigger is the bot's reason to act this instant instead of waiting. If none of the three below is true, the bot does nothing, and that restraint is the whole point: it buys on real dips and genuine rebalancing needs, not on every flicker of the price. Any one trigger is enough to fire.
freshTroughThe price just set a new low for this side, the cheapest it has been since the last buy here. That signals a real dip rather than ordinary jitter, so the bot steps in while the discount is genuinely on the table, before the price ticks back up.
fires when: ask ≤ its running low + 0.003deepThe price has fallen so far below the ceiling that the profit this buy locks in, its edge, is unusually large: at least six cents per pair. A discount that big is worth taking right away, so this trigger overrides the usual patience and buys immediately.
fires when: edge ≥ 0.06 (six cents under the cap)patientNo dramatic dip, but two things are true at once: enough time has passed since the last buy (the patience window), and the bot holds fewer shares of this side than the other. It tops up the lagging side to complete more pairs, rather than sitting idle waiting for a dip that may never come.
fires when: waited ≥ patience AND this side is behindOnce a buy fires, its size scales with the opportunity. The base order (the clip) grows up to five times larger as the discount deepens, so the best prices get the most capital. It can also jump to close a gap when one side has fallen behind. Then, as the capital already deployed in a market approaches its budget, every order tapers down, so the bot eases off the gas rather than going all in.
size = clip × (deeper discount, up to 5×) × taperEverything above is soft pacing: gentle nudges up or down. There is exactly one absolute refusal. Once the capital deployed in a single market reaches three times its budget, no new buys are allowed, period. It is the backstop that caps how much can ever be at risk in one market.
no new buys once deployed ≥ 3 × budgetA reference implementation runs the exact gate, triggers, and sizing described above, tick by tick on a synthetic price path, using the Balanced config. It is not an animation to watch passively. Press Play, then pause and hover any dot to read the full reason that order fired: the gate math, the trigger, the eligibility, and the config behind it. The one number to watch is Combined avg in the ledger. As long as it stays at or below the ceiling C, every completed pair is locked profit.
One feature some bots add. A rolling window watches each side's ask, and when one side trends cleanly in a single direction (a large net move with a high fraction of same-direction steps and little chop) the bot flags a stable one-way market. The arbitrage gate would keep rejecting the rising favorite, so trend mode can override it.
net move ≥ trendMove · same-direction ≥ trendConsist over trendWindow ticksOn detection a bot can buy the rising (winning) side directionally, riding it toward $1 at resolution. That is a momentum bet, not a hedge. It is no longer arbitrage: exposure is capped to a trend budget and it stops the moment the trend breaks. This is distinct from "forcing the second leg," which pairs the winner against held inventory and locks a loss.
buy rising side ≤ trendMaxPrice · exposure ≤ trendBudget · stop on reversalA matched pair (one A share plus one B share) is guaranteed to be worth exactly $1 when the market resolves. The key insight is that you do not have to wait for resolution to collect it. You can convert matched pairs back into cash immediately and redeploy that cash into the next dip. This recycling runs continuously, outside the gate, and it is what lets a small bankroll trade many times its own size.
One A share plus one B share is a complete set, and a complete set is always worth exactly $1: whichever side wins pays $1, the other pays $0. The on-chain Merge operation (mergePositions) burns a complete set and returns $1 in collateral (pUSD) right away, with no need to wait for the market to resolve. The neg_risk flag only decides whether that Merge routes through the standard contract or the NegRiskAdapter.
How many pairs can you Merge? Exactly min(qA, qB), the smaller of your two share counts, where qA is the shares of A you hold and qB the shares of B. A pair needs one of each, so the number of complete pairs you own is simply whichever side you hold less of. Merging those pairs locks in their profit and frees the cash to redeploy, leaving only the surplus on the heavier side as open inventory. Bots batch their Merges so the Polygon gas cost stays small next to the capital freed.
Merge min(qA, qB) pairs → min(qA, qB) × $1 in pUSDWhatever inventory is left when the market settles, the unmatched surplus on one side, is cashed out with the Redeem operation (redeemPositions). Once the oracle reports the result, Redeem pays $1 for each winning share and $0 for each losing share.
Capital stays locked inside a position until you Merge it or Redeem it: those are the only two ways to turn shares back into cash. Merge is the early exit for pairs you have already hedged, and Redeem is the final settlement for everything still open at the end.
Redeem after the oracle → $1 per winning share, $0 per losing shareThe guards that keep one bad market from becoming a bad day. The most important is a rule about what a bot must never do.
In plain termsIf you keep buying one side more than the other, you are quietly making a directional bet. This cap limits how lopsided your two stacks of shares can get, so you stay hedged instead of gambling on an outcome.
Stop adding to the heavy side once the gap hits the limit: |qA−qB| ≥ imb bounds directional exposure (the part of your position that wins or loses with the market).
In plain termsA ceiling on how much you put into any single market, so one trade cannot sink you. The bot slows down as it nears the planned amount, and flatly refuses to go far past it.
Soft taper as you approach budget; hard refuse at 3 × budget.
In plain termsArbitrage assumes prices wobble around a fair value and come back. If the price just keeps marching one way, that assumption is broken and the bot should stop opening positions.
If signed flow (net buying pressure, with direction) stays beyond 2σ (two standard deviations, an unusually large move) with no reversion, stop initiating and Merge what exists.
In plain termsA hard stop for the day. Once total losses reach a set ceiling, the bot shuts off new trades so a bad day cannot become a disaster.
Global circuit breaker. Halt all new intents past the limit.
In plain termsA trade that looks profitable before fees can be a loser after them, so the bot checks the after-fee number before committing.
Run C_effective (your ceiling after fees), never raw C, in taker mode (Step 6).
In plain termsIf the bot loses its live connection it may be trading on stale prices, so it resyncs first. And once a market is closing out, it stops trading it.
Resync on a heartbeat lapse (a missed "still alive" ping); stop trading once a market enters settlement (the final payout phase).
Simulated configurations are usually tuned on a zero-fee, instant-fill model. Three realities have to be folded in before live capital, and they mostly tighten the gate.
In plain termsFees only hit you when you take an order already resting on the book, not when you post your own. So a patient, maker-style bot can pay nothing.
Fees are taker-only. Resting limit (maker) orders fill free, so a maker bot keeps C_effective = C. Takers pay fee = shares · feeRate · p(1−p), largest near 50¢ and smaller toward the wings. Worst case per pair runs ≈0.015 (sports) to ≈0.035 (crypto), so a taker C=0.99 drops to ~0.95 effective on crypto. Run C_effective = C − fee/pair in taker mode.
In plain termsBy the time your order reaches the exchange, the cheap price you saw may already be taken. Quote from the live book and cap the price you will accept, so you never chase a fill.
Use the WS book and marketable limits (an order that fills now, but with a price ceiling) with a small buffer. Sports has a multi-second taker delay. Patience, not speed.
In plain termsYou rarely get your whole order at once. Often only part fills (a partial), so base every decision on what you actually got, not what you asked for.
Model fills against real book depth and re-evaluate on the actual filled quantity. The imbalance cap is enforced on realized shares.
Every configuration runs the exact same engine and differs only in seven tunable parameters (a setting you can dial up or down). To compare them fairly, each was run through one shared test: the same 20 synthetic markets the demo above draws from, a mix of oscillating, choppy, drifting, and blowout games. The numbers in each row are that test's output. They rank the configurations against one another and are not a forecast of live dollars. Press Run in demo on any row to watch that exact configuration trade a market.
One engine, three data sources. The dollar figures above are only believed once rebuilt on real recorded books, then proven live in graduated, gated steps.
Deterministic replay means feeding the same recorded data through the bot and getting the exact same result every time. Replay real order books (the live lists of buy and sell offers) into the same engine. This is the gold standard. Rebuild the configuration table with these real-data numbers.
Shadow, or paper, trading runs on the live feed but only pretends to place orders, so no real money moves. It catches feed-handling, reconnection, clock, and rate-limit bugs that a replay cannot.
A canary is a tiny live test that warns you of trouble before you scale up. Trade just 1 to 2 markets in 5 to 10 share clips, compare your paper fills to your actual fills, and tune until paper is about equal to live.
Grow your size slowly, and only after Stages A through C agree with each other. Scale by no more than 2x per step, re-checking every gate as you go.
| Go / no-go gate | Threshold |
|---|---|
| Realized combined VWAP your real average price paid | ≤ C on ≥90% |
| Fill ratio share of orders that filled | ≥ 60% |
| Slippage vs quote expected price vs price you got | in band ≥90% |
| Max imbalance how lopsided the two legs got | never > imb |
| Capital cap most money at risk at once | ≤ budget × hard |
| Rejects / heartbeat failed orders, health ping | 0 / session |
| Single-market loss worst loss on any one market | ≤ modeled worst |
A sound build sequences the work. Engine and harness first, so the same code runs in all three modes. Execution, risk, and scale follow. Maker mode is usually a later, separately validated track.
Build the core engine and a runner with all three modes, then get every unit test passing.
Record 30 to 100 real markets and rebuild the configuration table on that real data.
Add the V2 trading client, a rate limiter, a heartbeat health check, and the Merge and Redeem operations.
Run on the live feed with pretend fills, to prove the bot behaves identically to replay.
Trade tiny real size and confirm the bot passes every go/no-go gate.
Grow size gradually, getting approval for each market category before expanding it.
Optionally chase maker rebates and rewards, and hedge whole games across legs.
Sports behave differently from crypto, so approve each category on its own.
The logic above is clean. The market is not. This section grounds the strategy in what live bots actually do, and is honest about the failure modes, so you read the rest with the right expectations. In practice these bots sort into two archetypes, two distinct playbooks for earning the same spread: one crosses the spread actively, the other sits back and gets paid to wait.
The approach this guide focuses on, run by bots like gabagool, Bonereaper, and the RN1 sports bot. As a taker (someone who takes an existing order off the book, paying for speed), cross the spread the instant the pair-cost math permits, accumulating each side on its own dips. The profit driver is the spread captured across time, hence temporal. One documented gabagool BTC cycle: ~1,267 YES at avg $0.517 and ~1,295 NO at avg $0.449, a pair cost near $0.966, about $58 profit on ~$1,237 deployed in 15 minutes.
What the open-source poly-maker does. Post resting limit orders just inside the book on both sides and earn the spread plus maker rebates (small payments for adding orders to the book) and liquidity rewards (extra payouts for keeping orders posted near the mid). The profit driver is rebate and reward income, not the spread. Fills are slower and probabilistic, but makers pay zero fees, so the economics are gentler. It is the natural next step beyond the taker approach.
Filter for oscillation, meaning prices that swing back and forth rather than drifting one way: competitive matchups, liquid books, an active reward pool. Avoid blowout-prone games, where one side runs away early and the price never comes back. This is the biggest determinant of survival, and the step most homegrown bots skip.
As a maker, rest bids just below the mid on both sides and build a hedged base near 50/50 at close to cost, paid for by rewards and rebates.
All resting sports orders auto-cancel at game start. The base inventory carries over, and the bot flips into reactive, in-game, taker-leaning mode.
Apply the gate on every book update. The latency window, the brief lag before everyone sees the same news, is the opening: official feeds update seconds after an event while livestreams lag far longer, leaving stale orders to pick off.
Merge the matched pairs, min(qA, qB) of them, back to pUSD any time before resolution and redeploy. The same dollars round-trip many times, which is why these bots show huge volume relative to their balance.
Prices go extreme, the back-and-forth dies, and any remaining imbalance is a naked directional bet (an unhedged wager on one outcome). Accept small, cheap, capped one-sided inventory. Never force the second leg.
Press Watch live market and the page connects to the current Bitcoin 5-minute up/down market on Polymarket and starts polling its real order book every couple of seconds. The same gate, triggers, sizing, merge, and Trend Rider logic from the demo above runs as a paper bot on each live tick, and every trade it would make is plotted on the chart as it happens. Tune the seven dials or flip Trend Rider at any time and the bot instantly recomputes over the live ticks so far. This is read-only paper trading. No wallet is connected, no orders are placed, and nothing here is financial advice. Every dollar is hypothetical.