A limit order book matching engine (price-time priority) and a market-making bot, built from scratch in Python — the same mechanics behind prop trading firms like Optiver, IMC or Da Vinci Trading. Built incrementally, weekend by weekend, tested with pytest at every step.
Every number on this page is the actual output of the project's 50-seed evaluation sweeps — nothing here is simulated for the page itself. For a live, interactive version of the matching engine, see the in-browser demo.
Single-run check (seed = 42, 200 ticks): with no inventory management, the bot drifted to -279 unitsof inventory and a PnL of -38.71. A large cash balance (+27,984.05) hid an equally large liability — the 279 units it still "owed" at market price. Raw cash is a misleading number on its own; PnL (cash + inventory marked to the current price) is what actually matters.
| Run | Inventory | Cash | PnL |
|---|---|---|---|
| No skew | -279.0 | 27984.05 | -38.71 |
| skew = 0.001 | -10.0 | 993.68 | -11.72 |
| skew = 0.01 | 0.0 | -27.59 | -27.59 |
Note skew = 0.01 looks "worse" than skew = 0.001 on this one seed — that's exactly the trap the 50-seed sweep below catches: a single run can't tell you which coefficient is actually better.
evaluate_skew.py reruns the bot across 50 random seeds per candidate skew_coefficient, and reports the mean PnL, its standard deviation, and the mean absolute inventory. Across 50 seeds, skew = 0.001 turns out to be mediocre — the earlier single-seed result favouring it was noise from one lucky run.
Inventory risk drops steadily as skew increases, but PnL only improves up to ~0.01, then gets sharply worse (-15.62 → -365.39) even though inventory barely changes further. Beyond that point the bot overcorrects — quoting so aggressively to stay flat that it gives away edge on every trade for no extra risk reduction. skew_coefficient = 0.01 is the value now used by default.
With skew_coefficient fixed at 0.01, evaluate_volatility.py swept vol_coefficientacross 50 seeds each. PnL keeps improving as it grows — but that's a trap, not a win.
Past a certain point the spread becomes so wide the bot's bid and ask never get crossed by anyone — it stops trading almost entirely. A bot that never trades never loses money, but it also isn't a market maker anymore.
| vol_coefficient | mean trades / 200 ticks | % of baseline |
|---|---|---|
| 0 | 47.8 | 100% |
| 100 | 42.7 | 89% |
| 200 | 40.8 | 85% |
| 500 | 35.6 | 74% |
| 1000 | 29.1 | 61% |
| 5000 | 9.8 | 20% |
vol_coefficient = 200 was chosen because it keeps ~85% of normal trading activity — a genuinely functioning bot — while still meaningfully trimming risk in volatile periods, instead of chasing the highest PnL number at the cost of barely participating.
The simulated market is pure noise — every incoming order is a coin-flip side at a random price around the true price, with no informed trader on the other side. A market maker with no informational edge should not expect to profit from noise alone; the spread it earns is offset by the risk of the reference price drifting against its resting inventory between quotes. Skew and volatility-based spread reduce that risk and its variance — they don't manufacture edge that isn't there. Real market makers profit through scale, faster reaction times, and detecting informed ("toxic") flow to avoid, all out of scope here. A PnL close to break-even with low variance is the expected, defensible outcome for a correctly risk-managed bot in a directionless toy market.
The matching engine, reimplemented in JS, running live in your browser.