Opening checkout

Trading strategies7 min read

How to Backtest a VWAP Trading Strategy in Python

Learn how to backtest a VWAP trading strategy in Python. Step-by-step guide with code and results explained.

By QuantVPS Team

Published
Updated
How to Backtest a VWAP Trading Strategy in Python

The demand for systematic and data-driven trading strategies has grown exponentially among professional traders in the futures and forex markets. Among the myriad of tools available to modern traders, the Volume-Weighted Average Price (VWAP) indicator has become a cornerstone for identifying trends and generating trading signals. But how do you use it to develop and test a trading strategy? This article delves into the details of backtesting a VWAP trading strategy using Python, building on key insights from a detailed walkthrough of a systematic approach.

In this step-by-step guide, we’ll break down the strategy’s core concepts, the Python implementation process, and the results of the backtesting exercise. By the end of this article, you’ll have a deeper understanding of how VWAP can be integrated into your trading toolkit, along with actionable insights to refine your own strategies.

What Is the VWAP Strategy?

The VWAP (Volume-Weighted Average Price) indicator serves as a benchmark to assess the average price of an asset, weighted by volume, over a certain time period. Traders commonly use it to identify intraday price trends and make decisions based on the relative position of the price to the VWAP.

Core Concept of the Strategy

This VWAP strategy focuses on short-term price trends using a 15-minute timeframe. The premise is simple:

  • Price above VWAP: Indicates a short-term uptrend, signaling potential long entries.
  • Price below VWAP: Indicates a short-term downtrend, signaling potential short entries.

The strategy combines these VWAP signals with additional filters, including:

  1. Candle color: Green candles (closing price above opening price) confirm bullish momentum, while red candles (closing price below opening price) confirm bearish momentum.
  2. Daily opening price: Trades are further validated by comparing the current price to the day’s opening price.

Furthermore, stop-losses are dynamically set based on the Average True Range (ATR), ensuring they adapt to market volatility.

Uptime

99.999% uptime. Built for 24/7 trading reliability.

Your VPS runs in Tier IV datacenters with redundant power and network, monitored 24/7 on site.

See the uptime SLA
A dot-matrix heatmap resolves into the 99.999% uptime SLA figure. An illustration, not live status.

Performance Highlights

The source video backtests these rules on three years of 15-minute data. Its headline numbers depend on the asset, the test period and the fees assumed, so this guide gives you the full code to run the same test on your own data.

Building the VWAP Strategy in Python: A Step-by-Step Guide

Python homepage, October 2026

Implementing the VWAP strategy in Python involves a systematic approach, leveraging libraries like pandas and backtesting frameworks. Below is a breakdown of the process:

1. Load Historical Data

To begin, you’ll need historical price data for the desired asset. The example uses a 15-minute timeframe for assets such as Apple stock, Bitcoin, and Tesla.

import pandas as pd

# Load 15-minute bars with a datetime index and Open, High, Low, Close, Volume columns
data = pd.read_csv('historical_data.csv', index_col=0, parse_dates=True)

2. Calculate the VWAP

Instead of using pre-built technical analysis libraries, the VWAP function is defined by hand, so you control when it resets. The version below uses the typical price, (High + Low + Close) / 3, and starts a new running total at the first bar of each trading day.

def calculate_vwap(data):
    # Typical price for each bar
    typical = (data['High'] + data['Low'] + data['Close']) / 3
    day = data.index.date
    # Running totals that restart at the first bar of each day
    data['cum_price_volume'] = (typical * data['Volume']).groupby(day).cumsum()
    data['cum_volume'] = data['Volume'].groupby(day).cumsum()
    data['vwap'] = data['cum_price_volume'] / data['cum_volume']
    return data

3. Define Entry and Exit Rules

The strategy generates trading signals based on VWAP, candle color, and daily opening price:

  • Long Entry: Price above VWAP, green candle, and price above daily open.
  • Short Entry: Price below VWAP, red candle, and price below daily open.

Trailing stop-losses are based on 1.5x ATR, dynamically adjusting to market volatility.

if (close_price > vwap) and (close_price > day_open) and (candle_color == 'green'):
    # Execute long trade
    do_buy()

DDoS protection

Always-on DDoS protection with advanced filtering. On every plan.

Always-on mitigation filters attack traffic at the network edge. Every plan includes a dedicated IP and unmetered bandwidth for the trading environment.

See every security layer
A flood of traffic meets three filter layers at the network edge. Only clean packets reach your VPS.

4. Backtest the Strategy

The full example below uses the open-source backtesting.py package (pip install backtesting). It applies the entry rules from step 3, a 1.5x ATR trailing stop, a flat position before the session ends and a commission on every fill.

import pandas as pd
from backtesting import Backtest
from backtesting.lib import TrailingStrategy

# 15-minute bars: a datetime index plus Open, High, Low, Close and Volume columns
data = pd.read_csv('historical_data.csv', index_col=0, parse_dates=True)

def session_vwap(df):
    # Typical price, reset at the start of each trading day
    typical = (df['High'] + df['Low'] + df['Close']) / 3
    day = df.index.date
    cum_pv = (typical * df['Volume']).groupby(day).cumsum()
    cum_vol = df['Volume'].groupby(day).cumsum()
    return cum_pv / cum_vol

def daily_open(df):
    return df['Open'].groupby(df.index.date).transform('first')

class VwapStrategy(TrailingStrategy):
    atr_mult = 1.5  # trailing stop distance in ATRs

    def init(self):
        super().init()
        self.set_atr_periods(14)
        self.set_trailing_sl(self.atr_mult)
        df = self.data.df
        self.vwap = self.I(lambda: session_vwap(df).values, name='VWAP')
        self.day_open = self.I(lambda: daily_open(df).values, name='Day open')

    def next(self):
        super().next()
        bar_time = self.data.index[-1]
        close, open_ = self.data.Close[-1], self.data.Open[-1]

        # Flat before the session ends (last 15-minute bar starts at 15:45)
        if bar_time.hour == 15 and bar_time.minute >= 45:
            self.position.close()
            return

        if not self.position:
            if close > self.vwap[-1] and close > self.day_open[-1] and close > open_:
                self.buy()
            elif close < self.vwap[-1] and close < self.day_open[-1] and close < open_:
                self.sell()

# commission=0.0005 charges 0.05% of order value on every fill
bt = Backtest(data, VwapStrategy, cash=100_000, commission=0.0005,
              exclusive_orders=True, finalize_trades=True)
stats = bt.run()
print(stats[['Return [%]', 'Buy & Hold Return [%]', 'Max. Drawdown [%]',
             'Win Rate [%]', '# Trades']])

# Same rules with no commission, to see how much the fees cost
print(Backtest(data, VwapStrategy, cash=100_000, commission=0,
               exclusive_orders=True, finalize_trades=True).run()['Return [%]'])

A few notes on the code. TrailingStrategy moves the stop loss to 1.5 ATRs behind the price on every bar, and set_atr_periods(14) uses a 14-bar ATR. The commission argument is a share of order value, and backtesting.py charges it at entry and again at exit. The 15:45 check assumes US stock hours in exchange time, so change it for futures, forex or crypto.

The stats object also reports annual return, volatility, Sharpe ratio and the full trade list in stats._trades. Call bt.plot() to see each trade on the chart.

Results and Observations

The source video reports high volatility and a substantial drawdown for this rule set, so read these numbers first when you run the code on your own data:

Key Metrics:

  1. Return vs buy and hold: Return [%] next to Buy & Hold Return [%] shows whether the rules beat simply holding the asset.
  2. Drawdown: Max. Drawdown [%] shows the deepest fall from a peak, which tells you whether you could live with the strategy.
  3. Win rate and trade count: Win Rate [%] and # Trades show how often the rules win and trade, which drives your total fees.

Sensitivity to Commissions

One notable finding was the strategy’s sensitivity to trading fees. For instance, at 0.05% commission per fill, each round trip costs 0.1% of the position. If the average trade earns 0.08% before costs, the same signals lose money after fees. The example code runs the backtest with and without commission so you can see this gap on your own data.

This underscores the importance of minimizing trading costs when using high-frequency strategies on lower timeframes.

Optimizing the Strategy

The strategy’s performance can be further refined by experimenting with key parameters:

  1. ATR Coefficient: Testing values between 0.5 and 2.5 revealed that a coefficient of 1.5 strikes the best balance between risk and reward.
  2. VWAP Reset Frequency: While the example used a daily VWAP, experimenting with weekly resets could improve results on higher timeframes.
  3. Condition Filtering: Removing the green/red candle condition yielded better backtest results, suggesting a simpler entry rule might be more effective.

Strengths and Limitations of the Strategy

Strengths:

  • Clear Entry/Exit Criteria: The combination of VWAP, daily opening price, and ATR provides filters for identifying trends.
  • Day Trading Focus: Closing all trades by the end of the day avoids overnight fees and reduces exposure to gapping risks.
  • Customizability: The Python implementation allows for extensive parameter tweaking and experimentation.

Order flow

Built for scalpers. Heatmaps on dedicated resources.

Order-flow heatmaps and DOM ladders run on dedicated CPU, memory and NVMe, so your charts keep responding during volatility.

See the dedicated hardware
Order flow through a news release: resting bids and offers paint in as a heatmap beside the DOM ladder, then a burst of buys takes the offer wall. Illustrative; prices are decorative.

Limitations:

  • High Sensitivity to Fees: The reliance on intraday trades makes the strategy vulnerable to trading costs.
  • Limited to Certain Markets: The strategy’s effectiveness depends on the liquidity and volatility of the chosen asset.

Key Takeaways

  • VWAP as a Trend Indicator: Using VWAP alongside daily opening prices provides a reliable method for detecting intraday trends.
  • Dynamic Stop-Losses: ATR-based trailing stops ensure the strategy adapts to market volatility.
  • Parameter Tuning: Experimenting with ATR coefficients and VWAP reset frequencies can optimize performance.
  • Cost Management: For intraday strategies, minimizing fees is critical to preserving profitability.

Conclusion

Backtesting a VWAP trading strategy in Python showcases the power of systematic, data-driven approaches in futures and forex markets. By understanding how to integrate indicators like VWAP and ATR into your trading logic, you can develop strategies for your trading style. However, always keep in mind the potential pitfalls, such as sensitivity to trading costs and the importance of rigorous backtesting.

This strategy is a starting point, offering traders a framework to build upon and refine. The key to success lies in continuous experimentation, optimization, and adaptation to ever-changing market conditions. Happy trading!

For an always-on trading workspace, QuantVPS can host the platform while your local computer is off; use only automation that your broker or prop firm permits.

Source: "A Simple VWAP Rule Beats Buy & Hold | Full Backtest In Python" - CodeTrading, YouTube, Aug 7, 2025 - https://www.youtube.com/watch?v=e9IDuLESYv8

Use: Embedded for reference. Brief quotes used for commentary/review.

Share this article

Disclaimer: QuantVPS does not represent, guarantee, support, or endorse any third-party brands, products, or services mentioned in this article. All brand references are for informational purposes only. This information does not constitute a recommendation to trade futures or any other financial instruments. All trading decisions are made at your own discretion. Please be aware that futures trading involves significant risk of loss, and past performance does not guarantee future results. Read our full Brand Non-Endorsement Disclaimer.

Risk Disclosure: QuantVPS does not provide financial, investment, or trading advice. Trading involves substantial risk of loss and is not suitable for every investor. Past performance is not indicative of future results. You should consult a qualified financial advisor before making any trading decisions. Read our full Trading Disclaimer.