Alexandre Dalban

Work · competition · 4 min read

IMC Prosperity 4

The competition ran for sixteen days over five rounds. Each round we submitted one Python file that traded against the exchange's bots. Our team of three finished 458th of 18,803 teams. The part I'd show is the infrastructure we built to get there.

Dates
14 to 30 April 2026
Status
Finished at rank 458, Finalist Crew, with 338,701.77 XIRECs (the competition's fictional currency)
Role
One of a team of three. I wrote about three quarters of the commits, including the simulator, the optimiser framework and the rounds 1 to 3 trader. The Rust was written by Claude Code under my direction. Credits.
Code
github.com/ADnocap/IMC_Prosperity4, public
IMC's Prosperity 4 Finalist Crew card for team I am Maximus: ranking 458, P&L 338,701.77 XIRECs.
The competition's result card for our team. Source: IMC's participant media kit; the 18,803 team count is IMC's.

Simulate the exchange, do not replay it

The competition's markets are made by a few scripted bots around a hidden fair value. Replaying the historical CSVs assumes our orders wouldn't have changed anything, so we reverse-engineered the bots and simulated the market instead. The trick came from the open-source tooling. Submit a trader that buys one unit at the first tick and holds, and the exchange's own P&L log gives away the fair-value path.

A brute-force search over quoting formulas then matched the bots' published quotes exactly, 99.7 to 100% for the two bots on one product and 99.9% and 99.0% on another. A non-obvious finding was that one set of bots quoted proportional offsets, floor(FV × (1 − 3/4000)), instead of fixed ticks. On matched fair-value paths the simulator reproduced the portal's P&L to within 0.8% in total.

The simulator we inherited was a single Rust file. Working with Claude Code, I refactored it into a per-asset plug-in architecture and added the twelve round-3 products. For round 5 I rebuilt it as a two-layer scenario engine. The first layer generates joint fair-value paths that honour the hidden constraints the data analysis had found. Five products summed to exactly 50,000. One pair's sum drifted only slowly, with a within-day standard deviation far below that of either leg. A triplet was tied by a looser weighted linear relation. The second layer is three shared Poisson trade-burst processes across 50 products, because trades arrived in synchronised bursts and not as 50 independent streams. Across those changes the simulator grew from 1,872 to 5,923 lines.

Sum of five product mid-prices over 30,000 ticks hugging 50,000 with a standard deviation of 2.8, and a log-scale histogram of the deviations.
The round-5 hidden constraint. The mid-prices of five products sum to 50,000, with a standard deviation of 2.80 over 30,000 ticks. The principal-components analysis that found it had a smallest eigenvalue of 1.57, against 187,287 for the next one. Source: round-5 exploratory analysis, 28 April 2026.

On top of the simulator sat an Optuna optimiser. It splits seeds into train, validation and test sets, retests on fresh seeds at the end of every study, and runs anti-overfitting diagnostics: deflated Sharpe ratio, probability of backtest overfitting by combinatorial cross-validation, cluster stability and fANOVA. Round 3 ran five studies and 2,224 trials. The trader we shipped was the out-of-sample winner of the second study (deflated-Sharpe probability 1.0, overfitting probability 0.03).

A browser workshop for the analysis had thirteen market-microstructure kernels (microprice, queue imbalance, order-flow imbalance, mark-outs by counterparty, effective versus realised spread, lead-lag, pair spreads with half-life) compiled from Rust to WebAssembly, running in a web worker. A panel took 5 to 50 milliseconds on 580,000-row rounds.

Results by round

RoundAlgorithmic P&LManual P&LPosition after round
199,54687,995432
298,726204,355176
3 (leaderboard reset to zero)11,14166,2291,998
470,32057,684716
5 (final)66,247≈ 67,081458
P&L in XIRECs. The position after round 2 is the interim rank that sometimes gets quoted for this team. The leaderboard restarted from zero for rounds 3 to 5, and 458 is the final rank. The round-5 manual P&L is inferred from the final total.
Portal scorecard for round 4: position 716, algorithmic result plus 70,320 with a cumulative P&L curve that dips early and recovers, manual result plus 57,684.
The round-4 scorecard, our strongest algorithmic round after the reset. It dipped early to about −35,000 and recovered to +70,320 over 10,000 ticks. Source: competition portal screenshot.

Five things we killed

There's also one lesson about the tooling. The portal's 1,000-tick preview backtest is dominated by price-path luck. Our round-3 trader showed +17,449 there and made +11,141 in the 10,000-tick final, and the first thousand ticks of the final contributed +817.

Credits

We were a team of three, and I wrote about three quarters of the commits. My parts were the simulator refactor and its round-3 and round-5 calibrations (my teammate contributed the round-5 triplet factor model), the Rust/WASM analysis workshop, the optimiser framework, the main trader line for rounds 1 to 3, and the manual-challenge solutions. My teammate led the round-1 bot calibration, the round-4 shipped trader and much of round 5. The backtester and visualiser skeleton came from an open-source release by another team, which we adapted and credit in the repository. All of the Rust and WebAssembly code was written with Claude Code.