Leveraging Delisted Stocks for Backtesting a 3-EMA Strategy Using Python

Learn how this strategy compares to the buy/hold approach and discover the potential for improved trading decisions.

EODHD APIs
8 min readJul 24, 2024

The Exponential Moving Average (EMA) is a popular technical indicator in the trading community, but it is often not utilized to its full potential. Many traders rely on generic strategies such as the EMA 50 & 200 crossover. While not inherently bad, its widespread use has reduced its effectiveness. Today, we will explore a different EMA crossover strategy to see if it can outperform the traditional buy/hold strategy.

However, there’s a twist. We will backtest this strategy on delisted stocks. Why? Because delisted stocks tend to have more volatile price movements compared to listed stocks, allowing us to test our strategy across various market conditions.

In this article, we will start by extracting historical data for delisted stocks using EODHD’s end-of-day data API endpoint. Next, we will obtain the EMA data using EODHD’s technical indicator API. Finally, we will construct the trading strategy and backtest it on a delisted stock. We will also compare the performance of our EMA crossover strategy with the buy/hold strategy for validation.

Let’s dive into the details!

The Trading Strategy

Before diving into the code, it’s crucial to understand our EMA crossover trading strategy. This strategy uses three EMAs with different lookback periods and generates signals based on specific crossover criteria.

Here’s how the strategy works:

  1. Enter the market if: EMA 10 crosses above EMA 30 and EMA 10 is greater than EMA 50
  2. Exit the market if: EMA 10 crosses below EMA 30 and EMA 9 is lower than EMA 50

This straightforward crossover strategy aims to capture price movement directions and provide trading signals accordingly.

Now that we have a clear understanding of the trading strategy, let’s move on to the coding part to implement this idea into a practical strategy.

Importing Packages

First, we need to import all the necessary packages into our Python environment. For this article, we’ll be using the following packages:

  • Pandas — for data formatting, cleaning, manipulation, and other related tasks
  • Matplotlib — for creating charts and visualizations
  • eodhd — for extracting historical and technical indicator data
  • Termcolor — for customizing the standard output shown in Jupyter notebook
  • Math — for various mathematical functions and operations

Here’s the code to import these packages into our Python environment:

# IMPORTING PACKAGES

import pandas as pd
from eodhd import APIClient
import math
from termcolor import colored as cl
import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (20,10)
plt.style.use('fivethirtyeight')

If you haven’t installed any of these packages, make sure to do so using the pip command in your terminal.

API Key Activation

The next step is to activate your API key to use the EODHD stock screener API, which is included in the All-In-One and EOD+Intraday plans.

If you don’t have an EODHD API key, visit website, complete the registration process to create an account, and navigate to the Dashboard page to find your secret API key. Ensure that your API key remains confidential.

Here’s the code to activate your API key:

# API KEY ACTIVATION

api_key = '<YOUR API KEY>'
client = APIClient(api_key)

This code is straightforward. The first line stores the secret EODHD API key in the `api_key` variable. The second line uses the `APIClient` class provided by the `eodhd` package to activate the API key and stores the response in the `client` variable.

Remember to replace `<YOUR API KEY>` with your secret EODHD API key. For better security, you can also use environmental variables to store the API key instead of hardcoding it.

Getting the List of Delisted Stocks

Let’s extract the list of delisted stocks available on EODHD using the `get_list_of_tickers` function from the eodhd package. Here’s how you can do it:

# GETTING LIST OF DELISTED STOCKS

tickers = client.get_list_of_tickers(delisted = 1, code = ‘NASDAQ’)

tickers_df = pd.DataFrame(tickers)

tickers_df = tickers_df.fillna(‘None’)

tickers_df = tickers_df[tickers_df.Isin != ‘None’]

tickers_df

The `get_list_of_tickers` function has two parameters. The first is `delisted`, which should be set to 1 to fetch delisted stocks or 0 for listed stocks. The second parameter is `code`, where you specify the exchange of interest.

After retrieving the list of tickers, we convert the response into a Pandas dataframe. The final output, after some data cleaning and manipulation, looks like this:

One aspect I particularly appreciate about this function is that it not only provides a list of tickers but also includes additional information such as the stock’s name, exchange, current status, type, etc., which can be very useful in various scenarios.

Extracting Historical Data

For backtesting our strategy, we’ll focus on a delisted stock: AveXis, which is now known as Novartis Gene Therapies. AveXis was a biotechnology company based in Dallas, Texas, founded in 2012 and acquired by Novartis in 2018. The following code uses EODHD’s end-of-day data API endpoint to extract the historical data of AveXis:

# EXTRACTING HISTORICAL DATA

hist_json = client.get_eod_historical_stock_market_data(symbol = 'AVXS.US', period = 'd')
df = pd.DataFrame(hist_json)
df = df.set_index('date')

df

This code is straightforward. First, we use the `get_eod_historical_stock_market_data` function provided by the eodhd package to retrieve the historical data of AveXis from 2016, the year it went public.

Then, we convert the JSON response into a Pandas dataframe and perform some basic data preprocessing. The final output is a dataframe with the historical data:

The data covers just over two years, reflecting the short period AveXis was traded before its acquisition.

Extracting EMA Values

While some people prefer calculating the EMA values by manually coding the mathematical formulas, we will use a more streamlined approach by obtaining the EMA values through APIs. To do this, we will use EODHD’s technical indicator API endpoint. The following code uses this specific endpoint to retrieve the EMA values for AveXis:

# EXTRACTING EMA DATA

ema10 = pd.DataFrame(client.get_technical_indicator_data(ticker = 'AVXS.US', function = 'ema', period = 10)).set_index('date').rename(columns = {'ema':'ema10'})
ema30 = pd.DataFrame(client.get_technical_indicator_data(ticker = 'AVXS.US', function = 'ema', period = 30)).set_index('date').rename(columns = {'ema':'ema30'})
ema50 = pd.DataFrame(client.get_technical_indicator_data(ticker = 'AVXS.US', function = 'ema', period = 50)).set_index('date').rename(columns = {'ema':'ema50'})
adj_close = df[['adjusted_close']]

avxs_df = ema50.join([ema10, ema30, adj_close])
avxs_df.index = pd.to_datetime(avxs_df.index)

avxs_df.tail()

The code might seem complex at first glance, but it’s quite straightforward. In the first three lines, we are retrieving the EMA values with different lookback periods, each stored in separate dataframes. Finally, we combine these dataframes along with the stock’s adjusted close price into a single dataframe. Here is the final output:

Now that we have all the necessary data, let’s proceed to construct and backtest our 3-EMA crossover trading strategy.

Backtesting the Strategy

Now comes the most crucial and engaging part of the article: backtesting our trading strategy. The following code implements a straightforward backtesting system and reveals the results of our trading strategy:

# BACKTESTING THE STRATEGY

def implement_strategy(df, investment):

in_position = False
equity = investment

for i in range(1, len(df)):
if df['ema10'][i-1] < df['ema30'][i-1] and df['ema10'][i] > df['ema30'][i] and df['ema10'][i] > df['ema50'][i] and in_position == False:
no_of_shares = math.floor(equity/df.adjusted_close[i])
equity -= (no_of_shares * df.adjusted_close[i])
in_position = True
print(cl('BUY: ', color = 'green', attrs = ['bold']), f'{no_of_shares} Shares are bought at ${df.adjusted_close[i]} on {str(df.index[i])[:10]}')
elif df['ema10'][i-1] > df['ema30'][i-1] and df['ema10'][i] < df['ema30'][i] and df['ema10'][i] < df['ema50'][i] and in_position == True:
equity += (no_of_shares * df.adjusted_close[i])
in_position = False
print(cl('SELL: ', color = 'red', attrs = ['bold']), f'{no_of_shares} Shares are bought at ${df.adjusted_close[i]} on {str(df.index[i])[:10]}')
if in_position == True:
equity += (no_of_shares * df.adjusted_close[i])
print(cl(f'\nClosing position at {df.adjusted_close[i]} on {str(df.index[i])[:10]}', attrs = ['bold']))
in_position = False

earning = round(equity - investment, 2)
roi = round(earning / investment * 100, 2)

print('')
print(cl(f'EARNING: ${earning} ; ROI: {roi}%', attrs = ['bold']))

implement_strategy(avxs_df, 100000)

I won’t delve deeply into the mechanics of this code as it can be quite detailed. In essence, the program executes trades based on whether specific conditions are met. It enters the market when our entry condition is satisfied and exits when the exit condition is met. Here are the trades executed by our program followed by the backtesting results:

The results are quite intriguing. Over the span of two years, our program executed only three trades. This could be seen as both positive and negative, depending on the perspective. Ultimately, the focus should be on the returns of our strategy. Our 3-EMA trading strategy generated $664K with an ROI of 664.83%. This is indeed impressive news!

Buy/Hold Returns Comparison

An effective trading strategy should not only be profitable but should also outperform the buy/hold strategy. For those unfamiliar, the buy/hold strategy involves purchasing a stock and holding onto it regardless of market conditions for an extended period.

If our strategy surpasses the buy/hold strategy, it indicates that we have developed a strong trading strategy that is almost ready for real-world deployment. Conversely, if it doesn’t, we either need to make significant adjustments or discard it altogether. Essentially, this step will determine whether we proceed with our strategy.

The following code implements the buy/hold strategy and calculates the returns:

# BUY/HOLD STRATEGY RETURNS

bh_roi = round(list(avxs_df['adjusted_close'].pct_change().cumsum())[-1],4)*100
print(cl(f'BUY/HOLD STRATEGY ROI: {round(bh_roi,2)}%', attrs = ['bold']))

This code calculates the stock returns for the given data, and here is the output:

After comparing the buy/hold strategy results with our 3-EMA strategy, we see that our strategy outperformed the buy/hold strategy by 381% in terms of ROI. That’s incredible! It’s nearly 2.3 times the returns of the buy/hold strategy. We can now confidently say that we have developed a robust trading strategy.

Conclusion

To recap, we began by extracting a list of delisted stocks from EODHD and selected one for backtesting our strategy. We then obtained historical data and the EMA values with different lookback periods for that stock using EODHD’s end-of-day API endpoint and technical indicator API endpoint. After gathering the necessary data, we constructed and backtested our 3-EMA trading strategy. Finally, we compared our strategy’s results with the buy/hold strategy to validate its effectiveness.

There is still plenty of room for improvement that this article does not cover. For instance, implementing a proper risk management system could lead to a more systematic trading approach. Additionally, using machine learning models to optimize the strategy parameters could be beneficial. Although these aspects are not practically explored in this article, they are worth considering if you plan to deploy the strategy.

With that, we’ve reached the end of the article. I hope you found this information useful and insightful. Thank you for your time.

The original article was published in the EODHD Academy by Nikhil Adithyan.

Please note that this article is for informational purposes only and should not be taken as financial advice. We do not bear responsibility for any trading decisions made based on the content of this article. Readers are advised to conduct their own research or consult with a qualified financial professional before making any investment decisions.

For those eager to delve deeper into such insightful articles and broaden their understanding of different strategies in financial markets, we invite you to follow our account and subscribe for email notifications.

Stay tuned for more valuable articles that aim to enhance your data science skills and market analysis capabilities.

--

--

EODHD APIs
EODHD APIs

Written by EODHD APIs

eodhd.com — stock market fundamental and historical prices API for stocks, ETFs, mutual funds and bonds all over the world.

Responses (4)