Built a Bot That Trades the World Cup on Polymarket. Here’s What I Learned.
Rust, prediction markets, and the uncomfortable truth about “alpha” in 2026.
Polymarket hit $50 million in daily trading volume last week. Most of it was people betting on whether France beats Brazil and who wins the Golden Boot.
I was one of them-except I wasn’t clicking buttons. My bot was doing it for me, in Rust, at around 800 microseconds per decision loop.
This is the story of how I built it, what worked, what embarrassingly didn’t, and why I think prediction markets are the most interesting trading venue for developers right now.
Why prediction markets, of all places?
If you’re a developer who’s ever thought about algorithmic trading, your first instinct is probably crypto: liquid, 24/7, API-accessible. Your second instinct is probably equities, if you have the regulatory patience. Prediction markets are rarely anyone’s first instinct.
They should be.
Here’s what makes Polymarket unusual as a trading venue: every market is a binary. You’re not modeling a continuous price process-you’re estimating a probability. Will France win the World Cup? Will the Fed hold rates in June? Will Bitcoin hit a new all-time high by December 31?
Each market resolves to 1 or 0. Shares trade between $0.01 and $0.99. A share at $0.30 is the market saying there’s a 30% chance the thing happens. If it does happen, your share pays $1.00.
For a developer with a quantitative mindset, this is a completely different game than traditional trading. You’re not predicting price direction. You’re finding markets where the crowd’s implied probability is wrong-and you have a reason to believe it.
The World Cup is a perfect example right now. <cite index=”11-1">Polymarket’s top markets are dominated by FIFA World Cup 2026 and geopolitical events, with the platform seeing combined 24-hour trading volume of $50.4 million and $21.7 million in liquidity.</cite> With that much money sloshing around sports markets, the mispricings are real and they move fast. My bot is there to catch them.
The tech decision: why Rust, and why it was annoying, and why I’d do it again
My first prototype was Python. Median decision loop: 48ms. P99: 120ms.
In a liquid market with automated competition, 48ms is a lifetime. By the time I see an update, evaluate it, and submit an order, the mispricing is gone. Someone faster already took it.
I tried Go next. Better-around 8ms median. But Go’s garbage collector still gave me occasional 5-15ms pauses that I couldn’t predict or control. In a fast-moving match market, those pauses happen at exactly the wrong time.
Rust: 800 microseconds median. No GC pauses. Deterministic.
The borrow checker is everything they say it is-both the frustrating part and the reason it works. In the first two weeks, I spent more time arguing with the compiler than writing logic. At one point I had a Vec<PriceLevel> that I was trying to both iterate over and modify in the same function, which is obviously a data race waiting to happen, and Rust just wouldn't let me. In Python I would have done it, shipped it, and debugged a corruption bug three weeks later at 2am. In Rust, the compiler told me at compile time.
That’s the trade. You pay the complexity upfront. The runtime is clean.
The actual stack:
tokio = { version = "1", features = ["full"] } # async runtime
tokio-tungstenite = "0.21" # WebSocket
reqwest = { version = "0.12", features = ["json"] } # REST client
rust_decimal = "1" # NEVER use f64 for prices
serde_json = "1" # message parsingAnd the release profile that matters more than most people realize:
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"lto = true lets LLVM optimize across your entire codebase at link time. codegen-units = 1 is slower to compile but produces a faster binary. panic = "abort" removes the stack-unwinding overhead. Together these gave me another 15–20% on top of a vanilla release build.
The architecture that didn’t blow up
The thing I got right on the second attempt-after the first attempt was a mess of shared Arc<Mutex<OrderBook>> everywhere-was going channel-first.
Every component is a Tokio task. They never share memory. They talk through mpsc channels.
WebSocket feed → channel → Strategy engine → channel → ExecutionThe feed task does one thing: connect, parse incoming messages, forward them. The strategy task does one thing: receive market updates, evaluate signals, emit orders. The execution task does one thing: receive orders, hit the REST API.
No locks on the hot path. No shared mutable state. When I need to add a new feature-say, a risk manager that can cancel orders when drawdown exceeds a threshold -I add another task and another channel. Nothing else changes.
Get Casatrick - Web3 Trading Systems’s stories in your inbox
Join Medium for free to get updates from this writer.
The order book update itself is deliberately simple:
pub fn apply_update(&mut self, side: Side, price: Decimal, size: Decimal) {
let levels = match side {
Side::Bid => &mut self.bids,
Side::Ask => &mut self.asks,
};if size.is_zero() {
levels.retain(|l| l.price != price); // remove the level
} else {
if let Some(level) = levels.iter_mut().find(|l| l.price == price) {
level.size = size; // update existing
} else {
levels.push(PriceLevel { price, size }); // add new
}
} match side {
Side::Bid => levels.sort_by(|a, b| b.price.cmp(&a.price)),
Side::Ask => levels.sort_by(|a, b| a.price.cmp(&b.price)),
}
}
Delta updates, not full rebuilds. Polymarket’s WebSocket sends diffs. The naive approach-replace the whole book on every message-adds milliseconds per update. apply_update runs in 4 microseconds median.
The strategy: what I can actually tell you
I’m not going to publish my exact signal logic-that would be giving away the edge. But I can describe the class of strategies that work in prediction markets, because it’s genuinely different from traditional finance.
Calibration arbitrage. The market’s implied probability is wrong relative to base rates. World Cup match markets are a good example-the crowd systematically overestimates upset probabilities in group stage matches. You can back-test this with historical Polymarket data.
Liquidity-driven mean reversion. In illiquid markets, a large single order can push the price 5-10 cents in one direction. That move often partially reverses. Fast bots catch the reversal. This is essentially the same as statistical arbitrage in equity markets, but the binary structure makes position sizing cleaner.
Resolution timing. Markets that are about to resolve get mispriced in specific ways. A market at $0.97 with 10 minutes to go is almost certain to resolve at $1.00, but you can still buy it at $0.97 and capture the spread. The risk is obvious-if the event outcome flips in the last 10 minutes, you lose. Managing that tail risk is the whole game.
What doesn’t work: pure technical analysis. There’s no “resistance level” at $0.65 on a binary market. The price is an estimate of a probability, and probability has a prior. If you’re not thinking in terms of probability calibration, you’re just gambling with extra steps.
The World Cup angle
<cite index=”11–1">Daily trading activity on Polymarket reveals its highest-volume markets are currently driven by sports and geopolitics</cite>, and the World Cup is eating the platform right now. That creates specific opportunities and specific problems for automated trading.
The opportunity: match-day markets move fast. Goals, red cards, VAR decisions — each one reprices instantly across a dozen correlated markets (match result, next goal scorer, total goals, halftime result). A bot that can process the feed and react to correlated repricing faster than human traders has a genuine edge.
The problem: information asymmetry is brutal. If a goal goes in and you’re depending on Polymarket’s WebSocket feed to know about it, you’re already behind everyone watching the match on TV. The people who win consistently in sports prediction markets either have faster data feeds, or they’re not playing the live-game markets at all-they’re playing pre-match markets where the edge is analytical rather than speed-based.
I mostly play pre-match and pre-event markets for this reason. The edge I have is quantitative modeling, not co-location next to a stadium.
TypeScript for the parts Rust is bad at
<cite index=”1-1">TypeScript became the most-used language on GitHub by contributor count in August 2025, surpassing Python by roughly 42,000 contributors</cite>, and if you’re building a trading system in 2026, there are genuinely things TypeScript does better than Rust.
Dashboard and monitoring: I have a Next.js frontend that shows live PnL, open positions, order history, and strategy signals in real time. Writing this in Rust would be masochism. TypeScript with tRPC connecting to a lightweight Rust backend API is the right split.
Rapid strategy prototyping: new signal ideas start in TypeScript, running against historical data. If they look promising, I port the signal logic to Rust. The type system is good enough for financial logic, and the iteration loop is much faster.
Configuration and orchestration: market subscriptions, strategy parameters, risk limits-all JSON, loaded at startup, hot-reloaded when the file changes. TypeScript handles this with zero ceremony.
The architecture is Rust for the latency-critical path and TypeScript for everything that needs to move fast in a different sense-fast to write, fast to iterate, fast to visualize.
The honest numbers
After running this for several months:
- Strategy win rate: 58%. That sounds low but compound it over enough trades and it’s meaningful.
- Average hold time: 4 hours. I’m not high-frequency in the traditional sense.
- Biggest single loss: a market where I modeled a 15% implied probability as underpriced-I had it at 8%-and the 15% event happened. That’s not a bug, that’s prediction.
- Biggest single win: a geopolitical market that repriced from $0.22 to $0.88 overnight on a news event I had modeled. The bot had a position.
The Rust infrastructure cost me maybe three weeks of initial development. The strategy work-backtesting, calibration, deciding which markets to play and which to avoid-is ongoing and probably always will be. That’s the actual work.
What’s next
The World Cup ends in mid-July. After that, Polymarket’s volume will drop and redistribute. I’m already looking at US political markets for the 2028 cycle, which will start heating up late this year, and crypto price markets which run year-round with consistent liquidity.
I’m also experimenting with a casino game engine in Rust-provably fair random number generation, game state management, payout calculation. The latency requirements are similar to trading (you don’t want a dice roll taking 200ms), and a lot of the infrastructure-async tasks, channel-based communication, decimal arithmetic-transfers directly.
If you’re building in this space and want to compare notes, I’m reachable in the comments. The ecosystem of developers working on prediction market automation is surprisingly small, and the conversations I’ve had have been worth more than any blog post.
Tags: rust, trading, prediction-markets, polymarket, typescript, system-design, algorithmic-trading








