Contributions

Indicators

Introduction

LEAN currently supports over 100 indicators. This page explains how to contribute a new indicator to the open-source project by making a pull request to Lean. Before you get started, familiarize yourself with our contributing guidelines. If you don't already have a new indicator in mind that you want to contribute, see the GitHub Issues in the Lean repository for a list of indicators that community members have requested.

Get Third-Party Values

As a quantitative algorithmic trading engine, accuracy and reliability are very important to LEAN. When you submit a new indicator to the LEAN, you must include third-party source values are required as reference points in your pull request to contrast the values output by your indicator implementation. This requirement validates that your indicator implementation is correct. The following sections explain some examples of acceptable third-party sources.

Renowned Open-source Projects

Developed and maintained by expert teams, these sources undergo rigorous testing and optimization, ensuring accurate calculations. The transparent nature of open-source projects allows for community scrutiny, resulting in bug fixes and continuous improvements. Open-source projects provide thorough information on how the indicator values are calculated, which provides excellent reproducibility. Thus, we accept values from these projects with high confidence. Example projects include TA-Lib and QuantLib.

Highly Credible Websites

Similar reasons apply to these websites as well. The site should be either the original source or a very popular trading data provider, such that we have confidence in their accuracy and reliability. These sources might provide structured data samples, like a JSON response, CSV/Excel file, or scripts for calculating the indicator values.

Define the Class

To add a new indicator to Lean, add a class file to the Lean / Indicators directory. Indicators are classified as either a data point, bar, or TradeBar indicator. Their classification depends on the class they inherit and the type of data they receive. The following sections explain how to implement each type. Regardless of the indicator type, the class must define the following properties:

PropertyTypeDescription
WarmUpPeriodintThe minimum number of data entries required to calculate an accurate indicator value.
IsReadyboolA flag that states whether the indicator has sufficient data to generate values.

The class must also define a ComputeNextValue method, which accepts some data and returns the indicator value. As shown in the following sections, the data/arguments that this method receives depends on the indicator type.

On rare occassions, some indicators can produce invalid values. For example, a moving average can produce unexpected values due to extreme quotes. In cases like these, override the ValidateAndComputeNextValue method to return an IndicatorResult with an IndicatorStatus enumeration. If the IndicatorStatus states the value is invalid, it won't be passed to the main algorithm. The IndicatorStatus enumeration has the following members:

To enable the algorithm to warm up the indicator with the WarmUpIndicator method, inherit the IIndicatorWarmUpPeriodProvider interface.

If your indicator requires a moving average, see the Extra Steps for Moving Averages Types as you complete the following tutorial.

Data Point Indicators

Data point indicators use IndicatorDataPoint objects to compute their value. These types of indicators can inherit the IndicatorBase<IndicatorDataPoint> or WindowIndicator<IndicatorDataPoint> class. The WindowIndicator<IndicatorDataPoint> class has several members to help you compute indicator values over multiple periods.

public class CustomPointIndicator : IndicatorBase<IndicatorDataPoint>, IIndicatorWarmUpPeriodProvider
{
    public int WarmUpPeriod = 2;
    public override bool IsReady => Samples >= WarmUpPeriod;

    protected override decimal ComputeNextValue(IndicatorDataPoint input)
    {
        return 1m;
    }

    protected virtual IndicatorResult ValidateAndComputeNextValue(IndicatorDataPoint input)
    {
        var indicatorValue = ComputeNextValue(input);
        return IsReady ?
            new IndicatorResult(indicatorValue) :
            new IndicatorResult(indicatorValue, IndicatorStatus.ValueNotReady);
    }
}

To view some example data point indicators that inherit the IndicatorBase<IndicatorDataPoint> class, see the implementation of the following indicators in the LEAN repository:

public class CustomWindowIndicator : WindowIndicator<IndicatorDataPoint>
{
    public int WarmUpPeriod => base.WarmUpPeriod;
    public override bool IsReady => base.IsReady;

    protected override decimal ComputeNextValue(IReadOnlyWindow<T> window, IndicatorDataPoint input)
    {
        return window.Average();
    }

    protected virtual IndicatorResult ValidateAndComputeNextValue(IndicatorDataPoint input)
    {
        var indicatorValue = ComputeNextValue(input);
        return IsReady ?
            new IndicatorResult(indicatorValue) :
            new IndicatorResult(indicatorValue, IndicatorStatus.InvalidInput);
    }
}

To view some example data point indicators that inherit the WindowIndicator<IndicatorDataPoint> class, see the implementation of the following indicators in the LEAN repository:

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.

public class CustomBarIndicator : BarIndicator, IIndicatorWarmUpPeriodProvider
{
    public int WarmUpPeriod = 2;
    public override bool IsReady => Samples >= WarmUpPeriod;

    protected override decimal ComputeNextValue(IBaseDataBar input)
    {
        return 1m;
    }

    protected virtual IndicatorResult ValidateAndComputeNextValue(IBaseDataBar input)
    {
        var indicatorValue = ComputeNextValue(input);
        return IsReady ?
            new IndicatorResult(indicatorValue) :
            new IndicatorResult(indicatorValue, IndicatorStatus.ValueNotReady);
    }
}

To view some example bar indicators, see the implementation of the following indicators in the LEAN repository:

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.

public class CustomTradeBarIndicator : TradeBarIndicator, IIndicatorWarmUpPeriodProvider
{
    public int WarmUpPeriod = 2;
    public override bool IsReady => Samples >= WarmUpPeriod;

    protected override decimal ComputeNextValue(TradeBar input)
    {
        return 1m;
    }

    protected virtual IndicatorResult ValidateAndComputeNextValue(TradeBar input)
    {
        var indicatorValue = ComputeNextValue(input);
        return IsReady ?
            new IndicatorResult(indicatorValue) :
            new IndicatorResult(indicatorValue, IndicatorStatus.ValueNotReady);
    }
}

To view some example TradeBar indicators, see the implementation of the following indicators in the LEAN repository:

Define the Helper Method

The preceding indicator class is sufficient to instatiate a manual version of the indicator. To enable users to create an automatic version of the indicator, add a new method to the Lean / Algorithm / QCAlgorithm.Indicators.cs file. Name the method a short abbreviation of the indicator's full name. In the method definition, call the InitializeIndicator method to create a consolidator and register the indicator for automatic updates with the consolidated data.

public CustomIndicator CI(Symbol symbol, Resolution? resolution = null, Func<IBaseData, IBaseDataBar> selector = null)
{
    var name = CreateIndicatorName(symbol, $"CI()", resolution);
    var ci = new CustomIndicator(name, symbol);
    InitializeIndicator(symbol, ci, resolution, selector);
    return ci;
}

Add Unit Tests

Unit tests ensure your indicator functions correctly and produces accurate values. Follow these steps to add unit tests for your indicator:

  1. Save the third-party values in the Lean / Tests / TestData directory as a CSV file.
  2. In the Lean / Tests / QuantConnect.Tests.csproj file, reference the new data file.
    <Content Include="TestData\<filePath>.csv">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  3. Create a Lean / Tests / Indicators / <IndicatorName>Tests.cs file with the following content:
    namespace QuantConnect.Tests.Indicators
    {
        [TestFixture]
        public class CustomIndicatorTests : CommonIndicatorTests<T>
        {
            protected override IndicatorBase<T> CreateIndicator()
            {
                return new CustomIndicator();
            }
    
            protected override string TestFileName => "custom_3rd_party_data.csv";
    
            protected override string TestColumnName => "CustomIndicatorValueColumn";
    
            // How do you compare the values
            protected override Action<IndicatorBase<T>, double> Assertion
            {
                get { return (indicator, expected) => Assert.AreEqual(expected, (double)indicator.Current.Value, 1e-4); }        // allow 0.0001 error margin of indicator values
            }
        }
    }
  4. Set the values of the TestFileName and TestColumnName attributes to the CSV file name and the column name of the testing values in the CSV file of third-party values, respectively.
  5. Add test cases.
  6. Test if the constructor, IsReady flag, and Reset method work. If there are other custom calculation methods in your indicator class, add a tests for them.

The following example shows the testing class structure:

namespace QuantConnect.Tests.Indicators
{
    [TestFixture]
    public class CustomIndicatorTests : CommonIndicatorTests<T>
    {
        protected override IndicatorBase<T> CreateIndicator()
        {
            return new CustomIndicator();
        }

        protected override string TestFileName => "custom_3rd_party_data.csv";

        protected override string TestColumnName => "CustomIndicatorValueColumn";

        // How do you compare the values
        protected override Action<IndicatorBase<T>, double> Assertion
        {
            get { return (indicator, expected) => Assert.AreEqual(expected, (double)indicator.Current.Value, 1e-4); }        // allow 0.0001 error margin of indicator values
        }

        [Test]
        public void IsReadyAfterPeriodUpdates()
        {
            var ci = CreateIndicator();

            Assert.IsFalse(ci.IsReady);
            ci.Update(DateTime.UtcNow, 1m);
            Assert.IsTrue(ci.IsReady);
        }

        [Test]
        public override void ResetsProperly()
        {
            var ci = CreateIndicator();

            ci.Update(DateTime.UtcNow, 1m);
            Assert.IsTrue(ci.IsReady);
            
            ci.Reset();

            TestHelper.AssertIndicatorIsInDefaultState(ci);
        }
    }
}

For a full example, see SimpleMovingAverageTests.cs in the LEAN repository.

Documentation Changes

After the indicator was merged in the Lean engine, make sure you also ensure it is porperly documented in the documentation. Follow the below steps to do so:

  1. Create an issue in the Documentation GitHub repository regarding the required changes in the documentation.
  2. Fork the Documentation GitHub repository and create a new branch named by feature-<ISSUE_NUMBER>-<INDICATOR_NAME>-indicator.
  3. Edit the IndicatorImageGenerator.py file to include the details of the newly added indicator for documentation page generation.
  4. If the indicator only involves 1 symbol and does not depend on other indicators, put it under the indicators dictionary.

    If the indicator involves 2 or more symbols or it is a composite indicator, put it under the special_indicators dictionary.

    If the indicator is an option-related indicator (e.g. option greeks indicator), put it under the option_indicators dictionary.

    Format of the added member should be as below:

    '<hyphenated-title-case-of-the-indicator>':
    {
        'code': <IndicatorConstructor>(<constructor-arguments>),
        'title' : '<CSharpHelperMethod>(<helper-method-arguments>)',
        'columns' : [<any-extra-series-of-the-indicator>]
    },
  5. Save the file and run the API generator. It will help generate the indicator reference page.
  6. (Optional) Run the IndicatorImageGenerator.py in LeanCLI to obtain the generated plotly image of the indicator. You can retreive it from the storage folder from the root directory of the LeanCLI. Put it in the Resource indicator image folder by the name <hyphenated-title-case-of-the-indicator>.
  7. Push the branch and start a pull request on the documentation changes.

Extra Steps for Moving Average Types

A moving average is a special type of indicator that smoothes out the fluctuations in a security's price or market data. It calculates the average value of a security's price over a specified period with a special smoothing function, helping traders to identify trends and reduce noise. Moving averages can also be used in conjunction with other technical indicators to make more informed trading decisions and identify potential support or resistance levels in the market. LEAN has extra abstraction interface for indicators to implement a specific type of moving average. The MovingAverageType enumeration currently has the following members:

If you are contributing an indicator that requires a new moving average type, follow these additional steps:

  1. In the Lean / Indicators / MovingAverageType.cs file, define a new MovingAverageType enumeration member.
  2. namespace QuantConnect.Indicators
    {
        public enum MovingAverageType
        {
            ...
            /// <summary>
            /// Description of the custom moving average indicator (<the next enum number>)
            /// </summary>
            <CustomMovingAverageEnum>,
        }
    }
  3. In the Lean / Indicators / MovingAverageTypeExtensions.cs file, add a new case of your custom moving average indicator in each AsIndicator method.
  4. namespace QuantConnect.Indicators
    {
        public static class MovingAverageTypeExtensions
        {
            public static IndicatorBase<IndicatorDataPoint> AsIndicator(this MovingAverageType movingAverageType, int period)
            {
                switch (movingAverageType)
                {
                    ...
                    case MovingAverageType.CustomMovingAverageEnum:
                        return new CustomMovingAverage(period);
                }
            }
    
            public static IndicatorBase<IndicatorDataPoint> AsIndicator(this MovingAverageType movingAverageType, string name, int period)
            {
                switch (movingAverageType)
                {
                    ...
                    case MovingAverageType.CustomMovingAverageEnum:
                        return new CustomMovingAverage(name, period);
                }
            }
        }
    }
  5. In the Lean / Tests/ Indicators / MovingAverageTypeExtensionsTests.cs file, add a new test case of your custom moving average indicator that asserts the indicator is correctly instantiated through the abstraction methods.
  6. namespace QuantConnect.Tests.Indicators
    {
        [TestFixture]
        public class MovingAverageTypeExtensionsTests
        {
            [Test]
            public void CreatesCorrectAveragingIndicator()
            {
                ...
                var indicator = MovingAverageType.CustomMovingAverageEnum.AsIndicator(1);
                Assert.IsInstanceOf(typeof(CustomMovingAverage), indicator);
                ...
                string name = string.Empty;
                ...
                indicator = MovingAverageType.CustomMovingAverageEnum.AsIndicator(name, 1);
                Assert.IsInstanceOf(typeof(CustomMovingAverage), indicator);
            }
        }
    }

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: