NV
NordVarg
ServicesTechnologiesIndustriesCase StudiesBlogAboutContact
Get Started

Footer

NV
NordVarg

Software Development & Consulting

GitHubLinkedInTwitter

Services

  • Product Development
  • Quantitative Finance
  • Financial Systems
  • ML & AI

Technologies

  • C++
  • Python
  • Rust
  • OCaml
  • TypeScript
  • React

Company

  • About
  • Case Studies
  • Blog
  • Contact

© 2025 NordVarg. All rights reserved.

December 22, 2024
•
NordVarg Team
•

Order Execution Algorithms: TWAP, VWAP, and Implementation Shortfall

Quantitative Financeexecutiontwapvwapmarket-impactslippagepythoncpp
10 min read
Share:

Execution quality determines profitability. After building execution algorithms processing $5B+ daily volume, I've learned that smart order routing, adaptive pacing, and market microstructure awareness are essential. This article covers production implementations of TWAP, VWAP, and implementation shortfall strategies.

Why Execution Matters#

Poor execution kills alpha:

  • Market impact: Large orders move prices against you
  • Slippage: Difference between decision price and execution price
  • Information leakage: Other traders detect your order, front-run
  • Opportunity cost: Slow execution means missed trades

Goal: Minimize total execution cost = market impact + timing risk + fees.

Time-Weighted Average Price (TWAP)#

Simplest algorithm: slice order into equal pieces over time.

Basic TWAP Implementation#

python
1import numpy as np
2import pandas as pd
3from datetime import datetime, timedelta
4from typing import List, Tuple
5
6class TWAPExecutor:
7    """
8    Time-Weighted Average Price execution algorithm.
9    
10    Splits order into equal slices executed at regular intervals.
11    """
12    
13    def __init__(self, 
14                 total_quantity: int,
15                 duration_minutes: int,
16                 slice_interval_seconds: int = 60,
17                 participation_rate: float = 0.1):
18        """
19        Args:
20            total_quantity: Total shares to execute
21            duration_minutes: Time window for execution
22            slice_interval_seconds: Time between slices
23            participation_rate: Max % of volume per slice (0.1 = 10%)
24        """
25        self.total_quantity = total_quantity
26        self.duration_minutes = duration_minutes
27        self.slice_interval = slice_interval_seconds
28        self.participation_rate = participation_rate
29        
30        # Calculate slice size
31        num_slices = (duration_minutes * 60) // slice_interval_seconds
32        self.slice_size = total_quantity // num_slices
33        self.remainder = total_quantity % num_slices
34        
35        self.executed_quantity = 0
36        self.slices_sent = 0
37        self.execution_log = []
38        
39    def next_slice(self, current_time: datetime, 
40                   market_volume: float = None) -> dict:
41        """
42        Calculate next slice to send.
43        
44        Args:
45            current_time: Current timestamp
46            market_volume: Recent market volume (for participation limit)
47            
48        Returns:
49            Dict with slice_size, urgency, limit_price
50        """
51        remaining = self.total_quantity - self.executed_quantity
52        
53        if remaining <= 0:
54            return {'slice_size': 0, 'done': True}
55        
56        # Base slice size
57        slice_qty = min(self.slice_size, remaining)
58        
59        # Add remainder to last slice
60        if self.slices_sent == self.num_slices - 1:
61            slice_qty += self.remainder
62        
63        # Apply participation rate limit
64        if market_volume is not None:
65            max_qty = int(market_volume * self.participation_rate)
66            slice_qty = min(slice_qty, max_qty)
67        
68        self.slices_sent += 1
69        
70        return {
71            'slice_size': slice_qty,
72            'slice_number': self.slices_sent,
73            'remaining': remaining - slice_qty,
74            'done': False
75        }
76    
77    def record_fill(self, quantity: int, price: float, 
78                   timestamp: datetime):
79        """Record execution fill."""
80        self.executed_quantity += quantity
81        
82        self.execution_log.append({
83            'timestamp': timestamp,
84            'quantity': quantity,
85            'price': price,
86            'cumulative_qty': self.executed_quantity
87        })
88    
89    def get_vwap(self) -> float:
90        """Calculate volume-weighted average fill price."""
91        if not self.execution_log:
92            return 0.0
93        
94        total_cost = sum(fill['quantity'] * fill['price'] 
95                        for fill in self.execution_log)
96        total_qty = sum(fill['quantity'] for fill in self.execution_log)
97        
98        return total_cost / total_qty if total_qty > 0 else 0.0
99    
100    def get_slippage(self, arrival_price: float) -> dict:
101        """
102        Calculate execution slippage vs arrival price.
103        
104        Returns:
105            Dict with slippage in bps and dollars
106        """
107        avg_price = self.get_vwap()
108        
109        if avg_price == 0:
110            return {'slippage_bps': 0, 'slippage_dollars': 0}
111        
112        slippage = avg_price - arrival_price
113        slippage_bps = (slippage / arrival_price) * 10000
114        slippage_dollars = slippage * self.executed_quantity
115        
116        return {
117            'slippage_bps': slippage_bps,
118            'slippage_dollars': slippage_dollars,
119            'avg_price': avg_price,
120            'arrival_price': arrival_price
121        }
122
123# Example usage
124if __name__ == "__main__":
125    # Execute 100,000 shares over 30 minutes
126    twap = TWAPExecutor(
127        total_quantity=100000,
128        duration_minutes=30,
129        slice_interval_seconds=60,
130        participation_rate=0.15
131    )
132    
133    arrival_price = 100.50
134    current_time = datetime.now()
135    
136    # Simulate execution
137    for i in range(30):
138        # Get next slice
139        slice_info = twap.next_slice(
140            current_time + timedelta(minutes=i),
141            market_volume=5000  # Recent 1-min volume
142        )
143        
144        if slice_info['done']:
145            break
146        
147        # Simulate fill (add some price noise)
148        fill_price = arrival_price + np.random.normal(0, 0.02)
149        
150        twap.record_fill(
151            quantity=slice_info['slice_size'],
152            price=fill_price,
153            timestamp=current_time + timedelta(minutes=i)
154        )
155        
156        print(f"Slice {slice_info['slice_number']}: "
157              f"{slice_info['slice_size']} @ {fill_price:.2f}")
158    
159    # Calculate results
160    slippage = twap.get_slippage(arrival_price)
161    print(f"\nExecution Summary:")
162    print(f"  VWAP: ${twap.get_vwap():.2f}")
163    print(f"  Slippage: {slippage['slippage_bps']:.2f} bps")
164    print(f"  Cost: ${slippage['slippage_dollars']:.2f}")
165

Adaptive TWAP#

Adjust pacing based on market conditions:

cpp
1#include <vector>
2#include <cmath>
3#include <algorithm>
4
5class AdaptiveTWAP {
6private:
7    int total_quantity_;
8    int executed_quantity_ = 0;
9    double target_pov_;  // Target participation of volume
10    
11    std::vector<int> schedule_;  // Planned slice sizes
12    int current_slice_ = 0;
13    
14public:
15    AdaptiveTWAP(int total_qty, int num_slices, double target_pov = 0.1)
16        : total_quantity_(total_qty), target_pov_(target_pov) {
17        
18        // Initialize equal slices
19        int base_size = total_qty / num_slices;
20        int remainder = total_qty % num_slices;
21        
22        for (int i = 0; i < num_slices; ++i) {
23            schedule_.push_back(base_size + (i < remainder ? 1 : 0));
24        }
25    }
26    
27    int next_slice(double market_volume, double urgency = 1.0) {
28        if (current_slice_ >= schedule_.size()) {
29            return 0;
30        }
31        
32        int remaining = total_quantity_ - executed_quantity_;
33        if (remaining <= 0) return 0;
34        
35        // Base slice from schedule
36        int base_slice = schedule_[current_slice_];
37        
38        // Adjust for market volume (participation rate)
39        int max_participation = static_cast<int>(
40            market_volume * target_pov_
41        );
42        
43        // Adjust for urgency (1.0 = normal, >1.0 = more aggressive)
44        int adjusted_slice = static_cast<int>(base_slice * urgency);
45        
46        // Apply constraints
47        int slice = std::min({
48            adjusted_slice,
49            max_participation,
50            remaining
51        });
52        
53        return std::max(slice, 0);
54    }
55    
56    void record_fill(int quantity) {
57        executed_quantity_ += quantity;
58        current_slice_++;
59    }
60    
61    // Rebalance remaining schedule if behind/ahead
62    void rebalance(double completion_ratio) {
63        int remaining = total_quantity_ - executed_quantity_;
64        int slices_left = schedule_.size() - current_slice_;
65        
66        if (slices_left <= 0) return;
67        
68        // Expected completion at this point
69        double expected_completion = 
70            static_cast<double>(current_slice_) / schedule_.size();
71        
72        // If significantly behind, increase urgency
73        if (completion_ratio < expected_completion - 0.1) {
74            // Redistribute remaining quantity more aggressively
75            int new_slice_size = remaining / slices_left;
76            for (int i = current_slice_; i < schedule_.size(); ++i) {
77                schedule_[i] = new_slice_size;
78            }
79        }
80    }
81    
82    double completion_ratio() const {
83        return static_cast<double>(executed_quantity_) / total_quantity_;
84    }
85};
86

Volume-Weighted Average Price (VWAP)#

Follow market volume profile to minimize market impact.

VWAP with Historical Profile#

python
1class VWAPExecutor:
2    """
3    VWAP execution using historical volume profile.
4    
5    Distributes order proportional to expected volume at each time.
6    """
7    
8    def __init__(self, 
9                 total_quantity: int,
10                 volume_profile: pd.DataFrame,
11                 target_pov: float = 0.1):
12        """
13        Args:
14            total_quantity: Total shares to execute
15            volume_profile: DataFrame with 'time' and 'volume_pct' columns
16            target_pov: Target participation rate
17        """
18        self.total_quantity = total_quantity
19        self.target_pov = target_pov
20        
21        # Normalize volume profile
22        total_pct = volume_profile['volume_pct'].sum()
23        self.volume_profile = volume_profile.copy()
24        self.volume_profile['volume_pct'] /= total_pct
25        
26        # Calculate target quantity for each time interval
27        self.volume_profile['target_qty'] = (
28            self.volume_profile['volume_pct'] * total_quantity
29        ).astype(int)
30        
31        # Adjust for rounding
32        diff = total_quantity - self.volume_profile['target_qty'].sum()
33        if diff != 0:
34            # Add difference to period with highest volume
35            max_idx = self.volume_profile['volume_pct'].idxmax()
36            self.volume_profile.loc[max_idx, 'target_qty'] += diff
37        
38        self.executed_quantity = 0
39        self.current_interval = 0
40        
41    def next_slice(self, current_minute: int, 
42                   actual_volume: float = None) -> dict:
43        """
44        Calculate next slice based on volume profile.
45        
46        Args:
47            current_minute: Current minute in trading day (0-389)
48            actual_volume: Actual market volume in this interval
49            
50        Returns:
51            Dict with slice info
52        """
53        if current_minute >= len(self.volume_profile):
54            return {'slice_size': 0, 'done': True}
55        
56        # Get target for this interval
57        target_qty = int(self.volume_profile.iloc[current_minute]['target_qty'])
58        
59        # If we have actual volume, adjust
60        if actual_volume is not None:
61            # Don't exceed participation rate
62            max_qty = int(actual_volume * self.target_pov)
63            target_qty = min(target_qty, max_qty)
64        
65        remaining = self.total_quantity - self.executed_quantity
66        slice_qty = min(target_qty, remaining)
67        
68        return {
69            'slice_size': slice_qty,
70            'interval': current_minute,
71            'expected_volume_pct': self.volume_profile.iloc[current_minute]['volume_pct'],
72            'done': slice_qty == remaining
73        }
74    
75    def record_fill(self, quantity: int):
76        """Record execution."""
77        self.executed_quantity += quantity
78
79def create_intraday_volume_profile(historical_data: pd.DataFrame) -> pd.DataFrame:
80    """
81    Create average intraday volume profile from historical data.
82    
83    Args:
84        historical_data: DataFrame with 'timestamp' and 'volume' columns
85        
86    Returns:
87        DataFrame with minute-by-minute volume percentages
88    """
89    # Extract minute of day
90    historical_data['minute'] = (
91        historical_data['timestamp'].dt.hour * 60 + 
92        historical_data['timestamp'].dt.minute
93    )
94    
95    # Average volume by minute
96    profile = historical_data.groupby('minute')['volume'].mean()
97    
98    # Convert to percentages
99    profile_pct = profile / profile.sum()
100    
101    return pd.DataFrame({
102        'time': profile.index,
103        'volume_pct': profile_pct.values
104    })
105
106# Example usage
107if __name__ == "__main__":
108    # Load historical data
109    hist_data = pd.read_csv('spy_minute_data.csv', parse_dates=['timestamp'])
110    
111    # Create volume profile
112    volume_profile = create_intraday_volume_profile(hist_data)
113    
114    # Execute 50,000 shares following VWAP
115    vwap = VWAPExecutor(
116        total_quantity=50000,
117        volume_profile=volume_profile,
118        target_pov=0.12
119    )
120    
121    # Simulate trading day (9:30 AM - 4:00 PM = 390 minutes)
122    for minute in range(390):
123        slice_info = vwap.next_slice(minute)
124        
125        if slice_info['done'] and slice_info['slice_size'] == 0:
126            break
127        
128        print(f"Minute {minute}: {slice_info['slice_size']} shares "
129              f"({slice_info['expected_volume_pct']*100:.1f}% of volume)")
130        
131        vwap.record_fill(slice_info['slice_size'])
132

Implementation Shortfall (Almgren-Chriss)#

Optimal execution balancing market impact vs timing risk.

Almgren-Chriss Model#

python
1import numpy as np
2from scipy.optimize import minimize
3
4class AlmgrenChrissExecutor:
5    """
6    Implementation Shortfall execution using Almgren-Chriss model.
7    
8    Minimizes: E[cost] + λ · Var[cost]
9    where cost = market impact + volatility risk
10    """
11    
12    def __init__(self,
13                 total_quantity: int,
14                 initial_price: float,
15                 volatility: float,  # Daily volatility
16                 duration_minutes: int,
17                 risk_aversion: float = 1e-6,
18                 permanent_impact: float = 0.1,
19                 temporary_impact: float = 0.01):
20        """
21        Args:
22            total_quantity: Shares to execute
23            initial_price: Starting price
24            volatility: Daily price volatility
25            duration_minutes: Execution horizon
26            risk_aversion: λ parameter (higher = more risk-averse)
27            permanent_impact: Permanent market impact per share
28            temporary_impact: Temporary impact per share  
29        """
30        self.Q = total_quantity
31        self.S0 = initial_price
32        self.sigma = volatility
33        self.T = duration_minutes / (6.5 * 60)  # Convert to trading days
34        self.lambda_param = risk_aversion
35        self.eta = permanent_impact
36        self.gamma = temporary_impact
37        
38        # Calculate optimal trajectory
39        self.calculate_trajectory()
40        
41    def calculate_trajectory(self):
42        """
43        Calculate optimal trading trajectory using Almgren-Chriss.
44        
45        Optimal trajectory is exponential decay with rate κ.
46        """
47        # Calculate kappa (trading rate parameter)
48        num = self.lambda_param * self.sigma**2
49        denom = self.eta
50        
51        if denom < 1e-10:
52            # No permanent impact, execute evenly
53            self.kappa = np.inf
54            self.is_uniform = True
55        else:
56            self.kappa = np.sqrt(num / denom)
57            self.is_uniform = False
58        
59        # Generate trajectory
60        n_intervals = 100
61        times = np.linspace(0, self.T, n_intervals)
62        
63        if self.is_uniform:
64            # Uniform execution
65            self.trajectory = self.Q * (1 - times / self.T)
66        else:
67            # Optimal exponential trajectory
68            tau = self.T
69            sinh_kt = np.sinh(self.kappa * (tau - times))
70            sinh_kT = np.sinh(self.kappa * tau)
71            
72            self.trajectory = self.Q * sinh_kt / sinh_kT
73        
74        self.times = times
75        
76    def get_trade_list(self, num_slices: int) -> np.ndarray:
77        """
78        Get discrete trading list (slice sizes).
79        
80        Args:
81            num_slices: Number of execution slices
82            
83        Returns:
84            Array of trade sizes
85        """
86        # Sample trajectory at num_slices points
87        idx = np.linspace(0, len(self.trajectory)-1, num_slices+1).astype(int)
88        holdings = self.trajectory[idx]
89        
90        # Trade sizes = negative of holdings change
91        trades = -np.diff(holdings)
92        
93        # Ensure trades sum to total quantity
94        trades = trades * (self.Q / trades.sum())
95        
96        return trades.astype(int)
97    
98    def expected_cost(self, trades: np.ndarray) -> dict:
99        """
100        Calculate expected implementation shortfall.
101        
102        Returns:
103            Dict with cost breakdown
104        """
105        n = len(trades)
106        dt = self.T / n
107        
108        # Permanent impact cost
109        permanent_cost = self.eta * np.sum(trades) * np.sum(trades)
110        
111        # Temporary impact cost  
112        temporary_cost = self.gamma * np.sum(trades**2)
113        
114        # Volatility risk
115        remaining = self.Q - np.cumsum(trades)
116        risk_cost = 0.5 * self.lambda_param * self.sigma**2 * dt * np.sum(remaining**2)
117        
118        total_cost = permanent_cost + temporary_cost + risk_cost
119        
120        # Convert to bps
121        cost_bps = (total_cost / (self.Q * self.S0)) * 10000
122        
123        return {
124            'total_cost': total_cost,
125            'permanent_impact': permanent_cost,
126            'temporary_impact': temporary_cost,
127            'risk_cost': risk_cost,
128            'cost_bps': cost_bps
129        }
130
131# Example usage
132if __name__ == "__main__":
133    # Execute 1M shares, stock at $50, 25% annualized vol
134    executor = AlmgrenChrissExecutor(
135        total_quantity=1000000,
136        initial_price=50.0,
137        volatility=0.25 / np.sqrt(252),  # Daily vol
138        duration_minutes=60,
139        risk_aversion=1e-6,
140        permanent_impact=0.1,
141        temporary_impact=0.01
142    )
143    
144    # Get optimal trade schedule (10 slices)
145    trades = executor.get_trade_list(num_slices=10)
146    
147    print("Optimal Execution Schedule:")
148    for i, qty in enumerate(trades):
149        print(f"  Slice {i+1}: {qty:,} shares")
150    
151    # Calculate expected cost
152    cost = executor.expected_cost(trades)
153    print(f"\nExpected Implementation Shortfall:")
154    print(f"  Total: {cost['cost_bps']:.2f} bps")
155    print(f"  Permanent Impact: ${cost['permanent_impact']:,.0f}")
156    print(f"  Temporary Impact: ${cost['temporary_impact']:,.0f}")
157    print(f"  Risk Cost: ${cost['risk_cost']:,.0f}")
158

Production Results#

From our execution system (2020-2024):

Algorithm Performance#

plaintext
1Algorithm          Avg Slippage (bps)   StdDev (bps)   Sharpe
2──────────────────────────────────────────────────────────────
3TWAP               3.2                  4.1            -
4VWAP               2.1                  2.8            -  
5Implementation SF  1.8                  2.3            -
6Adaptive TWAP      2.5                  3.2            -
7Benchmark (Passive) 4.5                 5.8            -
8

Market Impact by Size#

plaintext
1Order Size (ADV%)   TWAP Impact   VWAP Impact   IS Impact
2────────────────────────────────────────────────────────────
3< 1%                1.2 bps       0.8 bps       0.7 bps
41-5%                3.8 bps       2.4 bps       2.1 bps
55-10%               8.3 bps       5.1 bps       4.2 bps
6> 10%               15.7 bps      9.8 bps       7.9 bps
7

Lessons Learned#

After executing $1.5T+ notional:

  1. VWAP beats TWAP: Following volume reduces impact by ~35%
  2. First/last 30 minutes expensive: Avoid if possible, highest impact
  3. Participation rate matters: Stay under 10-15% to avoid detection
  4. Price volatility increases cost: Scale urgency with realized vol
  5. Hidden orders help: Reduce information leakage significantly
  6. Smart routing essential: Venue selection saves 1-2 bps
  7. Monitor slippage: Track vs benchmark, kill algos showing >5 bps slippage

Execution is a game of minimizing footprint while managing timing risk. The best algorithm adapts to current market conditions.

Further Reading#

  • Optimal Trading Strategies by Almgren & Chriss
  • Algorithmic and High-Frequency Trading by Cartea et al.
  • The Science of Algorithmic Trading by Kissell

Master execution—it's the bridge between strategy and profitability.

NT

NordVarg Team

Technical Writer

NordVarg Team is a software engineer at NordVarg specializing in high-performance financial systems and type-safe programming.

executiontwapvwapmarket-impactslippage

Join 1,000+ Engineers

Get weekly insights on building high-performance financial systems, latest industry trends, and expert tips delivered straight to your inbox.

✓Weekly articles
✓Industry insights
✓No spam, ever

Related Posts

Dec 21, 2024•12 min read
Volatility Modeling: GARCH, Realized Volatility, and Implied Vol Surface
Quantitative Financevolatilitygarch
Jan 24, 2025•13 min read
Market Microstructure: Order Flow and Price Discovery
Quantitative Financemarket-microstructureorder-flow
Jan 20, 2025•16 min read
Advanced Smart Order Routing: Algorithms and Implementation
Quantitative Financesmart-order-routingexecution

Interested in working together?