Strasmore Research
Learn Matt ConnorBy Matt Connor

Anchored VWAP Explained: Formula and Uses

Anchored VWAP starts its running average at a bar you choose and never resets. See the formula in plain Python, and what moving the anchor does to the answer.

Anchored VWAP is a volume weighted average price that starts its running totals at one bar you pick and never resets them. Session VWAP starts over at every opening bell, so it only ever answers what the average share has changed hands for today. The anchored version answers a different question: what the average share has changed hands for since the moment you chose as the starting line.

What is anchored VWAP?

Both versions run identical arithmetic. Take every bar since the start, multiply each bar price by that bar volume, and keep a running total. Keep a second running total of the volume on its own. Divide the first by the second. Written out, the anchored VWAP at bar n is the sum of price times volume from the anchor bar through bar n, divided by the sum of volume across those same bars.

The price of a bar is usually its typical price, the average of its high, low and close, or the VWAP of the bar itself when the feed publishes one.

Nothing is ever removed from either total. A 50-day moving average drops its oldest observation every time it takes on a new one, which is what lets it turn. The anchored VWAP only ever adds, so its memory is permanent. Session VWAP is the same calculation with both totals zeroed at every open, which makes it an anchored VWAP whose anchor is the opening bell.

What is the difference between VWAP and anchored VWAP?

The panel below runs both on Apple daily bars for one quarter. The session_vwap column is the volume weighted average price inside each single session, a fresh number every day. The anchored_vwap column starts on the first session of the window and accumulates from there.

QueryDaily session VWAP against a VWAP anchored on one date (AAPL)
The exact SQL behind every number
SELECT
    toString(date)                AS session_date,
    formatDateTime(date, '%b %e') AS bar_label,
    round(toFloat64(vwap), 2)     AS session_vwap,
    round(
        sum(toFloat64(vwap) * toFloat64(volume)) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
      / sum(toFloat64(volume))                   OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
    , 2)                          AS anchored_vwap
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'AAPL'
  AND date >= '2026-04-01'
  AND date <= '2026-06-30'
ORDER BY date
Run this yourself

Across 62 sessions, from Apr 1 to Jun 30, the daily series moves with the tape while the anchored series bends slowly. The anchored line opens at 255.02, since on day one a single session is the whole history, and arrives at 286.86 by the end of the window, against a final session VWAP of 288.09. The shape is the lesson. The anchored line tracks the daily one closely at first, then flattens as the volume behind it piles up.

Where do people put the anchor?

Popular anchors are an earnings release, the first bar of a gap, a 52-week low, or the opening print of a newly listed stock. The claim attached to all of them is the same. Everyone who transacted after that moment paid, on average, the anchored VWAP, so a price above the line describes an average participant sitting at a paper gain.

Here is the part that usually goes unsaid. The anchor is a judgment call, and the answer travels with it. The panel below anchors the same stock at the start of each of the last twelve months and computes the average price paid from that month through one fixed end date.

QueryThe same stock and the same last price, twelve different anchors (AAPL)
The exact SQL behind every number
WITH
    monthly AS (
        SELECT
            toStartOfMonth(date)                     AS m,
            sum(toFloat64(vwap) * toFloat64(volume)) AS pv,
            sum(toFloat64(volume))                   AS vol
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'AAPL'
          AND date >= '2025-07-01'
          AND date <= '2026-06-30'
        GROUP BY m
    ),
    anchored AS (
        SELECT
            m,
            sum(pv)  OVER (ORDER BY m ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
          / sum(vol) OVER (ORDER BY m ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS avwap
        FROM monthly
    ),
    final_close AS (
        SELECT toFloat64(argMax(close, date)) AS last_close
        FROM global_markets.stocks_daily_aggs
        WHERE ticker = 'AAPL'
          AND date >= '2026-06-01'
          AND date <= '2026-06-30'
    )
SELECT
    formatDateTime(a.m, '%b %Y')                 AS anchor_from,
    round(a.avwap, 2)                            AS anchored_vwap,
    round(100 * (f.last_close / a.avwap - 1), 2) AS close_vs_avwap_pct
FROM anchored AS a
CROSS JOIN final_close AS f
ORDER BY a.m
Run this yourself

Anchoring at Jul 2025 puts the average price paid at 259.52, and the last close of the window measures 11.5% against it, a figure that is positive when the close sat above the line. Anchoring at Jun 2026 gives 294.07 and -1.6%. Same stock, same last price, twelve different verdicts on whether holders since the anchor are above water. Nothing in the arithmetic picks the anchor for you. An anchor chosen after the fact can be moved until it supports whatever the chart is meant to say, which is why the anchor date belongs in the caption of every anchored VWAP chart you read.

How do you calculate anchored VWAP in Python?

Both series come out of one pass over the bars. The script below is standard library Python 3, with no packages and no network. It reads thirty bars and prints the session series next to the anchored series, then reports the first bar where price crossed back through the anchored line. The bars are invented for the demonstration. Point the same loop at real bars and the arithmetic is unchanged.

"""Session VWAP and anchored VWAP from the same running totals. Standard library only."""
import csv
import io

# Illustrative bars, not market data: three sessions of ten 30 minute bars.
BARS = """bar,price,volume
2026-03-02 09:30,100.40,240000
2026-03-02 10:00,100.90,180000
2026-03-02 10:30,101.30,150000
2026-03-02 11:00,101.10,130000
2026-03-02 11:30,100.60,120000
2026-03-02 12:00,100.20,110000
2026-03-02 12:30,99.80,120000
2026-03-02 13:00,99.50,140000
2026-03-02 13:30,99.70,170000
2026-03-02 14:00,99.40,260000
2026-03-03 09:30,99.10,300000
2026-03-03 10:00,98.70,210000
2026-03-03 10:30,98.90,160000
2026-03-03 11:00,98.40,150000
2026-03-03 11:30,98.10,140000
2026-03-03 12:00,98.30,120000
2026-03-03 12:30,98.80,130000
2026-03-03 13:00,99.20,150000
2026-03-03 13:30,99.60,190000
2026-03-03 14:00,99.90,280000
2026-03-04 09:30,100.20,320000
2026-03-04 10:00,100.60,230000
2026-03-04 10:30,100.90,170000
2026-03-04 11:00,101.40,160000
2026-03-04 11:30,101.20,140000
2026-03-04 12:00,101.60,130000
2026-03-04 12:30,102.10,140000
2026-03-04 13:00,102.40,160000
2026-03-04 13:30,102.20,200000
2026-03-04 14:00,102.60,290000
"""

ANCHOR = "2026-03-02 10:30"   # the bar the anchored series starts on

session_pv = session_vol = 0.0
anchor_pv = anchor_vol = 0.0
current_day = None
anchored = False
start_side = None
crossover = None

print(f"{'bar':<18}{'price':>8}{'session':>10}{'anchored':>10}")

for row in csv.DictReader(io.StringIO(BARS)):
    stamp = row["bar"]
    day, price, vol = stamp[:10], float(row["price"]), float(row["volume"])

    if day != current_day:                  # the session totals reset at every open
        session_pv = session_vol = 0.0
        current_day = day
    session_pv += price * vol
    session_vol += vol
    session_vwap = session_pv / session_vol

    anchored = anchored or stamp == ANCHOR
    if not anchored:
        print(f"{stamp:<18}{price:>8.2f}{session_vwap:>10.2f}{'.':>10}")
        continue

    anchor_pv += price * vol                # the anchored totals never reset
    anchor_vol += vol
    anchored_vwap = anchor_pv / anchor_vol
    print(f"{stamp:<18}{price:>8.2f}{session_vwap:>10.2f}{anchored_vwap:>10.2f}")

    gap = price - anchored_vwap
    if start_side is None and abs(gap) > 1e-9:
        start_side = gap > 0
    elif start_side is not None and crossover is None and (gap > 0) != start_side:
        crossover = stamp

print("first bar back through the anchored VWAP:", crossover or "no crossing here")

The two pairs of counters are the whole idea. session_pv and session_vol get zeroed whenever the date changes. anchor_pv and anchor_vol never do. Everything after that is printing.

Why the anchored line stops moving

An anchored VWAP gets harder to move as it ages, for a mechanical reason. Each new bar enters the average with a weight equal to its own volume divided by all the volume since the anchor. On the second day, one session can carry half the total. Two hundred sessions later, that same session carries a fraction of one percent. The panel below measures that weight on Apple bars, in blocks of ten sessions.

QueryHow much the newest session can move an anchored VWAP (AAPL)
The exact SQL behind every number
WITH running AS (
    SELECT
        row_number() OVER (ORDER BY date) AS n,
        toFloat64(volume)
      / sum(toFloat64(volume)) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS weight
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'AAPL'
      AND date >= '2026-01-02'
      AND date <= '2026-06-30'
)
SELECT
    intDiv(n - 1, 10) * 10 + 10 AS bars_since_anchor,
    round(100 * avg(weight), 2) AS newest_bar_weight_pct
FROM running
GROUP BY bars_since_anchor
ORDER BY bars_since_anchor
Run this yourself

Over the first ten sessions the incoming bar averages 30.09% of the line, with the anchor bar itself accounting for all of it. By session 130 the newest bar is down to 2.14%. Two things follow when you read these charts. An old anchor draws a nearly flat line that barely responds to what is happening now, and a fresh anchor draws a line that whips around the price and crosses it constantly. Neither is more correct. They are the same formula at different ages. For a direct read on how much volume is arriving relative to normal, average daily volume and relative volume measure it.

What does anchored VWAP claim to show?

The standard claim is a positioning read. With the anchor on an event, the line is the average price paid by everyone who transacted since, so a price above the line describes a majority holding at a paper gain and a price below it the reverse. The panel below anchors five household names at the lowest daily close each of them printed over the past twelve months, then measures the last close of the window against the resulting average.

QueryAnchored at each name's own lowest close of the past twelve months
The exact SQL behind every number
WITH
    bars AS (
        SELECT
            ticker,
            date,
            toFloat64(vwap)   AS px,
            toFloat64(volume) AS vol,
            toFloat64(close)  AS c
        FROM global_markets.stocks_daily_aggs
        WHERE ticker IN ('AAPL', 'MSFT', 'NVDA', 'KO', 'SPY')
          AND date >= '2025-07-01'
          AND date <= '2026-06-30'
    ),
    lows AS (
        SELECT
            ticker,
            argMin(date, c) AS low_date
        FROM bars
        GROUP BY ticker
    )
SELECT
    b.ticker                                AS symbol,
    formatDateTime(l.low_date, '%b %e, %Y') AS anchored_at,
    round(sumIf(b.px * b.vol, b.date >= l.low_date) / sumIf(b.vol, b.date >= l.low_date), 2) AS anchored_vwap,
    round(argMax(b.c, b.date), 2)           AS last_close,
    round(100 * (argMax(b.c, b.date)
        / (sumIf(b.px * b.vol, b.date >= l.low_date) / sumIf(b.vol, b.date >= l.low_date)) - 1), 1) AS close_vs_avwap_pct
FROM bars AS b
INNER JOIN lows AS l ON b.ticker = l.ticker
GROUP BY b.ticker, l.low_date
ORDER BY close_vs_avwap_pct DESC
Run this yourself

The five names spread from SPY at 10.4% down to MSFT at 1.3%, each measured against its own anchor date. That spread describes what happened, and it is worth being precise about the limits. Anchored VWAP is a descriptive statistic computed from prices and volumes that have already printed, not a forecast and not a rule with an established edge in published research. It contains no information the price and volume history does not already hold, and the anchor that produced it was picked by a person. Read as a summary of the tape since a date, it is honest. Read as a verdict, the anchor is doing the talking.

FAQ

What is the difference between VWAP and anchored VWAP?

Session VWAP resets its running totals at every market open, so it only ever describes the current day. Anchored VWAP starts its totals at a bar of your choosing and never resets, so it describes the average price paid across every session since that bar.

Where do traders anchor a VWAP?

Common choices are an earnings date, the first bar of a gap, a swing high or swing low, and the first day of trading for a new listing. Any bar can serve as an anchor, and the anchor date is what a reader has to know to interpret the line.

Does anchored VWAP reset overnight?

No, and that is the defining property. The running sum of price times volume and the running sum of volume both carry across the close, then pick up again on the next session, however many sessions later that is.

Does anchored VWAP predict anything?

It is a descriptive statistic rather than a forecast. It summarizes what has already been paid since the anchor. Moving the anchor moves the line and can reverse the apparent conclusion, so any claim built on one is only as solid as the case for that anchor date.

Can anchored VWAP be calculated on daily bars?

Yes. The panels on this page use daily bars, with each session VWAP as the bar price. Intraday charts use minute bars instead. The formula is identical at either resolution, and only the size of the increments changes.


Every panel above ships with the SQL that produced it, so you can open one and move the anchor date yourself. To run the same running totals from a date you care about, ask for it in plain English on the Strasmore terminal.