Hey, I’m Rachel Carpenter, the CEO of Intrinio. In this article, I’m going to show you how to quickly construct and backtest a simple moving average crossover strategy with Python.
The Simple Moving Average Crossover Strategy is one of many technical strategies used to predict the future price trend of an underlying security. This walkthrough will demonstrate how to use Intrinio’s API and End of Day Market Data to generate and backtest SMA trading signals quickly.
There are three components needed to construct and backtest our Simple Moving Average Crossover Strategy:
But before we get started with the code, let’s discuss moving averages a little more.
The Moving Average (MA) of a stock is simply the average price of a security over a specific period.
For example, a 50-day moving average sums up the latest 50 trading days of end-of-day price data of security and divides it by 50, leaving an investor with the average stock price over the last 50 trading days.
The benefit provided by constantly updating and viewing the moving average of a stock is that it allows investors to see a smooth price history and a clear upward, downward, or horizontal trend of the stock’s price versus the erratic behavior that a stock price can demonstrate over shorter periods of time.
The Simple Moving Averages (SMAs) that construct our strategy are known as lagging indicators, since they are made up of historical data. These lagging indicators can show investors whether a stock’s price action is moving upward, downward, or trading horizontally (range bound).
Investors typically use the moving average indicator in two ways to inform their investment decisions:
In our tutorial, we will be using the Moving Average Crossover strategy, but the code I’ll show you can be easily modified to generate and backtest Price Crossover signals as well.
Investors typically view longer moving averages, 50-day, 100-day, and 200-day, as either support or resistance benchmarks to a stock price’s current trend.
For example, if the stock’s price consistently bounces off the top moving average, investors typically presume that the latest moving average price is the “price floor” or support level.
One explanation for the moving average acting as a price floor is that investors in the stock are consistently dollar-cost averaging into the stock as it recedes in price to lower valuation levels, ultimately “supporting” the stock’s current growth trajectory.
On the contrary, if a stock’s moving average is above its current price, this moving average may be acting as a “ceiling” or resistance level.
One explanation for the moving average acting as a ceiling is that investors who previously purchased the stock at higher prices are now exiting it as it reaches these prices again.
In a sense, these investors are “ringing the register” or washing their hands whenever the stock’s price allows them to get out of their current holdings with a profit or minimal losses.
Using this knowledge, some investors believe that when a stock’s price crosses these moving average support - or -resistance thresholds, it signifies that there is strength behind this new upward or downward trend and that they should enter into a long position, or short the stock accordingly.
The moving average crossover is similar to the price crossover we just covered; however, it relies on two moving averages, one longer and one shorter, crossing over each other — instead of the price crossing over a single moving average.
A buy signal or “golden cross” occurs when the short-term moving average crosses up through the long-term moving average. This cross occurs because the short-term price average moves upward “faster” than the long-term average. Investors view this movement as a signal that the stock’s price trend has positive upward momentum behind it and that they should enter.
A sell signal, or “death cross,” occurs when the short-term moving average crosses down through the long-term moving average. Again, that cross occurs because the short-term price is falling quicker than the average long-term price. Investors view this movement as a signal that the stock’s range-bound or upward trend has ended and that it is time to exit the security or initiate a short position.
One drawback occurs when investors use stop-loss orders with their trend-following strategies. A stop-loss order automatically closes a position when a particular trailing threshold is hit.
For example, if you bought a stock at $100 a share and set a 10% trailing stop-loss order, you would automatically close this trade at $90 per share if the stock price directly fell from your initial entry.
This type of outcome can be pretty frequent with trend and momentum trading strategies, and an investor can become “stopped out” if the crossover trend quickly reverses in the other direction.
Another drawback to consider is the tax implication of entering and exiting trend-following trades. If your strategies have a shorter horizon for entries and exits, you will have to pay short-term capital gains tax, which can quickly eat into your profits.
Google Colab makes it super simple to create a coding environment, so we will be using it to construct and backtest our strategy.
Building this strategy and backtest is pretty simple and a great way to get familiar with trend-following trading strategies.
We will first ingest the end-of-day data we need from Intrinio’s Stock Prices by Security Endpoint. The endpoint documentation will show you how easy it is to sample different time periods - you can adjust the start and end date to further curate your stock prices history and backtest period.
For the purposes of this tutorial we are using the latest 10,000 days of price history (when applicable). Our _stock_prices_dataset function will limit our columns to just the date and adjusted close price since these are the only two values we need for this backtest.
Furthermore, within _stock_prices_dataset we will construct a “returns” column to store our calculated logarithmic or continuously compounded return of a security’s daily price using the adjusted close as an input. This “returns” column will eventually be used to calculate our strategy’s overall returns.
Next, we will use the _sma_stock_prices_dataset to create long-term moving and short-term moving average columns, each containing the average price over its respective rolling period.
Note that we will drop off the oldest 200 rows after constructing our moving averages, as these will have Null values for our long-term moving average.
Next, using our previously constructed SMA columns, we will use the _sma_crossover_dataset function to construct our crossover buy and sell signals.
Once these signals are constructed, we iterate over our dataset and create one final column, “hold.” This column is necessary for calculating our returns as it will use boolean True/False values to designate the periods in which we are “holding” our stock after buying.
We then use the _sma_returns_data function and our previously constructed “hold” and “returns” columns to calculate our SMA crossover strategy’s returns.
We also calculate the returns if we were to merely buy and hold the equity over the backtest period, serving as an excellent benchmark to see whether the time periods selected for our long-term and short-term moving averages were a good decision or not.
Additionally, we return the total number of times we entered into a position based on our crossover signals.
Finally, the sma_handler function allows us to perform all of these dataset transformations in transition returning us our final return stats data.
Putting all of this into practice we will analyze our SMA Crossover’s returns for the following three stocks: PLTR | AAPL | GME.
An investor running our strategy for PLTR from 2020–09–30 to present would have established two different positions, and their returns would have been -15.29%, which is not great, but a lot better than the -66.87% return this investor would have had by just buying and holding the stock.
On the contrary, an investor running our strategy for AAPL from 1982–09–16 to the present would have established 26 separate positions over that period. Their returns would have been 429.39%, which is pretty good. Still, comparing it to the 685.88% returns received from just buying and holding (not to mention the short-term tax savings) - shows you that adding additional signals or different moving averages with the SMA crossover strategy might be needed for better profitability.
Finally, an example of superior returns with this strategy can be seen with a recent meme stock favorite, GME. From 2002–02–13 to the present, an investor running this strategy would have established 16 trades and made a 345.54% ROI, compared to a 303.32% buy and hold return - not bad for a simple algorithm.
It should go without saying that all of the work you do here doesn’t matter if you aren’t using a high quality, well supported, and reliable data feed.
At Intrinio, we’re recognized for being one of the top data providers that specialize in high-quality data, immediate customer service, and modern tools for a variety of fintech platforms and businesses.
Our dedicated team wants to help you find the best data package that fits your unique needs. If you request a consultation, you can chat with a data specialist and find out more about our equities market data packages.
Okay, that’s it! You now know how to backtest a simple moving average crossover strategy with Python. To access the full code, you can refer to our Github.
Thanks for reading and as we always say at Intrinio, we can’t wait to see what you build.