σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademySkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
Skills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python skill›Module 11 · Production systems & frontier models›Topic 11.5·~1 hrExpert

VWAP execution

Skill positionModule 11 of 11 · Topic 5 of 13
11.5 VWAP execution
—Lab score— / 100Total XP—Streak—

Trade when the market trades

The volume-weighted average price is the average price of everything that traded, each trade weighted by its size: sum(price * volume) / sum(volume). It is the most common benchmark in institutional trading because it is what an average participant paid that day. A VWAP algorithm tries to match or beat it by trading *in proportion to the market's volume*: a lot when the market is busy (the open, the close), little when it is quiet (lunchtime). Where TWAP follows the clock, VWAP follows the crowd, so each of your orders is a similar fraction of what everyone else is doing and moves the price less.

# The benchmark: each price counts as much as the volume that traded at it
def vwap(prices, volumes):
    return sum(p * v for p, v in zip(prices, volumes)) / sum(volumes)

prices = [100.0, 101.0, 102.0]
volumes = [100, 300, 600]
print(round(vwap(prices, volumes), 4))               # 101.5     most volume traded near 102, so the VWAP leans that way
print(sum(prices) / len(prices))                     # 101.0     the plain average ignores how much traded at each price

A volume profile and a schedule that follows it

You cannot know today's volume in advance, so a VWAP algorithm uses a volume profile estimated from history: the average share of the day's volume that traded in each interval over, say, the last 20 sessions. Intraday volume is typically U-shaped, heavy at the open and the close. The schedule then gives each interval total * share shares. These are rarely whole numbers, and simply rounding each can lose or add shares. The largest remainder method fixes that: give every interval the floor, then hand the leftover shares, one each, to the intervals with the largest fractional parts, so the schedule always adds up to the order.

# From volumes to a profile, then to whole shares by the largest remainder method
import math

def volume_profile(volumes):
    total = sum(volumes)
    return [v / total for v in volumes]

def vwap_schedule(total, profile):
    exact = [total * share for share in profile]
    shares = [math.floor(value + 1e-9) for value in exact]
    leftover = total - sum(shares)
    for index in sorted(range(len(profile)), key=lambda i: (-(exact[i] - shares[i]), i))[:leftover]:
        shares[index] += 1
    return shares

profile = volume_profile([300, 100, 100, 200, 500, 800])       # a U-shape: busy at the open and the close
print([round(p, 3) for p in profile])                # [0.15, 0.05, 0.05, 0.1, 0.25, 0.4]
print(vwap_schedule(1003, profile))                  # [151, 50, 50, 100, 251, 401]   adds up to 1003
print(sum(vwap_schedule(1003, profile)))             # 1003

Participation limits and tracking the benchmark

A schedule is a plan; the market may be quieter than the profile predicted. A participation cap limits your trading in each interval to a fraction of the market's actual volume in it, say 10 %, so you never become most of the flow. Shares you could not trade are carried forward to later intervals, which may leave some unexecuted at the end; then you decide whether to take more risk, extend the window or stop. After the day, compare your average price with the market VWAP: the difference, in basis points, is the tracking error of the algorithm. Beating VWAP by trading late in a rising market is not skill; judge an algorithm by its slippage over many days.

# A participation cap: never more than a fraction of the market's volume, carrying the rest forward
import math

def cap_participation(schedule, market_volumes, rate):
    trades, carry = [], 0
    for scheduled, volume in zip(schedule, market_volumes):
        wanted = scheduled + carry
        done = min(wanted, math.floor(rate * volume + 1e-9))
        trades.append(done)
        carry = wanted - done
    return trades, carry

print(cap_participation([100, 100, 100], [500, 1000, 1000], 0.1))      # ([50, 100, 100], 50)
print(cap_participation([100, 0, 0, 0], [500, 1000, 400, 800], 0.1))   # ([50, 50, 0, 0], 0)   the carry is traded when the market allows it
# Following the clock (TWAP) and following the volume (VWAP) give different average prices on the same day
prices = [100.0, 100.2, 100.1, 100.4, 100.3, 100.5]
volumes = [300, 100, 100, 200, 500, 800]               # busy at the open and the close

market_vwap = sum(p * v for p, v in zip(prices, volumes)) / sum(volumes)
twap_average = sum(prices) / len(prices)                # equal pieces at every time
print(round(market_vwap, 4), round(twap_average, 4))    # 100.33 100.25
print(round((twap_average - market_vwap) / market_vwap * 10000, 1))      # -8.0   a TWAP buy beat the VWAP here, by trading light when volume was heavy
VWAP is a benchmark, not a goalMatching the VWAP says you traded like the average participant, which may be a poor result if your own order pushed the price (your trades are part of the VWAP you are compared to). Check the algorithm against the arrival price as well, test the profile on days it was not estimated on, and expect profiles to fail on news days, half-days and index rebalances. Keep a human-readable log of every child order for later analysis.
  • VWAP is sum(price * volume) / sum(volume), the common benchmark; a VWAP algorithm trades in proportion to the market's volume.
  • Estimate a volume profile from history and split the order along it with the largest remainder method so whole shares add up exactly.
  • Cap your participation in each interval and carry the rest forward; judge the algorithm by its tracking error and slippage over many days.

Next up: market making, quoting both sides and managing the inventory that results.

Lab · what's graded

  • Compute the benchmark with vwap25 pts
  • Shape the day with volume_profile25 pts
  • Split the order with vwap_schedule25 pts
  • Respect the market with cap_participation25 pts
Explorer · lab-134-1
▾ lab-134-1
…
bash · in-browser
learner@quantcraft:~/lab-134-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-134-1 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes

Log in to run your code.

Every lecture is free to read. Create an account or log in to run code, run tests and submit this lab.

  • In-browser IDE with a bash terminal — nothing to install
  • Instant grading, XP and your place on the skill
Create an accountLog in
OR
Continue with GitHub