PART 5

Market Microstructure

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.

Matching rules taken from shared/orderbook.py Impact model from exchange/price_engine.py Liquidity is a price, not a property
Part 5.1

1What is it? Five questions, and one object

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.

Price discovery

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.

Liquidity

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).

Transaction costs

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.

Market design

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.

Information asymmetry

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.

Where it lives in code

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.

Interactive · anatomy of a central limit order book

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.

Best bid
—
Best ask
—
Mid
—
Microprice
—
Spread
—
Imbalance
—

Send an order

What the engine just did

Part 5.2

2How does it work? Order types, and where a spread comes from

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.

Interactive · the order-type explorer

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.

—

— —

Interactive · where a bid-ask spread actually comes from

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.

Breakeven half-spread, decomposed

Breakeven half
—
Your spread (2×)
—
On a $100 stock
—
P&L / 1,000 sh
—

Interactive · maker, taker, and why “free” liquidity is not free

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.

Take now
—
Post passively
—
Breakeven fill
—
Verdict
—
Part 5.3

3Why it matters: impact, measurement, queues, and halts

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.

Interactive · one metaorder, sliced

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.

Slippage
—
Permanent shift
—
Of which temporary
—
√-law estimate
—

Reading the chart

  • Amber — the mark you actually trade against: fair value plus the temporary impact buffer.
  • Blue — fair value alone. Where the price stays after your footprint decays. This is the part you told the market.
  • Each dot is a child fill. Your slippage is the gap between the average dot and the price before you started.

Market quality, measured on the book you just traded

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.

Interactive · queue position, and the price of a millisecond

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.

The queue at your price level

Shares ahead
—
Queue position
—
Fill probability
—
Expected fill
—

Interactive · when microstructure gives up: bands and halts

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.

Move in 60 s
—
Entry collar
—
Velocity halt
—
Session band
—
SSR
—
State
—
Part 5.4

4Five ways this goes wrong

These are the microstructure mistakes that turn a profitable backtest into a losing bot. Open them.

Backtesting fills at the mid

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.

Using the mid as the fair price when the book is lopsided

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.

Quoting a spread with no adverse-selection term

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.

Cancelling and replacing, and losing the queue

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.

Confusing temporary impact with alpha

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.

Part 5.5

5Check yourself

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?

Part 5.6

6Do the work

Before you write a bot

  • Compute the microprice by hand for a book with 5,000 on the bid and 100 on the offer, and say which side you would rather be resting on.
  • Derive s = αE + c + I from the breakeven condition, then say what happens to your quotes when α doubles.
  • Write down the effective, realised and quoted spread for one of your own fills, and check that effective minus realised equals the price impact.

Then, in code

  • Log queue_position(order_id) every time you consider a cancel-replace. Most of them are not worth it.
  • Record a 1-second and a 10-second mark-out on every fill. This single habit finds adverse selection before it finds you.
  • Separate permanent from temporary impact in your TCA report, and never mark to a price your own order just moved.
Code
Get the code
The starter repo is public: the real matching engine, the impact model and the circuit breakers used on this page. Use it as a template, clone, make install.
Previous
← Part 4 · Market Types & Structure
The venues this order book lives inside, and how orders get routed between them.
Home
Start again from Part 1
Five parts, from what systematic trading is to the machinery underneath it. Now go and build something that trades.