🚀 AI One-Click Generation of quantconnect Strategy Code

A Step-by-Step Guide to Implementing a Mean Reversion Trading Strategy in QuantConnect

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

Problem Description

Can you help me write a mean reversion trading strategy in Python?

Solution

Here is a complete, robust Mean Reversion trading strategy implemented in Python for the QuantConnect Lean engine.

Strategy Logic

This strategy utilizes Bollinger Bands to identify overbought and oversold conditions.

  1. Indicator: 20-period Simple Moving Average (SMA) with 2 Standard Deviation bands.
  2. Entry Long: When the price crosses below the Lower Bollinger Band (indicating the asset is oversold).
  3. Entry Short: When the price crosses above the Upper Bollinger Band (indicating the asset is overbought).
  4. Exit: When the price reverts to the Middle Band (the mean).

Python Code

# region imports
from AlgorithmImports import *
# endregion

class BollingerMeanReversion(QCAlgorithm):

    def initialize(self):
        # 1. Set Strategy Settings
        self.set_start_date(2018, 1, 1)
        self.set_end_date(2023, 1, 1)
        self.set_cash(100000)

        # 2. Add Security
        # We use SPY (S&P 500 ETF) for this example
        self.ticker = "SPY"
        self.equity = self.add_equity(self.ticker, Resolution.DAILY)
        self.symbol = self.equity.symbol

        # 3. Define Parameters
        self.period = 20
        self.k = 2  # Standard Deviations

        # 4. Initialize Indicators
        # Create a Bollinger Band indicator
        self.bb = self.bb(self.symbol, self.period, self.k, MovingAverageType.SIMPLE, Resolution.DAILY)

        # 5. Set WarmUp
        # Ensure indicators have enough data before the algorithm starts trading
        self.set_warm_up(self.period)

    def on_data(self, data: Slice):
        # Check if the algorithm is warming up or if data is missing
        if self.is_warming_up or not self.bb.is_ready or self.symbol not in data:
            return

        # Get current price and indicator values
        price = self.securities[self.symbol].close
        upper_band = self.bb.upper_band.current.value
        middle_band = self.bb.middle_band.current.value
        lower_band = self.bb.lower_band.current.value
        
        # Current position quantity
        holdings = self.portfolio[self.symbol].quantity

        # --- Entry Logic ---
        if holdings == 0:
            # Long Condition: Price falls below the lower band (Oversold)
            if price < lower_band:
                self.set_holdings(self.symbol, 1.0)
                self.plot("Trade Plot", "Price", price)
                self.plot("Trade Plot", "Lower Band", lower_band)
                self.debug(f"Buy Signal at {self.time}: Price {price} < Lower Band {lower_band}")

            # Short Condition: Price rises above the upper band (Overbought)
            elif price > upper_band:
                self.set_holdings(self.symbol, -1.0)
                self.plot("Trade Plot", "Price", price)
                self.plot("Trade Plot", "Upper Band", upper_band)
                self.debug(f"Sell Signal at {self.time}: Price {price} > Upper Band {upper_band}")

        # --- Exit Logic ---
        else:
            # Exit Long: Price returns to the mean (Middle Band)
            if holdings > 0 and price >= middle_band:
                self.liquidate(self.symbol)
                self.debug(f"Take Profit (Long) at {self.time}: Price {price} reached Mean {middle_band}")

            # Exit Short: Price returns to the mean (Middle Band)
            elif holdings < 0 and price <= middle_band:
                self.liquidate(self.symbol)
                self.debug(f"Take Profit (Short) at {self.time}: Price {price} reached Mean {middle_band}")

    def on_end_of_algorithm(self):
        self.log(f"Final Portfolio Value: {self.portfolio.total_portfolio_value}")

Key Implementation Details

  • initialize: Sets up the backtest timeframe, cash, and initializes the Bollinger Band indicator using self.bb. We use self.set_warm_up to prevent the algorithm from trading before the indicator has calculated valid values.
  • on_data: This is the event handler for market data. It checks if the indicators are ready, retrieves the current price, and compares it against the bands.
  • set_holdings: Used to allocate portfolio weight. 1.0 means 100% long, -1.0 means 100% short.
  • liquidate: Closes all positions for the specific symbol immediately.

Q&A: Mean Reversion Strategies

Q: Why use Bollinger Bands for mean reversion?
A: Bollinger Bands dynamically adjust to market volatility. In high volatility, the bands widen, requiring a more significant price deviation to trigger a trade. In low volatility, they contract. This makes them more adaptive than static price thresholds.

Q: What are the risks of this strategy?
A: The primary risk is "momentum" or "trending" markets. If the price crosses the lower band and continues to crash (a strong downtrend), a mean reversion strategy will buy too early and suffer losses. To mitigate this, traders often add a Stop Loss or filter trades using a trend indicator (like ADX) to avoid trading against strong trends.

Q: Can I use this on minute resolution data?
A: Yes. You can change Resolution.DAILY to Resolution.MINUTE in the add_equity and bb indicator calls. However, lower timeframes often have more "noise," which may result in more false signals and higher transaction costs.

Q: How do I handle short selling restrictions?
A: The code above assumes a margin account that allows shorting. If you are trading in a cash account or an asset class that cannot be shorted easily (like some crypto spot pairs without margin), you should remove the elif price > upper_band: block in the Entry Logic.