chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Indicators;
|
||||
using QuantConnect.Tests.Indicators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Engine.DataFeeds;
|
||||
using QuantConnect.Util;
|
||||
using QuantConnect.Data;
|
||||
using QuantConnect.Interfaces;
|
||||
using QuantConnect.Lean.Engine.DataFeeds;
|
||||
using QuantConnect.Lean.Engine.HistoricalData;
|
||||
using Moq;
|
||||
using NodaTime;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlgorithmRegisterIndicatorTests
|
||||
{
|
||||
private Symbol _spy;
|
||||
private Symbol _option;
|
||||
private QCAlgorithm _algorithm;
|
||||
private IEnumerable<Type> _indicatorTestsTypes;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_algorithm = new QCAlgorithm();
|
||||
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
|
||||
_spy = _algorithm.AddEquity("SPY").Symbol;
|
||||
_option = _algorithm.AddOption("SPY").Symbol;
|
||||
|
||||
_indicatorTestsTypes =
|
||||
from type in GetType().Assembly.GetTypes()
|
||||
where type.IsPublic && !type.IsAbstract
|
||||
where
|
||||
typeof(CommonIndicatorTests<TradeBar>).IsAssignableFrom(type) ||
|
||||
typeof(CommonIndicatorTests<IBaseDataBar>).IsAssignableFrom(type) ||
|
||||
typeof(CommonIndicatorTests<IndicatorDataPoint>).IsAssignableFrom(type)
|
||||
select type;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegistersIndicatorProperly()
|
||||
{
|
||||
var expected = 0;
|
||||
|
||||
foreach (var type in _indicatorTestsTypes)
|
||||
{
|
||||
var indicatorTest = Activator.CreateInstance(type);
|
||||
if (indicatorTest is OptionBaseIndicatorTests<OptionIndicatorBase>)
|
||||
{
|
||||
var indicator = (indicatorTest as OptionBaseIndicatorTests<OptionIndicatorBase>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_option, indicator, Resolution.Minute));
|
||||
expected++;
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IndicatorDataPoint>)
|
||||
{
|
||||
var indicator = (indicatorTest as CommonIndicatorTests<IndicatorDataPoint>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute, Field.Close));
|
||||
expected++;
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IBaseDataBar>)
|
||||
{
|
||||
var indicator = (indicatorTest as CommonIndicatorTests<IBaseDataBar>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute));
|
||||
expected++;
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<TradeBar>)
|
||||
{
|
||||
var indicator = (indicatorTest as CommonIndicatorTests<TradeBar>).Indicator;
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute));
|
||||
expected++;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"RegistersIndicatorProperlyPython(): Unsupported indicator data type: {indicatorTest.GetType()}");
|
||||
}
|
||||
var actual = _algorithm.SubscriptionManager.Subscriptions
|
||||
.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity))
|
||||
.Consolidators.Count;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static TestCaseData[] IndicatorNameParameters => new[]
|
||||
{
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Tick, "TEST(SPY_tick)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Second, "TEST(SPY_sec)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Minute, "TEST(SPY_min)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Hour, "TEST(SPY_hr)"),
|
||||
new TestCaseData(Symbols.SPY, "TEST", Resolution.Daily, "TEST(SPY_day)"),
|
||||
new TestCaseData(Symbol.Empty, "TEST", Resolution.Minute, "TEST(min)"),
|
||||
new TestCaseData(Symbol.None, "TEST", Resolution.Minute, "TEST(min)"),
|
||||
new TestCaseData(Symbol.Empty, "TEST", null, "TEST()"),
|
||||
new TestCaseData(Symbol.None, "TEST", null, "TEST()")
|
||||
};
|
||||
|
||||
[Test, TestCaseSource(nameof(IndicatorNameParameters))]
|
||||
public void CreateIndicatorName(Symbol symbol, string baseName, Resolution? resolution, string expectation)
|
||||
{
|
||||
Assert.AreEqual(expectation, _algorithm.CreateIndicatorName(symbol, baseName, resolution));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlotAndRegistersIndicatorProperlyPython()
|
||||
{
|
||||
var expected = 0;
|
||||
PyObject indicator;
|
||||
|
||||
foreach (var type in _indicatorTestsTypes)
|
||||
{
|
||||
var indicatorTest = Activator.CreateInstance(type);
|
||||
if (indicatorTest is OptionBaseIndicatorTests<OptionIndicatorBase>)
|
||||
{
|
||||
indicator = (indicatorTest as OptionBaseIndicatorTests<OptionIndicatorBase>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IndicatorDataPoint>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<IndicatorDataPoint>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<IBaseDataBar>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<IBaseDataBar>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else if (indicatorTest is CommonIndicatorTests<TradeBar>)
|
||||
{
|
||||
indicator = (indicatorTest as CommonIndicatorTests<TradeBar>).GetIndicatorAsPyObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"RegistersIndicatorProperlyPython(): Unsupported indicator data type: {indicatorTest.GetType()}");
|
||||
}
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, indicator, Resolution.Minute));
|
||||
Assert.DoesNotThrow(() => _algorithm.Plot(_spy.Value, indicator));
|
||||
expected++;
|
||||
|
||||
var actual = _algorithm.SubscriptionManager.Subscriptions
|
||||
.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity))
|
||||
.Consolidators.Count;
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterPythonCustomIndicatorProperly()
|
||||
{
|
||||
const string code = @"
|
||||
class GoodCustomIndicator:
|
||||
def __init__(self):
|
||||
self.IsReady = True
|
||||
self.Value = 0
|
||||
def Update(self, input):
|
||||
self.Value = input.Value
|
||||
return True
|
||||
class BadCustomIndicator:
|
||||
def __init__(self):
|
||||
self.IsReady = True
|
||||
self.Value = 0
|
||||
def Updat(self, input):
|
||||
self.Value = input.Value
|
||||
return True";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
var module = PyModule.FromString(Guid.NewGuid().ToString(), code);
|
||||
|
||||
var goodIndicator = module.GetAttr("GoodCustomIndicator").Invoke();
|
||||
Assert.DoesNotThrow(() => _algorithm.RegisterIndicator(_spy, goodIndicator, Resolution.Minute));
|
||||
|
||||
var actual = _algorithm.SubscriptionManager.Subscriptions
|
||||
.Single(s => s.TickType == LeanData.GetCommonTickType(SecurityType.Equity))
|
||||
.Consolidators.Count;
|
||||
Assert.AreEqual(1, actual);
|
||||
|
||||
var badIndicator = module.GetAttr("BadCustomIndicator").Invoke();
|
||||
Assert.Throws<NotImplementedException>(() => _algorithm.RegisterIndicator(_spy, badIndicator, Resolution.Minute));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegistersIndicatorProperlyPythonScript()
|
||||
{
|
||||
const string code = @"
|
||||
from AlgorithmImports import *
|
||||
|
||||
AddReference('QuantConnect.Lean.Engine')
|
||||
from QuantConnect.Lean.Engine.DataFeeds import *
|
||||
|
||||
algo = QCAlgorithm()
|
||||
|
||||
marketHoursDatabase = MarketHoursDatabase.FromDataFolder()
|
||||
symbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder()
|
||||
securityService = SecurityService(algo.Portfolio.CashBook, marketHoursDatabase, symbolPropertiesDatabase, algo, RegisteredSecurityDataTypesProvider.Null, SecurityCacheProvider(algo.Portfolio), algorithm=algo)
|
||||
algo.Securities.SetSecurityService(securityService)
|
||||
dataPermissionManager = DataPermissionManager()
|
||||
dataManager = DataManager(None, UniverseSelection(algo, securityService, dataPermissionManager, None), algo, algo.TimeKeeper, marketHoursDatabase, False, RegisteredSecurityDataTypesProvider.Null, dataPermissionManager)
|
||||
algo.SubscriptionManager.SetDataManager(dataManager)
|
||||
|
||||
|
||||
forex = algo.AddForex('EURUSD', Resolution.Daily)
|
||||
indicator = IchimokuKinkoHyo('EURUSD', 9, 26, 26, 52, 26, 26)
|
||||
algo.RegisterIndicator(forex.Symbol, indicator, Resolution.Daily)";
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
Assert.DoesNotThrow(() => PyModule.FromString("RegistersIndicatorProperlyPythonScript", code));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsCanBeRegisteredWithTickDataSelectors()
|
||||
{
|
||||
var ibm = _algorithm.AddEquity("IBM", Resolution.Tick).Symbol;
|
||||
var indicator = _algorithm.Identity(ibm, Resolution.Tick, Field.BidPrice);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(new Tick() { BidPrice = 101 });
|
||||
Assert.AreEqual(101, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndicatorUseDefaultSelectorWhenResolutionDoesNotMatchWithSelectorDataTypeTestCases))]
|
||||
public void IndicatorUseDefaultSelectorWhenDataTypeDoesNotMatchWithSelectorDataType(
|
||||
Symbol symbol,
|
||||
SecurityType securityType,
|
||||
Resolution resolution,
|
||||
Func<IBaseData, decimal> selector,
|
||||
IBaseData input,
|
||||
decimal expectedValue)
|
||||
{
|
||||
_algorithm.AddSecurity(symbol, resolution);
|
||||
var indicator = _algorithm.Identity(symbol, resolution, selector);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(input);
|
||||
Assert.AreEqual(expectedValue, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsCanBeRegisteredWithQuoteDataSelectors()
|
||||
{
|
||||
var ibm = _algorithm.AddEquity("IBM", Resolution.Minute).Symbol;
|
||||
var indicator = _algorithm.Identity(ibm, Resolution.Minute, Field.BidClose);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(new QuoteBar() { Bid = new Bar() { Close = 101 }});
|
||||
Assert.AreEqual(101, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(IndicatorsCanBeWarmedUpWithDataSelectorsTestCases))]
|
||||
public void IndicatorsCanBeWarmedUpWithDataSelectors(Symbol symbol,
|
||||
SecurityType securityType,
|
||||
Resolution resolution,
|
||||
Func<IBaseData, decimal> selector,
|
||||
Slice warmUpinput,
|
||||
decimal expectedValue)
|
||||
{
|
||||
_algorithm.Settings.AutomaticIndicatorWarmUp = true;
|
||||
_algorithm.AddSecurity(symbol, resolution);
|
||||
|
||||
var historyProvider = new Mock<SubscriptionDataReaderHistoryProvider>();
|
||||
historyProvider.Setup(x => x.GetHistory(It.IsAny<IEnumerable<HistoryRequest>>(), It.IsAny<DateTimeZone>())).Returns(new List<Slice>() { warmUpinput });
|
||||
_algorithm.SetHistoryProvider(historyProvider.Object);
|
||||
var indicator = _algorithm.Identity(symbol, resolution, selector);
|
||||
|
||||
Assert.AreEqual(expectedValue, indicator.Current.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndicatorsCanBeRegisteredWithTradeDataSelectors()
|
||||
{
|
||||
var ibm = _algorithm.AddEquity("IBM", Resolution.Minute).Symbol;
|
||||
var indicator = _algorithm.Identity(ibm, Resolution.Minute, Field.Volume);
|
||||
|
||||
var consolidator = indicator.Consolidators.Single();
|
||||
consolidator.Update(new TradeBar() { Volume = 101 });
|
||||
Assert.AreEqual(101, indicator.Current.Value);
|
||||
}
|
||||
|
||||
public static object[] IndicatorUseDefaultSelectorWhenResolutionDoesNotMatchWithSelectorDataTypeTestCases =
|
||||
{
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Tick, Field.BidClose, new Tick() { BidPrice = 101, Value = 102 }, 102m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Tick, Field.Volume, new Tick() { Quantity = 101, Value = 102 }, 101m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.BidPrice, new QuoteBar() { Value = 102, Bid = new Bar() { Close = 103 } }, 103m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.AskPrice, new QuoteBar() { Value = 102, Ask = new Bar() { Close = 103 } }, 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.BidPrice, new QuoteBar() { Value = 102, Bid = new Bar() { Close = 103} }, 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.AskPrice, new QuoteBar() { Value = 102, Ask = new Bar() { Close = 103} }, 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.BidPrice, new QuoteBar() { Value = 102, Bid = new Bar() { Close = 103 } }, 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.AskPrice, new QuoteBar() { Value = 102, Ask = new Bar() { Close = 103 } }, 103m }
|
||||
};
|
||||
|
||||
public static object[] IndicatorsCanBeWarmedUpWithDataSelectorsTestCases =
|
||||
{
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.BidPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.IBM, Bid = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.Fut_SPY_Feb19_2016, SecurityType.Future, Resolution.Minute, Field.Volume, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars() { new TradeBar() { Symbol = Symbols.IBM, Volume = 103m } },
|
||||
new QuoteBars(),
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.IBM, SecurityType.Equity, Resolution.Minute, Field.AskPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.IBM, Ask = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.BidPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.EURGBP, Bid = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.EURGBP, SecurityType.Forex, Resolution.Minute, Field.AskPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.EURGBP, Ask = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.BidPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.SPY_C_192_Feb19_2016, Bid = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m },
|
||||
new object[] {Symbols.SPY_C_192_Feb19_2016, SecurityType.Option, Resolution.Minute, Field.AskPrice, new Slice(
|
||||
new DateTime(2013, 10, 3),
|
||||
new List<BaseData>(),
|
||||
new TradeBars(),
|
||||
new QuoteBars() { new QuoteBar() { Symbol = Symbols.EURGBP, Ask = new Bar(){ Close = 103 } } },
|
||||
new Ticks(),
|
||||
new OptionChains(),
|
||||
new FuturesChains(),
|
||||
new Splits(),
|
||||
new Dividends(),
|
||||
new Delistings(),
|
||||
new SymbolChangedEvents(),
|
||||
new MarginInterestRates(),
|
||||
DateTime.UtcNow), 103m }
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user