/* * 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.Linq; using System.Threading; using System.Threading.Tasks; using NodaTime; using NUnit.Framework; using QuantConnect.Data; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; using QuantConnect.Lean.Engine.DataFeeds; using QuantConnect.Lean.Engine.DataFeeds.Enumerators; using QuantConnect.Logging; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Future; using QuantConnect.Securities.Option; using QuantConnect.Util; namespace QuantConnect.Tests.Engine.DataFeeds { [TestFixture, Parallelizable(ParallelScope.All)] public class SubscriptionCollectionTests { [Test] public void EnumerationWhileUpdatingDoesNotThrow() { using var cts = new CancellationTokenSource(); var subscriptions = new SubscriptionCollection(); var start = DateTime.UtcNow; var end = start.AddSeconds(10); var config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.SPY, Resolution.Minute, DateTimeZone.Utc, DateTimeZone.Utc, true, false, false); var security = new Equity( Symbols.SPY, SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc), new Cash(Currencies.USD, 0, 1), SymbolProperties.GetDefault(Currencies.USD), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache() ); var timeZoneOffsetProvider = new TimeZoneOffsetProvider(DateTimeZone.Utc, start, end); using var enumerator = new EnqueueableEnumerator(); using var subscriptionDataEnumerator = new SubscriptionDataEnumerator(config, security.Exchange.Hours, timeZoneOffsetProvider, enumerator, false, false); var subscriptionRequest = new SubscriptionRequest(false, null, security, config, start, end); var subscription = new Subscription(subscriptionRequest, subscriptionDataEnumerator, timeZoneOffsetProvider); var addTask = Task.Factory.StartNew(() => { Log.Trace("Add task started"); while (DateTime.UtcNow < end) { if (!subscriptions.Contains(config)) { subscriptions.TryAdd(subscription); } Thread.Sleep(1); } Log.Trace("Add task ended"); }, cts.Token); var removeTask = Task.Factory.StartNew(() => { Log.Trace("Remove task started"); while (DateTime.UtcNow < end) { Subscription removed; subscriptions.TryRemove(config, out removed); Thread.Sleep(1); } Log.Trace("Remove task ended"); }, cts.Token); var readTask = Task.Factory.StartNew(() => { Log.Trace("Read task started"); while (DateTime.UtcNow < end) { foreach (var sub in subscriptions) { } Thread.Sleep(1); } Log.Trace("Read task ended"); }, cts.Token); Task.WaitAll(addTask, removeTask, readTask); subscription.Dispose(); } [Test] public void DefaultFillForwardResolution() { var subscriptionColletion = new SubscriptionCollection(); var defaultFillForwardResolutio = subscriptionColletion.UpdateAndGetFillForwardResolution(); Assert.AreEqual(defaultFillForwardResolutio.Value, new TimeSpan(0, 1, 0)); } [Test] public void UpdatesFillForwardResolutionOverridesDefaultWhenNotAdding() { var subscriptionColletion = new SubscriptionCollection(); var subscription = CreateSubscription(Resolution.Daily); var fillForwardResolutio = subscriptionColletion.UpdateAndGetFillForwardResolution(subscription.Configuration); Assert.AreEqual(fillForwardResolutio.Value, new TimeSpan(1, 0, 0, 0)); subscription.Dispose(); } [Test] public void UpdatesFillForwardResolutionSuccessfullyWhenNotAdding() { var subscriptionColletion = new SubscriptionCollection(); var subscription = CreateSubscription(Resolution.Second); var fillForwardResolutio = subscriptionColletion.UpdateAndGetFillForwardResolution(subscription.Configuration); Assert.AreEqual(fillForwardResolutio.Value, new TimeSpan(0, 0, 1)); subscription.Dispose(); } [Test] public void UpdatesFillForwardResolutionSuccessfullyWhenAdding() { var subscriptionColletion = new SubscriptionCollection(); using var subscription = CreateSubscription(Resolution.Second); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 0, 1)); } [Test] public void UpdatesFillForwardResolutionSuccessfullyOverridesDefaultWhenAdding() { var subscriptionColletion = new SubscriptionCollection(); var subscription = CreateSubscription(Resolution.Daily); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(1, 0, 0, 0)); subscription.Dispose(); } [Test] public void DoesNotUpdateFillForwardResolutionWhenAddingBiggerResolution() { var subscriptionColletion = new SubscriptionCollection(); var subscription = CreateSubscription(Resolution.Second); var subscription2 = CreateSubscription(Resolution.Minute); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 0, 1)); subscriptionColletion.TryAdd(subscription2); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 0, 1)); subscription.Dispose(); subscription2.Dispose(); } [Test] public void UpdatesFillForwardResolutionWhenRemoving() { var subscriptionColletion = new SubscriptionCollection(); using var subscription = CreateSubscription(Resolution.Second); using var subscription2 = CreateSubscription(Resolution.Daily); subscriptionColletion.TryAdd(subscription); subscriptionColletion.TryAdd(subscription2); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 0, 1)); subscriptionColletion.TryRemove(subscription.Configuration, out var outSubscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(1, 0, 0, 0)); subscriptionColletion.TryRemove(subscription2.Configuration, out var outSubscription2); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 1, 0)); outSubscription.Dispose(); outSubscription2.Dispose(); } [Test] public void FillForwardResolutionIgnoresTick() { var subscriptionColletion = new SubscriptionCollection(); using var subscription = CreateSubscription(Resolution.Tick); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 1, 0)); subscriptionColletion.TryRemove(subscription.Configuration, out var outSubscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 1, 0)); outSubscription.Dispose(); } [Test] public void FillForwardResolutionIgnoresInternalFeed() { var subscriptionColletion = new SubscriptionCollection(); using var subscription = CreateSubscription(Resolution.Second, "AAPL", true); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 1, 0)); subscriptionColletion.TryRemove(subscription.Configuration, out var outSubscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 1, 0)); outSubscription.Dispose(); } [Test] public void DoesNotUpdateFillForwardResolutionWhenRemovingDuplicateResolution() { var subscriptionColletion = new SubscriptionCollection(); using var subscription = CreateSubscription(Resolution.Second); using var subscription2 = CreateSubscription(Resolution.Second, "SPY"); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 0, 1)); subscriptionColletion.TryAdd(subscription2); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 0, 1)); subscriptionColletion.TryRemove(subscription.Configuration, out var outSubscription); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 0, 1)); subscriptionColletion.TryRemove(subscription2.Configuration, out var outSubscription2); Assert.AreEqual(subscriptionColletion.UpdateAndGetFillForwardResolution().Value, new TimeSpan(0, 1, 0)); outSubscription.Dispose(); outSubscription2.Dispose(); } [Test] public void SubscriptionsAreSortedWhenAdding() { var subscriptionColletion = new SubscriptionCollection(); var subscription = CreateSubscription(Resolution.Second, Futures.Metals.Gold, false, SecurityType.Future); var subscription2 = CreateSubscription(Resolution.Second, "SPY"); var subscription3 = CreateSubscription(Resolution.Second, "AAPL", false, SecurityType.Option); var subscription4 = CreateSubscription(Resolution.Second, "EURGBP"); var subscription5 = CreateSubscription(Resolution.Second, "AAPL", false, SecurityType.Option, TickType.OpenInterest); var subscription6 = CreateSubscription(Resolution.Second, "AAPL", false, SecurityType.Option, TickType.Quote); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription }); subscriptionColletion.TryAdd(subscription2); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription2, subscription }); subscriptionColletion.TryAdd(subscription3); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription2, subscription3, subscription }); subscriptionColletion.TryAdd(subscription4); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription4, subscription2, subscription3, subscription }); subscriptionColletion.TryAdd(subscription5); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription4, subscription2, subscription3, subscription5, subscription }); subscriptionColletion.TryAdd(subscription6); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription4, subscription2, subscription3, subscription6, subscription5, subscription }); Assert.AreEqual(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList(), new[] { SecurityType.Equity, SecurityType.Equity, SecurityType.Option, SecurityType.Option, SecurityType.Option, SecurityType.Future }); subscription.Dispose(); subscription2.Dispose(); subscription3.Dispose(); subscription4.Dispose(); subscription5.Dispose(); subscription6.Dispose(); } [Test] public void SubscriptionsAreSortedWhenAdding2() { var subscriptionColletion = new SubscriptionCollection(); var subscription = CreateSubscription(Resolution.Second, Futures.Metals.Gold, false, SecurityType.Future); var subscription2 = CreateSubscription(Resolution.Second, "SPY"); var subscription3 = CreateSubscription(Resolution.Second, "AAPL", false, SecurityType.Option); var subscription4 = CreateSubscription(Resolution.Second, "EURGBP"); subscriptionColletion.TryAdd(subscription); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription }); subscriptionColletion.TryAdd(subscription2); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription2, subscription }); subscriptionColletion.TryAdd(subscription3); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription2, subscription3, subscription }); subscriptionColletion.TryAdd(subscription4); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription4, subscription2, subscription3, subscription }); Assert.AreEqual(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList(), new[] { SecurityType.Equity, SecurityType.Equity, SecurityType.Option, SecurityType.Future }); subscription.Dispose(); subscription2.Dispose(); subscription3.Dispose(); subscription4.Dispose(); } [Test] public void SubscriptionsAreSortedWhenRemoving() { var subscriptionColletion = new SubscriptionCollection(); var subscription = CreateSubscription(Resolution.Second, Futures.Metals.Gold, false, SecurityType.Future); var subscription2 = CreateSubscription(Resolution.Second, "SPY"); var subscription3 = CreateSubscription(Resolution.Second, "AAPL", false, SecurityType.Option); var subscription4 = CreateSubscription(Resolution.Second, "EURGBP"); var subscription5 = CreateSubscription(Resolution.Second, "AAPL", false, SecurityType.Option, TickType.OpenInterest); var subscription6 = CreateSubscription(Resolution.Second, "AAPL", false, SecurityType.Option, TickType.Quote); subscriptionColletion.TryAdd(subscription); subscriptionColletion.TryAdd(subscription2); subscriptionColletion.TryAdd(subscription3); subscriptionColletion.TryAdd(subscription4); subscriptionColletion.TryAdd(subscription5); subscriptionColletion.TryAdd(subscription6); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription4, subscription2, subscription3, subscription6, subscription5, subscription }); subscriptionColletion.TryRemove(subscription2.Configuration, out subscription2); Assert.AreEqual(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList(), new[] { SecurityType.Equity, SecurityType.Option, SecurityType.Option, SecurityType.Option, SecurityType.Future }); subscriptionColletion.TryRemove(subscription3.Configuration, out subscription3); Assert.AreEqual(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList(), new[] { SecurityType.Equity, SecurityType.Option, SecurityType.Option, SecurityType.Future }); subscriptionColletion.TryRemove(subscription.Configuration, out subscription); Assert.AreEqual(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList(), new[] { SecurityType.Equity, SecurityType.Option, SecurityType.Option }); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription4, subscription6, subscription5 }); subscriptionColletion.TryRemove(subscription6.Configuration, out subscription6); Assert.AreEqual(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList(), new[] { SecurityType.Equity, SecurityType.Option }); Assert.AreEqual(subscriptionColletion.ToList(), new[] { subscription4, subscription5 }); subscriptionColletion.TryRemove(subscription5.Configuration, out subscription5); Assert.AreEqual(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList(), new[] {SecurityType.Equity}); subscriptionColletion.TryRemove(subscription4.Configuration, out subscription4); Assert.IsTrue(subscriptionColletion.Select(x => x.Configuration.SecurityType).ToList().IsNullOrEmpty()); subscription.Dispose(); subscription2.Dispose(); subscription3.Dispose(); subscription4.Dispose(); subscription5.Dispose(); subscription6.Dispose(); } private Subscription CreateSubscription(Resolution resolution, string symbol = "AAPL", bool isInternalFeed = false, SecurityType type = SecurityType.Equity, TickType tickType = TickType.Trade) { var start = DateTime.UtcNow; var end = start.AddSeconds(10); Security security; Symbol _symbol; if (type == SecurityType.Equity) { _symbol = new Symbol(SecurityIdentifier.GenerateEquity(DateTime.Now, symbol, Market.USA), symbol); security = new Equity( _symbol, SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc), new Cash(Currencies.USD, 0, 1), SymbolProperties.GetDefault(Currencies.USD), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache() ); } else if (type == SecurityType.Option) { _symbol = Symbol.CreateOption( new Symbol(SecurityIdentifier.GenerateEquity(DateTime.Now, symbol, Market.USA), symbol), Market.USA, OptionStyle.American, OptionRight.Call, 0m, DateTime.Now ); security = new Option( _symbol, SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc), new Cash(Currencies.USD, 0, 1), new OptionSymbolProperties(SymbolProperties.GetDefault(Currencies.USD)), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache(), null ); } else if (type == SecurityType.Future) { _symbol = new Symbol(SecurityIdentifier.GenerateFuture(DateTime.Now, symbol, Market.COMEX), symbol); security = new Future( _symbol, SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc), new Cash(Currencies.USD, 0, 1), SymbolProperties.GetDefault(Currencies.USD), ErrorCurrencyConverter.Instance, RegisteredSecurityDataTypesProvider.Null, new SecurityCache() ); } else { throw new RegressionTestException("SecurityType not implemented"); } var config = new SubscriptionDataConfig(typeof(TradeBar), _symbol, resolution, DateTimeZone.Utc, DateTimeZone.Utc, true, false, isInternalFeed, false, tickType); var timeZoneOffsetProvider = new TimeZoneOffsetProvider(DateTimeZone.Utc, start, end); # pragma warning disable CA2000 var enumerator = new EnqueueableEnumerator(); var subscriptionDataEnumerator = new SubscriptionDataEnumerator(config, security.Exchange.Hours, timeZoneOffsetProvider, enumerator, false, false); # pragma warning restore CA2000 var subscriptionRequest = new SubscriptionRequest(false, null, security, config, start, end); return new Subscription(subscriptionRequest, subscriptionDataEnumerator, timeZoneOffsetProvider); } } }