How to Calculate Weighted Moving Average in Python
A weighted moving average (WMA) assigns different levels of importance to data points within a window, typically giving more weight to recent observations. Unlike a simple moving average that treats...
Key Insights
- Weighted moving averages give more importance to recent data points, making them more responsive to trend changes than simple moving averages—critical for financial analysis and real-time forecasting
- NumPy’s
convolve()function provides a vectorized approach that’s 10-50x faster than loop-based implementations for calculating WMA on large datasets - Linear weighting (where most recent value gets highest weight) is the standard approach, but custom weight schemes let you fine-tune sensitivity to match your specific use case
Introduction to Weighted Moving Average
A weighted moving average (WMA) assigns different levels of importance to data points within a window, typically giving more weight to recent observations. Unlike a simple moving average that treats all values equally, WMA recognizes that in many real-world scenarios, newer data is more relevant for predictions and trend analysis.
The practical difference is significant. In stock price analysis, yesterday’s closing price tells you more about today’s likely direction than the price from two weeks ago. In demand forecasting, last week’s sales data is more indicative of next week’s performance than sales from a month ago. WMA captures this temporal relevance mathematically.
Common applications include financial technical analysis (identifying support/resistance levels), signal processing (noise reduction while preserving recent changes), and time-series forecasting (where recent patterns matter most). The key trade-off is responsiveness versus stability—WMA reacts faster to changes but can also amplify noise if not configured properly.
The Mathematics Behind WMA
The WMA formula multiplies each data point by a predetermined weight, then divides by the sum of weights:
WMA = (w₁ × p₁ + w₂ × p₂ + ... + wₙ × pₙ) / (w₁ + w₂ + ... + wₙ)
Where p represents data points and w represents weights. The most common approach uses linearly decreasing weights, where the most recent value gets weight n, the previous gets n-1, and so on.
For a 5-period WMA with linear weights, the most recent value gets weight 5, the previous gets 4, continuing down to 1. Here’s a manual calculation:
import numpy as np
# Sample data: last 5 days of closing prices
prices = np.array([100, 102, 101, 105, 107])
# Linear weights: most recent gets highest weight
weights = np.array([1, 2, 3, 4, 5])
# Calculate WMA
wma = np.sum(weights * prices) / np.sum(weights)
print(f"WMA: {wma:.2f}") # Output: WMA: 104.67
# Compare with simple moving average
sma = np.mean(prices)
print(f"SMA: {sma:.2f}") # Output: SMA: 103.00
The WMA of 104.67 is higher than the SMA of 103.00 because it emphasizes the recent upward trend (105, 107). This responsiveness is exactly why WMA is valuable.
Implementing WMA from Scratch with NumPy
Let’s build a reusable function that calculates WMA for any dataset and window size:
import numpy as np
def calculate_wma(data, period):
"""
Calculate weighted moving average with linear weights.
Args:
data: numpy array or list of values
period: window size for WMA calculation
Returns:
numpy array of WMA values
"""
data = np.array(data)
weights = np.arange(1, period + 1)
wma = []
for i in range(period - 1, len(data)):
window = data[i - period + 1:i + 1]
wma_value = np.sum(weights * window) / np.sum(weights)
wma.append(wma_value)
# Pad beginning with NaN to match input length
return np.array([np.nan] * (period - 1) + wma)
# Test with sample data
prices = np.array([100, 102, 101, 105, 107, 106, 108, 110, 109, 111])
wma_5 = calculate_wma(prices, period=5)
print(wma_5)
You can also implement custom weighting schemes for specific needs:
def calculate_wma_custom(data, weights):
"""Calculate WMA with custom weight array."""
data = np.array(data)
weights = np.array(weights)
period = len(weights)
wma = []
for i in range(period - 1, len(data)):
window = data[i - period + 1:i + 1]
wma_value = np.sum(weights * window) / np.sum(weights)
wma.append(wma_value)
return np.array([np.nan] * (period - 1) + wma)
# Exponential-like weights (manually defined)
exp_weights = np.array([1, 2, 4, 8, 16])
wma_exp = calculate_wma_custom(prices, exp_weights)
# Linear weights for comparison
linear_weights = np.array([1, 2, 3, 4, 5])
wma_linear = calculate_wma_custom(prices, linear_weights)
print(f"Exponential-weighted: {wma_exp[-1]:.2f}")
print(f"Linear-weighted: {wma_linear[-1]:.2f}")
Using Pandas for Rolling WMA Calculations
Pandas provides powerful rolling window functionality that integrates seamlessly with time-series data:
import pandas as pd
import numpy as np
# Create sample stock data
dates = pd.date_range('2024-01-01', periods=20, freq='D')
df = pd.DataFrame({
'price': [100, 102, 101, 105, 107, 106, 108, 110, 109, 111,
113, 112, 115, 114, 116, 118, 117, 119, 121, 120]
}, index=dates)
def weighted_mean(x):
"""Apply linear weights to rolling window."""
weights = np.arange(1, len(x) + 1)
return np.sum(weights * x) / np.sum(weights)
# Calculate 5-period WMA
df['WMA_5'] = df['price'].rolling(window=5).apply(weighted_mean, raw=True)
print(df.head(10))
Handling edge cases and missing data properly is crucial:
# Data with missing values
df_missing = df.copy()
df_missing.loc['2024-01-05':'2024-01-07', 'price'] = np.nan
# Option 1: Skip NaN values (reduces effective window)
df_missing['WMA_skipna'] = df_missing['price'].rolling(
window=5, min_periods=3
).apply(weighted_mean, raw=False)
# Option 2: Forward-fill missing values before calculation
df_missing['price_filled'] = df_missing['price'].fillna(method='ffill')
df_missing['WMA_filled'] = df_missing['price_filled'].rolling(
window=5
).apply(weighted_mean, raw=True)
print(df_missing)
Performance Optimization with Vectorization
For large datasets, vectorization dramatically improves performance. Here’s a benchmark comparison:
import time
def wma_loop(data, period):
"""Loop-based implementation."""
weights = np.arange(1, period + 1)
result = np.full(len(data), np.nan)
for i in range(period - 1, len(data)):
window = data[i - period + 1:i + 1]
result[i] = np.sum(weights * window) / np.sum(weights)
return result
def wma_convolve(data, period):
"""Vectorized implementation using convolution."""
weights = np.arange(1, period + 1)
weights = weights / np.sum(weights)
# Convolve and handle edges
convolved = np.convolve(data, weights[::-1], mode='valid')
# Pad to match input length
return np.concatenate([np.full(period - 1, np.nan), convolved])
# Benchmark with large dataset
large_data = np.random.randn(100000)
start = time.time()
result_loop = wma_loop(large_data, 20)
time_loop = time.time() - start
start = time.time()
result_convolve = wma_convolve(large_data, 20)
time_convolve = time.time() - start
print(f"Loop-based: {time_loop:.4f} seconds")
print(f"Convolve-based: {time_convolve:.4f} seconds")
print(f"Speedup: {time_loop/time_convolve:.1f}x")
The np.convolve() approach typically runs 10-50x faster depending on dataset size and window period.
Real-World Application: Stock Price Analysis
Let’s build a complete analysis system with multiple WMA periods and trading signals:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Generate realistic stock data
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=252, freq='B')
price = 100 + np.cumsum(np.random.randn(252) * 2)
df = pd.DataFrame({'price': price}, index=dates)
def wma_vectorized(data, period):
"""Efficient WMA calculation."""
weights = np.arange(1, period + 1) / np.sum(np.arange(1, period + 1))
return np.convolve(data, weights[::-1], mode='valid')
# Calculate multiple WMA periods
for period in [5, 20, 50]:
wma_values = wma_vectorized(df['price'].values, period)
df[f'WMA_{period}'] = np.concatenate([
np.full(period - 1, np.nan),
wma_values
])
# Generate trading signals (WMA crossover strategy)
df['signal'] = 0
df.loc[df['WMA_5'] > df['WMA_20'], 'signal'] = 1 # Bullish
df.loc[df['WMA_5'] < df['WMA_20'], 'signal'] = -1 # Bearish
# Identify crossover points
df['position_change'] = df['signal'].diff()
buy_signals = df[df['position_change'] == 2].index
sell_signals = df[df['position_change'] == -2].index
# Visualization
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['price'], label='Price', alpha=0.7, linewidth=1)
plt.plot(df.index, df['WMA_5'], label='WMA 5', linewidth=1.5)
plt.plot(df.index, df['WMA_20'], label='WMA 20', linewidth=1.5)
plt.plot(df.index, df['WMA_50'], label='WMA 50', linewidth=1.5)
plt.scatter(buy_signals, df.loc[buy_signals, 'price'],
color='green', marker='^', s=100, label='Buy Signal', zorder=5)
plt.scatter(sell_signals, df.loc[sell_signals, 'price'],
color='red', marker='v', s=100, label='Sell Signal', zorder=5)
plt.xlabel('Date')
plt.ylabel('Price')
plt.title('Stock Price with WMA and Trading Signals')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('wma_analysis.png', dpi=300)
plt.show()
print(f"Total Buy Signals: {len(buy_signals)}")
print(f"Total Sell Signals: {len(sell_signals)}")
Conclusion and Best Practices
Choose WMA over simple moving average when recent data should influence your analysis more heavily. This makes sense for volatile markets, real-time monitoring systems, and short-term forecasting.
For window size selection, shorter periods (5-10) respond quickly but generate more false signals. Longer periods (20-50) provide stability but lag behind trend changes. Use multiple WMA periods together—crossovers between fast and slow WMAs are more reliable than single-period analysis.
Weight selection matters. Linear weights work well for most applications. Use exponential-like weights when you need extreme emphasis on recent values, but be aware this increases noise sensitivity. Always backtest your weight scheme with historical data before deploying.
For production systems, use the vectorized np.convolve() implementation—it’s dramatically faster and the code is cleaner. When working with pandas DataFrames, the rolling window approach provides better integration with time-series operations and handles missing data gracefully.
Finally, remember that WMA is a lagging indicator. It tells you what has happened, not what will happen. Combine it with other technical indicators and fundamental analysis for robust decision-making. The code examples in this article provide a solid foundation—adapt the weighting schemes and parameters to match your specific domain requirements.