Files
quantconnect--lean/Tests/Algorithm/AlgorithmRegisterIndicatorTests.cs
T
2026-07-13 13:02:50 +08:00

415 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 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 }
};
}
}