Microstructure and Execution · Execution
28Build: an Execution Algorithm
A trader hands the algorithm an order to buy 200 000 shares by three o’clock with a limit price. Everything this book has built, the schedule, the placement, the routing, the measurement, has to run at once, and keep running when the market does something the design did not expect: a halt, a spike, a kill from the desk. This chapter assembles firm.execalgo, an implementation-shortfall algorithm, from chapters 14, 17, 18 and 25, runs 200 parent orders through chapter 27’s agent market against a TWAP baseline on the same order flow, and measures both with chapter 19’s toolkit. The answer to the question every desk asks, “how much does it beat TWAP by?”, turns out to depend on the order.
28.1 The algorithm as a state machine
An execution algorithm (chapter 16) is a program that holds a parent order and decides, again and again, what child orders to have in the market. Written as a state machine it has few states and many events (Figure 28.1): a new order starts working at its start time and records the arrival mid; it is paused while its stock is halted or paused and resumes when trading does; it ends done when filled, expired when its deadline passes with shares left, or killed when the desk or a risk control stops it. Every transition and every refusal is written to an audit log with its time, the state and the reason: the log is how the desk, compliance and the developer reconstruct what the algorithm did and why.
28.2 Schedule, placement and routing together
The schedule is chapter 14’s: the Almgren–Chriss trajectory of holdings , with the urgency given as (zero for a straight line) through firm.acexec’s scheduler interface (Almgren and Chriss 2001; Kissell’s textbook describes the same family of implementation-shortfall algorithms). Every second the algorithm computes what the schedule says should be done by now and what it says should be done a little later (20 seconds), and bounds both by the participation band.
Definition 28.1 (Participation band)
A participation band is a lower and an upper limit on an execution algorithm’s share of the market’s volume since the order started: whatever its schedule says, the algorithm trades at least enough to stay above the lower limit and never so much as to go above the upper one.
Definition 28.2 (I-would price)
An I-would price is a price, set by the trader, at or better than which an algorithm should trade more than its schedule says, up to its upper participation limit, because the trader would be glad to complete the order there.
Placement is chapter 17’s lesson made operational: rest passively at the near touch for the schedule a little ahead, and cross the spread only for what the order is behind by more than a tolerance (20 seconds of schedule), so that the passive order has time to fill before it is overtaken. Crossing goes through chapter 18’s sweep when the stock trades on several venues: the displayed quotes at the far touch, best price first, each leg an immediate-or-cancel child. A limit price caps every child; an I-would price switches the algorithm to its band’s upper edge while the far touch is at or better than it.
def check(self, ctx):
p, sgn = self.p, self.sign
tau = self._t(ctx) - self.paused_s
left = p.qty - self.filled
if left <= 0:
self._cancel_all(ctx)
self.state = "done"
self._note(ctx, "done", f"{self.filled} filled")
return
b, a = self._touch(ctx)
if b is None or a is None:
return
far, near = (a, b) if sgn > 0 else (b, a)
if tau >= p.horizon_s: # the deadline
self._cancel_all(ctx)
if p.finish and tau < p.horizon_s + p.check_s:
self._cross(ctx, left, far)
self._note(ctx, "finish", f"crossing the last {left}")
else:
self.state = "expired"
self._note(ctx, "expire", f"{left} left unfilled")
return
now = self._band(p.qty - float(self.sched.targets([tau])[0]))
if p.i_would is not None and sgn * (p.i_would - far) >= 0:
now = max(now, min(p.qty, self._band(math.inf)))
if not self.wanting:
self._note(ctx, "i-would", f"far touch {far} at or better than {p.i_would}")
self.wanting = True
else:
self.wanting = False
tol = p.tol_s / p.horizon_s * p.qty if p.passive else 0.0
behind = int((now - self.filled - tol) // 100) * 100
if behind >= 100:
if self.passive_cl is not None: # the resting order is re-sized below
self._cancel(ctx, self.passive_cl)
self.passive_cl = None
self._cross(ctx, behind, far)
if p.passive:
ahead = self._band(p.qty - float(self.sched.targets([tau + p.look_s])[0]))
self._rest(ctx, int((min(ahead, p.qty) - self.filled - max(behind, 0)) // 100) * 100, near)
28.3 Limits, pauses and exceptions
Every child passes pre-trade controls before it is sent: no child above a maximum size, no price further than a collar from the mid, never more working and filled than the order. They are the algorithm’s own share of the controls a broker must have in front of the market.
As of September 2026 — Market access controls
The SEC adopted Rule 15c3-5 on 3 November 2010, ending unfiltered market access. A broker-dealer with market access must have controls reasonably designed to prevent the entry of orders that exceed pre-set credit or capital thresholds in the aggregate for each customer and for itself, and of erroneous orders, by rejecting orders that exceed price or size parameters order by order or over a short period, or that indicate duplicative orders (17 CFR 240.15c3-5).
def _send(self, ctx, qty: int, price: int, tif: str, venue: str = "") -> int | None:
"""Pre-trade controls (child size, price collar around the mid, never more than the order), then send."""
mid = self._mid(ctx)
working = sum(o["leaves"] for o in ctx.working() if o["cl"] not in self.cancelled)
why = None
if qty > self.p.max_child:
why = f"child {qty} > {self.p.max_child}"
elif mid is not None and abs(price - mid) > self.p.collar_ticks * TICK:
why = f"price {price} outside the collar"
elif self.filled + working + qty > self.p.qty:
why = "would exceed the order"
if why:
self._note(ctx, "reject", why)
return None
return ctx.send(Order(side=self.p.side, qty=qty, price=int(price), tif=tif, venue=venue))
The exceptions are driven by the feed and the desk. A trading-action message that leaves the trading state (chapter 25’s halts and pauses) cancels every working child and stops the schedule’s clock; when trading resumes, the schedule is shifted by the pause, so the order neither rushes to catch up nor loses its deadline. A kill, sent by the desk or by a risk monitor through the simulator’s scheduled calls, cancels everything and ends the order. Three runs of the same buy of 16 000 shares show them (Figure 28.2, Table 28.1).
mx_execalgo.stress.| run | time (s) | event | detail |
|---|---|---|---|
| halt | 200.0 | pause | trading state H |
| 260.0 | resume | schedule shifted by 60 s | |
| 661.0 | finish | crossing the last 600 | |
| 662.0 | done | 16 000 filled | |
| spike | 252.0 | limit | far touch 100.18 beyond the limit 100.105 |
| 465.0 | limit off | far touch 100.07 | |
| 587.0 | done | 16 000 filled | |
| kill | 299.6 | kill | 8 500 of 16 000 filled |
mx_execalgo.stress.The halted order finished a minute late, as designed, and paid 2.62 basis points against 2.31 on the normal day. In the spike, another buyer’s 60 000 shares took the far touch past the limit within twelve seconds; the algorithm stopped crossing for 213 seconds and kept its passive order at the limit, and paid 3.79 basis points, against 4.31 for the same order without a limit. The killed order stopped at 8 500 shares and ended at 8 700: a kill cancels what is resting, not what has already been sent, which is why the desk’s position after a kill is read from the fills, never from the order.
28.4 Measuring it
The measure is chapter 19’s: the implementation shortfall (Perold 1988) against the arrival mid, all-in (fees and rebates included), attributed by firm.tca into spread, impact (the order’s own push, from a copy of the session without it), timing (the market’s own move) and fees. The experiment is an A/B test with common random numbers: 200 parent orders, four sizes (4 000 to 32 000 shares over ten minutes, 2.4% to 15.9% of the market’s volume) by two urgencies ( of 0.5 and 3) by 25 seeds, each run with the full algorithm and with a TWAP baseline of the same size on the same seed (a straight line, a market order every ten seconds for what is due).
mx_execalgo.ab_study.Over the 200 orders the algorithm saves basis points against TWAP, with a 95% interval of clustered by seed: no saving on average. The average hides a slope (Figure 28.3). A regression of the saving on the size’s doubling and a high-urgency dummy gives , where is 1 at high urgency (standard errors 0.08, 0.04 and 0.22): the algorithm saves 1.06 basis points on the smallest orders at low urgency and loses 1.96 on the largest at high urgency, and breaks even near 13 700 shares, about 8% of the volume, at low urgency.
firm.tca’s attribution of the two arms’ costs, averaged over the 200 pairs (a tick is about a basis point at USD 100). Data: mx_execalgo.ab_study.The attribution says why (Figure 28.4). Resting passively earns the algorithm what TWAP pays: ticks of spread against , and rebates against fees, against . It pays it back in impact, 1.73 ticks against 0.93: a resting buy absorbs the sell orders that would have pushed the price down, the catch-up crosses come in bursts, and urgency front-loads the order into the market’s memory. The spread and the fees scale with the shares, the impact with the order’s size too, so the passive design wins on small orders and loses on large ones. This is not a failure of the build: it is the measurement that tells the desk which orders to give it, and which parameters to change for the others (a lower urgency, a smaller tolerance, a wider band).
28.5 Rolling it out
A new algorithm reaches clients the way any model does (Book 7, chapter 21): first in simulation (this chapter), then on a canary, a small share of the desk’s own flow, with a kill criterion written in advance, then in a randomised A/B test against the incumbent, and only then as the default. The test’s size follows from the paired dispersion: the difference between the arms has a standard deviation of 1.33 basis points per order even with common random numbers, so detecting an average saving of 0.15 with 80% power at the 5% level needs orders, and live orders, which do not share their market with a counterfactual, need many more. A per-order effect that depends on size is easier: stratify by size and test within the stratum where the algorithm is meant to win.
28.6 Tutorial: beat TWAP
Goal. Assemble an implementation-shortfall algorithm, run it through chapter 27’s market against TWAP, measure both with firm.tca, and stress it. End state: Table 28.1, Figures 28.2, 28.3 and 28.4.
- Build.
firm_execalgo.Params(side, qty, start_s, horizon_s, urgency, limit, i_would, band, look_s, tol_s);ISAlgo(params, venues); its.log,.fills,.state. - Run.
mx_execalgo.simulate(seed, agents, controls, calls),run(params, seed), the armsIS(qty, urgency)andTWAP(qty). - Measure.
ab_study(): the paired savings,firm_tca.cluster_ols, the attribution;stress(); draw withfig_execalgo.py.
What to change next. Replace the tolerance rule with chapter 17’s dynamic-programming placement; let the urgency depend on size; route passive orders across venues with chapter 18’s allocation; run the A/B test on chapter 16’s volume curves.
28.7 Build: execalgo
Purpose. The firm’s implementation-shortfall algorithm on firm.exchsim, for this chapter and for the execution work of Books 11 and 13.
Interface. Params (the fields of the tutorial), ISAlgo(params, name, venues) (a firm.exchsim Agent; .kill(t_ns) for Simulator.schedule_call), shortfall_bp(side, fills, arrival, qty, last_mid).
Rules. One decision a second; every child through the pre-trade controls; halts cancel and stop the clock; the end states are final; every transition and refusal in the audit log.
Acceptance tests. code/firm/execalgo/tests/: the order completes and urgency front-loads it; TWAP only takes; a halt pauses and shifts the schedule and nothing trades while halted; no fill beyond the limit; the collar refuses and logs; a kill ends the order; the I-would price trades ahead of schedule; a cross sweeps two venues’ displayed quotes.
Stretch. Chapter 17’s dynamic-programming placement; dark venues first; a volume-curve schedule; a real-time risk monitor that sends the kill.
Sources and further reading
- R. Almgren and N. Chriss, “Optimal execution of portfolio transactions”, Journal of Risk 3(2), 2001.
- A. F. Perold, “The implementation shortfall: paper versus reality”, Journal of Portfolio Management 14(3), 1988.
- R. Kissell, The Science of Algorithmic Trading and Portfolio Management, Academic Press, 2014.
- SEC Rule 15c3-5, Risk management controls for brokers or dealers with market access, 17 CFR 240.15c3-5 (adopted in Release 34-63241, 2010).
28.8 Exercises
Exercise 28.1 ★
An order to buy 10 000 shares over 600 seconds with : how many shares should be done after 300 seconds, and with a tolerance of 20 seconds, from how far behind does the algorithm cross?
Solution
Solution of Exercise 28.1.
5 000 shares (a straight line). The tolerance is shares, and the algorithm crosses for what it is behind beyond that in whole lots, so it crosses once it is at least 433 shares behind: 500 shares, since its fills come in lots.
Exercise 28.2 ★
The market has traded 40 000 shares since the order started, of which the algorithm 8 000. With a band of 0 to 30%, how many more shares may it trade before trading more?
Solution
Solution of Exercise 28.2.
The others traded shares; 30% of the total allows shares of its own, so 5 714 more at the market’s volume so far.
Exercise 28.3 ★
From the regression, what is the expected saving of a 16 000-share order at high urgency, and at what size does a low-urgency order break even?
Solution
Solution of Exercise 28.3.
basis points (a loss; the cell’s mean is ). Break-even at low urgency: , so shares.
Exercise 28.4 ★★
Why does a passive buy order have impact, when it never crosses the spread?
Solution
Solution of Exercise 28.4.
It takes the other side of sell orders that would otherwise have hit the rest of the bid queue and pushed the price down: the price stays higher than it would have without the order. The counterfactual mid in firm.tca measures this directly; the catch-up crosses and the front-loading add to it.
Exercise 28.5 ★★
Why does the algorithm shift its schedule by the length of a halt instead of catching up?
Solution
Solution of Exercise 28.5.
Catching up would cross the spread for a minute’s worth of schedule the moment the stock reopens, when the book is thin and prices are unsettled; shifting keeps the planned participation, at the cost of finishing later (here a minute past the deadline; a hard deadline would instead spread the missing shares over the time left).
Exercise 28.6 ★★
After a kill the order had 200 more shares than when the kill was logged. What should the desk’s systems do about it?
Solution
Solution of Exercise 28.6.
Treat the fills, not the order, as the position: reconcile the drop copy after the kill, report the late fill to the trader, and let risk see the true position; a kill switch that assumes the order stopped at the kill understates it.
Exercise 28.7 ★★★
Coding. From the study’s pairs, compute the all-in saving before fees are counted, and explain the difference from the all-in figure.
Solution
Solution of Exercise 28.7.
Before fees the saving is basis points on average: the algorithm’s fills are worse by 0.47 basis points, but it pays about 0.32 less in fees (it earns rebates on its passive fills, ticks a share, where TWAP pays 0.30 to take), so all-in it costs only 0.15 more. A comparison before fees would overstate the loss by a factor of three.
Exercise 28.8 ★★★
Find the flaw. “Our new algorithm beat TWAP by 1 basis point on the 200 orders we gave it last month, so it saves our clients 1 basis point.”
Solution
Solution of Exercise 28.8.
The orders it was given are not TWAP’s orders: without assignment at random (or a paired comparison on the same orders) the difference mixes the algorithm with the orders’ difficulty (chapter 19); 200 live orders with a paired dispersion above 1.3 basis points give an interval of about at best; and a saving that depends on size says nothing about another client’s orders. Randomise, adjust for difficulty, report the interval, and state the sizes it holds for.
28.9 Problem: Beat TWAP by How Much?
Problem 28.1
Weekend problem — beat TWAP by how much?
The head of the execution desk wants to know whether the new implementation-shortfall algorithm should replace TWAP as the default.
Part I — The machine.
- Draw the algorithm’s states and transitions.
- What does the audit log record, and for whom?
- Define a participation band and an I-would price.
- How are the schedule, the placement and the sweep combined each second?
- What does the tolerance do, and what happens at the deadline?
Part II — Controls and exceptions.
- What pre-trade controls does every child pass?
- What does Rule 15c3-5 require of a broker with market access?
- What does the algorithm do on a halt, and why shift the schedule?
- What did the limit do in the spike, and what did it save?
- Why did the killed order end with more shares than it had at the kill?
Part III — The experiment.
- Describe the 200 parent orders and the TWAP baseline.
- Why run both arms on the same seeds?
- What does
firm.tcaattribute the costs to? - State the named result: the shortfall saved against TWAP with its confidence interval, and its dependence on urgency and order size.
- Why does the passive design win on small orders and lose on large ones?
Part IV — Judgement.
- How many orders would a live A/B test need, and why more than in simulation?
- How would you roll the algorithm out?
- Which orders would you give it, and what would you change for the others?
- What would you tell the head of the desk?
- In one sentence: how much does it beat TWAP by?
Solution
Solution of Problem 28.1.
1. New, working, paused, done, expired, killed; start time, halt or pause and resumption, fill, deadline, kill. 2. Time, state, event and reason of every transition and refusal: for the desk, compliance and the developer. 3. See the definitions. 4. Every second: the schedule’s shares due now and 20 seconds ahead, bounded by the band; a cross for what is behind beyond the tolerance, swept across venues; one passive order at the near touch for the rest of the schedule ahead. 5. It leaves the passive order time to fill before crossing; at the deadline the rest is crossed within the limit, and what does not fill expires. 6. Maximum child size, a collar around the mid, never more working and filled than the order. 7. Controls reasonably designed to prevent orders exceeding credit or capital thresholds and erroneous orders exceeding price or size parameters or duplicative orders. 8. It cancels its orders and stops the clock, then shifts the schedule by the pause, to avoid rushing into a reopening market. 9. It stopped crossing for 213 seconds and kept buying passively at the limit; 3.79 basis points against 4.31 without it. 10. A child was in flight when the kill was sent. 11. Four sizes from 4 000 to 32 000 shares over ten minutes, two urgencies, 25 seeds; TWAP sends a market order every ten seconds for what a straight line says is due. 12. So that both face the same order flow and the difference measures the algorithms, not the days. 13. Spread, impact (against a counterfactual session), timing, opportunity and fees. 14. Named result: over 200 paired orders the algorithm saves basis points all-in against TWAP, 95% interval : nothing on average; the saving is ( at high urgency), from on 4 000-share orders at low urgency to on 32 000 at high urgency, breaking even near 13 700 shares (about 8% of volume). 15. Passive fills save the spread and fees in proportion to the shares; impact grows with size and urgency and overtakes them. 16. About 620 at the average effect with common random numbers; live orders share no counterfactual, so their paired dispersion is larger. 17. Canary on the desk’s own small orders with a kill criterion, then a randomised test stratified by size, then default for the winning stratum. 18. Small orders at low urgency; for large orders a lower urgency, a smaller tolerance or TWAP. 19. Make it the default for orders below about 8% of volume and keep TWAP above, pending a live test. 20. By a basis point on small patient orders, and not at all on large urgent ones.
28.10 Interview questions
Interview question 28.1 ★ trader
Your algorithm is 30% behind schedule with an hour to go and the stock is moving away. What do you do?
Solution
Solution of Interview question 28.1.
Check the reason (limit, band, liquidity) and the client’s instructions; if the limit and urgency allow, raise participation within the band or cross part of the shortfall; if not, tell the client early that the order may not complete, rather than chase the price in the last minutes.
Interview question 28.2 ★★ developer
How would you structure an execution algorithm’s code so that it is testable, and what would you log?
Solution
Solution of Interview question 28.2.
A pure decision function of the state (schedule, fills, book, clock) separate from the messaging, driven by a simulated clock so that it can be replayed; explicit states and transitions; pre-trade controls in one place; an audit log of every transition, child and refusal with its reason.
Interview question 28.3 ★★ researcher
How would you measure whether a new algorithm beats the old one, and how many orders do you need?
Solution
Solution of Interview question 28.3.
Randomise orders between the algorithms (or pair them on the same orders in simulation), measure all-in shortfall against arrival, adjust for difficulty with clustered errors, and size the test from the paired dispersion: .
Interview question 28.4 ★★ risk
Which pre-trade controls belong in the algorithm, and which in a layer the algorithm cannot bypass?
Solution
Solution of Interview question 28.4.
The algorithm’s own controls (child size, collars, never beyond the order, participation) catch its mistakes; the broker’s market-access layer (credit and capital thresholds, erroneous-order checks, kill switch) must sit outside it, under the firm’s direct control, so that no bug in the algorithm can bypass it.
Interview question 28.5 ★★ bank
A client asks why your implementation-shortfall algorithm cost more than TWAP on its large orders. What do you answer?
Solution
Solution of Interview question 28.5.
That its passive design saves the spread and fees on small orders but has more impact on large ones; show the attribution, the comparison on the client’s size bucket, and offer a lower urgency or a different algorithm for those orders.
Interview question 28.6 ★★★ mle
You want to learn the algorithm’s parameters from its own history of orders. What biases the data, and how do you fix it?
Solution
Solution of Interview question 28.6.
The orders were not assigned at random: traders chose parameters for the conditions they saw (urgency on volatile days), so costs and parameters are confounded; fix it with randomised parameter variations on a slice of flow, or with difficulty adjustment and a simulation that reproduces the choices.