Lean  $LEAN_TAG$
ZigZag.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;
17 using QuantConnect.Data;
19 
21 {
22  /// <summary>
23  /// The ZigZag indicator identifies significant turning points in price movements,
24  /// filtering out noise using a sensitivity threshold and a minimum trend length.
25  /// It alternates between high and low pivots to determine market trends.
26  /// </summary>
28  {
29  /// <summary>
30  /// The most recent pivot point, represented as a bar of market data.
31  /// Used as a reference for calculating subsequent pivots.
32  /// </summary>
33  private IBaseDataBar _lastPivot;
34 
35  /// <summary>
36  /// The minimum number of bars required to confirm a valid trend.
37  /// Ensures that minor fluctuations in price do not create false pivots.
38  /// </summary>
39  private readonly int _minTrendLength;
40 
41  /// <summary>
42  /// The sensitivity threshold for detecting significant price movements.
43  /// A decimal value between 0 and 1 that determines the percentage change required
44  /// to recognize a new pivot.
45  /// </summary>
46  private readonly decimal _sensitivity;
47 
48  /// <summary>
49  /// A counter to track the number of bars since the last pivot was identified.
50  /// Used to enforce the minimum trend length requirement.
51  /// </summary>
52  private int _count;
53 
54  /// <summary>
55  /// Tracks whether the most recent pivot was a low pivot.
56  /// Used to alternate between identifying high and low pivots.
57  /// </summary>
58  private bool _lastPivotWasLow;
59 
60  /// <summary>
61  /// Stores the most recent high pivot value in the ZigZag calculation.
62  /// Updated whenever a valid high pivot is identified.
63  /// </summary>
65 
66  /// <summary>
67  /// Stores the most recent low pivot value in the ZigZag calculation.
68  /// Updated whenever a valid low pivot is identified.
69  /// </summary>
71 
72  /// <summary>
73  /// Represents the current type of pivot (High or Low) in the ZigZag calculation.
74  /// The value is updated based on the most recent pivot identified:
75  /// </summary>
76  public PivotPointType PivotType { get; private set; }
77 
78  /// <summary>
79  /// Initializes a new instance of the <see cref="ZigZag"/> class with the specified parameters.
80  /// </summary>
81  /// <param name="name">The name of the indicator.</param>
82  /// <param name="sensitivity">The sensitivity threshold as a decimal value between 0 and 1.</param>
83  /// <param name="minTrendLength">The minimum number of bars required to form a valid trend.</param>
84  public ZigZag(string name, decimal sensitivity = 0.05m, int minTrendLength = 1) : base(name)
85  {
86  if (sensitivity <= 0 || sensitivity >= 1)
87  {
88  throw new ArgumentException("Sensitivity must be between 0 and 1.", nameof(sensitivity));
89  }
90 
91  if (minTrendLength < 1)
92  {
93  throw new ArgumentException("Minimum trend length must be greater than 0.", nameof(minTrendLength));
94  }
95  HighPivot = new Identity(name + "_HighPivot");
96  LowPivot = new Identity(name + "_LowPivot");
97  _sensitivity = sensitivity;
98  _minTrendLength = minTrendLength;
100  }
101 
102  /// <summary>
103  /// Initializes a new instance of the <see cref="ZigZag"/> class using default parameters.
104  /// </summary>
105  /// <param name="sensitivity">The sensitivity threshold as a decimal value between 0 and 1.</param>
106  /// <param name="minTrendLength">The minimum number of bars required to form a valid trend.</param>
107  public ZigZag(decimal sensitivity = 0.05m, int minTrendLength = 1)
108  : this($"ZZ({sensitivity},{minTrendLength})", sensitivity, minTrendLength)
109  {
110  }
111 
112  /// <summary>
113  /// Indicates whether the indicator has enough data to produce meaningful output.
114  /// The indicator is considered "ready" when the number of samples exceeds the minimum trend length.
115  /// </summary>
116  public override bool IsReady => Samples > _minTrendLength;
117 
118  /// <summary>
119  /// Gets the number of periods required for the indicator to warm up.
120  /// This is equal to the minimum trend length plus one additional bar for initialization.
121  /// </summary>
122  public int WarmUpPeriod => _minTrendLength + 1;
123 
124  /// <summary>
125  /// Computes the next value of the ZigZag indicator based on the input bar.
126  /// Determines whether the input bar forms a new pivot or updates the current trend.
127  /// </summary>
128  /// <param name="input">The current bar of market data used for the calculation.</param>
129  /// <returns>
130  /// The value of the most recent pivot, either a high or low, depending on the current trend.
131  /// </returns>
132  protected override decimal ComputeNextValue(IBaseDataBar input)
133  {
134  if (_lastPivot == null)
135  {
136  UpdatePivot(input, true);
137  return decimal.Zero;
138  }
139 
140  var currentPivot = _lastPivotWasLow ? _lastPivot.Low : _lastPivot.High;
141 
142  if (_lastPivotWasLow)
143  {
144  if (input.High >= _lastPivot.Low * (1m + _sensitivity) && _count >= _minTrendLength)
145  {
146  UpdatePivot(input, false);
147  currentPivot = HighPivot;
148  }
149  else if (input.Low <= _lastPivot.Low)
150  {
151  UpdatePivot(input, true);
152  currentPivot = LowPivot;
153  }
154  }
155  else
156  {
157  if (input.Low <= _lastPivot.High * (1m - _sensitivity) && _count >= _minTrendLength)
158  {
159  UpdatePivot(input, true);
160  currentPivot = LowPivot;
161  }
162  else if (input.High >= _lastPivot.High)
163  {
164  UpdatePivot(input, false);
165  currentPivot = HighPivot;
166  }
167  }
168  _count++;
169  return currentPivot;
170  }
171 
172  /// <summary>
173  /// Updates the pivot point based on the given input bar.
174  /// If a change in trend is detected, the pivot type is switched and the corresponding pivot (high or low) is updated.
175  /// </summary>
176  /// <param name="input">The current bar of market data used for the update.</param>
177  /// <param name="pivotDirection">Indicates whether the trend has reversed.</param>
178  private void UpdatePivot(IBaseDataBar input, bool pivotDirection)
179  {
180  _lastPivot = input;
181  _count = 0;
182  if (_lastPivotWasLow == pivotDirection)
183  {
184  //Update previous pivot
185  (_lastPivotWasLow ? LowPivot : HighPivot).Update(input.EndTime, _lastPivotWasLow ? input.Low : input.High);
186  }
187  else
188  {
189  //Create new pivot
190  (_lastPivotWasLow ? HighPivot : LowPivot).Update(input.EndTime, _lastPivotWasLow ? input.High : input.Low);
191  PivotType = _lastPivotWasLow ? PivotPointType.High : PivotPointType.Low;
192  }
193  _lastPivotWasLow = pivotDirection;
194  }
195 
196  /// <summary>
197  /// Resets this indicator to its initial state
198  /// </summary>
199  public override void Reset()
200  {
201  _lastPivot = null;
203  HighPivot.Reset();
204  LowPivot.Reset();
205  base.Reset();
206  }
207  }
208 }