🚀 AI One-Click Generation of quantconnect Strategy Code

A Developer's Guide to Building a Custom Alpha Model in the QuantConnect Framework

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

Problem Description

How do I create a custom alpha model for the algorithm framework?

Solution

To create a custom Alpha Model in the QuantConnect Algorithm Framework, you need to define a class that inherits from AlphaModel. This class is responsible for analyzing data and emitting Insight objects, which predict the future direction and magnitude of assets.

Key Components of a Custom Alpha Model

  1. update(self, algorithm, data): This is the core method. It runs every time a new Slice of data is available. It returns a list of Insight objects.
  2. on_securities_changed(self, algorithm, changes): This method manages the setup and teardown of indicators or data specific to assets as they enter or leave the universe.
  3. Insight: The return object containing the Symbol, TimeDelta (duration), InsightDirection (Up, Down, Flat), and optionally magnitude/confidence.

Complete Example: EMA Crossover Alpha

Below is a complete, runnable example of a custom Alpha Model that uses an Exponential Moving Average (EMA) crossover strategy. It includes a helper class to manage indicators for each specific symbol.

# region imports
from AlgorithmImports import *
# endregion

class CustomAlphaModelAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2023, 1, 1)
        self.set_cash(100000)

        # 1. Select a Universe
        self.set_universe_selection(
            ManualUniverseSelectionModel([Symbol.create("SPY", SecurityType.EQUITY, Market.USA)])
        )

        # 2. Set the Custom Alpha Model
        self.set_alpha(EmaCrossoverAlphaModel(fast_period=12, slow_period=26))

        # 3. Set Portfolio Construction (Equal Weighting)
        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())

        # 4. Set Execution Model (Immediate)
        self.set_execution(ImmediateExecutionModel())

class EmaCrossoverAlphaModel(AlphaModel):
    """
    Emits 'Up' insights when Fast EMA > Slow EMA, and 'Down' insights otherwise.
    """
    
    def __init__(self, fast_period=12, slow_period=26, resolution=Resolution.DAILY):
        self.fast_period = fast_period
        self.slow_period = slow_period
        self.resolution = resolution
        self.prediction_interval = Time.multiply(Extensions.to_time_span(resolution), 1)
        self.symbol_data_by_symbol = {}

    def update(self, algorithm: QCAlgorithm, data: Slice) -> List[Insight]:
        """
        Called each time the algorithm receives data.
        Returns a list of Insights.
        """
        insights = []

        for symbol, symbol_data in self.symbol_data_by_symbol.items():
            # Ensure indicators are ready and we have data for this symbol
            if not symbol_data.is_ready or symbol not in data:
                continue

            # Logic: Check for Crossover
            if symbol_data.fast.current.value > symbol_data.slow.current.value:
                # Generate an UP insight with a duration of 1 bar
                insights.append(Insight.price(symbol, self.prediction_interval, InsightDirection.UP))
            
            elif symbol_data.fast.current.value < symbol_data.slow.current.value:
                # Generate a DOWN insight
                insights.append(Insight.price(symbol, self.prediction_interval, InsightDirection.DOWN))

        return insights

    def on_securities_changed(self, algorithm: QCAlgorithm, changes: SecurityChanges) -> None:
        """
        Called when securities are added or removed from the universe.
        """
        # Clean up data for removed securities
        for security in changes.removed_securities:
            if security.symbol in self.symbol_data_by_symbol:
                self.symbol_data_by_symbol.pop(security.symbol)

        # Initialize data for added securities
        for security in changes.added_securities:
            if security.symbol not in self.symbol_data_by_symbol:
                self.symbol_data_by_symbol[security.symbol] = SymbolData(
                    algorithm, 
                    security.symbol, 
                    self.fast_period, 
                    self.slow_period, 
                    self.resolution
                )

class SymbolData:
    """
    Helper class to store indicators and state for a specific symbol.
    """
    def __init__(self, algorithm, symbol, fast_period, slow_period, resolution):
        self.symbol = symbol
        
        # Define Indicators
        self.fast = algorithm.EMA(symbol, fast_period, resolution)
        self.slow = algorithm.EMA(symbol, slow_period, resolution)
        
        # Warm up indicators using historical data
        history = algorithm.history(symbol, slow_period, resolution)
        if not history.empty:
            # History requests return a DataFrame; iterate to update indicators
            for time, row in history.loc[symbol].iterrows():
                if "close" in row:
                    self.fast.update(time, row["close"])
                    self.slow.update(time, row["close"])

    @property
    def is_ready(self):
        return self.fast.is_ready and self.slow.is_ready

Implementation Details

  1. Inheritance: The class must inherit from AlphaModel.
  2. SymbolData Pattern: Notice the SymbolData class. Since the Alpha Model handles the entire universe, you should not store single variables like self.ema in the main Alpha class. Instead, use a dictionary (self.symbol_data_by_symbol) to map every Symbol to its own set of indicators.
  3. Warm Up: In on_securities_changed, when a new security is added, we immediately request algorithm.history to warm up the indicators. This ensures the model can emit insights immediately rather than waiting 26 days (in this example) for the EMA to fill up.
  4. Insight Duration: Insight.price requires a duration (how long the prediction is valid). In the example, we use Time.multiply(Extensions.to_time_span(resolution), 1) to make the insight valid for exactly one bar (e.g., one day).

Q&A

Q: Can I emit multiple insights for different symbols in one update?
A: Yes. The update method returns a List[Insight]. You can append as many insights as necessary for different symbols within that list.

Q: What happens if I don't emit an insight for a symbol that previously had one?
A: Insights have an expiration time (the duration you set). If an insight expires and no new insight is emitted to replace it, the Portfolio Construction Model will generally liquidate the position or reduce it to zero, depending on the specific PCM logic.

Q: How do I combine multiple Alpha Models?
A: You can use the CompositeAlphaModel.

self.set_alpha(CompositeAlphaModel(RsiAlphaModel(), EmaCrossoverAlphaModel()))

Q: Do I have to use indicators?
A: No. You can use any logic inside the update method, including alternative data, fundamental data, or machine learning predictions, as long as you return Insight objects.