The Volume-Weighted Average Price (VWAP) is a widely used technical indicator in technical analysis. It calculates the average price of a security, weighted by the volume of trades during a specific period. The central concept of VWAP is that it provides traders with the instrument’s fair value, combining both price and volume data for better market insights.
The Maths!
To better understand what VWAP is, let’s look closely at the mathematical formula. First, we need to define a typical price for the instrument for each candle. The most common way to do that is the following:

As you can understand, we take the average of the candle’s High, Low, and Close. There are also other ways to calculate this, like only the average of the high and low, the average of all ohlc prices, or the weighted close, which means high, low and close multiplied by 2. However, we can safely assume that hlc/3 is the most common.
Knowing the typical price now, it is time to put the volume in the game. the formula is as follows:

It is quite simple mathematically, but let’s provide two extreme examples so we understand it better:
Example A
- Day 1 typical price:$100 and volume 200
- Day 2 typical price:$150 and volume 50
The calculation of VWAP is (100*200)+(150*50) / (200+50): The fair value according to VWAP is $110
Example B
- Day 1 typical price:$100, and volume 50
- Day 2 typical price $150, and volume 200
The calculation of VWAP is (100*50)+(150*200) / (200+50) : The fair value according to VWAP is $140
As you can see in the example, the instrument’s typical price on day two is the same, but the fair value is entirely different if you switch the daily volumes. This is because, in example A, more trades were executed at 100, so the fair value is 110, while in example B, more at 150, so the fair value is 140.
Practically VWAP weights more the transactions for a specific price
Where can you use VWAP?
- Benchmarking Trade Execution: Institutional traders use VWAP to evaluate trade performance. Executing trades below the VWAP indicates better-than-average pricing, while trades above it suggest higher costs.
- Trend Confirmation: Traders often use VWAP to confirm market trends. A stock price above VWAP may indicate bullish sentiment; if it is below, bearish sentiment.
- Support and Resistance Levels: VWAP acts as a dynamic support or resistance line in intraday trading. Prices bouncing off VWAP can signal potential entry or exit points.
- Algorithmic Trading: Many algorithmic strategies are designed to execute trades close to or better than VWAP to minimize market impact and optimize execution costs
- Market Impact Reduction: Large institutional orders are often broken into smaller trades executed around the VWAP to avoid significant price disruptions
Note that VWAP needs high liquidity instruments for accurate analysis, while it can work with any type of instrument like equities, commodities, FOREX, Crypto — you name it…
Let’s code!
First, we need to get our data. I will use FMP’s API with the free plan, which will be more than enough for our analysis. Also, I will use one of my favourite Python modules, requests_cache, to limit my calls during testing and increase performance. You can check my article below to understand how this can be done.
Stop Overpaying for Slow API Calls! Cache Your Results, Save Time, and Cut Costs
How often do you request the same API over and over again? Learn how caching responses can save time and reduce costs.
First, I will do the imports and define a function that will be used to call the API and get the data.
import pandas as pd
import requests_cache
import requests
from datetime import datetime, timedelta
import mplfinance as mpf
import configparser
import warnings
warnings.filterwarnings('ignore')
requests_cache.install_cache('mycache')
def get_api_key(config_file='config.ini', section='api', key='key'):
config = configparser.ConfigParser()
# Read the configuration file
config.read(config_file)
try:
# Retrieve and return the API key
return config.get(section, key)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
print(f"Error reading {key} from {config_file}: {e}")
return None
token = get_api_key()
Then, I will run a script to get the 15-minute candles looping month by month. The reason is that the API has a limit on what it returns (for 15-minute candles, it returns a bit more than a month), so I cannot use one API call to get the entire year in one call.
def get_stock_ohlc(instrument, timeframe, from_date, to_date):
url = f'https://financialmodelingprep.com/api/v3/historical-chart/{timeframe}/{instrument}?from={from_date}&to={to_date}'
querystring = {"apikey":token}
resp = requests.get(url, querystring).json()
df = pd.DataFrame(resp)
return df
instrument = 'AAPL'
timeframe = "15min" # Replace with your desired timeframe (e.g., '1min', '5min', '1hour')
year = 2024
df = pd.DataFrame() # Initialize an empty DataFrame to store combined results
for month in range(1, 13): # Loop through months from January (1) to December (12)
# Define the start and end dates for each month
start_date = datetime(year, month, 1)
end_date = (start_date + timedelta(days=31)).replace(day=1) - timedelta(days=1)
# Convert dates to string format 'YYYY-MM-DD'
from_date = start_date.strftime('%Y-%m-%d')
to_date = end_date.strftime('%Y-%m-%d')
# Call the function for the current month's data
monthly_data = get_stock_ohlc(instrument, timeframe, from_date, to_date)
# Combine the monthly data into the overall DataFrame
df = pd.concat([df, monthly_data], ignore_index=True)
df['date'] = pd.to_datetime(df['date'])
df.sort_values(by='date', inplace=True)
df.set_index('date', inplace=True)
df

Now that we have the 15-minute candles for an entire year, I will define one more function to plot candles and VWAP and save some code as we will analyse VWAP.
def plot_VWAP(df, plot_series_list, title ="VWAP", day_change_vlines=False):
if 'date' in df.columns:
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
# Create additional plots
list_of_colors = ['blue', 'red', 'orange', 'green']
plots = []
for i, series in enumerate(plot_series_list):
plots.append(mpf.make_addplot(df[series], color=list_of_colors[i], linestyle='--'))
# Identify date changes for vertical lines
date_changes = df.index.date[:-1] != df.index.date[1:]
if day_change_vlines:
vertical_dates = df.index[1:][date_changes]
else:
vertical_dates = []
# Convert to list of datetime objects for vlines
vlines = list(vertical_dates)
# Plot with mplfinance
mpf.plot(
df,
type='candle',
style='charles',
addplot=plots,
title=title,
ylabel="Price",
volume=True,
vlines=vlines, # Pass the list of datetime objects directly
)
VWAP Continuous
I will start from VWAP continuous, which does not make sense, and I will only mention it for demonstration purposes to show its one-liner calculation and to show you a trap that, while calculating, you can fall into. As you will see below in the plot, for VWAP to work, it needs to have a period on which it will perform the calculations. In the case below, it started being calculated when our data started.
Unfortunately I saw a lot of articles just mentioning the formula without getting into the logic of when we start the calculation, so I felt obliged to explain — even though for some of you this is obvious and known.
df_to_plot = df.copy()
df_to_plot['VWAP'] = (((df_to_plot['high'] + df_to_plot['low'] + df_to_plot['close']) / 3) * df_to_plot['volume']).cumsum() / df_to_plot['volume'].cumsum()
window_size = 100
std_multiplier = 2
df_to_plot['cumulative_std'] = [df_to_plot['VWAP'][:i+1].std() for i in range(len(df_to_plot))]
df_to_plot['Upper_Channel'] = df_to_plot['VWAP'] + (df_to_plot['cumulative_std']*std_multiplier)
df_to_plot['Lower_Channel'] = df_to_plot['VWAP'] - (df_to_plot['cumulative_std']*std_multiplier)
# plot_VWAP(df_to_plot, ['VWAP','Upper_Channel','Lower_Channel'])
plot_VWAP(df_to_plot, ['VWAP','Upper_Channel','Lower_Channel'], title="VWAP Continuous")

VWAP with Daily Reset
This VWAP is the most common one. Practically, it resets every day. As you will notice in the code, I group each day and calculate VWAP autonomously. In the plot, notice the blue line. This is where the day starts (VWAP is almost equal to the price at this point) and then widens.
Another thing you should notice is the channel of the standard deviation. Similar to many other technical indicators that we try to identify a channel in which we assume that the price will be bouncing in, same with VWAP the most common way is to calculate a standard deviation and plot a channel based on this. In this case, I have plotted one standard deviation up and down, but feel free to change the multiplier according to your strategy.
Use Cases:
- Intraday trading to identify fair value.
- Evaluating price trends and momentum within a single trading session.
- Establishing dynamic support and resistance levels.
def calculate_daily_vwap(df):
# Group by instrument and date
def calculate_vwap(group):
group['VWAP'] = (((group['high'] + group['low'] + group['close']) / 3) * group['volume']).cumsum() / group['volume'].cumsum()
std_multiplier = 2
# Calculate cumulative standard deviation for each row
group['cumulative_std'] = [group['VWAP'][:i+1].std() for i in range(len(group))]
group['Upper_Channel'] = group['VWAP'] + (group['cumulative_std']*std_multiplier)
group['Lower_Channel'] = group['VWAP'] - (group['cumulative_std']*std_multiplier)
return group
# Apply VWAP calculation per date
return df.groupby(df.index.to_series().dt.date, group_keys=False).apply(calculate_vwap)
df_to_plot = df.copy()
df_to_plot = calculate_daily_vwap(df_to_plot)
plot_VWAP(df_to_plot.tail(50), ['VWAP','Upper_Channel','Lower_Channel'], title="VWAP with Daily Reset", day_change_vlines=True)

Rolling VWAP
Another way to use VWAP is to use a rolling logic. This means that at any point, we are calculating the VWAP based on a predefined amount of candles (14 in my example below). Imagine it like a moving average, where you calculate the average close for the last x candles.
Use Cases:
- Short-term trend analysis.
- Suitable for scalping or momentum strategies.
- It helps smooth out noise in volatile markets.
df_to_plot = df.copy()
# Define the rolling window size
rolling_window = 14
std_multiplier = 2
# Calculate the Rolling VWAP
df_to_plot['TPV'] = ((df_to_plot['high'] + df_to_plot['low'] + df_to_plot['close']) / 3) * df['volume'] # Typical Price Volume
df_to_plot['Rolling_TPV'] = df_to_plot['TPV'].rolling(window=rolling_window).sum() # Rolling sum of TPV
df_to_plot['Rolling_Volume'] = df_to_plot['volume'].rolling(window=rolling_window).sum() # Rolling sum of Volume
df_to_plot['VWAP'] = df_to_plot['Rolling_TPV'] / df_to_plot['Rolling_Volume'] # Rolling VWAP
df_to_plot['rolling_std'] = df_to_plot['VWAP'].rolling(window=rolling_window).std()
df_to_plot['Upper_Channel'] = df_to_plot['VWAP'] + (df_to_plot['rolling_std']*std_multiplier)
df_to_plot['Lower_Channel'] = df_to_plot['VWAP'] - (df_to_plot['rolling_std']*std_multiplier)
plot_VWAP(df_to_plot.tail(100), ['VWAP','Upper_Channel','Lower_Channel'], title="VWAP with Moving or Rolling Window", day_change_vlines=True)

Moving VWAP
This is the time to combine the daily reset VWAP with a rolling window VWAP. The Moving VWAP resets every day, however it will not be using the whole daily session for its calculation but a rolling period (in our case I put it as five candles).
Use Cases:
- Intraday strategies require both local and session-wide insights.
- Identifying short-term reversals or breakouts within daily trading.
def calculate_daily_vwap(df):
# Group by instrument and date
def calculate_vwap(group):
# Define the rolling window size
rolling_window = 5
# Calculate the Rolling VWAP
group['TPV'] = ((group['high'] + group['low'] + group['close']) / 3) * group['volume'] # Typical Price Volume
group['Rolling_TPV'] = group['TPV'].rolling(window=rolling_window).sum() # Rolling sum of TPV
group['Rolling_Volume'] = group['volume'].rolling(window=rolling_window).sum() # Rolling sum of Volume
group['VWAP'] = group['Rolling_TPV'] / group['Rolling_Volume'] # Rolling VWAP
# group['VWAP'] = (((group['high'] + group['low'] + group['close']) / 3) * group['volume']).cumsum() / group['volume'].cumsum()
std_multiplier = 2
# Calculate cumulative standard deviation for each row
group['cumulative_std'] = [group['VWAP'][:i+1].std() for i in range(len(group))]
group['Upper_Channel'] = group['VWAP'] + (group['cumulative_std']*std_multiplier)
group['Lower_Channel'] = group['VWAP'] - (group['cumulative_std']*std_multiplier)
return group
# Apply VWAP calculation per date
return df.groupby(df.index.to_series().dt.date, group_keys=False).apply(calculate_vwap)
df_to_plot = df.copy()
df_to_plot = calculate_daily_vwap(df_to_plot)
df_to_plot
plot_VWAP(df_to_plot.tail(50), ['VWAP','Upper_Channel','Lower_Channel'], title="VWAP with Moving Window", day_change_vlines=True)

Anchored VWAP
Anchored VWAP is quite interesting. To understand the logic, you need to remember that VWAP is trying to calculate a fair value of the instrument. Can transactions with missing information be compared with transactions with this essential info? For example (as you will see in the below code), Apple announced its results on the 1st of October 2024. Does it make sense to calculate a fair price on the 2nd of October, including prices from the 30th of September when nobody knew the results of the stock? That is why we start the calculation from this event onwards when we have major events. This event is our anchor.
Use Cases:
- Event-driven trading strategies (e.g., earnings, news).
- Assessing fair value after significant market events.
- Long-term trend analysis starting from key turning points.
df_to_plot = df.copy()
anchor_date = pd.to_datetime('2024-10-01')
filtered_df = df_to_plot.loc[anchor_date:]
# Calculate VWAP for the filtered DataFrame
filtered_df['VWAP'] = (((filtered_df['high'] + filtered_df['low'] + filtered_df['close']) / 3) * filtered_df['volume']).cumsum() / filtered_df['volume'].cumsum()
std_multiplier = 2
# Calculate cumulative standard deviation for each row
filtered_df['cumulative_std'] = [filtered_df['VWAP'][:i+1].std() for i in range(len(filtered_df))]
filtered_df['Upper_Channel'] = filtered_df['VWAP'] + (filtered_df['cumulative_std']*std_multiplier)
filtered_df['Lower_Channel'] = filtered_df['VWAP'] - (filtered_df['cumulative_std']*std_multiplier)
df_to_plot.loc[filtered_df.index, 'VWAP'] = filtered_df['VWAP']
df_to_plot.loc[filtered_df.index, 'Upper_Channel'] = filtered_df['Upper_Channel']
df_to_plot.loc[filtered_df.index, 'Lower_Channel'] = filtered_df['Lower_Channel']
# Convert the index to a DatetimeIndex if it's not already
if not isinstance(df_to_plot.index, pd.DatetimeIndex):
df_to_plot.index = pd.to_datetime(df_to_plot.index, errors='coerce')
df_to_plot.sort_index()
# Filter the data efficiently without using the removed `append()` method
filtered_df = pd.concat([
df_to_plot[df_to_plot.index < anchor_date].tail(20),
# df_to_plot.loc[:anchor_date].iloc[-10:], # Last 10 rows before or at anchor_date
df_to_plot[df_to_plot.index >= anchor_date].head(50) # First 50 rows after anchor_date
])
plot_VWAP(filtered_df, ['VWAP','Upper_Channel','Lower_Channel'], title="VWAP with Anchored Window", day_change_vlines=True)

How to use this article
This article comprehensively guides understanding and applying various VWAP (Volume-Weighted Average Price) calculations. Besides, as an educational resource, it can be used in the following ways:
- Trading Strategy Development: Identify which VWAP type aligns with specific trading goals, such as intraday trading, trend analysis, or event-driven strategies.
- Practical Implementation: Use the provided Python code snippets to calculate and visualize VWAP for any instrument or timeframe.
- Trend Confirmation and Analysis: Apply VWAP to confirm market trends and determine dynamic support/resistance levels in real-time trading.
- Algorithmic Trading: Leverage VWAP insights to optimize trade execution and minimize market impact through automated strategies.
Conclusion
Understanding VWAP types will help you make informed decisions for your strategies and all market conditions. You can enhance precision and profitability by integrating these tools into your trading arsenal. Remember, “In trading, knowledge isn’t just power — it’s profit.”
Thank you for reading, and I hope you enjoyed it. If you did, I would propose to add it in your lists for future reference.
You can find the jupyter notebook with the Python code here.