Lean  $LEAN_TAG$
AverageDirectionalIndex.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
18 
20 {
21  /// <summary>
22  /// This indicator computes Average Directional Index which measures trend strength without regard to trend direction.
23  /// Firstly, it calculates the Directional Movement and the True Range value, and then the values are accumulated and smoothed
24  /// 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.
25  /// From these accumulated values we are therefore able to derived the 'Positive Directional Index' (+DI) and 'Negative Directional Index' (-DI)
26  /// which is used to calculate the Average Directional Index.
27  /// Computation source:
28  /// https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:average_directional_index_adx
29  /// </summary>
31  {
32  private readonly int _period;
33  private readonly IndicatorBase<IBaseDataBar> _trueRange;
34  private readonly IndicatorBase<IBaseDataBar> _directionalMovementPlus;
35  private readonly IndicatorBase<IBaseDataBar> _directionalMovementMinus;
36  private readonly IndicatorBase<IndicatorDataPoint> _smoothedTrueRange;
37  private readonly IndicatorBase<IndicatorDataPoint> _smoothedDirectionalMovementPlus;
38  private readonly IndicatorBase<IndicatorDataPoint> _smoothedDirectionalMovementMinus;
39  private readonly IndicatorBase<IndicatorDataPoint> _averageDirectionalIndex;
40  private IBaseDataBar _previousInput;
41 
42  /// <summary>
43  /// Gets a flag indicating when this indicator is ready and fully initialized
44  /// </summary>
45  public override bool IsReady => _averageDirectionalIndex.IsReady;
46 
47  /// <summary>
48  /// Gets the index of the Plus Directional Indicator
49  /// </summary>
50  /// <value>
51  /// The index of the Plus Directional Indicator.
52  /// </value>
54 
55  /// <summary>
56  /// Gets the index of the Minus Directional Indicator
57  /// </summary>
58  /// <value>
59  /// The index of the Minus Directional Indicator.
60  /// </value>
62 
63  /// <summary>
64  /// Required period, in data points, for the indicator to be ready and fully initialized.
65  /// </summary>
66  public int WarmUpPeriod => _period * 2;
67 
68  /// <summary>
69  /// Initializes a new instance of the <see cref="AverageDirectionalIndex"/> class.
70  /// </summary>
71  /// <param name="period">The period.</param>
72  public AverageDirectionalIndex(int period)
73  : this($"ADX({period})", period)
74  {
75  }
76 
77  /// <summary>
78  /// Initializes a new instance of the <see cref="AverageDirectionalIndex"/> class.
79  /// </summary>
80  /// <param name="name">The name.</param>
81  /// <param name="period">The period.</param>
82  public AverageDirectionalIndex(string name, int period)
83  : base(name)
84  {
85  _period = period;
86 
87  _trueRange = new FunctionalIndicator<IBaseDataBar>(name + "_TrueRange",
88  ComputeTrueRange,
89  isReady => _previousInput != null
90  );
91 
92  _directionalMovementPlus = new FunctionalIndicator<IBaseDataBar>(name + "_PositiveDirectionalMovement",
93  ComputePositiveDirectionalMovement,
94  isReady => _previousInput != null
95  );
96 
97  _directionalMovementMinus = new FunctionalIndicator<IBaseDataBar>(name + "_NegativeDirectionalMovement",
98  ComputeNegativeDirectionalMovement,
99  isReady => _previousInput != null
100  );
101 
102  PositiveDirectionalIndex = new FunctionalIndicator<IndicatorDataPoint>(name + "_PositiveDirectionalIndex",
103  input =>
104  {
105  // Computes the Plus Directional Indicator(+DI period).
106  if (_smoothedTrueRange != 0 && _smoothedDirectionalMovementPlus.IsReady)
107  {
108  return 100m * _smoothedDirectionalMovementPlus.Current.Value / _smoothedTrueRange.Current.Value;
109  }
110  return 0m;
111  },
112  positiveDirectionalIndex => _smoothedDirectionalMovementPlus.IsReady,
113  () =>
114  {
115  _directionalMovementPlus.Reset();
116  _trueRange.Reset();
117  }
118  );
119 
120  NegativeDirectionalIndex = new FunctionalIndicator<IndicatorDataPoint>(name + "_NegativeDirectionalIndex",
121  input =>
122  {
123  // Computes the Minus Directional Indicator(-DI period).
124  if (_smoothedTrueRange != 0 && _smoothedDirectionalMovementMinus.IsReady)
125  {
126  return 100m * _smoothedDirectionalMovementMinus.Current.Value / _smoothedTrueRange.Current.Value;
127  }
128  return 0m;
129  },
130  negativeDirectionalIndex => _smoothedDirectionalMovementMinus.IsReady,
131  () =>
132  {
133  _directionalMovementMinus.Reset();
134  _trueRange.Reset();
135  }
136  );
137 
138  _smoothedTrueRange = new FunctionalIndicator<IndicatorDataPoint>(name + "_SmoothedTrueRange",
139  input =>
140  {
141  // Computes the Smoothed True Range value.
142  var value = Samples > _period + 1 ? _smoothedTrueRange.Current.Value / _period : 0m;
143  return _smoothedTrueRange.Current.Value + _trueRange.Current.Value - value;
144  },
145  isReady => Samples > period
146  );
147 
148  _smoothedDirectionalMovementPlus = new FunctionalIndicator<IndicatorDataPoint>(name + "_SmoothedDirectionalMovementPlus",
149  input =>
150  {
151  // Computes the Smoothed Directional Movement Plus value.
152  var value = Samples > _period + 1 ? _smoothedDirectionalMovementPlus.Current.Value / _period : 0m;
153  return _smoothedDirectionalMovementPlus.Current.Value + _directionalMovementPlus.Current.Value - value;
154  },
155  isReady => Samples > period
156  );
157 
158  _smoothedDirectionalMovementMinus = new FunctionalIndicator<IndicatorDataPoint>(name + "_SmoothedDirectionalMovementMinus",
159  input =>
160  {
161  // Computes the Smoothed Directional Movement Minus value.
162  var value = Samples > _period + 1 ? _smoothedDirectionalMovementMinus.Current.Value / _period : 0m;
163  return _smoothedDirectionalMovementMinus.Current.Value + _directionalMovementMinus.Current.Value - value;
164  },
165  isReady => Samples > period
166  );
167 
168  _averageDirectionalIndex = new WilderMovingAverage(period);
169  }
170 
171  /// <summary>
172  /// Computes the True Range value.
173  /// </summary>
174  /// <param name="input">The input.</param>
175  /// <returns></returns>
176  private decimal ComputeTrueRange(IBaseDataBar input)
177  {
178  if (_previousInput == null) return 0m;
179 
180  var range1 = input.High - input.Low;
181  var range2 = Math.Abs(input.High - _previousInput.Close);
182  var range3 = Math.Abs(input.Low - _previousInput.Close);
183 
184  return Math.Max(range1, Math.Max(range2, range3));
185  }
186 
187  /// <summary>
188  /// Computes the positive directional movement.
189  /// </summary>
190  /// <param name="input">The input.</param>
191  /// <returns></returns>
192  private decimal ComputePositiveDirectionalMovement(IBaseDataBar input)
193  {
194  if (_previousInput != null &&
195  input.High > _previousInput.High &&
196  input.High - _previousInput.High >= _previousInput.Low - input.Low)
197  {
198  return input.High - _previousInput.High;
199  }
200  return 0m;
201  }
202 
203  /// <summary>
204  /// Computes the negative directional movement.
205  /// </summary>
206  /// <param name="input">The input.</param>
207  /// <returns></returns>
208  private decimal ComputeNegativeDirectionalMovement(IBaseDataBar input)
209  {
210  if (_previousInput != null &&
211  _previousInput.Low > input.Low &&
212  _previousInput.Low - input.Low > input.High - _previousInput.High)
213  {
214  return _previousInput.Low - input.Low;
215  }
216  return 0m;
217  }
218 
219  /// <summary>
220  /// Computes the next value of this indicator from the given state
221  /// </summary>
222  /// <param name="input">The input given to the indicator</param>
223  /// <returns>A new value for this indicator</returns>
224  protected override decimal ComputeNextValue(IBaseDataBar input)
225  {
226  _trueRange.Update(input);
227  _directionalMovementPlus.Update(input);
228  _directionalMovementMinus.Update(input);
229  _smoothedTrueRange.Update(Current);
230  _smoothedDirectionalMovementPlus.Update(Current);
231  _smoothedDirectionalMovementMinus.Update(Current);
232  _previousInput = input;
233 
234  PositiveDirectionalIndex.Update(Current);
235  NegativeDirectionalIndex.Update(Current);
236 
237  var diff = Math.Abs(PositiveDirectionalIndex.Current.Value - NegativeDirectionalIndex.Current.Value);
238  var sum = PositiveDirectionalIndex.Current.Value + NegativeDirectionalIndex.Current.Value;
239  if (sum == 0) return 50m;
240 
241  _averageDirectionalIndex.Update(input.EndTime, 100m * diff / sum);
242 
243  return _averageDirectionalIndex.Current.Value;
244  }
245 
246  /// <summary>
247  /// Resets this indicator to its initial state
248  /// </summary>
249  public override void Reset()
250  {
251  base.Reset();
252  _previousInput = null;
253  _trueRange.Reset();
254  _directionalMovementPlus.Reset();
255  _directionalMovementMinus.Reset();
256  _smoothedTrueRange.Reset();
257  _smoothedDirectionalMovementPlus.Reset();
258  _smoothedDirectionalMovementMinus.Reset();
259  _averageDirectionalIndex.Reset();
260  PositiveDirectionalIndex.Reset();
261  NegativeDirectionalIndex.Reset();
262  }
263  }
264 }