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

780 lines
42 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.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NodaTime;
using NUnit.Framework;
using Python.Runtime;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Algorithm.Selection;
using QuantConnect.AlgorithmFactory.Python.Wrappers;
using QuantConnect.Configuration;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Custom.IconicTypes;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Util;
using Bitcoin = QuantConnect.Algorithm.CSharp.LiveTradingFeaturesAlgorithm.Bitcoin;
using HistoryRequest = QuantConnect.Data.HistoryRequest;
namespace QuantConnect.Tests.Algorithm
{
[TestFixture]
public class AlgorithmAddDataTests
{
[Test]
public void DefaultDataFeeds_CanBeOverwritten_Successfully()
{
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
// forex defult - should be quotebar
var forexTrade = algo.AddForex("EURUSD");
Assert.IsTrue(forexTrade.Subscriptions.Count() == 1);
Assert.IsTrue(GetMatchingSubscription(algo, forexTrade.Symbol, typeof(QuoteBar)) != null);
// Change
Config.Set("security-data-feeds", "{ Forex: [\"Trade\"] }");
var dataFeedsConfigString = Config.Get("security-data-feeds");
Dictionary<SecurityType, List<TickType>> dataFeeds = new Dictionary<SecurityType, List<TickType>>();
if (!string.IsNullOrEmpty(dataFeedsConfigString))
{
dataFeeds = JsonConvert.DeserializeObject<Dictionary<SecurityType, List<TickType>>>(dataFeedsConfigString);
}
algo.SetAvailableDataTypes(dataFeeds);
// new forex - should be tradebar
var forexQuote = algo.AddForex("EURUSD");
// The quote bar subscription is kept
Assert.IsTrue(forexQuote.Subscriptions.Count() == 2);
Assert.IsTrue(GetMatchingSubscription(algo, forexQuote.Symbol, typeof(TradeBar)) != null);
Assert.IsTrue(GetMatchingSubscription(algo, forexQuote.Symbol, typeof(QuoteBar)) != null);
// reset to empty string, affects other tests because config is static
Config.Set("security-data-feeds", "");
}
[Test]
public void DefaultDataFeeds_AreAdded_Successfully()
{
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
// forex
var forex = algo.AddSecurity(SecurityType.Forex, "eurusd");
Assert.IsTrue(forex.Subscriptions.Count() == 1);
Assert.IsTrue(GetMatchingSubscription(algo, forex.Symbol, typeof(QuoteBar)) != null);
// equity high resolution
var equityMinute = algo.AddSecurity(SecurityType.Equity, "goog");
Assert.IsTrue(equityMinute.Subscriptions.Count() == 2);
Assert.IsTrue(GetMatchingSubscription(algo, equityMinute.Symbol, typeof(TradeBar)) != null);
Assert.IsTrue(GetMatchingSubscription(algo, equityMinute.Symbol, typeof(QuoteBar)) != null);
// equity low resolution
var equityDaily = algo.AddSecurity(SecurityType.Equity, "goog", Resolution.Daily);
Assert.IsTrue(equityDaily.Subscriptions.Count() == 3);
Assert.IsTrue(GetMatchingSubscription(algo, equityDaily.Symbol, typeof(TradeBar)) != null);
Assert.IsTrue(GetMatchingSubscription(algo, equityMinute.Symbol, typeof(QuoteBar)) != null);
Assert.IsTrue(ReferenceEquals(equityMinute, equityDaily));
var equitySubscriptions = algo.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(equityMinute.Symbol);
Assert.IsTrue(equitySubscriptions.SingleOrDefault(
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Minute) != null);
Assert.IsTrue(equitySubscriptions.SingleOrDefault(
s => s.TickType == TickType.Quote && s.Type == typeof(QuoteBar) && s.Resolution == Resolution.Minute) != null);
Assert.IsTrue(equitySubscriptions.SingleOrDefault(
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Daily) != null);
// option
var option = algo.AddSecurity(SecurityType.Option, "goog");
Assert.IsTrue(option.Subscriptions.Count() == 1);
Assert.IsTrue(GetMatchingSubscription(algo, option.Symbol, typeof(OptionUniverse)) != null);
// index option
var indexOption = algo.AddSecurity(SecurityType.IndexOption, "spx");
Assert.IsTrue(indexOption.Subscriptions.Count() == 1);
Assert.IsTrue(GetMatchingSubscription(algo, indexOption.Symbol, typeof(OptionUniverse)) != null);
// cfd
var cfd = algo.AddSecurity(SecurityType.Cfd, "abc");
Assert.IsTrue(cfd.Subscriptions.Count() == 1);
Assert.IsTrue(GetMatchingSubscription(algo, cfd.Symbol, typeof(QuoteBar)) != null);
// future
var future = algo.AddSecurity(SecurityType.Future, "ES");
Assert.IsTrue(future.Subscriptions.Count() == 1);
Assert.IsTrue(future.Subscriptions.FirstOrDefault(x => typeof(FutureUniverse) == x.Type) != null);
// Crypto high resolution
var cryptoMinute = algo.AddSecurity(SecurityType.Crypto, "btcusd");
Assert.IsTrue(cryptoMinute.Subscriptions.Count() == 2);
Assert.IsTrue(GetMatchingSubscription(algo, cryptoMinute.Symbol, typeof(TradeBar)) != null);
Assert.IsTrue(GetMatchingSubscription(algo, cryptoMinute.Symbol, typeof(QuoteBar)) != null);
// Crypto low resolution
var cryptoHourly = algo.AddSecurity(SecurityType.Crypto, "btcusd", Resolution.Hour);
Assert.IsTrue(cryptoHourly.Subscriptions.Count() == 4);
Assert.IsTrue(GetMatchingSubscription(algo, cryptoHourly.Symbol, typeof(TradeBar)) != null);
Assert.IsTrue(GetMatchingSubscription(algo, cryptoHourly.Symbol, typeof(QuoteBar)) != null);
Assert.IsTrue(ReferenceEquals(cryptoMinute, cryptoHourly));
var cryptoSubscriptions = algo.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(cryptoMinute.Symbol);
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Minute) != null);
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
s => s.TickType == TickType.Quote && s.Type == typeof(QuoteBar) && s.Resolution == Resolution.Minute) != null);
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
s => s.TickType == TickType.Trade && s.Type == typeof(TradeBar) && s.Resolution == Resolution.Hour) != null);
Assert.IsTrue(cryptoSubscriptions.SingleOrDefault(
s => s.TickType == TickType.Quote && s.Type == typeof(QuoteBar) && s.Resolution == Resolution.Hour) != null);
}
[Test]
public void CustomDataTypes_AreAddedToSubscriptions_Successfully()
{
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
// Add a bitcoin subscription
qcAlgorithm.AddData<Bitcoin>("BTC");
var bitcoinSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Type == typeof(Bitcoin));
Assert.AreEqual(bitcoinSubscription.Type, typeof(Bitcoin));
// Add a unlinkedData subscription
qcAlgorithm.AddData<UnlinkedData>("EURCAD");
var unlinkedDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Type == typeof(UnlinkedData));
Assert.AreEqual(unlinkedDataSubscription.Type, typeof(UnlinkedData));
}
[Test]
public void OnEndOfTimeStepSeedsUnderlyingSecuritiesThatHaveNoData()
{
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm, new MockDataFeed()));
qcAlgorithm.SetLiveMode(true);
qcAlgorithm.Settings.SeedInitialPrices = false;
var testHistoryProvider = new TestHistoryProvider();
qcAlgorithm.HistoryProvider = testHistoryProvider;
var option = qcAlgorithm.AddSecurity(SecurityType.Option, testHistoryProvider.underlyingSymbol);
var option2 = qcAlgorithm.AddSecurity(SecurityType.Option, testHistoryProvider.underlyingSymbol2);
Assert.IsFalse(qcAlgorithm.Securities.ContainsKey(option.Symbol.Underlying));
Assert.IsFalse(qcAlgorithm.Securities.ContainsKey(option2.Symbol.Underlying));
qcAlgorithm.OnEndOfTimeStep();
var data = qcAlgorithm.Securities[testHistoryProvider.underlyingSymbol].GetLastData();
var data2 = qcAlgorithm.Securities[testHistoryProvider.underlyingSymbol2].GetLastData();
Assert.IsNotNull(data);
Assert.IsNotNull(data2);
Assert.AreEqual(data.Price, 2);
Assert.AreEqual(data2.Price, 3);
}
[Test, Parallelizable(ParallelScope.Self)]
public void OnEndOfTimeStepDoesNotThrowWhenSeedsSameUnderlyingForTwoSecurities()
{
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm, new MockDataFeed()));
qcAlgorithm.SetLiveMode(true);
qcAlgorithm.Settings.SeedInitialPrices = false;
var testHistoryProvider = new TestHistoryProvider();
qcAlgorithm.HistoryProvider = testHistoryProvider;
var option = qcAlgorithm.AddOption(testHistoryProvider.underlyingSymbol);
var symbol = Symbol.CreateOption(testHistoryProvider.underlyingSymbol, Market.USA, OptionStyle.American,
OptionRight.Call, 1, new DateTime(2015, 12, 24));
var symbol2 = Symbol.CreateOption(testHistoryProvider.underlyingSymbol, Market.USA, OptionStyle.American,
OptionRight.Put, 1, new DateTime(2015, 12, 24));
var optionContract = qcAlgorithm.AddOptionContract(symbol);
var optionContract2 = qcAlgorithm.AddOptionContract(symbol2);
qcAlgorithm.OnEndOfTimeStep();
var data = qcAlgorithm.Securities[testHistoryProvider.underlyingSymbol].GetLastData();
Assert.IsNotNull(data);
Assert.AreEqual(data.Price, 2);
}
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Cfd, false, true)]
[TestCase("BTCUSD", typeof(IndexedLinkedData), SecurityType.Crypto, false, true)]
[TestCase("CL", typeof(IndexedLinkedData), SecurityType.Future, true, true)]
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Forex, false, true)]
[TestCase("AAPL", typeof(IndexedLinkedData), SecurityType.Equity, true, true)]
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Cfd, false, false)]
[TestCase("BTCUSD", typeof(UnlinkedData), SecurityType.Crypto, false, false)]
[TestCase("CL", typeof(UnlinkedData), SecurityType.Future, true, false)]
[TestCase("AAPL", typeof(UnlinkedData), SecurityType.Equity, true, false)]
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Forex, false, false)]
public void AddDataSecuritySymbolWithUnderlying(string ticker, Type customDataType, SecurityType securityType, bool securityShouldBeMapped, bool customDataShouldBeMapped)
{
SymbolCache.Clear();
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
Security asset;
switch (securityType)
{
case SecurityType.Cfd:
asset = qcAlgorithm.AddCfd(ticker, Resolution.Daily);
break;
case SecurityType.Crypto:
asset = qcAlgorithm.AddCrypto(ticker, Resolution.Daily);
break;
case SecurityType.Equity:
asset = qcAlgorithm.AddEquity(ticker, Resolution.Daily);
break;
case SecurityType.Forex:
asset = qcAlgorithm.AddForex(ticker, Resolution.Daily);
break;
case SecurityType.Future:
asset = qcAlgorithm.AddFuture(ticker, Resolution.Minute);
break;
default:
throw new Exception($"SecurityType {securityType} is not valid for this test");
}
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
// This covers the case where two idential data subscriptions are created.
var dummy = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
var customData = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
Assert.IsTrue(customData.Symbol.HasUnderlying, $"{customDataType.Name} added as {ticker} Symbol with SecurityType {securityType} does not have underlying");
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol, $"Custom data underlying does not match {securityType} Symbol for {ticker}");
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First();
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
var assetShouldBeMapped = assetSubscription.TickerShouldBeMapped();
var customShouldBeMapped = customDataSubscription.TickerShouldBeMapped();
Assert.AreEqual(securityShouldBeMapped, assetShouldBeMapped);
Assert.AreEqual(customDataShouldBeMapped, customShouldBeMapped);
Assert.AreNotEqual(assetSubscription, customDataSubscription);
if (assetShouldBeMapped == customShouldBeMapped)
{
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
Assert.AreEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
}
}
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Cfd, false, false)]
[TestCase("BTCUSD", typeof(IndexedLinkedData), SecurityType.Crypto, false, false)]
[TestCase("CL", typeof(IndexedLinkedData), SecurityType.Future, false, false)]
[TestCase("EURUSD", typeof(IndexedLinkedData), SecurityType.Forex, false, false)]
[TestCase("AAPL", typeof(IndexedLinkedData), SecurityType.Equity, true, true)]
public void AddDataSecurityTickerWithUnderlying(string ticker, Type customDataType, SecurityType securityType, bool securityShouldBeMapped, bool customDataShouldBeMapped)
{
SymbolCache.Clear();
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
Security asset;
switch (securityType)
{
case SecurityType.Cfd:
asset = qcAlgorithm.AddCfd(ticker, Resolution.Daily);
break;
case SecurityType.Crypto:
asset = qcAlgorithm.AddCrypto(ticker, Resolution.Daily);
break;
case SecurityType.Equity:
asset = qcAlgorithm.AddEquity(ticker, Resolution.Daily);
break;
case SecurityType.Forex:
asset = qcAlgorithm.AddForex(ticker, Resolution.Daily);
break;
case SecurityType.Future:
asset = qcAlgorithm.AddFuture(ticker, Resolution.Minute);
break;
default:
throw new Exception($"SecurityType {securityType} is not valid for this test");
}
// Aliased value for Futures contains a forward-slash, which causes the
// lookup in the SymbolCache to fail
if (securityType == SecurityType.Future)
{
ticker = asset.Symbol.Value;
}
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
// This covers the case where two idential data subscriptions are created.
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
Assert.IsTrue(customData.Symbol.HasUnderlying, $"Custom data added as {ticker} Symbol with SecurityType {securityType} does not have underlying");
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol, $"Custom data underlying does not match {securityType} Symbol for {ticker}");
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First();
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
var assetShouldBeMapped = assetSubscription.TickerShouldBeMapped();
var customShouldBeMapped = customDataSubscription.TickerShouldBeMapped();
if (securityType == SecurityType.Equity)
{
Assert.AreEqual(securityShouldBeMapped, assetShouldBeMapped);
Assert.AreEqual(customDataShouldBeMapped, customShouldBeMapped);
Assert.AreNotEqual(assetSubscription, customDataSubscription);
if (assetShouldBeMapped == customShouldBeMapped)
{
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
Assert.AreEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
}
}
}
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Cfd, false, false)]
[TestCase("BTCUSD", typeof(UnlinkedData), SecurityType.Crypto, false, false)]
[TestCase("CL", typeof(UnlinkedData), SecurityType.Future, true, false)]
[TestCase("AAPL", typeof(UnlinkedData), SecurityType.Equity, true, false)]
[TestCase("EURUSD", typeof(UnlinkedData), SecurityType.Forex, false, false)]
public void AddDataSecurityTickerNoUnderlying(string ticker, Type customDataType, SecurityType securityType, bool securityShouldBeMapped, bool customDataShouldBeMapped)
{
SymbolCache.Clear();
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
Security asset;
switch (securityType)
{
case SecurityType.Cfd:
asset = qcAlgorithm.AddCfd(ticker, Resolution.Daily);
break;
case SecurityType.Crypto:
asset = qcAlgorithm.AddCrypto(ticker, Resolution.Daily);
break;
case SecurityType.Equity:
asset = qcAlgorithm.AddEquity(ticker, Resolution.Daily);
break;
case SecurityType.Forex:
asset = qcAlgorithm.AddForex(ticker, Resolution.Daily);
break;
case SecurityType.Future:
asset = qcAlgorithm.AddFuture(ticker, Resolution.Minute);
break;
default:
throw new Exception($"SecurityType {securityType} is not valid for this test");
}
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
// This covers the case where two idential data subscriptions are created.
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First().DataTimeZone);
// Check to see if we have an underlying symbol when we shouldn't
Assert.IsFalse(customData.Symbol.HasUnderlying, $"{customDataType.Name} has underlying symbol for SecurityType {securityType} with ticker {ticker}");
Assert.AreEqual(customData.Symbol.Underlying, null, $"{customDataType.Name} - Custom data underlying Symbol for SecurityType {securityType} is not null");
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == securityType).First();
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
var assetShouldBeMapped = assetSubscription.TickerShouldBeMapped();
var customShouldBeMapped = customDataSubscription.TickerShouldBeMapped();
Assert.AreEqual(securityShouldBeMapped, assetShouldBeMapped);
Assert.AreEqual(customDataShouldBeMapped, customShouldBeMapped);
Assert.AreNotEqual(assetSubscription, customDataSubscription);
if (assetShouldBeMapped == customShouldBeMapped)
{
// Would fail with CL future without this check because MappedSymbol returns "/CL" for the Future symbol
if (assetSubscription.SecurityType == SecurityType.Future)
{
Assert.AreNotEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
Assert.AreNotEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
}
else
{
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
Assert.AreEqual(asset.Symbol.Value, customData.Symbol.Value.Split('.').First());
}
}
}
[Test]
public void AddOptionWithUnderlyingFuture()
{
// Adds an option containing a Future as its underlying Symbol.
// This is an essential step in enabling custom derivatives
// based on any asset class provided to Option. This test
// checks the ability to create Future Options.
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
var underlying = algo.AddFuture("ES", Resolution.Minute, Market.CME);
underlying.SetFilter(0, 365);
var futureOption = algo.AddOption(underlying.Symbol, Resolution.Minute);
Assert.IsTrue(futureOption.Symbol.HasUnderlying);
Assert.AreEqual(underlying.Symbol, futureOption.Symbol.Underlying);
}
[Test]
public void AddFutureOptionContractNonEquityOption()
{
// Adds an option contract containing an underlying future contract.
// We test to make sure that the security returned is a specific option
// contract and with the future as the underlying.
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
var underlying = algo.AddFutureContract(
Symbol.CreateFuture("ES", Market.CME, new DateTime(2021, 3, 19)),
Resolution.Minute);
var futureOptionContract = algo.AddFutureOptionContract(
Symbol.CreateOption(underlying.Symbol, Market.CME, OptionStyle.American, OptionRight.Call, 2550m, new DateTime(2021, 3, 19)),
Resolution.Minute);
Assert.AreEqual(underlying.Symbol, futureOptionContract.Symbol.Underlying);
Assert.AreEqual(underlying, futureOptionContract.Underlying);
Assert.IsFalse(underlying.Symbol.IsCanonical());
Assert.IsFalse(futureOptionContract.Symbol.IsCanonical());
}
[Test]
public void AddFutureOptionAddsUniverseSelectionModel()
{
var algo = new QCAlgorithm();
algo.SubscriptionManager.SetDataManager(new DataManagerStub(algo));
var underlying = algo.AddFuture("ES", Resolution.Minute, Market.CME);
underlying.SetFilter(0, 365);
algo.AddFutureOption(underlying.Symbol, _ => _);
Assert.IsTrue(algo.UniverseSelection is CompositeUniverseSelectionModel);
}
[TestCase("AAPL", typeof(IndexedLinkedData), true)]
[TestCase("TWX", typeof(IndexedLinkedData), true)]
[TestCase("FB", typeof(IndexedLinkedData), true)]
[TestCase("NFLX", typeof(IndexedLinkedData), true)]
[TestCase("TWX", typeof(UnlinkedData), false)]
[TestCase("AAPL", typeof(UnlinkedData), false)]
public void AddDataOptionsSymbolHasChainedUnderlyingSymbols(string ticker, Type customDataType, bool customDataShouldBeMapped)
{
SymbolCache.Clear();
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
var asset = qcAlgorithm.AddOption(ticker);
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
// This covers the case where two idential data subscriptions are created.
var dummy = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
var customData = qcAlgorithm.AddData(customDataType, asset.Symbol, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
// Check to see if we have an underlying symbol when we shouldn't
Assert.IsTrue(customData.Symbol.HasUnderlying, $"{customDataType.Name} - {ticker} has no underlying Symbol");
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol);
Assert.AreEqual(customData.Symbol.Underlying.Underlying, asset.Symbol.Underlying);
Assert.AreEqual(customData.Symbol.Underlying.Underlying.Underlying, null);
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single();
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
Assert.IsTrue(assetSubscription.TickerShouldBeMapped());
Assert.AreEqual(customDataShouldBeMapped, customDataSubscription.TickerShouldBeMapped());
Assert.AreEqual($"?{assetSubscription.MappedSymbol}", customDataSubscription.MappedSymbol);
}
[TestCase("AAPL", typeof(IndexedLinkedData))]
[TestCase("TWX", typeof(IndexedLinkedData))]
[TestCase("FB", typeof(IndexedLinkedData))]
[TestCase("NFLX", typeof(IndexedLinkedData))]
public void AddDataOptionsTickerHasChainedUnderlyingSymbol(string ticker, Type customDataType)
{
SymbolCache.Clear();
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
var asset = qcAlgorithm.AddOption(ticker);
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
// This covers the case where two idential data subscriptions are created.
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
// Check to see if we have an underlying symbol when we shouldn't
Assert.IsTrue(customData.Symbol.HasUnderlying, $"{customDataType.Name} - {ticker} has no underlying Symbol");
Assert.AreNotEqual(customData.Symbol.Underlying, asset.Symbol);
Assert.IsFalse(customData.Symbol.Underlying.HasUnderlying);
Assert.AreEqual(customData.Symbol.Underlying, asset.Symbol.Underlying);
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single();
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
Assert.IsTrue(assetSubscription.TickerShouldBeMapped());
Assert.IsTrue(customDataSubscription.TickerShouldBeMapped());
Assert.AreEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
}
[TestCase("AAPL", typeof(UnlinkedData))]
[TestCase("FDTR", typeof(UnlinkedData))]
public void AddDataOptionsTickerHasNoChainedUnderlyingSymbols(string ticker, Type customDataType)
{
SymbolCache.Clear();
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
var asset = qcAlgorithm.AddOption(ticker);
// Dummy here is meant to try to corrupt the SymbolCache. Ideally, SymbolCache should return non-custom data types with higher priority
// in case we want to add two custom data types, but still have them associated with the equity from the cache if we're using it.
// This covers the case where two idential data subscriptions are created.
var dummy = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
var customData = qcAlgorithm.AddData(customDataType, ticker, Resolution.Daily, qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single().DataTimeZone);
// Check to see if we have an underlying symbol when we shouldn't
Assert.IsFalse(customData.Symbol.HasUnderlying, $"{customDataType.Name} has an underlying Symbol");
var assetSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Option).Single();
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.Where(x => x.SecurityType == SecurityType.Base).Single();
Assert.IsTrue(assetSubscription.TickerShouldBeMapped());
Assert.IsFalse(customDataSubscription.TickerShouldBeMapped());
//Assert.AreNotEqual(assetSubscription.MappedSymbol, customDataSubscription.MappedSymbol);
}
[Test]
public void PythonCustomDataTypes_AreAddedToSubscriptions_Successfully()
{
var qcAlgorithm = new AlgorithmPythonWrapper("Test_CustomDataAlgorithm");
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
// Initialize contains the statements:
// self.AddData(Nifty, "NIFTY")
// self.AddData(CustomPythonData, "IBM", Resolution.Daily)
qcAlgorithm.Initialize();
var niftySubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Symbol.Value == "NIFTY");
Assert.IsNotNull(niftySubscription);
var niftyFactory = (BaseData)ObjectActivator.GetActivator(niftySubscription.Type).Invoke(new object[] { niftySubscription.Type });
Assert.DoesNotThrow(() => niftyFactory.GetSource(niftySubscription, DateTime.UtcNow, false));
var customDataSubscription = qcAlgorithm.SubscriptionManager.Subscriptions.FirstOrDefault(x => x.Symbol.Value == "IBM");
Assert.IsNotNull(customDataSubscription);
Assert.IsTrue(customDataSubscription.IsCustomData);
Assert.AreEqual("custom_data.CustomPythonData", customDataSubscription.Type.ToString());
var customDataFactory = (BaseData)ObjectActivator.GetActivator(customDataSubscription.Type).Invoke(new object[] { customDataSubscription.Type });
Assert.DoesNotThrow(() => customDataFactory.GetSource(customDataSubscription, DateTime.UtcNow, false));
}
[Test]
public void PythonCustomDataTypes_AreAddedToConsolidator_Successfully()
{
var qcAlgorithm = new AlgorithmPythonWrapper("Test_CustomDataAlgorithm");
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
// Initialize contains the statements:
// self.AddData(Nifty, "NIFTY")
// self.AddData(CustomPythonData, "IBM", Resolution.Daily)
qcAlgorithm.Initialize();
#pragma warning disable CS0618
using var niftyConsolidator = new DynamicDataConsolidator(TimeSpan.FromDays(2));
Assert.DoesNotThrow(() => qcAlgorithm.SubscriptionManager.AddConsolidator("NIFTY", niftyConsolidator));
using var customDataConsolidator = new DynamicDataConsolidator(TimeSpan.FromDays(2));
Assert.DoesNotThrow(() => qcAlgorithm.SubscriptionManager.AddConsolidator("IBM", customDataConsolidator));
#pragma warning restore CS0618
}
[Test]
public void AddingInvalidDataTypeThrows()
{
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
Assert.Throws<ArgumentException>(() => qcAlgorithm.AddData(typeof(double),
"double",
Resolution.Daily,
DateTimeZone.Utc));
}
[Test]
public void AddDataWithStringAsTypeArgumentThrowsClearError()
{
var qcAlgorithm = new QCAlgorithm();
qcAlgorithm.SubscriptionManager.SetDataManager(new DataManagerStub(qcAlgorithm));
using var _ = Py.GIL();
using var pyTicker = "VIX".ToPython();
// Passing a string instead of a custom data class as the first argument used to silently build a
// dynamic assembly named after the string and later hang/fail downstream. It must now throw.
var ex = Assert.Throws<ArgumentException>(() => qcAlgorithm.AddData(pyTicker, "VIX", Resolution.Daily));
StringAssert.Contains("AddData", ex.Message);
StringAssert.Contains("AddEquity", ex.Message);
}
[Test]
public void AppendsCustomDataTypeName_ToSecurityIdentifierSymbol()
{
const string ticker = "ticker";
var algorithm = Algorithm();
var security = algorithm.AddData<UnlinkedData>(ticker);
Assert.AreEqual(ticker.ToUpperInvariant(), security.Symbol.Value);
Assert.AreEqual($"{ticker.ToUpperInvariant()}.{typeof(UnlinkedData).Name}", security.Symbol.ID.Symbol);
Assert.AreEqual(SecurityIdentifier.GenerateBaseSymbol(typeof(UnlinkedData), ticker), security.Symbol.ID.Symbol);
}
[Test]
public void RegistersSecurityIdentifierSymbol_AsTickerString_InSymbolCache()
{
var algorithm = Algorithm();
Symbol cachedSymbol;
var security = algorithm.AddData<UnlinkedData>("ticker");
var symbolCacheAlias = security.Symbol.ID.Symbol;
Assert.IsTrue(SymbolCache.TryGetSymbol(symbolCacheAlias, out cachedSymbol));
Assert.AreSame(security.Symbol, cachedSymbol);
}
[Test]
public void DoesNotCauseCollision_WhenRegisteringMultipleDifferentCustomDataTypes_WithSameTicker()
{
const string ticker = "ticker";
var algorithm = Algorithm();
var security1 = algorithm.AddData<UnlinkedData>(ticker);
var security2 = algorithm.AddData<Bitcoin>(ticker);
var unlinkedData = algorithm.Securities[security1.Symbol];
Assert.AreSame(security1, unlinkedData);
var bitcoin = algorithm.Securities[security2.Symbol];
Assert.AreSame(security2, bitcoin);
Assert.AreNotSame(unlinkedData, bitcoin);
}
[TestCase(SecurityType.Equity)]
[TestCase(SecurityType.Index)]
[TestCase(SecurityType.Future)]
public void AddOptionContractWithDelistedUnderlyingThrows(SecurityType underlyingSecurityType)
{
var algorithm = Algorithm();
algorithm.SetStartDate(2007, 05, 25);
Security underlying = underlyingSecurityType switch
{
SecurityType.Equity => algorithm.AddEquity("SPY"),
SecurityType.Index => algorithm.AddIndex("SPX"),
SecurityType.Future => algorithm.AddFuture("ES"),
_ => throw new ArgumentException($"Invalid test underlying security type {underlyingSecurityType}")
};
underlying.IsDelisted = true;
// let's remove the underlying since it's delisted
algorithm.RemoveSecurity(underlying.Symbol);
var optionContractSymbol = Symbol.CreateOption(underlying.Symbol, Market.USA, OptionStyle.American, OptionRight.Call, 100,
new DateTime(2007, 06, 15));
var exception = Assert.Throws<ArgumentException>(() => algorithm.AddOptionContract(optionContractSymbol));
Assert.IsTrue(exception.Message.Contains("is delisted"), $"Unexpected exception message: {exception.Message}");
}
private static SubscriptionDataConfig GetMatchingSubscription(QCAlgorithm algorithm, Symbol symbol, Type type)
{
// find a subscription matchin the requested type with a higher resolution than requested
return algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(symbol)
.Where(config => type.IsAssignableFrom(config.Type))
.OrderByDescending(s => s.Resolution)
.FirstOrDefault();
}
private static QCAlgorithm Algorithm()
{
var algorithm = new QCAlgorithm();
algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(algorithm));
return algorithm;
}
private class TestHistoryProvider : HistoryProviderBase
{
public string underlyingSymbol = "GOOG";
public string underlyingSymbol2 = "AAPL";
public override int DataPointCount { get; }
public override void Initialize(HistoryProviderInitializeParameters parameters)
{
throw new NotImplementedException();
}
public override IEnumerable<Slice> GetHistory(IEnumerable<HistoryRequest> requests, DateTimeZone sliceTimeZone)
{
var now = DateTime.UtcNow;
#pragma warning disable CS0618
var tradeBar1 = new TradeBar(now, underlyingSymbol, 1, 1, 1, 1, 1, TimeSpan.FromDays(1));
var tradeBar2 = new TradeBar(now, underlyingSymbol2, 3, 3, 3, 3, 3, TimeSpan.FromDays(1));
var slice1 = new Slice(now, new List<BaseData> { tradeBar1, tradeBar2 },
new TradeBars(now) { tradeBar1, tradeBar2 }, new QuoteBars(),
new Ticks(), new OptionChains(),
new FuturesChains(), new Splits(),
new Dividends(now), new Delistings(),
new SymbolChangedEvents(), new MarginInterestRates(), now);
var tradeBar1_2 = new TradeBar(now, underlyingSymbol, 2, 2, 2, 2, 2, TimeSpan.FromDays(1));
#pragma warning restore CS0618
var slice2 = new Slice(now, new List<BaseData> { tradeBar1_2 },
new TradeBars(now) { tradeBar1_2 }, new QuoteBars(),
new Ticks(), new OptionChains(),
new FuturesChains(), new Splits(),
new Dividends(now), new Delistings(),
new SymbolChangedEvents(), new MarginInterestRates(), now);
return new[] { slice1, slice2 };
}
}
}
}