all lessons / reinforcement learning / 69 · Quant-trading order execution lesson 69 / 87

Quant-trading order execution

You are handed a parent order — "buy 200k shares before the close" — and an RL agent that decides, microsecond by microsecond, what to post and where. The domain looks like a clean MDP until you write it down: the state is a level-2 order book that leaks the future if you encode it carelessly, the action is a hybrid price-and-size decision constrained by a 500 µs exchange round-trip, the reward conflates your own market impact with the market's noise, you can never run the policy live to evaluate it so everything rides on off-policy estimation under a non-stationary market, and every action is gated by regulation that can change at 9am. Each binding difficulty names a tool.

The method — five steps, every lesson
Applied RL is not a grab-bag of tricks. Every domain in this track runs the same loop: (1) Formulate the MDP — state, action, reward, transition, horizon. (2) Diagnose the one property that makes the MDP hard. (3) Engineer the mechanism that removes that difficulty. (4) Guard it in production — detect when it breaks and fall back. (5) Iterate. The art is only in which difficulty binds first. For order execution, four bind hard: a leaky state, a hybrid latency-bounded action, an impact-confounded reward, and an unrunnable evaluation under a regulator.

1 · Formulate — the MDP behind an execution agent

Intuition. A parent order has to be sliced into hundreds of child orders over a horizon (say, the trading day). At each tick the agent sees the order book, decides where and how much to post, and is rewarded for buying cheap relative to a benchmark while not moving the price against itself. That is an MDP — but every one of the four pieces is awkward in a way that does not show up in a game.

MDP = (S, A, R, P, γ)  ·  maximize  E[ Σₜ γᵗ rₜ ]   subject to   fill all Q shares by horizon H
PieceFor an execution agentThe awkward part
State Stop-5 book levels per side (price+size → 20 features), inventory remaining, time left, realized volatility — a (K×21×2) causal tensor over the last K snapshotsnaïve features leak the future; a persistent order-queue ID breaks the Markov property
Action Awhich price level to post at (discrete, 5–10 ticks) × price offset and size fraction (continuous)hybrid discrete-continuous, and it must clear in under the 500 µs exchange round-trip
Reward Rexecution price vs. VWAP/arrival benchmark, minus slippageyour fill moves the price — reward = your impact + market noise, two confounded signals
Transition Pthe matching engine + every other participant in the booknon-stationary regime; you can never A/B the live market → evaluation is off-policy only

Same pattern as every domain: the MDP table writes itself and the rightmost column is the lesson. The four rows below become four mechanisms — causal encoding, structured masked heads, impact-separated reward, and off-policy evaluation with a regulatory guard.

2 · The state leaks the future — build a causal tensor

Intuition. The order book is a snapshot of prices and sizes at ten levels on each side. If you hand the network raw prices it learns scale, not structure; worse, if any feature is computed from data that only existed after the decision instant, the backtest looks brilliant and the live system loses money. The whole job here is to encode the book so the network sees only the past, in a form that is stationary across price regimes.

Engineering detail. Encode each level as an incremental log relative to the mid-price, which is scale-free and stable across a stock at $5 or $500:

bid_pricei = ln(Pbidi / Pmid)  ·  bid_voli = ln(Vbidi + 1)  ·  Pmid = (Pbid1 + Pask1)/2

The +1 guards against log(0) on an empty level. Maintain a deque of the last K=20 snapshots — appendleft on arrival, pop when full, O(1) per tick to meet the microsecond budget — and stack them into a (K, 21, 2) tensor: axis 0 is time (index 0 oldest, K−1 = now, strict causal order), axis 1 is the 20 book features plus a 21st channel of relative timestamps Δτ = ti−t0, and axis 2 is the price/volume channel so a 2-D CNN can convolve over (time, depth) with kernel=(3,3), dilation=(2,1) — a wider temporal receptive field at no extra parameters. A Transformer variant reshapes to (K, 42) and masks the upper triangle for causal self-attention.

The leakage test that must be in CI
Look-ahead bias is the silent killer of execution backtests. Write a unit test that injects a fabricated "future limit-up price" at time t: if the network's output at t changes at all, the causal barrier has been breached and the build fails. Back it with offline replay on tick-by-tick historical data to confirm no feature reads across the decision boundary. And treat the order book's own structure as suspect: a persistent order-queue ID that survives across frames lets the model track a single resting order, which injects historical path information into the state and violates the Markov assumption. The fix is to randomly re-shuffle queue indices each frame and drop persistent IDs, substituting instantaneous book shape + traded volume as the Markov-safe feature; importance sampling then keeps the policy gradient unbiased. Quantify the residual leak with a χ² test on the next-frame index distribution before you ever ship.

Engineering detail — irregular arrivals. A tick-by-tick order feed arrives at non-uniform intervals, so absolute Δτ misleads. Replace it with an exponential time-decay encoding, decay coefficient α = 1/τhalf with τhalf ≈ 500 ms, which keeps causality while expressing continuous time. On an edge inference accelerator the (K=20, 21, 2) tensor runs FP16 in under ~400 µs — inside the sub-1 ms order round-trip. Push K past ~40 and latency grows linearly; if you need a longer memory, distill to a student network at K=10 fed differenced inputs to retain the information at a fraction of the cost.

3 · The action is hybrid and latency-bounded — structure it, mask it, schedule it

Intuition. "Post 30% of what's left, one tick inside the bid" is two decisions of different types: which price level (a choice from a menu of 5–10 ticks) and how far in and how much (a continuous offset and size fraction). Flattening into one giant discrete grid explodes; treating it as one continuous vector throws away the tick structure. Keep both — a discrete head for the level and conditioned continuous heads for offset and size — and never let the agent emit an action it can't legally place.

a = (alevel, Δp, Δq)  |  alevel ~ Categorical(K logits)   Δp ∈ [−0.5, 0.5] tick ~ TanhNormal   Δq ∈ [0,1] ~ TanhNormal

Engineering detail. A shared two-layer LayerNorm+ReLU MLP (256 units → 64-dim latent) feeds a discrete head (K logits, K=5 on the main board, up to 10 on a tick-finer board) and a continuous head (2-D: price offset, size fraction). Sample the discrete part with Gumbel-Softmax (τ=0.5, straight-through one-hot forward, gradient backward) and the continuous part with a Tanh-Normal; the final order is level-center price + round(Δp/tick)·tick and Δq × remaining quantity. Critically, the replay buffer stores the raw continuous outputs, not the rounded prices — rounding before storage severs the gradient. Anneal τ linearly every ~500 steps so Gumbel-Softmax converges toward a true one-hot. The loss is policy NLL × GAE advantage + discrete entropy + continuous entropy, value MSE, and a hard compliance penalty λ·ReLU(|price − limit|) with λ=1e3 so the gradient satisfies regulation before it optimizes return. Against a TWAP baseline this lifts the information ratio by ~0.8 and cuts the cancel rate ~35%.

Matching the 500 µs exchange minimum-latency floor — SMDP, not faster polling
The exchange enforces a minimum order round-trip (~500 µs). Firing the policy faster than that wastes compute and tempts latency-arbitrage behavior. Use event-driven sampling: act on book-change events, not a fixed clock. If the venue switches to batch auction matching and latency jumps from 500 µs to ~5 ms, event-driven sampling degrades to low-frequency polling — at which point promote to a multi-timescale SMDP: a macro policy steps on auction periods, a micro policy issues sub-actions on book-depth changes (hierarchical RL). For options market-making, where 500 µs sits near the implied-vol-surface refresh, train under MAML with simulated 0–2 ms latency jitter so the policy adapts on day one.

4 · The reward confounds your impact with the market's noise — separate them

Intuition. When you buy, the price ticks up — partly because you pushed it (your market impact), partly because the market was moving anyway (noise). A raw price-improvement reward mixes the two, so the agent can't tell whether a bad fill was its fault or the market's. Worse, impact has two parts: a temporary component that relaxes after you stop trading, and a permanent component that stays — and the regulator wants the permanent part reported. So you separate the signal, normalize the learnable part, and keep the reportable part on the side.

Engineering detail — online impact separation. On each transition (s,a,r,s′), track the permanent-impact mean with an EWMA, take the residual as temporary impact, and standardize:

μ̂perm ← (1−α)·μ̂perm + α·r  ·  εtemp = r − μ̂perm  ·  r̃ = εtemp / max(σ̂temp, 1e−3)

The shaped reward r̃ has mean 0 and variance 1, ready for any deep-RL algorithm. Feed μ̂perm into the critic as an extra state feature so the value head only learns "de-drifted" value, while the actor's gradient uses the standardized r̃ to cut variance. For the compliance report, add — outside the Bellman target, so it stays unbiased — a logged term λ·sign(μ̂perm)·log(1+|μ̂perm|), with λ calibrated by risk control, and persist μ̂perm and σ̂temp per child order to generate the regulator's impact-cost curve.

Engineering detail — taming VWAP-deviation outliers. When the reward is deviation from VWAP, a single fat-tailed print blows up the gradient. Defend in layers: (1) Winsorize — linearly penalize only the part of the deviation beyond a threshold δ to cap gradient magnitude. (2) Switch exploration noise from 𝒩(0,σ²) to a truncated normal and halve σ when the anomaly rate exceeds 15%. (3) Down-weight extreme samples with importance weight w = exp(−|r|/τ) so outlier rewards decay exponentially, then map the winsorized reward through tanh into [−1,1] so the gradient is bounded but never identically zero. On index-constituent backtests this cuts reward variance ~62% and max drawdown ~1.8pp while leaving the information coefficient essentially unchanged.

Engineering detail — slippage discount that adapts to inventory. The slippage penalty should matter less when little is left to trade. Make the potential-shaping discount a function of remaining quantity and fold it into the advantage so the policy-gradient theorem still holds:

γslip(qrem) = γmin + (γmax − γmin)·tanh(qrem/Q)  ·  Ât = rt + γslip·Φ(st+1) − Φ(st) + γV(st+1) − V(st)

Versus a fixed γslip=0.9 baseline, this adaptive form cut slippage cost ~18.7% and lifted fill rate ~4.2% while holding the cancel rate under 15%. For power-law-liquidity names, swap tanh for a stretched exponential γslip = γmin + (γmax−γmin)·exp(−α·(qrem/Q)β) and meta-learn α, β across stocks.

Impact-aware execution: slice size → market impact vs. timing risk

The central tension of order execution: trade fast and you pay your own impact (the square-root law); trade slow and you eat timing risk as the price drifts away over the remaining horizon. The optimal schedule sits where the two costs balance. Slide the slice size and feel the trade-off.

impact cost
timing risk
total cost
verdict

1 · FORMULATE causal LOB tensor, hybrid action 2 · DIAGNOSE leakage, impact, no live A/B 3 · ENGINEER masking, impact split, DR-OPE 4 · GUARD circuit breaker, Lagrangian λ 5 · ITERATE re-diagnose removing one difficulty exposes the next — re-run the loop

5 · You can never run the policy live to test it — off-policy evaluation under non-stationarity

Intuition. You cannot A/B a new execution policy against the real market without spending real money and moving real prices. So you must estimate a new policy's value purely from logs generated by the old one — off-policy evaluation (OPE) — in a market whose statistics drift across the day. The naïve estimator (importance-weighted return) is unbiased only if the market is stationary and the weights don't explode; both assumptions fail, so you build an estimator that survives drift and a test that tells you when "unbiased" stops being true.

Engineering detail — IPS, then double-robust. Estimate value with importance-weighted returns over a rolling 60-day window, V̂(π₁) = (1/n)Σ ρiRi, and tame the weights with truncationmax=20) and Pareto smoothing. To detect when non-stationarity has broken unbiasedness, take the error series εi against a same-seed simulator, run CUSUM to cut it into locally-stationary blocks, t-test each block, and control the family-wise false-discovery rate with Benjamini–Hochberg at 5%. Reduce residual variance with the double-robust estimator, where a fitted Q model only has to be approximately right:

DR = (1/n) Σ [ ρiRi − ρiQ(si,ai) + Σa π₁(a|si)Q(si,a) ]

Push variance down further by estimating the Q correction with quasi-Monte-Carlo rollouts (Sobol/Halton sequences via torch.quasirandom.SobolEngine; Gray-code the discrete actions to preserve space-filling), giving Var(V̂DR-QMC) ≤ (1+‖ρ‖)²·O((log K)d/K²) + εQ. Use the DR-QMC value as a safety gate: if a candidate policy improves the estimated value <2% with variance reduction <10%, reject it before any capital is risked.

The calendar-rolling replay buffer — the second place leakage hides
Even Markov-safe features leak if the replay buffer samples the future. Maintain a block-structured buffer keyed by calendar day with a shared "max trainable day" gate Tc: at each close, append today's experience to the tail block, atomically drop the head block once the window exceeds T blocks, and publish the new Tc. The sampler filters out every block with t > Tc, and for n-step returns it also checks that the state n steps ahead falls before Tc, truncating otherwise. Monitor an information-leakage rate (samples with t>Tc over total; target 0), a window-utilization rate (alert below 60%), and a distribution-drift KS test comparing the last 20 days to the prior 20 — when p<0.05, shrink the window or raise the discount. Feature operators carry their own timestamp tf with the invariant tf ≤ t and tf ≤ Tc, so a metric computed from post-close data can never sneak in.

6 · Guard in production — the regulator is part of the environment

Intuition. An execution policy that is profitable but non-compliant is worthless: it gets the desk's trading license pulled. Self-trading, "flickering" orders that cancel within milliseconds to fake liquidity, and breaching a price limit are all illegal, and the rules can change overnight. So compliance is not a post-hoc filter — it is wired into the action space, the loss, and a runtime circuit breaker.

Engineering detail — three layers against flickering / fake fills. (1) Reward: penalize cancels of orders that rested <50 ms. (2) Environment hard wall: the Gym step() embeds the matching-engine rule engine, returns a "forbidden" signal for sub-50 ms cancels, and randomizes feed latency over 0–500 ms so the policy can't stably exploit a fixed delay. (3) Conservative offline training: train with BCQ/CQL on historical flickering negatives so the policy assigns low value to high-cancel regions, and every 10k steps run a replay evaluation — if replay fill-rate diverges from training fill-rate by >3%, auto-rollback and freeze that period's samples. Together this compresses the inflated fill rate from flickering to under 1%.

Engineering detail — a differentiable compliance mask. Build a binary mask M ∈ {0,1} over the action space marking actions that would cause self-trading (price/size in the self-cross band), set forbidden logits to zero, and renormalize the discrete distribution; for continuous actions, project the masked dimension to the nearest legal region and record the projection distance as a penalty. The total loss is L = LRL + λ·(‖araw − alegal‖² + Iself·Rpenalty), λ ∈ [1e2, 1e3] so compliance is satisfied before return is optimized. Register the mask in the graph (Torch FX / ONNX export) so the broker's gateway can do a simulation-vs-live consistency check with zero drift.

Engineering detail — Lagrangian multipliers that re-tune in minutes. When a rule changes, you cannot afford to retrain. Treat the constraint as a Lagrangian and update the multiplier online: each trajectory, compute mean constraint violation ĉ = (1/T)Σ g(st,at) − ḡnew, then λk+1 = [λk + κ·ĉ]+ through a softplus (κ=0.1 damps oscillation, softplus stops small violations from amplifying noise), λ capped by the regulator's worst-case violation cost. The actor maximizes Rt − λk+1·ct + γ·H(π) with the entropy coefficient rising as λ grows, so the policy doesn't collapse onto a degenerate "do nothing" action. Hold a sliding-window compliance rate ≥99.5% and a return drawdown <2% as dual gates; tripping either rolls back to the last stable multiplier — a "gray-scale rollback" that re-calibrates to a new rule in ~15 minutes with zero offline retraining.

The through-line
Every section above is one row of the MDP table turned into a mechanism: a leaky, non-Markov state → causal log-tensor + queue-ID shuffling + a calendar-gated buffer; a hybrid latency-bounded action → structured masked heads + event-driven SMDP scheduling; an impact-confounded reward → EWMA impact separation + winsorized VWAP shaping + inventory-adaptive slippage; an unrunnable, non-stationary evaluation under a regulator → DR-QMC off-policy estimation as a safety gate + a compliance mask, Lagrangian multipliers, and a circuit breaker. You never reached for a tool until a row of the table demanded it. That discipline is the whole track.

Further considerations