Generating Trade Signals using Moving Average(MA) Crossover Strategy — A Python implementation | by Pratik Nabriya | Towards Data Science

— — — — — — — — — — — — — — — — Disclaimer — The trading strategies and related information in this article is for the educational purpose only. All investments and trading in the stock market involve risk. Any decisions related to buying/selling of stocks or other financial instruments should only be made after a thorough research and seeking a professional assistance if required.— — — — — — — — — — — — — — — —
Indicators such as Moving averages(MAs), Bollinger bands, Relative Strength Index(RSI) are mathematical technical analysis tools that traders and investors use to analyze the past and anticipate future price trends and patterns. Where fundamentalists may track economic data, annual reports, or various other measures, quantitative traders and analysts rely on the charts and indicators to help interpret price moves.
The goal when using indicators is to identify trading opportunities. For example, a moving average crossover often signals an upcoming trend change. Applying the moving average crossover strategy to a price chart allows traders to identify areas where the trend changes the direction creating a potential trading opportunity.
Before we begin, you may consider going through below article to get yourself accustomed with some common finance jargons associated with stock market.
medium.com
A moving average, also called as rolling average or running average is a used to analyze the time-series data by calculating a series of averages of the different subsets of full dataset.
Moving averages are the averages of a series of numeric values. They have a predefined length for the number of values to average and this set of values moves forward as more data is added with time. Given a series of numbers and a fixed subset size, the first element of the moving averages is obtained by taking the average of the initial fixed subset of the number series. Then to obtain subsequent moving averages the subset is ‘shift forward’ i.e. exclude the first element of the previous subset and add the element immediately after the previous subset to the new subset keeping the length fixed . Since it involves taking the average of the dataset over time, it is also called a moving mean (MM) or rolling mean.
In the technical analysis of financial data, moving averages(MAs) are among the most widely used trend following indicators that demonstrate the direction of the market’s trend.
There are many different types of moving averages depending on how the averages are computed. In any time-series data analysis, the most commonly used types of moving averages are —
Simple Moving Average(SMA)
Weighted Moving Average(WMA)
Exponential Moving Average (EMA or EWMA)
The only noteworthy difference between the various moving averages is the weight assigned to data points in the moving average period. Simple moving averages apply equal weight to all data points. Exponential and weighted averages apply more weight to recent data points.
Among these, Simple Moving Averages(SMAs) and Exponential Moving Averages(EMAs) are arguably the most popular technical analysis tool used by the analysts and traders. In this article, we’ll focus primarily on the strategies involving SMAs and EMAs.
Simple Moving Average is one of the core technical indicators used by traders and investors for the technical analysis of a stock, index or securities. Simple moving average is calculated by adding the the closing price of last n number of days and then diving by the number of days(time-period). Before we dive deep, let’s first understand the math behind simple averages.
We have studied how to compute average in school and even in our daily life we often come across the notion of it. Let’s say you are watching a game of cricket and a batsman comes for batting. By looking at his previous 5 match scores— 60, 75, 55, 80, 50; you can expect him to score roughly around 60–70 runs in today’s match.
By calculating the average of a batsman from his last 5 matches, you were able to make a crude prediction that he’ll score this much runs today. Although, this is a rough estimation and doesn’t guarantee that he’ll score exactly same runs, but still the chances are high. Likewise, SMA helps in predicting the future trend and determine whether an asset price will continue or reverse a bull or bear trend. The SMA is usually used to identify trend direction, but it can also be used to generate potential trading signals.
Calculating Simple moving averages — The formula for calculating the SMA is straightforward:
The simple moving average = (sum of the an asset price over the past n periods) / (number of periods)
All elements in the SMA have the same weightage. If the moving average period is 5, then each element in the SMA will have a 20% (1/5) weightage in the SMA.
‘n periods’ can be anything. You can have a 200 day simple moving average, a 100 hour simple moving average, a 5 day simple moving average, a 26 week simple moving average, etc.
Now that we have accustomed ourselves with the basics, let’s jump to the Python implementation.
For this example, I have taken the 2 years of historical data of the Closing Price of UltraTech Cement Limited stock(ULTRACEMCO as registered on NSE) from 1st Feb 2018 to 1st Feb 2020. You may choose your own set of stocks and the time period for the analysis.
Let’s began by extracting the stock price data from Yahoo Finance by using Pandas-datareader API.
Importing necessary libraries —
import numpy as np import pandas as pdimport matplotlib.pyplot as pltimport datetimeimport 
Extracting closing price data of UltraTech Cement stock for the aforementioned time-period —
# import packageimport pandas_datareader.data as web# set start and end dates start = datetime.datetime(2018, 2, 1) end = datetime.datetime(2020, 2, 1) # extract the closing price dataultratech_df = web.DataReader(['ULTRACEMCO.NS'], 'yahoo', start = start, end = end)['Close']ultratech_df.columns = {'Close Price'}ultratech_df.head(10)
Note that SMAs are calculated on closing prices and not adjusted close because we want the trade signal to be generated on the price data and not influenced by dividends paid.
Observe general price variation of the closing price for the give period —
ultratech_df[‘Close Price’].plot(figsize = (15, 8))plt.grid()plt.ylabel("Price in Rupees"plt.show()
Create new columns in our dataframe for both the long(i.e. 50 days) and short (i.e 20 days) simple moving averages (SMAs) —
# create 20 days simple moving average columnultratech_df[‘20_SMA’] = ultratech_df[‘Close Price’].rolling(window = 20, min_periods = 1).mean()# create 50 days simple moving average columnultratech_df[‘50_SMA’] = ultratech_df[‘Close Price’].rolling(window = 50, min_periods = 1).mean()# display first few rowsultratech_df.head()
In Pandas, dataframe.rolling() function provides the feature of rolling window calculations. min_periods parameter specifies the minimum number of observations in window required to have a value (otherwise result is NA).
Now that we have 20-days and 50-days SMAs, next we see how to strategize this information to generate the trade signals.
There are several ways in which stock market analysts and investors can use moving averages to analyse price trends and predict upcoming change of trends. There are vast varieties of the moving average strategies that can be developed using different types of moving averages. In this article, I’ve tried to demonstrate well-known simplistic yet effective momentum strategies — Simple Moving Average Crossover strategy and Exponential Moving Average Crossover strategy.
In the statistics of time-series, and in particular the Stock market technical analysis, a moving-average crossover occurs when on plotting, the two moving averages each based on different time-periods tend to cross. This indicator uses two (or more) moving averages — a faster moving average(short-term) and a slower(long-term) moving average. The faster moving average may be 5-, 10- or 25-day period while the slower moving average can be 50-, 100- or 200-day period. A short term moving average is faster because it only considers prices over short period of time and is thus more reactive to daily price changes. On the other hand, a long-term moving average is deemed slower as it encapsulates prices over a longer period and is more lethargic.
A moving average, as a line by itself, is often overlaid in price charts to indicate price trends. A crossover occurs when a faster moving average (i.e. a shorter period moving average) crosses a slower moving average (i.e. a longer period moving average). In stock trading, this meeting point can be used as a potential indicator to buy or sell an asset.
When the short term moving average crosses above the long term moving average, this indicates a buy signal.
Contrary, when the short term moving average crosses below the long term moving average, it may be a good moment to sell.
Having equipped with the necessary theory, now let’s continue our Python implementation wherein we’ll try to incorporate this strategy.
In our existing pandas dataframe, create a new column ‘Signal’ such that if 20-day SMA is greater than 50-day SMA then set Signal value as 1 else when 50-day SMA is greater than 20-day SMA then set it’s value as 0.
ultratech_df['Signal'] = 0.0ultratech_df['Signal'] = np.where(ultratech_df['20_SMA'] > ultratech_df['50_SMA'], 1.0, 0.0)
From these ‘Signal’ values, the position orders can be generated to represent trading signals. Crossover happens when the faster moving average and the slower moving average cross, or in other words the ‘Signal’ changes from 0 to 1 (or 1 to 0). So, to incorporate this information, create a new column ‘Position’ which nothing but a day-to-day difference of the ‘Signal’ column.
ultratech_df[‘Position’] = ultratech_df[‘Signal’].diff()# display first few rowsultratech_df.head()
When ‘Position’ = 1, it implies that the Signal has changed from 0 to 1 meaning a short-term(faster) moving average has crossed above the long-term(slower) moving average, thereby triggering a buy call.
When ‘Position’ = -1, it implies that the Signal has changed from 1 to 0 meaning a short-term(faster) moving average has crossed below the long-term(slower) moving average, thereby triggering a sell call.
Now let’s visualize this using a plot to make it more clear.
plt.figure(figsize = (20,10))# plot close price, short-term and long-term moving averages ultratech_df[‘Close Price’].plot(color = ‘k’, label= ‘Close Price’) ultratech_df[‘20_SMA’].plot(color = ‘b’,label = ‘20-day SMA’) ultratech_df[‘50_SMA’].plot(color = ‘g’, label = ‘50-day SMA’)# plot ‘buy’ signalsplt.plot(ultratech_df[ultratech_df[‘Position’] == 1].index,          ultratech_df[‘20_SMA’][ultratech_df[‘Position’] == 1],          ‘^’, markersize = 15, color = ‘g’, label = 'buy')# plot ‘sell’ signalsplt.plot(ultratech_df[ultratech_df[‘Position’] == -1].index,          ultratech_df[‘20_SMA’][ultratech_df[‘Position’] == -1],          ‘v’, markersize = 15, color = ‘r’, label = 'sell')plt.ylabel('Price in Rupees', fontsize = 15 )plt.xlabel('Date', fontsize = 15 )plt.title('ULTRACEMCO', fontsize = 20)plt.legend()plt.grid()plt.show()
As you can see in the above plot, the blue line represents the faster moving average(20 day SMA), the green line represents the slower moving average(50 day SMA) and the black line represents the actual closing price. If you carefully observe, these moving averages are nothing but the smoothed versions of the actual price, but lagging by certain period of time. The short-term moving average closely resembles the actual price which perfectly makes sense as it takes into consideration more recent prices. In contrast, the long-term moving average has comparatively more lag and loosely resembles the actual price curve.
A signal to buy (as represented by green up-triangle) is triggered when the fast moving average crosses above the slow moving average. This shows a shift in trend i.e. the average price over last 20 days has risen above the average price of past 50 days. Likewise, a signal to sell(as represented by red down-triangle) is triggered when the fast moving average crosses below the slow moving average indicating that the average price in last 20 days has fallen below the average price of the last 50 days.
So far we have discussed the moving average crossover strategy using the simple moving averages(SMAs). It is straightforward to observe that SMA time-series are much less noisy than the original price. However, this comes at a cost — SMA lag the original price, which means that changes in the trend are only seen with a delay of L days. How much is this lag L? For a SMA moving average calculated using M days, the lag is roughly around M/2 days. Thus, if we are using a 50 days SMA, this means we may be late by almost 25 days, which can significantly affect our strategy.
One way to reduce the lag induced by the use of the SMA is to use Exponential Moving Average(EMA). Exponential moving averages give more weight to the most recent periods. This makes them more reliable than SMAs as they are comparatively better representation of the recent performance of the asset. The EMA is calculated as:
EMA [today] = (α x Price [today] ) + ((1 — α) x EMA [yesterday] )
Where: α = 2/(N + 1)N = the length of the window (moving average period)EMA [today] = the current EMA valuePrice [today] = the current closing priceEMA [yesterday] = the previous EMA value
Although the calculation for an EMA looks bit daunting, in practice it’s simple. In fact, it’s easier to calculate than SMA, and besides, the Pandas ewm functionality will do it for you in a single-line of code!
Having understood the basics, let’s try to incorporate EMAs in place of SMAs in our moving average strategy. We’re going to use the same code as above, with some minor changes.
# set start and end datesstart = datetime.datetime(2018, 2, 1)end = datetime.datetime(2020, 2, 1)# extract the daily closing price dataultratech_df = web.DataReader(['ULTRACEMCO.NS'], 'yahoo', start = start, end = end)['Close']ultratech_df.columns = {'Close Price'}# Create 20 days exponential moving average columnultratech_df['20_EMA'] = ultratech_df['Close Price'].ewm(span = 20, adjust = False).mean()# Create 50 days exponential moving average columnultratech_df['50_EMA'] = ultratech_df['Close Price'].ewm(span = 50, adjust = False).mean()# create a new column 'Signal' such that if 20-day EMA is greater   # than 50-day EMA then set Signal as 1 else 0  ultratech_df['Signal'] = 0.0  ultratech_df['Signal'] = np.where(ultratech_df['20_EMA'] > ultratech_df['50_EMA'], 1.0, 0.0)# create a new column 'Position' which is a day-to-day difference of # the 'Signal' columnultratech_df['Position'] = ultratech_df['Signal'].diff()plt.figure(figsize = (20,10))# plot close price, short-term and long-term moving averages ultratech_df['Close Price'].plot(color = 'k', lw = 1, label = 'Close Price')  ultratech_df['20_EMA'].plot(color = 'b', lw = 1, label = '20-day EMA') ultratech_df['50_EMA'].plot(color = 'g', lw = 1, label = '50-day EMA')# plot ‘buy’ and 'sell' signalsplt.plot(ultratech_df[ultratech_df[‘Position’] == 1].index,          ultratech_df[‘20_EMA’][ultratech_df[‘Position’] == 1],          ‘^’, markersize = 15, color = ‘g’, label = 'buy')plt.plot(ultratech_df[ultratech_df[‘Position’] == -1].index,          ultratech_df[‘20_EMA’][ultratech_df[‘Position’] == -1],          ‘v’, markersize = 15, color = ‘r’, label = 'sell')plt.ylabel('Price in Rupees', fontsize = 15 )plt.xlabel('Date', fontsize = 15 )plt.title('ULTRACEMCO - EMA Crossover', fontsize = 20)plt.legend()plt.grid()plt.show()
The following extract from John J. Murphy’s work, “Technical Analysis of the Financial Markets” published by the New York Institute of Finance, explains the advantage of the exponentially weighted moving average over the simple moving average—
“The exponentially smoothed moving average addresses both of the problems associated with the simple moving average. First, the exponentially smoothed average assigns a greater weight to the more recent data. Therefore, it is a weighted moving average. But while it assigns lesser importance to past price data, it does include in its calculation all the data in the life of the instrument. In addition, the user is able to adjust the weighting to give greater or lesser weight to the most recent day’s price, which is added to a percentage of the previous day’s value. The sum of both percentage values adds up to 100.”
The function ‘MovingAverageCrossStrategy()’ takes following inputs —
stock_symbol —(str) stock ticker as on Yahoo finance. Eg: 'ULTRACEMCO.NS'
start_date — (str)start analysis from this date (format: 'YYYY-MM-DD') Eg: '2018-01-01'.
end_date— (str)end analysis on this date (format: 'YYYY-MM-DD') Eg: '2020-01-01'.
short_window— (int)look-back period for short-term moving average. Eg: 5, 10, 20
long_window — (int)look-back period for long-term moving average. Eg: 50, 100, 200
moving_avg— (str)the type of moving average to use ('SMA' or 'EMA').
display_table — (bool)whether to display the date and price table at buy/sell positions(True/False).
Now, let’s test our script on last 4 years of HDFC bank stock. We’ll be using 50-day and 200-day SMA crossover strategy.
Input:
MovingAverageCrossStrategy('HDFC.NS', '2016-08-31', '2020-08-31', 50, 200, 'SMA', display_table = True)
Output:
How about Fortis Healtcare stock? This time we analyze past 1 year of data and consider 20-days and 50-days EMA Crossover. Also, this time we won’t be displaying the table.
Input:
MovingAverageCrossStrategy('FORTIS.NS', '2019-08-31', '2020-08-31', 20, 50, 'EMA', display_table = False)
Output:
Due to the fundamental difference in the way they are calculated, EMA reacts quickly to the price changes while SMA is comparatively slow to react. But, one is not necessarily better than another. Each trader must decide which MA is better for his or her particular strategy. In general, shorter-term traders tend to use EMAs because they want to be alerted as soon as the price is moving the other way. On the other hand, longer-term traders tend to rely on SMAs since these investors aren’t rushing to act and prefer to be less actively engaged in their trades.
Beware! As a trend-following indicators, moving averages work in markets that have clear, long term trends. They don’t work that well in markets that can be very choppy for long periods of time. Moral of the story — moving averages are not a one-size-fits-all holy grail. In fact, there is no perfect indicator or a strategy that will guarantee success on each investment in all circumstances. Quantitative traders often use a variety of technical indicators and their combinations to come up with different strategies. In my subsequent articles, I will try to introduce some of these technical indicators.
In this article, I showed how to build a powerful tool to perform technical analysis and generate trade signals using moving average crossover strategy. This script can be used for investigating other company stocks by simply changing the argument to the function MovingAverageCrossStrategy().
This is only the beginning, it is possible to create much more sophisticated strategies which I’ll be looking forward to.
Incorporate more strategies based on indicators like Bollinger bands, Moving Average Convergence Divergence (MACD), Relative Strength Index(RSI) etc.
Perform backtesting to evaluate the performance of different strategies using appropriate metrics.
QuantInsti blogsInvestopediaYahoo FinanceMoving Averages Simplified by Clif DrokeTechnical Analysis of the Financial Markets by John J. Murphy
QuantInsti blogs
Investopedia
Yahoo Finance
Moving Averages Simplified by Clif Droke
Technical Analysis of the Financial Markets by John J. Murphy
You may also want to check my other article —