/* * 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; using QuantConnect.Python; namespace QuantConnect.Indicators { /// /// The ZigZag indicator identifies significant turning points in price movements, /// filtering out noise using a sensitivity threshold and a minimum trend length. /// It alternates between high and low pivots to determine market trends. /// public class ZigZag : BarIndicator, IIndicatorWarmUpPeriodProvider { /// /// The most recent pivot point, represented as a bar of market data. /// Used as a reference for calculating subsequent pivots. /// private IBaseDataBar _lastPivot; /// /// The minimum number of bars required to confirm a valid trend. /// Ensures that minor fluctuations in price do not create false pivots. /// private readonly int _minTrendLength; /// /// The sensitivity threshold for detecting significant price movements. /// A decimal value between 0 and 1 that determines the percentage change required /// to recognize a new pivot. /// private readonly decimal _sensitivity; /// /// A counter to track the number of bars since the last pivot was identified. /// Used to enforce the minimum trend length requirement. /// private int _count; /// /// Tracks whether the most recent pivot was a low pivot. /// Used to alternate between identifying high and low pivots. /// private bool _lastPivotWasLow; /// /// Stores the most recent high pivot value in the ZigZag calculation. /// Updated whenever a valid high pivot is identified. /// [PandasIgnore] public Identity HighPivot { get; } /// /// Stores the most recent low pivot value in the ZigZag calculation. /// Updated whenever a valid low pivot is identified. /// [PandasIgnore] public Identity LowPivot { get; } /// /// Represents the current type of pivot (High or Low) in the ZigZag calculation. /// The value is updated based on the most recent pivot identified: /// public PivotPointType PivotType { get; private set; } /// /// Initializes a new instance of the class with the specified parameters. /// /// The name of the indicator. /// The sensitivity threshold as a decimal value between 0 and 1. /// The minimum number of bars required to form a valid trend. public ZigZag(string name, decimal sensitivity = 0.05m, int minTrendLength = 1) : base(name) { if (sensitivity <= 0 || sensitivity >= 1) { throw new ArgumentException("Sensitivity must be between 0 and 1.", nameof(sensitivity)); } if (minTrendLength < 1) { throw new ArgumentException("Minimum trend length must be greater than 0.", nameof(minTrendLength)); } HighPivot = new Identity(name + "_HighPivot"); LowPivot = new Identity(name + "_LowPivot"); _sensitivity = sensitivity; _minTrendLength = minTrendLength; PivotType = PivotPointType.Low; } /// /// Initializes a new instance of the class using default parameters. /// /// The sensitivity threshold as a decimal value between 0 and 1. /// The minimum number of bars required to form a valid trend. public ZigZag(decimal sensitivity = 0.05m, int minTrendLength = 1) : this($"ZZ({sensitivity},{minTrendLength})", sensitivity, minTrendLength) { } /// /// Indicates whether the indicator has enough data to produce meaningful output. /// The indicator is considered "ready" when the number of samples exceeds the minimum trend length. /// public override bool IsReady => Samples > _minTrendLength; /// /// Gets the number of periods required for the indicator to warm up. /// This is equal to the minimum trend length plus one additional bar for initialization. /// public int WarmUpPeriod => _minTrendLength + 1; /// /// Computes the next value of the ZigZag indicator based on the input bar. /// Determines whether the input bar forms a new pivot or updates the current trend. /// /// The current bar of market data used for the calculation. /// /// The value of the most recent pivot, either a high or low, depending on the current trend. /// protected override decimal ComputeNextValue(IBaseDataBar input) { if (_lastPivot == null) { UpdatePivot(input, true); return decimal.Zero; } var currentPivot = _lastPivotWasLow ? _lastPivot.Low : _lastPivot.High; if (_lastPivotWasLow) { if (input.High >= _lastPivot.Low * (1m + _sensitivity) && _count >= _minTrendLength) { UpdatePivot(input, false); currentPivot = HighPivot; } else if (input.Low <= _lastPivot.Low) { UpdatePivot(input, true); currentPivot = LowPivot; } } else { if (input.Low <= _lastPivot.High * (1m - _sensitivity) && _count >= _minTrendLength) { UpdatePivot(input, true); currentPivot = LowPivot; } else if (input.High >= _lastPivot.High) { UpdatePivot(input, false); currentPivot = HighPivot; } } _count++; return currentPivot; } /// /// Updates the pivot point based on the given input bar. /// If a change in trend is detected, the pivot type is switched and the corresponding pivot (high or low) is updated. /// /// The current bar of market data used for the update. /// Indicates whether the trend has reversed. private void UpdatePivot(IBaseDataBar input, bool pivotDirection) { _lastPivot = input; _count = 0; if (_lastPivotWasLow == pivotDirection) { //Update previous pivot (_lastPivotWasLow ? LowPivot : HighPivot).Update(input.EndTime, _lastPivotWasLow ? input.Low : input.High); } else { //Create new pivot (_lastPivotWasLow ? HighPivot : LowPivot).Update(input.EndTime, _lastPivotWasLow ? input.High : input.Low); PivotType = _lastPivotWasLow ? PivotPointType.High : PivotPointType.Low; } _lastPivotWasLow = pivotDirection; } /// /// Resets this indicator to its initial state /// public override void Reset() { _lastPivot = null; PivotType = PivotPointType.Low; HighPivot.Reset(); LowPivot.Reset(); base.Reset(); } } }