Generate code in 3 minutes
Enter your idea and AI generates runnable code.
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.
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.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.Insight: The return object containing the Symbol, TimeDelta (duration), InsightDirection (Up, Down, Flat), and optionally magnitude/confidence.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
AlphaModel.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.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.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: 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.