🚀 AI One-Click Generation of quantconnect Strategy Code

QuantConnect Python Example: Implementing a MACD Crossover Trading Strategy

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

Problem Description

Please write a MACD crossover trading strategy in Python.

Solution

Here is a complete, robust MACD (Moving Average Convergence Divergence) Crossover strategy implemented in Python for the QuantConnect Lean engine.

This strategy uses the standard MACD parameters (12, 26, 9) on the SPY ETF. It buys when the MACD line crosses above the Signal line and liquidates when the MACD line crosses below the Signal line.

MACD Crossover Strategy Code

# region imports
from AlgorithmImports import *
# endregion

class MACDCrossoverStrategy(QCAlgorithm):

    def initialize(self):
        """
        Initialize the algorithm settings, data subscriptions, and indicators.
        """
        # 1. Set Strategy Settings
        self.set_start_date(2018, 1, 1)    # Set Start Date
        self.set_end_date(2023, 1, 1)      # Set End Date
        self.set_cash(100000)              # Set Strategy Cash

        # 2. Add Data
        # We use Daily resolution for a standard trend-following approach
        self.ticker = "SPY"
        self.equity = self.add_equity(self.ticker, Resolution.DAILY)
        self.symbol = self.equity.symbol

        # 3. Define MACD Indicator
        # Parameters: Fast=12, Slow=26, Signal=9
        # Moving Average Type: Exponential (standard for MACD)
        self.macd_indicator = self.macd(
            self.symbol, 
            12, 
            26, 
            9, 
            MovingAverageType.EXPONENTIAL, 
            Resolution.DAILY
        )

        # 4. Set WarmUp
        # We need at least 26 bars for the slow EMA to calculate correctly before trading
        self.set_warm_up(26, Resolution.DAILY)

    def on_data(self, data: Slice):
        """
        Event handler for new data.
        """
        # 1. Check if data exists and indicator is ready
        if not self.macd_indicator.is_ready or self.symbol not in data:
            return

        # 2. Get current indicator values
        # The MACD object has a .current.value (the MACD line) 
        # and a .signal.current.value (the Signal line)
        macd_value = self.macd_indicator.current.value
        signal_value = self.macd_indicator.signal.current.value
        
        # Optional: Plotting for visual debugging
        self.plot("MACD", "MACD", macd_value)
        self.plot("MACD", "Signal", signal_value)
        self.plot("MACD", "Zero", 0)

        # 3. Trading Logic
        # Check if we are currently invested
        if not self.portfolio.invested:
            # Buy Signal: MACD crosses ABOVE Signal line
            if macd_value > signal_value:
                self.set_holdings(self.symbol, 1.0)
                self.debug(f"Buy Signal at {self.time}: MACD {macd_value} > Signal {signal_value}")
        
        else:
            # Sell Signal: MACD crosses BELOW Signal line
            if macd_value < signal_value:
                self.liquidate(self.symbol)
                self.debug(f"Sell Signal at {self.time}: MACD {macd_value} < Signal {signal_value}")

Strategy Explanation

  1. Initialization (initialize):

    • add_equity: We subscribe to SPY data with Resolution.DAILY. Daily resolution is generally preferred for MACD to reduce noise compared to Minute data.
    • self.macd: This helper method creates the indicator and automatically registers it to receive data updates. We use the standard settings: Fast EMA (12), Slow EMA (26), and Signal Line (9).
    • set_warm_up: This is crucial. It tells the engine to feed historical data into the indicator before the start date (or before the first live loop) so the MACD values are mathematically correct immediately upon starting.
  2. Execution (on_data):

    • is_ready: We ensure the indicator has enough data points before attempting to access its values to prevent errors.
    • Signal Logic:
      • Entry: We check if macd_value > signal_value. If we are not currently holding the asset, we allocate 100% of the portfolio (set_holdings(..., 1.0)).
      • Exit: We check if macd_value < signal_value. If we are holding the asset, we liquidate (sell all) positions.
    • Plotting: The self.plot commands will generate a custom chart in the QuantConnect results page, allowing you to visually verify the crossovers against the trade execution.

Q&A

Q: How can I change the MACD parameters to make the strategy more sensitive?
A: You can modify the parameters in the self.macd call within the initialize method. For example, self.macd(self.symbol, 5, 35, 5, ...) would use a 5-period fast EMA, 35-period slow EMA, and 5-period signal line. Lower numbers generally make the indicator more sensitive to price changes but may produce more false signals.

Q: Can I use this strategy on Minute resolution data?
A: Yes. Change Resolution.DAILY to Resolution.MINUTE in both the add_equity call and the self.macd definition. However, note that MACD crossovers on minute data can be very "noisy," leading to high transaction costs due to frequent trading.

Q: How do I add a Stop Loss to this strategy?
A: You can implement a stop loss by tracking your entry price. In on_data, if self.portfolio[self.symbol].invested, check if the current price is below self.portfolio[self.symbol].average_price * 0.95 (a 5% stop loss). If true, call self.liquidate().

Q: Why do we need set_warm_up?
A: Indicators like MACD rely on past data (Moving Averages). If you start the algorithm without warming up, the first 26 data points will be used just to build the initial value of the indicator, meaning no trades would occur during that time, or the indicator values would be incorrect (0) at the very start. set_warm_up pre-loads this data.