I Tested an EMA + RSI Strategy on the 50 Largest S&P 500 Companies. Here Are the?Result

I Tested an EMA + RSI Strategy on the 50 Largest S&P 500 Companies. Here Are the?Result

Introduction

Imagine having a strategy that could help you identify potential reversals in the market while also confirming the overall trend direction. Combining the Relative Strength Index (RSI) with an Exponential Moving Average (EMA) offers a powerful method to do just that. In this article, we’ll explore how these two indicators can work together to create a robust trading strategy. We’ll also dive into the details of each indicator and how they can be implemented in Python to backtest their effectiveness on various assets.

What is RSI?

The Relative Strength Index (RSI) is a momentum oscillator that measures the speed and change of price movements. It ranges from 0 to 100 and is typically used to identify overbought or oversold conditions in a market.

  • Overbought: When the RSI is above 70, it suggests that the asset may be overbought and could be due for a pullback.
  • Oversold: When the RSI is below 30, it indicates that the asset might be oversold and could be due for a rebound.

RSI is particularly useful in spotting potential reversal points, especially when the market is trending strongly in one direction.

What is EMA?

The Exponential Moving Average (EMA) is a type of moving average that gives more weight to recent prices, making it more responsive to new information compared to a Simple Moving Average (SMA). The EMA smooths out price data to create a trend-following indicator.

  • EMA Crossovers: When a short-term EMA crosses above a long-term EMA, it signals a potential upward trend, and vice versa for a downward trend.

EMAs are widely used in various trading strategies to confirm the direction of the trend and filter out market noise.


RSI + EMA Trading Strategy

This strategy leverages both the RSI and EMA indicators to find trading opportunities. The RSI helps identify potential reversals, while the EMA confirms the overall trend direction.

Here’s how the strategy works:

  1. Buy Signal:

  • The RSI crosses above 30 (indicating the asset is recovering from oversold conditions).
  • The price is above the 50-period EMA, confirming an uptrend.

2. Sell Signal:

  • The RSI crosses below 70 (indicating the asset might be reversing from overbought conditions).
  • The price is below the 50-period EMA, confirming a downtrend.

Implementing the Strategy in Python

To implement this strategy, we’ll use Python along with libraries like yfinance to download historical price data, pandas for data manipulation, and ta for calculating technical indicators like RSI and EMA.

Code Implementation

Below is the Python code that implements and backtests the RSI + EMA strategy on various assets:

import yfinance as yf
import pandas as pd
from backtesting import Backtest, Strategy
import stockstats
import openpyxl

# List of top 50 S&P 500 companies by market cap
top_50_sp500 = [
    'AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'BRK-B', 'TSLA', 'NVDA', 'JPM', 'JNJ',
    'V', 'UNH', 'HD', 'PG', 'DIS', 'MA', 'PYPL', 'VZ', 'ADBE', 'NFLX',
    'INTC', 'CMCSA', 'KO', 'PFE', 'PEP', 'T', 'XOM', 'CSCO', 'ABT', 'MRK',
    'NKE', 'ABBV', 'CRM', 'AVGO', 'MCD', 'QCOM', 'TXN', 'ACN', 'MDT', 'COST',
    'NEE', 'DHR', 'WMT', 'AMGN', 'HON', 'IBM', 'GE', 'LOW', 'CAT', 'BA'
]

# Parameters
start_date = '2020-01-01'
end_date = '2023-01-01'
cash = 10000
commission = 0.002

# Function to calculate RSI and EMA using stockstats
def calculate_rsi_ema(data, rsi_window=14, ema_window=50):
    stock = stockstats.StockDataFrame.retype(data)
    data['RSI'] = stock[f'rsi_{rsi_window}']
    data['EMA'] = stock[f'close_{ema_window}_ema']
    return data[['RSI', 'EMA']]

# Define the RSI + EMA Strategy
class RSI_EMAStrategy(Strategy):
    def init(self):
        try:
            print(f"Initializing strategy for the ticker...")  # General trace
            # Calculate RSI and EMA indicators
            indicators = calculate_rsi_ema(pd.DataFrame({
                'close': self.data.Close,
                'high': self.data.High,
                'low': self.data.Low,
                'volume': self.data.Volume
            }))
            self.rsi = self.I(lambda: indicators['RSI'])
            self.ema = self.I(lambda: indicators['EMA'])
            print("Indicators calculated.")  # Confirmation that indicators were calculated
        except Exception as e:
            print(f"Error during initialization: {e}")

    def next(self):
        try:
            rsi_value = self.rsi[-1]
            ema_value = self.ema[-1]
            close_price = self.data.Close[-1]

            print(f"Processing day {self.data.index[-1]}: Close price = {close_price}, RSI = {rsi_value}, EMA = {ema_value}")

            # Check for valid RSI and EMA values
            if pd.isna(rsi_value) or pd.isna(ema_value):
                print("Invalid indicator values, skipping this day.")
                return

            # Buy when RSI crosses above 30 and price is above 50 EMA
            if not self.position and rsi_value > 30 and close_price > ema_value:
                print("Buy signal detected.")
                self.buy()
            # Sell when RSI crosses below 70 and price is below 50 EMA
            elif self.position and rsi_value < 70 and close_price < ema_value:
                print("Sell signal detected.")
                self.position.close()
        except Exception as e:
            print(f"Error during the processing of day {self.data.index[-1]}: {e}")

# Function to run the backtest for a list of tickers
def run_backtests(tickers, start_date, end_date, cash, commission):
    all_metrics = []

    for ticker in tickers:
        print(f"\nProcessing ticker: {ticker}")  # Indicate which ticker is being processed
        try:
            # Download historical data
            data = yf.download(ticker, start=start_date, end=end_date)
            print(f"Data downloaded for {ticker}.")  # Confirmation that the data was downloaded

            if data.empty:
                print(f"No data for {ticker}. Skipping...")  # Trace if no data is available
                continue

            # Verify the first few data points downloaded
            print(f"First data points downloaded for {ticker}:\n{data.head()}")

            # Ensure there is sufficient data to calculate indicators
            if len(data) < 50:
                print(f"Not enough data to calculate indicators for {ticker}. Skipping...")  # Trace if insufficient data
                continue

            # Execute the backtest
            bt = Backtest(data, RSI_EMAStrategy, cash=cash, commission=commission)
            stats = bt.run()
            print(f"Backtest completed for {ticker}.")  # Confirmation that the backtest was completed

            # Collect metrics
            metrics = {
                "Stock": ticker,
                "Start": stats['Start'],
                "End": stats['End'],
                "Duration": stats['Duration'],
                "Equity Final [$]": stats['Equity Final [$]'],
                "Equity Peak [$]": stats['Equity Peak [$]'],
                "Return [%]": stats['Return [%]'],
                "Buy & Hold Return [%]": stats['Buy & Hold Return [%]'],
                "Max. Drawdown [%]": stats['Max. Drawdown [%]'],
                "Avg. Drawdown [%]": stats['Avg. Drawdown [%]'],
                "Max. Drawdown Duration": stats['Max. Drawdown Duration'],
                "Trades": stats['# Trades'],
                "Win Rate [%]": stats['Win Rate [%]'],
                "Best Trade [%]": stats['Best Trade [%]'],
                "Worst Trade [%]": stats['Worst Trade [%]'],
                "Avg. Trade [%]": stats['Avg. Trade [%]'],
                "Max. Trade Duration": stats['Max. Trade Duration'],
                "Avg. Trade Duration": stats['Avg. Trade Duration'],
                "Profit Factor": stats['Profit Factor'],
                "Expectancy [%]": stats['Expectancy [%]'],
                "Sharpe Ratio": stats['Sharpe Ratio'],
                "Sortino Ratio": stats['Sortino Ratio'],
            }

            all_metrics.append(metrics)

        except Exception as e:
            print(f"Error processing {ticker}: {e}")  # Display any errors during processing

    # Convert to DataFrame
    metrics_df = pd.DataFrame(all_metrics)

    # Save to Excel
    metrics_df.to_excel("top_50_sp500_rsi_ema_metrics.xlsx", index=False)
    print("Results saved to Excel.")  # Confirmation that the results were saved

    return metrics_df

# Run the backtests
metrics_df = run_backtests(top_50_sp500, start_date, end_date, cash, commission)

# Print the results
print(metrics_df)        

This code implements a backtesting strategy for analyzing stock performance based on two technical indicators: the RSI (Relative Strength Index) and the EMA (Exponential Moving Average). It uses the yfinance library to download historical price data and then runs backtests using the Backtesting library.

General Workflow of the Code:

  1. Parameter Setup: The main parameters are defined, such as the start and end dates for the backtest, a list of the top 50 S&P 500 companies, the initial capital, and the commission per trade.
  2. Indicator Calculation: The calculate_rsi_ema function uses the stockstats library to calculate the RSI and EMA from the historical price data downloaded for each stock. These indicators are used to generate buy and sell signals.
  3. RSI + EMA Strategy: The strategy (RSI_EMAStrategy) is defined to buy when the RSI crosses above 30 and the price is above the 50-day EMA, and to sell when the RSI crosses below 70 and the price is below the EMA.
  4. Backtest Execution: The run_backtests function runs the defined strategy for each stock in the list, records performance metrics, and finally saves the results in an Excel file.

Importance of the Libraries:

  • stockstats: This library is crucial because it simplifies the calculation of technical indicators like RSI and EMA. While these calculations can be done manually, stockstats automates the process, saving time and reducing the risk of errors. It’s a valuable tool for anyone performing technical analysis in Python, providing an efficient and reliable way to calculate a wide range of indicators from historical price data.
  • yfinance: Allows for easy and efficient downloading of historical data, which is essential for analysis and backtesting of trading strategies.
  • Backtesting: Facilitates the simulation of trading strategies, providing an environment to test and evaluate how different strategies would have performed in the past.
  • openpyxl: Used to save the analysis results in an Excel file, allowing for easy review and further analysis of the obtained metrics.

Installing these libraries is important to ensure that the code works correctly and to leverage their functionalities, which simplify and enhance the process of technical analysis and backtesting.

Results of Experiment

In general, after seeing results, the strategy is not working really good , only in a few examples: PYPL, QCOM and CSCO is better than buy and hold strategy

Finding good strategies is difficult, but we should test and experiment with the data!

Conclusion

The RSI + EMA strategy offers a compelling way to combine momentum and trend-following indicators into a cohesive trading strategy. While the initial results were promising, further testing and optimization could enhance its performance across different market conditions. This strategy serves as a solid foundation for traders looking to capture both reversals and trends, making it a valuable addition to any trading toolkit. As with any trading strategy, continuous refinement and testing are crucial to adapting to the ever-changing market landscape.

Follow me on Linkedin https://www.dhirubhai.net/in/kevin-meneses-897a28127/

and Medium https://medium.com/@kevinmenesesgonzalez/subscribe

Subscribe to the Data Pulse Newsletter https://www.dhirubhai.net/newsletters/datapulse-python-finance-7208914833608478720

Join my Patreon Community https://patreon.com/user?u=29567141&utm_medium=unknown&utm_source=join_link&utm_campaign=creatorshare_creator&utm_content=copyLink

sahar gharib

Junior Web Developer | Python, HTML, CSS, Java Scripts, Django

1 个月

Very informative

要查看或添加评论,请登录

社区洞察

其他会员也浏览了