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
  1. The VWAP formula, worked on the first five minutes
  2. A full session of VWAP on US:KO
  3. VWAP from trades vs VWAP from 1-minute bars
  4. The stocks.agg vw field: exact VWAP without the trades
  5. When VWAP resets
  6. How to calculate VWAP in Python
  7. Anchored VWAP and TWAP vs VWAP
  8. How traders read VWAP, and what it is not
  9. 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.
09:30 to 09:34 ET on US:KO: 35,234,706 dollars of typical-price volume ÷ 400,268 shares = 88.0278.
Minute (ET)HighLowCloseVolumeTypicalCum. volumeVWAP
09:3088.318387.93588.06268,55088.1044268,55088.1044
09:3188.0487.8287.914759,48087.9249328,03088.0719
09:3288.0187.77587.8224,80587.8683352,83588.0576
09:3387.87587.7887.8327,13187.8283379,96688.0412
09:3487.8587.7387.7520,30287.7767400,26888.0278
US:KO 1-minute bars from the REST API, regular session of 2026-09-25.

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
Candles aggregated from 390 one-minute bars; VWAP is the running value at the end of each half hour, times in New York.TickerLayer REST, GET /stocks/agg/US:KO/1/minute/2026-09-25/2026-09-25

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

  • 09:301,430k shares
  • 10:00775k shares
  • 10:30523k shares
  • 11:00374k shares
  • 11:30516k shares
  • 12:00442k shares
  • 12:30327k shares
  • 13:00274k shares
  • 13:30418k shares
  • 14:00652k shares
  • 14:30641k shares
  • 15:00798k shares
  • 15:301,830k shares
The first and last half hours carried 16% and 20% of the day's minute-bar volume, so they pull VWAP hardest.Sum of TickerLayer 1-minute bars, US:KO, 2026-09-25

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 fromVWAP for US:KO, 2026-09-25Difference vs 1-minute
1-minute bars (390)87.8318reference
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 bar87.8944+7.1 bps
Typical-price VWAP from the same minute bars aggregated to coarser sizes. One basis point is 0.01%.

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
}
  1. sessionregular for 09:30 to 16:00 New York; extended-hours bars arrive when you subscribe with session: "extended".
  2. vShares traded in the minute.
  3. nNumber of trades in the minute. May be omitted.
  4. vwThe bar's own VWAP, from its trades. May be omitted; fall back to the typical price.
  5. tsBar start in Unix milliseconds. Bars are published once settled, never while forming.
Documented stocks.agg frame. Numbers arrive as JSON numbers on this channel.

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)

US pre-market04:00 to 09:30 ET
US regularstandard VWAP windowreset at 09:30 ET
US post-market16:00 to 20:00 ET
Cryptocommon conventionreset at 00:00 UTC

Hours in UTC

Whether pre-market trades count is a choice. Decide it once and use it everywhere you compare.

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.

vwap.pyPython
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")
Output
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 shares

For 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.

vwap_stream.pyPython
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.

vwap_trades.pyPython
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.
TWAP sat 2.2 cents above VWAP because the heavy-volume minutes traded lower than the quiet ones.

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.

Keep reading

Ready to integrate?

Start with the free tier, explore the docs, and connect via REST or WebSocket in minutes.