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 SMAsma 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 Sizesize 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 Sizesize 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] = 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 IsReadyis_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 Samplessamples 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 IsReadyis_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.Valuecurrent.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.Valuecurrent.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.Valuecurrent.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 IndicatorHistoryindicator_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 Previousprevious 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 nullNone 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 IndicatorHistoryindicator_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 IndicatorHistoryindicator_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 IndicatorHistoryindicator_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 IndicatorHistoryindicator_history uses the SCALED_RAWScaledRaw data normalization mode when requesting historical prices.

If you already have a list of Slice objects, you can pass them to the IndicatorHistoryindicator_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 Resetreset 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 Resetreset method and then warm up the indicator with ScaledRawSCALED_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 OnSecuritiesChangedon_securities_changed.

If your algorithm has multiple assets or a dynamic universe of assets, as you receive new Security objects in OnSecuritiesChangedon_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.

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: