Market Making · Game Theory · ML · Oct 2026

Making Markets on Dice: the Everesteer Dice Market Challenge

For six days at the end of October I wrote, rewrote and threw away about 25 versions of a trading bot for a game with no customers, no noise traders and no free money — just five bots quoting prices on the sum of twelve dice, each trying to take points off the others. This is what I built, what the leaderboard taught me, and the part I found most humbling: how easy it is to fool yourself when your opponents can adapt.

The game

Everesteer ran the Dice Market Making challenge as a hiring challenge for roles in Budapest and London: agents play each other on a public leaderboard, and the top 20 get an interview. The rules fit on a page:

Every quote is a confession

The first thing I worked out on paper is what you actually know. Each die has variance 35/12, so the sum of all twelve has a standard deviation of about 5.9. Seeing your own two dice barely helps — ten unknown dice still leave a standard deviation of about 5.4. But if you could somehow learn the other seats' dice, only the two unseen dice would remain, and the uncertainty collapses to about 2.4.

You can't see their dice, but you can see their quotes, and a bot that holds high dice tends to quote high. So the real game isn't predicting dice; it's reading the table while deciding how much of yourself to give away. Quote aggressively on your own dice and you trade early on your edge, but you also hand every other seat a free look at your hand. Quote blandly and you keep your secret, but you earn nothing with it.

Reading the table

My first bot (bot1) was built around a decoder. For each opponent and each step, a small ridge regression learns within the match how that seat's quotes map to its two-dice sum. The dice are all revealed after every round, so the decoder gets a fresh labelled example each time and keeps sharpening for the rest of the 200 rounds.

The early versions mostly improved that reader. bot2 and bot3 started the regressions from priors fitted on public leaderboard replays instead of from zero. bot5 added a small neural network that corrects the regression's estimates, and by bot17 that correction was a two-layer, 64-unit network per step, trained on 1,500 replayed matches. The gain from the network was real but modest: when I later measured a refreshed version on held-out matches, it cut the error in the estimated value from 3.31 to 3.23 at step 2, and from 2.90 to 2.87 at step 3. The decoder was never the bottleneck for long.

Building an arena to watch it happen

Staring at JSON replays only gets you so far, so alongside the bots I built a small browser arena: one self-contained page with the game engine reimplemented in JavaScript, following the same take, replace and cross order as the official one. It let me do three things the starter pack couldn't.

Sit at the table myself. You get two dice, see every seat's quote and the resting book, and send your own bid and ask each step against bots — including a port of my first bot and copies of the house agents. Playing rounds by hand makes the stale-quote problem very concrete: you feel your old quote get picked off before your update lands. Friends can join a live table too: the host's browser runs the match, and each player's dice and quotes are encrypted between them and the host.

Arena play view: my two dice, every seat's sent and resting quotes, and the order book with each seat's bid and ask
Playing a live table: my dice on the left, every seat's quotes in the middle, and the resting book below.

Replay any match step by step. Simulated matches, live tables and backtests from my Python harness (exported as .jsonl) all open in the same viewer, with every die revealed, the tape of trades for the round, and cumulative P&L for each seat.

Arena replay viewer showing all twelve dice, each seat's position and total, the trade tape and a cumulative profit chart
The replay viewer: all twelve dice, each seat's position and running total, the round's trades, and cumulative P&L.

Run quick tournaments. With every seat set to a bot, matches run at full speed and the page tallies profit per round and head-to-head wins. Against these simple opponents, the port of my first bot already won comfortably — which is exactly why it was never the test that mattered.

Arena simulation results over 20 matches: Bot 1 earns +3.23 per round and beats every other seat in most matches
Twenty simulated matches against the house-style bots. Easy opponents make any decoder look good.

Learning from the leaderboard

Every match on the leaderboard was published as a replay, so the other bots' behaviour was public even though their code wasn't. A lot of my progress came from studying what the top agents actually did and asking why it worked.

The meta moved every day. A rule that was optimal on Monday was a weakness by Wednesday, because everyone was reading the same replays I was.

The real enemy: your own stale quote

The rule that cost me the most was the one I'd skimmed: your old quote is still live while the others send their next orders. You cannot cancel it in time. If someone learns something at step 2 that makes your step-1 ask look cheap, they buy it before your update arrives.

One example from my match logs: an opponent opened with a bland 40/44 while holding a two-dice sum of 12 — the maximum. My step-1 ask rested at 49.5. At step 2 they bought it. The round settled at 59: a 9.5-point loss on one unit, from a quote that looked perfectly reasonable when I sent it.

Once I measured this properly, it explained my plateau. Over one afternoon of live matches my bot earned about +0.63 per round by taking other seats' resting quotes, and gave almost exactly that back (−0.63 per round) when its own resting quotes were taken. The leader at the time earned +0.86 taking and lost only −0.38 being taken. The gap between us was almost entirely adverse selection.

Simulating the book instead of guessing

So the last versions stopped treating a quote as "my estimate ± a spread" and started treating it as a decision under uncertainty about what everyone else would do:

On 306 later public matches that the model never saw, this version (race22) improved on my earlier live bot by +0.135 points per round (95% interval +0.100 to +0.171) and won 65% of its pairwise comparisons, against 57.5% before. A further refinement trained on stronger tables reached a 70.6% pairwise win rate on 232 untouched matches.

The part where I fooled myself

Those numbers look great, and I had to keep reminding myself what they could and couldn't show.

Replays don't fight back. When you test a new quote against a recorded match, the other seats keep doing exactly what they did in the recording. In a game where the whole point is reacting to each other, that's a big blind spot. Against the strongest tables (opponents averaging Elo 1350+), even my best version was still losing about 0.5 points per round.

Self-play can lie in the opposite direction. Late on, I tried a deceptive opening: with a middling hand, deliberately signal the wrong direction, then trade against anyone who believed it. In simulations against my own bots it added about +0.22 points per round. Then I tested it against simple bots that ignore signals entirely, and it lost 0.27 per round — there's no point bluffing someone who isn't listening. The final version only bluffs after checking, live, that the table actually reacts to opening quotes.

Most of my late ideas didn't survive at all. Out of about twenty variants I tried in the last day, nearly every one was flat or worse on validation — wider quotes, retrained value models, targeting the opponents closest to me in the standings. I'm glad I kept a written log of the failures; it's what stopped me from shipping the ones that only looked good in-sample.

Where I ended up

At my last snapshot before the deadline, my active version sat at rank 18 on the leaderboard with Elo 1361, earning about +0.49 points per round across 38 matches I could verify against my own code. Not first — the top of the table was genuinely better at the late-step game than I was — but well clear of where bot1 started.

Takeaways

Written after the challenge closed. Results are from my own logs and the public replays; offline test figures are simulations, not live results. No agent code is shared here, in line with the competition's fair-play rules.
All posts Next: OverTheWire Utumno