Optimal Execution Algorithms: TWAP, VWAP & Market Impact

Michael BrenndoerferJanuary 20, 202551 min read

Master execution algorithms from TWAP and VWAP to Almgren-Chriss optimal trading. Learn to balance market impact against timing risk for superior results.

Reading Level

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Execution Algorithms and Optimal Execution

When a portfolio manager decides to buy one million shares of a stock, the real challenge has just begun. Executing that order is not simply a matter of sending it to an exchange. As we explored in the previous chapter on market microstructure and as we examined in depth in our discussion of transaction costs and market impact, your large orders move markets. The very act of buying pushes prices higher, eroding the alpha that motivated the trade in the first place.

Execution algorithms emerged to solve this fundamental problem. Unlike the alpha-seeking strategies we covered in Part VI, execution algorithms have a different objective. They take a given trading decision as input and focus purely on minimizing the cost of implementation. A portfolio manager might use machine learning to identify an undervalued stock, but the execution algorithm's job is to buy that stock as cheaply as possible, not to question whether it should be bought at all. develops the theory and practice of optimal execution. We begin with simple schedule-based algorithms like TWAP and VWAP, progress to implementation shortfall frameworks, and culminate in the Almgren-Chriss model, which provides a rigorous mathematical foundation for balancing the competing costs that execution traders face. Understanding these algorithms is essential for anyone building or evaluating quantitative trading systems, as execution quality often determines whether a profitable strategy remains profitable after accounting for real-world frictions.

The Execution Problem

Consider an institutional trader who needs to sell 500,000 shares of a stock that trades approximately 2 million shares per day. Selling the entire position immediately would require crossing roughly 25% of the daily volume in a single burst, almost certainly moving the price against you and resulting in poor execution.

The alternative is for you to spread the execution over time, but this introduces a new risk. The price might move unfavorably while you wait. This tension between market impact and timing risk lies at the heart of optimal execution.

::: {.callout-note title="Market Impact vs. Timing Risk"} Market impact is the adverse price movement caused by your trading activity. Timing risk (also called price risk or volatility risk) is the risk that the asset price moves unfavorably while waiting to complete execution. Aggressive execution minimizes timing risk but maximizes impact, while patient execution minimizes impact but maximizes timing risk.

Out[2]:
Visualization
Execution cost trade-off between market impact and timing risk. Market impact costs decline with longer execution duration as lower trading rates consume less liquidity, while timing risk costs increase with duration due to prolonged exposure to price volatility. The total execution cost reaches its minimum at the optimal horizon, balancing the marginal reduction in impact against the marginal increase in timing risk.
Execution cost trade-off between market impact and timing risk. Market impact costs decline with longer execution duration as lower trading rates consume less liquidity, while timing risk costs increase with duration due to prolonged exposure to price volatility. The total execution cost reaches its minimum at the optimal horizon, balancing the marginal reduction in impact against the marginal increase in timing risk.

Understanding why this fundamental tension exists requires us to think carefully about the mechanics of trading. When a trader submits a large buy order, the immediate effect is to consume available liquidity on the ask side of the order book. As the order depletes offers at the best price, subsequent fills occur at increasingly worse prices. This is market impact in its most direct form. The trader's own activity causes the execution price to deteriorate. The more aggressively the trader buys, the more liquidity is consumed in a short window, and the more severe the impact.

Conversely, if the trader waits patiently, spreading the order over hours or even days, each individual trade is small enough to have minimal impact. However, during this extended waiting period, the underlying price is free to move based on market forces entirely unrelated to the trader's activity. Perhaps unfavorable news arrives, or the broader market sells off, or other participants independently decide to sell the same stock. This is timing risk, the random price movement that accumulates while the trader waits to complete execution.

Let's formalize the execution problem mathematically. You need to execute a position of XX shares over a time horizon TT. At any time tt, let xtx_t denote the remaining inventory. The execution schedule specifies the trading rate vt=dxt/dtv_t = -dx_t/dt, which represents how quickly shares are being executed at each moment. The negative sign appears because inventory decreases as trading proceeds. This trading rate must satisfy the fundamental constraint that execution completes by the horizon:

0Tvtdt=X\int_0^T v_t \, dt = X

where:

  • vtv_t: trading rate at time tt measured in shares per unit time
  • TT: execution time horizon
  • XX: total shares to be executed

This integral constraint simply states that the cumulative amount traded over all time must equal the total order size. It ensures that the trader neither under-executes, leaving inventory unfinished, nor over-executes, trading beyond the intended position.

The trader's objective is to choose the trajectory {vt}\{v_t\} that minimizes some cost function, typically involving both expected execution cost and its variance. The expected cost captures the average amount paid above the initial price, while the variance captures the uncertainty around that average. Different algorithms embody different trade-offs between these objectives, and different traders may prefer different points along the efficiency frontier depending on their risk tolerance and urgency.

Schedule-Based Algorithms

Schedule-based algorithms follow predetermined trading patterns, executing orders according to fixed rules regardless of short-term market conditions. These algorithms are simple, transparent, and widely used for benchmarking purposes.

Time-Weighted Average Price (TWAP)

The simplest execution algorithm is TWAP, which divides the total order into equal-sized pieces and executes one piece at regular intervals throughout the trading period. The logic behind TWAP is straightforward: without any information about when liquidity will be available or when prices might be favorable, the most sensible approach for you is to spread risk evenly across time.

For an order of XX shares executed over NN intervals of duration Δt\Delta t, TWAP trades at a constant rate. In continuous time, this rate is simply the total shares divided by the total duration:

vTWAP=XTv_{\text{TWAP}} = \frac{X}{T}

where:

  • vTWAPv_{\text{TWAP}}: trading rate in shares per unit time
  • XX: total order size
  • TT: total duration

This formula expresses a fundamental principle: TWAP commits to a fixed pace of execution that does not vary regardless of market activity. Whether the market is quiet or hectic, or whether spreads are narrow or wide, the algorithm continues trading at the same steady rate.

Equivalently, in discrete intervals where the trading day is divided into NN equal periods:

qn=XNq_n = \frac{X}{N}

where:

  • qnq_n: shares traded in interval nn
  • XX: total order size
  • NN: number of intervals

Each interval receives exactly the same allocation of shares. The index nn ranges from 1 to NN, and every interval is treated identically regardless of its position in the sequence or the market conditions prevailing at that time.

TWAP's appeal lies in its simplicity: it requires no forecasting of volume patterns, no estimation of market impact parameters, and no optimization. You can implement TWAP with nothing more than a clock and a calculator. The algorithm performs well when true trading costs are linear in execution rate and when the trader has no information about intraday volume patterns. In such circumstances, there is no basis for preferring one time period over another; equal treatment of all periods is the natural choice.

In[3]:
Code
import numpy as np


def generate_twap_schedule(total_shares: int, num_intervals: int) -> np.ndarray:
    """Generate a TWAP execution schedule."""
    shares_per_interval = total_shares / num_intervals
    return np.full(num_intervals, shares_per_interval)


# Example: Execute 100,000 shares over 20 intervals
total_order = 100_000
intervals = 20
twap_schedule = generate_twap_schedule(total_order, intervals)
Out[4]:
Console
TWAP Schedule for 100,000 shares over 20 intervals:
Shares per interval: 5,000
Total scheduled: 100,000

The output confirms that the algorithm divides the total order into equal chunks, ensuring a constant trading rate regardless of market activity or price movements.

Volume-Weighted Average Price (VWAP)

VWAP improves on TWAP by executing in proportion to expected market volume. The key insight motivating VWAP is that markets naturally absorb more trading during high-volume periods, so concentrating execution during these times reduces market impact. Think of it this way. If one million shares trade during the open but only one hundred thousand during lunch, submitting your large order during the open will constitute a smaller fraction of total activity and therefore have less price impact.

If VnV_n represents the expected volume in interval nn and Vtotal=nVnV_{\text{total}} = \sum_n V_n is total expected volume across all intervals, then VWAP allocates shares according to the volume weights:

qn=XVnVtotalq_n = X \cdot \frac{V_n}{V_{\text{total}}}

where:

  • qnq_n: target shares to trade in interval nn
  • XX: total order size
  • VnV_n: expected volume in interval nn
  • VtotalV_{\text{total}}: total expected volume

This formula ensures that the algorithm participates at a consistent rate relative to market activity. When volume is high, the algorithm trades more shares. When volume is low, the algorithm trades fewer shares. The ratio Vn/VtotalV_n / V_{\text{total}} represents the fraction of daily volume expected in interval nn, and this same fraction is applied to the total order size.

The VWAP benchmark itself, which gives the algorithm its name, is defined as the volume-weighted average of all prices observed during the trading period:

VWAP=n=1NPnVnn=1NVn\text{VWAP} = \frac{\sum_{n=1}^{N} P_n \cdot V_n}{\sum_{n=1}^{N} V_n}

where:

  • VWAP\text{VWAP}: volume-weighted average price benchmark
  • PnP_n: price in interval nn
  • VnV_n: volume in interval nn
  • NN: total number of intervals

This benchmark represents the average price at which all market participants traded during the period, with more weight given to prices during high-volume intervals. A VWAP algorithm aims to achieve an average execution price close to this benchmark. If the algorithm executes exactly according to the volume profile, it will by construction achieve a price equal to the market VWAP, assuming prices don't move in response to the algorithm's own trading.

In[5]:
Code
import numpy as np


def generate_vwap_schedule(
    total_shares: int, volume_profile: np.ndarray
) -> np.ndarray:
    """Generate a VWAP execution schedule based on expected volume profile."""
    volume_weights = volume_profile / volume_profile.sum()
    return total_shares * volume_weights


# Typical U-shaped intraday volume pattern
# Higher volume at open and close, lower during midday
def create_intraday_volume_profile(num_intervals: int) -> np.ndarray:
    """Create a realistic U-shaped intraday volume profile."""
    x = np.linspace(0, 1, num_intervals)
    # U-shape: high at endpoints, low in middle
    profile = 1 + 0.8 * (2 * (x - 0.5) ** 2)
    return profile / profile.sum()


volume_profile = create_intraday_volume_profile(intervals)
vwap_schedule = generate_vwap_schedule(total_order, volume_profile)
Out[6]:
Visualization
TWAP execution schedule distributing 100,000 shares evenly across 20 intervals. The constant allocation per interval demonstrates predictable, equal treatment of time, but forgoes opportunities to adapt to actual intraday liquidity patterns.
TWAP execution schedule distributing 100,000 shares evenly across 20 intervals. The constant allocation per interval demonstrates predictable, equal treatment of time, but forgoes opportunities to adapt to actual intraday liquidity patterns.
VWAP execution schedule for 100,000 shares following a U-shaped intraday volume pattern: execution concentrates at high-volume periods during market open and close while declining during midday, minimizing market impact by maintaining consistent proportional participation with available liquidity.
VWAP execution schedule for 100,000 shares following a U-shaped intraday volume pattern: execution concentrates at high-volume periods during market open and close while declining during midday, minimizing market impact by maintaining consistent proportional participation with available liquidity.

The figure illustrates a key difference between the two approaches. TWAP maintains constant execution throughout the day, while VWAP front-loads and back-loads execution to match the typical U-shaped volume pattern observed in equity markets.

Limitations of Schedule-Based Algorithms

While simple and transparent, schedule-based algorithms have significant limitations:

  • No adaptation to conditions: They execute regardless of whether the spread is wide, the order book is thin, or prices are moving adversely
  • Predictability: Sophisticated market participants can detect and exploit predictable execution patterns
  • Benchmark mismatch: Matching a volume-weighted benchmark may not align with minimizing actual execution cost

TWAP performs poorly when volume varies significantly throughout the day, while VWAP can underperform when the actual volume distribution differs from the historical pattern used for scheduling. Both approaches ignore the trader's risk preferences and the specific characteristics of the security being traded.

Implementation Shortfall

Implementation shortfall, introduced by André Perold in 1988, measures the total cost of implementing an investment decision. Unlike TWAP or VWAP, which compare execution to market-derived benchmarks, implementation shortfall compares your actual performance to a "paper portfolio" that could have been achieved if your entire trade executed instantaneously at the decision price.

Implementation Shortfall

Implementation shortfall is the difference between the theoretical return of a paper portfolio, executed at decision prices with no costs, and the actual return of the real portfolio, executed over time with market impact and other costs. It captures the full cost of translating investment ideas into actual positions.

The power of implementation shortfall as a concept lies in its comprehensiveness. When you identify an opportunity, say a stock trading at $50 that you believe is worth $60, the value of that idea is based on the price at which the decision was made. Every penny your actual execution price deviates from that decision price represents value lost, value that transfers from your trading strategy to the market or to intermediaries. Implementation shortfall captures all of these costs in a single unified measure.

Components of Implementation Shortfall

Implementation shortfall can be decomposed into several components that help identify where execution costs originate. Let P0P_0 be the decision price (the price when the trading decision was made), Pˉ\bar{P} the average execution price achieved across all fills, and PTP_T the price at the end of the trading period.

For a buy order of XX shares, the total implementation shortfall is simply the difference between what was paid and what would have been paid at the decision price:

Implementation Shortfall=X(PˉP0)\text{Implementation Shortfall} = X \cdot (\bar{P} - P_0)

where:

  • XX: total shares
  • Pˉ\bar{P}: average execution price
  • P0P_0: decision price

This total shortfall represents the aggregate cost of execution, but it does not reveal where those costs came from. To gain deeper insight, we can decompose the shortfall into components that isolate different sources of slippage:

IS=X(PˉP0)Total=X(PˉP~)Market Impact+X(P~P0)Timing Cost\begin{aligned} \text{IS} &= \underbrace{X \cdot (\bar{P} - P_0)}_{\text{Total}} \\ &= \underbrace{X \cdot (\bar{P} - \tilde{P})}_{\text{Market Impact}} + \underbrace{X \cdot (\tilde{P} - P_0)}_{\text{Timing Cost}} \end{aligned}

where:

  • IS\text{IS}: implementation shortfall
  • XX: total shares
  • Pˉ\bar{P}: average execution price
  • P0P_0: decision price
  • P~\tilde{P}: benchmark price representing value absent trading (e.g., VWAP or arrival price)

The market impact component captures the portion of the price movement that can be attributed to the trader's own activity. The timing cost component captures price drift that would have occurred even without the trader's participation, reflecting market movements that happened simply because execution took time.

A more detailed decomposition, often used in transaction cost analysis, includes additional components:

  • Delay cost: Price movement between decision and start of execution, capturing the cost of any lag between deciding to trade and entering the market
  • Market impact cost: Adverse price movement caused by our trading, the direct consequence of consuming liquidity
  • Timing cost: Unfavorable price drift during execution, representing exposure to random market movements
  • Opportunity cost: Cost of any unexecuted portion, relevant when the algorithm fails to complete the full order

Implementation Shortfall Algorithms

Implementation shortfall (IS) algorithms attempt to minimize expected shortfall while controlling its variance. Unlike VWAP, which targets a benchmark regardless of market conditions, IS algorithms adapt their aggressiveness based on an urgency parameter specified by you. The urgency parameter controls the trade-off between market impact and timing risk, allowing you to express your preferences about how to balance these competing concerns. High urgency means execute quickly to minimize timing risk while accepting higher market impact. Low urgency means execute patiently to minimize impact, accepting higher timing risk.

If you believe prices are about to move unfavorably or if you have limited risk budget for the position, you would choose high urgency. If you have a long-term view and tolerance for volatility, you would choose low urgency, preserving capital by avoiding unnecessary impact costs.

In[7]:
Code
def implementation_shortfall_schedule(
    total_shares: int,
    num_intervals: int,
    urgency: float,  # 0 = patient (TWAP-like), 1 = aggressive (front-loaded)
    volume_profile: np.ndarray = None,
) -> np.ndarray:
    """
    Generate an implementation shortfall schedule.

    Higher urgency front-loads execution to reduce timing risk.
    Lower urgency spreads execution to reduce market impact.
    """
    if volume_profile is None:
        volume_profile = np.ones(num_intervals)

    # Create base schedule from volume profile
    base_weights = volume_profile / volume_profile.sum()

    # Front-loading factor based on urgency
    # exponential decay weighted by urgency
    time_decay = np.exp(-urgency * 3 * np.arange(num_intervals) / num_intervals)

    # Combine volume profile with urgency-based front-loading
    combined_weights = base_weights * (1 - urgency) + time_decay * urgency
    combined_weights = combined_weights / combined_weights.sum()

    return total_shares * combined_weights


# Generate schedules at different urgency levels
urgencies = [0.0, 0.3, 0.7, 1.0]
is_schedules = {
    u: implementation_shortfall_schedule(
        total_order, intervals, u, volume_profile
    )
    for u in urgencies
}
Out[8]:
Visualization
Notebook output
Notebook output
Notebook output
Notebook output

The visualization demonstrates how urgency transforms the execution profile. At zero urgency (0.0), the algorithm resembles VWAP. As urgency increases to 1.0, execution concentrates increasingly in early intervals, allowing traders to reduce price volatility exposure.

The Almgren-Chriss Framework

The Almgren-Chriss model, published in 2000, provides a rigorous mathematical framework for optimal execution. It formalizes the trade-off between market impact and timing risk, deriving closed-form optimal trajectories under specific assumptions about price dynamics and market impact. This framework provided explicit mathematical solutions that practitioners could implement and analyze.

Model Setup

The framework begins by modeling the midpoint price StS_t as following arithmetic Brownian motion. This means the price evolves randomly over time, with increments that are normally distributed and independent across time periods:

dSt=σdWtdS_t = \sigma \, dW_t

where:

  • StS_t: midpoint price at time tt
  • σ\sigma: arithmetic volatility, representing the standard deviation of price changes per unit time
  • WtW_t: standard Wiener process, the mathematical representation of random price fluctuations

Note the deliberate absence of drift in this equation. The framework assumes no alpha and no expected direction of price movement. This assumption is philosophically consistent with the role of execution. The decision about what to trade and why has already been made elsewhere; it is not the execution algorithm's responsibility. The execution algorithm's job is not to predict where prices are going but to minimize the cost of getting the desired position, given that prices will fluctuate randomly during execution.

You must liquidate XX shares over time horizon TT. Let xtx_t denote remaining inventory at time tt, with initial condition x0=Xx_0 = X (we start with the full position) and terminal condition xT=0x_T = 0 (we must be completely liquidated by the horizon). The trading rate is vt=x˙tv_t = -\dot{x}_t, where the negative sign reflects that inventory decreases as we trade. A positive trading rate means we are selling, and our inventory is declining.

Market Impact Model

The model incorporates both temporary and permanent market impact, as we discussed in detail in the chapter on transaction costs. These two types of impact capture different mechanisms by which trading affects prices. When trading at rate vtv_t:

  • Permanent impact. A fraction of impact persists permanently, moving the midpoint price for all subsequent trades and the wider market. Permanent impact reflects the information content of order flow. When you trade, the market updates its beliefs about fair value. The cumulative permanent impact is g(vt)=γvtg(v_t) = \gamma v_t, where γ\gamma is the permanent impact coefficient. Every share traded permanently shifts the price by γ\gamma in the adverse direction.
  • Temporary impact. Additional adverse price movement affects only the current trade, not future ones. This Temporary impact reflects the cost of demanding immediacy. To trade quickly, one must pay for liquidity, crossing the spread or walking up the order book. The temporary impact is h(vt)=ηvt+ϵsgn(vt)h(v_t) = \eta v_t + \epsilon \, \text{sgn}(v_t), where η\eta is the temporary impact coefficient measuring the additional cost per share from trading quickly, and ϵ\epsilon represents fixed costs like half the bid-ask spread that must be paid regardless of trade size.

For mathematical tractability, and because the fixed cost does not affect the optimal trajectory shape, the model typically uses linear impact functions without the fixed component:

g(v)=γv(permanent)h(v)=ηv(temporary)\begin{aligned} g(v) &= \gamma v && \text{(permanent)} \\ h(v) &= \eta v && \text{(temporary)} \end{aligned}

where:

  • g(v)g(v): permanent impact function describing how price permanently shifts with trading
  • h(v)h(v): temporary impact function describing additional cost for the current trade only
  • γ\gamma: permanent impact coefficient in dollars per share per share traded
  • η\eta: temporary impact coefficient in dollars per share per share traded
  • vv: trading rate

The linearity assumption, while stylized, captures the essential economics. Trading faster costs more due to temporary impact, and total trading permanently moves prices. The distinction between temporary and permanent impact is crucial because permanent impact represents a cost that cannot be avoided by changing the trading schedule, while temporary impact depends critically on how aggressively we trade.

Expected Cost and Variance

The expected execution cost consists of permanent and temporary components. For a trading trajectory with trading rate vtv_t, we can derive your expected cost by integrating the impact over time. The calculation proceeds as follows:

E[Cost]=0T(γ(Xxt)vt+ηvt2)dt=γ0T(Xxt)(dxtdt)dt+0Tηvt2dt(substitute vt=x˙t)=γX0(Xx)dx+0Tηvt2dt(change variable to x)=γ[Xxx22]0X+0Tηvt2dt=12γX2+0Tηvt2dt\begin{aligned} E[\text{Cost}] &= \int_0^T \left( \gamma(X - x_t)v_t + \eta v_t^2 \right) dt \\ &= \gamma \int_0^T (X - x_t) \left(-\frac{dx_t}{dt}\right) dt + \int_0^T \eta v_t^2 \, dt && \text{(substitute } v_t = -\dot{x}_t \text{)} \\ &= -\gamma \int_X^0 (X - x) \, dx + \int_0^T \eta v_t^2 \, dt && \text{(change variable to } x \text{)} \\ &= \gamma \left[ Xx - \frac{x^2}{2} \right]_0^X + \int_0^T \eta v_t^2 \, dt \\ &= \frac{1}{2}\gamma X^2 + \int_0^T \eta v_t^2 \, dt \end{aligned}

where:

  • E[Cost]E[\text{Cost}]: expected execution cost across all trading
  • XX: initial inventory to be liquidated
  • xtx_t: remaining inventory at time tt
  • vtv_t: trading rate at time tt
  • γ\gamma: permanent impact coefficient
  • η\eta: temporary impact coefficient
  • TT: trading horizon

This derivation reveals a fundamental insight about the structure of execution costs. The expected cost decomposes into two qualitatively different terms. The permanent impact term 12γX2\frac{1}{2}\gamma X^2 depends only on total shares and is completely path-independent. No matter how we schedule our trades, we will pay this cost. The factor of one-half appears because early trades suffer the full permanent impact of later trades, while later trades have already "locked in" some of that impact, creating an averaging effect. The temporary impact term ηvt2dt\int \eta v_t^2 \, dt depends on the execution speed and is the only component we can minimize through clever scheduling. Because it is quadratic in the trading rate, this term penalizes bursts of rapid trading and rewards smooth, steady execution.

The variance of execution cost arises from the accumulation of price risk on the held inventory. The intuition is straightforward: while we hold shares, their value fluctuates randomly. The more shares we hold and the longer we hold them, the more exposed we are to these fluctuations. If the price evolves as dSt=σdWtdS_t = \sigma dW_t, the fluctuation in portfolio value is 0TxtdSt=0TxtσdWt\int_0^T x_t dS_t = \int_0^T x_t \sigma dW_t. This stochastic integral represents the cumulative random gains and losses from price movements applied to our inventory over time.

By the Itô isometry, a fundamental result in stochastic calculus, the variance of this integral can be computed by integrating the squared integrand:

Var[Cost]=E[(0TσxtdWt)2]=0TE[(σxt)2]dt(Itoˆ isometry)=σ20Txt2dt(deterministic xt)\begin{aligned} \text{Var}[\text{Cost}] &= E\left[ \left( \int_0^T \sigma x_t \, dW_t \right)^2 \right] \\ &= \int_0^T E[(\sigma x_t)^2] \, dt && \text{(Itô isometry)} \\ &= \sigma^2 \int_0^T x_t^2 \, dt && \text{(deterministic } x_t \text{)} \end{aligned}

where:

  • Var[Cost]\text{Var}[\text{Cost}]: variance of execution cost
  • σ\sigma: price volatility per unit time
  • TT: trading horizon
  • xtx_t: remaining inventory at time tt

This variance formula captures the essence of timing risk. The unexecuted inventory xtx_t remains exposed to price volatility σ\sigma at every moment. The variance integrates xt2x_t^2 because variance is a quadratic measure. Holding twice as many shares exposes us to four times the risk. An execution schedule that maintains high inventory for a long time will have high variance, while a schedule that rapidly reduces inventory will have low variance.

Mean-Variance Optimization

With expressions for both expected cost and variance in hand, we can formulate the optimization problem. The Almgren-Chriss objective balances expected cost against variance using a risk aversion parameter λ\lambda:

min{xt}E[Cost]+λVar[Cost]\min_{\{x_t\}} \quad E[\text{Cost}] + \lambda \cdot \text{Var}[\text{Cost}]

where:

  • {xt}\{x_t\}: execution trajectory over time, the decision variable we optimize
  • λ\lambda: risk aversion parameter, expressing how much the trader dislikes variance relative to expected cost

This mean-variance formulation should feel familiar. It is directly analogous to Markowitz portfolio optimization, which we covered in Part IV. In portfolio theory, we balance expected return against portfolio variance when choosing asset weights. Here, instead of choosing portfolio weights, we choose an execution trajectory. Instead of maximizing return while controlling risk, we minimize cost while controlling execution uncertainty. The mathematical structure is identical, which is why the Almgren-Chriss framework produces an "efficient frontier" of execution strategies just as Markowitz produces an efficient frontier of portfolios.

The risk aversion parameter λ\lambda is chosen by the trader and reflects their preferences. A trader with high risk aversion, large λ\lambda, will pay more in expected impact to achieve certainty about execution cost. A trader with low risk aversion, small λ\lambda, will accept uncertain outcomes in exchange for lower expected cost. There is no objectively "correct" value for λ\lambda; it depends on the trader's circumstances, constraints, and risk tolerance.

The Optimal Trajectory

To derive the optimal trajectory, we must solve this optimization problem using calculus of variations. We first observe that the permanent impact term 12γX2\frac{1}{2}\gamma X^2 depends only on total shares XX, not the trading path. Since this term is the same for all feasible execution schedules, we can drop it from the optimization without affecting which trajectory is optimal.

Substituting the trading rate vt=x˙tv_t = -\dot{x}_t into the remaining cost and variance terms gives the objective functional that we seek to minimize:

J[x]=0Tηvt2dt+λ0Tσ2xt2dt=0T(ηx˙t2+λσ2xt2)dt\begin{aligned} J[x] &= \int_0^T \eta v_t^2 \, dt + \lambda \int_0^T \sigma^2 x_t^2 \, dt \\ &= \int_0^T \left( \eta \dot{x}_t^2 + \lambda \sigma^2 x_t^2 \right) dt \end{aligned}

where:

  • J[x]J[x]: objective functional to minimize, a function of the entire trajectory
  • η\eta: temporary impact coefficient
  • x˙t\dot{x}_t: time derivative of inventory, equal to negative of trading rate
  • λ\lambda: risk aversion parameter
  • σ\sigma: volatility parameter
  • xtx_t: inventory at time tt

The integrand acts as the Lagrangian L(x,x˙)=ηx˙t2+λσ2xt2\mathcal{L}(x, \dot{x}) = \eta \dot{x}_t^2 + \lambda \sigma^2 x_t^2. This Lagrangian has the structure of a harmonic oscillator in classical mechanics, with a term quadratic in position (inventory) and a term quadratic in velocity (trading rate). This connection to physics is not merely coincidental; the same mathematical structure appears whenever a system must balance a position penalty against a velocity penalty.

The Euler-Lagrange equation provides the necessary condition for optimality. Any trajectory that minimizes the functional must satisfy:

Lxddt(Lx˙)=0\frac{\partial \mathcal{L}}{\partial x} - \frac{d}{dt} \left( \frac{\partial \mathcal{L}}{\partial \dot{x}} \right) = 0

where:

  • L\mathcal{L}: Lagrangian function
  • xx: state variable representing inventory
  • x˙\dot{x}: time derivative of state variable representing trading rate

Calculating the necessary partial derivatives from our Lagrangian:

Lx=2λσ2xtLx˙=2ηx˙tddt(2ηx˙t)=2ηx¨t\begin{aligned} \frac{\partial \mathcal{L}}{\partial x} &= 2 \lambda \sigma^2 x_t \\ \frac{\partial \mathcal{L}}{\partial \dot{x}} &= 2 \eta \dot{x}_t \\ \frac{d}{dt} \left( 2 \eta \dot{x}_t \right) &= 2 \eta \ddot{x}_t \end{aligned}

where:

  • L\mathcal{L}: Lagrangian function
  • xtx_t: inventory at time tt
  • x˙t\dot{x}_t: first time derivative of inventory (negative of trading rate)
  • x¨t\ddot{x}_t: second time derivative of inventory (negative of rate of change of trading rate)
  • λ\lambda: risk aversion parameter
  • σ\sigma: volatility parameter
  • η\eta: temporary impact coefficient

Substituting these expressions into the Euler-Lagrange equation LxddtLx˙=0\frac{\partial \mathcal{L}}{\partial x} - \frac{d}{dt} \frac{\partial \mathcal{L}}{\partial \dot{x}} = 0 yields the governing linear differential equation:

2λσ2xt2ηx¨t=0x¨tλσ2ηxt=0\begin{aligned} 2 \lambda \sigma^2 x_t - 2 \eta \ddot{x}_t &= 0 \\ \ddot{x}_t - \frac{\lambda \sigma^2}{\eta} x_t &= 0 \end{aligned}

where:

  • x¨t\ddot{x}_t: second time derivative of inventory
  • λ\lambda: risk aversion parameter
  • σ\sigma: price volatility
  • η\eta: temporary impact coefficient
  • xtx_t: inventory at time tt

This is a second-order linear ordinary differential equation with constant coefficients. The positive coefficient on xtx_t indicates that solutions will be hyperbolic functions rather than trigonometric ones. Solving this ODE subject to boundary conditions x(0)=Xx(0)=X (we start with full inventory) and x(T)=0x(T)=0 (we must be fully liquidated by the horizon) yields a remarkable result: the optimal trajectory has a closed-form solution expressible in terms of hyperbolic functions.

Defining the risk-adjusted time constant that emerges naturally from the coefficient ratio:

κ=λσ2η\kappa = \sqrt{\frac{\lambda \sigma^2}{\eta}}

where:

  • κ\kappa: decay constant for the optimal trajectory, with units of inverse time
  • λ\lambda: risk aversion parameter
  • σ2\sigma^2: price variance rate
  • η\eta: temporary impact coefficient

This parameter κ\kappa plays a central role in the solution. It combines all the relevant model parameters into a single quantity that determines the shape of optimal execution. The numerator λσ2\lambda \sigma^2 represents the marginal cost of holding inventory, risk aversion times variance, while the denominator η\eta represents the marginal cost of trading faster. The ratio balances these competing costs, and the square root converts this ratio into a time-scale parameter.

The optimal inventory trajectory is:

xt=Xsinh(κ(Tt))sinh(κT)x_t^* = X \cdot \frac{\sinh(\kappa(T-t))}{\sinh(\kappa T)}

where:

  • xtx_t^*: optimal remaining inventory at time tt
  • XX: initial inventory
  • TT: trading horizon
  • κ\kappa: risk-adjusted decay constant
  • sinh\sinh: hyperbolic sine function, defined as sinh(z)=(ezez)/2\sinh(z) = (e^z - e^{-z})/2

This elegant formula describes exactly how inventory should decline over time. The hyperbolic sine function ensures that the trajectory satisfies both boundary conditions. At t=0t=0, the argument is κT\kappa T and the ratio equals 1, giving x0=Xx_0 = X. At t=Tt=T, the argument is 0 and sinh(0)=0\sinh(0)=0, giving xT=0x_T=0.

The optimal trading rate follows as the derivative of the inventory trajectory:

vt=Xκcosh(κ(Tt))sinh(κT)v_t^* = X \cdot \frac{\kappa \cosh(\kappa(T-t))}{\sinh(\kappa T)}

where:

  • vtv_t^*: optimal trading rate
  • XX: initial inventory
  • κ\kappa: risk-adjusted decay constant
  • TT: trading horizon
  • tt: time
  • cosh,sinh\cosh, \sinh: hyperbolic cosine and sine functions, where cosh(z)=(ez+ez)/2\cosh(z) = (e^z + e^{-z})/2

The trading rate is highest at the beginning when cosh(κ(Tt))\cosh(\kappa(T-t)) is largest, and decreases over time as the remaining horizon shrinks.

Interpretation of κ\kappa

The parameter κ\kappa determines the shape of the optimal trajectory. It balances the marginal cost of slow execution (timing risk, proportional to λ σ 2) against the marginal cost of fast execution (temporary impact, proportional to η). When κ is large as with high risk aversion or volatility and low temporary impact, optimal execution is front-loaded. When κ\kappa is small, execution approaches TWAP.

The optimal trajectory encompasses several intuitive limiting cases that help build understanding of what the solution means:

  • Risk-neutral trader (λ → 0): When you have no risk aversion, κ → 0 and the optimal strategy approaches TWAP. Without concern for timing risk, there is no reason to accept higher impact by trading quickly. Mathematically, as κ0\kappa \to 0, the ratio sinh(κ(Tt))/sinh(κT)\sinh(\kappa(T-t))/\sinh(\kappa T) approaches (Tt)/T(T-t)/T, which is the linear inventory path of TWAP.

  • Infinite risk aversion (λ → ∞): When timing risk is completely unacceptable, κ → ∞ and you liquidate immediately, accepting maximum market impact. You effectively treat any exposure to price uncertainty as infinitely costly and are willing to pay any amount in impact to eliminate it.

  • High volatility: Increasing σ increases κ, leading to faster execution. Higher volatility means greater timing risk per unit time, which justifies the acceptance of more impact to complete sooner. This makes intuitive sense: if prices are whipping around wildly, you want to finish quickly rather than remain exposed.

  • High temporary impact: Increasing η decreases κ, leading to slower execution. When impact is costly, patience pays off, as each share of accelerated trading costs more. This captures the fundamental trade-off: when impact is cheap, trade fast; when impact is expensive, trade slow.

In[9]:
Code
def almgren_chriss_trajectory(
    X: float,  # Total shares to execute
    T: float,  # Time horizon
    sigma: float,  # Price volatility (per unit time)
    eta: float,  # Temporary impact coefficient
    lambda_risk: float,  # Risk aversion parameter
    num_points: int = 100,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Compute the optimal Almgren-Chriss execution trajectory.

    Returns:
        t: Time points
        x: Inventory at each time point
        v: Trading rate at each time point
    """
    # Risk-adjusted decay parameter
    kappa = np.sqrt(lambda_risk * sigma**2 / eta)

    # Time grid
    t = np.linspace(0, T, num_points)

    # Optimal inventory trajectory
    if kappa * T < 1e-6:  # Near risk-neutral case
        x = X * (1 - t / T)  # TWAP
        v = np.full_like(t, X / T)
    else:
        x = X * np.sinh(kappa * (T - t)) / np.sinh(kappa * T)
        v = X * kappa * np.cosh(kappa * (T - t)) / np.sinh(kappa * T)

    return t, x, v


import numpy as np

# Parameters
X = 100_000  # shares
T = 1.0  # normalized time horizon
sigma = 0.02  # 2% volatility over horizon
eta = 2.5e-6  # temporary impact coefficient

# Different risk aversion levels
lambdas = [0.01, 0.1, 1.0, 10.0]
trajectories = {}

for lam in lambdas:
    t, x, v = almgren_chriss_trajectory(X, T, sigma, eta, lam)
    trajectories[lam] = {"t": t, "x": x, "v": v}
Out[10]:
Visualization
Line plot showing declining inventory curves from 100000 to 0 shares, with steeper initial decline for higher risk aversion.
Optimal inventory trajectories under the Almgren-Chriss model for liquidating 100,000 shares across risk aversion levels. Risk-neutral traders (λ = 0.01) follow nearly linear TWAP-like paths, while risk-averse traders (λ = 10.0) exhibit increasingly convex trajectories, completing over 60% of execution in the first 30% of the time horizon.
Line plot showing trading rate over time, with higher initial rates for risk-averse traders.
Optimal trading rates corresponding to inventory trajectories under the Almgren-Chriss model. Trading rates are highest at the beginning and decay over time, with more rapid decay for higher risk aversion. The hyperbolic decay pattern reflects the optimal balance between temporary impact costs from fast trading and timing risk costs from holding inventory.

The trajectories reveal the fundamental insight of the Almgren-Chriss model. A risk-neutral trader (λ=0.01\lambda = 0.01) executes nearly linearly, resembling TWAP. As risk aversion increases, the trajectory becomes increasingly convex, with rapid early execution that slows as the position decreases. The most risk-averse trader (λ=10\lambda = 10) completes over half the execution in the first 20% of the time horizon.

Computing Expected Cost and Risk

The optimal trajectory achieves a specific expected cost and variance that depend on the model parameters. These expressions allow you to understand what you gain and give up by choosing a particular level of risk aversion. For the Almgren-Chriss solution:

E[Cost]=γX22+ηX2κ(cosh(κT)1)Tsinh(κT)E[\text{Cost}^*] = \frac{\gamma X^2}{2} + \frac{\eta X^2 \kappa (\cosh(\kappa T) - 1)}{T \sinh(\kappa T)}

where:

  • E[Cost]E[\text{Cost}^*]: minimal expected cost achievable for the given risk aversion
  • γ,η\gamma, \eta: permanent and temporary impact coefficients
  • κ\kappa: risk-adjusted decay constant
  • XX: total shares
  • TT: time horizon
  • cosh,sinh\cosh, \sinh: hyperbolic cosine and sine functions

The first term γX22\frac{\gamma X^2}{2} represents the unavoidable permanent impact cost that applies regardless of execution strategy. The second term represents the minimized temporary impact cost and depends on how aggressively we trade. A higher κ\kappa (more aggressive trading) increases this term because we incur more temporary impact.

The minimal variance of the execution cost is given by integrating the squared inventory trajectory:

Var[Cost]=σ20T(xt)2dt\text{Var}[\text{Cost}^*] = \sigma^2 \int_0^T (x_t^*)^2 \, dt

where:

  • Var[Cost]\text{Var}[\text{Cost}^*]: minimal cost variance for the given trajectory
  • σ\sigma: price volatility
  • xtx_t^*: optimal inventory trajectory
  • TT: trading horizon

This integral can be computed in closed form using properties of hyperbolic functions. The key observation is that faster execution, higher κ\kappa, reduces variance by reducing the integral of squared inventory but increases expected cost through higher temporary impact.

In[11]:
Code
import numpy as np


def almgren_chriss_cost_variance(
    X: float,
    T: float,
    sigma: float,
    eta: float,
    gamma: float,
    lambda_risk: float,
) -> tuple[float, float]:
    """Compute expected cost and variance for the optimal trajectory."""
    kappa = np.sqrt(lambda_risk * sigma**2 / eta)

    if kappa * T < 1e-6:
        # TWAP case
        expected_cost = gamma * X**2 / 2 + eta * X**2 / T
        variance = sigma**2 * X**2 * T / 3
    else:
        # General case
        sinh_kT = np.sinh(kappa * T)
        cosh_kT = np.cosh(kappa * T)
        tanh_kT2 = np.tanh(kappa * T / 2)

        expected_cost = gamma * X**2 / 2 + eta * X**2 * kappa * (
            cosh_kT - 1
        ) / (T * sinh_kT)
        variance = sigma**2 * X**2 * tanh_kT2 / (2 * kappa * T)

    return expected_cost, variance


# Compute efficient frontier
gamma = 1e-6  # permanent impact coefficient
lambda_values = np.logspace(-3, 2, 50)

costs = []
risks = []

for lam in lambda_values:
    cost, var = almgren_chriss_cost_variance(X, T, sigma, eta, gamma, lam)
    costs.append(cost)
    risks.append(np.sqrt(var))

costs = np.array(costs)
risks = np.array(risks)
Out[12]:
Visualization
Curved line showing decreasing expected cost with increasing execution risk, demonstrating the cost-risk trade-off.
Execution efficient frontier showing the cost-risk trade-off when liquidating 100,000 shares. The downward-sloping frontier reveals a fundamental insight: reducing execution cost requires accepting higher uncertainty in outcomes, as risk-averse traders willingly incur higher expected costs to achieve lower variance in execution results.

The efficient frontier illustrates that reducing execution risk requires accepting higher expected cost. Just as in portfolio theory, there is no free lunch: faster execution eliminates timing risk but increases market impact. The trader's risk aversion determines where on this frontier they should operate.

Adaptive Execution Algorithms

While the Almgren-Chriss framework provides optimal trajectories given known parameters, real markets present additional challenges. Volatility fluctuates, liquidity varies, and short-term price movements may signal information. Adaptive algorithms modify their behavior in response to market conditions.

Percentage of Volume (POV)

Percentage of Volume algorithms trade as a fixed fraction of observed market volume, adapting execution speed to current liquidity. The algorithm monitors real-time trading activity and participates proportionally. This approach gives you natural camouflage.

vt=αVtmarketv_t = \alpha \cdot V_t^{\text{market}}

where:

  • vtv_t: trading rate at time tt
  • α\alpha: target participation rate, typically between 5% and 20%
  • VtmarketV_t^{\text{market}}: observed market volume at time tt

The participation rate α\alpha is specified by you and represents what fraction of all market activity the algorithm should constitute. A 10% participation rate means that for every 100 shares traded in the market the algorithm trades 10.

POV offers natural camouflage. By trading proportionally with the market, your algorithm's activity is harder to distinguish from background flow. Other market participants cannot easily detect that your algorithmic execution is underway because the trading pattern mirrors overall market activity. POV also automatically slows during thin markets and accelerates during liquid periods, providing a built-in adaptation mechanism that schedule-based algorithms lack.

In[13]:
Code
def simulate_pov_execution(
    total_shares: int,
    participation_rate: float,
    market_volumes: np.ndarray,
    max_participation: float = 0.25,  # Cap to avoid excessive impact
) -> tuple[np.ndarray, np.ndarray]:
    """
    Simulate POV execution given market volume series.

    Returns:
        executed: Shares executed in each interval
        remaining: Remaining inventory after each interval
    """
    remaining = total_shares
    executed = []
    inventory = [remaining]

    for vol in market_volumes:
        if remaining <= 0:
            executed.append(0)
            inventory.append(0)
            continue

        # Trade up to participation rate of volume, capped
        target_trade = min(participation_rate * vol, max_participation * vol)
        actual_trade = min(target_trade, remaining)

        executed.append(actual_trade)
        remaining -= actual_trade
        inventory.append(remaining)

    return np.array(executed), np.array(inventory[:-1])


import numpy as np


# Simulate varying market volume
np.random.seed(42)
num_periods = 50
base_volume = create_intraday_volume_profile(num_periods)
# Add random variation to volume
market_volumes = (
    100_000 * base_volume * (1 + 0.3 * np.random.randn(num_periods))
)
market_volumes = np.maximum(market_volumes, 1000)  # Ensure positive

# Execute with 15% POV
pov_executed, pov_remaining = simulate_pov_execution(
    total_shares=50_000, participation_rate=0.15, market_volumes=market_volumes
)
Out[14]:
Visualization
Dual-axis chart showing market volume bars and executed shares line tracking each other proportionally.
POV algorithm executing 50,000 shares at 15% participation rate, automatically scaling execution to match market volume across 50 intervals. The algorithm concentrates execution during high-volume periods at market open and close, declining during midday, which maintains consistent proportional participation and reduces market impact.

Pegging Algorithms

Pegging algorithms dynamically adjust limit order prices to maintain a position relative to the current market. Rather than crossing the spread to execute immediately, these algorithms post passive orders that capture Common peg types include:

  • Midpoint peg: order price tracks the midpoint between bid and ask, offering price improvement to both sides
  • Primary peg: order tracks the same-side best quote, joining the queue at the bid for buys or the ask for sells
  • Passive peg: order tracks one tick behind the best quote for added price improvement at the cost of lower fill probability

These algorithms are useful when you prioritize execution price. By posting limit orders rather than crossing the spread, pegging algorithms capture the spread rather than paying it but risk non-execution if the market moves away.

In[15]:
Code
def simulate_midpoint_peg(
    total_shares: int,
    num_periods: int,
    fill_probability: float = 0.3,  # Probability of fill each period when pegged
    price_volatility: float = 0.01,
) -> dict:
    """
    Simulate midpoint pegging execution.

    Lower fill probability reflects the uncertainty of passive execution.
    """
    np.random.seed(123)

    remaining = total_shares
    period_size = total_shares // num_periods

    executed = []
    midpoints = [100.0]  # Starting midpoint
    execution_prices = []

    for i in range(num_periods):
        # Random midpoint movement
        midpoint = midpoints[-1] * (1 + price_volatility * np.random.randn())
        midpoints.append(midpoint)

        if remaining <= 0:
            executed.append(0)
            continue

        # Probabilistic fill at midpoint
        if np.random.random() < fill_probability:
            fill_amount = min(period_size, remaining)
            executed.append(fill_amount)
            execution_prices.extend([midpoint] * int(fill_amount // 100))
            remaining -= fill_amount
        else:
            executed.append(0)

    return {
        "executed": np.array(executed),
        "midpoints": np.array(midpoints[1:]),
        "remaining": remaining,
        "avg_price": np.mean(execution_prices) if execution_prices else None,
    }


import numpy as np


peg_target = 50_000
peg_result = simulate_midpoint_peg(peg_target, 50, fill_probability=0.35)
Out[16]:
Console
Midpoint Pegging Results:
Total executed: 17,000 / 50,000 shares (34.0%)
Remaining: 33,000 shares
Average execution price: $101.3119
Out[17]:
Visualization
Midpoint pegging execution simulation for 50,000 shares over 50 periods with 35% fill probability per period. Executed fills reach approximately 17,500 shares, revealing how midpoint pegging captures bid-ask spread savings at the cost of execution uncertainty. This strategy is optimal when price improvement matters more than completing the full order.
Midpoint pegging execution simulation for 50,000 shares over 50 periods with 35% fill probability per period. Executed fills reach approximately 17,500 shares, revealing how midpoint pegging captures bid-ask spread savings at the cost of execution uncertainty. This strategy is optimal when price improvement matters more than completing the full order.

Adaptive Shortfall Algorithms

Modern implementation shortfall algorithms combine the theoretical foundation of Almgren-Chriss with real-time adaptation. Rather than committing to a fixed trajectory computed at the start of execution, these algorithms continuously revise their plans based on incoming market data. Key features include:

  • Intraday volatility estimation: adjusting urgency based on current versus expected volatility
  • Spread monitoring: slowing execution when spreads widen beyond normal levels
  • Momentum detection: accelerating or decelerating based on short-term price trends
  • Volume surprise: adjusting participation when actual volume differs from forecast

These algorithms maintain a target trajectory as a baseline but allow deviations when market conditions diverge significantly from expectations.

In[18]:
Code
class AdaptiveISAlgorithm:
    """
    Adaptive Implementation Shortfall algorithm that adjusts
    execution based on real-time market conditions.
    """

    def __init__(
        self,
        total_shares: int,
        num_periods: int,
        base_urgency: float = 0.5,
        volatility_sensitivity: float = 0.5,
        spread_sensitivity: float = 0.3,
    ):
        self.total_shares = total_shares
        self.num_periods = num_periods
        self.base_urgency = base_urgency
        self.vol_sens = volatility_sensitivity
        self.spread_sens = spread_sensitivity

        # Generate base schedule
        self.base_schedule = implementation_shortfall_schedule(
            total_shares, num_periods, base_urgency
        )
        self.remaining = total_shares
        self.executed_history = []

    def compute_execution(
        self,
        period: int,
        realized_volatility: float,
        expected_volatility: float,
        current_spread: float,
        normal_spread: float,
    ) -> float:
        """Compute adaptive execution for current period."""

        if self.remaining <= 0:
            return 0

        # Base execution from schedule
        base_exec = self.base_schedule[period]

        # Volatility adjustment: execute faster when vol is high
        vol_ratio = realized_volatility / expected_volatility
        vol_adjustment = 1 + self.vol_sens * (vol_ratio - 1)

        # Spread adjustment: slow down when spreads are wide
        spread_ratio = current_spread / normal_spread
        spread_adjustment = 1 - self.spread_sens * (spread_ratio - 1)
        spread_adjustment = max(0.3, min(1.5, spread_adjustment))  # Bounds

        # Combined adjustment
        adjusted_exec = base_exec * vol_adjustment * spread_adjustment

        # Cap at remaining inventory
        actual_exec = min(adjusted_exec, self.remaining)

        self.remaining -= actual_exec
        self.executed_history.append(actual_exec)

        return actual_exec


import numpy as np


# Simulate adaptive execution
np.random.seed(456)

algo = AdaptiveISAlgorithm(
    total_shares=100_000, num_periods=40, base_urgency=0.5
)

# Simulate market conditions
expected_vol = 0.015
normal_spread = 0.02

adaptive_executions = []
market_conditions = []

for period in range(40):
    # Varying market conditions
    if 10 <= period <= 20:  # High volatility regime
        realized_vol = expected_vol * (1.5 + 0.3 * np.random.randn())
        current_spread = normal_spread * (1.3 + 0.2 * np.random.randn())
    else:  # Normal regime
        realized_vol = expected_vol * (1.0 + 0.2 * np.random.randn())
        current_spread = normal_spread * (1.0 + 0.1 * np.random.randn())

    realized_vol = max(0.005, realized_vol)
    current_spread = max(0.005, current_spread)

    execution = algo.compute_execution(
        period, realized_vol, expected_vol, current_spread, normal_spread
    )

    adaptive_executions.append(execution)
    market_conditions.append(
        {"volatility": realized_vol, "spread": current_spread}
    )

adaptive_executions = np.array(adaptive_executions)
Out[19]:
Visualization
Three-panel chart showing volatility conditions, spread conditions, and resulting execution pattern over 40 periods.
Adaptive implementation shortfall algorithm executing 100,000 shares in response to changing market conditions. During periods 10 to 20, realized volatility spikes and spreads widen, causing the algorithm to accelerate execution. Outside these volatile periods, execution follows the baseline schedule, demonstrating how adaptive algorithms adjust execution rates to dynamic market conditions.

The adaptive algorithm increases execution during the high-volatility regime (periods 10-20) to reduce timing risk, while the widened spreads partially counteract this acceleration. This dynamic adjustment is impossible with static schedules like TWAP or pre-computed VWAP.

Putting It All Together: An Execution Simulator

To solidify these concepts, let's build a complete execution simulation that compares algorithm performance across different market scenarios.

In[20]:
Code
class ExecutionSimulator:
    """
    Simulates execution of different algorithms against
    realistic price dynamics with market impact.
    """

    def __init__(
        self,
        initial_price: float,
        volatility: float,
        permanent_impact: float,
        temporary_impact: float,
        num_periods: int,
    ):
        self.S0 = initial_price
        self.sigma = volatility
        self.gamma = permanent_impact
        self.eta = temporary_impact
        self.num_periods = num_periods

    def simulate_execution(
        self, schedule: np.ndarray, seed: int = None
    ) -> dict:
        """
        Simulate execution and compute performance metrics.

        Returns execution prices, costs, and statistics.
        """
        if seed is not None:
            np.random.seed(seed)

        n = len(schedule)
        prices = [self.S0]
        execution_prices = []

        # Price follows Brownian motion with impact
        for i in range(n):
            shares = schedule[i]

            # Permanent impact shifts fundamental price
            permanent_shift = self.gamma * shares

            # Random price movement
            random_shock = self.sigma * np.random.randn() / np.sqrt(n)

            # New fundamental price
            new_price = prices[-1] + permanent_shift + random_shock
            prices.append(new_price)

            # Execution occurs at fundamental + temporary impact
            if shares > 0:
                temp_impact = self.eta * shares
                exec_price = new_price + temp_impact
                execution_prices.append((shares, exec_price))

        # Compute metrics
        total_shares = sum(s for s, _ in execution_prices)
        if total_shares > 0:
            vwap_exec = sum(s * p for s, p in execution_prices) / total_shares
        else:
            vwap_exec = self.S0

        implementation_shortfall = (vwap_exec - self.S0) * total_shares

        return {
            "prices": np.array(prices),
            "execution_prices": execution_prices,
            "average_price": vwap_exec,
            "implementation_shortfall": implementation_shortfall,
            "arrival_price": self.S0,
            "final_price": prices[-1],
        }


# Set up simulation parameters
sim = ExecutionSimulator(
    initial_price=100.0,
    volatility=2.0,  # $2 volatility over execution period
    permanent_impact=1e-5,  # Price moves $0.01 per 1000 shares permanently
    temporary_impact=2e-5,  # Additional $0.02 per 1000 shares temporarily
    num_periods=40,
)

# Generate different schedules
order_size = 100_000
num_periods = 40

schedules = {
    "TWAP": generate_twap_schedule(order_size, num_periods),
    "VWAP": generate_vwap_schedule(
        order_size, create_intraday_volume_profile(num_periods)
    ),
    "IS-Patient": implementation_shortfall_schedule(
        order_size, num_periods, 0.3
    ),
    "IS-Urgent": implementation_shortfall_schedule(
        order_size, num_periods, 0.8
    ),
}

import numpy as np


# Run multiple simulations for each schedule
num_simulations = 500
results = {name: [] for name in schedules}

for name, schedule in schedules.items():
    for seed in range(num_simulations):
        result = sim.simulate_execution(schedule, seed=seed)
        results[name].append(result["implementation_shortfall"])
Out[21]:
Visualization
Box plot comparing implementation shortfall distributions for TWAP, VWAP, IS-Patient, and IS-Urgent algorithms.
Distribution of implementation shortfall across 500 Monte Carlo simulations comparing four execution algorithms. IS-Urgent achieves the lowest median shortfall but exhibits the widest distribution, while TWAP shows the highest median cost with the narrowest distribution: the fundamental trade-off shows that aggressive execution reduces expected cost but increases outcome uncertainty, while patient execution provides stable costs at higher average levels.
In[22]:
Code
import numpy as np

performance_metrics = []
for name in schedules.keys():
    mean_is = np.mean(results[name])
    std_is = np.std(results[name])
    # Negative because lower IS is better
    sharpe_like = -mean_is / std_is if std_is > 0 else 0
    performance_metrics.append((name, mean_is, std_is, sharpe_like))
Out[23]:
Console
Algorithm Performance Summary:
------------------------------------------------------------
Algorithm       Mean IS ($)     Std Dev ($)     Sharpe-like    
------------------------------------------------------------
TWAP            58,271.74       119,247.10      -0.489         
VWAP            58,184.59       118,062.57      -0.493         
IS-Patient      58,616.04       107,360.20      -0.546         
IS-Urgent       60,791.98       88,313.35       -0.688         

The simulation reveals the trade-offs inherent in execution algorithm selection. TWAP provides stable, predictable performance but misses opportunities to reduce timing risk. The urgent IS algorithm achieves the lowest average shortfall by completing execution quickly, but exhibits higher variance because it's more sensitive to the initial price movements. The "Sharpe-like" ratio (negative mean shortfall divided by standard deviation) provides a risk-adjusted measure of execution quality.

Limitations and Practical Considerations

The theoretical frameworks presented here provide valuable intuition, but practitioners must navigate several challenges when implementing execution algorithms in production.

Model Limitations

The Almgren-Chriss framework assumes linear market impact but empirical research consistently finds that impact is concave. Your first thousand shares have greater per-share price impact than your next thousand. Square-root impact models often fit data better:

ΔPσQV\Delta P \propto \sigma \sqrt{\frac{Q}{V}}

where:

  • ΔP\Delta P: price impact
  • σ\sigma: volatility
  • QQ: order size
  • VV: market volume

However, these models do not yield closed-form optimal trajectories.

Similarly, the assumption of constant volatility fails during news events, earnings announcements, and market stress. Adaptive algorithms attempt to address this but accurate real-time volatility estimation remains challenging. The model also ignores the bid-ask spread and other fixed costs, which can dominate impact costs for smaller orders.

Parameter Estimation

Optimal execution requires knowing impact coefficients γ\gamma and η\eta, volatility σ\sigma, and risk aversion λ\lambda. Permanent impact is notoriously difficult to measure. It requires separating the price movement caused by your trading from the price movement that would have occurred anyway. Temporary impact estimates are sensitive to the measurement window. These parameters likely vary by security, time of day, market conditions and order size. A model calibrated on historical data may perform poorly when your trading conditions change.

Adversarial Considerations

In practice, execution algorithms operate in an adversarial environment. High-frequency traders can detect algorithmic patterns and trade against them in a practice known as "algo sniffing." TWAP's predictability makes it particularly vulnerable. Even sophisticated algorithms can be reverse-engineered if their behavior is sufficiently regular. This creates pressure toward randomization and adaptation that goes beyond what theory suggests.

Implementation Details

The difference between theoretical and realized performance often comes down to implementation details that academic models ignore. These include:

  • Order routing decisions
  • Exchange selection
  • Handling of partial fills
  • Behavior at the close
  • Response to exchange outages As we will discuss in the next chapter on trading systems infrastructure, these engineering challenges can easily overwhelm your theoretical gains from optimal scheduling.

Summary

This chapter developed the theory and practice of optimal execution, a critical component of your quantitative trading system. We covered several key concepts and algorithms:

Schedule-based algorithms provide simple, transparent approaches to execution. TWAP divides orders evenly across time, while VWAP weights execution according to expected volume patterns. These algorithms are easy for you to implement and explain, but they cannot adapt to changing market conditions.

Implementation shortfall measures the total cost of implementing a trading decision by comparing actual execution to a paper portfolio that traded at decision prices. IS algorithms balance market impact against timing risk based on trader-specified urgency parameters.

The Almgren-Chriss framework formalizes optimal execution as a mean-variance optimization problem. The key insight is that the optimal trajectory depends on a single parameter κ=λσ2/η\kappa = \sqrt{\lambda \sigma^2 / \eta} that balances timing risk (proportional to λσ2\lambda \sigma^2) against temporary impact (proportional to If you are risk-averse, you should front-load execution, while if you are risk-neutral, you should approach TWAP.

Adaptive algorithms extend these theoretical foundations by adjusting to real-time market conditions. POV algorithms trade proportionally with market volume, pegging algorithms capture spread by posting passive orders, and adaptive IS algorithms modify their baseline trajectory based on volatility and spread observations.

Your choice of execution algorithm depends on your objectives, the characteristics of the security, and current market conditions. No single algorithm dominates in all scenarios. Understanding the trade-offs between impact and timing risk, and having the infrastructure to implement appropriate algorithms, often determines whether your theoretical alpha translates into realized profits. chapter addresses the systems and infrastructure required to deploy these algorithms in production environments.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about execution algorithms and optimal execution.

Loading component...

Reference

BIBTEXAcademic
@misc{optimalexecutionalgorithmstwapvwapmarketimpact, author = {Michael Brenndoerfer}, title = {Optimal Execution Algorithms: TWAP, VWAP & Market Impact}, year = {2025}, url = {https://mbrenndoerfer.com/writing/execution-algorithms-optimal-trading-strategies}, organization = {mbrenndoerfer.com}, note = {Accessed: 2025-01-01} }
APAAcademic
Michael Brenndoerfer (2025). Optimal Execution Algorithms: TWAP, VWAP & Market Impact. Retrieved from https://mbrenndoerfer.com/writing/execution-algorithms-optimal-trading-strategies
MLAAcademic
Michael Brenndoerfer. "Optimal Execution Algorithms: TWAP, VWAP & Market Impact." 2026. Web. today. <https://mbrenndoerfer.com/writing/execution-algorithms-optimal-trading-strategies>.
CHICAGOAcademic
Michael Brenndoerfer. "Optimal Execution Algorithms: TWAP, VWAP & Market Impact." Accessed today. https://mbrenndoerfer.com/writing/execution-algorithms-optimal-trading-strategies.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Optimal Execution Algorithms: TWAP, VWAP & Market Impact'. Available at: https://mbrenndoerfer.com/writing/execution-algorithms-optimal-trading-strategies (Accessed: today).
SimpleBasic
Michael Brenndoerfer (2025). Optimal Execution Algorithms: TWAP, VWAP & Market Impact. https://mbrenndoerfer.com/writing/execution-algorithms-optimal-trading-strategies