Module G · Strategy Playbook - Chapter 30

Strategy: VWAP Intraday System

An intraday mean-reversion / trend system around VWAP, with time-based entries and a session reset.

StrategyBacktest
What you'll learn
  • ·What VWAP is
  • ·Building intraday VWAP
  • ·Session-aware logic
  • ·Entries around VWAP
  • ·Time-based exits
  • ·Intraday backtest notes

Walk onto any intraday trading desk and one line is on every screen: VWAP, the Volume Weighted Average Price. It answers a question a simple moving average cannot - what is the average price everyone actually paid today, weighted by how much traded there? Large institutions measure their own fills against it, so price tends to gravitate toward VWAP and respect it as support or resistance. For a day-trader it is the single most useful reference on the chart: above it the day belongs to the bulls, below it to the bears.

Good to know

VWAP - the volume-weighted average price - is the benchmark institutions grade their own fills against. Beating VWAP is a common trading-desk mandate, which is why intraday traders watch it so closely.

The catch, and the reason VWAP needs care in AFL, is that it must reset every session. Yesterday's volume has no business in today's average. In this chapter we build a session-resetting intraday VWAP, wrap a simple system around it, and - crucially - square off before the close so we are never left holding an intraday position overnight.

What makes VWAP different

A moving average treats every bar equally. VWAP weights each bar by its volume, so a bar where a million shares changed hands moves the line far more than a quiet one. The formula is the running total of price times volume, divided by the running total of volume, measured from the session open:

VWAP = cumulative(typical price * volume) / cumulative(volume)

where the typical price is (High + Low + Close) / 3. The word cumulative is doing the heavy lifting: it must accumulate only since today's first bar, then start fresh tomorrow.

Building a session-resetting VWAP

The trick is to count how many bars have elapsed since the session began, then sum over exactly that many bars. AmiBroker's Sum(array, periods) accepts a variable period - an array - so we feed it the bar count for the day:

// First bar of a new session - the day number changed
newDay    = Day() != Ref(Day(), -1);
barsToday = 1 + BarsSince(newDay);            // bars elapsed since the open

typical = (H + L + C) / 3;                     // typical price of each bar
cumPV   = Sum(typical * Volume, barsToday);    // running price x volume
cumVol  = Sum(Volume, barsToday);              // running volume
VWAP    = cumPV / Max(cumVol, 1);              // session VWAP (Max guards against zero)

BarsSince(newDay) resets to zero at every session open, so barsToday is 1 on the first bar, 2 on the second, and so on - exactly the window Sum needs to accumulate only today's data. The Max(cumVol, 1) is a small safety net so a zero-volume bar never causes a divide-by-zero.

Key idea

The pattern 1 + BarsSince(Day() != Ref(Day(), -1)) is the workhorse of all session-anchored intraday indicators - VWAP, opening ranges, day highs and lows. Learn it once and you can anchor any running calculation to the start of the trading day.

The complete intraday system

Now we trade it. The classic VWAP play is a reclaim: go long when price crosses back above VWAP during the active part of the session, and exit when it loses VWAP again. We confine new entries to a time window with TimeNum(), and we force a square-off near the close so nothing carries overnight.

_SECTION_BEGIN("VWAP Intraday System");

SetChartOptions(0, chartShowArrows | chartShowDates);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}}  O %g  H %g  L %g  C %g  (%.1f%%)",
    O, H, L, C, SelectedValue(ROC(C, 1))));
Plot(Close, "Close", colorDefault, styleCandle | styleNoTitle | GetPriceStyle());

// === Session-resetting VWAP (Indian session opens 09:15) ===
newDay    = Day() != Ref(Day(), -1);
barsToday = 1 + BarsSince(newDay);
typical   = (H + L + C) / 3;
VWAP      = Sum(typical * Volume, barsToday) / Max(Sum(Volume, barsToday), 1);
Plot(VWAP, "VWAP", colorYellow, styleThick);

// === Session time windows (HHMMSS) ===
tn         = TimeNum();
allowEntry = tn >= 093000 AND tn <= 150000;   // take new trades only in this window
squareOff  = tn >= 151500;                     // flatten everything from 15:15

// === Rules: reclaim VWAP to go long, lose it (or time out) to exit ===
rawBuy  = Cross(Close, VWAP) AND allowEntry;
rawSell = Cross(VWAP, Close);

// === Non-repainting one-bar delay; always out by square-off ===
Buy  = Ref(rawBuy,  -1);
Sell = Ref(rawSell, -1) OR squareOff;
Buy  = ExRem(Buy,  Sell);
Sell = ExRem(Sell, Buy);

SetTradeDelays(0, 0, 0, 0);
BuyPrice = SellPrice = Open;
SetPositionSize(100, spsPercentOfEquity);

PlotShapes(IIf(Buy,  shapeUpArrow,   shapeNone), colorLime,   0, Low,  -20);
PlotShapes(IIf(Sell, shapeDownArrow, shapeNone), colorOrange, 0, High,  20);

_SECTION_END();
Tip

There are two opposite ways to trade VWAP, and both are valid. The trend version above buys a reclaim of VWAP and rides the side price is on. The reversion version does the reverse - it fades stretches away from VWAP, buying when price dips well below and betting it snaps back to the line. Pick one deliberately; trying to do both at once just cancels out. Swap the Cross rules to flip from trend to reversion.

Why the time logic matters

TimeNum() returns the bar's time as a plain number in HHMMSS form, so 093000 means 9:30 am and 151500 means 3:15 pm. We use it twice. The allowEntry window keeps us from opening fresh positions in the first frantic minutes after the open or in the dying minutes before the close. The squareOff flag is non-negotiable for an intraday system: by OR-ing it into Sell, we guarantee any open long is closed from 3:15 pm onward, so the backtest never simulates holding overnight - which would quietly turn an intraday strategy into something else entirely.

Heads up

Adjust the times to your instrument. Indian equities and index futures run 09:15 to 15:30, so a 15:15 square-off leaves a comfortable cushion. Commodities like CRUDEOIL or GOLD trade much later - square those off near their own close, not the equity close.

Intraday backtest notes

Two settings make or break an intraday backtest. First, periodicity: in the Analysis window's settings, set the backtest to the same intraday interval as your data - 5-minute or 15-minute - not Daily, or the session logic collapses. Second, costs: intraday systems trade often, and brokerage plus slippage compound fast. Put realistic figures into the backtest settings; a strategy that looks brilliant at zero cost can bleed out once each round trip pays its toll.

Note

Backtests of intraday systems are only as good as your intraday history. Make sure your data has clean, continuous bars for the full session, with no gaps or duplicated timestamps, before you trust the numbers - and always rehearse the live version in sandbox trading (analyzer mode in OpenAlgo) first. This is education, not advice.

Try it yourself

  • Run the system on RELIANCE 5-minute data and confirm the VWAP line truly resets each morning.
  • Tighten the entry window to 103000-140000 and see whether avoiding the open and close improves results.
  • Build the reversion variant: buy when Close is more than 0.5% below VWAP, exit on a touch of VWAP.
  • Move the square-off to 152000 and check how much the last few minutes change the report.

Recap

  • VWAP is the volume-weighted average price since the session open - the benchmark intraday traders watch for support, resistance and bias.
  • Reset it each day with barsToday = 1 + BarsSince(Day() != Ref(Day(), -1)) and a variable-period Sum.
  • TimeNum() gates entries to a sensible window and forces a square-off before the close so nothing carries overnight.
  • Choose a trend (reclaim) or reversion (fade) stance, not both.
  • Backtest at the correct intraday periodicity with realistic costs, and rehearse in sandbox first.

Next we round out the playbook with two breakout classics - the Donchian channel and the opening-range breakout.