Market Microstructure
Chapter: 6.3 Market Microstructure Difficulty: Level 3 — Production (from-scratch derivations, code-from-memory, explain-out-loud) Time limit: 45 minutes Total marks: 60
Instructions: Show all working. Derivations must start from first principles. Where code is asked, write it from memory (Python/pseudocode acceptable, syntax leniency applies). Use notation for math.
Question 1 — Order Book Reconstruction & Depth (10 marks)
You receive the following resting limit orders for stock XYZ (price × size):
| Bids (price, qty) | Asks (price, qty) |
|---|---|
| (100.02, 300) | (100.05, 200) |
| (100.01, 500) | (100.06, 400) |
| (100.00, 800) | (100.07, 700) |
(a) State the best bid, best ask, mid-price, and quoted spread (in currency and in basis points). (4)
(b) Define cumulative market depth and compute the cumulative depth available on the ask side up to and including price 100.06. (3)
(c) A market buy order of 500 shares arrives. Walk through (explain-out-loud) exactly how the matching engine fills it, and compute the volume-weighted average execution price (VWAP) and the resulting new best ask. (3)
Question 2 — Price Impact from Market Depth (10 marks)
(a) From first principles, derive an expression for the execution cost (slippage) of a market buy order of size that sweeps ask levels, where level has price and available quantity (with ). Define slippage relative to the mid-price . (5)
(b) Assume a linear ask-side depth profile: price at cumulative depth is , where is Kyle's-lambda-style impact coefficient. Derive the total cost and the average price impact per share for buying quantity . (5)
Question 3 — Bid-Ask Spread Decomposition (12 marks)
(a) Name and explain the three canonical components of the bid-ask spread. For each, explain the economic mechanism in one or two sentences. (6)
(b) Derive the Roll (1984) implied spread estimator from the assumption that the effective half-spread bounces price around an efficient mid-price via i.i.d. trade-direction indicators with and independent of the efficient price. Show that and hence give the spread estimator . (6)
Question 4 — Adverse Selection & Glosten–Milgrom (10 marks)
A dealer faces a fraction of informed traders (who know the true value , each equally likely) and uninformed traders (who buy or sell with probability each, independent of ).
(a) Explain out loud why adverse selection forces a positive spread even when the dealer has zero inventory cost and zero order-processing cost. (3)
(b) Derive the dealer's ask price as using Bayes' rule. Then compute the numeric ask if , , and . (7)
Question 5 — Iceberg Orders & Auction Clearing (10 marks)
(a) Explain what an iceberg order is and, in an explain-out-loud manner, describe two ways a fast trader might detect one from the order book / trade tape. (4)
(b) A single-price call auction collects the following orders. Buy orders are cumulative-demand at-or-above price; sell orders are cumulative-supply at-or-below price:
| Price | Cumulative Buy Qty | Cumulative Sell Qty |
|---|---|---|
| 50.0 | 900 | 200 |
| 50.1 | 700 | 450 |
| 50.2 | 500 | 500 |
| 50.3 | 300 | 750 |
Determine the auction clearing price (the price that maximizes executable volume) and the matched (executed) quantity. Justify the rule you used. (6)
Question 6 — Latency, Co-location & Tick Size (8 marks)
(a) Explain out loud how co-location reduces latency and why sub-millisecond advantages are economically valuable at the top of the book. (3)
(b) A minimum tick size of \0.01$0.006$, explain what happens and describe the queue / time-priority effect this creates. (3)
(c) Give one reason exchanges use larger tick sizes for some securities despite raising trading costs. (2)
Answer keyMark scheme & solutions
Question 1 (10)
(a) (4)
- Best bid = 100.02, best ask = 100.05. (1)
- Mid-price . (1)
- Quoted spread = 100.05 - 100.02 = \mathbf{\0.03}$. (1)
- In bps: . (1)
(b) (3)
- Cumulative depth = total resting quantity available from the best price up to a stated price. (1)
- Ask side to 100.06 inclusive: . (2)
(c) (3)
- Fill 200 @ 100.05 (exhausts level 1), remaining 300 @ 100.06. (1)
- VWAP . (1)
- Level 100.06 had 400; 300 consumed, 100 remain. New best ask = 100.06 (100 shares). (1)
Question 2 (10)
(a) (5)
- Order sweeps levels until filled. Let be the level where is exhausted; for full levels take , at level take residual . (2)
- Total cash paid . (1)
- VWAP . (1)
- Slippage (cost vs mid) ; per-share impact . (1)
(b) (5)
- With continuous linear depth , cost to buy : . (3)
- Slippage . (1)
- Average impact per share (half the marginal impact ). (1)
Question 3 (12)
(a) (6, 2 each)
- Order-processing cost — fixed operational/clearing/exchange costs the dealer recovers per round-trip. (2)
- Inventory-holding cost — dealers demand compensation for price risk of holding unwanted positions and skew quotes to manage inventory. (2)
- Adverse-selection cost — the risk of trading against better-informed counterparties; the dealer widens the spread to offset expected losses to informed flow. (2)
(b) (6)
- Model: observed price , with efficient price a random walk, i.i.d. equal prob, independent of . (1)
- . (1)
- Assume white noise, independent of 's. Then Only overlapping term is . (2)
- So . Full spread . (2)
Question 4 (10)
(a) (3)
- Some counterparties know ; they buy only when (asset undervalued) and sell only when . (1)
- A buy order is therefore informative: it raises the conditional expected value. (1)
- Posting a single price = mid would systematically lose to informed traders, so the dealer must set ask > mid and bid < mid → positive spread, even with no processing/inventory cost. (1)
(b) (7)
- Prior . On a buy: informed buys iff ; uninformed buys w.p. . (1)
- . (1)
- . (1)
- Bayes: (2)
- Ask . (1)
- Numeric: : . (1)
(By symmetry bid = 99.6, spread = 0.8.)
Question 5 (10)
(a) (4)
- Iceberg = large order that displays only a small "peak" quantity; when the peak fills, a new peak is automatically refreshed from the hidden reserve. (2)
- Detection (any two): (i) Repeated refills at the same price — trades keep executing at a level whose displayed size seemingly never depletes. (ii) Post-fill instant reappearance of a same-size quote at that price. Also acceptable: pinging with small orders, timestamp regularity of refresh. (2)
(b) (6)
- Executable volume at each price = :
- 50.0:
- 50.1:
- 50.2:
- 50.3: (3)
- Maximum executable volume at price 50.2. (2)
- Rule: single-price auction chooses the price that maximizes matched volume (tie-breaks by minimum imbalance / reference price). Clearing price , matched qty . (1)
Question 6 (8)
(a) (3) Co-location places the trader's servers physically inside/adjacent to the exchange's matching engine, minimizing signal propagation and network hops → lower round-trip latency. At the top of book, order priority is time-based, so being microseconds faster wins the queue, captures fleeting price edges, and avoids being picked off on stale quotes.
(b) (3) Since \0.006 < $0.01$0.01$**. The excess (would-be) competition instead expresses itself through queue length: many traders post at the same best price and compete on time priority, so speed determines fills rather than price.
(c) (2) A larger tick reduces the incentive to "step ahead" by an economically trivial amount, thickening displayed depth/queues and reducing quote-flickering (lower message/adverse-selection load) — improving displayed liquidity stability.
[
{"claim":"Q1c VWAP for 200@100.05 + 300@100.06 over 500 = 100.056","code":"vwap=(200*100.05+300*100.06)/500; result = (vwap==100.056)"},
{"claim":"Q1a spread in bps approx 3.0","code":"bps=(0.03/100.035)*10000; result = abs(bps-3.0)<0.05"},
{"claim":"Q2b slippage for linear depth is (1/2)*lambda*Q^2","code":"from sympy import symbols,integrate; m,lam,x,Q=symbols('m lam x Q',positive=True); C=integrate(m+lam*x,(x,0,Q)); slip=C-m*Q; result = (slip.simplify()==(lam*Q**2/2))"},
{"claim":"Q4b ask price with alpha=0.4,VH=101,VL=99 equals 100.4","code":"a=0.4; VH=101; VL=99; ask=(1+a)/2*VH+(1-a)/2*VL; result = (abs(ask-100.4)<1e-9)"},
{"claim":"Q5b max matched volume is 500 at price 50.2","code":"buy=[900,700,500,300]; sell=[200,450,500,750]; vols=[min(b,s) for b,s in zip(buy,sell)]; result = (max(vols)==500 and vols.index(500)==2)"}
]