Testing a Powerful Mean Reversion Trading Strategy Using Python

Backtesting using Apple’s stock tick data

EODHD APIs
8 min readApr 16, 2024

In the vast world of algorithmic trading, the challenge lies in sifting through and selecting the most reliable and profitable strategies. In this article, we will develop and evaluate a specific type of strategy known as mean reversion. We’ll demonstrate what makes this strategy effective, backtesting it in Python to assess its outcomes and profitability. For our backtesting, we’ll utilize the EODHD stock tick data API via the EODHD Python package. Let’s dive into the details without further delay!

Exploring the Backtesting of a Mean Reversion Strategy

Before we dive into the coding, it’s crucial to understand the strategy we’re about to develop. Today’s focus is on a mean-reversion trading strategy, which hinges on the premise that a stock’s price will revert to its average or ‘normal’ price following significant fluctuations.

The Relative Strength Index (RSI) is one of the most effective indicators for analyzing mean reversions. We’ll combine the RSI with the Simple Moving Average (SMA) to formulate our custom mean-reversion strategy.

Here’s how it works:

- Entry criteria: Enter the market when the RSI 10 falls below 30 and the price is above the SMA 200.

- Exit criteria: Exit the market when the RSI 10 rises above 70 and the price is below the SMA 200.

Although our strategy is straightforward, it remains a potent method in trading. Let’s proceed to the coding section to test the efficacy of our strategy.

Step 1/6: Importing Necessary Libraries

The initial step in crafting a mean-reversion trading strategy involves importing essential packages into the Python environment. This is an indispensable part of the setup. The primary packages include ‘EODHD’ for accessing historical stock data, ‘Pandas’ for data formatting and manipulations, and ‘Pandas TA-Lib’ for calculating technical indicators. Additionally, secondary packages like ‘Math’ for mathematical functions, ‘Datetime’ for date operations, and ‘Termcolor’ for font customization (optional) are also required.

# IMPORTING PACKAGES

import pandas as pd
from eodhd import APIClient
import pandas_ta as ta
from datetime import datetime
from termcolor import colored as cl
import math

With all necessary packages now imported into Python, we can move on to retrieving historical data for Tesla using the EODHD’s Python library. If you haven’t yet installed any of the imported packages, ensure you do so by executing the pip command in your terminal.

Step 2/6: Activating the API Key

The second step in developing a mean-reversion trading strategy involves activating your API key. To utilize the functions of the EODHD package, you must register your EODHD API key with it. If you don’t have an EODHD API key yet, start by visiting our website. Complete the registration to create an account, and then go to the settings page to locate your secret EODHD API key. Remember, it’s crucial to keep this API key confidential. Here’s how you can activate the API key with the following code:

# API KEY ACTIVATION

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

The code required is straightforward. Initially, we store the secret EODHD API key in the variable ‘api_key’. Next, we utilize the ‘APIClient’ class from the EODHD package to activate the API key, storing the activated client in the variable ‘client’.

Make sure to replace ‘<YOUR API KEY>’ with your actual secret EODHD API key. For enhanced security, instead of directly embedding your API key in the code, consider more secure methods like using environmental variables.

Step 3/6: Retrieving Historical Data

The third step in developing a mean-reversion trading strategy involves extracting stock data. For this tutorial, we’ll use the EODHD Stock Tick API, which offers detailed tick data for all US equities across all exchanges. This includes both current and delisted companies, covering around 80,000 tickers. Over 16,000 securities are updated daily, with the total data size amounting to approximately 10 terabytes. The dataset starts from January 1st, 2008, and is updated up to the present day. Here’s the code to extract tick data for Apple using the EODHD package:

# EXTRACTING HISTORICAL DATA

hist_json = client.get_stock_market_tick_data(from_timestamp = '1694354400', to_timestamp = '1694455200', symbol = 'AAPL')
df = pd.DataFrame(hist_json)

df.tail()

The code provided utilizes the ‘get_stock_market_tick_data’ function from the EODHD package to retrieve Apple’s tick data. This function requires three parameters:

  1. ‘from_timestamp’: The start date of the data in UNIX timestamp format.
  2. ‘to_timestamp’: The end date of the data in UNIX timestamp format.
  3. ‘symbol’: The ticker symbol of the stock.

Here is how you would extract data for a specific time range, from September 10th at 14:00 to September 11th at 18:00

The retrieved data contains several important columns that provide detailed insights into each stock transaction:

1. ex: The exchange where the stock is listed (for example, ‘Q’ denotes NASDAQ).

2. mkt: The market where the trade was executed.

3. price: The price of the stock at the time of the transaction.

4. seq: The sequence number of the trade.

5. shares: The number of shares involved in the transaction.

6. sl: Sales condition associated with the trade.

7. sub_mkt: The sub-market where the trade occurred.

8. ts: Timestamp of when the transaction took place.

This structure allows for a comprehensive analysis of market behavior and stock performance over specified periods.

Step 4/6: Organizing and Processing Data

The fourth step in developing a mean-reversion trading strategy involves data wrangling, which is essential for refining and restructuring the data into a more usable form. Let’s begin by eliminating unnecessary columns from our dataframe. Aside from the timestamp and stock price, the other columns are not needed for our analysis. Here’s how we can streamline our dataframe by removing these extraneous columns:

aapl_df = df[['price', 'ts']]

Next, we’ll address the format of the timestamp column, which is currently in UNIX format and can be cumbersome to work with. To make our data easier to analyze, let’s convert these timestamps into a more conventional date and time format:

aapl_df.ts = aapl_df.ts.apply(lambda x: x/1000)
aapl_df.ts = aapl_df.ts.apply(lambda x: datetime.utcfromtimestamp(x))

We won’t go into detailed code explanations here, but essentially, we’re using `apply` and `lambda` functions to handle our data manipulations. Additionally, we’re utilizing the `datetime` package to convert timestamps. Here’s what our final processed dataframe looks like after these adjustments:

Step 5/6: Calculating RSI & SMA

The fifth step in developing our mean-reversion trading strategy involves calculating the necessary technical indicators. While it’s possible to write the code from scratch to compute these indicators, doing so can be quite time-consuming. Therefore, we’ll utilize the Pandas TA-Lib package, which automatically calculates the indicators based on the provided data. Here’s how we calculate the Relative Strength Index (RSI) and Simple Moving Average (SMA) for Apple stock using this package:

# RSI & SMA CALCULATION

aapl_df['sma200'] = ta.sma(aapl_df.price, length = 200)
aapl_df['rsi10'] = ta.rsi(aapl_df.price, length = 10)
aapl_df = aapl_df.dropna().reset_index().drop('index', axis = 1)

aapl_df.tail()

As outlined in our trading strategy earlier, we are calculating the Relative Strength Index (RSI) with a lookback period of 10 and the Simple Moving Average (SMA) over 200 periods. Here is the output generated by the above code:

Step 6/6: Backtesting the Mean-Reversion Trading Strategy in Python

The sixth and final step in developing our mean-reversion trading strategy is to backtest it. This is the most crucial and thrilling part, as it allows us to evaluate whether our strategy is effective. Here is the code that implements our mean-reversion trading strategy and backtests it using the Apple stock tick data we previously extracted:

# CREATING & BACKTESTING THE STRATEGY

def implement_strategy(aapl_df, investment):

in_position = False
equity = investment

for i in range(3, len(aapl_df)):
if aapl_df['rsi10'][i] < 30 and aapl_df['price'][i] > aapl_df['sma200'][i] and in_position == False:
no_of_shares = math.floor(equity/aapl_df.price[i])
equity -= (no_of_shares * aapl_df.price[i])
in_position = True
print(cl('BUY: ', color = 'green', attrs = ['bold']), f'{no_of_shares} Shares are bought at ${aapl_df.price[i]} on {aapl_df.ts[i]}')
elif aapl_df['rsi10'][i] > 70 and aapl_df['price'][i] < aapl_df['sma200'][i] and in_position == True:
equity += (no_of_shares * aapl_df.price[i])
in_position = False
print(cl('SELL: ', color = 'red', attrs = ['bold']), f'{no_of_shares} Shares are bought at ${aapl_df.price[i]} on {aapl_df.ts[i]}')
if in_position == True:
equity += (no_of_shares * aapl_df.price[i])
print(cl(f'\nClosing position at {aapl_df.price[i]} on {aapl_df.ts[i]}', 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(aapl_df, 100000)

This backtesting code draws significant inspiration from Yong Hong Tan’s work on the SuperTrend indicator, as detailed in his article. A special thanks to him for the remarkable framework provided. The results from backtesting our strategy are quite intriguing. The strategy triggered a multitude of trades, sometimes even within milliseconds between each trade. This could suggest that our strategy might be producing false signals. However, setting that consideration aside, here are the results from the backtesting process:

It appears that our strategy achieved a positive return on investment (ROI) of 3.39% in a single day, which is quite impressive. This is certainly encouraging news.

Conclusion

In this article, we constructed a straightforward yet effective mean-reversion trading strategy and backtested it using Apple’s stock tick data, sourced from EODHD’s stock tick data API.

While our coding process was comprehensive, there remains substantial room for improvement. Notably, as mentioned earlier, our strategy generated a high volume of false trading signals, which could pose significant risks in actual trading scenarios. Therefore, it’s essential to refine the strategy parameters and possibly adjust the logic before implementing it in real-world markets. To enhance the reliability of the backtesting results, incorporating transaction costs for each trade executed could provide a more accurate picture. Additionally, implementing various risk management strategies can help safeguard trading operations.

Hopefully, our article has provided you with new and valuable insights. Thank you for your time and interest. Happy backtesting!

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

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.

We publish 2 pieces per week!

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

--

--

EODHD APIs

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