🚀 AI One-Click Generation of quantconnect Strategy Code

How to Code an Overbought/Oversold RSI Strategy with QuantConnect

quantconnect | Published on: 2025/12/10
The following content is generated by EasyQuant .

Problem Description

Can you show me how to build a trading bot based on the RSI indicator?

Solution

Here is a complete, fully functional Python script for a Relative Strength Index (RSI) Mean Reversion trading bot on QuantConnect.

Strategy Overview

This strategy implements a classic Mean Reversion logic:

  1. Indicator: Uses a 14-period RSI on the daily timeframe.
  2. Entry Signal (Long): When the RSI drops below 30 (Oversold), the asset is considered undervalued, and the bot buys 100% of the portfolio.
  3. Exit Signal: When the RSI rises above 70 (Overbought), the asset is considered overvalued, and the bot liquidates the position.
  4. Asset: SPY (S&P 500 ETF).

QuantConnect Strategy Code

# region imports
from AlgorithmImports import *
# endregion

class RsiMeanReversionBot(QCAlgorithm):

    def initialize(self):
        """
        Initialize the algorithm settings, assets, and indicators.
        """
        # 1. Set Backtest Parameters
        self.set_start_date(2018, 1, 1)  # Start Date
        self.set_end_date(2023, 1, 1)    # End Date
        self.set_cash(100000)            # Starting Cash

        # 2. Add Assets
        # We use Resolution.DAILY for this example, but Resolution.MINUTE is also common.
        self.ticker = "SPY"
        self.equity = self.add_equity(self.ticker, Resolution.DAILY)
        self.symbol = self.equity.symbol

        # 3. Define Strategy Parameters
        self.rsi_period = 14
        self.oversold_threshold = 30
        self.overbought_threshold = 70
        
        # 4. Initialize Indicators
        # We use the built-in RSI helper method.
        # MovingAverageType.WILDERS is the standard smoothing for RSI.
        self.rsi_indicator = self.rsi(self.symbol, self.rsi_period, MovingAverageType.WILDERS, Resolution.DAILY)

        # 5. Set WarmUp
        # Automatically feed historical data to ready the indicator before the start date
        self.set_warm_up(self.rsi_period)

        # 6. Setup Charts (Optional but recommended for visualization)
        chart = Chart("RSI Analysis")
        chart.add_series(Series("RSI", SeriesType.LINE, 0))
        chart.add_series(Series("Overbought", SeriesType.LINE, 0))
        chart.add_series(Series("Oversold", SeriesType.LINE, 0))
        self.add_chart(chart)

    def on_data(self, data: Slice):
        """
        Event handler for new data packets.
        """
        # Ensure the indicator is ready before trading
        if self.is_warming_up or not self.rsi_indicator.is_ready:
            return

        # Ensure data exists for the symbol in the current slice
        if self.symbol not in data:
            return

        # Get current RSI value
        current_rsi = self.rsi_indicator.current.value

        # Plotting for visualization
        self.plot("RSI Analysis", "RSI", current_rsi)
        self.plot("RSI Analysis", "Overbought", self.overbought_threshold)
        self.plot("RSI Analysis", "Oversold", self.oversold_threshold)

        # --- Trading Logic ---

        # Check if we are not currently invested
        if not self.portfolio.invested:
            # Buy Signal: RSI is Oversold (< 30)
            if current_rsi < self.oversold_threshold:
                self.set_holdings(self.symbol, 1.0) # Allocate 100% of portfolio
                self.debug(f"BUY {self.ticker} at {self.time} | Price: {data[self.symbol].close} | RSI: {current_rsi:.2f}")

        # Check if we are currently invested
        else:
            # Sell Signal: RSI is Overbought (> 70)
            if current_rsi > self.overbought_threshold:
                self.liquidate(self.symbol) # Sell all holdings
                self.debug(f"SELL {self.ticker} at {self.time} | Price: {data[self.symbol].close} | RSI: {current_rsi:.2f}")

    def on_end_of_algorithm(self):
        """
        Called at the end of the backtest.
        """
        self.log(f"Final Portfolio Value: {self.portfolio.total_portfolio_value}")

Code Breakdown

  1. initialize Method:

    • set_warm_up: This is crucial for indicator-based strategies. It processes data prior to the StartDate so that on the very first day of the backtest, the RSI indicator already has a valid value (is "ready").
    • self.rsi(...): This helper creates the indicator and automatically registers it to receive data updates. We use MovingAverageType.WILDERS because the standard RSI formula uses Wilder's Smoothing.
  2. on_data Method:

    • self.is_warming_up: Prevents the algorithm from trading while it is still processing the warm-up data.
    • self.portfolio.invested: A boolean property that checks if you currently hold any quantity of the asset.
    • set_holdings(symbol, 1.0): A helper function that calculates the number of shares required to allocate 100% of your buying power to this specific asset.
    • liquidate(symbol): Closes all positions (long or short) for the specified symbol.
  3. Visualization:

    • The code includes self.plot commands. When you run this backtest, you will see a custom chart below the equity curve showing the RSI oscillating between the Overbought (70) and Oversold (30) lines.

Frequently Asked Questions (Q&A)

Q: How can I change the timeframe from Daily to Minute?
A: In the initialize method, change Resolution.DAILY to Resolution.MINUTE in both the add_equity call and the self.rsi indicator definition. Note that RSI signals on minute data will be much more frequent and may result in higher transaction costs.

Q: Can I use this strategy for Shorting?
A: Yes. To short the market, you would modify the logic to self.set_holdings(self.symbol, -1.0) when the RSI is Overbought (> 70) and liquidate when it returns to neutral or becomes Oversold.

Q: Why is MovingAverageType.WILDERS used?
A: The original Relative Strength Index developed by J. Welles Wilder uses Wilder's Smoothing (a specific type of Exponential Moving Average). While you can use Simple or Exponential moving averages, Wilder's is the standard calculation found on most trading platforms (like TradingView).

Q: How do I add a Stop Loss?
A: You can add a stop loss by using self.stop_market_order immediately after placing your entry trade, or by checking the price in on_data and calling self.liquidate if the price drops below a certain percentage relative to your average entry price (self.portfolio[self.symbol].average_price).