Execution Algorithms (wraquant.execution)

Execution algorithms for minimizing market impact and transaction costs: TWAP, VWAP, Almgren-Chriss optimal execution, and transaction cost analysis.

Quick Example

from wraquant.execution import algorithms, cost, optimal

# TWAP schedule: split order evenly across time
schedule = algorithms.twap(total_shares=10000, n_intervals=20)

# VWAP schedule: split order proportional to volume profile
schedule = algorithms.vwap(total_shares=10000, volume_profile=hist_volume)

# Almgren-Chriss optimal execution
ac = optimal.almgren_chriss(
    total_shares=10000,
    volatility=0.02,
    impact_coeff=0.001,
    risk_aversion=1e-6,
    T=1.0,
    n_intervals=20,
)
print(f"Optimal trajectory: {ac['trajectory']}")
print(f"Expected cost: {ac['expected_cost']:.4f}")

# Transaction cost estimation
tc = cost.estimate_cost(shares=10000, price=50.0, spread=0.02, impact=0.001)
print(f"Estimated cost: ${tc:.2f}")

See also

API Reference

Execution algorithms, optimal execution, and transaction cost analysis.

Provides a full suite of tools for splitting large orders into smaller child orders to minimize market impact, plus analytical frameworks for measuring and modeling transaction costs. Bridges the gap between portfolio construction (wraquant.opt) and realized performance by quantifying the cost of turning target weights into actual positions.

Key sub-modules:

  • Algorithms (algorithms) – Standard execution schedules: twap_schedule (Time-Weighted Average Price – uniform slicing), vwap_schedule (Volume-Weighted – follows intraday volume profile), pov_schedule (Percentage of Volume), is_schedule (Implementation Shortfall – front-loads to minimize drift risk), adaptive_schedule (adjusts in real time), and arrival_price_benchmark.

  • Optimal execution (optimal) – Analytical optimal trading trajectories: almgren_chriss (mean-variance optimal execution balancing urgency risk against market impact), bertsimas_lo (dynamic programming approach), optimal_execution_cost (expected cost of a given trajectory), and execution_frontier (cost-risk trade-off curve).

  • Transaction cost analysis (cost) – Post-trade TCA: slippage, commission_cost, market_impact_model (square-root or linear), liquidity_adjusted_cost, total_cost aggregation, expected_cost_model (pre-trade cost estimation), and transaction_cost_analysis (full TCA report).

Example

>>> from wraquant.execution import vwap_schedule, almgren_chriss
>>> schedule = vwap_schedule(total_shares=100_000, n_intervals=78)
>>> trajectory = almgren_chriss(
...     total_shares=100_000, T=1.0, sigma=0.02, eta=0.01, gamma=0.001
... )

Use wraquant.execution when you need to model or plan how to execute trades efficiently. For measuring how microstructure affects your fills, see wraquant.microstructure. For backtesting that incorporates transaction costs, pass cost models to wraquant.backtest.

twap_schedule(total_qty, n_intervals)[source]

Time-weighted average price (TWAP) schedule.

Use TWAP for low-urgency orders in liquid markets where you want to minimise timing risk by spreading execution evenly over time. TWAP is the simplest algorithm and is often used as a benchmark for more sophisticated strategies.

Splits total_qty evenly across n_intervals.

Parameters:
  • total_qty (float) – Total quantity to execute (positive for buys).

  • n_intervals (int) – Number of time intervals (e.g., 78 for 5-min bars in a 6.5-hour trading day).

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Array of length n_intervals with equal quantities. Each element is total_qty / n_intervals.

Example

>>> schedule = twap_schedule(10_000, n_intervals=10)
>>> schedule[0]
1000.0
>>> len(schedule)
10

See also

vwap_schedule: Volume-proportional scheduling. participation_rate_schedule: Participation-of-volume scheduling.

vwap_schedule(total_qty, historical_volume_profile)[source]

Volume-weighted average price (VWAP) schedule.

Use VWAP when you want to trade “at the market’s pace” and your benchmark is the market VWAP. Allocating proportionally to historical volume minimises deviation from VWAP and is the most common institutional execution benchmark.

Allocates total_qty proportionally to the historical volume profile so that execution tracks the expected VWAP.

Parameters:
  • total_qty (float) – Total quantity to execute.

  • historical_volume_profile (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected volume in each interval (e.g., average intraday volume by 5-min bucket). The shape determines the number of intervals.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval, proportional to expected volume. Sum equals total_qty.

Example

>>> import numpy as np
>>> # U-shaped intraday volume profile (high at open/close)
>>> volume = np.array([500, 300, 200, 200, 300, 500])
>>> schedule = vwap_schedule(10_000, volume)
>>> schedule[0]  # most volume at open
2500.0
>>> np.isclose(schedule.sum(), 10_000)
True

See also

twap_schedule: Equal-time scheduling (ignores volume). participation_rate_schedule: Fixed participation rate.

implementation_shortfall(execution_prices, decision_price, benchmark_price, quantities=None)[source]

Implementation shortfall decomposition.

Use implementation shortfall (IS) for the most comprehensive attribution of execution costs. IS decomposes the gap between the paper portfolio (executed at the decision price) and the actual portfolio into three components, allowing you to identify whether costs come from delay, market impact, or missed opportunities.

Decomposes execution cost into delay cost, trading impact, and opportunity cost relative to the decision and benchmark prices.

Parameters:
  • execution_prices (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Price of each fill (chronologically ordered).

  • decision_price (float) – Price at the time the decision was made (e.g., previous close or signal generation price).

  • benchmark_price (float) – Closing or end-of-day benchmark price.

  • quantities (Series | ndarray[tuple[Any, ...], dtype[floating]] | None, default: None) – Quantity per fill. If None, assumed uniform across fills.

Returns:

  • 'total_is': Total implementation shortfall (avg exec price - decision price). Positive = paid more than decision.

  • 'delay_cost': Cost from waiting between decision and first execution (first fill - decision price).

  • 'trading_impact': Cost from executing in the market (avg exec price - first fill price).

  • 'opportunity_cost': Benchmark price vs decision price; the cost of not executing instantly at the decision price.

Return type:

dict[str, float]

Example

>>> import numpy as np
>>> fills = np.array([100.10, 100.15, 100.20, 100.18])
>>> result = implementation_shortfall(fills, decision_price=100.0,
...                                  benchmark_price=100.25)
>>> result['total_is'] > 0  # paid more than decision price
True
>>> result['delay_cost']  # first fill - decision
0.1

References

  • Perold (1988), “The Implementation Shortfall”

See also

arrival_price_benchmark: Simpler benchmark-relative cost.

participation_rate_schedule(total_qty, target_rate, expected_volume)[source]

Participation-of-volume (POV) execution schedule.

Use POV when you need to limit your market footprint to a known fraction of market volume. This is common for large institutional orders where exceeding 10-20% of volume would cause excessive market impact.

In each interval, the algorithm participates at target_rate of the expected market volume, up to the remaining quantity.

Parameters:
  • total_qty (float) – Total quantity to execute.

  • target_rate (float) – Target participation rate in (0, 1]. For example, 0.10 means “trade no more than 10% of market volume per interval.”

  • expected_volume (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected market volume per interval.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval (may not fully exhaust total_qty if cumulative expected volume is too low).

Example

>>> import numpy as np
>>> volume = np.array([1000, 2000, 1500, 3000, 2500])
>>> schedule = participation_rate_schedule(500, 0.10, volume)
>>> schedule[0]  # 10% of 1000
100.0
>>> schedule.sum() <= 500
True

See also

twap_schedule: Time-based scheduling. vwap_schedule: Volume-proportional scheduling.

arrival_price_benchmark(execution_prices, volumes, arrival_price)[source]

Arrival price cost analysis.

Use arrival price benchmarking for a quick, intuitive measure of how much your execution cost relative to the market price when the order was submitted. Simpler than full implementation shortfall but widely used in practice.

Measures execution quality relative to the price at the time the order was submitted (arrival price).

Parameters:
Returns:

  • 'vwap': Volume-weighted average execution price.

  • 'arrival_cost': VWAP minus arrival price. Positive means you paid more than the arrival price (slippage).

  • 'arrival_cost_bps': Arrival cost in basis points (10,000 bps = 100%). Typical equity execution costs are 5-30 bps.

Return type:

dict[str, float]

Example

>>> import numpy as np
>>> prices = np.array([100.05, 100.10, 100.08])
>>> volumes = np.array([1000, 2000, 1500])
>>> result = arrival_price_benchmark(prices, volumes, arrival_price=100.0)
>>> result['arrival_cost_bps'] > 0  # paid more than arrival
True

See also

implementation_shortfall: Full cost attribution.

adaptive_schedule(total_quantity, market_volumes, spread_series, urgency=0.5)[source]

Adapt execution schedule based on real-time microstructure signals.

Uses bid-ask spread and volume from the microstructure module to dynamically adjust VWAP/TWAP schedules. The core idea: trade more aggressively when spreads are tight and volume is high (low execution cost), and slow down when spreads widen or volume dries up.

This bridges execution and microstructure – the schedule responds to liquidity conditions rather than following a static plan.

The allocation for each interval is proportional to:

score_i = volume_i * (1 / spread_i)^urgency

where urgency controls how much the schedule responds to spread changes. Higher urgency means the algorithm shifts more volume into tight-spread intervals.

Parameters:
  • total_quantity (float) – Total quantity to execute (positive for buys).

  • market_volumes (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected or observed market volume per interval. Shape determines the number of intervals.

  • spread_series (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Bid-ask spread (or effective spread) per interval. Must have the same length as market_volumes. Wider spreads cause the algorithm to defer volume.

  • urgency (float, default: 0.5) –

    Urgency parameter in [0, 1]. Controls the trade-off between execution speed and cost minimisation:

    • 0.0 – ignore spreads entirely (pure VWAP schedule).

    • 0.5 – balanced (default). Moderate reallocation.

    • 1.0 – maximum spread sensitivity. Concentrates volume in the tightest-spread intervals.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval. Sum equals total_quantity. Intervals with wider spreads receive less volume; intervals with higher volume and tighter spreads receive more.

Example

>>> import numpy as np
>>> volumes = np.array([1000, 2000, 1500, 3000, 2500])
>>> spreads = np.array([0.05, 0.02, 0.08, 0.03, 0.04])
>>> schedule = adaptive_schedule(10_000, volumes, spreads, urgency=0.5)
>>> np.isclose(schedule.sum(), 10_000)
True
>>> schedule[1] > schedule[2]  # more volume when spread is tight
True

Notes

In production, feed live spread and volume data from wraquant.microstructure.liquidity to dynamically re-plan the remaining execution at each interval.

See also

vwap_schedule: Static volume-proportional scheduling. twap_schedule: Equal-time scheduling. wraquant.microstructure.liquidity.effective_spread: Spread input.

is_schedule(total_quantity, market_volumes, alpha=0.5)[source]

Implementation Shortfall (IS) optimal schedule.

Use this when you want to balance execution urgency against market impact, following the simplified Almgren-Chriss intuition. The alpha parameter controls the trade-off: higher alpha front-loads execution (reduces timing risk but increases impact); lower alpha spreads execution more evenly (reduces impact but increases timing risk).

The schedule allocates volume proportionally to a blend of uniform (TWAP-like) and volume-proportional (VWAP-like) components, with alpha controlling the blend:

allocation_i = alpha * (1/N) + (1 - alpha) * (V_i / sum(V))

Parameters:
  • total_quantity (float) – Total quantity to execute (positive for buys).

  • market_volumes (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected market volume per interval. Shape determines the number of intervals.

  • alpha (float, default: 0.5) –

    Urgency parameter in [0, 1]. Controls the balance between front-loading (high alpha, TWAP-like urgency) and volume-tracking (low alpha, VWAP-like patience):

    • 0.0 – pure VWAP (follow volume exactly).

    • 0.5 – balanced (default).

    • 1.0 – pure TWAP (ignore volume, equal slices).

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval. Sum equals total_quantity.

Example

>>> import numpy as np
>>> volumes = np.array([1000, 2000, 1500, 3000, 2500])
>>> schedule = is_schedule(10_000, volumes, alpha=0.5)
>>> np.isclose(schedule.sum(), 10_000)
True
>>> schedule_urgent = is_schedule(10_000, volumes, alpha=0.9)
>>> np.std(schedule_urgent) < np.std(is_schedule(10_000, volumes, alpha=0.1))
True

References

  • Almgren & Chriss (2001), “Optimal Execution of Portfolio Transactions”

See also

twap_schedule: Pure time-weighted scheduling. vwap_schedule: Pure volume-weighted scheduling. adaptive_schedule: Spread-aware dynamic scheduling.

pov_schedule(total_quantity, market_volumes, pov_rate=0.1)[source]

Percentage of Volume (POV) schedule with constant participation.

Use this when you need to maintain a constant participation rate across all intervals. Unlike participation_rate_schedule() which caps at total_quantity, this function explicitly models constant-rate participation and returns the quantity per interval.

Each interval’s allocation is pov_rate * market_volume_i, with the constraint that the cumulative allocation does not exceed total_quantity.

Parameters:
  • total_quantity (float) – Total quantity to execute.

  • market_volumes (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected market volume per interval.

  • pov_rate (float, default: 0.1) –

    Target participation rate in (0, 1]. Common values:

    • 0.05 – 5% (very passive, minimal impact).

    • 0.10 – 10% (standard institutional).

    • 0.20 – 20% (moderately aggressive).

    • ``0.30``+ – aggressive (significant impact risk).

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval. Each entry is at most pov_rate * market_volume_i. Total may be less than total_quantity if cumulative volume is insufficient.

Example

>>> import numpy as np
>>> volumes = np.array([5000, 8000, 6000, 10000, 7000])
>>> schedule = pov_schedule(2000, volumes, pov_rate=0.10)
>>> schedule[0]  # 10% of 5000
500.0
>>> schedule.sum() <= 2000
True

See also

participation_rate_schedule: Similar but from the algorithms module. is_schedule: Urgency-based IS schedule.

close_auction_allocation(total_quantity, historical_close_volume_pct=0.2)[source]

Reserve a portion of the order for the closing auction.

Use this when you want to participate in the closing auction (MOC – Market on Close) to benefit from the closing price, which is the most common benchmark for fund NAV calculations and index rebalancing.

Splits the order into a continuous-market portion (to be executed throughout the day using TWAP/VWAP/IS) and a closing-auction portion. The close allocation is based on the historical fraction of daily volume that occurs at the close.

Parameters:
  • total_quantity (float) – Total quantity to execute.

  • historical_close_volume_pct (float, default: 0.2) –

    Historical fraction of daily volume that trades at the close (default 0.20 = 20%). This varies by market:

    • US equities: ~7-15% (higher on rebalance days).

    • European equities: ~15-25%.

    • Index constituents on rebalance: ~30-50%.

Returns:

  • continuous_quantity (float) – Quantity to execute during the continuous trading session.

  • close_quantity (float) – Quantity reserved for the closing auction.

  • close_pct (float) – Fraction allocated to close.

Return type:

dict[str, float]

Example

>>> result = close_auction_allocation(10_000, historical_close_volume_pct=0.15)
>>> result['close_quantity']
1500.0
>>> result['continuous_quantity']
8500.0

See also

vwap_schedule: Schedule the continuous portion. is_schedule: Schedule with urgency control.

almgren_chriss(total_qty, sigma, eta, gamma, lambda_risk, n_periods)[source]

Almgren-Chriss optimal execution trajectory.

Use Almgren-Chriss when you need the optimal rate at which to liquidate a large position, balancing the cost of trading quickly (market impact) against the risk of trading slowly (price uncertainty). This is the foundational model of optimal execution.

Minimises a mean-variance objective:

E[cost] + lambda * Var[cost]

where cost has a temporary component (eta * trade_size^2) and a permanent component (gamma * trade_size * remaining_position).

The solution is an exponentially decaying trajectory:

x_j = X * sinh(kappa * (T - j)) / sinh(kappa * T)

where kappa = sqrt(lambda * sigma^2 / eta).

Parameters:
  • total_qty (float) – Total shares to execute.

  • sigma (float) – Price volatility per period (e.g., daily std of returns times price).

  • eta (float) – Temporary impact coefficient (cost per share^2 traded).

  • gamma (float) – Permanent impact coefficient (permanent price shift per share traded).

  • lambda_risk (float) – Risk-aversion parameter. lambda_risk = 0 yields the risk-neutral (minimum-cost) linear trajectory. Higher values front-load execution (trade faster to reduce risk).

  • n_periods (int) – Number of execution periods.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Optimal holdings trajectory of length n_periods + 1, starting at total_qty and ending at 0. Take differences to get the quantity to trade in each period.

Example

>>> trajectory = almgren_chriss(10_000, sigma=0.02, eta=0.001,
...                            gamma=0.0001, lambda_risk=1e-4,
...                            n_periods=20)
>>> trajectory[0]
10000.0
>>> trajectory[-1]
0.0
>>> len(trajectory)
21

References

  • Almgren & Chriss (2001), “Optimal Execution of Portfolio Transactions”

See also

optimal_execution_cost: Compute cost/risk for a given trajectory. execution_frontier: Sweep lambda to trace cost-risk frontier.

bertsimas_lo(total_shares, n_periods, volatility, impact_coeff, risk_aversion=0.0)[source]

Bertsimas-Lo (1998) optimal execution with discrete trading.

Use this for a discrete-time optimal execution model that minimises expected cost plus a risk penalty. Unlike Almgren-Chriss which models continuous trading, Bertsimas-Lo explicitly works in discrete periods and provides a closed-form solution under a linear temporary impact model.

The optimal strategy in each period trades a constant fraction of the remaining position. With zero risk aversion, this simplifies to the naive strategy of trading total_shares / n_periods per period (linear liquidation).

Temporary impact model:

cost_t = impact_coeff * n_t^2

where n_t is the number of shares traded in period t.

Total expected cost:

E[cost] = impact_coeff * sum(n_t^2)

Parameters:
  • total_shares (float) – Total shares to liquidate (positive).

  • n_periods (int) – Number of discrete trading periods.

  • volatility (float) – Per-period price volatility (standard deviation).

  • impact_coeff (float) – Temporary impact coefficient. The cost of trading n shares in one period is impact_coeff * n^2.

  • risk_aversion (float, default: 0.0) – Risk-aversion parameter (default 0.0 for risk-neutral). Higher values front-load execution to reduce variance.

Returns:

  • trajectory (NDArray) – Holdings path of length n_periods + 1, starting at total_shares and ending at 0.

  • trades (NDArray) – Shares traded per period (length n_periods).

  • expected_cost (float) – Expected total execution cost.

  • cost_variance (float) – Variance of execution cost from holding risk.

Return type:

dict[str, object]

Example

>>> result = bertsimas_lo(10_000, n_periods=20, volatility=0.02,
...                      impact_coeff=0.001, risk_aversion=0.0)
>>> result['trajectory'][0]
10000.0
>>> result['trajectory'][-1]
0.0
>>> len(result['trades'])
20

References

  • Bertsimas & Lo (1998), “Optimal Control of Execution Costs” Journal of Financial Markets, 1(1), 1-50.

See also

almgren_chriss: Continuous-time optimal execution. optimal_execution_cost: Evaluate a given trajectory.

optimal_execution_cost(trajectory, sigma, eta, gamma)[source]

Compute expected cost and variance of a given execution trajectory.

Use this to evaluate execution strategies by computing their expected cost and execution risk. Compare different trajectories (e.g., aggressive vs. passive) to find the right trade-off.

Parameters:
  • trajectory (ndarray[tuple[Any, ...], dtype[floating]]) – Holdings path of length n_periods + 1, starting at the initial position and ending at 0.

  • sigma (float) – Price volatility per period.

  • eta (float) – Temporary impact coefficient.

  • gamma (float) – Permanent impact coefficient.

Returns:

  • 'expected_cost': Expected total execution cost (temporary + permanent impact). Lower is better.

  • 'variance': Variance of execution cost due to price uncertainty while holding the position.

  • 'std_dev': Standard deviation of execution cost. This is the “execution risk.”

Return type:

dict[str, float]

Example

>>> import numpy as np
>>> traj = np.linspace(10_000, 0, 21)  # linear liquidation
>>> metrics = optimal_execution_cost(traj, sigma=0.02, eta=0.001, gamma=0.0001)
>>> metrics['expected_cost'] > 0
True
>>> metrics['std_dev'] > 0
True

See also

almgren_chriss: Compute the optimal trajectory. execution_frontier: Trace the cost-risk frontier.

execution_frontier(total_qty, sigma, eta, gamma, n_points=20, n_periods=20)[source]

Efficient frontier of (expected cost, risk) pairs.

Sweeps over a range of risk-aversion parameters to trace out the trade-off between expected execution cost and execution risk.

Parameters:
  • total_qty (float) – Total shares to execute.

  • sigma (float) – Price volatility per period.

  • eta (float) – Temporary impact coefficient.

  • gamma (float) – Permanent impact coefficient.

  • n_points (int, default: 20) – Number of points on the frontier.

  • n_periods (int, default: 20) – Number of execution periods for each trajectory.

Return type:

dict[str, ndarray[tuple[Any, ...], dtype[floating]]]

Returns:

Dictionary with 'lambda_values', 'expected_cost', and 'std_dev' arrays, each of length n_points.

slippage(execution_price, benchmark_price, side='buy')[source]

Per-trade slippage relative to a benchmark.

Use slippage to measure the implicit cost of executing a trade at a worse price than the benchmark. Slippage is always defined so that positive values represent a cost to the trader.

For buys, slippage is positive when the execution price exceeds the benchmark. For sells, it is positive when the execution price is below the benchmark.

Parameters:
Return type:

float | ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Signed slippage value(s). Positive = cost, negative = improvement.

Example

>>> slippage(100.05, 100.00, side='buy')
0.05
>>> slippage(99.95, 100.00, side='sell')
0.05

See also

total_cost: Aggregate cost breakdown across multiple trades.

commission_cost(qty, price, rate=0.001)[source]

Commission cost calculation.

Computes the explicit broker commission as a fraction of notional value (|qty| * price * rate).

Parameters:
Return type:

float | ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Commission cost(s). Always non-negative.

Example

>>> commission_cost(1000, 50.0, rate=0.001)
50.0
>>> commission_cost(1000, 50.0, rate=0.0005)  # 5 bps
25.0

See also

slippage: Implicit execution cost. total_cost: Combined slippage + commission.

total_cost(trades_df, commission_rate=0.001)[source]

Total execution cost breakdown from a trades DataFrame.

Use total_cost to get a complete picture of execution quality across a batch of trades, decomposed into slippage (implicit market cost) and commissions (explicit broker cost).

The DataFrame must contain columns 'execution_price', 'benchmark_price', 'qty', and 'side'.

Parameters:
  • trades_df (DataFrame) – DataFrame with one row per fill. Required columns: 'execution_price', 'benchmark_price', 'qty', 'side' ('buy' or 'sell').

  • commission_rate (float, default: 0.001) – Commission rate as a fraction of notional value (default 0.001 = 10 bps).

Returns:

  • 'total_slippage': Sum of slippage * qty across all trades.

  • 'total_commission': Sum of explicit commissions.

  • 'total_cost': Slippage + commission.

  • 'cost_bps': Total cost in basis points of total notional.

  • 'n_trades': Number of fills.

Return type:

dict[str, float]

Example

>>> import pandas as pd
>>> trades = pd.DataFrame({
...     'execution_price': [100.05, 50.10],
...     'benchmark_price': [100.00, 50.00],
...     'qty': [1000, 2000],
...     'side': ['buy', 'buy'],
... })
>>> result = total_cost(trades, commission_rate=0.001)
>>> result['n_trades']
2
>>> result['cost_bps'] > 0
True

See also

slippage: Per-trade slippage calculation. commission_cost: Per-trade commission calculation.

market_impact_model(qty, avg_daily_volume, volatility, model='sqrt')[source]

Estimate market impact using a parametric model.

Use market impact models to pre-estimate how much your order will move the price, before you execute. This is essential for pre-trade cost analysis and optimal execution sizing.

Parameters:
  • qty (float) – Order quantity (shares).

  • avg_daily_volume (float) – Average daily volume.

  • volatility (float) – Daily price volatility (standard deviation of returns).

  • model (str, default: 'sqrt') – Impact model to use. 'sqrt' for the square-root model (impact = sigma * sqrt(Q/ADV)), which is the industry standard. 'linear' for a simple linear model (impact = sigma * Q/ADV).

Return type:

float

Returns:

Estimated market impact as a fraction of price (same scale as volatility). Multiply by the stock price to get dollar impact.

Example

>>> # 10,000 shares, ADV 1M, 2% daily vol
>>> impact = market_impact_model(10_000, 1_000_000, 0.02, model='sqrt')
>>> 0 < impact < 0.02  # less than full daily vol
True

Notes

The square-root model is empirically well-supported for equities: impact scales as the square root of participation rate, consistent with Kyle (1985) and Almgren et al. (2005).

See also

slippage: Measure actual post-trade slippage. wraquant.execution.optimal.almgren_chriss: Optimal execution trajectory.

liquidity_adjusted_cost(price, quantity, bid, ask, volume, avg_daily_volume=None)[source]

Estimate execution cost using microstructure liquidity measures.

Combines spread cost from microstructure.liquidity with market impact from execution.cost for a complete cost estimate. This bridges the microstructure and execution modules, giving a single function that incorporates both implicit (spread) and impact costs.

The total cost has three components:

  1. Spread cost: half the effective spread times quantity. This is the immediate cost of crossing the bid-ask spread.

  2. Market impact: estimated price impact from the square-root model, scaled by quantity and price.

  3. Total cost: sum of spread cost and market impact cost.

When to use:

Use this for pre-trade cost estimation when you have both quote data (bid/ask) and volume data. It gives a more realistic cost estimate than spread or impact alone because real execution costs include both components.

Parameters:
  • price (float) – Current mid-price or last trade price.

  • quantity (float) – Order quantity (shares).

  • bid (float | Series) – Best bid price(s).

  • ask (float | Series) – Best ask price(s).

  • volume (float | Series) – Recent trading volume.

  • avg_daily_volume (float | None, default: None) – Average daily volume for impact estimation. If None, uses the mean of volume (when volume is a Series) or volume itself (when scalar).

Returns:

  • 'spread_cost' (float) – Cost from crossing the bid-ask spread (half-spread times quantity).

  • 'market_impact_cost' (float) – Estimated market impact cost in dollar terms.

  • 'total_cost' (float) – Sum of spread and impact costs.

  • 'cost_bps' (float) – Total cost in basis points of notional value.

  • 'effective_spread' (float) – Mean effective spread used in the calculation.

  • 'amihud_illiquidity' (float) – Amihud illiquidity ratio (higher = less liquid).

Return type:

dict[str, float]

Example

>>> result = liquidity_adjusted_cost(
...     price=100.0, quantity=5000,
...     bid=99.98, ask=100.02, volume=1_000_000,
... )
>>> result['spread_cost'] > 0
True
>>> result['total_cost'] >= result['spread_cost']
True

See also

wraquant.microstructure.liquidity.effective_spread: Raw spread. wraquant.microstructure.liquidity.amihud_illiquidity: Illiquidity. market_impact_model: Parametric impact estimation.

expected_cost_model(quantity, price, adv, volatility, spread)[source]

Comprehensive pre-trade expected cost model.

Use this before executing an order to estimate total expected cost, broken down into three components: spread crossing cost, market impact, and timing risk. This helps decide whether to execute aggressively (high urgency) or passively (low urgency).

Components:
  1. Spread cost: immediate cost of crossing the bid-ask spread = 0.5 * spread * quantity.

  2. Market impact: price impact from trading = sigma * sqrt(Q / ADV) * price * quantity (square-root model).

  3. Timing risk: opportunity cost of slow execution = sigma * sqrt(T) * price * quantity, where T is estimated execution duration proportional to Q / ADV.

Parameters:
  • quantity (float) – Order quantity (shares or units).

  • price (float) – Current market price.

  • adv (float) – Average daily volume (shares).

  • volatility (float) – Daily price volatility (standard deviation of returns, e.g., 0.02 = 2%).

  • spread (float) – Bid-ask spread in price terms (e.g., 0.02 for a $0.02 spread).

Returns:

  • spread_cost (float) – Half-spread cost in dollar terms.

  • impact_cost (float) – Estimated market impact cost.

  • timing_risk (float) – Estimated timing/opportunity risk.

  • total_cost (float) – Sum of all components.

  • cost_bps (float) – Total cost in basis points of notional value.

Return type:

dict[str, float]

Example

>>> result = expected_cost_model(
...     quantity=5000, price=100.0, adv=1_000_000,
...     volatility=0.02, spread=0.02,
... )
>>> result['spread_cost']
50.0
>>> result['total_cost'] > result['spread_cost']
True

See also

market_impact_model: Parametric impact estimation. liquidity_adjusted_cost: Spread + impact using microstructure data.

transaction_cost_analysis(trades_df, market_data_df)[source]

Post-trade Transaction Cost Analysis (TCA).

Use this after execution to evaluate the quality of each fill relative to multiple benchmarks. TCA is the standard way institutional investors assess broker/algorithm performance.

Compares each trade’s execution price against:

  • Arrival price: mid-price when the order was submitted.

  • VWAP: volume-weighted average price during execution.

  • Close: closing price of the day.

Parameters:
  • trades_df (DataFrame) –

    DataFrame with one row per fill. Required columns:

    • 'execution_price' – actual fill price.

    • 'qty' – fill quantity (positive for buys).

    • 'side''buy' or 'sell'.

    • 'timestamp' – execution timestamp (optional, used for ordering).

  • market_data_df (DataFrame) –

    DataFrame with market reference data. Required columns:

    • 'arrival_price' – mid-price at order arrival.

    • 'vwap' – VWAP during execution window.

    • 'close' – closing price.

    If market_data_df has a single row, the same benchmarks are applied to all trades. If it has one row per trade, benchmarks are matched by index.

Returns:

  • 'arrival_cost' – execution price vs arrival price (positive = slippage cost for buys).

  • 'arrival_cost_bps' – arrival cost in basis points.

  • 'vwap_cost' – execution price vs VWAP.

  • 'vwap_cost_bps' – VWAP cost in basis points.

  • 'close_cost' – execution price vs close.

  • 'close_cost_bps' – close cost in basis points.

Return type:

DataFrame

Example

>>> import pandas as pd
>>> trades = pd.DataFrame({
...     'execution_price': [100.05, 100.10, 100.08],
...     'qty': [1000, 2000, 1500],
...     'side': ['buy', 'buy', 'buy'],
... })
>>> market = pd.DataFrame({
...     'arrival_price': [100.00],
...     'vwap': [100.06],
...     'close': [100.12],
... })
>>> tca = transaction_cost_analysis(trades, market)
>>> 'arrival_cost_bps' in tca.columns
True

See also

total_cost: Aggregate cost breakdown. arrival_price_benchmark: Simpler arrival-price analysis.

Algorithms

Execution algorithm schedules and transaction cost analysis.

Execution algorithms control how a large order is sliced into smaller child orders and distributed across time to minimise market impact and execution cost. This module provides scheduling logic for the three most common algorithms and tools for measuring execution quality.

Algorithms:
  • twap_schedule: Time-Weighted Average Price. Splits the order evenly across intervals. The simplest algorithm; minimises timing risk but ignores volume patterns. Use for low-urgency orders in liquid markets.

  • vwap_schedule: Volume-Weighted Average Price. Allocates proportionally to expected volume. Tracks the market’s VWAP, which is the most common benchmark for institutional execution. Use when you want to trade “at the market’s pace.”

  • participation_rate_schedule: Participation-of-Volume (POV). Targets a fixed fraction of market volume in each interval. Use when you want to limit market impact to a known participation rate (e.g., “never trade more than 10% of volume”).

Execution quality analytics:
  • implementation_shortfall: decomposes total execution cost into delay cost (waiting), trading impact (price movement during execution), and opportunity cost (unexecuted portion). The most comprehensive cost metric.

  • arrival_price_benchmark: measures execution cost relative to the price when the order arrived. Simpler than IS but widely used for benchmarking.

How to choose:
  • Low urgency, liquid market: TWAP or VWAP.

  • Volume-sensitive market: VWAP (tracks volume profile).

  • Need to limit participation: POV.

  • High urgency: aggressive POV with high target rate.

  • Measuring execution quality: implementation shortfall for attribution, arrival price for quick benchmarking.

References

  • Almgren & Chriss (2001), “Optimal Execution of Portfolio Transactions”

  • Perold (1988), “The Implementation Shortfall”

twap_schedule(total_qty, n_intervals)[source]

Time-weighted average price (TWAP) schedule.

Use TWAP for low-urgency orders in liquid markets where you want to minimise timing risk by spreading execution evenly over time. TWAP is the simplest algorithm and is often used as a benchmark for more sophisticated strategies.

Splits total_qty evenly across n_intervals.

Parameters:
  • total_qty (float) – Total quantity to execute (positive for buys).

  • n_intervals (int) – Number of time intervals (e.g., 78 for 5-min bars in a 6.5-hour trading day).

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Array of length n_intervals with equal quantities. Each element is total_qty / n_intervals.

Example

>>> schedule = twap_schedule(10_000, n_intervals=10)
>>> schedule[0]
1000.0
>>> len(schedule)
10

See also

vwap_schedule: Volume-proportional scheduling. participation_rate_schedule: Participation-of-volume scheduling.

vwap_schedule(total_qty, historical_volume_profile)[source]

Volume-weighted average price (VWAP) schedule.

Use VWAP when you want to trade “at the market’s pace” and your benchmark is the market VWAP. Allocating proportionally to historical volume minimises deviation from VWAP and is the most common institutional execution benchmark.

Allocates total_qty proportionally to the historical volume profile so that execution tracks the expected VWAP.

Parameters:
  • total_qty (float) – Total quantity to execute.

  • historical_volume_profile (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected volume in each interval (e.g., average intraday volume by 5-min bucket). The shape determines the number of intervals.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval, proportional to expected volume. Sum equals total_qty.

Example

>>> import numpy as np
>>> # U-shaped intraday volume profile (high at open/close)
>>> volume = np.array([500, 300, 200, 200, 300, 500])
>>> schedule = vwap_schedule(10_000, volume)
>>> schedule[0]  # most volume at open
2500.0
>>> np.isclose(schedule.sum(), 10_000)
True

See also

twap_schedule: Equal-time scheduling (ignores volume). participation_rate_schedule: Fixed participation rate.

implementation_shortfall(execution_prices, decision_price, benchmark_price, quantities=None)[source]

Implementation shortfall decomposition.

Use implementation shortfall (IS) for the most comprehensive attribution of execution costs. IS decomposes the gap between the paper portfolio (executed at the decision price) and the actual portfolio into three components, allowing you to identify whether costs come from delay, market impact, or missed opportunities.

Decomposes execution cost into delay cost, trading impact, and opportunity cost relative to the decision and benchmark prices.

Parameters:
  • execution_prices (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Price of each fill (chronologically ordered).

  • decision_price (float) – Price at the time the decision was made (e.g., previous close or signal generation price).

  • benchmark_price (float) – Closing or end-of-day benchmark price.

  • quantities (Series | ndarray[tuple[Any, ...], dtype[floating]] | None, default: None) – Quantity per fill. If None, assumed uniform across fills.

Returns:

  • 'total_is': Total implementation shortfall (avg exec price - decision price). Positive = paid more than decision.

  • 'delay_cost': Cost from waiting between decision and first execution (first fill - decision price).

  • 'trading_impact': Cost from executing in the market (avg exec price - first fill price).

  • 'opportunity_cost': Benchmark price vs decision price; the cost of not executing instantly at the decision price.

Return type:

dict[str, float]

Example

>>> import numpy as np
>>> fills = np.array([100.10, 100.15, 100.20, 100.18])
>>> result = implementation_shortfall(fills, decision_price=100.0,
...                                  benchmark_price=100.25)
>>> result['total_is'] > 0  # paid more than decision price
True
>>> result['delay_cost']  # first fill - decision
0.1

References

  • Perold (1988), “The Implementation Shortfall”

See also

arrival_price_benchmark: Simpler benchmark-relative cost.

participation_rate_schedule(total_qty, target_rate, expected_volume)[source]

Participation-of-volume (POV) execution schedule.

Use POV when you need to limit your market footprint to a known fraction of market volume. This is common for large institutional orders where exceeding 10-20% of volume would cause excessive market impact.

In each interval, the algorithm participates at target_rate of the expected market volume, up to the remaining quantity.

Parameters:
  • total_qty (float) – Total quantity to execute.

  • target_rate (float) – Target participation rate in (0, 1]. For example, 0.10 means “trade no more than 10% of market volume per interval.”

  • expected_volume (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected market volume per interval.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval (may not fully exhaust total_qty if cumulative expected volume is too low).

Example

>>> import numpy as np
>>> volume = np.array([1000, 2000, 1500, 3000, 2500])
>>> schedule = participation_rate_schedule(500, 0.10, volume)
>>> schedule[0]  # 10% of 1000
100.0
>>> schedule.sum() <= 500
True

See also

twap_schedule: Time-based scheduling. vwap_schedule: Volume-proportional scheduling.

arrival_price_benchmark(execution_prices, volumes, arrival_price)[source]

Arrival price cost analysis.

Use arrival price benchmarking for a quick, intuitive measure of how much your execution cost relative to the market price when the order was submitted. Simpler than full implementation shortfall but widely used in practice.

Measures execution quality relative to the price at the time the order was submitted (arrival price).

Parameters:
Returns:

  • 'vwap': Volume-weighted average execution price.

  • 'arrival_cost': VWAP minus arrival price. Positive means you paid more than the arrival price (slippage).

  • 'arrival_cost_bps': Arrival cost in basis points (10,000 bps = 100%). Typical equity execution costs are 5-30 bps.

Return type:

dict[str, float]

Example

>>> import numpy as np
>>> prices = np.array([100.05, 100.10, 100.08])
>>> volumes = np.array([1000, 2000, 1500])
>>> result = arrival_price_benchmark(prices, volumes, arrival_price=100.0)
>>> result['arrival_cost_bps'] > 0  # paid more than arrival
True

See also

implementation_shortfall: Full cost attribution.

adaptive_schedule(total_quantity, market_volumes, spread_series, urgency=0.5)[source]

Adapt execution schedule based on real-time microstructure signals.

Uses bid-ask spread and volume from the microstructure module to dynamically adjust VWAP/TWAP schedules. The core idea: trade more aggressively when spreads are tight and volume is high (low execution cost), and slow down when spreads widen or volume dries up.

This bridges execution and microstructure – the schedule responds to liquidity conditions rather than following a static plan.

The allocation for each interval is proportional to:

score_i = volume_i * (1 / spread_i)^urgency

where urgency controls how much the schedule responds to spread changes. Higher urgency means the algorithm shifts more volume into tight-spread intervals.

Parameters:
  • total_quantity (float) – Total quantity to execute (positive for buys).

  • market_volumes (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected or observed market volume per interval. Shape determines the number of intervals.

  • spread_series (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Bid-ask spread (or effective spread) per interval. Must have the same length as market_volumes. Wider spreads cause the algorithm to defer volume.

  • urgency (float, default: 0.5) –

    Urgency parameter in [0, 1]. Controls the trade-off between execution speed and cost minimisation:

    • 0.0 – ignore spreads entirely (pure VWAP schedule).

    • 0.5 – balanced (default). Moderate reallocation.

    • 1.0 – maximum spread sensitivity. Concentrates volume in the tightest-spread intervals.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval. Sum equals total_quantity. Intervals with wider spreads receive less volume; intervals with higher volume and tighter spreads receive more.

Example

>>> import numpy as np
>>> volumes = np.array([1000, 2000, 1500, 3000, 2500])
>>> spreads = np.array([0.05, 0.02, 0.08, 0.03, 0.04])
>>> schedule = adaptive_schedule(10_000, volumes, spreads, urgency=0.5)
>>> np.isclose(schedule.sum(), 10_000)
True
>>> schedule[1] > schedule[2]  # more volume when spread is tight
True

Notes

In production, feed live spread and volume data from wraquant.microstructure.liquidity to dynamically re-plan the remaining execution at each interval.

See also

vwap_schedule: Static volume-proportional scheduling. twap_schedule: Equal-time scheduling. wraquant.microstructure.liquidity.effective_spread: Spread input.

is_schedule(total_quantity, market_volumes, alpha=0.5)[source]

Implementation Shortfall (IS) optimal schedule.

Use this when you want to balance execution urgency against market impact, following the simplified Almgren-Chriss intuition. The alpha parameter controls the trade-off: higher alpha front-loads execution (reduces timing risk but increases impact); lower alpha spreads execution more evenly (reduces impact but increases timing risk).

The schedule allocates volume proportionally to a blend of uniform (TWAP-like) and volume-proportional (VWAP-like) components, with alpha controlling the blend:

allocation_i = alpha * (1/N) + (1 - alpha) * (V_i / sum(V))

Parameters:
  • total_quantity (float) – Total quantity to execute (positive for buys).

  • market_volumes (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected market volume per interval. Shape determines the number of intervals.

  • alpha (float, default: 0.5) –

    Urgency parameter in [0, 1]. Controls the balance between front-loading (high alpha, TWAP-like urgency) and volume-tracking (low alpha, VWAP-like patience):

    • 0.0 – pure VWAP (follow volume exactly).

    • 0.5 – balanced (default).

    • 1.0 – pure TWAP (ignore volume, equal slices).

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval. Sum equals total_quantity.

Example

>>> import numpy as np
>>> volumes = np.array([1000, 2000, 1500, 3000, 2500])
>>> schedule = is_schedule(10_000, volumes, alpha=0.5)
>>> np.isclose(schedule.sum(), 10_000)
True
>>> schedule_urgent = is_schedule(10_000, volumes, alpha=0.9)
>>> np.std(schedule_urgent) < np.std(is_schedule(10_000, volumes, alpha=0.1))
True

References

  • Almgren & Chriss (2001), “Optimal Execution of Portfolio Transactions”

See also

twap_schedule: Pure time-weighted scheduling. vwap_schedule: Pure volume-weighted scheduling. adaptive_schedule: Spread-aware dynamic scheduling.

pov_schedule(total_quantity, market_volumes, pov_rate=0.1)[source]

Percentage of Volume (POV) schedule with constant participation.

Use this when you need to maintain a constant participation rate across all intervals. Unlike participation_rate_schedule() which caps at total_quantity, this function explicitly models constant-rate participation and returns the quantity per interval.

Each interval’s allocation is pov_rate * market_volume_i, with the constraint that the cumulative allocation does not exceed total_quantity.

Parameters:
  • total_quantity (float) – Total quantity to execute.

  • market_volumes (Series | ndarray[tuple[Any, ...], dtype[floating]]) – Expected market volume per interval.

  • pov_rate (float, default: 0.1) –

    Target participation rate in (0, 1]. Common values:

    • 0.05 – 5% (very passive, minimal impact).

    • 0.10 – 10% (standard institutional).

    • 0.20 – 20% (moderately aggressive).

    • ``0.30``+ – aggressive (significant impact risk).

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Quantity to execute in each interval. Each entry is at most pov_rate * market_volume_i. Total may be less than total_quantity if cumulative volume is insufficient.

Example

>>> import numpy as np
>>> volumes = np.array([5000, 8000, 6000, 10000, 7000])
>>> schedule = pov_schedule(2000, volumes, pov_rate=0.10)
>>> schedule[0]  # 10% of 5000
500.0
>>> schedule.sum() <= 2000
True

See also

participation_rate_schedule: Similar but from the algorithms module. is_schedule: Urgency-based IS schedule.

close_auction_allocation(total_quantity, historical_close_volume_pct=0.2)[source]

Reserve a portion of the order for the closing auction.

Use this when you want to participate in the closing auction (MOC – Market on Close) to benefit from the closing price, which is the most common benchmark for fund NAV calculations and index rebalancing.

Splits the order into a continuous-market portion (to be executed throughout the day using TWAP/VWAP/IS) and a closing-auction portion. The close allocation is based on the historical fraction of daily volume that occurs at the close.

Parameters:
  • total_quantity (float) – Total quantity to execute.

  • historical_close_volume_pct (float, default: 0.2) –

    Historical fraction of daily volume that trades at the close (default 0.20 = 20%). This varies by market:

    • US equities: ~7-15% (higher on rebalance days).

    • European equities: ~15-25%.

    • Index constituents on rebalance: ~30-50%.

Returns:

  • continuous_quantity (float) – Quantity to execute during the continuous trading session.

  • close_quantity (float) – Quantity reserved for the closing auction.

  • close_pct (float) – Fraction allocated to close.

Return type:

dict[str, float]

Example

>>> result = close_auction_allocation(10_000, historical_close_volume_pct=0.15)
>>> result['close_quantity']
1500.0
>>> result['continuous_quantity']
8500.0

See also

vwap_schedule: Schedule the continuous portion. is_schedule: Schedule with urgency control.

Transaction Costs

Transaction cost analysis.

Provides slippage calculation, commission accounting, total execution cost breakdown, and market impact models.

slippage(execution_price, benchmark_price, side='buy')[source]

Per-trade slippage relative to a benchmark.

Use slippage to measure the implicit cost of executing a trade at a worse price than the benchmark. Slippage is always defined so that positive values represent a cost to the trader.

For buys, slippage is positive when the execution price exceeds the benchmark. For sells, it is positive when the execution price is below the benchmark.

Parameters:
Return type:

float | ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Signed slippage value(s). Positive = cost, negative = improvement.

Example

>>> slippage(100.05, 100.00, side='buy')
0.05
>>> slippage(99.95, 100.00, side='sell')
0.05

See also

total_cost: Aggregate cost breakdown across multiple trades.

commission_cost(qty, price, rate=0.001)[source]

Commission cost calculation.

Computes the explicit broker commission as a fraction of notional value (|qty| * price * rate).

Parameters:
Return type:

float | ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Commission cost(s). Always non-negative.

Example

>>> commission_cost(1000, 50.0, rate=0.001)
50.0
>>> commission_cost(1000, 50.0, rate=0.0005)  # 5 bps
25.0

See also

slippage: Implicit execution cost. total_cost: Combined slippage + commission.

total_cost(trades_df, commission_rate=0.001)[source]

Total execution cost breakdown from a trades DataFrame.

Use total_cost to get a complete picture of execution quality across a batch of trades, decomposed into slippage (implicit market cost) and commissions (explicit broker cost).

The DataFrame must contain columns 'execution_price', 'benchmark_price', 'qty', and 'side'.

Parameters:
  • trades_df (DataFrame) – DataFrame with one row per fill. Required columns: 'execution_price', 'benchmark_price', 'qty', 'side' ('buy' or 'sell').

  • commission_rate (float, default: 0.001) – Commission rate as a fraction of notional value (default 0.001 = 10 bps).

Returns:

  • 'total_slippage': Sum of slippage * qty across all trades.

  • 'total_commission': Sum of explicit commissions.

  • 'total_cost': Slippage + commission.

  • 'cost_bps': Total cost in basis points of total notional.

  • 'n_trades': Number of fills.

Return type:

dict[str, float]

Example

>>> import pandas as pd
>>> trades = pd.DataFrame({
...     'execution_price': [100.05, 50.10],
...     'benchmark_price': [100.00, 50.00],
...     'qty': [1000, 2000],
...     'side': ['buy', 'buy'],
... })
>>> result = total_cost(trades, commission_rate=0.001)
>>> result['n_trades']
2
>>> result['cost_bps'] > 0
True

See also

slippage: Per-trade slippage calculation. commission_cost: Per-trade commission calculation.

market_impact_model(qty, avg_daily_volume, volatility, model='sqrt')[source]

Estimate market impact using a parametric model.

Use market impact models to pre-estimate how much your order will move the price, before you execute. This is essential for pre-trade cost analysis and optimal execution sizing.

Parameters:
  • qty (float) – Order quantity (shares).

  • avg_daily_volume (float) – Average daily volume.

  • volatility (float) – Daily price volatility (standard deviation of returns).

  • model (str, default: 'sqrt') – Impact model to use. 'sqrt' for the square-root model (impact = sigma * sqrt(Q/ADV)), which is the industry standard. 'linear' for a simple linear model (impact = sigma * Q/ADV).

Return type:

float

Returns:

Estimated market impact as a fraction of price (same scale as volatility). Multiply by the stock price to get dollar impact.

Example

>>> # 10,000 shares, ADV 1M, 2% daily vol
>>> impact = market_impact_model(10_000, 1_000_000, 0.02, model='sqrt')
>>> 0 < impact < 0.02  # less than full daily vol
True

Notes

The square-root model is empirically well-supported for equities: impact scales as the square root of participation rate, consistent with Kyle (1985) and Almgren et al. (2005).

See also

slippage: Measure actual post-trade slippage. wraquant.execution.optimal.almgren_chriss: Optimal execution trajectory.

liquidity_adjusted_cost(price, quantity, bid, ask, volume, avg_daily_volume=None)[source]

Estimate execution cost using microstructure liquidity measures.

Combines spread cost from microstructure.liquidity with market impact from execution.cost for a complete cost estimate. This bridges the microstructure and execution modules, giving a single function that incorporates both implicit (spread) and impact costs.

The total cost has three components:

  1. Spread cost: half the effective spread times quantity. This is the immediate cost of crossing the bid-ask spread.

  2. Market impact: estimated price impact from the square-root model, scaled by quantity and price.

  3. Total cost: sum of spread cost and market impact cost.

When to use:

Use this for pre-trade cost estimation when you have both quote data (bid/ask) and volume data. It gives a more realistic cost estimate than spread or impact alone because real execution costs include both components.

Parameters:
  • price (float) – Current mid-price or last trade price.

  • quantity (float) – Order quantity (shares).

  • bid (float | Series) – Best bid price(s).

  • ask (float | Series) – Best ask price(s).

  • volume (float | Series) – Recent trading volume.

  • avg_daily_volume (float | None, default: None) – Average daily volume for impact estimation. If None, uses the mean of volume (when volume is a Series) or volume itself (when scalar).

Returns:

  • 'spread_cost' (float) – Cost from crossing the bid-ask spread (half-spread times quantity).

  • 'market_impact_cost' (float) – Estimated market impact cost in dollar terms.

  • 'total_cost' (float) – Sum of spread and impact costs.

  • 'cost_bps' (float) – Total cost in basis points of notional value.

  • 'effective_spread' (float) – Mean effective spread used in the calculation.

  • 'amihud_illiquidity' (float) – Amihud illiquidity ratio (higher = less liquid).

Return type:

dict[str, float]

Example

>>> result = liquidity_adjusted_cost(
...     price=100.0, quantity=5000,
...     bid=99.98, ask=100.02, volume=1_000_000,
... )
>>> result['spread_cost'] > 0
True
>>> result['total_cost'] >= result['spread_cost']
True

See also

wraquant.microstructure.liquidity.effective_spread: Raw spread. wraquant.microstructure.liquidity.amihud_illiquidity: Illiquidity. market_impact_model: Parametric impact estimation.

expected_cost_model(quantity, price, adv, volatility, spread)[source]

Comprehensive pre-trade expected cost model.

Use this before executing an order to estimate total expected cost, broken down into three components: spread crossing cost, market impact, and timing risk. This helps decide whether to execute aggressively (high urgency) or passively (low urgency).

Components:
  1. Spread cost: immediate cost of crossing the bid-ask spread = 0.5 * spread * quantity.

  2. Market impact: price impact from trading = sigma * sqrt(Q / ADV) * price * quantity (square-root model).

  3. Timing risk: opportunity cost of slow execution = sigma * sqrt(T) * price * quantity, where T is estimated execution duration proportional to Q / ADV.

Parameters:
  • quantity (float) – Order quantity (shares or units).

  • price (float) – Current market price.

  • adv (float) – Average daily volume (shares).

  • volatility (float) – Daily price volatility (standard deviation of returns, e.g., 0.02 = 2%).

  • spread (float) – Bid-ask spread in price terms (e.g., 0.02 for a $0.02 spread).

Returns:

  • spread_cost (float) – Half-spread cost in dollar terms.

  • impact_cost (float) – Estimated market impact cost.

  • timing_risk (float) – Estimated timing/opportunity risk.

  • total_cost (float) – Sum of all components.

  • cost_bps (float) – Total cost in basis points of notional value.

Return type:

dict[str, float]

Example

>>> result = expected_cost_model(
...     quantity=5000, price=100.0, adv=1_000_000,
...     volatility=0.02, spread=0.02,
... )
>>> result['spread_cost']
50.0
>>> result['total_cost'] > result['spread_cost']
True

See also

market_impact_model: Parametric impact estimation. liquidity_adjusted_cost: Spread + impact using microstructure data.

transaction_cost_analysis(trades_df, market_data_df)[source]

Post-trade Transaction Cost Analysis (TCA).

Use this after execution to evaluate the quality of each fill relative to multiple benchmarks. TCA is the standard way institutional investors assess broker/algorithm performance.

Compares each trade’s execution price against:

  • Arrival price: mid-price when the order was submitted.

  • VWAP: volume-weighted average price during execution.

  • Close: closing price of the day.

Parameters:
  • trades_df (DataFrame) –

    DataFrame with one row per fill. Required columns:

    • 'execution_price' – actual fill price.

    • 'qty' – fill quantity (positive for buys).

    • 'side''buy' or 'sell'.

    • 'timestamp' – execution timestamp (optional, used for ordering).

  • market_data_df (DataFrame) –

    DataFrame with market reference data. Required columns:

    • 'arrival_price' – mid-price at order arrival.

    • 'vwap' – VWAP during execution window.

    • 'close' – closing price.

    If market_data_df has a single row, the same benchmarks are applied to all trades. If it has one row per trade, benchmarks are matched by index.

Returns:

  • 'arrival_cost' – execution price vs arrival price (positive = slippage cost for buys).

  • 'arrival_cost_bps' – arrival cost in basis points.

  • 'vwap_cost' – execution price vs VWAP.

  • 'vwap_cost_bps' – VWAP cost in basis points.

  • 'close_cost' – execution price vs close.

  • 'close_cost_bps' – close cost in basis points.

Return type:

DataFrame

Example

>>> import pandas as pd
>>> trades = pd.DataFrame({
...     'execution_price': [100.05, 100.10, 100.08],
...     'qty': [1000, 2000, 1500],
...     'side': ['buy', 'buy', 'buy'],
... })
>>> market = pd.DataFrame({
...     'arrival_price': [100.00],
...     'vwap': [100.06],
...     'close': [100.12],
... })
>>> tca = transaction_cost_analysis(trades, market)
>>> 'arrival_cost_bps' in tca.columns
True

See also

total_cost: Aggregate cost breakdown. arrival_price_benchmark: Simpler arrival-price analysis.

Optimal Execution

Optimal execution models.

Implements the Almgren-Chriss (2001) framework for optimal trade execution, balancing expected market-impact cost against execution risk.

almgren_chriss(total_qty, sigma, eta, gamma, lambda_risk, n_periods)[source]

Almgren-Chriss optimal execution trajectory.

Use Almgren-Chriss when you need the optimal rate at which to liquidate a large position, balancing the cost of trading quickly (market impact) against the risk of trading slowly (price uncertainty). This is the foundational model of optimal execution.

Minimises a mean-variance objective:

E[cost] + lambda * Var[cost]

where cost has a temporary component (eta * trade_size^2) and a permanent component (gamma * trade_size * remaining_position).

The solution is an exponentially decaying trajectory:

x_j = X * sinh(kappa * (T - j)) / sinh(kappa * T)

where kappa = sqrt(lambda * sigma^2 / eta).

Parameters:
  • total_qty (float) – Total shares to execute.

  • sigma (float) – Price volatility per period (e.g., daily std of returns times price).

  • eta (float) – Temporary impact coefficient (cost per share^2 traded).

  • gamma (float) – Permanent impact coefficient (permanent price shift per share traded).

  • lambda_risk (float) – Risk-aversion parameter. lambda_risk = 0 yields the risk-neutral (minimum-cost) linear trajectory. Higher values front-load execution (trade faster to reduce risk).

  • n_periods (int) – Number of execution periods.

Return type:

ndarray[tuple[Any, ...], dtype[floating]]

Returns:

Optimal holdings trajectory of length n_periods + 1, starting at total_qty and ending at 0. Take differences to get the quantity to trade in each period.

Example

>>> trajectory = almgren_chriss(10_000, sigma=0.02, eta=0.001,
...                            gamma=0.0001, lambda_risk=1e-4,
...                            n_periods=20)
>>> trajectory[0]
10000.0
>>> trajectory[-1]
0.0
>>> len(trajectory)
21

References

  • Almgren & Chriss (2001), “Optimal Execution of Portfolio Transactions”

See also

optimal_execution_cost: Compute cost/risk for a given trajectory. execution_frontier: Sweep lambda to trace cost-risk frontier.

optimal_execution_cost(trajectory, sigma, eta, gamma)[source]

Compute expected cost and variance of a given execution trajectory.

Use this to evaluate execution strategies by computing their expected cost and execution risk. Compare different trajectories (e.g., aggressive vs. passive) to find the right trade-off.

Parameters:
  • trajectory (ndarray[tuple[Any, ...], dtype[floating]]) – Holdings path of length n_periods + 1, starting at the initial position and ending at 0.

  • sigma (float) – Price volatility per period.

  • eta (float) – Temporary impact coefficient.

  • gamma (float) – Permanent impact coefficient.

Returns:

  • 'expected_cost': Expected total execution cost (temporary + permanent impact). Lower is better.

  • 'variance': Variance of execution cost due to price uncertainty while holding the position.

  • 'std_dev': Standard deviation of execution cost. This is the “execution risk.”

Return type:

dict[str, float]

Example

>>> import numpy as np
>>> traj = np.linspace(10_000, 0, 21)  # linear liquidation
>>> metrics = optimal_execution_cost(traj, sigma=0.02, eta=0.001, gamma=0.0001)
>>> metrics['expected_cost'] > 0
True
>>> metrics['std_dev'] > 0
True

See also

almgren_chriss: Compute the optimal trajectory. execution_frontier: Trace the cost-risk frontier.

execution_frontier(total_qty, sigma, eta, gamma, n_points=20, n_periods=20)[source]

Efficient frontier of (expected cost, risk) pairs.

Sweeps over a range of risk-aversion parameters to trace out the trade-off between expected execution cost and execution risk.

Parameters:
  • total_qty (float) – Total shares to execute.

  • sigma (float) – Price volatility per period.

  • eta (float) – Temporary impact coefficient.

  • gamma (float) – Permanent impact coefficient.

  • n_points (int, default: 20) – Number of points on the frontier.

  • n_periods (int, default: 20) – Number of execution periods for each trajectory.

Return type:

dict[str, ndarray[tuple[Any, ...], dtype[floating]]]

Returns:

Dictionary with 'lambda_values', 'expected_cost', and 'std_dev' arrays, each of length n_points.

bertsimas_lo(total_shares, n_periods, volatility, impact_coeff, risk_aversion=0.0)[source]

Bertsimas-Lo (1998) optimal execution with discrete trading.

Use this for a discrete-time optimal execution model that minimises expected cost plus a risk penalty. Unlike Almgren-Chriss which models continuous trading, Bertsimas-Lo explicitly works in discrete periods and provides a closed-form solution under a linear temporary impact model.

The optimal strategy in each period trades a constant fraction of the remaining position. With zero risk aversion, this simplifies to the naive strategy of trading total_shares / n_periods per period (linear liquidation).

Temporary impact model:

cost_t = impact_coeff * n_t^2

where n_t is the number of shares traded in period t.

Total expected cost:

E[cost] = impact_coeff * sum(n_t^2)

Parameters:
  • total_shares (float) – Total shares to liquidate (positive).

  • n_periods (int) – Number of discrete trading periods.

  • volatility (float) – Per-period price volatility (standard deviation).

  • impact_coeff (float) – Temporary impact coefficient. The cost of trading n shares in one period is impact_coeff * n^2.

  • risk_aversion (float, default: 0.0) – Risk-aversion parameter (default 0.0 for risk-neutral). Higher values front-load execution to reduce variance.

Returns:

  • trajectory (NDArray) – Holdings path of length n_periods + 1, starting at total_shares and ending at 0.

  • trades (NDArray) – Shares traded per period (length n_periods).

  • expected_cost (float) – Expected total execution cost.

  • cost_variance (float) – Variance of execution cost from holding risk.

Return type:

dict[str, object]

Example

>>> result = bertsimas_lo(10_000, n_periods=20, volatility=0.02,
...                      impact_coeff=0.001, risk_aversion=0.0)
>>> result['trajectory'][0]
10000.0
>>> result['trajectory'][-1]
0.0
>>> len(result['trades'])
20

References

  • Bertsimas & Lo (1998), “Optimal Control of Execution Costs” Journal of Financial Markets, 1(1), 1-50.

See also

almgren_chriss: Continuous-time optimal execution. optimal_execution_cost: Evaluate a given trajectory.

estimate_impact_params(trade_prices, volumes, directions)[source]

Estimate temporary and permanent impact coefficients from trade data.

Bridges wraquant.microstructure.liquidity into the Almgren-Chriss framework by calibrating impact parameters from observed trade data. The permanent impact coefficient (gamma) is derived from the microstructure price_impact function, and the temporary impact coefficient (eta) is estimated from the effective spread.

Parameters:
  • trade_prices (pd.Series) – Executed trade prices.

  • volumes (pd.Series) – Volume for each trade.

  • directions (pd.Series) – Trade direction (+1 buy, -1 sell).

Return type:

dict[str, float]

Returns:

Dictionary with eta (temporary impact coefficient) and gamma (permanent impact coefficient) suitable for use with almgren_chriss() and optimal_execution_cost().

Example

>>> import pandas as pd, numpy as np
>>> prices = pd.Series([100.0, 100.05, 100.08, 99.95])
>>> vols = pd.Series([1000, 2000, 1500, 1800])
>>> dirs = pd.Series([1, 1, -1, -1])
>>> params = estimate_impact_params(prices, vols, dirs)
>>> 'eta' in params and 'gamma' in params
True

See also

almgren_chriss: Uses the estimated parameters. wraquant.microstructure.liquidity.price_impact: Source of impact data.