99 Francs
sales@99francs.agency

HQ Paphos, Cyprus · worldwide

How We Cut a Trading Bot's Reaction Time from ~2 Seconds to Milliseconds — by Moving Only the Hot Path to Rust

A live market-making bot on the Polymarket prediction market was leaking money to a slow control loop. We kept the quant logic in Python and moved only the latency-critical execution path into a Rust sidecar — cutting the reaction time on the most expensive event from roughly two seconds to the low tens of milliseconds.

How We Cut a Trading Bot's Reaction Time from ~2 Seconds to Milliseconds — by Moving Only the Hot Path to Rust

We build custom trading and automation systems for clients, and one of them — an independent quantitative trading desk running a market-making strategy on the Polymarket prediction market — had a very specific problem. The strategy was sound in backtests, but in production it kept leaking money on exactly one event: reassembling a position after a partial fill.

The temptation, when a Python system is too slow, is to rewrite the whole thing in Rust. That was the wrong instinct here. The problem was not the language — it was a slow control loop.

A slow control loop, not a slow language

A binary prediction market has two legs — YES and NO — that together redeem for exactly $1. A market maker rests bids on both legs, aiming to assemble the pair for less than $1. When the underlying price moves, a resting bid on the now-stale leg can get hit before the maker pulls it, leaving a one-sided “naked” position that eventually costs more than $1 to close. A completed pair earns a small, reliable amount; a single naked leg loses several times that. So the economics live entirely in how fast you can react.

The original engine was pure Python, and its decision tick ran on a multi-second poll. After the first fill of a pair, the follow-up (“repair”) quote appeared anywhere from zero to two-plus seconds later — the Python tick, plus a database write, plus reconciliation, plus telemetry. On a market that can move in well under a second, that gap was the whole problem. Notably, it was never about garbage-collector pauses or event-loop jitter: the measured cost was the poll interval and the bookkeeping around it. A slow control loop, not a slow language.

What we moved to Rust — and what we didn't

We split the system into two planes. The Python control plane stays the financial source of truth: strategy models (numpy/scipy), market discovery, EIP-712 order signing, accounting, risk stops, the dashboard and alerts. The Rust execution plane owns only the millisecond-sensitive work.

  • Order-book ingestion — the venue market-data WebSocket feeds a lock-free in-memory book cache, so the quoting loop reads both legs with no cross-thread contention.
  • Decision / quoting loop — a tight ~5 ms scan tick computes target quotes and a re-quote throttle, instead of a multi-second poll blind to sub-second events.
  • Order placement (place / cancel) — the cancel used to be a Python HTTP call; moving place and cancel into Rust removes that hop from the hot path. It wraps the venue's official Rust CLOB SDK rather than a hand-rolled client.
  • User-fill handling — an authenticated user-WebSocket parses fills straight into inventory with exact-integer accounting, so restarts or replays never double-count.

On-chain settlement, gas and the strategy brain stayed in Python. The two planes talk over a local Unix socket, and the sidecar is best-effort: if it drops, Python falls back to native handling, so it can never take the system down. The core migration — book, decision loop and place/cancel — landed in about two weeks; the broader multi-market execution plane followed over the weeks after.

The result: reaction time on the expensive event

What we measuredBefore (Python)After (Rust plane)
Reaction after a partial fill0–2+ secondslow tens of ms
Decision tickmulti-second poll~5 ms
Cancel pathPython HTTP round-tripremoved from the hot path
Strategy code rewrittennone

One honesty note, because latency numbers are easy to inflate: the headline figure — the fill-to-repair reaction — is the one we treat as real, backed by a sub-20 ms p95 in-process decision budget verified on recorded market data. The place, cancel and signing figures are engineering estimates from reading the SDK and venue behaviour; we use them as context, not as a benchmark. The cleanest way to state the win is that we cut the reaction time on the most expensive event from roughly two seconds to the low tens of milliseconds.

Anti-pickoff: why milliseconds pay for themselves

Speed only matters because of adverse selection. Because the order is fully formed and signed before it needs to be sent, the place/cancel path carries almost no compute — so when the Rust sidecar sees the underlying move against a resting leg, it can pull the quote in milliseconds instead of on the next poll. Pair that with an order-book imbalance guard that stops buying the leg the market has already abandoned, and enforced post-only quoting, and the window in which a stale quote can be picked off collapses. The full mechanics are their own topic; the point here is that the latency work is what makes the anti-pickoff logic actually fire in time.

Was two weeks realistic?

For the core hot-path migration, yes — because of heavy up-front spec-and-review discipline. Each milestone had a detailed design doc that went through several independent review rounds before implementation, and shipped in small, feature-flagged, reversible steps. The one part everyone feared — cryptographic pre-signing — turned out to be a non-issue on investigation (signing was already ~1 ms and not the bottleneck), which removed most of the risk. That is exactly what disciplined design surfaces early.

If you have a system that is fast enough to be dangerous but slow enough to leak money, that is the shape of problem we like: keep the correct parts, put the speed exactly where it pays for itself. See the full build in our Polymarket trading system case study, or read about our Python development and AI agents and bots work.

FAQ

Frequently asked questions.

Only when you are latency-bound — arbitrage or market-making where reaction time decides profit and loss. Expect a longer, more careful build for far better tail latency. For strategy research, backtesting and everything that is not on the hot path, Python is usually the better choice, which is why we kept it there.
In this project the measured win was on the fill-to-repair path: from roughly two seconds down to the low tens of milliseconds, with a sub-20 ms p95 in-process decision budget verified on recorded market data. The exact number depends on what was slow — here it was a multi-second control loop, not the language itself.
No. We moved only order-book ingestion, the decision loop and order placement into a Rust sidecar and left the quant models, signing, accounting and risk in Python. The two communicate over a local socket, and the Rust side is best-effort so it can never take the system down.
Work with 99 Francs

Need this done instead of just read about it?

99 Francs is a subscription-based design studio: one flat monthly rate, unlimited requests, first delivery in 1–2 days. Start with pricing or book a free intro call.