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.
var pep = Identity("PEP"); // Pepsi ticker
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
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.
private BollingerBands _manualBB; private BollingerBands _autoBB; private Symbol _symbol; public override void Initialize() { // Create a manual indicator with the indicator constructor _manualBB = new BollingerBands(20, 2); // Create an automatic indicator with the helper method _symbol = AddCrypto("BTCUSD").Symbol; _autoBB = BB(_symbol, 20, 2, Resolution.Daily); }
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 window size to 10 for the Bollinger Band _manualBB.Window.Size = 10; _autoBB.Window.Size = 10; // Increase the Size to 20 for the middle band _manualBB.MiddleBand.Window.Size = 20; _autoBB.MiddleBand.Window.Size = 20;
# 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
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 null
None
. For example, the following code increases the Size
size
to 10, sets the 10th element to the indicator current value, and sets the 6th-9th elements to null
None
:
_manualBB.Window[9] = _manualBB.Current; _autoBB.Window[9] = _autoBB.Current;
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 IsReady
is_ready
property.
if not self.auto_bb.is_ready: return
if (!_autoBB.IsReady) return;
To get the number of samples the indicator has processed, use the Samples
samples
property.
samples = self.auto_bb.samples
var samples = _autoBB.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 IsReady
is_ready
flag.
if not self.auto_bb.window.is_ready: return
if (!_autoBB.Window.IsReady) 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)
// After you create the indicator, attach an event handler _autoBB.Updated += updateEventHandler; // Define the event handler within the same class private void updateEventHandler(object indicator, IndicatorDataPoint indicatorDataPoint) { if (indicator.IsReady) { Plot("Indicator", "Value", indicatorDataPoint.Value); } }
Get Current Values
To access the indicator value, use the Current.Value
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
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
var sma = _sma.Current.Value; var currentPrice = _autoBB.Current.Value; var bbUpperBand = _autoBB.UpperBand.Current.Value; var bbLowerBand = _autoBB.LowerBand.Current.Value;
You can implicitly cast indicators to the decimal version of their Current.Value
current.value
property.
if self.sma.current.value > self.auto_bb.upper_band.current.value: self.set_holdings(self._symbol, -0.1)
if (_sma > _autoBB.UpperBand) { SetHoldings(_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 IndicatorHistory
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
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 null
None
value and the window length grows to support the new index.
var currentBB = _autoBB.Current; // or _autoBB[0]; var previousBB = _autoBB.Previous; // or _autoBB[1]; var oldestBB = _autoBB.Window[_autoBB.window.Count - 1]; var nullValue = _autoBB.Window[100]; var previousUpperBand = _autoBB.UpperBand.Previous; var oldestUpperBand = _autoBB.UpperBand.Window[_autoBB.UpperBand.Window.Count - 1];
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.
foreach (var value in _autoBB) { Log(value.ToString()); }
for value in self.auto_bb: self.log(f'{value}')
Request Historical Values
The IndicatorHistory
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 IndicatorHistory
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. var history = IndicatorHistory(_sma, _symbol, TimeSpan.FromDays(30)); // Iterate through the historical values. foreach (var indicatorDataPoint in history) { var t = indicatorDataPoint.EndTime; var value = indicatorDataPoint.Value; }
# 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 IndicatorHistory
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.
var history = IndicatorHistory(_sma, _symbol, TimeSpan.FromDays(30), selector: Field.Volume);
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.
var spy = AddEquity("SPY").Symbol; var aapl = AddEquity("AAPL").Symbol; var beta = new Beta("", aapl, spy, 21); var history = IndicatorHistory(beta, new[] {spy, aapl}, 10, Resolution.Daily);
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 IndicatorHistory
indicator_history
uses the SCALED_RAW
ScaledRaw
data normalization mode when requesting historical prices.
If you already have a list of Slice objects, you can pass them to the IndicatorHistory
indicator_history
method to avoid the internal history request.
var slices = History(new[] {_symbol}, 30, Resolution.Daily); var history = IndicatorHistory(_sma, slices);
Reset Indicators
To reset indicators, call the Reset
reset
method.
self.auto_bb.reset()
_autoBB.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
reset
method and then warm up the indicator with ScaledRaw
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)
// In OnData if (data.Splits.ContainsKey(_symbol) || data.Dividends.ContainsKey(_symbol)) { // Reset the indicator _sma.Reset(); // Warm up the indicator var tradeBars = History<TradeBar>(_symbol, _sma.WarmUpPeriod, Resolution.Daily, dataNormalizationMode: DataNormalizationMode.ScaledRaw); foreach (var tradeBar in tradeBars) { _sma.Update(tradeBar.EndTime, tradeBar.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)
private Symbol _symbol; private SimpleMovingAverage _sma; public override void Initialize() { _symbol = AddEquity("SPY").Symbol; Settings.AutomaticIndicatorWarmUp = true; _sma = SMA(_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 OnSecuritiesChanged
on_securities_changed
.
If your algorithm has multiple assets or a dynamic universe of assets, as you receive new Security
objects in OnSecuritiesChanged
on_securities_changed
, cast them to dynamic
objects and add custom members to them.
public override void OnSecuritiesChanged(SecurityChanges changes) { foreach (var security in changes.AddedSecurities) { var dynamicSecurity = security as dynamic; // Create an indicator dynamicSecurity.Indicator = SMA(security.Symbol, 10); // Warm up the indicator WarmUpIndicator(security.Symbol, dynamicSecurity.Indicator); } foreach (var security in changes.RemovedSecurities) { DeregisterIndicator((security as dynamic).Indicator); } }
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.