Consolidating Data
Updating Indicators
Introduction
You can use consolidators to automatically update indicators in your algorithms. The consolidators can update your indicators at each time step or with aggregated bars. By default, LEAN updates data point indicators with the close price of the consolidated bars, but you can change it to a custom data field.
Standard Indicator Periods
If your algorithm has a static universe, you can create automatic indicators in just one line of code. When you create an automatic indicator, LEAN creates a consolidator, hooks it up for automatic updates, and then updates the indicator with the consolidated bars.
# Consolidate minute SPY data into 14-bar daily indicators ema = self.ema("SPY", 14, Resolution.DAILY) sma = self.sma("SPY", 14, Resolution.DAILY)
If your algorithm has a dynamic universe, create a manual indicator and then create a time period consolidator that updates the indicator at each time step. Keep a reference to the consolidator so you can remove it when your algorithm removes the security from the universe.
def initialize(self): self._ema = ExponentialMovingAverage(14) self._consolidator = TradeBarConsolidator(1) self._consolidator.data_consolidated += self._on_data_consolidated self.subscription_manager.add_consolidator(self._symbol, self._consolidator) # Define consolidator handler function as a class method def _on_data_consolidated(self, sender, bar): self._ema.update(bar.end_time, bar.close)
Custom Indicator Periods
It's common to update indicators with price data that spans a normal time period like one minute or one day. It's less common to update an indicator with exotic time periods (for example, a 7-minute consolidated bar), so these types of indicators may provide more opportunities for alpha. To update indicators with exotic data, create a manual indicator and then call the register_indicator
method. The register_indicator
method wires up the indicator for automatic updates at the time interval you provide.
# Calculate the SMA with 10 7-minute bars self._symbol = self.add_equity("SPY", Resolution.MINUTE).symbol self._indicator = SimpleMovingAverage(10) self.register_indicator(self._symbol, self._indicator, timedelta(minutes=7))
The register_indicator
method can accept a timedelta
, Resolution
, or an unregistered consolidator. If you apply the indicator to a security in a dynamic universe, provide a consolidator so that you can remove it when your algorithm removes the security from the universe.
# timedelta self.register_indicator(self._symbol, self._indicator, timedelta(minutes=7)) # Resolution self.register_indicator(self._symbol, self._indicator, Resolution.HOUR) # Consolidator self._consolidator = TradeBarConsolidator(35) self.register_indicator(self._symbol, self._indicator, self._consolidator)
Custom Indicator Values
Data point indicators use only a single price data in their calculations. By default, those indicators use the closing price. For
assets with TradeBar
data, that price is the TradeBar
close price. For assets with QuoteBar
data, that price is the mid-price of the bid closing price and the ask closing price. To create an indicator with the other fields like the OPEN
, HIGH
, LOW
, or CLOSE
, provide a selector
argument to the register_indicator
method.
# Define a 10-period RSI with indicator constructor self._rsi = RelativeStrengthIndex(10, MovingAverageType.SIMPLE) # Register the daily High price data to automatically update the indicator self.register_indicator(self._symbol, self._rsi, Resolution.DAILY, Field.HIGH)
The register_indicator
method can accept a timedelta
, Resolution
, or an unregistered consolidator. If you apply the indicator to a security in a dynamic universe, provide a consolidator so that you can remove it when your algorithm removes the security from the universe.
The Field
class has the following selector
properties:
Examples
The following examples demonstrate common practices for updating indicators using consolidators.
Example 1: Hammer Pattern With 5-Minute Consolidator
The following algorithm trades Hammer and Inverted Hammer candlestick patterns. The candles are constructed with five-minute SPY trade bars consolidated by a five-minute
TradeBarConsolidator
, while automatically updating with the
register_indicator
method. Both patterns are bullish, so we buy SPY if the pattern is signaled.
from QuantConnect.Indicators.CandlestickPatterns import * class ConsolidatorUpdatingIndicatorsAlgorithm(QCAlgorithm): hammer = Hammer() inverted_hammer = InvertedHammer() def initialize(self) -> None: self.set_start_date(2021, 10, 1) self.set_end_date(2022, 1, 1) # Request SPY data for signal generation and trading. self.spy = self.add_equity("SPY", Resolution.MINUTE).symbol # The candlestick patterns are based on a 5-minute consolidated trade bar. consolidator = TradeBarConsolidator(timedelta(minutes=5)) # Subscribe for automatically updating the indicators with the 5-minute consolidator. self.register_indicator(self.spy, self.hammer, consolidator) self.register_indicator(self.spy, self.inverted_hammer, consolidator) # Add an event handler on candlestick indicators that are updated to trade the pattern. self.hammer.updated += self.on_updated self.inverted_hammer.updated += self.on_updated self.set_warm_up(1) def on_updated(self, sender: object, point: IndicatorDataPoint) -> None: # Both the hammer and inverted hammer patterns indicate bullish movement, so we buy SPY. if point.value == 1 and not self.portfolio[self.spy].is_long: self.set_holdings(self.spy, 0.5) def on_order_event(self, order_event: OrderEvent) -> None: if order_event.status == OrderStatus.FILLED: if order_event.ticket.order_type == OrderType.MARKET: # Stop loss order at 1%. stop_price = order_event.fill_price * (0.99 if order_event.fill_quantity > 0 else 1.01) self.stop_market_order(self.spy, -self.portfolio[self.spy].quantity, stop_price) # Take profit order at 2%. take_profit_price = order_event.fill_price * (1.02 if order_event.fill_quantity > 0 else 0.98) self.limit_order(self.spy, -self.portfolio[self.spy].quantity, take_profit_price) elif order_event.ticket.order_type == OrderType.STOP_MARKET or order_event.ticket.order_type == OrderType.LIMIT: # Cancel any open order if stop loss or take profit order filled. self.transactions.cancel_open_orders()