Files
2026-07-13 13:02:50 +08:00

280 lines
12 KiB
C#

/*
* 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 NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Indicators;
using QuantConnect.Data.Market;
using QuantConnect.Tests.Engine.DataFeeds;
namespace QuantConnect.Tests.Indicators
{
[TestFixture]
public class PivotPointsHighLowTests : CommonIndicatorTests<IBaseDataBar>
{
protected override IndicatorBase<IBaseDataBar> CreateIndicator()
{
// Even if the indicator is ready, there may be zero values
ValueCanBeZero = true;
return new PivotPointsHighLow(10);
}
protected override string TestFileName => "spy_pivot_pnt_hl.txt";
protected override string TestColumnName => "PPHL";
[Test]
public override void ComparesAgainstExternalData()
{
var indicator = (PivotPointsHighLow)CreateIndicator();
RunTestIndicator(indicator);
var highPivotPoints = indicator.GetHighPivotPointsArray();
var lowPivotPoints = indicator.GetLowPivotPointsArray();
var pivotPoints = indicator.GetAllPivotPointsArray();
Assert.True(highPivotPoints.Length > 0);
Assert.True(lowPivotPoints.Length > 0);
Assert.True(pivotPoints.Length > 0);
Assert.AreEqual(pivotPoints.Length, highPivotPoints.Length + lowPivotPoints.Length);
Assert.That(pivotPoints, Is.Ordered.Descending.By("Time"));
}
[TestCase(PivotPointType.Low)]
[TestCase(PivotPointType.High)]
[TestCase(PivotPointType.Both)]
[TestCase(PivotPointType.None)]
public void PivotPointPerType(PivotPointType pointType)
{
var pointsHighLow = new PivotPointsHighLow(10, 20);
for (var i = 0; i < pointsHighLow.WarmUpPeriod; i++)
{
Assert.IsFalse(pointsHighLow.IsReady);
var low = 1;
var high = 1;
if (i == 10)
{
if (pointType == PivotPointType.Low || pointType == PivotPointType.Both)
{
low = 0;
}
if (pointType == PivotPointType.High || pointType == PivotPointType.Both)
{
high = 2;
}
}
var bar = new TradeBar(DateTime.UtcNow.AddSeconds(i), Symbols.AAPL, i, high, low, i, i);
pointsHighLow.Update(bar);
}
Assert.IsTrue(pointsHighLow.IsReady);
var bothPivotPoint = pointsHighLow.GetAllPivotPointsArray();
var lowPivotPoint = pointsHighLow.GetLowPivotPointsArray();
var highPivotPoint = pointsHighLow.GetHighPivotPointsArray();
if (pointType == PivotPointType.None)
{
Assert.AreEqual(0, bothPivotPoint.Length);
}
if (pointType == PivotPointType.Both)
{
Assert.AreEqual(2, bothPivotPoint.Length);
Assert.AreEqual(1, lowPivotPoint.Length);
Assert.AreEqual(1, highPivotPoint.Length);
Assert.IsTrue(lowPivotPoint.Any(point => point.Value == 0));
Assert.IsTrue(highPivotPoint.Any(point => point.Value == 2));
}
if (pointType == PivotPointType.High)
{
Assert.AreEqual(1, bothPivotPoint.Length);
Assert.AreEqual(0, lowPivotPoint.Length);
Assert.AreEqual(1, highPivotPoint.Length);
Assert.IsTrue(highPivotPoint.Any(point => point.Value == 2));
}
if (pointType == PivotPointType.Low)
{
Assert.AreEqual(1, bothPivotPoint.Length);
Assert.AreEqual(1, lowPivotPoint.Length);
Assert.AreEqual(0, highPivotPoint.Length);
Assert.IsTrue(lowPivotPoint.Any(point => point.Value == 0));
}
}
/// <summary>
/// The expected value for this indicator is always zero
/// </summary>
/// <param name="indicator"></param>
protected override void IndicatorValueIsNotZeroAfterReceiveRenkoBars(IndicatorBase indicator)
{
}
/// <summary>
/// The expected value for this indicator is always zero
/// </summary>
protected override void IndicatorValueIsNotZeroAfterReceiveVolumeRenkoBars(IndicatorBase indicator)
{
}
[TestCase(true)]
[TestCase(false)]
public void StrictVsRelaxedHighPivotDetection(bool strict)
{
var indicator = new PivotPointsHighLow(2, 2, strict: strict);
var referenceTime = new DateTime(2020, 1, 1);
// Create 5 bars where middle bar high EQUALS neighbors
// All bars have high = 100, which should only detect pivot in relaxed mode
for (var i = 0; i < 5; i++)
{
var bar = new TradeBar(referenceTime.AddSeconds(i), Symbols.AAPL, 100, 100, 90, 95, 1000);
indicator.Update(bar);
}
var highPivots = indicator.GetHighPivotPointsArray();
if (strict)
{
// Strict mode: middle bar high (100) is NOT > neighbors (100), so NO pivot
Assert.AreEqual(0, highPivots.Length, "Strict mode should reject equal high values");
}
else
{
// Relaxed mode: middle bar high (100) is >= neighbors (100), so YES pivot
Assert.AreEqual(1, highPivots.Length, "Relaxed mode should accept equal high values");
Assert.AreEqual(100, highPivots[0].Value);
}
var lowPivots = indicator.GetLowPivotPointsArray();
if (strict)
{
// Strict mode: middle bar low (50) is NOT < neighbors (50), so NO pivot
Assert.AreEqual(0, lowPivots.Length, "Strict mode should reject equal low values");
}
else
{
// Relaxed mode: middle bar low (50) is <= neighbors (50), so YES pivot
Assert.AreEqual(1, lowPivots.Length, "Relaxed mode should accept equal low values");
Assert.AreEqual(90, lowPivots[0].Value);
}
}
[Test]
public void DefaultBehaviorIsStrict()
{
// Create indicator without specifying strict parameter (should default to true)
var indicator = new PivotPointsHighLow(2, 2);
var referenceTime = new DateTime(2020, 1, 1);
// Create bars with equal high values
for (var i = 0; i < 5; i++)
{
var bar = new TradeBar(referenceTime.AddSeconds(i), Symbols.AAPL, 100, 100, 90, 95, 1000);
indicator.Update(bar);
}
var highPivots = indicator.GetHighPivotPointsArray();
// Default behavior should be strict, so NO pivot detected
Assert.AreEqual(0, highPivots.Length, "Default behavior should be strict mode");
}
[Test]
public void QCAlgorithmHelperMethodOverloadResolution()
{
// This test verifies that calling PPHL with minimal arguments compiles without ambiguity
// and uses the correct default behavior (strict mode)
// Instead of using QCAlgorithm, directly test the indicator
// The key point is that the method signature compiles without ambiguity
var indicator = new PivotPointsHighLow(2, 2);
var referenceTime = new DateTime(2020, 1, 1);
// Create bars with equal high values
for (var i = 0; i < 5; i++)
{
var bar = new TradeBar(referenceTime.AddSeconds(i), Symbols.AAPL, 100, 100, 90, 95, 1000);
indicator.Update(bar);
}
var highPivots = indicator.GetHighPivotPointsArray();
// Should default to strict mode, so NO pivot detected with equal values
Assert.AreEqual(0, highPivots.Length, "Default constructor should use strict mode");
}
[Test]
public void QCAlgorithmHelperOverloadResolution()
{
// This test verifies that all valid PPHL helper method call patterns compile without ambiguity
// and maintain backward compatibility with the original API.
// If this test compiles and passes, the overload resolution is working correctly.
var algorithm = new QCAlgorithm();
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
var spy = algorithm.AddEquity("SPY");
var symbol = spy.Symbol;
// Backward-compatible patterns that existed before adding the strict parameter:
// Pattern 1: Minimal call with just symbol and lengths
var pphl1 = algorithm.PPHL(symbol, 3, 3);
Assert.IsNotNull(pphl1, "Minimal call pattern should work");
// Pattern 2: With lastStoredValues
var pphl2 = algorithm.PPHL(symbol, 3, 3, 100);
Assert.IsNotNull(pphl2, "Call with lastStoredValues should work");
// Pattern 3: With lastStoredValues and resolution (CRITICAL backward compatibility test)
var pphl3 = algorithm.PPHL(symbol, 3, 3, 100, Resolution.Minute);
Assert.IsNotNull(pphl3, "Call with lastStoredValues and resolution should work for backward compatibility");
// Pattern 4: With lastStoredValues, resolution, and selector (CRITICAL backward compatibility test)
var pphl4 = algorithm.PPHL(symbol, 3, 3, 100, Resolution.Minute, (x) => x as IBaseDataBar);
Assert.IsNotNull(pphl4, "Full original signature should work for backward compatibility");
// New patterns with strict parameter:
// Pattern 5: With named strict parameter only
var pphl5 = algorithm.PPHL(symbol, 3, 3, strict: false);
Assert.IsNotNull(pphl5, "Call with named strict parameter should work");
// Pattern 6: With lastStoredValues and strict
var pphl6 = algorithm.PPHL(symbol, 3, 3, 100, false);
Assert.IsNotNull(pphl6, "Call with lastStoredValues and strict should work");
// Pattern 7: With lastStoredValues, strict, and resolution
var pphl7 = algorithm.PPHL(symbol, 3, 3, 100, false, Resolution.Minute);
Assert.IsNotNull(pphl7, "Call with lastStoredValues, strict, and resolution should work");
// Pattern 8: Full new signature
var pphl8 = algorithm.PPHL(symbol, 3, 3, 100, true, Resolution.Minute, (x) => x as IBaseDataBar);
Assert.IsNotNull(pphl8, "Full new signature should work");
// Pattern 9: With named parameters for clarity
var pphl9 = algorithm.PPHL(symbol, 3, 3, lastStoredValues: 50, strict: false, resolution: Resolution.Daily);
Assert.IsNotNull(pphl9, "Call with named parameters should work");
}
}
}