Problem Description
Solution
To use daily data for signals and minute data for execution in QuantConnect, you should follow this architectural pattern:
- Subscribe to Minute Resolution: Set your asset subscription to
Resolution.MINUTE. This ensures youron_datamethod triggers every minute, allowing for precise execution. - Create a Consolidator: Use a
TradeBarConsolidatorto aggregate the minute bars into daily bars. - Register Indicators to the Consolidator: Attach your indicators (e.g., SMA, RSI) to the daily consolidator. This ensures the indicators only update once a day using the aggregated daily data, rather than every minute.
Strategy Example
The following algorithm demonstrates this approach. It calculates a 20-day Simple Moving Average (SMA) using Daily bars but checks for entry/exit conditions and executes trades using Minute bars.
# region imports
from AlgorithmImports import *
# endregion
class DailySignalMinuteExecution(QCAlgorithm):
def initialize(self):
self.set_start_date(2023, 1, 1)
self.set_end_date(2023, 12, 1)
self.set_cash(100000)
# 1. Subscribe to the asset with Minute resolution for execution
self.symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
# 2. Define the indicator (Empty at first)
self.sma_daily = SimpleMovingAverage(20)
# 3. Create a Consolidator to aggregate Minute data into Daily data
# This creates a bar representing 1 day of data
daily_consolidator = TradeBarConsolidator(timedelta(days=1))
# 4. Bind an event handler to the consolidator (Optional, for logging/debugging)
daily_consolidator.data_consolidated += self.on_daily_bar_close
# 5. Register the indicator to the consolidator
# This ensures the SMA only updates when a full Daily bar is formed
self.register_indicator(self.symbol, self.sma_daily, daily_consolidator)
# 6. Add the consolidator to the subscription manager
self.subscription_manager.add_consolidator(self.symbol, daily_consolidator)
# Warm up the indicator using historical daily data
self.warm_up_indicator(self.symbol, self.sma_daily, Resolution.DAILY)
def on_data(self, data: Slice):
"""
This runs every MINUTE. We use this for execution logic.
"""
if not self.sma_daily.is_ready or self.symbol not in data:
return
# Get current minute price
current_price = data[self.symbol].close
# Get the latest daily indicator value
daily_sma_value = self.sma_daily.current.value
# Execution Logic:
# We are comparing the Minute price against the Daily SMA
if not self.portfolio.invested:
if current_price > daily_sma_value:
self.set_holdings(self.symbol, 1.0)
self.debug(f"BUY at {self.time} | Price: {current_price} | Daily SMA: {daily_sma_value}")
elif self.portfolio.invested:
if current_price < daily_sma_value:
self.liquidate(self.symbol)
self.debug(f"SELL at {self.time} | Price: {current_price} | Daily SMA: {daily_sma_value}")
def on_daily_bar_close(self, sender, bar):
"""
This runs once a DAY when the daily bar is consolidated.
Useful for logging or resetting daily variables.
"""
self.plot("Indicators", "Daily SMA", self.sma_daily.current.value)
Key Components Explained
-
self.add_equity(..., Resolution.MINUTE):
This is the most critical step. By setting the resolution to Minute, the LEAN engine feeds data toon_dataevery minute. If you set this to Daily, you would not be able to execute intraday. -
TradeBarConsolidator(timedelta(days=1)):
This object takes the stream of minute bars and "glues" them together. It waits until 24 hours (or a trading day) have passed, creates a singleTradeBar, and then releases it. -
self.register_indicator(...):
Standard indicators in QuantConnect update automatically with the data resolution of the security. By explicitly registering the indicator to thedaily_consolidator, we force the indicator to ignore the raw minute stream and only update when the consolidator produces a daily bar. -
self.warm_up_indicator(...):
Since the indicator needs 20 days of data to be ready, and we are running on minute resolution, waiting for 20 days of minute data to consolidate would take too long in a live scenario. This helper function fetches historical daily data immediately to prime the indicator.
Q&A
Q: Can I use self.history instead of consolidators?
A: Yes. You can request self.history(self.symbol, 20, Resolution.DAILY) inside on_data or a Scheduled Event. However, using Consolidators is generally more efficient computationally than requesting a history dataframe every single minute.
Q: Does the daily bar close at market close or midnight?
A: The timedelta(days=1) consolidator typically creates bars based on a 24-hour period. If you specifically need the bar to close exactly at Market Close (e.g., 4:00 PM ET), you should use the self.consolidate helper with a calendar rule, or a ScheduledEvent to sample the data at that specific time.
Q: Can I mix resolutions for different assets?
A: Yes. You can have SPY on Minute resolution and AAPL on Daily resolution in the same algorithm. However, on_data will trigger based on the finest resolution available (Minute).