03efc1b735
* Convert usages of implicit operator in IndicatorBase and IndicatorDataPoint * Reverting changes to example code
574 lines
19 KiB
C#
574 lines
19 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 NUnit.Framework;
|
|
using QuantConnect.Indicators;
|
|
using System.Linq;
|
|
using Python.Runtime;
|
|
using QuantConnect.Data;
|
|
|
|
namespace QuantConnect.Tests.Indicators
|
|
{
|
|
[TestFixture]
|
|
public class IndicatorExtensionsTests
|
|
{
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void PipesDataUsingOfFromFirstToSecond()
|
|
{
|
|
var first = new SimpleMovingAverage(2);
|
|
var second = new Delay(1);
|
|
|
|
// this is a configuration step, but returns the reference to the second for method chaining
|
|
second.Of(first);
|
|
|
|
var data1 = new IndicatorDataPoint(DateTime.UtcNow, 1m);
|
|
var data2 = new IndicatorDataPoint(DateTime.UtcNow, 2m);
|
|
var data3 = new IndicatorDataPoint(DateTime.UtcNow, 3m);
|
|
var data4 = new IndicatorDataPoint(DateTime.UtcNow, 4m);
|
|
|
|
// sma has one item
|
|
first.Update(data1);
|
|
Assert.IsFalse(first.IsReady);
|
|
Assert.AreEqual(0m, second.Current.Value);
|
|
|
|
// sma is ready, delay will repeat this value
|
|
first.Update(data2);
|
|
Assert.IsTrue(first.IsReady);
|
|
Assert.IsFalse(second.IsReady);
|
|
Assert.AreEqual(1.5m, second.Current.Value);
|
|
|
|
// delay is ready, and repeats its first input
|
|
first.Update(data3);
|
|
Assert.IsTrue(second.IsReady);
|
|
Assert.AreEqual(1.5m, second.Current.Value);
|
|
|
|
// now getting the delayed data
|
|
first.Update(data4);
|
|
Assert.AreEqual(2.5m, second.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void PipesDataFirstWeightedBySecond()
|
|
{
|
|
const int period = 4;
|
|
var value = new Identity("Value");
|
|
var weight = new Identity("Weight");
|
|
|
|
var third = value.WeightedBy(weight, period);
|
|
|
|
var data = Enumerable.Range(1, 10).ToList();
|
|
var window = Enumerable.Reverse(data).Take(period);
|
|
var current = window.Sum(x => 2 * x * x) / (decimal)window.Sum(x => x);
|
|
|
|
foreach (var item in data)
|
|
{
|
|
value.Update(new IndicatorDataPoint(DateTime.UtcNow, Convert.ToDecimal(2 * item)));
|
|
weight.Update(new IndicatorDataPoint(DateTime.UtcNow, Convert.ToDecimal(item)));
|
|
}
|
|
|
|
Assert.AreEqual(current, third.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void NewDataPushesToDerivedIndicators()
|
|
{
|
|
var identity = new Identity("identity");
|
|
var sma = new SimpleMovingAverage(3);
|
|
|
|
identity.Updated += (sender, consolidated) =>
|
|
{
|
|
sma.Update(consolidated);
|
|
};
|
|
|
|
identity.Update(DateTime.UtcNow, 1m);
|
|
identity.Update(DateTime.UtcNow, 2m);
|
|
Assert.IsFalse(sma.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 3m);
|
|
Assert.IsTrue(sma.IsReady);
|
|
Assert.AreEqual(2m, sma.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void MultiChainSMA()
|
|
{
|
|
var identity = new Identity("identity");
|
|
var delay = new Delay(2);
|
|
|
|
// create the SMA of the delay of the identity
|
|
var sma = delay.Of(identity).SMA(2);
|
|
|
|
identity.Update(DateTime.UtcNow, 1m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(sma.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 2m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(sma.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 3m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsFalse(sma.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 4m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsTrue(sma.IsReady);
|
|
|
|
Assert.AreEqual(1.5m, sma.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void MultiChainEMA()
|
|
{
|
|
var identity = new Identity("identity");
|
|
var delay = new Delay(2);
|
|
|
|
// create the EMA of chained methods
|
|
var ema = delay.Of(identity).EMA(2, 1);
|
|
|
|
// Assert.IsTrue(ema. == 1);
|
|
identity.Update(DateTime.UtcNow, 1m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(ema.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 2m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(ema.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 3m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsFalse(ema.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 4m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsTrue(ema.IsReady);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void MultiChainMAX()
|
|
{
|
|
var identity = new Identity("identity");
|
|
var delay = new Delay(2);
|
|
|
|
// create the MAX of the delay of the identity
|
|
var max = delay.Of(identity).MAX(2);
|
|
|
|
identity.Update(DateTime.UtcNow, 1m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(max.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 2m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(max.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 3m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsFalse(max.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 4m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsTrue(max.IsReady);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void MultiChainMIN()
|
|
{
|
|
var identity = new Identity("identity");
|
|
var delay = new Delay(2);
|
|
|
|
// create the MIN of the delay of the identity
|
|
var min = delay.Of(identity).MIN(2);
|
|
|
|
identity.Update(DateTime.UtcNow, 1m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(min.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 2m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsFalse(delay.IsReady);
|
|
Assert.IsFalse(min.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 3m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsFalse(min.IsReady);
|
|
|
|
identity.Update(DateTime.UtcNow, 4m);
|
|
Assert.IsTrue(identity.IsReady);
|
|
Assert.IsTrue(delay.IsReady);
|
|
Assert.IsTrue(min.IsReady);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void PlusAddsLeftAndRightAfterBothUpdated()
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = left.Plus(right);
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
right.Update(DateTime.Today, 1m);
|
|
Assert.AreEqual(2m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(2m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 3m);
|
|
Assert.AreEqual(2m, composite.Current.Value);
|
|
|
|
right.Update(DateTime.Today, 4m);
|
|
Assert.AreEqual(7m, composite.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void PlusAddsLeftAndConstant()
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = left.Plus(5);
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
Assert.AreEqual(6m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(7m, composite.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void MinusSubtractsLeftAndRightAfterBothUpdated()
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = left.Minus(right);
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
right.Update(DateTime.Today, 1m);
|
|
Assert.AreEqual(0m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(0m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 3m);
|
|
Assert.AreEqual(0m, composite.Current.Value);
|
|
|
|
right.Update(DateTime.Today, 4m);
|
|
Assert.AreEqual(-1m, composite.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void MinusSubtractsLeftAndConstant()
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = left.Minus(1);
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
Assert.AreEqual(0m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void OverDividesLeftAndRightAfterBothUpdated()
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = left.Over(right);
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
right.Update(DateTime.Today, 1m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 3m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
|
|
right.Update(DateTime.Today, 4m);
|
|
Assert.AreEqual(3m / 4m, composite.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void OverDividesLeftAndConstant()
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = left.Over(2);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 4m);
|
|
Assert.AreEqual(2m, composite.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void OverHandlesDivideByZero()
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = left.Over(right);
|
|
var updatedEventFired = false;
|
|
composite.Updated += delegate { updatedEventFired = true; };
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
Assert.IsFalse(updatedEventFired);
|
|
right.Update(DateTime.Today, 0m);
|
|
Assert.IsFalse(updatedEventFired);
|
|
|
|
// submitting another update to right won't cause an update without corresponding update to left
|
|
right.Update(DateTime.Today, 1m);
|
|
Assert.IsFalse(updatedEventFired);
|
|
left.Update(DateTime.Today, 1m);
|
|
Assert.IsTrue(updatedEventFired);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void TimesMultipliesLeftAndRightAfterBothUpdated()
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = left.Times(right);
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
right.Update(DateTime.Today, 1m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 3m);
|
|
Assert.AreEqual(1m, composite.Current.Value);
|
|
|
|
right.Update(DateTime.Today, 4m);
|
|
Assert.AreEqual(12m, composite.Current.Value);
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void TimesMultipliesLeftAndConstant()
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = left.Times(10);
|
|
|
|
left.Update(DateTime.Today, 1m);
|
|
Assert.AreEqual(10m, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2m);
|
|
Assert.AreEqual(20m, composite.Current.Value);
|
|
|
|
}
|
|
|
|
[Test, Parallelizable(ParallelScope.Self)]
|
|
public void WorksForIndicatorsOfDifferentTypes()
|
|
{
|
|
var indicatorA1 = new TestIndicatorA("1");
|
|
var indicatorA2 = new TestIndicatorA("2");
|
|
|
|
indicatorA1.Over(indicatorA2);
|
|
indicatorA1.Minus(indicatorA2);
|
|
indicatorA1.Times(indicatorA2);
|
|
indicatorA1.Plus(indicatorA2);
|
|
indicatorA1.Of(indicatorA2);
|
|
|
|
var indicatorB1 = new TestIndicatorB("1");
|
|
var indicatorB2 = new TestIndicatorB("2");
|
|
indicatorB1.Over(indicatorB2);
|
|
indicatorB1.Minus(indicatorB2);
|
|
indicatorB1.Times(indicatorB2);
|
|
indicatorB1.Plus(indicatorB2);
|
|
indicatorB1.Of(indicatorB2);
|
|
}
|
|
|
|
[Test]
|
|
public void MinusSubtractsLeftAndConstant_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = (IIndicator) IndicatorExtensions.Minus(left.ToPython(), 10);
|
|
|
|
left.Update(DateTime.Today, 1);
|
|
Assert.AreEqual(-9, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2);
|
|
Assert.AreEqual(-8, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void PlusAddsLeftAndConstant_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = (IIndicator)IndicatorExtensions.Plus(left.ToPython(), 10);
|
|
|
|
left.Update(DateTime.Today, 1);
|
|
Assert.AreEqual(11, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 2);
|
|
Assert.AreEqual(12, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void OverDivdesLeftAndConstant_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = (IIndicator)IndicatorExtensions.Over(left.ToPython(), 5);
|
|
|
|
left.Update(DateTime.Today, 10);
|
|
Assert.AreEqual(2, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 20);
|
|
Assert.AreEqual(4, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void TimesMultipliesLeftAndConstant_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var composite = (IIndicator)IndicatorExtensions.Times(left.ToPython(), 5);
|
|
|
|
left.Update(DateTime.Today, 10);
|
|
Assert.AreEqual(50, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 20);
|
|
Assert.AreEqual(100, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void TimesMultipliesLeftAndRight_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = (IIndicator)IndicatorExtensions.Times(left.ToPython(), right.ToPython());
|
|
|
|
left.Update(DateTime.Today, 10);
|
|
right.Update(DateTime.Today, 10);
|
|
Assert.AreEqual(100, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 20);
|
|
Assert.AreEqual(100, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void OverDividesLeftAndRight_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = (IIndicator)IndicatorExtensions.Over(left.ToPython(), right.ToPython());
|
|
|
|
left.Update(DateTime.Today, 10);
|
|
right.Update(DateTime.Today, 10);
|
|
Assert.AreEqual(1, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 20);
|
|
Assert.AreEqual(1, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void PlusAddsLeftAndRight_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = (IIndicator)IndicatorExtensions.Plus(left.ToPython(), right.ToPython());
|
|
|
|
left.Update(DateTime.Today, 10);
|
|
right.Update(DateTime.Today, 10);
|
|
Assert.AreEqual(20, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 20);
|
|
Assert.AreEqual(20, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void MinusSubstractsLeftAndRight_Py()
|
|
{
|
|
using (Py.GIL())
|
|
{
|
|
var left = new Identity("left");
|
|
var right = new Identity("right");
|
|
var composite = (IIndicator)IndicatorExtensions.Minus(left.ToPython(), right.ToPython());
|
|
|
|
left.Update(DateTime.Today, 10);
|
|
right.Update(DateTime.Today, 10);
|
|
Assert.AreEqual(0, composite.Current.Value);
|
|
|
|
left.Update(DateTime.Today, 20);
|
|
Assert.AreEqual(0, composite.Current.Value);
|
|
}
|
|
}
|
|
|
|
private class TestIndicatorA : IndicatorBase<IBaseData>
|
|
{
|
|
public TestIndicatorA(string name) : base(name)
|
|
{
|
|
}
|
|
public override bool IsReady { get; }
|
|
protected override decimal ComputeNextValue(IBaseData input)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private class TestIndicatorB : IndicatorBase<IndicatorDataPoint>
|
|
{
|
|
public TestIndicatorB(string name) : base(name)
|
|
{
|
|
}
|
|
public override bool IsReady
|
|
{
|
|
get
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
protected override decimal ComputeNextValue(IndicatorDataPoint input)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|
|
}
|