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.

private BollingerBands _manualBB;
private BollingerBands _autoBB;
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
    var 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
    symbol = self.AddCrypto("BTCUSD").Symbol;
    self.auto_bb = self.BB(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 5 for the Bollinger Band
_manualBB.Window.Size = 5;
_autoBB.Window.Size = 5;

// Increase the Size to 4 for the middle band
_manualBB.MiddleBand.Window.Size = 4;
_autoBB.MiddleBand.Window.Size = 4;
# Increase the Size to 5 for Bollinger Band
self.manual_bb.Window.Size = 5
self.auto_bb.Window.Size = 5

# Increase the Size to 4 for the middle band
self.manual_bb.MiddleBand.Window.Size = 4
self.auto_bb.MiddleBand.Window.Size = 4

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 nullNone. 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 nullNone:

_manualBB.Window[9] = _manualBB.Current;
_autoBB.Window[9] = _autoBB.Current;
    
self.manual_bb.Window[9] = _manualBB.Current;
self.auto_bb.Window[9] = _autoBB.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 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;

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 flag.

if not self.indicator.Window.IsReady:
    return
if (!indicator.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.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 access historical 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 object, and so on until the length of the window. You increase the window size and get a nullNone value when using an index greater than the length of the window.

var currentBB = _bb[0];
var previousBB = _bb.Previous; // or _bb[1];
var oldestBB = _bb[_bb.window.Count - 1];
var nullValue = _bb[100];

var previousUpperBand = _bb.UpperBand.Previous;
var oldestUpperBand = _bb.UpperBand[_bb.UpperBand.Window.Count - 1];
current_bb = self.bb[0]
previous_bb = self.bb.Previous # or self.bb[1]
oldest_bb = self.bb[self.bb.Count - 1]
none_value = self.bb[100];

previous_upper_band = self.bb.UpperBand.Previous;
oldest_upper_band = self.bb.UpperBand[self.bb.UpperBand.Window.Count - 1];

To access all of an indicator's historical values, iterate through the indicator object.

foreach (var value in _bb)
{
    Log(value.ToString());
}
for value in self.bb:
    self.Log(f'{value}');

Reset Indicators

To reset indicators, call the Reset method.

self.indicator.Reset() 
_indicator.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 ScaledRaw data from a history request.

# In OnData
if data.Splits.ContainsKey(self.symbol) or data.Dividends.ContainsKey(self.symbol):
    # Reset the indicator
    self.sma.Reset() 

    # Warm up the indicator
    trade_bars = self.History[TradeBar](self.symbol, self.sma.WarmUpPeriod, Resolution.Daily, dataNormalizationMode=DataNormalizationMode.ScaledRaw)
    for trade_bar in trade_bars:
        self.sma.Update(trade_bar.EndTime, 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.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, add custom members to the Security objects as you receive them in OnSecuritiesChanged.

If your algorithm has multiple assets or a dynamic universe of assets, as you receive new Security objects in OnSecuritiesChanged, 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 OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
    for security in changes.AddedSecurities:
        # Create an indicator
        security.indicator = self.SMA(security.Symbol, 10)

        # Warm up the indicator
        self.WarmUpIndicator(security.Symbol, security.indicator)

    for security in changes.RemovedSecurities:
        self.DeregisterIndicator(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.

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: