Volume Weighted Trade Analysis
Analyze volume-weighted average trade price distributions to identify specific price levels where significant transaction volume was concentrated, revealing potential institutional accumulation and distribution zones and high-volume nodes of market participant interest.
Volume Weighted Trade Analysis
Volume Weighted Trade Analysis (VWTA) is a critical concept in financial markets, especially for understanding the average price at which a stock or asset has traded over a specific period, considering the volume of transactions at each price point. It provides a more accurate representation of the market's consensus price than a simple average price, as it gives more weight to prices where more shares were traded.
Two primary metrics under VWTA are:
- Volume Weighted Average Price (VWAP): A benchmark used by institutional traders to evaluate their execution quality, aiming to buy below VWAP and sell above VWAP.
- Time Weighted Average Price (TWAP): A benchmark primarily used for executing large orders over a specific period, designed to minimize market impact by spreading trades evenly over time.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import datetime
# Set a random seed for reproducibility
np.random.seed(42)Mock Data Generation
To demonstrate Volume Weighted Trade Analysis, we'll generate mock trade data. This data will include timestamp, price, and volume for a series of hypothetical trades.
# Generate mock trade data
start_time = pd.Timestamp('2023-01-01 09:00:00')
trade_data = []
current_price = 100.0
for i in range(100):
timestamp = start_time + pd.Timedelta(minutes=i)
# Simulate price movement
price_change = np.random.normal(0, 0.5) # Small random walk
current_price += price_change
current_price = max(90.0, min(110.0, current_price)) # Keep price within bounds
volume = np.random.randint(100, 1000) # Random volume between 100 and 1000
trade_data.append({'timestamp': timestamp, 'price': current_price, 'volume': volume})
df = pd.DataFrame(trade_data)
print("Mock Trade Data Head:")
display(df.head())
print("\nMock Trade Data Info:")
df.info()Mock Trade Data Head:
| timestamp | price | volume | |
|---|---|---|---|
| 0 | 2023-01-01 09:00:00 | 100.248357 | 206 |
| 1 | 2023-01-01 09:01:00 | 100.179225 | 171 |
| 2 | 2023-01-01 09:02:00 | 99.623285 | 566 |
| 3 | 2023-01-01 09:03:00 | 99.782736 | 314 |
| 4 | 2023-01-01 09:04:00 | 100.572342 | 761 |
Mock Trade Data Info: <class 'pandas.core.frame.DataFrame'> RangeIndex: 100 entries, 0 to 99 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 timestamp 100 non-null datetime64[ns] 1 price 100 non-null float64 2 volume 100 non-null int64 dtypes: datetime64[ns](1), float64(1), int64(1) memory usage: 2.5 KB
Types of Volume Weighted Trade Analysis: Volume Weighted Average Price (VWAP)
Definition: VWAP is the ratio of the total value of shares traded to the total volume of shares traded over a specific period. It reflects the true average price of a security for the trading day, considering both price and volume.
Formula:
$$VWAP = \frac{\sum_{i=1}^{n} (Price_i \times Volume_i)}{\sum_{i=1}^{n} Volume_i}$$
Where:
- $Price_i$ = Price of trade
i - $Volume_i$ = Volume of trade
i - $n$ = Total number of trades
Importance: Traders use VWAP as a benchmark. Institutional buyers often try to purchase below the VWAP, while sellers aim to sell above it, to demonstrate good execution. It's also used to identify intraday trends and liquidity.
def calculate_vwap_single_period(df: pd.DataFrame) -> float:
"""
Calculates the Volume Weighted Average Price (VWAP) for a given period.
Formula: Sum(Price * Volume) / Sum(Volume)
Args:
df (pd.DataFrame): DataFrame containing 'price' and 'volume' columns.
Returns:
float: The calculated VWAP for the entire period.
"""
# Ensure price and volume are numeric
df['price'] = pd.to_numeric(df['price'])
df['volume'] = pd.to_numeric(df['volume'])
total_value = (df['price'] * df['volume']).sum()
total_volume = df['volume'].sum()
if total_volume == 0:
return 0.0 # Avoid division by zero
return total_value / total_volume
# Calculate VWAP for the entire mock dataset
period_vwap = calculate_vwap_single_period(df)
print(f"The VWAP for the entire trading period is: {period_vwap:.2f}")
# Interpretation: This is the average price at which the asset traded, weighted by volume, for the whole period.The VWAP for the entire trading period is: 96.41
def calculate_cumulative_vwap(df: pd.DataFrame) -> pd.Series:
"""
Calculates the cumulative Volume Weighted Average Price (VWAP) over time.
Formula: Cumulative Sum(Price * Volume) / Cumulative Sum(Volume)
Args:
df (pd.DataFrame): DataFrame containing 'price' and 'volume' columns,
sorted by time.
Returns:
pd.Series: A Series containing the cumulative VWAP at each point in time.
"""
# Ensure price and volume are numeric
df['price'] = pd.to_numeric(df['price'])
df['volume'] = pd.to_numeric(df['volume'])
cumulative_value = (df['price'] * df['volume']).cumsum()
cumulative_volume = df['volume'].cumsum()
# Handle cases where cumulative_volume might be zero at the start
cumulative_vwap = cumulative_value / cumulative_volume.replace(0, np.nan)
return cumulative_vwap
# Calculate cumulative VWAP and add it to the DataFrame
df['cumulative_vwap'] = calculate_cumulative_vwap(df)
print("Mock Data with Cumulative VWAP Head:")
display(df[['timestamp', 'price', 'volume', 'cumulative_vwap']].head())
print("\nMock Data with Cumulative VWAP Tail:")
display(df[['timestamp', 'price', 'volume', 'cumulative_vwap']].tail())Mock Data with Cumulative VWAP Head:
| timestamp | price | volume | cumulative_vwap | |
|---|---|---|---|---|
| 0 | 2023-01-01 09:00:00 | 100.248357 | 206 | 100.248357 |
| 1 | 2023-01-01 09:01:00 | 100.179225 | 171 | 100.217000 |
| 2 | 2023-01-01 09:02:00 | 99.623285 | 566 | 99.860645 |
| 3 | 2023-01-01 09:03:00 | 99.782736 | 314 | 99.841183 |
| 4 | 2023-01-01 09:04:00 | 100.572342 | 761 | 100.116908 |
Mock Data with Cumulative VWAP Tail:
| timestamp | price | volume | cumulative_vwap | |
|---|---|---|---|---|
| 95 | 2023-01-01 10:35:00 | 96.886852 | 445 | 96.422769 |
| 96 | 2023-01-01 10:36:00 | 96.111520 | 963 | 96.417429 |
| 97 | 2023-01-01 10:37:00 | 96.145802 | 810 | 96.413565 |
| 98 | 2023-01-01 10:38:00 | 96.413970 | 739 | 96.413570 |
| 99 | 2023-01-01 10:39:00 | 95.956624 | 650 | 96.408478 |
Types of Volume Weighted Trade Analysis: Time Weighted Average Price (TWAP)
Definition: TWAP is the average price of a security over a specified time frame, where each price point is weighted equally regardless of the volume traded at that point. It's often used by traders to execute large orders without significantly impacting the market.
Formula:
$$TWAP = \frac{\sum_{i=1}^{n} Price_i}{n}$$
Where:
- $Price_i$ = Price of trade
i(assuming trades occur at regular intervals or prices are sampled at regular intervals) - $n$ = Total number of price observations
Importance: TWAP helps achieve an average execution price over a period, minimizing the immediate market impact that a single large order might have. It's suitable for illiquid stocks or large orders that need to be spread out over time.
def calculate_twap_single_period(df: pd.DataFrame) -> float:
"""
Calculates the Time Weighted Average Price (TWAP) for a given period.
Formula: Sum(Price) / Count(Price)
Args:
df (pd.DataFrame): DataFrame containing a 'price' column.
Returns:
float: The calculated TWAP for the entire period.
"""
# Ensure price is numeric
df['price'] = pd.to_numeric(df['price'])
if df['price'].empty:
return 0.0
return df['price'].mean()
# Calculate TWAP for the entire mock dataset
period_twap = calculate_twap_single_period(df)
print(f"The TWAP for the entire trading period is: {period_twap:.2f}")
# Interpretation: This is the simple average price of the asset over the entire period, without considering trade volume.The TWAP for the entire trading period is: 96.52
def calculate_cumulative_twap(df: pd.DataFrame) -> pd.Series:
"""
Calculates the cumulative Time Weighted Average Price (TWAP) over time.
Formula: Cumulative Sum(Price) / Cumulative Count(Price)
Args:
df (pd.DataFrame): DataFrame containing a 'price' column, sorted by time.
Returns:
pd.Series: A Series containing the cumulative TWAP at each point in time.
"""
# Ensure price is numeric
df['price'] = pd.to_numeric(df['price'])
cumulative_sum_price = df['price'].cumsum()
cumulative_count_price = pd.Series(np.arange(1, len(df) + 1), index=df.index)
# Handle cases where cumulative_count_price might be zero (though unlikely with arange)
cumulative_twap = cumulative_sum_price / cumulative_count_price.replace(0, np.nan)
return cumulative_twap
# Calculate cumulative TWAP and add it to the DataFrame
df['cumulative_twap'] = calculate_cumulative_twap(df)
print("Mock Data with Cumulative TWAP Head:")
display(df[['timestamp', 'price', 'volume', 'cumulative_twap']].head())
print("\nMock Data with Cumulative TWAP Tail:")
display(df[['timestamp', 'price', 'volume', 'cumulative_twap']].tail())Mock Data with Cumulative TWAP Head:
| timestamp | price | volume | cumulative_twap | |
|---|---|---|---|---|
| 0 | 2023-01-01 09:00:00 | 100.248357 | 206 | 100.248357 |
| 1 | 2023-01-01 09:01:00 | 100.179225 | 171 | 100.213791 |
| 2 | 2023-01-01 09:02:00 | 99.623285 | 566 | 100.016956 |
| 3 | 2023-01-01 09:03:00 | 99.782736 | 314 | 99.958401 |
| 4 | 2023-01-01 09:04:00 | 100.572342 | 761 | 100.081189 |
Mock Data with Cumulative TWAP Tail:
| timestamp | price | volume | cumulative_twap | |
|---|---|---|---|---|
| 95 | 2023-01-01 10:35:00 | 96.886852 | 445 | 96.538292 |
| 96 | 2023-01-01 10:36:00 | 96.111520 | 963 | 96.533892 |
| 97 | 2023-01-01 10:37:00 | 96.145802 | 810 | 96.529932 |
| 98 | 2023-01-01 10:38:00 | 96.413970 | 739 | 96.528760 |
| 99 | 2023-01-01 10:39:00 | 95.956624 | 650 | 96.523039 |
Comparison and Interpretation
VWAP and TWAP serve different purposes and provide different insights:
- VWAP is heavily influenced by periods of high trading volume. If a large trade occurs at a certain price, that price will have a greater impact on the VWAP.
- TWAP is simply the average of prices over time, giving equal weight to each observed price point, regardless of how much volume was traded at that price.
Comparing these two can give insights into market dynamics. For instance, if the market price is consistently above VWAP, it might indicate buying pressure. If it's above TWAP but below VWAP, it suggests that trades with higher volume might have occurred at lower prices than the time-weighted average.
Visualizations
Visualizing trade data along with VWAP and TWAP can help in understanding their behavior and relationship to price and volume over time.
fig, axes = plt.subplots(3, 1, figsize=(15, 18), sharex=True)
# Plot 1: Price over Time
axes[0].plot(df['timestamp'], df['price'], label='Trade Price', color='blue', alpha=0.7)
axes[0].set_title('Trade Price Over Time')
axes[0].set_ylabel('Price')
axes[0].legend()
axes[0].grid(True, linestyle='--', alpha=0.6)
# Plot 2: Volume over Time
axes[1].bar(df['timestamp'], df['volume'], label='Trade Volume', color='green', alpha=0.7, width=pd.Timedelta(minutes=0.5))
axes[1].set_title('Trade Volume Over Time')
axes[1].set_ylabel('Volume')
axes[1].legend()
axes[1].grid(True, linestyle='--', alpha=0.6)
# Plot 3: Price with Cumulative VWAP and TWAP
axes[2].plot(df['timestamp'], df['price'], label='Trade Price', color='blue', alpha=0.7)
axes[2].plot(df['timestamp'], df['cumulative_vwap'], label='Cumulative VWAP', color='red', linestyle='--')
axes[2].plot(df['timestamp'], df['cumulative_twap'], label='Cumulative TWAP', color='purple', linestyle=':')
axes[2].set_title('Price, Cumulative VWAP, and Cumulative TWAP Over Time')
axes[2].set_xlabel('Timestamp')
axes[2].set_ylabel('Price / Weighted Average')
axes[2].legend()
axes[2].grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()
Interpretation of Multi-panel Plot
- Price over Time: Shows the fluctuation of the asset's price during the trading period.
- Volume over Time: Illustrates when higher or lower volumes of trades occurred. Peaks in volume often coincide with significant price movements.
- Price with Cumulative VWAP and TWAP: This panel is crucial. It shows how the cumulative VWAP and TWAP evolve relative to the actual trade price. Notice how VWAP tends to follow price movements more closely when large volumes are traded, while TWAP offers a smoother, time-averaged view. The divergence or convergence between the current price and these averages can signal potential trading opportunities or market sentiment.
plt.figure(figsize=(15, 8))
plt.plot(df['timestamp'], df['cumulative_vwap'], label='Cumulative VWAP', color='red', linestyle='-', linewidth=2)
plt.plot(df['timestamp'], df['cumulative_twap'], label='Cumulative TWAP', color='purple', linestyle='--', linewidth=2)
# Add a benchmark: simple average price for the whole period
simple_average_price = df['price'].mean()
plt.axhline(y=simple_average_price, color='gray', linestyle=':', label=f'Simple Average Price ({simple_average_price:.2f})')
# Optional: Add starting price as another benchmark
starting_price = df['price'].iloc[0]
plt.axhline(y=starting_price, color='orange', linestyle='-.', label=f'Starting Price ({starting_price:.2f})')
plt.title('Cumulative VWAP and TWAP with Benchmarks Over Time')
plt.xlabel('Timestamp')
plt.ylabel('Weighted Average Price')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()Interpretation of Cumulative/Trend-based Plot
This plot directly compares the evolution of cumulative VWAP and TWAP against fixed benchmarks (simple average price and starting price).
- If the current price is consistently above cumulative VWAP, it suggests that buying pressure is driving the price higher, with significant volume occurring at these elevated prices.
- Conversely, if the price is below cumulative VWAP, it could indicate selling pressure.
- The relationship between VWAP and TWAP can also be telling. If VWAP is significantly higher than TWAP, it implies that higher volumes are being traded at higher prices, suggesting stronger buying conviction. If VWAP is lower than TWAP, it might suggest selling pressure at higher volumes.
Conclusion
Volume Weighted Trade Analysis, through metrics like VWAP and TWAP, provides invaluable tools for traders and analysts to understand market dynamics and execution quality. VWAP offers a volume-sensitive average price, crucial for institutional trading benchmarks and trend identification, while TWAP provides a time-sensitive average, ideal for minimizing market impact during large order executions. By understanding and utilizing these metrics, market participants can make more informed decisions and improve their trading strategies.