/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using QuantConnect.Data.Market; namespace QuantConnect.Indicators { /// /// This indicator computes Average Directional Index which measures trend strength without regard to trend direction. /// Firstly, it calculates the Directional Movement and the True Range value, and then the values are accumulated and smoothed /// using a custom smoothing method proposed by Wilder. For an n period smoothing, 1/n of each period's value is added to the total period. /// From these accumulated values we are therefore able to derived the 'Positive Directional Index' (+DI) and 'Negative Directional Index' (-DI) /// which is used to calculate the Average Directional Index. /// Computation source: /// https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:average_directional_index_adx /// public class AverageDirectionalIndex : BarIndicator, IIndicatorWarmUpPeriodProvider { private readonly int _period; private readonly IndicatorBase _trueRange; private readonly IndicatorBase _directionalMovementPlus; private readonly IndicatorBase _directionalMovementMinus; private readonly IndicatorBase _smoothedTrueRange; private readonly IndicatorBase _smoothedDirectionalMovementPlus; private readonly IndicatorBase _smoothedDirectionalMovementMinus; private readonly IndicatorBase _averageDirectionalIndex; private IBaseDataBar _previousInput; /// /// Gets a flag indicating when this indicator is ready and fully initialized /// public override bool IsReady => _averageDirectionalIndex.IsReady; /// /// Gets the index of the Plus Directional Indicator /// /// /// The index of the Plus Directional Indicator. /// public IndicatorBase PositiveDirectionalIndex { get; } /// /// Gets the index of the Minus Directional Indicator /// /// /// The index of the Minus Directional Indicator. /// public IndicatorBase NegativeDirectionalIndex { get; } /// /// Required period, in data points, for the indicator to be ready and fully initialized. /// public int WarmUpPeriod => _period * 2; /// /// Initializes a new instance of the class. /// /// The period. public AverageDirectionalIndex(int period) : this($"ADX({period})", period) { } /// /// Initializes a new instance of the class. /// /// The name. /// The period. public AverageDirectionalIndex(string name, int period) : base(name) { _period = period; _trueRange = new FunctionalIndicator(name + "_TrueRange", ComputeTrueRange, isReady => _previousInput != null ); _directionalMovementPlus = new FunctionalIndicator(name + "_PositiveDirectionalMovement", ComputePositiveDirectionalMovement, isReady => _previousInput != null ); _directionalMovementMinus = new FunctionalIndicator(name + "_NegativeDirectionalMovement", ComputeNegativeDirectionalMovement, isReady => _previousInput != null ); PositiveDirectionalIndex = new FunctionalIndicator(name + "_PositiveDirectionalIndex", input => { // Computes the Plus Directional Indicator(+DI period). if (_smoothedTrueRange != 0 && _smoothedDirectionalMovementPlus.IsReady) { return 100m * _smoothedDirectionalMovementPlus.Current.Value / _smoothedTrueRange.Current.Value; } return 0m; }, positiveDirectionalIndex => _smoothedDirectionalMovementPlus.IsReady, () => { _directionalMovementPlus.Reset(); _trueRange.Reset(); } ); NegativeDirectionalIndex = new FunctionalIndicator(name + "_NegativeDirectionalIndex", input => { // Computes the Minus Directional Indicator(-DI period). if (_smoothedTrueRange != 0 && _smoothedDirectionalMovementMinus.IsReady) { return 100m * _smoothedDirectionalMovementMinus.Current.Value / _smoothedTrueRange.Current.Value; } return 0m; }, negativeDirectionalIndex => _smoothedDirectionalMovementMinus.IsReady, () => { _directionalMovementMinus.Reset(); _trueRange.Reset(); } ); _smoothedTrueRange = new FunctionalIndicator(name + "_SmoothedTrueRange", input => { // Computes the Smoothed True Range value. var value = Samples > _period + 1 ? _smoothedTrueRange.Current.Value / _period : 0m; return _smoothedTrueRange.Current.Value + _trueRange.Current.Value - value; }, isReady => Samples > period ); _smoothedDirectionalMovementPlus = new FunctionalIndicator(name + "_SmoothedDirectionalMovementPlus", input => { // Computes the Smoothed Directional Movement Plus value. var value = Samples > _period + 1 ? _smoothedDirectionalMovementPlus.Current.Value / _period : 0m; return _smoothedDirectionalMovementPlus.Current.Value + _directionalMovementPlus.Current.Value - value; }, isReady => Samples > period ); _smoothedDirectionalMovementMinus = new FunctionalIndicator(name + "_SmoothedDirectionalMovementMinus", input => { // Computes the Smoothed Directional Movement Minus value. var value = Samples > _period + 1 ? _smoothedDirectionalMovementMinus.Current.Value / _period : 0m; return _smoothedDirectionalMovementMinus.Current.Value + _directionalMovementMinus.Current.Value - value; }, isReady => Samples > period ); _averageDirectionalIndex = new WilderMovingAverage(period); } /// /// Computes the True Range value. /// /// The input. /// private decimal ComputeTrueRange(IBaseDataBar input) { if (_previousInput == null) return 0m; var range1 = input.High - input.Low; var range2 = Math.Abs(input.High - _previousInput.Close); var range3 = Math.Abs(input.Low - _previousInput.Close); return Math.Max(range1, Math.Max(range2, range3)); } /// /// Computes the positive directional movement. /// /// The input. /// private decimal ComputePositiveDirectionalMovement(IBaseDataBar input) { if (_previousInput != null && input.High > _previousInput.High && input.High - _previousInput.High >= _previousInput.Low - input.Low) { return input.High - _previousInput.High; } return 0m; } /// /// Computes the negative directional movement. /// /// The input. /// private decimal ComputeNegativeDirectionalMovement(IBaseDataBar input) { if (_previousInput != null && _previousInput.Low > input.Low && _previousInput.Low - input.Low > input.High - _previousInput.High) { return _previousInput.Low - input.Low; } return 0m; } /// /// Computes the next value of this indicator from the given state /// /// The input given to the indicator /// A new value for this indicator protected override decimal ComputeNextValue(IBaseDataBar input) { _trueRange.Update(input); _directionalMovementPlus.Update(input); _directionalMovementMinus.Update(input); _smoothedTrueRange.Update(Current); _smoothedDirectionalMovementPlus.Update(Current); _smoothedDirectionalMovementMinus.Update(Current); _previousInput = input; PositiveDirectionalIndex.Update(Current); NegativeDirectionalIndex.Update(Current); var diff = Math.Abs(PositiveDirectionalIndex.Current.Value - NegativeDirectionalIndex.Current.Value); var sum = PositiveDirectionalIndex.Current.Value + NegativeDirectionalIndex.Current.Value; if (sum == 0) return 50m; _averageDirectionalIndex.Update(input.EndTime, 100m * diff / sum); return _averageDirectionalIndex.Current.Value; } /// /// Resets this indicator to its initial state /// public override void Reset() { base.Reset(); _previousInput = null; _trueRange.Reset(); _directionalMovementPlus.Reset(); _directionalMovementMinus.Reset(); _smoothedTrueRange.Reset(); _smoothedDirectionalMovementPlus.Reset(); _smoothedDirectionalMovementMinus.Reset(); _averageDirectionalIndex.Reset(); PositiveDirectionalIndex.Reset(); NegativeDirectionalIndex.Reset(); } } }