Somebody wants to buy. Somebody wants to sell. Neither of them knows the other exists. Microstructure is the study of the machinery that turns those two latent wishes into a printed price and a number of shares — and of what that machinery charges you for the service. Every price you have ever seen came out of the object on this page.
s = αE + c + I.shared/orderbook.py
Impact model from exchange/price_engine.py
Liquidity is a price, not a property
Market microstructure asks how traders’ latent demands become prices and volumes. Asset pricing asks what something is worth; microstructure asks what happens between wanting it and owning it — and answers with measurable quantities: a spread in basis points, a depth in dollars, a decay constant in seconds.
How information gets into prices. Every trade leaks something about what the aggressor believes. Microstructure measures how fast and how accurately that belief is absorbed — and who pays for the absorption.
Can you trade size without moving the price? It has three dimensions people routinely conflate: width (the spread), depth (size at a price), and resiliency (how fast the book heals after you hit it).
Explicit — commission, exchange fee, clearing. Implicit — half the spread, market impact, and the fills you never got. The implicit ones are typically two to ten times the explicit ones, and they are the ones nobody puts on a confirmation.
Tick size, matching rule, fee schedule, halt logic, whether the book is visible. These are choices, made by someone, with consequences you can measure. In this course you make them.
A market maker quotes to informed and uninformed traders without knowing which has arrived. That single fact generates the bid-ask spread, the inventory skew, and most of the interesting mathematics in the field.
One class, no I/O:
shared/orderbook.py. It holds the book, applies the priority rule, prints trades at the
resting price, and refuses to match a team against itself. Everything below runs those same rules.
This is a real book with real rules. Post a limit order and
watch where it lands in the queue; send a market order and watch it walk the ladder, printing at each
resting order’s price rather than yours. YOU is your team — try to trade
against your own resting order and see what self-trade prevention does.
Two mechanisms do most of the work. An order type is your instruction about which certainty you are willing to give up. A spread is the market maker’s price for giving up the other one. Both are arithmetic, and both are on this page.
Pick a type. The card tells you what it guarantees and what it refuses to guarantee; pressing Send this into the book runs it against the live ladder above, so you can watch a post-only order get rejected for crossing and an FOK cancel itself rather than fill you partially.
A market maker quoting a half-spread s earns
s from an uninformed counterparty and loses E − s to an informed one. If
informed traders are a fraction α of the flow, breaking even requires
(1−α)s − α(E−s) = c + I, which collapses to a formula you can hold in your head:
# the breakeven half-spread a market maker must quote s = α·E # adverse selection: informed share × informed edge + c # order processing: exchange fee, clearing, tech, per share + I # inventory risk: what it costs to hold the position it just took on # quoted spread = 2s. Below breakeven, the market maker loses money on every share.
The exchange pays you to post and charges you to take. That looks like an argument for always posting — until you price the two things posting costs you: the fills you do not get, and the fills you do get precisely when you did not want them.
Here is where microstructure stops being vocabulary and starts costing money. Your order moves the price. Some of that move is permanent, because you told the market something; the rest decays, because you only borrowed the liquidity. Telling the two apart is the difference between an execution desk and a rumour.
The engine uses the model real venues use:
impact = k·√notional, of which PERMANENT_FRACTION = 0.30 shifts fair value for
good and the remaining 70% decays at MEAN_REVERSION_SPEED = 0.10 per tick. Split the same
parent order into more children and the square root does the work for you — up to the point where waiting
costs more than impact.
These six numbers are recomputed from the live ladder in Part 1
and from your most recent trade. Send a market order up there and watch every one of them move. The key
identity: effective spread = realised spread + price impact — what you paid splits into what
the market maker kept and what your own information moved.
Two identical limit orders at the same price are not the same order. The one that arrived first fills first, and it fills more often. Move the latency slider: your order does not change, but the number of shares in front of it does — and so does the probability that the queue ever reaches you.
Every rule above assumes a functioning book. Fire a shock large
enough and the venue stops pretending: the entry collar rejects the print, then the velocity halt stops
trading altogether. This is exchange/circuit_breaker.py, three layers, with the arena’s own
thresholds.
These are the microstructure mistakes that turn a profitable backtest into a losing bot. Open them.
Your signal fires, the backtest fills you at the midpoint, and the equity curve is beautiful. In
the live market a buy fills at the ask and a sell at the bid, so every round trip pays the full
spread — plus the taker fee. At the arena’s TAKER_FEE_RATE = 0.0015 that is 15 bps per
side before the spread, so a strategy turning over its book ten times a session
needs 300 bps of gross edge just to break even on fees.
The mirror error is assuming your limit orders always fill. A backtest that fills every passive order is not modelling a market maker; it is modelling a wish.
Fix: fill takers at the far touch plus half a tick of slippage, and fill makers only when the price actually trades through your level, not merely to it. Then subtract fees explicitly. If the edge survives, it is real.
Best bid 100.01 for 5,000 shares, best ask 100.02 for 100 shares. The mid says 100.015. The
microprice — (bid × ask_size + ask × bid_size) / (bid_size + ask_size) — says 100.0198,
because the weight on each price is the size on the other side. Almost anyone who
trades next will lift that thin offer.
A mean-reversion signal computed off the mid will read that book as “the price is at fair value”. It is not. It is one small order away from a penny higher, and your model just told you to sell.
Fix: use the microprice as the reference — it is what price_engine.py
uses, weighted MID_BLEND_WEIGHT = 0.85 against the fundamental. Guard against crossed or
one-sided snapshots by keeping the previous reference rather than averaging garbage.
You compute a spread from volatility and inventory, you quote it, you get filled constantly, and
you lose money on almost every fill. The reason is in the formula above: the fills you get are not a
random sample of the flow. When you are picked off, it is by the trader who knew the price was about
to move — that is what α·E is, and if you omit it you have priced only the risk you can
see.
The symptom is diagnostic and easy to log: your fill rate is high and your one-second mark-out is consistently negative. A market maker with an excellent fill rate is usually being adversely selected.
Fix: measure mark-outs at 1 s and 10 s per counterparty, widen or skew when the flow turns toxic, and treat a suddenly rising fill rate as a warning rather than a success. Toxic-flow detection is a Level-5 broker unlock for exactly this reason.
You are resting at the touch with 200 shares ahead of you. The fair value ticks by a hair, so your
bot cancels and reposts at the same price to “refresh”. It now has 2,400 shares ahead of it, because
seq — the arrival counter that is the real time-priority key — was reset. You gave away a
queue position that took real time to earn, for nothing.
Worse, some venues charge for it. The arena has CANCEL_FEE_PER_ORDER precisely so that
a bot which cancel-replaces on every tick pays for the message traffic it generates.
Fix: only move an order when the price must change. Reduce size in
place if you can (many venues keep priority on a downsize but not an upsize), and inspect
queue_position(order_id) before you touch anything.
You buy, the price goes up, you record a win. But 70% of that move was your own temporary impact and it decays at 10% a tick, so if you mark to market a second later you have a profit that does not exist. Run the same logic in a loop and you get a strategy whose backtest P&L is literally its own footprint.
The clean test is the one the impact widget above draws: compare your average fill against the price before you started and against fair value after the buffer has decayed. If the second number has not moved, you had no information — you had a footprint.
Fix: mark to a reference that excludes your own recent trades, measure P&L on mark-outs several seconds out, and report permanent impact separately in your TCA. Persistent permanent impact in your direction is alpha; anything that decays is a bill.
Three questions, instant feedback, nothing recorded.
Q1The best offer is 300 shares at $100.02. You send a buy limit for 300 at $100.05. At what price do you trade?
Q2Of the three components of a bid-ask spread, which is typically the largest for a liquid US equity?
Q3Your resting bid has 200 shares ahead of it. Your bot cancels it and immediately reposts the same size at the same price. What just happened?
s = αE + c + I from the breakeven condition, then say what happens to your
quotes when α doubles.queue_position(order_id) every time you consider a cancel-replace. Most of them
are not worth it.make install.