Python Trading Toolbox: Weighted and Exponential Moving Averages | by Stefano Basurto | Towards Data Science

In the first post of the Financial Trading Toolbox series (Building a Financial Trading Toolbox in Python: Simple Moving Average), we discussed how to calculate a simple moving average, add it to a price series chart, and use it for investment and trading decisions. The Simple Moving Average is only one of several moving averages available that can be applied to price series to build trading systems or investment decision frameworks. Among those, two other moving averages are commonly used among financial market :
Weighted Moving Average (WMA)
Exponential Moving Average (EMA)
In this article, we will explore how to calculate those two averages and how to ensure that the results match the definitions that we need to implement.
In some applications, one of the limitations of the simple moving average is that it gives equal weight to each of the daily prices included in the window. E.g., in a 10-day moving average, the most recent day receives the same weight as the first day in the window: each price receives a 10% weighting.
Compared to the Simple Moving Average, the Linearly Weighted Moving Average (or simply Weighted Moving Average, WMA), gives more weight to the most recent price and gradually less as we look back in time. On a 10-day weighted average, the price of the 10th day would be multiplied by 10, that of the 9th day by 9, the 8th day by 8 and so on. The total will then be divided by the sum of the weights (in this case: 55). In this specific example, the most recent price receives about 18.2% of the total weight, the second more recent 16.4%, and so on until the oldest price in the window that receives 0.02% of the weight.
Let’s put that in practice with an example in Python. In addition to pandas and Matplotlib, we are going to make use of NumPy:
import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport matplotlib as mpl
We apply a style for our charts. If you’re using Jupyter it’s a good idea to add the %matplotlib inline instruction (and skip plt.show() when creating charts):
plt.style.use('fivethirtyeight')
For the next examples, we are going to use price data from a StockCharts.com article. It’s an excellent educational article on moving averages and I recommend reading it. The price series used in that article could belong to any stock or financial instrument and will serve our purposes for illustration.
I modified the original Excel sheet by including calculations for the 10-day WMA since the calculation for the EMA is already included. You can access my Google Sheets file and download the data in CSV format here.
It is always a good practice, when modeling data, to start with a simple implementation of our model that we can use to make sure that the results from our final implementation are correct.
We start by loading the data into a data frame:
datafile = 'cs-movavg.csv'data = pd.read_csv(datafile, index_col = 'Date')data.index = pd.to_datetime(data.index)# We can drop the old index column:data = data.drop(columns='Unnamed: 0')data
We are going to consider only the Price and 10-Day WMA columns for now and move to the EMA later on.
When it comes to linearly weighted moving averages, the pandas library does not have a ready off-the-shelf method to calculate them. It offers, however, a very powerful and flexible method: .apply() This method allows us to create and pass any custom function to a rolling window: that is how we are going to calculate our Weighted Moving Average. To calculate a 10-Day WMA, we start by creating an array of weights - whole numbers from 1 to 10:
weights = np.arange(1,11) #this creates an array with integers 1 to 10 includedweights
Which looks like:
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
Next, using the .apply() method we pass our own function (a lambda function) to compute the dot product of weights and prices in our rolling window (prices in the window will be multiplied by the corresponding weight, then summed), then dividing it by the sum of the weights:
wma10 = data['Price'].rolling(10).apply(lambda prices: np.dot(prices, weights)/weights.sum(), raw=True)wma10.head(20)
Which gives:
Now, we want to compare our WMA to the one obtained with the spreadsheet. To do so, we can add an ‘Our 10-day WMA’ column to the dataframe. To make the visual comparison easier, we can round the WMA series to three decimals using the.round() method from NumPy. Then, we select the price and WMA columns to be displayed:
data['Our 10-day WMA'] = np.round(wma10, decimals=3)data[['Price', '10-day WMA', 'Our 10-day WMA']].head(20)
Showing:
The two WMA columns look the same. There are a few differences in the third decimal place, but we can put that down to rounding error and conclude that our implementation of the WMA is correct. In a real-life application, if we want to be more rigorous we should compute the differences between the two columns and check that they are not too large. For now, we keep things simple and we can be satisfied with the visual inspection.
It would be interesting to compare in a plot our newly created WMA with the familiar SMA:
sma10 = data['Price'].rolling(10).mean()plt.figure(figsize = (12,6))plt.plot(data['Price'], label="Price")plt.plot(wma10, label="10-Day WMA")plt.plot(sma10, label="10-Day SMA")plt.xlabel("Date")plt.ylabel("Price")plt.legend()plt.show()
This shows:
As we can see, both averages smooth out the price movement. The WMA is more reactive and follows the price closer than the SMA: we expect that since the WMA gives more weight to the most recent price observations. Also, both moving average series start on day 10: the first day with enough available data to compute the averages.
The Weighted Moving Average may be lesser known than its Exponential sibling. However, it can be an additional item in our toolbox when we try to build original solutions. Implementing the WMA in Python forced us to search for a way to create customized moving averages using .apply(): this technique can be used to implement new and original moving averages as well.
Similarly to the Weighted Moving Average, the Exponential Moving Average (EMA) assigns a greater weight to the most recent price observations. While it assigns lesser weight to past data, it is based on a recursive formula that includes in its calculation all the past data in our price series.
The EMA at time t is calculated as the current price multiplied by a smoothing factor alpha (a positive number less than 1) plus the EMA at time t−1 multiplied by 1 minus alpha. It is basically a value between the previous EMA and the current price:
The smoothing factor α ( alpha ) is defined as:
where n is the number of days in our span. Therefore, a 10-day EMA will have a smoothing factor:
Pandas includes a method to compute the EMA moving average of any time series: .ewm(). Will this method respond to our needs and compute an average that matches our definition? Let's test it:
ema10 = data['Price'].ewm(span=10).mean()ema10.head(10)
Which gives:
We want to compare this EMA series with the one obtained in the spreadsheet:
data['Our 10-day EMA'] = np.round(ema10, decimals=3)data[['Price', '10-day EMA', 'Our 10-day EMA']].head(20)
Which results:
As you have already noticed, we have a problem here: the 10-day EMA that we just calculated does not correspond to the one calculated in the downloaded spreadsheet. One starts on day 10, while the other starts on day 1. Also, the values do not match exactly.
Is our calculation wrong? Or is the calculation in the provided spreadsheet wrong? Neither: those two series correspond to two different definitions of EMA. To be more specific, the formula used to compute the EMA is the same. What changes is just the use of the initial values.
If we look carefully at the definition of Exponential Moving Average on the StockCharts.com web page we can notice one important detail: they start calculating a 10-day moving average on day 10, disregarding the previous days and replacing the price on day 10 with its 10-day SMA. It’s a different definition than the one applied when we calculated the EMA using the .ewm() method directly.
The following lines of code create a new modified price series where the first 9 prices (when the SMA is not available) are replaced by NaN and the price on the 10th date becomes its 10-Day SMA:
modPrice = data['Price'].copy()modPrice.iloc[0:10] = sma10[0:10]modPrice.head(20)
We can use this modified price series to calculate a second version of the EWM. By looking at the documentation, we can note that the .ewm() method has an adjust parameter that defaults to True. This parameter adjusts the weights to account for the imbalance in the beginning periods (if you need more detail, see the Exponentially weighted windows section in the pandas documentation).
If we want to emulate the EMA as in our spreadsheet using our modified price series, we don’t need this adjustment. We then set adjust=False:
ema10alt = modPrice.ewm(span=10, adjust=False).mean()
Will this newly calculated EMA match the one calculated in the spreadsheet? Let’s have a look:
data['Our 2nd 10-Day EMA'] = np.round(ema10alt, decimals=3)data[['Price', '10-day EMA', 'Our 10-day EMA', 'Our 2nd 10-Day EMA']].head(20)
Now, we are doing much better. We have obtained an EMA series that matches the one calculated in the spreadsheet.
We ended up with two different versions of EMA in our hands:
ema10: This version uses the plain .ewm() method, starts at the beginning of our price history but does not match the definition used in the spreadsheet.ema10alt: This version starts on day 10 (with an initial value equal to the 10-day SMA) and matches the definition on our spreadsheet.
ema10: This version uses the plain .ewm() method, starts at the beginning of our price history but does not match the definition used in the spreadsheet.
ema10alt: This version starts on day 10 (with an initial value equal to the 10-day SMA) and matches the definition on our spreadsheet.
Which one is the best to use? The answer is: it depends on what we need for our application and to build our system. If we need an EMA series that starts from day 1, then we should choose the first one. On the other hand, if we need to use our average in combination with other averages that have no values for the initial days (such as the SMA), then the second is probably the best one.
The second EMA is widely used among financial market analysts: if we need to implement an already existing system, we need to be careful to use the correct definition. Otherwise, the results may not be what is expected from us and may put the accuracy of all of our work into question. In any case, the numeric difference between those two averages is minimal, with an impact on our trading or investment decision system limited to the initial days.
Let’s look at all the moving averages we have used so far in a chart:
plt.figure(figsize = (12,6))plt.plot(data['Price'], label="Price")plt.plot(wma10, label="10-Day WMA")plt.plot(sma10, label="10-Day SMA")plt.plot(ema10, label="10-Day EMA-1")plt.plot(ema10alt, label="10-Day EMA-2")plt.xlabel("Date")plt.ylabel("Price")plt.legend()plt.show()
Of all the moving averages, the WMA appears the one that is more responsive and tags the price more closely, while the SMA is the one that responds with some more lag. The two versions of the EMA tend to overlap each other, mainly in the last days.
I hope you found this post useful. Introducing the Weighted Moving Average helped us to learn and implement a custom average based on a specific definition. Working with the Exponential Moving Average gave us the chance to highlight how important it is to ensure that any function we are using to work on price series matches the definition that we have for any given task.
Note from Towards Data Science’s editors: While we allow independent authors to publish articles in accordance with our rules and guidelines, we do not endorse each author’s contribution. You should not rely on an author’s works without seeking professional advice. See our Reader Terms for details.