Contributions

Indicators

Introduction

LEAN currently supports over 100 indicators. This page explains how to contribute a new indicator to the open-source project.

Getting Third Party Values

As a quantitative algorithmic trading engine, accuracy and reliability are very important to Lean. We required third-party source values as reference points to contrast the calculated values of the created indicators. This ensures that the custom indicator implementation is correct. The below includes some examples of accepted third-party sources.

Renown 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 detailed information on how the indicator values are calculated, which provides excellent reproducibility. Thus, we accept values from these projects with high confidence. Examples include:

  • TA-Lib
  • QuantLib

Highly Credible Websites

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

Implement Indicator

You can classify an indicator as either a data point, bar, or TradeBar indicator. Their classification depends on the type of data they receive. Details refer to the Indicators section. Custom indicators subclass either one of the IndicatorBase<IndicatorDataPoint>, BarIndicator, or TradeBarIndicator classes, depending on the indicator type.

public class CustomPointIndicator : IndicatorBase<IndicatorDataPoint>, IndicatorWarmUpPeriodProvider
{
}

public class CustomBarIndicator : BarIndicator, IIndicatorWarmUpPeriodProvider
{
}

public class CustomTradeBarIndicator : TradeBarIndicator, IIndicatorWarmUpPeriodProvider
{
}

If you would like to construct an indicator using multiple periods of BaseData to calculate its value, you can use a WindowIndicator.

public class CustomWindowIndicator : WindowIndicator<IndicatorDataPoint>
{
}

Indicator Structure

There are various properties that require manual inputs, including:

PropertyTypeDescription
WarmUpPeriodintThe number of data entries required to calculate an accurate indicator value.
IsReadyboolA flag on whether the indicator is readily useable, depending on the conditions given.

You must also implement a ComputeNextValue method to calculate the indicator value. Depending on the indicator type, the input argument(s) is/are the type of data it receives, but the method should output a single value, which is the updated indicator value.

Some indicators might produce invalid values on rare occasions. For example, a wild moving average due to extreme quotes. In such cases, you should also override the WindowIndicator method to return an invalid IndicatorResult, such that the indicator value will not be passed to the main algorithm.

public class CustomPointIndicator : IndicatorBase<IndicatorDataPoint>, IndicatorWarmUpPeriodProvider
{
    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);
    }
}

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);
    }
}

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);
    }
}

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);
    }
}

QCAlgorithm Helper Method

Except for the basic version of manual updating indicator, a helper method should also be created within the QCAlgorithm class under the file Lean/Algorithm/QCAlgorithm.Indicators.cs, to implement an automatic updating version of the indicator. The method should be named by the short abbreviation of the indicator's full name. It should also register the indicator with a consolidator to automatically update it.

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;
}

Testing

Unit tests should be performed to ensure the custom indicator is functioning correctly and producing accurate values. Custom indicator unit test class shall subclass from the CommonIndicatorTests<T> class, where T is the data type the indicator received.

You should save a CSV file containing the 3rd-party source data in Lean/Tests/TestData directory and compile the data file in Lean/Tests/QuantConnect.Tests.csproj. Override the attribute TestFileName and TestColumnName with the corresponding CSV file name and the column name of the testing values for 3rd-party source comparison testing.

For functionality testing, it is typical to test if the constructor, IsReady flag and Reset method working correctly. If there are other custom calculation methods used, it is also recommended to put that on unit testing.

The below 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);
        }
    }
}

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: