Indicators
Key Concepts
Introduction
Indicators translate a stream of data points into a numerical value you can use to detect trading opportunities. LEAN provides more than 100 pre-built technical indicators and candlestick patterns you can use in your algorithms. To view a list of all the indicators, see the Supported Indicators. One simple indicator to learn is the Identity indicator, which just returns the value of the asset.
pep = self.identity("PEP") # Pepsi ticker
Design Philosophy
Technical indicators take in a stream of data points, Bar
objects, or TradeBar
objects and produce a continuous value. Candlestick patterns take in a stream of Bar
or TradeBar
objects and produce a
value that's either 0, 1, or -1 to signify the presence of the pattern.
You can configure technical indicators and candlestick patterns to receive manual or automatic updates. With manual indicators, you update the indicator with whatever data you want and whenever you want. With automatic indicators, the algorithm automatically uses the security data to update the indicator value.
LEAN's indicators are implemented "on-line". This means as data pipes through, it updates the internal state of the indicator. Other platforms perform indicator math on an array of values in bulk which can be very inefficient when working on large volumes of data. You need to warm up your indicators and prime them with data to get them ready.
Indicator Types
You can classify an indicator as either a data point, bar, or TradeBar indicator. Their classification depends on the type of data they receive.
Data Point Indicators
Data point indicators use IndicatorDataPoint
objects to compute their value. Some examples of data point indicators include the following:
Bar Indicators
Bar indicators use QuoteBar
or TradeBar
objects to compute their value. Since Forex and CFD securities don't have TradeBar
data, they use bar indicators. Candlestick patterns are examples of bar indicators. Some other examples of bar indicators include the following:
TradeBar Indicators
TradeBar
indicators use TradeBar
objects to compute their value. Some TradeBar
indicators use the volume property of the TradeBar
to compute their value. Some examples of TradeBar
indicators include the following:
Naming Convention
The class name to create a manual indicator is the indicator name spelled in pascal case. For example, to create a manual simple moving average indicator, use the SimpleMovingAverage
class.
The method to create an automatic indicator is usually named after the acronym of the indicator name. For example, to create an automatic simple moving average indicator, use the sma
method.
To view the class name and shortcut method of each indicator, see the Supported Indicators.
Create Indicators
There are two ways to create indicators. You can create indicators with the indicator constructor or use a helper method in the QCAlgorithm
class. If you use the helper method, the indicator automatically updates as your algorithm receives new data.
def initialize(self) -> None: # Create a manual indicator with the indicator constructor self.manual_bb = BollingerBands(20, 2) # Create an automatic indicator with the helper method self._symbol = self.add_crypto("BTCUSD").Symbol self.auto_bb = self.bb(self._symbol, 20, 2, Resolution.DAILY)
Set Historical Values Window Size
Indicators have a built-in RollingWindow
that stores their historical values. The default window size is 2. To change the window size, set the Window.Size
member.
# Increase the Size to 10 for Bollinger Band self.manual_bb.window.size = 10 self.auto_bb.window.size = 10 # Increase the Size to 20 for the middle band self.manual_bb.middle_band.window.size = 20 self.auto_bb.middle_band.window.size = 20
When you decrease the size, it removes the oldest values that no longer fit in the RollingWindow
. When you explicitly increase the size
member, it doesn't automatically add any new elements to the RollingWindow
. However, if you set the value of an index in the RollingWindow
and the index doesn't currently exist, it fills the empty indices with None
. For example, the following code increases the size
to 10, sets the 10th element to the indicator current value, and sets the 6th-9th elements to None
:
self.manual_bb.window[9] = self.manual_bb.current self.auto_bb.window[9] = self.auto_bb.current
Check Readiness
Indicators aren't always ready when you first create them. The length of time it takes to trust the indicator values
depends on the indicator period. To know if an indicator is ready to use, use the is_ready
property.
if not self.auto_bb.is_ready: return
To get the number of samples the indicator has processed, use the samples
property.
samples = self.auto_bb.samples
The RollingWindow
objects that store the historical values may not be full when the indicator is ready to use. To check if the Window
is full, use its is_ready
flag.
if not self.auto_bb.window.is_ready: return
Track Update Events
When an indicator receives a new data point and updates its state, it triggers an update event. To be notified of update events, attach an event handler to the indicator object. The event handler receives 2 arguments: the indicator object and the latest IndicatorDataPoint
it produced.
# After you create the indicator, attach an event handler self.auto_bb.updated += self.update_event_handler # Define the event handler within the same class def update_event_handler(self, indicator: object, indicator_data_point: IndicatorDataPoint) -> None: if indicator.is_ready: self.plot("Indicator", "Value", indicator_data_point.value)
Get Current Values
To access the indicator value, use the current.value
property. Some indicators have one output and some indicators have multiple outputs. The SimpleMovingAverage
indicator only has one output, the average price over the last n periods, so the current.value
property returns this value. The BollingerBand
indicator has multiple outputs because it has a simple moving average, an upper band, and a lower band. For indicators that have multiple outputs, refer to the Supported Indicators to see how to access the output values.
sma = self.sma.current.value current_price = self.auto_bb.current.value bb_upper_band = self.auto_bb.upper_band.current.value bb_lower_band = self.auto_bb.lower_band.current.value
You can implicitly cast indicators to the decimal version of their current.value
property.
if self.sma.current.value > self.auto_bb.upper_band.current.value: self.set_holdings(self._symbol, -0.1)
Get Historical Values
To access historical indicator values, you can use the indicator's built-in RollingWindow
or you can call the indicator_history
method to request values over a specific period of time.
Get Trailing Values
To access trailing indicator values, use reverse list access semantics.
The current (most recent) indicator value is at index 0, the previous value is at index 1 or the previous
property, and so on until the length of the window.
By default, the window length is 2.
If you index the window with a number that's greater than the maximum index of the window, you get a None
value and the window length grows to support the new index.
current_bb = self.auto_bb.current # self.auto_bb[0] previous_bb = self.auto_bb.previous # or self.auto_bb[1] oldest_bb = self.auto_bb.window[self.auto_bb.window.count - 1] none_value = self.auto_bb.window[100] previous_upper_band = self.auto_bb.upper_band.previous oldest_upper_band = self.auto_bb.upper_band.window[self.auto_bb.upper_band.window.count - 1]
To access all of an indicator's historical values in the window, iterate through the indicator object.
for value in self.auto_bb: self.log(f'{value}')
Request Historical Values
The indicator_history
method also provides historical indicator values.
This method resets your indicator, makes a history request, and updates the indicator with the historical data.
Just like with regular history requests, the indicator_history
method supports time periods based on a trailing number of bars, a trailing period of time, or a defined period of time.
If you don't provide a resolution
argument, it defaults to match the resolution of the security subscription.
# Get historical indicator values. history = self.indicator_history(self.sma, self._symbol, timedelta(30)) # Organize the historical values into a DataFrame. df = history.data_frame # Iterate through the historical values. for indicator_data_point in history: t = indicator_data_point.end_time value = indicator_data_point.value
To make the indicator_history
method update the indicator with an alternative price field instead of the close (or mid-price) of each bar, pass a selector
argument.
history = self.indicator_history(self.sma, self._symbol, timedelta(30), selector=Field.VOLUME)
Some indicators require the prices of two assets to compute their value (for example, Beta).
In this case, pass a list of the Symbol
objects to the method.
self._spy = self.add_equity("SPY").symbol self._aapl = self.add_equity('AAPL').symbol self._beta = Beta("", self._aapl, self._spy, 21) history = self.indicator_history(self._beta, [self._spy, self._aapl], 10, Resolution.DAILY)
If the indicator is an Option indicator with an underlying Equity, the indicator_history
uses the SCALED_RAW
data normalization mode when requesting historical prices.
Reset Indicators
To reset indicators, call the reset
method.
self.auto_bb.reset()
The process to warm up your indicator depends on if it's a manual or automatic indicator.
If you are live trading Equities or backtesting Equities without the adjusted data normalization mode,
reset your indicators when splits and dividends occur.
If a split or dividend occurs, the data in your indicators becomes invalid because it doesn't account for the price adjustments that the split or dividend causes.
To replace your indicator history with the newly-adjusted prices, call the reset
method and then warm up the indicator with SCALED_RAW
data from a history request.
# In OnData if data.splits.contains_key(self._symbol) or data.dividends.contains_key(self._symbol): # Reset the indicator self._sma.reset() # Warm up the indicator trade_bars = self.history[TradeBar](self._symbol, self._sma.warm_up_period, Resolution.DAILY, data_normalization_mode=DataNormalizationMode.SCALED_RAW) for trade_bar in trade_bars: self._sma.update(trade_bar.end_time, trade_bar.close)
Common Design Patterns
The location in your algorithm where you create and manage indicators depends on the type of universe you use.
One Asset In a Static Universe
If you only have one security in your algorithm, you can create indicators for it during initialization.
def initialize(self): self._symbol = self.add_equity("SPY").symbol self.settings.automatic_indicator_warm_up = True self._sma = self.sma(self._symbol, 20, Resolution.DAILY)
Multiple Assets or a Dynamic Universe
If your algorithm has multiple assets or a dynamic universe of assets, add custom members to the Security
objects as you receive them in on_securities_changed
.
def on_securities_changed(self, changes: SecurityChanges) -> None: for security in changes.added_securities: # Create an indicator security.indicator = self.sma(security.Symbol, 10) # Warm up the indicator self.warm_up_indicator(security.symbol, security.indicator) for security in changes.removed_securities: self.deregister_indicator(security.indicator)
Differences From Third-Party Indicators
The values of our indicators can sometimes produce different results than the indicators on other platforms. There can be several reasons for these discrepancies.
Price Differences
If you find differences in indicator values, compare the prices that are fed into the indicator on each platform. If the input data is slightly different, the indicator values will be different. To test if it's a difference in price data, feed in the data from the third-party platform into our indicators. We validate the indicators against third-party sources and when the values are the same, the indicator values are similar too.
Timestamp Differences
We timestamp our data to the time when the period ends. Many other platforms timestamp to the beginning of the candle.
Implementation Differences
Some indicators can have slightly different default arguments for their implementation. A common difference across platforms is to use a different MovingAverageType
for the indicators. To view the default arguments for all our indicators, see Supported Indicators. To view the full implementation of our indicators, see the LEAN GitHub repository.
Warm Up Period Differences
Some platforms use all of the historical data to warm up their indicators while LEAN indicators have a fixed number of bars they need to warm up. As a result, indicators with long memory like the Exponential Moving Average can have slightly different values across platforms.
Risk Free Interest Rate Differences
Some indicators, like Sharpe ratios and Sortino ratios, are a function of the risk free interest rate. LEAN provides several different ways to define the interest rate, which can lead to different indicator values compared to other platforms.