Skip to content

VWAP Execution Algorithm

Difficulty expert

Overview

VWAP (Volume Weighted Average Price) execution splits orders to match the market's volume profile, minimizing market impact.

vwap calculation

VWAP = Σ(Price_i × Volume_i) / Σ(Volume_i)

where: Price_i trade price · Volume_i trade size · summation over every trade in the chosen window. does: VWAP is both the benchmark (the volume-weighted average trade price) and the algo (slice the parent order to match the historical intraday volume curve — heavier near open and close, lighter midday). Pick VWAP for large, non-urgent orders where matching the market's natural participation profile minimizes impact and produces a defensible benchmark — the institutional default for liquidity-driven execution.

def calculate_vwap(prices, volumes):
    """Calculate VWAP."""
    cumulative_pv = np.cumsum(prices * volumes)
    cumulative_v = np.cumsum(volumes)
    return cumulative_pv / cumulative_v

Algorithm Implementation

class VWAPExecution:
    """VWAP-based execution algorithm."""

    def __init__(self, target_shares, start_time, end_time, volume_profile):
        self.target = target_shares
        self.remaining = target_shares
        self.volume_profile = volume_profile  # Expected volume by time interval
        self.filled = 0
        self.total_cost = 0

    def get_target_participation(self, current_time):
        """Calculate target participation rate based on volume profile."""
        interval = self.get_time_interval(current_time)
        expected_volume_pct = self.volume_profile[interval]
        return self.target * expected_volume_pct

    def execute_interval(self, market_volume, current_price):
        """Execute for current time interval."""
        target_qty = self.get_target_participation(current_time)

        # Adjust for participation rate already achieved
        if self.remaining > 0:
            execute_qty = min(target_qty, self.remaining, market_volume * 0.1)  # Cap at 10% of volume
            self.filled += execute_qty
            self.remaining -= execute_qty
            self.total_cost += execute_qty * current_price

        return execute_qty

    def get_avg_price(self):
        """Calculate average execution price."""
        return self.total_cost / self.filled if self.filled > 0 else 0

    def get_slippage(self, benchmark_vwap):
        """Calculate VWAP slippage."""
        avg_price = self.get_avg_price()
        return avg_price - benchmark_vwap

Historical Volume Profile

def build_volume_profile(historical_data, n_intervals=390):
    """Build average volume profile from historical data."""
    # 390 = minutes in US trading day
    profiles = []
    for day in historical_data:
        profile = day['volume'].resample('1min').sum().values[:n_intervals]
        profiles.append(profile / profile.sum())  # Normalize

    avg_profile = np.mean(profiles, axis=0)
    return avg_profile  # Each value = % of daily volume for that minute

Performance Metrics

Metric Formula Target
VWAP Slippage Avg Exec Price - VWAP Close to 0
Participation Rate Our Volume / Market Volume Match profile
Completion Rate Filled / Target 100%
Market Impact Price move due to our trading Minimize

Practical Guidelines

  1. Use for Large Orders — Best when order > 5% of ADV
  2. Avoid First/Last 15 Min — Volume profile is noisy
  3. Monitor Closely — Adjust if volume deviates from profile
  4. Combine with Passive — Use limit orders within VWAP framework
  5. Track Slippage — Measure against actual VWAP

Next Steps