Prediction-Market Trading Stack
I built a real-time trading stack for Polymarket's 5-minute crypto markets: a Bun platform, a Rust engine for the hot path and a research lab. The lab's answer: the market is efficient after fees.
- PERIOD
- May–July 2026
- PLATFORM
- Polymarket
- STATUS
- Not recommended for real money
Challenge
The markets are 5-minute Up/Down windows on crypto prices. A separate reference feed, published about once a second, picks the winner at the close.
Polymarket holds every marketable taker order on these markets for 250 ms and re-validates it before matching. Takers pay 0.07 × p × (1−p) per share. Makers pay zero, and the maker rebate is 20% of the taker fee.
The venue also changed. Polymarket’s V2 upgrade on 28 April 2026 broke V1 SDK signatures, so the platform moved to the V2 SDK.
Approach
Real-time trading on Polymarket’s 5-minute crypto markets. A Bun platform fuses price feeds from four exchanges with order-book streams, signs EIP-712 orders and redeems on-chain on Polygon. A Rust engine took over the hot path: 16 crates, signing in about 1 ms, order hashes byte-identical to the JS signer. Around it: on-chain wallet forensics, a copy-trading desk, and a research lab on a 1.7 GB lookahead-free cache with a Rust backtester.
The Bun platform started on 2 May 2026 as a BTC and Polymarket collector. It took the top 20 levels of Polymarket’s order book once a second and an exchange WebSocket price feed, resolved 5-minute windows and stored about 145 MB a day in SQLite. The Rust rewrite began on 29 May 2026 by clearing a cloned third-party skeleton. At that point all real strategy code lived in the Bun bot.
I used multi-agent workflows to build, audit and research. Parallel agents built two backend batches on isolated worktrees. An 18-agent V2 correctness audit with adversarial re-verification found signing and auth correct on the EOA path. In one research loop, each iteration ran a researcher and a data analyst in parallel, then a backtest engineer who tested one hypothesis with walk-forward validation.
Architecture
The Bun platform is one process. It wires the feeds, SQLite repositories, trading and backtest orchestrators, a WebSocket broadcaster and the HTTP API. The fused BTC price is the mean of three fresh exchange mids times a live USDT/USD basis from a fourth exchange. The hot-path client posts orders straight to Polymarket without the SDK, with EIP-712 signing and an HMAC over the exact JSON body. A copy-trade lane mirrors watched traders’ bets on non-5-minute markets and only logs in paper mode.
The Rust workspace, ez, has 16 crates, from core and signer to orchestrator, api and testkit. Its signer runs on the libsecp256k1 C library and caches the EIP-712 domain separator at construction. Tests pin the ORDER_TYPEHASH to the on-chain constant of the deployed exchange contract. The Bun repo’s rust-engine reuses crates copied from ez, minus the Postgres-dependent ones. Its signer produced the same EIP-712 hash as the JS signer for the same order. A flag lets it own entry placement per experiment, while Bun still settles, redeems and recovers.
The research lab reads a columnar “golden” cache that holds one row per window and second, every print, per-window outcomes and a 1-second fused BTC price. The row for second s holds the book state at the window’s open plus s, and its flow columns cover the second that ends there, so there is no look-ahead by construction. The cache covers 145 days and 40,337 windows. The Rust backtester, rust_bt, rebuilds per-second top-of-book and depth with polars and rayon, in under a second per test.
pub struct Secp256k1Signer {
/// The raw private key bytes (zeroized on drop, never logged).
secret: SecretBytes,
/// Derived EOA address (public).
address: Address,
/// EIP-712 domain separator, cached at construction.
domain_sep: B256,
/// Reusable context object — `secp256k1::SECP256K1` global static is
/// already shared, but we hold a typed reference for ergonomics.
ctx: Secp256k1<secp256k1::All>,
}
Decisions
- I downgraded ethers from v6 to v5. Orders signed with v6 signTypedData were rejected with order_version_mismatch.
- The salt is masked to 53 bits and sent as a JSON number. A 256-bit decimal string round-trips to a different value through a JS-number backend, and the signature is rejected.
- Cost basis is reconciled every 30 s from on-chain Transfer logs, off the hot path, because the V2 API under-reported the average fill price by about 3–5%. An earlier balance read added 100–200 ms to the hot path, so I reverted it.
- Key decryption and API-key derivation stay in Bun, to avoid a risky crypto port. Bun writes the decrypted EOA key and L2 credentials to a 0600 root-only file, and Rust never sees the master key.
- A 30 s reconciliation loop halts trading when Polymarket reports orders the database does not know about. It treats that as a possible key compromise.
- Copy targets skip 5-minute markets, because those windows are a latency race the platform loses.
- Before real money, a July research synthesis set pre-committed kill criteria: stand down if the per-bet edge drops below +$0.50 per $10, if fewer than 60% of days are positive, or if fills run more than 3c past the seen ask on more than 20% of signals.
Outcome
The handoff worked. On 1 June 2026 the rust-engine placed a real $0.5 order, place_ms=362, filled on-chain, and Bun adopted it into the UI. In Bun, a keep-alive pool, API-key derivation at start-up and TLS pre-warming cut order latency from about 3 s to under 500 ms.
Several edges turned out to be artifacts. A six-agent research round found that 99.6% of “value” trades had fired on fictional crossed books, so fill realism became a mandatory gate in rust_bt. Two look-ahead leaks in the ETL, one worth up to 60 s of future data, had fabricated edges until I fixed them. Active market making tested at −5.10% per dollar, with 0 of 145 days positive over 3.9M fills.
For wallet forensics, a wallet lab computes a wallet’s settle-based PnL, win rate, daily equity and entry timing. Across 225 windows, 1.74M fills and 20k wallets, takers as a class lost $177k, the fee pool, and makers made $28k before rewards. Replicating the top wallets’ two mechanisms on 145 days found both dead. The verdict: the BTC 5-minute market is efficient at the level at which it can be traded, and nothing tested is recommended for real money.
> OUTPUT: Agent swarms tested about 1,500 hypotheses under pre-registered rules. The honest result: the market is efficient after fees. Bots paused. The rigor stayed.
ALL_FRAMES
FIELD_NOTES
- Guardrails as skills: no backtest gets reported until the checks pass Guardrails as skills: my Claude Code skill won't let agents report a backtest until data validation, bias guards and multiple-testing corrections pass.
- The market was efficient after fees: an honest negative result Agent swarms tested about 1,500 hypotheses under pre-registered rules. Several edges turned out to be bugs. The market was efficient after fees.