
The investment universe consists of QQQ [and/or TQQQ (a leveraged and liquid ETF that gives traders a 3x exposure to the daily fluctuation of QQQ), two] very liquid ETF[s] tracking the famous Nasdaq100 index.
ASSET CLASS: stocks | REGION: United States | FREQUENCY:
Intraday | MARKET: equities | KEYWORD: VWAP, Indicator, Day-Traders
I. STRATEGY IN A NUTSHELL
Trade QQQ (or TQQQ) intraday using a VWAP-based trend system. Enter long if the first 1-min candle closes above VWAP, short if below. Stop-loss triggered by candle crossing VWAP. Positions closed at day’s end.
II. ECONOMIC RATIONALE
VWAP captures intraday market trends and liquidity. The strategy exploits statistically validated short-term price patterns, enhancing intraday execution, risk-adjusted returns, and algorithmic trading efficiency.
III. SOURCE PAPER
Volume Weighted Average Price (VWAP) The Holy Grail for Day Trading Systems [Click to Open PDF]
Carlo Zarattini, Concretum Research; Andrew Aziz, Peak Capital Trading; [Next Author], Bear Bull Traders
<Abstract>
This paper explores the application of the Volume Weighted Average Price (VWAP) in detecting market imbalances and enhancing trading decisions across diverse market conditions. We introduce a straightforward VWAP-based day trading strategy, which initiates long positions when price is above the VWAP and short positions when it falls below the VWAP.
Our analysis employs QQQ and TQQQ as primary trading instruments, covering the period from January 2, 2018, to September 28, 2023. This timeframe includes two bear markets and multiple high-volatility events, providing a comprehensive test of market variations. Our findings reveal that an initial investment of $25,000 in the VWAP Trend Trading strategy with QQQ would have grown to $192,656, net of commissions, yielding a 671% return.
This performance is marked by a maximum drawdown of just 9.4% and a Sharpe Ratio of 2.1. In contrast, a passive buy-and-hold strategy in QQQ during the same period would have returned 126%, with a significantly higher maximum drawdown of 37% and a lower Sharpe Ratio of 0.7. Further enhancing our strategy with TQQQ (3x leveraged ETFs of QQQ), we observed extraordinary outcomes: a $25,000 investment surged to $2,085,417, net of commissions. This equates to an 8,242% total return, or an average annual return of 116%, maintaining a maximum drawdown comparable to the passive QQQ strategy. Although we do not regard it as a fully developed trading system, our findings highlight VWAP’s potential as a powerful tool for active traders and investors, emphasizing its superiority over standard buy-and-hold approaches in terms of profitability, risk-adjusted returns, and resilience during market fluctuations.


IV. BACKTEST PERFORMANCE
| Annualised Return | 43% |
| Volatility | 18% |
| Beta | -0.001 |
| Sharpe Ratio | 2.39 |
| Sortino Ratio | 0.511 |
| Maximum Drawdown | -9.4% |
| Win Rate | 17% |
V. FULL PYTHON CODE
from AlgorithmImports import *
import numpy as np
# endregion
class VolumeWeightedAveragePriceVWAPAsPreciseTrendFollowingIndicatorForDayTraders(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2018, 1, 1)
self.SetCash(100000)
self.symbol: Symbol = self.AddEquity("QQQ", Resolution.Minute).Symbol
self.Securities[self.symbol].SetFeeModel(ConstantFeeModel(0))
# self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.VWAP: IntradayVwap = IntradayVwap(self.symbol)
self.traded_weight: float = 1.
self.trade_flag: bool = False
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.OnMarketOpen)
self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol, 1), self.BeforeMarketClose)
def OnData(self, data: Slice) -> None:
# trading allowed only after open market
if not self.trade_flag:
return
# update VWAP indicator
bar: TradeBar = data.Bars.get(self.symbol)
self.VWAP.Update(bar)
# trade execution
if self.VWAP.IsReady:
if data[self.symbol].Close > self.VWAP.Current.Value:
if not self.Portfolio[self.symbol].IsLong:
self.SetHoldings(self.symbol, self.traded_weight)
else:
if not self.Portfolio[self.symbol].IsShort:
self.SetHoldings(self.symbol, -self.traded_weight)
def OnMarketOpen(self) -> None:
self.trade_flag = True
def BeforeMarketClose(self) -> None:
self.Liquidate()
self.VWAP.Reset()
self.trade_flag = False
VI. Backtest Performance