chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Logging;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
|
||||
namespace QuantConnect.Tests.Indicators
|
||||
{
|
||||
/// <summary>
|
||||
/// Test class for QuantConnect.Indicators.Indicator
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class IndicatorTests
|
||||
{
|
||||
[Test]
|
||||
public void NameSaves()
|
||||
{
|
||||
// just testing that we get the right name out
|
||||
const string name = "name";
|
||||
var target = new TestIndicator(name);
|
||||
Assert.AreEqual(name, target.Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdatesProperly()
|
||||
{
|
||||
// we want to make sure the initialized value is the default value
|
||||
// for a datapoint, and also verify the our indicator updates as we
|
||||
// expect it to, in this case, it should return identity
|
||||
var target = new TestIndicator();
|
||||
|
||||
Assert.AreEqual(DateTime.MinValue, target.Current.Time);
|
||||
Assert.AreEqual(0m, target.Current.Value);
|
||||
|
||||
var time = DateTime.UtcNow;
|
||||
var data = new IndicatorDataPoint(time, 1m);
|
||||
|
||||
target.Update(data);
|
||||
Assert.AreEqual(1m, target.Current.Value);
|
||||
|
||||
target.Update(new IndicatorDataPoint(time.AddMilliseconds(1), 2m));
|
||||
Assert.AreEqual(2m, target.Current.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldNotThrowOnDifferentDataType()
|
||||
{
|
||||
var target = new TestIndicator();
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
target.Update(new Tick());
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PassesOnDuplicateTimes()
|
||||
{
|
||||
var target = new TestIndicator();
|
||||
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
const decimal value1 = 1m;
|
||||
var data = new IndicatorDataPoint(time, value1);
|
||||
target.Update(data);
|
||||
Assert.AreEqual(value1, target.Current.Value);
|
||||
|
||||
// this won't update because we told it to ignore duplicate
|
||||
// data based on time
|
||||
target.Update(data);
|
||||
Assert.AreEqual(value1, target.Current.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SortsTheSameAsDecimalDescending()
|
||||
{
|
||||
int count = 100;
|
||||
var targets = Enumerable.Range(0, count)
|
||||
.Select(x => new TestIndicator(x.ToString(CultureInfo.InvariantCulture)))
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
targets[i].Update(DateTime.Today, i);
|
||||
}
|
||||
|
||||
var expected = Enumerable.Range(0, count)
|
||||
.Select(x => (decimal)x)
|
||||
.OrderByDescending(x => x)
|
||||
.ToList();
|
||||
|
||||
var actual = targets.OrderByDescending(x => x).ToList();
|
||||
foreach (var pair in expected.Zip<decimal, TestIndicator, Tuple<decimal, TestIndicator>>(actual, Tuple.Create))
|
||||
{
|
||||
Assert.AreEqual(pair.Item1, pair.Item2.Current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SortsTheSameAsDecimalAsecending()
|
||||
{
|
||||
int count = 100;
|
||||
var targets = Enumerable.Range(0, count).Select(x => new TestIndicator(x.ToString(CultureInfo.InvariantCulture))).ToList();
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
targets[i].Update(DateTime.Today, i);
|
||||
}
|
||||
|
||||
var expected = Enumerable.Range(0, count).Select(x => (decimal)x).OrderBy(x => x).ToList();
|
||||
var actual = targets.OrderBy(x => x).ToList();
|
||||
foreach (var pair in expected.Zip<decimal, TestIndicator, Tuple<decimal, TestIndicator>>(actual, Tuple.Create))
|
||||
{
|
||||
Assert.AreEqual(pair.Item1, pair.Item2.Current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ComparisonFunctions()
|
||||
{
|
||||
TestComparisonOperators<int>();
|
||||
TestComparisonOperators<long>();
|
||||
TestComparisonOperators<float>();
|
||||
TestComparisonOperators<double>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EqualsMethodShouldNotThrowExceptions()
|
||||
{
|
||||
var indicator = new TestIndicator();
|
||||
var res = true;
|
||||
try
|
||||
{
|
||||
res = indicator.Equals(new Exception(""));
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
Assert.IsFalse(res);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorMustBeEqualToItself()
|
||||
{
|
||||
var indicators = typeof(Indicator).Assembly.GetTypes()
|
||||
.Where(t => t.BaseType.Name != "CandlestickPattern" && !t.Name.StartsWith("<"))
|
||||
.OrderBy(t => t.Name)
|
||||
.ToList();
|
||||
|
||||
var counter = 0;
|
||||
object instantiatedIndicator;
|
||||
foreach (var indicator in indicators)
|
||||
{
|
||||
try
|
||||
{
|
||||
instantiatedIndicator = Activator.CreateInstance(indicator, new object[] { 10 });
|
||||
counter++;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Some indicators will fail because they don't have a single-parameter constructor.
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.IsTrue(instantiatedIndicator.Equals(instantiatedIndicator));
|
||||
var anotherInstantiatedIndicator = Activator.CreateInstance(indicator, new object[] { 10 });
|
||||
Assert.IsFalse(instantiatedIndicator.Equals(anotherInstantiatedIndicator));
|
||||
}
|
||||
Log.Trace($"{counter} indicators out of {indicators.Count} were tested.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsOfDifferentTypeDiplaySameCurrentTime()
|
||||
{
|
||||
var algorithm = new QCAlgorithm();
|
||||
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
|
||||
var spy = algorithm.AddEquity("SPY");
|
||||
|
||||
var indicatorTimeList = new List<DateTime>();
|
||||
// RSI is a DataPointIndicator
|
||||
algorithm.RSI(spy.Symbol, 14).Updated += (_, e) => indicatorTimeList.Add(e.EndTime);
|
||||
// STO is a BarIndicator
|
||||
algorithm.STO(spy.Symbol, 14, 2, 2).Updated += (_, e) => indicatorTimeList.Add(e.EndTime);
|
||||
// MFI is a TradeBarIndicator
|
||||
algorithm.MFI(spy.Symbol, 14).Updated += (_, e) => indicatorTimeList.Add(e.EndTime);
|
||||
|
||||
var consolidators = spy.Subscriptions.SelectMany(x => x.Consolidators).ToList();
|
||||
Assert.AreEqual(3, consolidators.Count); // One consolidator for each indicator
|
||||
|
||||
var bars = new[] { 30, 31 }.Select(d =>
|
||||
new TradeBar(new DateTime(2020, 03, 04, 9, d, 0),
|
||||
spy.Symbol, 100, 100, 100, 100, 1000));
|
||||
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
foreach (var consolidator in consolidators)
|
||||
{
|
||||
consolidator.Update(bar);
|
||||
}
|
||||
}
|
||||
|
||||
// All indicators should have the same EndTime, with xx:31:00 & xx:32:00
|
||||
Assert.AreEqual(6, indicatorTimeList.Count);
|
||||
Assert.AreEqual(2, indicatorTimeList.Distinct().Count());
|
||||
Assert.AreEqual(3, indicatorTimeList.Count(x => x.Minute == 31));
|
||||
Assert.AreEqual(3, indicatorTimeList.Count(x => x.Minute == 32));
|
||||
}
|
||||
|
||||
[TestCase(2)]
|
||||
[TestCase(5)]
|
||||
[TestCase(10)]
|
||||
public void IndicatorKeepsHistory(int historyWindow)
|
||||
{
|
||||
var indicator = new TestIndicator("Test indicator");
|
||||
indicator.Window.Size = historyWindow;
|
||||
|
||||
var points = new List<IndicatorDataPoint>(100);
|
||||
var referenceDate = new DateTime(2023, 06, 12, 9, 0, 0);
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
// The first iteration will not update the indicator. By default, first value is IndicatorDataPoint(DateTime.MinValue, 0)
|
||||
if (i == 0)
|
||||
{
|
||||
var defaultValue = new IndicatorDataPoint(DateTime.MinValue, 0);
|
||||
Assert.AreEqual(defaultValue, indicator.Current);
|
||||
Assert.AreEqual(defaultValue, indicator[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dateTime = referenceDate.AddMinutes(i);
|
||||
indicator.Update(dateTime, i);
|
||||
var expected = new IndicatorDataPoint(dateTime, i);
|
||||
|
||||
Assert.AreEqual(expected, indicator.Current);
|
||||
Assert.AreEqual(expected, indicator[0]);
|
||||
}
|
||||
|
||||
points.Insert(0, indicator[0]);
|
||||
var startIndex = Math.Max(0, i - historyWindow + 1);
|
||||
for (int j = startIndex; j <= i; j++)
|
||||
{
|
||||
Assert.AreEqual(points[i - j], indicator[i - j]);
|
||||
}
|
||||
|
||||
// Check the enumerator
|
||||
var windowPoints = indicator.ToList();
|
||||
var count = i - startIndex < historyWindow ? i - startIndex + 1 : historyWindow;
|
||||
CollectionAssert.AreEqual(points.GetRange(0, count), windowPoints);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HistoryWindowIsCorrectlyReset()
|
||||
{
|
||||
var indicator = new TestIndicator("Test indicator");
|
||||
indicator.Window.Size = 20;
|
||||
|
||||
// Update the indicator a few times
|
||||
var referenceDate = new DateTime(2023, 06, 12, 9, 0, 0);
|
||||
for (var i = 1; i < indicator.Window.Size; i++)
|
||||
{
|
||||
indicator.Update(referenceDate.AddMinutes(i - 1), i);
|
||||
}
|
||||
|
||||
Assert.AreEqual(indicator.Window.Size, indicator.Window.Count);
|
||||
|
||||
indicator.Reset();
|
||||
|
||||
// Window size is kept
|
||||
Assert.AreEqual(20, indicator.Window.Size);
|
||||
|
||||
// Window values are removed
|
||||
Assert.AreEqual(1, indicator.Window.Count);
|
||||
Assert.AreEqual(new IndicatorDataPoint(DateTime.MinValue, 0), indicator[0]);
|
||||
Assert.IsNull(indicator[1]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanAccessCurrentAndPreviousState()
|
||||
{
|
||||
var indicator = new TestIndicator("Test indicator");
|
||||
indicator.Window.Size = 10;
|
||||
|
||||
// Update the indicator a few times
|
||||
var referenceDate = new DateTime(2023, 06, 12, 9, 0, 0);
|
||||
var dataPoints = new List<IndicatorDataPoint>(indicator.Window.Size);
|
||||
for (var i = 0; i < indicator.Window.Size; i++)
|
||||
{
|
||||
var dateTime = referenceDate.AddMinutes(i);
|
||||
indicator.Update(dateTime, i);
|
||||
dataPoints.Add(new IndicatorDataPoint(dateTime, i));
|
||||
}
|
||||
|
||||
Assert.AreEqual(dataPoints[^1], indicator.Current);
|
||||
Assert.AreEqual(dataPoints[^1], indicator[0]);
|
||||
|
||||
Assert.AreEqual(dataPoints[^2], indicator.Previous);
|
||||
Assert.AreEqual(dataPoints[^2], indicator[1]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PreviousValueIsNotNullAtStart()
|
||||
{
|
||||
var indicator = new TestIndicator("Test indicator");
|
||||
|
||||
// Access current and previous without warming the indicator up
|
||||
var defaultValue = new IndicatorDataPoint(DateTime.MinValue, 0);
|
||||
Assert.IsNotNull(indicator.Current);
|
||||
Assert.AreEqual(defaultValue, indicator.Current);
|
||||
Assert.IsNotNull(indicator.Previous);
|
||||
Assert.AreEqual(defaultValue, indicator.Previous);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorShouldRetainSymbolWhenUpdatedWithDifferentDataType()
|
||||
{
|
||||
var target = new TestIndicator();
|
||||
var date = new DateTime(2020, 1, 1);
|
||||
target.Update(new Tick(date, Symbols.SPY, 1, 1));
|
||||
Assert.AreEqual(Symbols.SPY, target.Current.Symbol);
|
||||
}
|
||||
|
||||
private static void TestComparisonOperators<TValue>()
|
||||
{
|
||||
var indicator = new TestIndicator();
|
||||
TestOperator(indicator, default(TValue), "GreaterThan", true, false);
|
||||
TestOperator(indicator, default(TValue), "GreaterThan", false, false);
|
||||
TestOperator(indicator, default(TValue), "GreaterThanOrEqual", true, true);
|
||||
TestOperator(indicator, default(TValue), "GreaterThanOrEqual", false, true);
|
||||
TestOperator(indicator, default(TValue), "LessThan", true, false);
|
||||
TestOperator(indicator, default(TValue), "LessThan", false, false);
|
||||
TestOperator(indicator, default(TValue), "LessThanOrEqual", true, true);
|
||||
TestOperator(indicator, default(TValue), "LessThanOrEqual", false, true);
|
||||
TestOperator(indicator, default(TValue), "Equality", true, true);
|
||||
TestOperator(indicator, default(TValue), "Equality", false, true);
|
||||
TestOperator(indicator, default(TValue), "Inequality", true, false);
|
||||
TestOperator(indicator, default(TValue), "Inequality", false, false);
|
||||
}
|
||||
|
||||
private static void TestOperator<TIndicator, TValue>(TIndicator indicator, TValue value, string opName, bool tvalueIsFirstParm, bool expected)
|
||||
{
|
||||
var method = GetOperatorMethodInfo<TValue>(opName, tvalueIsFirstParm ? 0 : 1);
|
||||
var ctIndicator = Expression.Constant(indicator);
|
||||
var ctValue = Expression.Constant(value);
|
||||
var call = tvalueIsFirstParm ? Expression.Call(method, ctValue, ctIndicator) : Expression.Call(method, ctIndicator, ctValue);
|
||||
var lamda = Expression.Lambda<Func<bool>>(call);
|
||||
var func = lamda.Compile();
|
||||
Assert.AreEqual(expected, func());
|
||||
}
|
||||
|
||||
private static MethodInfo GetOperatorMethodInfo<T>(string @operator, int argIndex)
|
||||
{
|
||||
var methodName = "op_" + @operator;
|
||||
var method =
|
||||
typeof(IndicatorBase).GetMethods(BindingFlags.Static | BindingFlags.Public)
|
||||
.SingleOrDefault(x => x.Name == methodName && x.GetParameters()[argIndex].ParameterType == typeof(T));
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
Assert.Fail("Failed to find method for " + @operator + " of type " + typeof(T).Name + " at index: " + argIndex);
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
private class TestIndicator : Indicator
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Indicator class using the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of this indicator</param>
|
||||
public TestIndicator(string name)
|
||||
: base(name)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Indicator class using the name "test"
|
||||
/// </summary>
|
||||
public TestIndicator()
|
||||
: base("test")
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag indicating when this indicator is ready and fully initialized
|
||||
/// </summary>
|
||||
public override bool IsReady
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the next value of this indicator from the given state
|
||||
/// </summary>
|
||||
/// <param name="input">The input given to the indicator</param>
|
||||
/// <returns>A new value for this indicator</returns>
|
||||
protected override decimal ComputeNextValue(IndicatorDataPoint input)
|
||||
{
|
||||
return input.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user