Almgren-Chriss Optimal Trade Execution¶
Background¶
Liquidating a large position creates a timing trade-off. Selling quickly reduces exposure to future price variation, but concentrated trading increases temporary market impact. The continuous-time Almgren-Chriss benchmark captures this trade-off with a quadratic cost and has a closed-form optimal schedule.
This example liquidates 100,000 shares during one trading day. Inventory, trading speed, and cost coefficients are normalized, so the objective is a dimensionless numerical benchmark rather than a currency estimate.
Problem formulation¶
Let \(x(t)\) be the fraction of the initial order still held and \(v(t)\) the sell rate in initial-inventory fractions per day. Inventory evolves as
Complete liquidation imposes
The normalized mean-variance objective is
where \(\eta=0.05\) weights temporary market impact and \(\rho=0.20\) weights inventory risk. The path constraints are
For an order of \(Q=100{,}000\) shares, physical inventory and sell rate shown in the figure are \(X=Qx\) and \(V=Qv\).
Variables, parameters, and units¶
| Symbol | Meaning | Value or unit |
|---|---|---|
| \(t\) | Time within the execution window | trading days |
| \(x\) | Remaining inventory fraction | dimensionless |
| \(v\) | Sell rate divided by initial inventory | day⁻¹ |
| \(X=Qx\) | Remaining shares | shares |
| \(V=Qv\) | Physical sell rate | shares/day |
| \(Q\) | Initial order size | \(100{,}000\) shares |
| \(T\) | Execution horizon | \(1\) trading day |
| \(\eta\) | Normalized temporary-impact weight | \(0.05\) |
| \(\rho\) | Normalized inventory-risk weight | \(0.20\) |
| \(J\) | Normalized execution objective | dimensionless |
Analytical optimum¶
Because \(v=-\dot x\), the Euler-Lagrange equation is
Applying the two inventory endpoints gives
The exact unconstrained cost is
The maximum sell rate occurs initially:
Thus the rate cap was deliberately chosen to be inactive. If it were lower, the closed-form unconstrained path would no longer be the solution.
Modeling choices¶
The underlying benchmark assumes zero expected price drift. Price uncertainty then contributes a mean-variance penalty proportional to \(\int x^2\,\mathrm dt\), while temporary impact contributes \(\int v^2\,\mathrm dt\).
Linear permanent impact is omitted because, under complete monotone liquidation, its contribution is path independent:
It changes the total expected cost by a constant but cannot change this optimal schedule. A \(48\times4\) Lobatto mesh represents the smooth solution, and the analytical path provides the initial guess.
Independent validation¶
The script reconstructs inventory and sell rate at 4,001 times and compares both directly with the closed-form solution. It also recomputes the analytical objective by dense trapezoidal quadrature and checks all path bounds. This tests the entire trajectory rather than only the terminal inventory.
Verified result¶
| Quantity | Verified value |
|---|---|
| Numerical objective | \(0.1037314721\) |
| Dense-quadrature analytical objective | \(0.1037314807\) |
| Maximum inventory error | \(6.656\times10^{-9}\) |
| Maximum sell-rate error | \(1.206\times10^{-6}\) day⁻¹ |
| Numerical initial sell rate | \(207{,}462.824\) shares/day |
| Sell-rate cap | \(300{,}000\) shares/day |
The front-loaded schedule reduces inventory risk, then gradually slows as the remaining position falls. The numerical path agrees with the analytical path well below the stated validation tolerances.

Scope and limitations¶
The model assumes continuous trading, zero drift, constant coefficients, quadratic temporary impact, and a deterministic execution horizon. It omits the bid-ask spread, fees, discrete lots, changing volume, alpha signals, stochastic liquidity, nonlinear or transient impact, and fill risk. Because the parameters and objective are normalized, the output is not a live trading recommendation or an estimate of monetary implementation shortfall.
Run the example¶
Save the figure without opening a window:
system, phase = build_problem()
guess = initial_guess(phase)
solution = solve_problem(system, guess)
plot_solution(solution)
Source code¶
See the complete runnable example:
examples/optimal_trade_execution.py.