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
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.
// Create a manual indicator with the indicator constructor var manualSMA = SimpleMovingAverage(20, Resolution.Daily); // Create an automatic indicator with the helper method var symbol = AddCrypto("BTCUSD").Symbol; var autoSMA = SMA(symbol, 20, Resolution.Daily);
# Create a manual indicator with the indicator constructor self.manual_sma = SimpleMovingAverage(20, Resolution.Daily) # Create an automatic indicator with the helper method symbol = self.AddCrypto("BTCUSD").Symbol; auto_sma = self.SMA(symbol, 20, Resolution.Daily)
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
property.
if not self.indicator.IsReady: return
if (!indicator.IsReady) return;
To get the number of samples the indicator has processed, use the Samples
property.
samples = self.indicator.Samples
var samples = indicator.Samples;
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.indicator.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.IsReady: self.Plot("Indicator", "Value", indicator_data_point.Value)
// After you create the indicator, attach an event handler _indicator.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 Indicator Values
You can access current and historical indicator values.
Current Indicator 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.bb.Current.Value bb_upper_band = self.bb.UpperBand.Current.Value bb_lower_band = self.bb.LowerBand.Current.Value
var sma = _sma.Current.Value var currentPrice = _bb.Current.Value var bbUpperBand = _bb.UpperBand.Current.Value var bbLowerBand = _bb.LowerBand.Current.Value
You can implicitly cast indicators to the decimal version of their .Current.Value
property.
if self.sma > self.bb.UpperBand: self.SetHoldings(self.symbol, -0.1)
if (_sma > _bb.UpperBand) { SetHoldings(_symbol, -0.1); }
Historical Indicator Values
To track historical indicator values, use a RollingWindow
. Indicators emit an Updated
event when they update. To create a RollingWindow
of indicator points, attach an event handler function to the Updated
member that adds the last value of the indicator to the RollingWindow
. The value is an IndicatorDataPoint
object that represents a piece of data at a specific time.
public override void Initialize() { // Create an indicator and adds to a RollingWindow when it is updated smaWindow = new RollingWindow<IndicatorDataPoint>(5); SMA("SPY", 5).Updated += (sender, updated) => smaWindow.Add(updated); }
def Initialize(self) -> None: # Creates an indicator and adds to a RollingWindow when it is updated self.sma_window = RollingWindow[IndicatorDataPoint](5) self.SMA("SPY", 5).Updated += (lambda sender, updated: self.sma_window.Add(updated))
The current (most recent) indicator value is at index 0, the previous value is at index 1, and so on until the length of the window.
var currentSma = smaWin[0]; var previousSma = smaWin[1]; var oldestSma = smaWin[smaWin.Count - 1];
current_sma = self.sma_window[0] previous_sma = self.sma_window[1] oldest_sma = self.sma_window[sma_window.Count - 1]
Reset Indicators
To reset indicators, call the Reset
method.
self.indicator.Reset()
_indicator.Reset();
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.
# In OnData if data.Splits.ContainsKey(self.symbol) or data.Dividends.ContainsKey(self.symbol): # Reset the indicator self.sma.Reset() # Warm up the indicator
// In OnData if (data.Splits.ContainsKey(_symbol) || data.Dividends.ContainsKey(_symbol)) { // Reset the indicator _sma.Reset(); // Warm up the indicator }
The process to warm up your indicator depends on if it's a manual or automatic indicator.
Common Design Patterns
The location in your algorithm where you create and manage indicators depends on the type of universe in your algorithm.
One Asset In a Static Universe
If you only have one security in your algorithm, you can create an automatic indicator in the Initialize
method.
def Initialize(self):
self.symbol = self.AddEquity("SPY").Symbol self.EnableAutomaticIndicatorWarmUp = True self.sma = self.SMA(self.symbol, 20, Resolution.Daily)
private Symbol _symbol; private SimpleMovingAverage _sma; public override void Initialize() { _symbol = AddEquity("SPY").Symbol; EnableAutomaticIndicatorWarmUp = 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, abstract your indicator management logic into a separate class.
class SymbolData: def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol # Create an indicator self.sma = SimpleMovingAverage(20) # Create a consolidator to update the indicator self.consolidator = TradeBarConsolidator(1) self.consolidator.DataConsolidated += self.OnDataConsolidated # Register the consolidator to update the indicator algorithm.SubscriptionManager.AddConsolidator(symbol, self.consolidator) # Warm up the indicator algorithm.WarmUpIndicator(symbol, self.sma) def OnDataConsolidated(self, sender: object, consolidated_bar: TradeBar) -> None: self.sma.Update(consolidated_bar.EndTime, consolidated_bar.Close) # If you have a dynamic universe, remove consolidators for the securities removed from the universe def dispose(self) -> None: self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.consolidator)
public class SymbolData { private QCAlgorithm _algorithm; private Symbol _symbol; private SimpleMovingAverage _sma; private TradeBarConsolidator _consolidator; public SymbolData(QCAlgorithm algorithm, Symbol symbol) { _algorithm = algorithm; _symbol = symbol; // Create an indicator _sma = new SimpleMovingAverage(20); // Create a consolidator to update the indicator _consolidator = new TradeBarConsolidator(1); _consolidator.DataConsolidated += OnDataConsolidated; // Register the consolidator to update the indicator algorithm.SubscriptionManager.AddConsolidator(symbol, _consolidator); // Warm up the indicator algorithm.WarmUpIndicator(symbol, _sma); } private void OnDataConsolidated(object sender, TradeBar consolidatedBar) { _sma.Update(consolidatedBar.EndTime, consolidatedBar.Close); } // If you have a dynamic universe, remove consolidators for the securities removed from the universe public void Dispose() { _algorithm.SubscriptionManager.RemoveConsolidator(_symbol, _consolidator); } }
Every time the universe adds or removes a security, create or dispose of the respective SymbolData
object in the OnSecuritiesChanged
event handler.
class MyAlgorithm(QCAlgorithm): symbol_data_by_symbol = {} def OnSecuritiesChanged(self, changes: SecurityChanges) -> None: for security in changes.AddedSecurities: self.symbol_data_by_symbol[security.Symbol] = SymbolData(self, security.Symbol) # If you have a dynamic universe, track removed securities for security in changes.RemovedSecurities: symbol_data = self.symbol_data_by_symbol.pop(security.Symbol, None) if symbol_data: symbol_data.dispose()
public class MyAlgorithm : QCAlgorithm { private Dictionary<Symbol, SymbolData> _symbolDataBySymbol = new(); public override void OnSecuritiesChanged(SecurityChanges changes) { foreach (var security in changes.AddedSecurities) { _symbolDataBySymbol[security.Symbol] = new SymbolData(this, security.Symbol); } // If you have a dynamic universe, track removed securities foreach (var security in changes.RemovedSecurities) { if (_symbolDataBySymbol.TryGetValue(security.Symbol, out var symbolData)) { symbolData.Dispose(); } } } }
Differences From Third-Party Indicators
The values of our indicators can sometimes produce different results than the indicators on other platforms. These discrepancies can be a result of differences in the input data, timestamps, or implementation.
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.