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:
Property | Type | Description |
---|---|---|
WarmUpPeriod | int | The minimum number of data entries required to calculate an accurate indicator value. |
IsReady | bool | A 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:
- Save the third-party values in the Lean / Tests / TestData directory as a CSV file.
-
In the Lean / Tests / QuantConnect.Tests.csproj file, reference the new data file.
<Content Include="TestData\<filePath>.csv"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content>
-
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 } } }
- Set the values of the
TestFileName
andTestColumnName
attributes to the CSV file name and the column name of the testing values in the CSV file of third-party values, respectively. - Add test cases.
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:
- Create an issue in the Documentation GitHub repository regarding the required changes in the documentation.
- Fork the Documentation GitHub repository and create a new branch named by
feature-<ISSUE_NUMBER>-<INDICATOR_NAME>-indicator
. - Edit the IndicatorImageGenerator.py file to include the details of the newly added indicator for documentation page generation.
- Save the file and run the API generator. It will help generate the indicator reference page.
- (Optional) Run the
IndicatorImageGenerator.py
in LeanCLI to obtain the generated plotly image of the indicator. You can retreive it from thestorage
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>
. - Push the branch and start a pull request on the documentation changes.
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>] },
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:
- In the Lean / Indicators / MovingAverageType.cs file, define a new
MovingAverageType
enumeration member. - In the Lean / Indicators / MovingAverageTypeExtensions.cs file, add a new case of your custom moving average indicator in each
AsIndicator
method. - 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.
namespace QuantConnect.Indicators { public enum MovingAverageType { ... /// <summary> /// Description of the custom moving average indicator (<the next enum number>) /// </summary> <CustomMovingAverageEnum>, } }
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); } } } }
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); } } }