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.

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: