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

420 lines
21 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.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<BaseData>();
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<BaseData>();
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);
}
}
}