Explainer
VWAP explained: formula, calculation and Python code on live data
VWAP is the price the whole market actually paid today. Computing it is one division; computing the same number your broker shows means getting the session, the bars and the volume right.
On this page
- The VWAP formula, worked on the first five minutes
- A full session of VWAP on US:KO
- VWAP from trades vs VWAP from 1-minute bars
- The stocks.agg vw field: exact VWAP without the trades
- When VWAP resets
- How to calculate VWAP in Python
- Anchored VWAP and TWAP vs VWAP
- How traders read VWAP, and what it is not
- Questions
Key takeaways
- VWAP is the average price of every trade in a session weighted by size: the sum of price times volume divided by total volume, accumulated from the session start.
- It resets every session: US stocks usually start a fresh VWAP at the 09:30 New York open, and crypto charts usually reset at 00:00 UTC.
- On US:KO's 2026-09-25 session, VWAP from 390 one-minute bars was 87.832 against a close of 87.81; one daily bar's typical price was 7 basis points off.
- When each bar carries its own `vw`, summing vw times volume across bars reproduces the exact session VWAP; the typical price (H+L+C)/3 is the fallback.
- VWAP is an execution benchmark and a reference level. Price above it is not a buy signal on its own.
VWAP (volume-weighted average price) is the average price of every trade in a session, with each trade weighted by its size: VWAP = Σ(price × volume) ÷ Σ volume, summed from the session open to now. It answers one question, "what did the average share cost today", which is why institutions grade their fills against it and why intraday traders watch where price sits relative to it.
This explainer works the formula on a real session of US:KO, compares VWAP from individual trades with VWAP from OHLC bars, shows when it resets, and ends with Python that computes it from REST bars and keeps it live from the stream.
The VWAP formula, worked on the first five minutes
VWAP(t) = Σ (Pi × Vi) ÷ Σ Vi, for every trade i from the session open to tfrom bars: Pi = typical price = (high + low + close) ÷ 3
- Pi
- Trade price, or a bar's representative price when you only have bars.
- Vi
- Shares, contracts or coins traded at that price.
- Σ
- Running sums that start at zero at the session open.
| Minute (ET) | High | Low | Close | Volume | Typical | Cum. volume | VWAP |
|---|---|---|---|---|---|---|---|
| 09:30 | 88.3183 | 87.935 | 88.06 | 268,550 | 88.1044 | 268,550 | 88.1044 |
| 09:31 | 88.04 | 87.82 | 87.9147 | 59,480 | 87.9249 | 328,030 | 88.0719 |
| 09:32 | 88.01 | 87.775 | 87.82 | 24,805 | 87.8683 | 352,835 | 88.0576 |
| 09:33 | 87.875 | 87.78 | 87.83 | 27,131 | 87.8283 | 379,966 | 88.0412 |
| 09:34 | 87.85 | 87.73 | 87.75 | 20,302 | 87.7767 | 400,268 | 88.0278 |
Notice how heavy the first minute is. The opening minute traded 268,550 shares, two thirds of the five-minute total, so by 09:34 VWAP was still 88.03 while price had dropped to 87.75. That stickiness is the point: VWAP moves easily early in the session and becomes an anchor as volume piles up.
A full session of VWAP on US:KO
US:KO, 30-minute candles with session VWAP, 2026-09-25
- Session VWAP
The day in one picture: an early drop below VWAP, a midday climb above it, then an afternoon slide that closed at 87.81, two cents under the final VWAP of 87.832. Price closed above the running VWAP in 205 of the 390 minutes, which is what a range day looks like. On a trend day, price stays on one side for hours.
US:KO volume per half hour, thousands of shares
VWAP from trades vs VWAP from 1-minute bars
The textbook VWAP uses every trade. Most people compute it from bars instead, because a day of trades for a liquid stock is hundreds of thousands of rows while a day of 1-minute bars is 390. The cost is a small approximation: inside a bar you only know high, low, close and volume, so you pretend all the volume traded at the typical price.
| Built from | VWAP for US:KO, 2026-09-25 | Difference vs 1-minute |
|---|---|---|
| 1-minute bars (390) | 87.8318 | reference |
| 5-minute bars (78) | 87.8247 | -0.8 bps |
| 15-minute bars (26) | 87.8339 | +0.2 bps |
| 30-minute bars (13) | 87.8446 | +1.5 bps |
| 1-hour bars (7) | 87.8419 | +1.2 bps |
| One daily bar | 87.8944 | +7.1 bps |
On 1-minute bars of a liquid name the approximation is tiny: using the close instead of the typical price moves the answer by less than 0.2 bps. It grows with bar size and with how much a stock moves inside each bar, so for thin names or hourly bars prefer finer data. Also check what your bars contain. The same day's daily bar reports 12,261,067 shares while the 390 regular-session minute bars add up to 8,997,796; prints that fall outside those minutes (the closing auction can be one of them) are outside a minute-bar VWAP too. That, plus pre-market choices, is the usual reason two platforms show different VWAPs.
The stocks.agg vw field: exact VWAP without the trades
A settled 1-minute bar on the stocks.agg channel
{
"type": "agg",
"channel": "stocks.agg",
"asset": "stocks",
"symbol": "US:KO",
"interval": "1m",
"session": "regular",1
"o": 88.48,
"h": 88.52,
"l": 88.46,
"c": 88.5,
"v": 41230,2
"n": 312,3
"vw": 88.4917,4
"ts": 17891334000005
}
sessionregularfor 09:30 to 16:00 New York; extended-hours bars arrive when you subscribe withsession: "extended".vShares traded in the minute.nNumber of trades in the minute. May be omitted.vwThe bar's own VWAP, from its trades. May be omitted; fall back to the typical price.tsBar start in Unix milliseconds. Bars are published once settled, never while forming.
Because vw × v equals the bar's Σ price × volume, summing it across bars and dividing by total volume gives the trade-level session VWAP exactly, with no typical-price guess. The channel has two properties to design around: it does not replay a snapshot on subscribe, and bars that settle while you are disconnected are not resent. Backfill the session from REST first, then let the stream take over. The stocks.agg reference lists the intervals and session options.
When VWAP resets
US equity sessions and the crypto day, in UTC (New York summer time)
Hours in UTC
For US stocks the convention is the regular session, and the reset key in code is the New York calendar date of the bar. Some traders fold pre-market into the day's VWAP; the after-hours trading explainer covers why those prints are thinner and wider. Crypto never closes, so the reset is a convention, usually midnight UTC. Spot FX has no consolidated volume, so an FX VWAP depends on whose volume you weight by and is best treated as indicative.
How to calculate VWAP in Python
Start with the historical version: pull a session of 1-minute bars and run the formula. It needs only requests and your key in TICKERLAYER_API_KEY.
import os
from datetime import datetime
from zoneinfo import ZoneInfo
import requests # pip install requests
API = "https://api.tickerlayer.com"
HEADERS = {"x-api-key": os.environ["TICKERLAYER_API_KEY"]}
NEW_YORK = ZoneInfo("America/New_York")
def minute_bars(symbol, day):
"""Regular-session 1-minute bars for one date, oldest first."""
url = f"{API}/stocks/agg/{symbol}/1/minute/{day}/{day}"
params = {"sort": "asc", "limit": 5000, "offset": 0}
bars = []
while True:
resp = requests.get(url, params=params, headers=HEADERS, timeout=15)
resp.raise_for_status()
body = resp.json()
bars.extend(body["results"])
if body.get("next_offset") is None:
return bars
params["offset"] = body["next_offset"]
def running_vwap(bars):
"""Yield (bar, VWAP so far), using each bar's typical price."""
pv = volume = 0.0
for bar in bars:
typical = (bar["h"] + bar["l"] + bar["c"]) / 3
pv += typical * bar["v"]
volume += bar["v"]
yield bar, pv / volume
bars = minute_bars("US:KO", "2026-09-25")
rows = list(running_vwap(bars))
for bar, vwap in rows[::60] + rows[-1:]:
at = datetime.fromtimestamp(bar["t"] / 1000, NEW_YORK).strftime("%H:%M")
side = "above" if bar["c"] > vwap else "below"
print(f"{at} ET close {bar['c']:7.3f} VWAP {vwap:7.3f} ({side})")
print(f"{len(bars)} bars, {sum(b['v'] for b in bars):,} shares")09:30 ET close 88.060 VWAP 88.104 (below)
10:30 ET close 87.840 VWAP 87.859 (below)
11:30 ET close 87.835 VWAP 87.843 (below)
12:30 ET close 88.025 VWAP 87.859 (above)
13:30 ET close 88.100 VWAP 87.882 (above)
14:30 ET close 87.635 VWAP 87.877 (below)
15:30 ET close 87.660 VWAP 87.834 (below)
15:59 ET close 87.810 VWAP 87.832 (below)
390 bars, 8,997,796 sharesFor a live session VWAP, subscribe to stocks.agg before backfilling, so no minute can fall between the REST call and the first streamed bar, and de-duplicate by bar start. Streamed bars use their exact vw; backfilled ones use the typical price. WebSocket access comes with paid plans, and the connection details are in the Python WebSocket client tutorial.
import asyncio
import json
import os
import time
from datetime import datetime
from urllib.parse import quote
from zoneinfo import ZoneInfo
import requests
import websockets # pip install websockets requests
API = "https://api.tickerlayer.com"
KEY = os.environ["TICKERLAYER_API_KEY"]
STREAM = "wss://stream.tickerlayer.com/?apiKey=" + quote(KEY, safe="")
NEW_YORK = ZoneInfo("America/New_York")
SYMBOL = "US:KO"
class SessionVwap:
"""Running VWAP over 1-minute bars that starts again on each New York trading day."""
def __init__(self):
self.session, self.pv, self.volume, self.seen = None, 0.0, 0.0, set()
def add_bar(self, bar_start_ms, price, volume):
session = datetime.fromtimestamp(bar_start_ms / 1000, NEW_YORK).date()
if session != self.session: # new day: reset
self.session, self.pv, self.volume, self.seen = session, 0.0, 0.0, set()
if bar_start_ms not in self.seen: # REST backfill and stream may overlap
self.seen.add(bar_start_ms)
self.pv += price * volume
self.volume += volume
return self.pv / self.volume if self.volume else None
def backfill(vwap):
"""Closed regular-session minutes so far today. REST bars carry no vw: use typical price."""
today = datetime.now(NEW_YORK).date().isoformat()
resp = requests.get(f"{API}/stocks/agg/{SYMBOL}/1/minute/{today}/{today}",
params={"sort": "asc", "limit": 5000}, headers={"x-api-key": KEY}, timeout=15)
resp.raise_for_status()
forming = int(time.time()) // 60 * 60_000
for bar in resp.json()["results"]:
if bar["t"] < forming:
vwap.add_bar(bar["t"], (bar["h"] + bar["l"] + bar["c"]) / 3, bar["v"])
async def main():
vwap = SessionVwap()
async with websockets.connect(STREAM, compression=None, open_timeout=10) as ws:
await ws.recv() # {"type":"system","event":"ready",...}
await ws.send(json.dumps({"action": "subscribe", "channels": ["stocks.agg"],
"symbols": [SYMBOL], "interval": "1m", "session": "regular"}))
await asyncio.to_thread(backfill, vwap) # subscribe first, so no minute falls in a gap
async for raw in ws:
msg = json.loads(raw)
if msg.get("type") != "agg":
continue
price = msg.get("vw") or (msg["h"] + msg["l"] + msg["c"]) / 3 # vw can be omitted
value = vwap.add_bar(msg["ts"], price, msg["v"])
at = datetime.fromtimestamp(msg["ts"] / 1000, NEW_YORK).strftime("%H:%M")
print(f"{at} {SYMBOL} close {msg['c']:.2f} bar vw {price:.4f} session VWAP {value:.4f}")
if __name__ == "__main__":
asyncio.run(main())Crypto works the same way with trades instead of bars. The key change is the reset key (the UTC day) and the numeric types: trade frames on crypto.trades send price and size as strings, and tiny sizes arrive in exponent form such as "3e-8", which float() handles. Without a backfill this is the VWAP since you connected, not since midnight, so label it that way or backfill today's minute bars first.
import asyncio
import json
import os
from urllib.parse import quote
import websockets # pip install websockets
URL = "wss://stream.tickerlayer.com/?apiKey=" + quote(os.environ["TICKERLAYER_API_KEY"], safe="")
DAY_MS = 86_400_000
async def main():
day, pv, volume, n = None, 0.0, 0.0, 0
async with websockets.connect(URL, compression=None, open_timeout=10) as ws:
await ws.recv() # {"type":"system","event":"ready",...}
await ws.send(json.dumps({"action": "subscribe", "channels": ["crypto.trades"], "symbols": ["BTCUSD"]}))
async for raw in ws:
msg = json.loads(raw)
if msg.get("type") != "trade":
continue
price, size = float(msg["price"]), float(msg["size"]) # strings; size can be "3e-8"
if msg["ts"] // DAY_MS != day: # a new UTC day starts a new VWAP
day, pv, volume = msg["ts"] // DAY_MS, 0.0, 0.0
pv += price * size
volume += size
n += 1
if n % 100 == 0:
print(f"BTCUSD last {price:,.2f} VWAP since connect {pv / volume:,.2f} on {volume:.4f} BTC")
if __name__ == "__main__":
asyncio.run(main())Anchored VWAP and TWAP vs VWAP
Anchored VWAP is the same formula with a different start: instead of the session open, you begin the sums at an event you care about, such as an earnings release, a gap, or a swing low days ago. With the code above that is running_vwap(bars[i:]), where i is the anchor bar, fed with multi-day bars. It shows the average cost of everyone who traded since that event, which is why it is used as support or resistance after big news.
VWAP
- Weights each price by the volume traded at it.
- US:KO, 2026-09-25: 87.832.
- The benchmark for "did I buy better than the market today".
- Needs real traded volume, so it is weak on spot FX.
TWAP
- Plain average of prices sampled at equal time steps.
- US:KO, same day, 1-minute closes: 87.854.
- Used to slice an order evenly through time when volume is hard to predict.
- Works on anything with a price, volume or not.
How traders read VWAP, and what it is not
- Execution benchmarkA fund that bought below the day's VWAP did better than the average participant. Brokers report fills against it.
- Intraday referencePrice above a rising VWAP describes buyers in control on average; below a falling one, sellers. It describes, it does not predict.
- Session contextEarly in the day VWAP swings with every print. Late in the session it barely moves, so a cross at 15:30 means more than one at 09:35.
- Pairs with other toolsTraders combine it with momentum measures such as the RSI indicator or with trend filters like the golden cross.
Questions
Is VWAP bullish or bearish?
Neither by itself. Price above VWAP means recent buyers paid more than the session average, price below means the opposite; many traders read it as context and combine it with trend and volume before acting.
What is the VWAP formula?
VWAP = Σ(price × volume) ÷ Σ volume over all trades since the session start. With bars, use each bar's typical price (high + low + close) ÷ 3, or its own vw when the data provides one.
Does VWAP reset every day?
Yes. Session VWAP starts over at each session open, 09:30 New York time for the regular US session. Crypto has no close, so charts usually reset at 00:00 UTC. Anchored VWAP starts wherever you choose.
What is the difference between TWAP and VWAP?
TWAP averages prices at equal time intervals; VWAP weights each price by traded volume. On US:KO on 2026-09-25 the 1-minute TWAP was 87.854 and VWAP 87.832.
Why does my VWAP differ from my trading platform's?
Usually the inputs differ: bar size, whether pre-market or auction prints are included, and the time zone of the reset. Align those three and the numbers converge.