Optimal Execution Algorithms: TWAP, VWAP & Market Impact › Adaptive Execution Algorithms
Quantitative FinanceAll chapters Optimal Execution Algorithms: TWAP, VWAP & Market Impact › Adaptive Execution AlgorithmsOne section · Sections in this chapter6 / 10 ↓
Reading tools Definitions & inline links
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.
Article links
Make inline references clickable
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.
where:
- : trading rate at time
- : target participation rate, typically between 5% and 20%
- : observed market volume at time
The participation rate 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, and gives a built-in adaptation mechanism that schedule-based algorithms lack.
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
)
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 the spread. Common peg types include:
- Midpoint peg: order price tracks the midpoint between bid and ask; it has 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.
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)Midpoint Pegging Results: Total executed: 17,000 / 50,000 shares (34.0%) Remaining: 33,000 shares Average execution price: $101.3119

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.
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)


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.