/* * 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 System.Linq; using QuantConnect.Data; using QuantConnect.Data.Market; namespace QuantConnect.Indicators { /// /// Pivot Points (High/Low), also known as Bar Count Reversals, indicator. /// https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/pivot-points-high-low /// public class PivotPointsHighLow : IndicatorBase, IIndicatorWarmUpPeriodProvider { private readonly int _surroundingBarsCountForHighPoint; private readonly int _surroundingBarsCountForLowPoint; private readonly RollingWindow _windowHighs; private readonly RollingWindow _windowLows; // Stores information of that last N pivot points private readonly RollingWindow _windowPivotPoints; /// /// Event informs of new pivot point formed with new data update /// public event EventHandler NewPivotPointFormed; /// /// Gets a flag indicating when this indicator is ready and fully initialized /// public override bool IsReady => _windowHighs.IsReady && _windowLows.IsReady; /// /// Required period, in data points, for the indicator to be ready and fully initialized. /// public int WarmUpPeriod { get; protected set; } /// /// Creates a new instance of indicator with an equal high and low length /// /// The length parameter here defines the number of surrounding bars that we compare against the current bar high and lows for the max/min /// The number of last stored indicator values public PivotPointsHighLow(int surroundingBarsCount, int lastStoredValues = 100) : this($"PivotPointsHighLow({surroundingBarsCount})", surroundingBarsCount, surroundingBarsCount, lastStoredValues) { } /// /// Creates a new instance of indicator /// /// The number of surrounding bars whose high values should be less than the current bar's for the bar high to be marked as high pivot point /// The number of surrounding bars whose low values should be more than the current bar's for the bar low to be marked as low pivot point /// The number of last stored indicator values public PivotPointsHighLow(int surroundingBarsCountForHighPoint, int surroundingBarsCountForLowPoint, int lastStoredValues = 100) : this($"PivotPointsHighLow({surroundingBarsCountForHighPoint},{surroundingBarsCountForLowPoint})", surroundingBarsCountForHighPoint, surroundingBarsCountForLowPoint, lastStoredValues) { } /// /// Creates a new instance of indicator /// /// The name of an indicator /// The number of surrounding bars whose high values should be less than the current bar's for the bar high to be marked as high pivot point /// The number of surrounding bars whose low values should be more than the current bar's for the bar low to be marked as low pivot point /// The number of last stored indicator values public PivotPointsHighLow(string name, int surroundingBarsCountForHighPoint, int surroundingBarsCountForLowPoint, int lastStoredValues = 100) : base(name) { _surroundingBarsCountForHighPoint = surroundingBarsCountForHighPoint; _surroundingBarsCountForLowPoint = surroundingBarsCountForLowPoint; _windowHighs = new RollingWindow(2 * surroundingBarsCountForHighPoint + 1); _windowLows = new RollingWindow(2 * _surroundingBarsCountForLowPoint + 1); _windowPivotPoints = new RollingWindow(lastStoredValues); WarmUpPeriod = Math.Max(_windowHighs.Size, _windowLows.Size); } /// /// 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) { _windowHighs.Add(input); _windowLows.Add(input); PivotPoint highPoint = null, lowPoint = null; if (_windowHighs.IsReady) { highPoint = FindNextHighPivotPoint(_windowHighs, _surroundingBarsCountForHighPoint); } if (_windowLows.IsReady) { lowPoint = FindNextLowPivotPoint(_windowLows, _surroundingBarsCountForLowPoint); } OnNewPivotPointFormed(highPoint); OnNewPivotPointFormed(lowPoint); return ConvertToComputedValue(highPoint, lowPoint); } /// /// Looks for the next low pivot point. /// /// rolling window that tracks the lows /// The midpoint index or surrounding bars count for lows /// pivot point if found else null protected virtual PivotPoint FindNextLowPivotPoint(RollingWindow windowLows, int midPointIndexOrSurroundingBarsCount) { var isLow = true; var middlePoint = windowLows[midPointIndexOrSurroundingBarsCount]; for (var k = 0; k < windowLows.Size && isLow; k++) { if (k == midPointIndexOrSurroundingBarsCount) { continue; } isLow = windowLows[k].Low > middlePoint.Low; } PivotPoint low = null; if (isLow) { low = new PivotPoint(PivotPointType.Low, middlePoint.Low, middlePoint.EndTime); } return low; } /// /// Looks for the next high pivot point. /// /// rolling window that tracks the highs /// The midpoint index or surrounding bars count for highs /// pivot point if found else null protected virtual PivotPoint FindNextHighPivotPoint(RollingWindow windowHighs, int midPointIndexOrSurroundingBarsCount) { var isHigh = true; var middlePoint = windowHighs[midPointIndexOrSurroundingBarsCount]; for (var k = 0; k < windowHighs.Size && isHigh; k++) { // Skip the middle point if (k == midPointIndexOrSurroundingBarsCount) { continue; } // Check if current high is below middle point high isHigh = windowHighs[k].High < middlePoint.High; } PivotPoint high = null; if (isHigh) { high = new PivotPoint(PivotPointType.High, middlePoint.High, middlePoint.EndTime); } return high; } /// /// Method for converting high and low pivot points to a decimal value. /// /// new high point or null /// new low point or null /// a decimal value representing the values of high and low pivot points protected virtual decimal ConvertToComputedValue(PivotPoint highPoint, PivotPoint lowPoint) { if (highPoint != null) { if (lowPoint != null) { // Can be the bar forms both high and low pivot points at the same time return (decimal)PivotPointType.Both; } return (decimal)PivotPointType.High; } if (lowPoint != null) { return (decimal)PivotPointType.Low; } return (decimal)PivotPointType.None; } /// /// Resets this indicator to its initial state /// public override void Reset() { _windowHighs.Reset(); _windowLows.Reset(); _windowPivotPoints.Reset(); base.Reset(); } /// /// Get current high pivot points, in the order such that first element in collection is the nearest to the present date /// /// An array of high pivot points. /// Returned array can be empty if no points have been registered yet/ public PivotPoint[] GetHighPivotPointsArray() { return _windowPivotPoints.Where(p => p.PivotPointType == PivotPointType.High).ToArray(); } /// /// Get current low pivot points, in the order such that first element in collection is the nearest to the present date /// /// An array of low pivot points. /// Returned array can be empty if no points have been registered yet/ public PivotPoint[] GetLowPivotPointsArray() { return _windowPivotPoints.Where(p => p.PivotPointType == PivotPointType.Low).ToArray(); } /// /// Get all pivot points, in the order such that first element in collection is the nearest to the present date /// /// An array of low and high pivot points. Ordered by time in descending order. /// Returned array can be empty if no points have been registered yet/ public PivotPoint[] GetAllPivotPointsArray() { // Get all pivot points within rolling wind. collection as an array return _windowPivotPoints.ToArray(); } /// /// Invokes NewPivotPointFormed event /// private void OnNewPivotPointFormed(PivotPoint pivotPoint) { if (pivotPoint != null) { _windowPivotPoints.Add(pivotPoint); NewPivotPointFormed?.Invoke(this, new PivotPointsEventArgs(pivotPoint)); } } } /// /// Represents the points identified by Pivot Point High/Low Indicator. /// public class PivotPoint : BaseData { /// /// Represents pivot point type : High or Low /// public PivotPointType PivotPointType { get; set; } /// /// Peak value /// public sealed override decimal Value { get; set; } /// /// Creates a new instance of /// public PivotPoint(PivotPointType type, decimal price, DateTime time) { PivotPointType = type; Value = price; Time = time; } } /// /// Pivot point direction /// public enum PivotPointType { /// /// Low pivot point (-1) /// Low = -1, /// /// No pivot point (0) /// None = 0, /// /// High pivot point (1) /// High = 1, /// /// Both high and low pivot point (2) /// Both = 2 } /// /// Event arguments class for the event /// public class PivotPointsEventArgs : EventArgs { /// /// New pivot point /// public PivotPoint PivotPoint { get; } /// /// Creates a new instance of /// /// public PivotPointsEventArgs(PivotPoint pivotPoint) { PivotPoint = pivotPoint; } } }