[ 01 / 10 ]What it trades
Polymarket runs recurring up-or-down markets on BTC, ETH, SOL, and XRP: will the coin end the slot above the price it opened at. The main arena is the 5-minute slot - a fresh market every five minutes, all day, every day, resolved by a Chainlink oracle - with 15-minute and 1-hour variants alongside.
My bot trades them 24/7. A monitor samples the world twice a second: orderbook depth, spread, imbalance, and mid for each market, Binance spot, and the Chainlink feeds, across all four coins. The signal that pays is one number - how far spot has drifted from the slot's opening price. Drift past a threshold, and the bot bets the move holds through resolution.
That is the entire strategy in a sentence. Everything below is what it took to make it real: one expensive lesson about entry prices, a long war against latency, and a strategy layer distilled from three quarters of a million orderbook snapshots. I wrote the blow-by-blow as a three-part series on X; this is the condensed version, embarrassing parts included.
[ 02 / 10 ]Winning 86% and still losing
The first overnight session won 86% of 292 trades and finished down money. Nothing broke. Binary shares settle at $1.00 or $0.00, so a share bought at $0.95 risks $0.95 to win $0.05 - you need a 95% win rate just to break even, and 86% is not 95%.
Simulating over 16 days of collected data showed where the profit actually lives: entries between $0.50 and $0.70 produce 73% of all profit. Entries above $0.95 produce 1% of it, despite winning 98% of the time. The fix fits on one line.
# 73% of all profit comes from entries between $0.50 and $0.70
MAX_ENTRY_PRICE = 0.75
The deeper lesson took longer to land. Cheap shares are cheap because the move just happened and the books have not caught up. They exist for moments, and everyone fast is hunting them. Being right is table stakes.
[ The edge is not the prediction. The edge is execution. ]
Which makes the real product the order path. Mine took half a second. So began the war.
[ 03 / 10 ]Latency war I: out of the subprocess
An order's life is short: see the deviation, price the share, sign, POST, and match before everyone else with the same idea. Here is the whole campaign on one board.
The starting architecture was a Python monitor that, on every signal, spawned a Rust CLI subprocess to sign and submit the order. Process spawn, interpreter startup, fresh connections - every single trade paid the full cold-start tax. About 500ms.
Step one was obvious: move order placement in-process with the official py-clob-client SDK and keep the connections persistent. Three per-order API lookups that always return the same values got hardcoded away. The path settled around 320ms.
The SDK era also produced a genuine find: py-clob-client v0.34.6 ships a rounding bug. Its ROUNDING_CONFIG allows four decimal places on maker amounts while the CLOB rejects anything past two, so an order can sail through the client and die at the API.
[ 04 / 10 ]Latency war II: a tour of other people's datacenters
320ms of Python from a home connection still loses races, and the next lever was geography. The CLOB's edge sits in Ashburn, Virginia - the cf-ray header says IAD - so a $5-a-month Hetzner VPS went up as close to it as I could get. 93ms. Then I tried to trade through it: 403. Geoblocked.
Nuremberg, then. 36 milliseconds - and every trading request answered 403, because Germany is a restricted jurisdiction.
[ 36 milliseconds out of Nuremberg: the most beautiful, useless latency number of the project. ]
Helsinki: 55ms, and - critically - allowed. Orders went through. For about a day, that felt like winning.
[ 05 / 10 ]Latency war III: the rate limiter and the relay
In the first real session from Helsinki, the bot saw 24 signals and filled none of them. The orderbook fetcher was polling at 27 requests per second, the CLOB's rate limiter answered with 429s, and those 429s are sticky: by the time a signal fired, the trading path was already in the penalty box. 24 signals, zero fills. A perfect latency setup, rate-limited into a spectator.
The rebuild split the problem in two. Orders are signed locally, so keys never leave my machine, and the pre-signed payload is pushed through a Cloudflare Worker relay. First measurement: 19ms. Then a familiar friend: 403. The Worker's own outbound IP was geoblocked.
The fix is a feature most people never touch: Worker Placement with a hostname pin to clob.polymarket.com, which makes Cloudflare run the Worker near the CLOB instead of near the caller. That one worked: a 51ms warm relay, and my own location stopped mattering.
[ 06 / 10 ]Latency war IV: the Rust rewrite
The last enemy was the language. Python signs an order in about 15ms, which is tolerable, and occasionally stops the world for 50-100ms of garbage collection, which is not. In a race for a cheap ask, random jitter means losing races at random. A bot that is usually fast and occasionally frozen has no business at the front of the queue.
So the monitor got rewritten in Rust on the polymarket-client-sdk crate. Signing went from ~15ms to ~2ms. The GC jitter did not get smaller - it ceased to exist. Latency stopped being a distribution with a bad tail and became a number.
The binary keeps two order paths selectable at runtime: direct CLOB POST, or the Worker relay when trading from somewhere the CLOB dislikes. From my laptop, signal to order is 134ms end to end; deployed in Helsinki on the direct path, the target is ~42ms. Call it 12x from the 500ms where this started.
One more step is on the roadmap: compile the signer to WASM and run it inside the Cloudflare Worker itself, so orders are born inside Cloudflare's network. The target there is sub-10ms. The race for cheap shares is not getting slower.
[ 07 / 10 ]What 750,000 orderbook snapshots say
While the order path got faster, the monitor was hoarding data: 120-plus hours of markets, 5,655 market snapshots, 750,000-plus orderbook snapshots. Against that dataset I tested 15 strategies. Four made money, two broke even, nine lost. Most ideas lose; that is the baseline.
The dataset also killed the momentum fantasy. Each market's regime is classified by cross-slot outcome autocorrelation over a rolling 30 slots:
- TRENDING - autocorrelation 68% or higher: under 1% of the time.
- MIXED - 55% to 68%: about 20% of the time.
- CHOPPY - below 55%: 80% of the time.
The market spends 80% of its life in chop, so strategies that bet on the last slot repeating starve. The survivors are orderbook-microstructure readers: they detect informed flow inside the current slot and follow it to resolution.
Confirmation gating turned out to matter more than strategy selection. Requiring one confirming signal gives a 55% win rate. Two confirms: 78%. Three or more pushes past 85% but trades away most of the volume - two confirms is the PnL sweet spot. The best confirmer is multi-coin consensus (MCC), worth +$2.00 to +$3.11 per trade across strategies. The worst is one called ED, which fires on 90% of trades and therefore confirms nothing.
[ A signal that fires almost always is not a signal. ]
The most pleasing discovery is CCL, cross-coin lag: BTC leads the alts by 5 to 15 seconds inside a slot. When BTC commits to a direction and SOL or XRP still trades around $0.50, buy the laggard. In post-audit simulation, CCL alone took roughly 800 trades on SOL and XRP at about a 65% win rate for +$468; paired with IMB, the orderbook-imbalance signal, it took 2,121 trades at 61.5% for +$743 with a max drawdown of $196. It survives chop precisely because it is not cross-slot momentum - it is information propagating between coins within a single slot.
[ 08 / 10 ]The scan pipeline
All of this compiles down to self-contained JSON configs: which strategy trades which coin, which confirmer gates apply, entry thresholds, and a blocklist of toxic strategy-coin pairs. Configs are produced by a scan pipeline, not by hand:
- Run the combined simulator ungated - every strategy, every coin, no filters - to get the raw trade universe. Latest run: 15,422 trades, net -$59.
- For each strategy-coin combo, search confirmer-gate sets of size 2 to 4, targeting a win rate above 75% with at least 10 trades.
- Scan for toxic pairs and block them.
- Re-run the simulation with the gates applied and verify it.
- Prune whatever still loses and re-verify, until the config carries zero losing combos. Then emit it.
The workflow is run scan -> review -> save config -> one-click deploy. The same period got the codebase honest too: the monitor went from 10,000 lines to about 5,000, twenty-plus simulator scripts collapsed into one unified sim, configs get diffed before deploy, and compare_live_sim.py flags sim-vs-live divergences with root causes attached.
[ 09 / 10 ]The $33,796 that never existed
Midway through, the simulator was lying to me. The take-profit and stop-loss paths - simulate_tp() and simulate_sl() - valued exits using a DOWN price derived from the mid plus the threshold exit price, instead of the bid actually sitting in the orderbook. Phantom prices, phantom profit: +$33,796 of it across strategies. One strategy (FE) alone booked 141 trades at roughly $143 of fake profit each.
The fix validates every simulated exit against the actual orderbook bid, with a conservative fallback. When it landed, the entire strategy hierarchy reshuffled. If one pricing assumption can reorder your whole leaderboard, the leaderboard was fiction.
Live trading enforces the same humility. Polygon settles in 2-4 second batches, so the bot polls on-chain balance for up to 20 seconds before selling - trusting a premature balance update caused 83% of take-profit sells to fail. It sells 97% of shares rather than 100%, because sub-cent rounding makes "all" a lie. And it never re-sells while a LIVE order exists.
[ 10 / 10 ]Two accounts and a vault
For the record, the first small test account was tuition: $30 to $210 back down to $44 inside a week of early experiments. That account paid for most of the lessons above.
The system that came out the other side is profitable across two live accounts, and one of them is public: polymarket.com/@vuk5minmarkets - every fill on-chain, no screenshots required. It runs two modes: Standard, which takes profit early via fill-and-kill sells, and Resolution, which holds to expiry; resolution mode ran $2 flat bets.
Polymarket also accepted the project into its Builders Program: order attribution wired into the Rust CLI, gasless transactions through Polymarket's relayer, and a $2.5M Builder Incentive Fund on the other side of that attribution.
Where it goes next is a product, not just a bigger bot. The plan is a strategy vault: users deposit funds, and the optimized strategies - the zero-losing-combo configs, the confirmation gates, the 50ms order path - run in the background, the way a DeFi yield vault runs its strategies. There is a second path, an analytics platform that sells the scans and the backtests themselves. But the vault is the build. The latency war made the engine. The vault is what the engine is for.