chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,301 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using System;
using System.Collections.Generic;
using System.Threading;
using DateTime = System.DateTime;
using Tick = QuantConnect.Data.Market.Tick;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class AggregationManagerTests
{
[Test]
public void PassesTicksStraightThrough()
{
using var aggregator = GetDataAggregator();
var reference = DateTime.Today;
var config = GetSubscriptionDataConfig<Tick>(Symbols.SPY, Resolution.Tick);
var count = 0;
aggregator.Add(config, (s, e) => { count++; });
aggregator.Update(new Tick(reference.AddSeconds(1), Symbols.SPY, 30, 30) { TickType = TickType.Trade });
aggregator.Update(new Tick(reference.AddSeconds(2), Symbols.SPY, 20, 20) { TickType = TickType.Trade });
Assert.AreEqual(count, 2);
aggregator.Update(new Tick(reference.AddSeconds(3), Symbols.AAPL, 200, 200) { TickType = TickType.Trade });
Assert.AreEqual(count, 2);
aggregator.Remove(config);
aggregator.Update(new Tick(reference.AddSeconds(4), Symbols.SPY, 20, 20) { TickType = TickType.Trade });
Assert.AreEqual(count, 2);
}
[Test]
public void BadTicksIgnored()
{
using var aggregator = GetDataAggregator();
var reference = DateTime.Today;
var config = GetSubscriptionDataConfig<Tick>(Symbols.SPY, Resolution.Tick);
var count = 0;
aggregator.Add(config, (s, e) => { count++; });
aggregator.Update(new Tick(reference.AddSeconds(1), Symbols.AAPL, 200, 200));
Assert.AreEqual(count, 0);
}
[Test]
public void TickTypeRespected()
{
using var aggregator = GetDataAggregator();
var reference = DateTime.Today;
var config = GetSubscriptionDataConfig<Tick>(Symbols.SPY, Resolution.Tick);
var count = 0;
aggregator.Add(config, (s, e) => { count++; });
aggregator.Update(new Tick(reference.AddSeconds(3), Symbols.SPY, 200, 200) { TickType = TickType.Trade });
Assert.AreEqual(count, 1);
aggregator.Update(new Tick(reference.AddSeconds(4), Symbols.SPY, 20, 20) { TickType = TickType.Quote });
Assert.AreEqual(count, 1);
}
[Test]
public void UnknownSubscriptionIgnored()
{
using var aggregator = GetDataAggregator();
var reference = DateTime.Today;
var config = GetSubscriptionDataConfig<Tick>(Symbols.SPY, Resolution.Tick);
var count = 0;
aggregator.Update(new Tick(reference.AddSeconds(1), Symbols.SPY, 30, 30) { TickType = TickType.Trade });
aggregator.Update(new Tick(reference.AddSeconds(2), Symbols.SPY, 20, 20) { TickType = TickType.Trade });
Assert.AreEqual(count, 0);
aggregator.Add(config, (s, e) => { count++; });
aggregator.Update(new Tick(reference.AddSeconds(3), Symbols.SPY, 200, 200) { TickType = TickType.Trade });
Assert.AreEqual(count, 1);
aggregator.Remove(config);
aggregator.Update(new Tick(reference.AddSeconds(4), Symbols.SPY, 20, 20) { TickType = TickType.Trade });
Assert.AreEqual(count, 1);
}
[TestCase(100, 1, typeof(TradeBar), Resolution.Minute)]
[TestCase(120, 2, typeof(TradeBar), Resolution.Minute)]
[TestCase(121, 2, typeof(TradeBar), Resolution.Minute)]
[TestCase(100, 1, typeof(QuoteBar), Resolution.Minute)]
[TestCase(120, 2, typeof(QuoteBar), Resolution.Minute)]
[TestCase(121, 2, typeof(QuoteBar), Resolution.Minute)]
[TestCase(100, 99, typeof(TradeBar), Resolution.Second)]
[TestCase(121, 120, typeof(QuoteBar), Resolution.Second)]
[TestCase(3599, 0, typeof(QuoteBar), Resolution.Hour)]
[TestCase(3599, 0, typeof(TradeBar), Resolution.Hour)]
[TestCase(3600, 1, typeof(QuoteBar), Resolution.Hour)]
[TestCase(3600, 1, typeof(TradeBar), Resolution.Hour)]
[TestCase(3601, 1, typeof(QuoteBar), Resolution.Hour)]
[TestCase(3601, 1, typeof(TradeBar), Resolution.Hour)]
public void CanHandleMultipleSubscriptions(int secondsToAdd, int expectedBars, Type dataType, Resolution resolution)
{
using var aggregator = GetDataAggregator();
var reference = DateTime.Today;
var symbols = new[] { Symbols.SPY, Symbols.AAPL, Symbols.USDJPY, Symbols.EURUSD };
var enumerators = new Queue<IEnumerator<BaseData>>();
foreach (var symbol in symbols)
{
enumerators.Enqueue(aggregator.Add(GetSubscriptionDataConfig(dataType, symbol, resolution), (s, e) => { }));
}
for (var i = 1; i <= secondsToAdd; i++)
{
foreach (var symbol in symbols)
{
aggregator.Update(new Tick(reference.AddSeconds(i), symbol, 20 + i, 20 + i) { TickType = dataType == typeof(TradeBar) ? TickType.Trade : TickType.Quote });
}
}
foreach (var enumerator in enumerators)
{
for (int i = 0; i < expectedBars; i++)
{
enumerator.MoveNext();
Assert.IsNotNull(enumerator.Current);
}
enumerator.MoveNext();
Assert.IsNull(enumerator.Current);
}
}
[TestCase(typeof(TradeBar), TickType.Trade, Resolution.Second)]
[TestCase(typeof(QuoteBar), TickType.Quote, Resolution.Second)]
[TestCase(typeof(Tick), TickType.Trade, Resolution.Tick)]
[TestCase(typeof(Tick), TickType.Quote, Resolution.Tick)]
public void CanHandleBars(Type type, TickType tickType, Resolution resolution)
{
using var aggregator = GetDataAggregator();
var reference = DateTime.Today;
var total = 0;
var enumerator = aggregator.Add(GetSubscriptionDataConfig(type, Symbols.EURUSD, resolution, tickType), (s, e) => { });
for (int i = 0; i < 100; i++)
{
aggregator.Update(new Tick(reference.AddSeconds(i), Symbols.EURUSD, 20 + i, 20 + i) { TickType = tickType });
}
Thread.Sleep(250);
enumerator.MoveNext();
while (enumerator.Current != null)
{
Assert.IsTrue(enumerator.Current.GetType() == type);
var tick = enumerator.Current as Tick;
if (tick != null)
{
Assert.IsTrue(tick.TickType == tickType);
}
total++;
enumerator.MoveNext();
}
if (resolution == Resolution.Second)
{
Assert.AreEqual(99, total);
}
else
{
Assert.AreEqual(100, total);
}
}
[Test]
public void SubscribeMultipleDataTypes()
{
var reference = DateTime.Today;
var timeProvider = new ManualTimeProvider(reference);
using var aggregator = GetDataAggregator(timeProvider);
var symbol = Symbols.AAPL;
var configs = new[] {
GetSubscriptionDataConfig<TradeBar>(symbol, Resolution.Minute),
GetSubscriptionDataConfig<QuoteBar>(symbol, Resolution.Minute),
GetSubscriptionDataConfig<Tick>(symbol, Resolution.Tick, TickType.Trade),
GetSubscriptionDataConfig<Tick>(symbol, Resolution.Tick, TickType.Quote),
GetSubscriptionDataConfig<Dividend>(symbol, Resolution.Tick),
GetSubscriptionDataConfig<Split>(symbol, Resolution.Tick)
};
var enumerators = new Queue<IEnumerator<BaseData>>();
Array.ForEach(configs, (c) => enumerators.Enqueue(aggregator.Add(c, (s, e) => { })));
var expectedBars = new[] { 2, 2, 100, 100, 1, 1 };
for (int i = 1; i <= 100; i++)
{
aggregator.Update(new Tick(reference.AddSeconds(i), symbol, 20 + i, 20 + i)
{
TickType = TickType.Trade
});
aggregator.Update(new Tick(reference.AddSeconds(i), symbol, 20 + i, 20 + i)
{
TickType = TickType.Quote
});
}
aggregator.Update(new Dividend(symbol, reference.AddSeconds(1), 0.47m, 108.60m));
aggregator.Update(new Split(symbol, reference.AddSeconds(1), 645.57m, 0.142857m, SplitType.SplitOccurred));
timeProvider.SetCurrentTime(reference.AddMinutes(2));
var dividendCount = 0;
var splitCount = 0;
var j = 0;
foreach (var enumerator in enumerators)
{
for (int i = 0; i < expectedBars[j]; i++)
{
enumerator.MoveNext();
Assert.IsNotNull(enumerator.Current);
if (enumerator.Current is Dividend)
{
dividendCount++;
}
if (enumerator.Current is Split)
{
splitCount++;
}
}
enumerator.MoveNext();
Assert.IsNull(enumerator.Current);
j++;
}
Assert.AreEqual(1, dividendCount);
Assert.AreEqual(1, splitCount);
}
private IDataAggregator GetDataAggregator()
{
return GetDataAggregator(new ManualTimeProvider(DateTime.Today));
}
private IDataAggregator GetDataAggregator(ITimeProvider timeProvider)
{
return new TestAggregationManager(timeProvider);
}
private SubscriptionDataConfig GetSubscriptionDataConfig<T>(Symbol symbol, Resolution resolution, TickType? tickType = null)
{
return GetSubscriptionDataConfig(typeof(T), symbol, resolution, tickType);
}
private SubscriptionDataConfig GetSubscriptionDataConfig(Type T, Symbol symbol, Resolution resolution, TickType? tickType = null)
{
return new SubscriptionDataConfig(
T,
symbol,
resolution,
TimeZones.Utc,
TimeZones.Utc,
true,
true,
false,
tickType: tickType);
}
private class TestAggregationManager : AggregationManager
{
public TestAggregationManager(ITimeProvider timeProvider)
{
TimeProvider = timeProvider;
}
}
}
}
+104
View File
@@ -0,0 +1,104 @@
/*
* 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 QuantConnect.Python;
using QuantConnect.Algorithm;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Tests.Common.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds
{
/// <summary>
/// This type allows tests to easily create an algorithm that is mostly initialized in one line
/// </summary>
public class AlgorithmStub : QCAlgorithm
{
public List<SecurityChanges> SecurityChangesRecord { get; set; } = new List<SecurityChanges>();
public DataManager DataManager { get; set; }
public IDataFeed DataFeed { get; set; }
/// <summary>
/// Lanzy PandasConverter only if used
/// </summary>
public override PandasConverter PandasConverter
{
get
{
if(base.PandasConverter == null)
{
SetPandasConverter();
}
return base.PandasConverter;
}
}
public AlgorithmStub(bool createDataManager = true)
{
if (createDataManager)
{
var dataManagerStub = new DataManagerStub(this);
DataManager = dataManagerStub;
DataFeed = dataManagerStub.DataFeed;
SubscriptionManager.SetDataManager(DataManager);
}
var orderProcessor = new FakeOrderProcessor();
orderProcessor.TransactionManager = Transactions;
Transactions.SetOrderProcessor(orderProcessor);
}
public AlgorithmStub(IDataFeed dataFeed)
{
DataFeed = dataFeed;
DataManager = new DataManagerStub(dataFeed, this);
SubscriptionManager.SetDataManager(DataManager);
Transactions.SetOrderProcessor(new FakeOrderProcessor());
}
public void AddSecurities(Resolution resolution = Resolution.Second, List<string> equities = null, List<string> forex = null, List<string> crypto = null)
{
foreach (var ticker in equities ?? new List<string>())
{
AddSecurity(SecurityType.Equity, ticker, resolution);
var symbol = SymbolCache.GetSymbol(ticker);
Securities[symbol].Exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
}
foreach (var ticker in forex ?? new List<string>())
{
AddSecurity(SecurityType.Forex, ticker, resolution);
}
foreach (var ticker in crypto ?? new List<string>())
{
AddSecurity(SecurityType.Crypto, ticker, resolution);
var symbol = SymbolCache.GetSymbol(ticker);
Securities[symbol].Exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.Utc));
}
}
public void AddCryptoEntry(string ticker, string market)
{
var symbolProperties = SymbolPropertiesDatabase.GetSymbolProperties(market, null, SecurityType.Crypto, Currencies.USD);
SymbolPropertiesDatabase.SetEntry(market, ticker, SecurityType.Crypto, symbolProperties);
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
SecurityChangesRecord.Add(changes);
}
}
}
@@ -0,0 +1,222 @@
/*
* 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.Diagnostics;
using NUnit.Framework;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Logging;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds.Auxiliary
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class MapFileResolverTests
{
private readonly MapFileResolver _resolver = CreateMapFileResolver();
[Test]
public void ChecksFirstDate()
{
var mapFileProvider = TestGlobals.MapFileProvider;
var mapFileResolver = mapFileProvider.Get(AuxiliaryDataKey.EquityUsa);
// QQQ started trading on 19990310
var mapFile = mapFileResolver.ResolveMapFile("QQQ", new DateTime(1999, 3, 9));
Assert.IsTrue(mapFile.IsNullOrEmpty());
var mapFile2 = mapFileResolver.ResolveMapFile("QQQ", new DateTime(2015, 3, 10));
Assert.IsFalse(mapFile2.IsNullOrEmpty());
}
[Test]
public void ResolvesCorrectlyReUsedTicker()
{
var mapFileProvider = TestGlobals.MapFileProvider;
var mapFileResolver = mapFileProvider.Get(AuxiliaryDataKey.EquityUsa);
// FB.1 started trading on 19990929 and ended on 20030328
var mapFile = mapFileResolver.ResolveMapFile("FB", new DateTime(1999, 9, 28));
Assert.IsTrue(mapFile.IsNullOrEmpty());
mapFile = mapFileResolver.ResolveMapFile("FB", new DateTime(1999, 9, 29));
Assert.IsFalse(mapFile.IsNullOrEmpty());
// FB started trading on 20120518
mapFile = mapFileResolver.ResolveMapFile("FB", new DateTime(2012, 5, 17));
Assert.IsTrue(mapFile.IsNullOrEmpty());
mapFile = mapFileResolver.ResolveMapFile("FB", new DateTime(2015, 5, 18));
Assert.IsFalse(mapFile.IsNullOrEmpty());
}
[Test]
public void InitializationSpeedTest()
{
var mapFileProvider = TestGlobals.MapFileProvider;
var sw = Stopwatch.StartNew();
var mapFileresolver = mapFileProvider.Get(AuxiliaryDataKey.EquityUsa);
sw.Stop();
Log.Trace($"elapsed: {sw.Elapsed.TotalSeconds} seconds");
}
[Test]
public void ResolvesStraightMapping()
{
var spyMapFile = _resolver.ResolveMapFile("SPY", new DateTime(2015, 08, 23));
Assert.IsNotNull(spyMapFile);
Assert.AreEqual("SPY", spyMapFile.GetMappedSymbol(new DateTime(2015, 08, 23)));
}
[Test]
public void MapFileReturnDefaultValueCorrectly()
{
var spyMapFile = _resolver.ResolveMapFile("PEPE", new DateTime(2015, 08, 23));
Assert.IsNotNull(spyMapFile);
Assert.AreEqual("Pepito", spyMapFile.GetMappedSymbol(new DateTime(2015, 08, 23), "Pepito"));
}
[Test]
public void ResolvesMapFilesOnNonSpecifiedDates()
{
var mapFile = _resolver.ResolveMapFile("GOOG", new DateTime(2014, 04, 01));
Assert.AreEqual("GOOGL", mapFile.Permtick);
mapFile = _resolver.ResolveMapFile("GOOG", new DateTime(2014, 04, 03));
Assert.AreEqual("GOOG", mapFile.Permtick);
}
[Test]
public void ResolvesOldSymbolRemapped()
{
// on 2014.04.02 a symbol GOOG traded its last day, the following day it would trade under GOOGL
var april2 = new DateTime(2014, 04, 02);
var googMapFile = _resolver.ResolveMapFile("GOOG", april2);
Assert.IsNotNull(googMapFile);
Assert.AreEqual("GOOG", googMapFile.GetMappedSymbol(april2));
Assert.AreEqual("GOOGL", googMapFile.GetMappedSymbol(april2.AddDays(1)));
}
[Test]
public void ResolvesExactMapping()
{
var oih1 = _resolver.ResolveMapFile("OIH.1", new DateTime(2011, 12, 20));
Assert.IsNotNull(oih1);
Assert.AreEqual("OIH", oih1.GetMappedSymbol(new DateTime(2010, 02, 06)));
Assert.AreEqual("OIH", oih1.GetMappedSymbol(new DateTime(2010, 02, 07)));
Assert.AreEqual("OIH", oih1.GetMappedSymbol(new DateTime(2011, 12, 20)));
Assert.AreEqual(string.Empty, oih1.GetMappedSymbol(new DateTime(2011, 12, 21)));
}
[Test]
public void ResolvesRemappedSymbolWithBothMapFiles()
{
var date = new DateTime(2012, 06, 28);
var mapFile = _resolver.ResolveMapFile("SPXL", date);
Assert.IsNotNull(mapFile);
Assert.AreEqual("BGU", mapFile.GetMappedSymbol(date));
Assert.AreEqual("SPXL", mapFile.GetMappedSymbol(date.AddDays(1)));
}
[Test]
public void ResolvesRemappedAndDelistedSymbol()
{
var date = new DateTime(2018, 7, 23);
var mapFile = _resolver.ResolveMapFile("TWX", date);
Assert.IsNotNull(mapFile);
Assert.AreEqual("AOL", mapFile.GetMappedSymbol(new DateTime(2000, 1, 1)));
Assert.AreEqual("TWX", mapFile.GetMappedSymbol(new DateTime(2014, 1, 1)));
}
[Test]
public void ContinuousFuturesMappingMode()
{
var date = new DateTime(2018, 7, 23);
var mapFile = _resolver.ResolveMapFile("NG", date);
Assert.IsNotNull(mapFile);
Assert.AreEqual("NG UNT495KXTOLD", mapFile.GetMappedSymbol(new DateTime(2010, 6, 15), dataMappingMode: DataMappingMode.LastTradingDay));
Assert.AreEqual("NG UOMNNYK04BEP", mapFile.GetMappedSymbol(new DateTime(2010, 6, 15), dataMappingMode: DataMappingMode.FirstDayMonth));
Assert.AreEqual("NG UMWMI2IDASAP", mapFile.GetMappedSymbol(new DateTime(2010, 6, 15), dataMappingMode: DataMappingMode.OpenInterest));
}
private static MapFileResolver CreateMapFileResolver()
{
return new MapFileResolver(new List<MapFile>
{
// remapped
new MapFile("goog", new List<MapFileRow>
{
new MapFileRow(new DateTime(2014, 03, 27), "goocv"),
new MapFileRow(new DateTime(2014, 04, 02), "goocv"),
new MapFileRow(new DateTime(2050, 12, 31), "goog")
}),
new MapFile("googl", new List<MapFileRow>
{
new MapFileRow(new DateTime(2004, 08, 19), "goog"),
new MapFileRow(new DateTime(2014, 04, 02), "goog"),
new MapFileRow(new DateTime(2050, 12, 31), "googl")
}),
// remapped (with both map files)
new MapFile("bgu", new List<MapFileRow>
{
new MapFileRow(new DateTime(2008, 11, 05), "bgu"),
new MapFileRow(new DateTime(2012, 06, 28), "bgu")
}),
new MapFile("spxl", new List<MapFileRow>
{
new MapFileRow(new DateTime(2008, 11, 05), "bgu"),
new MapFileRow(new DateTime(2012, 06, 28), "bgu"),
new MapFileRow(new DateTime(2050, 12, 31), "spxl")
}),
// straight mapping
new MapFile("spy", new List<MapFileRow>
{
new MapFileRow(new DateTime(1998, 01, 02), "spy"),
new MapFileRow(new DateTime(2050, 12, 31), "spy")
}),
// new share class overtakes old share class same symbol
new MapFile("oih.1", new List<MapFileRow>
{
new MapFileRow(new DateTime(2010, 02, 07), "oih"),
new MapFileRow(new DateTime(2011, 12, 20), "oih")
}),
new MapFile("oih", new List<MapFileRow>
{
new MapFileRow(new DateTime(2011, 12, 21), "oih"),
new MapFileRow(new DateTime(2050, 12, 31), "oih")
}),
// remapped + delisted
new MapFile("twx.1", new List<MapFileRow>
{
new MapFileRow(new DateTime(1998, 1, 2), "twx"),
new MapFileRow(new DateTime(2001, 1, 11), "twx")
}),
new MapFile("twx", new List<MapFileRow>
{
new MapFileRow(new DateTime(1998, 1, 2), "aol"),
new MapFileRow(new DateTime(2003, 10, 15), "aol"),
new MapFileRow(new DateTime(2018, 6, 15), "twx")
}),
new MapFile("ng", new List<MapFileRow>
{
new MapFileRow(new DateTime(2010, 6, 15), "ng unt495kxtold", Exchange.NYMEX, DataMappingMode.LastTradingDay),
new MapFileRow(new DateTime(2010, 6, 15), "ng uomnnyk04bep", Exchange.NYMEX, DataMappingMode.FirstDayMonth),
new MapFileRow(new DateTime(2010, 6, 15), "ng umwmi2idasap", Exchange.NYMEX, DataMappingMode.OpenInterest),
}),
});
}
}
}
@@ -0,0 +1,75 @@
/*
* 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.Linq;
using NUnit.Framework;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class BacktestingFutureChainProviderTests
{
private ILogHandler _logHandler;
private BacktestingFutureChainProvider _provider;
[OneTimeSetUp]
public void SetUp()
{
// Store initial Log Handler
_logHandler = Log.LogHandler;
_provider = new BacktestingFutureChainProvider();
_provider.Initialize(new(TestGlobals.MapFileProvider, TestGlobals.HistoryProvider));
}
[OneTimeTearDown]
public void TearDown()
{
// Restore intial Log Handler
Log.LogHandler = _logHandler;
}
[TestCase("20131011")]
// saturday, will fetch previous tradable date instead
[TestCase("20131012")]
public void CorrectlyDeterminesContractList(string date)
{
var dateTime = Time.ParseDate(date);
var symbol = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, dateTime.AddDays(10));
var result = _provider.GetFutureContractList(symbol, dateTime);
Assert.IsNotEmpty(result);
}
[TestCase("20201007", 2)]
[TestCase("20131007", 5)]
public void UsesMultipleResolutions(string strDate, int expectedCount)
{
// we don't have minute data for this date
var date = Time.ParseDate(strDate);
var symbol = Symbol.CreateFuture(Futures.Indices.SP500EMini, Market.CME, date);
var futureChain = _provider.GetFutureContractList(symbol, date).ToList();
Assert.IsTrue(futureChain.All(x => x.ID.Date.Date >= date));
Assert.IsTrue(futureChain.All(x => x.SecurityType == SecurityType.Future));
Assert.IsTrue(futureChain.All(x => x.ID.Symbol == Futures.Indices.SP500EMini));
Assert.AreEqual(expectedCount, futureChain.Count);
}
}
}
@@ -0,0 +1,98 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Util;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class BaseDataCollectionAggregatorReaderTests
{
[TestCase(Resolution.Daily, 1, true, true)]
[TestCase( Resolution.Hour, 1, true, true)]
[TestCase(Resolution.Daily, 5849, false, true)]
[TestCase(Resolution.Hour, 40832, false, true)]
[TestCase(Resolution.Daily, 1, true, false)]
[TestCase(Resolution.Hour, 1, true, false)]
[TestCase(Resolution.Daily, 5849, false, false)]
[TestCase(Resolution.Hour, 40832, false, false)]
public void AggregatesDataPerTime(Resolution resolution, int expectedCount, bool singleDate, bool isDataEphemeral)
{
var reader = Initialize(false, resolution, isDataEphemeral, out var dataSource);
TestBaseDataCollection.SingleDate = singleDate;
var result = reader.Read(dataSource).ToList();
Assert.AreEqual(expectedCount, result.Count);
Assert.IsTrue(result.All(data => data is TestBaseDataCollection));
if (expectedCount == 1)
{
var collection = result[0] as TestBaseDataCollection;
Assert.IsNotNull(collection);
Assert.GreaterOrEqual(collection.Data.Count, 5000);
Assert.AreEqual(expectedCount, collection.Data.DistinctBy(data => data.Time).Count());
Assert.IsTrue(collection.Data.All(x => x.Symbol == Symbols.AAPL));
}
}
private static ISubscriptionDataSourceReader Initialize(bool liveMode, Resolution resolution, bool isDataEphemeral, out SubscriptionDataSource source)
{
using var cache = new ZipDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: isDataEphemeral);
var config = new SubscriptionDataConfig(typeof(TestBaseDataCollection),
Symbols.SPY,
resolution,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false);
var date = DateTime.MinValue;
var path = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, resolution, TickType.Trade);
source = new SubscriptionDataSource(path, SubscriptionTransportMedium.LocalFile);
return new BaseDataCollectionAggregatorReader(cache, config, date, liveMode, null);
}
private class TestBaseDataCollection : BaseDataCollection
{
public static volatile bool SingleDate;
private static readonly TradeBar _factory = new TradeBar();
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var dataPoint = _factory.Reader(config, line, date, isLiveMode);
if (SingleDate)
{
// single day
dataPoint.Time = date;
}
// universe data collections can return data with a symbol different than the one in the configuration
dataPoint.Symbol = Symbols.AAPL;
return dataPoint;
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return _factory.GetSource(config, date, isLiveMode);
}
}
}
}
@@ -0,0 +1,242 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class BaseDataExchangeTests
{
// This is a default timeout for all tests to wait if something went wrong
const int DefaultTimeout = 30000;
private CancellationTokenSource _cancellationTokenSource;
[SetUp]
public void SetUp()
{
_cancellationTokenSource = new CancellationTokenSource();
}
[TearDown]
public void TearDown()
{
_cancellationTokenSource.Cancel();
_cancellationTokenSource.DisposeSafely();
}
[Test]
public void EndsQueueConsumption()
{
var enqueable = new EnqueueableEnumerator<BaseData>();
var exchange = new BaseDataExchange("test");
using var cancellationToken = new CancellationTokenSource();
Task.Run(() =>
{
while (!cancellationToken.IsCancellationRequested)
{
Thread.Sleep(10);
enqueable.Enqueue(new Tick {Symbol = Symbols.SPY, Time = DateTime.UtcNow});
}
});
BaseData last = null;
using var lastUpdated = new AutoResetEvent(false);
exchange.AddEnumerator(Symbols.SPY, enqueable, handleData: spy =>
{
last = spy;
lastUpdated.Set();
});
using var finishedRunning = new AutoResetEvent(false);
Task.Run(() => { exchange.Start(); finishedRunning.Set(); } );
try
{
Assert.IsTrue(lastUpdated.WaitOne(DefaultTimeout));
}
finally
{
exchange.Stop();
cancellationToken.Cancel();
}
Assert.IsTrue(finishedRunning.WaitOne(DefaultTimeout));
var endTime = DateTime.UtcNow;
Assert.IsNotNull(last);
Assert.IsTrue(last.Time <= endTime);
enqueable.Dispose();
}
[Test]
public void DefaultErrorHandlerDoesNotStopQueueConsumption()
{
var enqueable = new EnqueueableEnumerator<BaseData>();
var exchange = new BaseDataExchange("test");
using var cancellationToken = new CancellationTokenSource();
Task.Run(() =>
{
while (!cancellationToken.IsCancellationRequested)
{
Thread.Sleep(10);
enqueable.Enqueue(new Tick { Symbol = Symbols.SPY, Time = DateTime.UtcNow });
}
});
var first = true;
BaseData last = null;
using var lastUpdated = new AutoResetEvent(false);
exchange.AddEnumerator(Symbols.SPY, enqueable, handleData: spy =>
{
if (first)
{
first = false;
throw new RegressionTestException("This exception should be swalloed by the exchange!");
}
last = spy;
lastUpdated.Set();
});
Task.Run(() => exchange.Start());
try
{
Assert.IsTrue(lastUpdated.WaitOne(DefaultTimeout));
}
finally
{
exchange.Stop();
cancellationToken.Cancel();
enqueable.Dispose();
}
}
[Test]
public void SetErrorHandlerExitsOnTrueReturn()
{
var enqueable = new EnqueueableEnumerator<BaseData>();
var exchange = new BaseDataExchange("test");
using var cancellationToken = new CancellationTokenSource();
Task.Factory.StartNew(() =>
{
while (!cancellationToken.IsCancellationRequested)
{
Thread.Sleep(10);
enqueable.Enqueue(new Tick { Symbol = Symbols.SPY, Time = DateTime.UtcNow });
}
});
var first = true;
using var errorCaught = new AutoResetEvent(true);
BaseData last = null;
exchange.SetErrorHandler(error =>
{
errorCaught.Set();
return true;
});
exchange.AddEnumerator(Symbols.SPY, enqueable, handleData: spy =>
{
if (first)
{
first = false;
throw new RegressionTestException();
}
last = spy;
});
Task.Run(() => exchange.Start());
try
{
Assert.IsTrue(errorCaught.WaitOne(DefaultTimeout));
Assert.IsNull(last);
}
finally
{
exchange.Stop();
enqueable.Dispose();
cancellationToken.Cancel();
}
}
[Test]
public void RespectsShouldMoveNext()
{
var exchange = new BaseDataExchange("test");
exchange.SetErrorHandler(exception => true);
exchange.AddEnumerator(Symbol.Empty, new List<BaseData> {new Tick()}.GetEnumerator(), () => false);
using var isFaultedEvent = new ManualResetEvent(false);
using var isCompletedEvent = new ManualResetEvent(false);
Task.Run(() => exchange.Start()).ContinueWith(task =>
{
if (task.IsFaulted) isFaultedEvent.Set();
isCompletedEvent.Set();
});
try
{
isCompletedEvent.WaitOne();
Assert.IsFalse(isFaultedEvent.WaitOne(0));
}
finally
{
exchange.Stop();
}
}
[Test]
public void FiresOnEnumeratorFinishedEvents()
{
var exchange = new BaseDataExchange("test");
IEnumerator<BaseData> enumerator = new List<BaseData>().GetEnumerator();
using var isCompletedEvent = new ManualResetEvent(false);
exchange.AddEnumerator(Symbol.Empty, enumerator, () => true, handler => isCompletedEvent.Set());
Task.Run(() => exchange.Start());
isCompletedEvent.WaitOne();
exchange.Stop();
}
[Test]
public void RemovesBySymbol()
{
var exchange = new BaseDataExchange("test");
var enumerator = new List<BaseData> {new Tick {Symbol = Symbols.SPY}}.GetEnumerator();
exchange.AddEnumerator(Symbols.SPY, enumerator);
var removed = exchange.RemoveEnumerator(Symbols.AAPL);
Assert.IsNull(removed);
removed = exchange.RemoveEnumerator(Symbols.SPY);
Assert.AreEqual(Symbols.SPY, removed.Symbol);
}
}
}
@@ -0,0 +1,45 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Custom.Tiingo;
using QuantConnect.Lean.Engine.DataFeeds;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class CollectionSubscriptionDataSourceReaderTests
{
[Test]
public void HandlesInitializationErrors()
{
var date = new DateTime(2018, 7, 7);
var config = new SubscriptionDataConfig(typeof(TiingoPrice), Symbols.AAPL, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false, true);
var reader = new CollectionSubscriptionDataSourceReader(null, config, date, false, null);
var source = new TiingoPrice().GetSource(config, date, false);
// should not throw with an empty or invalid Tiingo API token
Assert.DoesNotThrow(() =>
{
var list = reader.Read(source).ToList();
Assert.AreEqual(0, list.Count);
});
}
}
}
@@ -0,0 +1,53 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Lean.Engine.DataFeeds;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class CompositeTimeProviderTests
{
[Test]
public void NoProviderGiven()
{
var compositeTimeProvider = new CompositeTimeProvider(Enumerable.Empty<ITimeProvider>());
var utcNow = DateTime.UtcNow;
Assert.LessOrEqual(utcNow, compositeTimeProvider.GetUtcNow());
}
[Test]
public void SmallestTime()
{
var time = new DateTime(1999, 1, 1);
var timeProvider1 = new ManualTimeProvider();
timeProvider1.SetCurrentTimeUtc(time.AddYears(1));
var timeProvider2 = new ManualTimeProvider();
timeProvider2.SetCurrentTimeUtc(time);
var timeProvider3 = new ManualTimeProvider();
timeProvider3.SetCurrentTimeUtc(time.AddYears(2));
var compositeTimeProvider = new CompositeTimeProvider(new[] { timeProvider1 , timeProvider2, timeProvider3 });
Assert.LessOrEqual(time, compositeTimeProvider.GetUtcNow());
}
}
}
@@ -0,0 +1,611 @@
/*
* 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.Globalization;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Transport;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Logging;
using QuantConnect.Packets;
using QuantConnect.Util;
using QuantConnect.Lean.Engine.Storage;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class CustomLiveDataFeedTests
{
private LiveSynchronizer _synchronizer;
private IDataFeed _feed;
[TearDown]
public void TearDown()
{
_feed.Exit();
_synchronizer.DisposeSafely();
}
[Test]
public void EmitsDailyCustomFutureDataOverWeekends()
{
using var api = new Api.Api();
RemoteFileSubscriptionStreamReader.SetDownloadProvider(api);
var tickers = new[] { "CHRIS/CME_ES1", "CHRIS/CME_ES2" };
var startDate = new DateTime(2018, 4, 1);
var endDate = new DateTime(2018, 4, 20);
// delete temp files
foreach (var ticker in tickers)
{
var fileName = TestableCustomFuture.GetLocalFileName(ticker, "test");
File.Delete(fileName);
}
var algorithm = new QCAlgorithm();
CreateDataFeed(algorithm.Settings);
var dataManager = new DataManagerStub(algorithm, _feed);
algorithm.SubscriptionManager.SetDataManager(dataManager);
var symbols = tickers.Select(ticker => algorithm.AddData<TestableCustomFuture>(ticker, Resolution.Daily).Symbol).ToList();
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(startDate);
var dataPointsEmitted = 0;
RunLiveDataFeed(algorithm, startDate, symbols, timeProvider, dataManager);
using var cancellationTokenSource = new CancellationTokenSource();
var lastFileWriteDate = DateTime.MinValue;
// create a timer to advance time much faster than realtime and to simulate live Quandl data file updates
var timerInterval = TimeSpan.FromMilliseconds(20);
var timer = Ref.Create<Timer>(null);
timer.Value = new Timer(state =>
{
try
{
var currentTime = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
if (currentTime.Date > endDate.Date)
{
Log.Trace($"Total data points emitted: {dataPointsEmitted.ToStringInvariant()}");
_feed.Exit();
cancellationTokenSource.Cancel();
return;
}
if (currentTime.Date > lastFileWriteDate.Date)
{
foreach (var ticker in tickers)
{
var source = TestableCustomFuture.GetLocalFileName(ticker, "csv");
// write new local file including only rows up to current date
var outputFileName = TestableCustomFuture.GetLocalFileName(ticker, "test");
var sb = new StringBuilder();
{
using (var reader = new StreamReader(source))
{
var firstLine = true;
string line;
while ((line = reader.ReadLine()) != null)
{
if (firstLine)
{
sb.AppendLine(line);
firstLine = false;
continue;
}
var csv = line.Split(',');
var time = Parse.DateTimeExact(csv[0], "yyyy-MM-dd");
if (time.Date >= currentTime.Date)
break;
sb.AppendLine(line);
}
}
}
if (currentTime.Date.DayOfWeek != DayOfWeek.Saturday && currentTime.Date.DayOfWeek != DayOfWeek.Sunday)
{
var fileContent = sb.ToString();
try
{
File.WriteAllText(outputFileName, fileContent);
}
catch (IOException)
{
Log.Error("IOException: will sleep 200ms and retry once more");
// lets sleep 200ms and retry once more, consumer could be reading the file
// this exception happens in travis intermittently, GH issue 3273
Thread.Sleep(200);
File.WriteAllText(outputFileName, fileContent);
}
Log.Trace($"Time:{currentTime} - Ticker:{ticker} - Files written:{++_countFilesWritten}");
}
}
lastFileWriteDate = currentTime;
}
// 30 minutes is the check interval for daily remote files, so we choose a smaller one to advance time
timeProvider.Advance(TimeSpan.FromMinutes(20));
//Log.Trace($"Time advanced to: {timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork)}");
// restart the timer
timer.Value.Change(timerInterval.Milliseconds, Timeout.Infinite);
}
catch (Exception exception)
{
Log.Error(exception);
_feed.Exit();
cancellationTokenSource.Cancel();
}
}, null, timerInterval.Milliseconds, Timeout.Infinite);
try
{
foreach (var timeSlice in _synchronizer.StreamData(cancellationTokenSource.Token))
{
foreach (var dataPoint in timeSlice.Slice.Values)
{
Log.Trace($"Data point emitted at {timeSlice.Slice.Time.ToStringInvariant()}: " +
$"{dataPoint.Symbol.Value} {dataPoint.Value.ToStringInvariant()} " +
$"{dataPoint.EndTime.ToStringInvariant()}"
);
dataPointsEmitted++;
}
}
}
catch (Exception exception)
{
Log.Trace($"Error: {exception}");
}
timer.Value.Dispose();
dataManager.RemoveAllSubscriptions();
Assert.AreEqual(14 * tickers.Length, dataPointsEmitted);
}
[Test]
public void RemoteDataDoesNotIncreaseNumberOfSlices()
{
using var api = new Api.Api();
RemoteFileSubscriptionStreamReader.SetDownloadProvider(api);
var startDate = new DateTime(2017, 4, 2);
var endDate = new DateTime(2017, 4, 23);
var algorithm = new QCAlgorithm();
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(startDate);
var dataQueueHandler = new FuncDataQueueHandler(fdqh =>
{
var time = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
var tick = new Tick(time, Symbols.SPY, 1.3m, 1.2m, 1.3m)
{
TickType = TickType.Trade
};
var tick2 = new Tick(time, Symbols.AAPL, 1.3m, 1.2m, 1.3m)
{
TickType = TickType.Trade
};
return new[] { tick, tick2 };
}, timeProvider, algorithm.Settings);
CreateDataFeed(algorithm.Settings, dataQueueHandler);
var dataManager = new DataManagerStub(algorithm, _feed);
algorithm.SubscriptionManager.SetDataManager(dataManager);
var symbols = new List<Symbol>
{
algorithm.AddData<TestableRemoteCustomData>("FB", Resolution.Daily).Symbol,
algorithm.AddData<TestableRemoteCustomData>("IBM", Resolution.Daily).Symbol,
algorithm.AddEquity("SPY", Resolution.Daily).Symbol,
algorithm.AddEquity("AAPL", Resolution.Daily).Symbol
};
using var cancellationTokenSource = new CancellationTokenSource();
var dataPointsEmitted = 0;
var slicesEmitted = 0;
RunLiveDataFeed(algorithm, startDate, symbols, timeProvider, dataManager);
Thread.Sleep(5000); // Give remote sources a handicap, so the data is available in time
// create a timer to advance time much faster than realtime and to simulate live Quandl data file updates
var timerInterval = TimeSpan.FromMilliseconds(100);
var timer = Ref.Create<Timer>(null);
timer.Value = new Timer(state =>
{
// stop the timer to prevent reentrancy
timer.Value.Change(Timeout.Infinite, Timeout.Infinite);
var currentTime = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
if (currentTime.Date > endDate.Date)
{
_feed.Exit();
cancellationTokenSource.Cancel();
return;
}
timeProvider.Advance(TimeSpan.FromHours(3));
// restart the timer
timer.Value.Change(timerInterval, timerInterval);
}, null, TimeSpan.FromSeconds(2), timerInterval);
try
{
foreach (var timeSlice in _synchronizer.StreamData(cancellationTokenSource.Token))
{
if (timeSlice.Slice.HasData)
{
slicesEmitted++;
dataPointsEmitted += timeSlice.Slice.Values.Count;
if (timeSlice.Time.ConvertFromUtc(TimeZones.NewYork).Hour == 0)
{
Assert.IsTrue(timeSlice.Slice.Values.Any(x => x.Symbol == symbols[0]), $"Slice doesn't contain {symbols[0]}");
Assert.IsTrue(timeSlice.Slice.Values.Any(x => x.Symbol == symbols[1]), $"Slice doesn't contain {symbols[1]}");
}
else
{
Assert.IsTrue(timeSlice.Slice.Values.Any(x => x.Symbol == symbols[2]), $"Slice doesn't contain {symbols[2]}");
Assert.IsTrue(timeSlice.Slice.Values.Any(x => x.Symbol == symbols[3]), $"Slice doesn't contain {symbols[3]}");
}
}
}
}
catch (Exception exception)
{
Log.Trace($"Error: {exception}");
}
timer.Value.Dispose();
dataManager.RemoveAllSubscriptions();
dataQueueHandler.DisposeSafely();
// custom data arrives midnight, daily data 4pm
Assert.AreEqual(14 * 2, slicesEmitted);
Assert.AreEqual(14 * symbols.Count, dataPointsEmitted);
}
[Test]
public void LiveDataFeedSourcesDataFromObjectStoreSort()
{
var dataPointsEmitted = 0;
var slicesEmitted = 0;
using var cancellationTokenSource = new CancellationTokenSource();
var mockCustomData = new string[]
{
"2017-04-28,173.82",
"2017-04-27,173.52",
"2017-04-26,174.73",
"2017-04-25,173.47",
"2017-04-24,172.08",
"2017-04-21,172.53",
"2017-04-20,170.65",
"2017-04-19,171.04",
"2017-04-18,169.92",
"2017-04-17,169.75",
"2017-04-13,170.79",
"2017-04-12,161.76",
"2017-04-11,161.32",
"2017-04-10,162.05",
"2017-04-07,161.29",
"2017-04-06,161.78",
"2017-04-05,160.53",
"2017-04-04,160.29",
"2017-04-03,160.53"
};
var objectText = string.Join("\n", mockCustomData);
var (dataManager, timer, _) = RunLiveDataFeedWithObjectStore(
new DateTime(2017, 4, 2),
new DateTime(2017, 4, 28),
cancellationTokenSource, "CustomData/CustomIBM", objectText,
(algorithm) => algorithm.AddData<CustomDataSort>("IBM", Resolution.Daily).Symbol);
var baseData = new List<BaseData>();
foreach (var timeSlice in _synchronizer.StreamData(cancellationTokenSource.Token))
{
if (timeSlice.Slice.HasData)
{
foreach (var customData in timeSlice.CustomData)
{
foreach (var data in customData.Data)
{
baseData.Add(data);
}
}
slicesEmitted++;
dataPointsEmitted += timeSlice.Slice.Values.Count;
}
}
timer.Dispose();
dataManager.RemoveAllSubscriptions();
Assert.AreEqual(mockCustomData.Length, slicesEmitted);
Assert.AreEqual(slicesEmitted, dataPointsEmitted);
for (int i = 0; i < baseData.Count - 1; i++)
{
if (baseData[i].EndTime > baseData[i + 1].EndTime)
{
Assert.Fail($"Order failure: {baseData[i].EndTime} > {baseData[i + 1].EndTime} at index {i}.");
}
}
}
[Test]
public void LiveDataFeedSourcesDataFromObjectStore()
{
using var cancellationTokenSource = new CancellationTokenSource();
var dataPointsEmitted = 0;
var slicesEmitted = 0;
var mockCustomData = new string[]
{
"2017-04-03,173.82",
"2017-04-04,173.52",
"2017-04-05,174.72",
"2017-04-06,173.47",
"2017-04-07,172.08",
"2017-04-10,172.53",
"2017-04-11,170.65",
"2017-04-12,171.04",
"2017-04-13,169.92",
"2017-04-17,169.75",
"2017-04-18,170.79",
"2017-04-19,161.76",
"2017-04-20,161.32",
"2017-04-21,162.05",
"2017-04-24,161.29",
"2017-04-25,161.78",
"2017-04-26,160.53",
"2017-04-27,160.29",
"2017-04-28,160.5"
};
var objectText = string.Join("\n", mockCustomData);
var (dataManager, timer, symbol) = RunLiveDataFeedWithObjectStore(new DateTime(2017, 4, 2), new DateTime(2017, 4, 23), cancellationTokenSource, "CustomData/CustomIBM", objectText,
algorithm => algorithm.AddData<TestableObjectStoreCustomData>("IBM", Resolution.Daily).Symbol);
foreach (var timeSlice in _synchronizer.StreamData(cancellationTokenSource.Token))
{
if (timeSlice.Slice.HasData)
{
slicesEmitted++;
dataPointsEmitted += timeSlice.Slice.Values.Count;
Assert.AreEqual(symbol, timeSlice.Slice.Values.Single().Symbol, $"Slice doesn't contain {symbol}");
}
}
timer.Dispose();
dataManager.RemoveAllSubscriptions();
Assert.AreEqual(14, slicesEmitted);
Assert.AreEqual(slicesEmitted, dataPointsEmitted);
}
/// <summary>
/// Runs the live data feed with an object store, simulating real-time updates from stored data.
/// This method initializes the data feed, sets up a manual time provider, and stores data in the object store.
/// </summary>
/// <param name="startDate">The start date for the live data feed.</param>
/// <param name="endDate">The end date for the live data feed.</param>
/// <param name="cancellationTokenSource">A cancellation token to stop the live feed once the end date is reached.</param>
/// <param name="objectPath">The object store path where data is saved.</param>
/// <param name="objectText">The content (data) to be saved in the object store at the specified path.</param>
/// <param name="AddData">A function that adds data to the algorithm and returns a symbol representing the data.</param>
/// <returns>
/// A tuple containing:
/// <list type="bullet">
/// <item><description><see cref="DataManagerStub"/> - The data manager handling data subscriptions for the algorithm.</description></item>
/// <item><description><see cref="Timer"/> - The timer used to advance time and simulate live updates.</description></item>
/// <item><description><see cref="Symbol"/> - The symbol of the added data in the algorithm.</description></item>
/// </list>
/// </returns>
private (DataManagerStub, Timer, Symbol) RunLiveDataFeedWithObjectStore(DateTime startDate, DateTime endDate, CancellationTokenSource cancellationTokenSource,
string objectPath, string objectText, Func<QCAlgorithm, Symbol> AddData)
{
using var api = new Api.Api();
RemoteFileSubscriptionStreamReader.SetDownloadProvider(api);
var algorithm = new QCAlgorithm();
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(startDate);
CreateDataFeed(algorithm.Settings);
var dataManager = new DataManagerStub(algorithm, _feed);
algorithm.SubscriptionManager.SetDataManager(dataManager);
using var store = new LocalObjectStore();
store.Initialize(0, 0, "", new Controls(), AlgorithmMode.Backtesting);
algorithm.SetObjectStore(store);
algorithm.ObjectStore.Save(objectPath, objectText);
var symbol = AddData(algorithm);
RunLiveDataFeed(algorithm, startDate, new[] { symbol }, timeProvider, dataManager);
// create a timer to advance time much faster than realtime and to simulate live Quandl data file updates
var timerInterval = TimeSpan.FromMilliseconds(10);
var timer = Ref.Create<Timer>(null);
timer.Value = new Timer(state =>
{
// stop the timer to prevent reentrancy
timer.Value.Change(Timeout.Infinite, Timeout.Infinite);
var currentTime = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
if (currentTime.Date > endDate.Date)
{
_feed.Exit();
cancellationTokenSource.Cancel();
return;
}
timeProvider.Advance(TimeSpan.FromHours(3));
// restart the timer
timer.Value.Change(timerInterval, timerInterval);
}, null, TimeSpan.FromMilliseconds(500), timerInterval);
return (dataManager, timer.Value, symbol);
}
private void CreateDataFeed(IAlgorithmSettings settings,
FuncDataQueueHandler funcDataQueueHandler = null)
{
_feed = new TestableLiveTradingDataFeed(settings, funcDataQueueHandler ?? new FuncDataQueueHandler(x => Enumerable.Empty<BaseData>(), RealTimeProvider.Instance, new AlgorithmSettings()));
}
private void RunLiveDataFeed(
IAlgorithm algorithm,
DateTime startDate,
IEnumerable<Symbol> symbols,
ITimeProvider timeProvider,
DataManager dataManager)
{
_synchronizer = new TestableLiveSynchronizer(timeProvider);
_synchronizer.Initialize(algorithm, dataManager, new());
_feed.Initialize(algorithm, new LiveNodePacket(), new BacktestingResultHandler(),
TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider, dataManager, _synchronizer, new DataChannelProvider());
foreach (var symbol in symbols)
{
var config = algorithm.Securities[symbol].SubscriptionDataConfig;
var request = new SubscriptionRequest(false, null, algorithm.Securities[symbol], config, startDate, Time.EndOfTime);
dataManager.AddSubscription(request);
}
}
private static int _countFilesWritten;
public class TestableCustomFuture : BaseData
{
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
// use local file instead of remote file
var source = GetLocalFileName(config.Symbol.Value, "test");
return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile);
}
public static string GetLocalFileName(string ticker, string fileExtension)
{
return $"./TestData/custom_future_{ticker.Replace("/", "_").ToLowerInvariant()}.{fileExtension}";
}
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var csv = line.Split(',');
var data = new TestableCustomFuture
{
Symbol = config.Symbol,
Time = DateTime.ParseExact(csv[0], "yyyy-MM-dd", CultureInfo.InvariantCulture),
Value = csv[6].ToDecimal()
};
return data;
}
}
public class TestableRemoteCustomData : BaseData
{
public override DateTime EndTime
{
get { return Time + Period; }
set { Time = value - Period; }
}
/// <summary>
/// Gets a time span of one day
/// </summary>
public TimeSpan Period
{
get { return QuantConnect.Time.OneDay; }
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var source = $"https://www.dl.dropboxusercontent.com/s/1w6x1kfrlvx3d2v/CustomIBM.csv?dl=0";
return new SubscriptionDataSource(source, SubscriptionTransportMedium.RemoteFile);
}
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var csv = line.Split(',');
var data = new TestableRemoteCustomData
{
Symbol = config.Symbol,
Time = DateTime.ParseExact(csv[0], "yyyy-MM-dd", CultureInfo.InvariantCulture),
Value = csv[1].ToDecimal()
};
return data;
}
}
public class TestableObjectStoreCustomData : TestableRemoteCustomData
{
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("CustomData/CustomIBM", SubscriptionTransportMedium.ObjectStore, FileFormat.Csv);
}
}
private class CustomDataSort : TestableRemoteCustomData
{
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("CustomData/CustomIBM", SubscriptionTransportMedium.ObjectStore, FileFormat.Csv) { Sort = true };
}
}
}
}
@@ -0,0 +1,48 @@
/*
* 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 QuantConnect.Data;
namespace QuantConnect.Tests.Engine.DataFeeds
{
/// <summary>
/// Custom data type that uses a remote file download
/// </summary>
public class CustomMockedFileBaseData : BaseData
{
private int _incrementsToAdd;
public static DateTime StartDate;
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var start = StartDate.AddTicks(config.Increment.Ticks * _incrementsToAdd++).ConvertFromUtc(config.DataTimeZone);
return new CustomMockedFileBaseData
{
Symbol = config.Symbol,
Time = start,
EndTime = start.Add(config.Increment),
Value = 10
};
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
// using an already existing file, the file it self and its contents don't really matter. The reader will mock the values
return new SubscriptionDataSource("./TestData/spy_with_ichimoku.csv", SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
}
}
}
@@ -0,0 +1,84 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2026 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 QuantConnect.Lean.Engine.DataFeeds.DataDownloader;
using System;
namespace QuantConnect.Tests.Engine.DataFeeds.DataDownloader
{
[TestFixture]
public class CanonicalDataDownloaderDecoratorTests
{
[TestCase("2026/03/20", "2025/03/01", "2025/12/31", "2025/03/01", "2025/12/31")]
[TestCase("2026/03/20", "2025/03/01", "2026/03/25", "2025/03/01", "2026/03/21")]
[TestCase("2026/03/20", "2020/01/01", "2026/03/25", "2024/03/20", "2026/03/21")]
[TestCase("2022/02/28", "2018/01/01", "2020/12/31", "2020/02/28", "2020/12/31")]
[TestCase("2022/02/28", "2018/01/01", "2019/01/01", default, default)]
[TestCase("2022/02/28", "2022/01/01", "2019/01/01", default, default)]
[TestCase("2022/02/28", "2022/03/01", "2022/03/02", default, default)]
[TestCase("2022/02/28", "2022/02/01 9:30", "2022/02/10 16:00", "2022/02/01 9:30", "2022/02/10 16:00")]
[TestCase("2022/02/28", "2022/02/01 9:30", "2022/02/01 9:30", "2022/02/01 9:30", "2022/02/01 9:30")]
[TestCase("2022/02/28", "2018/02/01 9:30", "2022/03/01 9:30", "2020/02/28", "2022/03/01")]
[TestCase("2022/02/28", "2018/02/01 9:30", "2018/03/01 9:30", default, default)]
[TestCase("2022/02/28", "1997/12/31", "2026/02/20", "2020/02/28", "2022/03/01")]
public void ShouldReceiveAdjustedDateFutureContract(DateTime expiry, DateTime startUtc, DateTime endUtc, DateTime expectedStart, DateTime expectedEnd)
{
var future = Symbol.CreateFuture(Securities.Futures.Indices.SP500EMini, Market.CME, expiry);
CanonicalDataDownloaderDecorator.TryAdjustDateRangeForContract(future, startUtc, endUtc, out var start, out var end);
Assert.AreEqual(expectedStart, start);
Assert.AreEqual(expectedEnd, end);
}
[TestCase("2026/02/20", "2025/03/01", "2025/12/31", "2025/03/01", "2025/12/31")]
[TestCase("2026/02/20", "2025/02/18", "2026/03/25", "2025/02/20", "2026/02/21")]
[TestCase("2026/02/20", "2020/01/01", "2026/03/25", "2025/02/20", "2026/02/21")]
[TestCase("2026/02/20", "1997/12/31", "2026/02/20", "2025/02/20", "2026/02/21")]
[TestCase("2020/02/20", "2020/01/01", "2021/03/25", "2020/01/01", "2020/02/21")]
[TestCase("2020/02/20", "2019/01/01", "2021/03/25", "2019/02/20", "2020/02/21")]
[TestCase("2020/02/20", "2018/01/01", "2020/02/25", "2019/02/20", "2020/02/21")]
[TestCase("2020/02/20", "2019/08/10", "2020/02/20", "2019/08/10", "2020/02/21")]
[TestCase("2020/02/20", "2018/01/01", "2018/02/01", default, default)]
[TestCase("2020/02/20", "2021/01/01", "2022/02/01", default, default)]
[TestCase("2020/02/20", "2020/01/01 9:30", "2020/02/20 16:00", "2020/01/01 9:30", "2020/02/21")]
[TestCase("2020/02/20", "2019/01/01 9:30", "2020/02/20 16:00", "2019/02/20", "2020/02/21")]
[TestCase("2020/02/20", "2019/01/01 9:30", "2019/02/01 9:30", default, default)]
[TestCase("2020/02/20", "2019/02/20 9:30", "2020/02/20", "2019/02/20 9:30", "2020/02/21")]
[TestCase("2020/02/20", "2020/02/20 9:30", "2020/02/21", default, default)]
public void ShouldReceiveAdjustedDateOptionContract(DateTime expiry, DateTime startUtc, DateTime endUtc, DateTime expectedStart, DateTime expectedEnd)
{
var aapl = Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
var option = Symbol.CreateOption(aapl, aapl.ID.Market, aapl.SecurityType.DefaultOptionStyle(), OptionRight.Call, 260m, expiry);
CanonicalDataDownloaderDecorator.TryAdjustDateRangeForContract(option, startUtc, endUtc, out var start, out var end);
Assert.AreEqual(expectedStart, start);
Assert.AreEqual(expectedEnd, end);
}
[Test]
public void ShouldNotAdjustNonOptionOrFutureContract()
{
var aapl = Symbol.Create("AAPL", SecurityType.Equity, Market.USA);
var res = CanonicalDataDownloaderDecorator.TryAdjustDateRangeForContract(aapl, new DateTime(2025, 03, 01), new DateTime(2025, 12, 31), out var start, out var end);
Assert.IsFalse(res);
Assert.AreEqual(default(DateTime), start);
Assert.AreEqual(default(DateTime), end);
}
}
}
@@ -0,0 +1,67 @@
/*
* 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.IO;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds.DataDownloader;
namespace QuantConnect.Tests.Engine.DataFeeds.DataDownloader
{
[TestFixture]
public class DataDownloaderSelectorTests
{
private readonly IMapFileProvider _mapFileProvider = Composer.Instance.GetExportedValueByTypeName<IMapFileProvider>("LocalDiskMapFileProvider");
[Test]
public void GetDataDownloaderReturnsBaseDownloaderForNonLeanType()
{
var baseDownloader = new TestDataDownloader();
using var selector = new DataDownloaderSelector(baseDownloader, _mapFileProvider, new TestDataProvider());
var actualBaseDownloader = selector.GetDataDownloader(typeof(object));
Assert.AreSame(baseDownloader, actualBaseDownloader);
var actualCanonicalDownloader = selector.GetDataDownloader(typeof(TradeBar));
Assert.AreEqual(typeof(CanonicalDataDownloaderDecorator), actualCanonicalDownloader.GetType());
}
private class TestDataDownloader : IDataDownloader
{
public IEnumerable<BaseData> Get(DataDownloaderGetParameters dataDownloaderGetParameters)
{
throw new NotImplementedException();
}
}
private class TestDataProvider : IDataProvider
{
public event EventHandler<DataProviderNewDataRequestEventArgs> NewDataRequest;
public Stream Fetch(string key)
{
throw new NotImplementedException();
}
}
}
}
+112
View File
@@ -0,0 +1,112 @@
/*
* 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 QuantConnect.Algorithm;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds
{
internal class DataManagerStub : DataManager
{
public ISecurityService SecurityService { get; }
public IAlgorithm Algorithm { get; }
public IDataFeed DataFeed { get; }
public DataManagerStub()
: this(new QCAlgorithm())
{
}
public DataManagerStub(ITimeKeeper timeKeeper)
: this(new QCAlgorithm(), timeKeeper)
{
}
public DataManagerStub(IAlgorithm algorithm, IDataFeed dataFeed, bool liveMode = false)
: this(dataFeed, algorithm, new TimeKeeper(DateTime.UtcNow, TimeZones.NewYork), liveMode)
{
}
public DataManagerStub(IAlgorithm algorithm)
: this(new NullDataFeed(), algorithm, new TimeKeeper(DateTime.UtcNow, TimeZones.NewYork))
{
}
public DataManagerStub(IDataFeed dataFeed, IAlgorithm algorithm)
: this(dataFeed, algorithm, new TimeKeeper(DateTime.UtcNow, TimeZones.NewYork))
{
}
public DataManagerStub(IAlgorithm algorithm, ITimeKeeper timeKeeper)
: this(new NullDataFeed(), algorithm, timeKeeper)
{
}
public DataManagerStub(IDataFeed dataFeed, IAlgorithm algorithm, ITimeKeeper timeKeeper, bool liveMode = false)
: this(dataFeed, algorithm, timeKeeper, MarketHoursDatabase.FromDataFolder(), SymbolPropertiesDatabase.FromDataFolder(), liveMode)
{
}
public DataManagerStub(IDataFeed dataFeed, IAlgorithm algorithm, ITimeKeeper timeKeeper, MarketHoursDatabase marketHoursDatabase, SymbolPropertiesDatabase symbolPropertiesDatabase, bool liveMode = false)
: this(dataFeed, algorithm, timeKeeper, marketHoursDatabase,
new SecurityService(algorithm.Portfolio.CashBook,
marketHoursDatabase,
symbolPropertiesDatabase,
algorithm,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCacheProvider(algorithm.Portfolio),
algorithm: algorithm),
liveMode)
{
}
public DataManagerStub(IDataFeed dataFeed, IAlgorithm algorithm, ITimeKeeper timeKeeper, MarketHoursDatabase marketHoursDatabase, SecurityService securityService, bool liveMode = false)
: this(dataFeed,
algorithm,
timeKeeper,
marketHoursDatabase,
securityService,
new DataPermissionManager(),
liveMode)
{
}
public DataManagerStub(IDataFeed dataFeed, IAlgorithm algorithm, ITimeKeeper timeKeeper, MarketHoursDatabase marketHoursDatabase, SecurityService securityService, DataPermissionManager dataPermissionManager, bool liveMode = false)
: base(dataFeed,
new UniverseSelection(algorithm, securityService, dataPermissionManager, TestGlobals.DataProvider),
algorithm,
timeKeeper,
marketHoursDatabase,
liveMode,
RegisteredSecurityDataTypesProvider.Null,
dataPermissionManager)
{
SecurityService = securityService;
algorithm.Securities.SetSecurityService(securityService);
Algorithm = algorithm;
DataFeed = dataFeed;
}
}
}
+318
View File
@@ -0,0 +1,318 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using NUnit.Framework.Internal;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Packets;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class DataManagerTests
{
private QCAlgorithm _algorithm;
private SecurityService _securityService;
[SetUp]
public void SetUp()
{
_algorithm = new AlgorithmStub();
_securityService = new SecurityService(_algorithm.Portfolio.CashBook,
MarketHoursDatabase.AlwaysOpen,
SymbolPropertiesDatabase.FromDataFolder(),
_algorithm,
new RegisteredSecurityDataTypesProvider(),
new SecurityCacheProvider(_algorithm.Portfolio),
algorithm: _algorithm);
}
[Test]
public void ReturnsExistingConfig()
{
var dataPermissionManager = new DataPermissionManager();
var dataManager = new DataManager(new NullDataFeed(),
new UniverseSelection(_algorithm,
_securityService,
dataPermissionManager,
TestGlobals.DataProvider),
_algorithm,
_algorithm.TimeKeeper,
MarketHoursDatabase.AlwaysOpen,
false,
new RegisteredSecurityDataTypesProvider(),
dataPermissionManager);
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false);
var sameConfig = dataManager.SubscriptionManagerGetOrAdd(config);
Assert.IsTrue(ReferenceEquals(sameConfig, config));
Assert.AreEqual(1, dataManager.GetSubscriptionDataConfigs(config.Symbol).Count);
var otherInstance = new SubscriptionDataConfig(config.Type,
config.Symbol,
config.Resolution,
config.DataTimeZone,
config.ExchangeTimeZone,
config.FillDataForward,
config.ExtendedMarketHours,
config.IsInternalFeed);
sameConfig = dataManager.SubscriptionManagerGetOrAdd(otherInstance);
Assert.IsTrue(ReferenceEquals(sameConfig, config));
Assert.AreEqual(1, dataManager.GetSubscriptionDataConfigs(config.Symbol).Count);
dataManager.RemoveAllSubscriptions();
}
[Test]
public void RemovesExistingConfig()
{
var dataPermissionManager = new DataPermissionManager();
var dataFeed = new TestDataFeed();
var dataManager = new DataManager(dataFeed,
new UniverseSelection(_algorithm,
_securityService,
dataPermissionManager,
TestGlobals.DataProvider),
_algorithm,
_algorithm.TimeKeeper,
MarketHoursDatabase.AlwaysOpen,
false,
new RegisteredSecurityDataTypesProvider(),
dataPermissionManager);
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false);
Assert.IsTrue(ReferenceEquals(dataManager.SubscriptionManagerGetOrAdd(config), config));
Assert.AreEqual(1, dataManager.GetSubscriptionDataConfigs(config.Symbol).Count);
// we didn't add any subscription yet
Assert.IsFalse(dataManager.RemoveSubscription(config));
var request = new SubscriptionRequest(false,
null,
new Security(Symbols.SPY,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
new Cash(Currencies.USD, 1, 1),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
new RegisteredSecurityDataTypesProvider(),
new SecurityCache()),
config,
new DateTime(2019, 1, 1),
new DateTime(2019, 1, 1));
using var enquableEnumerator = new EnqueueableEnumerator<SubscriptionData>();
dataFeed.Subscription = new Subscription(request,
enquableEnumerator,
null);
Assert.IsTrue(dataManager.AddSubscription(request));
Assert.IsTrue(dataManager.RemoveSubscription(config));
Assert.AreEqual(0, dataManager.GetSubscriptionDataConfigs(config.Symbol).Count);
dataManager.RemoveAllSubscriptions();
}
[Test]
// reproduces GH issue 3877
public void ConfigurationForAddedSubscriptionIsAlwaysPresent()
{
var dataPermissionManager = new DataPermissionManager();
var dataFeed = new TestDataFeed();
var dataManager = new DataManager(dataFeed,
new UniverseSelection(_algorithm,
_securityService,
dataPermissionManager,
TestGlobals.DataProvider),
_algorithm,
_algorithm.TimeKeeper,
MarketHoursDatabase.AlwaysOpen,
false,
new RegisteredSecurityDataTypesProvider(),
dataPermissionManager);
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false);
// Universe A: adds the config
dataManager.SubscriptionManagerGetOrAdd(config);
var request = new SubscriptionRequest(false,
null,
new Security(Symbols.SPY,
SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc),
new Cash(Currencies.USD, 1, 1),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
new RegisteredSecurityDataTypesProvider(),
new SecurityCache()),
config,
new DateTime(2019, 1, 1),
new DateTime(2019, 1, 1));
using var enquableEnumerator = new EnqueueableEnumerator<SubscriptionData>();
dataFeed.Subscription = new Subscription(request,
enquableEnumerator,
null);
// Universe A: adds the subscription
dataManager.AddSubscription(request);
// Universe B: adds the config
dataManager.SubscriptionManagerGetOrAdd(config);
// Universe A: removes the subscription
Assert.IsTrue(dataManager.RemoveSubscription(config));
Assert.AreEqual(0, dataManager.GetSubscriptionDataConfigs(config.Symbol).Count);
// Universe B: adds the subscription
Assert.IsTrue(dataManager.AddSubscription(request));
// the config should be present
Assert.AreEqual(1, dataManager.GetSubscriptionDataConfigs(config.Symbol).Count);
dataManager.RemoveAllSubscriptions();
}
[Test]
public void ScaledRawNormalizationModeIsNotAllowed()
{
var dataPermissionManager = new DataPermissionManager();
var dataFeed = new TestDataFeed();
var dataManager = new DataManager(dataFeed,
new UniverseSelection(_algorithm,
_securityService,
dataPermissionManager,
TestGlobals.DataProvider),
_algorithm,
_algorithm.TimeKeeper,
MarketHoursDatabase.AlwaysOpen,
false,
new RegisteredSecurityDataTypesProvider(),
dataPermissionManager);
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
false,
false,
false,
dataNormalizationMode: DataNormalizationMode.ScaledRaw);
using var universe = new TestUniverse(
config,
new UniverseSettings(Resolution.Daily, 1, false, false, TimeSpan.FromDays(365)));
var security = new Equity(
config.Symbol,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new Cash(Currencies.USD, 1, 1),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
new RegisteredSecurityDataTypesProvider(),
new SecurityCache());
var subscriptionRequest = new SubscriptionRequest(
false,
universe,
security,
config,
new DateTime(2014, 10, 10),
new DateTime(2015, 10, 10));
var exception = Assert.Throws<InvalidOperationException>(() =>
{
dataManager.AddSubscription(subscriptionRequest);
});
Assert.That(exception.Message, Does.Contain(nameof(DataNormalizationMode.ScaledRaw)));
}
private class TestDataFeed : IDataFeed
{
public Subscription Subscription { get; set; }
public bool IsActive { get; }
public void Initialize(IAlgorithm algorithm, AlgorithmNodePacket job, IResultHandler resultHandler,
IMapFileProvider mapFileProvider, IFactorFileProvider factorFileProvider, IDataProvider dataProvider,
IDataFeedSubscriptionManager subscriptionManager, IDataFeedTimeProvider dataFeedTimeProvider,
IDataChannelProvider channelProvider)
{
}
public Subscription CreateSubscription(SubscriptionRequest request)
{
return Subscription;
}
public void RemoveSubscription(Subscription subscription)
{
}
public void Exit()
{
}
}
private class TestUniverse : Universe
{
public TestUniverse(SubscriptionDataConfig config, UniverseSettings universeSettings)
: base(config)
{
UniverseSettings = universeSettings;
}
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
throw new NotImplementedException();
}
}
}
}
@@ -0,0 +1,115 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Algorithm.CSharp;
using QuantConnect.Configuration;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Packets;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class DataPermissionManagerTests
{
[SetUp]
public void SetUp()
{
Config.Set("data-permission-manager", "TestDataPermissionManager");
TestInvalidConfigurationAlgorithm.Count = 0;
}
[TearDown]
public void TearDown()
{
Config.Reset();
}
[Test]
public void InvalidConfigurationAddSecurity()
{
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters(nameof(BasicTemplateDailyAlgorithm),
new Dictionary<string, string>(),
Language.CSharp,
// will throw on initialization
AlgorithmStatus.Running);
var result = AlgorithmRunner.RunLocalBacktest(parameter.Algorithm,
parameter.Statistics,
parameter.Language,
parameter.ExpectedFinalStatus);
// algorithm was never set
Assert.IsEmpty(result.AlgorithmManager.AlgorithmId);
}
[Test]
public void InvalidConfigurationHistoryRequest()
{
var parameter = new RegressionTests.AlgorithmStatisticsTestParameters(nameof(TestInvalidConfigurationAlgorithm),
new Dictionary<string, string>(),
Language.CSharp,
// will throw on initialization
AlgorithmStatus.Running);
var result = AlgorithmRunner.RunLocalBacktest(parameter.Algorithm,
parameter.Statistics,
parameter.Language,
parameter.ExpectedFinalStatus,
setupHandler: "TestInvalidConfigurationSetupHandler");
// algorithm was never set
Assert.IsEmpty(result.AlgorithmManager.AlgorithmId);
// let's assert initialize was called by the history call failed
Assert.AreEqual(1, TestInvalidConfigurationAlgorithm.Count);
}
public class TestDataPermissionManager : DataPermissionManager
{
public override void AssertConfiguration(SubscriptionDataConfig subscriptionDataConfig, DateTime startTimeLocal, DateTime endTimeLocal)
{
throw new InvalidOperationException("Invalid configuration");
}
}
public class TestInvalidConfigurationAlgorithm : BasicTemplateDailyAlgorithm
{
public static int Count;
public override void Initialize()
{
Count++;
#pragma warning disable CS0618
History("SPY", 1, Resolution.Tick).ToList();
#pragma warning restore CS0618
Count++;
}
}
public class TestInvalidConfigurationSetupHandler : AlgorithmRunner.RegressionSetupHandlerWrapper
{
public override IAlgorithm CreateAlgorithmInstance(AlgorithmNodePacket algorithmNodePacket, string assemblyPath)
{
Algorithm = new TestInvalidConfigurationAlgorithm();
return Algorithm;
}
}
}
}
@@ -0,0 +1,249 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Packets;
using QuantConnect.Queues;
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class DataQueueHandlerManagerTests
{
[TestCase("BacktestingBrokerage")]
public void GetFactoryFromDataQueueHandler(string dataQueueHandler)
{
var factory = JobQueue.GetFactoryFromDataQueueHandler(dataQueueHandler);
Assert.NotNull(factory);
}
[Test]
public void SetJob()
{
//Array IDQH
var dataHandlers = Newtonsoft.Json.JsonConvert.SerializeObject(new[] { "FakeDataQueue" });
var jobWithArrayIDQH = new LiveNodePacket
{
DataQueueHandler = dataHandlers
};
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.SetJob(jobWithArrayIDQH);
compositeDataQueueHandler.Dispose();
}
[Test]
public void SubscribeReturnsNull()
{
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
var enumerator = compositeDataQueueHandler.Subscribe(GetConfig(), (_, _) => {});
Assert.Null(enumerator);
compositeDataQueueHandler.Dispose();
}
[Test]
public void SubscribeReturnsNotNull()
{
var dataHandlers = Newtonsoft.Json.JsonConvert.SerializeObject(new[] { "FakeDataQueue" });
var job = new LiveNodePacket
{
DataQueueHandler = dataHandlers
};
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.SetJob(job);
var enumerator = compositeDataQueueHandler.Subscribe(GetConfig(), (_, _) => {});
Assert.NotNull(enumerator);
compositeDataQueueHandler.Dispose();
enumerator.Dispose();
}
[Test]
public void Unsubscribe()
{
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.Unsubscribe(GetConfig());
compositeDataQueueHandler.Dispose();
}
[Test]
public void IsNotUniverseProvider()
{
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
Assert.IsFalse(compositeDataQueueHandler.HasUniverseProvider);
Assert.Throws<NotSupportedException>(() => compositeDataQueueHandler.LookupSymbols(Symbols.ES_Future_Chain, false));
Assert.Throws<NotSupportedException>(() => compositeDataQueueHandler.CanPerformSelection());
compositeDataQueueHandler.Dispose();
}
[Test]
public void DoubleSubscribe()
{
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.SetJob(new LiveNodePacket { DataQueueHandler = "[ \"TestDataHandler\" ]" });
var dataConfig = GetConfig();
var enumerator = compositeDataQueueHandler.Subscribe(dataConfig, (_, _) => {});
Assert.DoesNotThrow(() => compositeDataQueueHandler.Subscribe(dataConfig, (_, _) => { }));
compositeDataQueueHandler.Dispose();
}
[Test]
public void SingleSubscribe()
{
TestDataHandler.UnsubscribeCounter = 0;
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.SetJob(new LiveNodePacket { DataQueueHandler = "[ \"TestDataHandler\" ]" });
var dataConfig = GetConfig();
var enumerator = compositeDataQueueHandler.Subscribe(dataConfig, (_, _) => {});
compositeDataQueueHandler.Unsubscribe(dataConfig);
compositeDataQueueHandler.Unsubscribe(dataConfig);
compositeDataQueueHandler.Unsubscribe(dataConfig);
Assert.AreEqual(1, TestDataHandler.UnsubscribeCounter);
compositeDataQueueHandler.Dispose();
}
[TestCase(false)]
[TestCase(true)]
public void MappedConfig(bool canonicalUnsubscribeFirst)
{
TestDataHandler.UnsubscribeCounter = 0;
TestDataHandler.SubscribeCounter = 0;
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.SetJob(new LiveNodePacket { DataQueueHandler = "[ \"TestDataHandler\" ]" });
var canonicalSymbol = Symbols.ES_Future_Chain.UpdateMappedSymbol(Symbols.Future_ESZ18_Dec2018.ID.ToString());
var canonicalConfig = GetConfig(canonicalSymbol);
var contractConfig = GetConfig(Symbols.Future_ESZ18_Dec2018);
var enumerator = new LiveSubscriptionEnumerator(canonicalConfig, compositeDataQueueHandler, (_, _) => {}, (_) => false);
var enumerator2 = new LiveSubscriptionEnumerator(contractConfig, compositeDataQueueHandler, (_, _) => {}, (_) => false);
var firstUnsubscribe = canonicalUnsubscribeFirst ? canonicalConfig : contractConfig;
var secondUnsubscribe = canonicalUnsubscribeFirst ? contractConfig : canonicalConfig;
Assert.AreEqual(2, TestDataHandler.SubscribeCounter);
compositeDataQueueHandler.UnsubscribeWithMapping(firstUnsubscribe);
Assert.AreEqual(1, TestDataHandler.UnsubscribeCounter);
compositeDataQueueHandler.UnsubscribeWithMapping(secondUnsubscribe);
Assert.AreEqual(2, TestDataHandler.UnsubscribeCounter);
enumerator.Dispose();
enumerator2.Dispose();
compositeDataQueueHandler.Dispose();
}
[Test]
public void HandleExplodingDataQueueHandler()
{
using var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
// first exploding
compositeDataQueueHandler.SetJob(new LiveNodePacket { DataQueueHandler = "[ \"ExplodingDataHandler\", \"TestDataHandler\" ]" });
IEnumerator<BaseData> enumerator = null;
Assert.DoesNotThrow(() =>
{
enumerator = compositeDataQueueHandler.Subscribe(GetConfig(), (_, _) => { });
});
Assert.IsNotNull(enumerator);
enumerator.Dispose();
}
[Test]
public void ExplodingDataQueueHandler()
{
using var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.SetJob(new LiveNodePacket { DataQueueHandler = "[ \"ExplodingDataHandler\" ]" });
Assert.Throws<Exception>(() =>
{
using var enumerator = compositeDataQueueHandler.Subscribe(GetConfig(), (_, _) => { });
});
}
[Test]
public void HandlesCustomData()
{
var customSymbol = Symbol.CreateBase(typeof(AlgorithmSettings), Symbols.SPY);
var config = new SubscriptionDataConfig(typeof(TradeBar), customSymbol, Resolution.Minute, TimeZones.NewYork, TimeZones.NewYork,
false, false, false, false, TickType.Trade, false);
var dataHandlers = Newtonsoft.Json.JsonConvert.SerializeObject(new[] { "FakeDataQueue" });
var job = new LiveNodePacket
{
DataQueueHandler = dataHandlers
};
var compositeDataQueueHandler = new DataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.SetJob(job);
var enumerator = compositeDataQueueHandler.Subscribe(config, (_, _) => { });
Assert.NotNull(enumerator);
compositeDataQueueHandler.Dispose();
enumerator.Dispose();
}
private static SubscriptionDataConfig GetConfig(Symbol symbol = null)
{
return new SubscriptionDataConfig(typeof(TradeBar), symbol ?? Symbols.SPY, Resolution.Minute, TimeZones.NewYork, TimeZones.NewYork,
false, false, false, false, TickType.Trade, false);
}
private class TestDataHandler : IDataQueueHandler
{
public static int SubscribeCounter { get; set; }
public static int UnsubscribeCounter { get; set; }
public void Dispose()
{
}
public virtual IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
{
SubscribeCounter++;
return Enumerable.Empty<BaseData>().GetEnumerator();
}
public void Unsubscribe(SubscriptionDataConfig dataConfig)
{
UnsubscribeCounter++;
}
public void SetJob(LiveNodePacket job)
{
}
public bool IsConnected { get; }
}
private class ExplodingDataHandler : TestDataHandler
{
public override IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
{
throw new Exception("ExplodingDataHandler exception!");
}
}
}
}
@@ -0,0 +1,360 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class DateChangeTimeKeeperTests
{
private static DateTime _start = new DateTime(2024, 10, 01);
private static DateTime _end = new DateTime(2024, 10, 11);
private static TestCaseData[] TimeZonesTestCases => new TestCaseData[]
{
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-1))),
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-2))),
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-3))),
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(-4))),
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(1))),
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(2))),
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(3))),
new(TimeZones.Utc, DateTimeZone.ForOffset(Offset.FromHours(4))),
new(TimeZones.Utc, TimeZones.Utc),
new(TimeZones.NewYork, TimeZones.NewYork),
new(TimeZones.HongKong, TimeZones.HongKong),
};
[TestCaseSource(nameof(TimeZonesTestCases))]
public void EmitsFirstExchangeDateEvent(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
{
using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, true, null, out var tradableDates, out var config, out var exchangeHours);
var emittedExchangeDates = new List<DateTime>();
void HandleNewTradableDate(object sender, DateTime date)
{
emittedExchangeDates.Add(date);
}
timeKeeper.NewExchangeDate += HandleNewTradableDate;
var firstDataDate = tradableDates[0];
var firstDataDateInExchangeTimeZone = firstDataDate.ConvertTo(dataTimeZone, exchangeTimeZone);
var firstExchangeDateIsBeforeFirstDataDate = firstDataDateInExchangeTimeZone < firstDataDate;
try
{
Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
Assert.AreEqual(1, emittedExchangeDates.Count);
if (firstExchangeDateIsBeforeFirstDataDate)
{
// The first exchange date is the day before the first data date when the exchange is behind the data
var expectedFirstExchangeDate = firstDataDate.AddDays(-1);
Assert.AreEqual(expectedFirstExchangeDate, emittedExchangeDates[0]);
Assert.AreEqual(expectedFirstExchangeDate.ConvertTo(exchangeTimeZone, dataTimeZone), timeKeeper.DataTime);
}
else
{
// If exchange is ahead of the data, even though technically at the first data date the exchange date hasn't changed,
// we emit the first one so that first daily actions are performed (mappings, delistings, etc).
Assert.AreEqual(firstDataDate, emittedExchangeDates[0]);
Assert.AreEqual(firstDataDate, timeKeeper.DataTime);
}
}
finally
{
timeKeeper.NewExchangeDate -= HandleNewTradableDate;
}
}
[TestCaseSource(nameof(TimeZonesTestCases))]
public void ExchangeDatesAreEmittedByAdvancingToNextDataDate(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
{
using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, null, out var tradableDates, out var config, out var exchangeHours);
var exchangeDateEmitted = false;
var emittedExchangeDates = new List<DateTime>();
void HandleNewTradableDate(object sender, DateTime date)
{
emittedExchangeDates.Add(date);
exchangeDateEmitted = true;
}
timeKeeper.NewExchangeDate += HandleNewTradableDate;
var expectedExchangeDates = new List<DateTime>()
{
new(2024, 10, 1),
new(2024, 10, 2),
new(2024, 10, 3),
new(2024, 10, 4),
new(2024, 10, 7),
new(2024, 10, 8),
new(2024, 10, 9),
new(2024, 10, 10),
new(2024, 10, 11),
};
var firstDataDate = tradableDates[0];
var firstDataDateInExchangeTimeZone = firstDataDate.ConvertTo(dataTimeZone, exchangeTimeZone);
var exchangeIsBehindData = firstDataDateInExchangeTimeZone < firstDataDate;
if (exchangeIsBehindData)
{
expectedExchangeDates.Insert(0, new(2024, 9, 30));
expectedExchangeDates.RemoveAt(expectedExchangeDates.Count - 1);
}
try
{
// Flush first date:
Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
Assert.IsTrue(exchangeDateEmitted);
for (var i = 1; i < tradableDates.Count; i++)
{
exchangeDateEmitted = false;
Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
if (timeKeeper.DataTime == timeKeeper.ExchangeTime)
{
Assert.AreEqual(tradableDates[i], timeKeeper.DataTime);
Assert.AreEqual(tradableDates[i], timeKeeper.ExchangeTime);
Assert.IsTrue(exchangeDateEmitted);
Assert.AreEqual(tradableDates[i], emittedExchangeDates[^1]);
}
else
{
if (exchangeIsBehindData)
{
Assert.IsFalse(exchangeDateEmitted);
Assert.AreEqual(tradableDates[i - 1], timeKeeper.DataTime);
}
else
{
Assert.IsTrue(exchangeDateEmitted);
Assert.AreEqual(emittedExchangeDates[^1], timeKeeper.ExchangeTime);
}
// Move again to the next data date or next exchange date
exchangeDateEmitted = false;
Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
if (exchangeIsBehindData)
{
Assert.IsTrue(exchangeDateEmitted);
Assert.AreEqual(emittedExchangeDates[^1], timeKeeper.ExchangeTime);
}
else
{
Assert.IsFalse(exchangeDateEmitted);
Assert.AreEqual(tradableDates[i], timeKeeper.DataTime);
}
}
}
CollectionAssert.AreEqual(expectedExchangeDates, emittedExchangeDates);
}
finally
{
timeKeeper.NewExchangeDate -= HandleNewTradableDate;
}
}
[TestCaseSource(nameof(TimeZonesTestCases))]
public void ExchangeDatesAreEmittedByAdvancingToNextGivenExchangeTime(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
{
using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, null, out var tradableDates, out var config, out var exchangeHours);
var exchangeDateEmitted = false;
var emittedExchangeDates = new List<DateTime>();
void HandleNewTradableDate(object sender, DateTime date)
{
emittedExchangeDates.Add(date);
exchangeDateEmitted = true;
}
timeKeeper.NewExchangeDate += HandleNewTradableDate;
var expectedExchangeDates = new List<DateTime>()
{
new(2024, 10, 1),
new(2024, 10, 2),
new(2024, 10, 3),
new(2024, 10, 4),
new(2024, 10, 7),
new(2024, 10, 8),
new(2024, 10, 9),
new(2024, 10, 10),
new(2024, 10, 11),
};
var firstDataDate = tradableDates[0];
var firstDataDateInExchangeTimeZone = firstDataDate.ConvertTo(dataTimeZone, exchangeTimeZone);
var exchangeIsBehindData = firstDataDateInExchangeTimeZone < firstDataDate;
if (exchangeIsBehindData)
{
expectedExchangeDates.Insert(0, new(2024, 9, 30));
expectedExchangeDates.RemoveAt(expectedExchangeDates.Count - 1);
}
try
{
// Flush first date:
Assert.IsTrue(timeKeeper.TryAdvanceUntilNextDataDate());
Assert.IsTrue(exchangeDateEmitted);
// Using multiple step sizes to simulate data coming in with gaps
var steps = new Queue<TimeSpan>();
steps.Enqueue(TimeSpan.FromMinutes(1));
steps.Enqueue(TimeSpan.FromMinutes(5));
steps.Enqueue(TimeSpan.FromMinutes(30));
steps.Enqueue(TimeSpan.FromHours(1));
while (emittedExchangeDates.Count != expectedExchangeDates.Count)
{
exchangeDateEmitted = false;
var currentExchangeTime = timeKeeper.ExchangeTime;
var step = steps.Dequeue();
steps.Enqueue(step);
var nextExchangeTime = currentExchangeTime + step;
timeKeeper.AdvanceTowardsExchangeTime(nextExchangeTime);
if (nextExchangeTime.Date != currentExchangeTime.Date &&
exchangeHours.IsDateOpen(nextExchangeTime.Date, config.ExtendedMarketHours))
{
Assert.IsTrue(exchangeDateEmitted);
Assert.AreEqual(emittedExchangeDates[^1], nextExchangeTime.Date);
Assert.AreEqual(timeKeeper.ExchangeTime, nextExchangeTime.Date);
}
else
{
Assert.IsFalse(exchangeDateEmitted);
Assert.AreEqual(timeKeeper.ExchangeTime, nextExchangeTime);
}
}
CollectionAssert.AreEqual(expectedExchangeDates, emittedExchangeDates);
}
finally
{
timeKeeper.NewExchangeDate -= HandleNewTradableDate;
}
}
[TestCaseSource(nameof(TimeZonesTestCases))]
public void DoesNotAdvancePastDelistingDateWhenAdvancingToNextDataDate(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
{
var delistingDate = _start.AddDays(7);
using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, delistingDate, out var _, out var _, out var _);
while (timeKeeper.TryAdvanceUntilNextDataDate())
{
Assert.LessOrEqual(timeKeeper.ExchangeTime.Date, delistingDate.AddDays(1));
}
}
[TestCaseSource(nameof(TimeZonesTestCases))]
public void ThrowsIfNotInitialized(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
{
using var timeKeeper = GetTimeKeeper(dataTimeZone, exchangeTimeZone, false, null, out var _, out var _, out var _);
Assert.Throws<InvalidOperationException>(() => timeKeeper.AdvanceTowardsExchangeTime(timeKeeper.ExchangeTime.AddHours(1)));
}
[TestCaseSource(nameof(TimeZonesTestCases))]
public void FinishesRightAwayIfThereAreNoTradableDates(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
{
using var timeKeeper = GetNoDatesTimeKeeper(dataTimeZone, exchangeTimeZone);
Assert.IsFalse(timeKeeper.TryAdvanceUntilNextDataDate());
}
private static DateChangeTimeKeeper GetTimeKeeper(DateTimeZone dataTimeZone,
DateTimeZone exchangeTimeZone,
bool exchangeAlwaysOpen,
DateTime? delistingDate,
out List<DateTime> tradableDates,
out SubscriptionDataConfig config,
out SecurityExchangeHours exchangeHours)
{
var symbol = Symbols.SPY;
exchangeHours = GetMarketHours(symbol, exchangeTimeZone, exchangeAlwaysOpen);
config = new SubscriptionDataConfig(typeof(TradeBar),
symbol,
Resolution.Minute,
dataTimeZone,
exchangeTimeZone,
false,
true,
false);
tradableDates = Time.EachTradeableDayInTimeZone(exchangeHours,
_start,
_end,
config.DataTimeZone,
config.ExtendedMarketHours).ToList();
return new DateChangeTimeKeeper(tradableDates, config, exchangeHours, delistingDate ?? symbol.GetDelistingDate());
}
private static DateChangeTimeKeeper GetNoDatesTimeKeeper(DateTimeZone dataTimeZone, DateTimeZone exchangeTimeZone)
{
var symbol = Symbols.SPY;
var exchangeHours = GetMarketHours(symbol, exchangeTimeZone, true);
var config = new SubscriptionDataConfig(typeof(TradeBar),
symbol,
Resolution.Minute,
dataTimeZone,
exchangeTimeZone,
false,
true,
false);
var tradableDates = Enumerable.Empty<DateTime>().ToList();
return new DateChangeTimeKeeper(tradableDates, config, exchangeHours, symbol.GetDelistingDate());
}
private static SecurityExchangeHours GetMarketHours(Symbol symbol, DateTimeZone exchangeTimeZone, bool alwaysOpen)
{
SecurityExchangeHours exchangeHours;
if (alwaysOpen)
{
exchangeHours = SecurityExchangeHours.AlwaysOpen(exchangeTimeZone);
}
else
{
var databaseExchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
exchangeHours = new SecurityExchangeHours(exchangeTimeZone,
databaseExchangeHours.Holidays,
databaseExchangeHours.MarketHours.ToDictionary(),
databaseExchangeHours.EarlyCloses,
databaseExchangeHours.LateOpens);
}
return exchangeHours;
}
}
}
@@ -0,0 +1,250 @@
/*
* 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.IO;
using System.Linq;
using NUnit.Framework;
using System.Threading;
using QuantConnect.Data;
using QuantConnect.Util;
using QuantConnect.Logging;
using System.Threading.Tasks;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using DataFeed = QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Data.Custom.IconicTypes;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class DownloaderDataProviderTests
{
[Test]
public void ConcurrentDownloadsSameFile()
{
Log.DebuggingEnabled = true;
var downloader = new DataDownloaderTest();
using var dataProvider = new DataFeed.DownloaderDataProvider(downloader);
var date = new DateTime(2000, 3, 17);
var dataSymbol = Symbol.Create("TEST", SecurityType.Equity, Market.USA);
var actualPath = LeanData.GenerateZipFilePath(Globals.DataFolder, dataSymbol, date, Resolution.Daily, TickType.Trade);
// the symbol of the data is the same
for (var i = 0; i < 10; i++)
{
downloader.Data.Add(new TradeBar(date.AddDays(i), dataSymbol, i, i, i, i, i));
}
File.Delete(actualPath);
var failures = 0L;
using var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10));
// here we request symbols which is different than the data the downloader will return & we need to store, simulating mapping behavior
// the data will always use 'dataSymbol' and the same output path
var endedCount = 0;
var taskCount = 5;
for (var i = 0; i < taskCount; i++)
{
var myLockId = i;
var task = new Task(() =>
{
try
{
var count = 0;
while (count++ < 10)
{
var requestSymbol = Symbol.Create($"TEST{count + Math.Pow(10, myLockId)}", SecurityType.Equity, Market.USA);
var path = LeanData.GenerateZipFilePath(Globals.DataFolder, requestSymbol, date, Resolution.Daily, TickType.Trade);
// we will get null back because the data is stored to another path, the 'dataSymbol' path which is read bellow
Assert.IsNull(dataProvider.Fetch(path));
}
}
catch (Exception exception)
{
Interlocked.Increment(ref failures);
Log.Error(exception);
cancellationToken.Cancel();
}
if(Interlocked.Increment(ref endedCount) == taskCount)
{
// the end
cancellationToken.Cancel();
}
}, cancellationToken.Token);
task.Start();
}
cancellationToken.Token.WaitHandle.WaitOne();
Assert.AreEqual(0, Interlocked.Read(ref failures));
lock (downloader.DataDownloaderGetParameters)
{
Assert.AreEqual(downloader.DataDownloaderGetParameters.Count, 50);
}
var data = QuantConnect.Compression.Unzip(actualPath).Single();
// the data was merged
Assert.AreEqual(66, data.Value.Count);
}
[Test]
public void CustomDataRequest()
{
var downloader = new DataDownloaderTest();
using var dataProvider = new DataFeed.DownloaderDataProvider(downloader);
var customData = Symbol.CreateBase(typeof(LinkedData), Symbols.SPY, Market.USA);
var date = new DateTime(2023, 3, 17);
var path = LeanData.GenerateZipFilePath(Globals.DataFolder + "fake", customData, date, Resolution.Minute, TickType.Trade);
Assert.IsNull(dataProvider.Fetch(path));
Assert.AreEqual(0, downloader.DataDownloaderGetParameters.Count);
}
[TestCase(Resolution.Daily, SecurityType.Equity)]
[TestCase(Resolution.Hour, SecurityType.Equity)]
[TestCase(Resolution.Minute, SecurityType.Equity)]
[TestCase(Resolution.Second, SecurityType.Equity)]
[TestCase(Resolution.Tick, SecurityType.Equity)]
[TestCase(Resolution.Daily, SecurityType.Option)]
[TestCase(Resolution.Hour, SecurityType.Option)]
[TestCase(Resolution.Minute, SecurityType.Option)]
[TestCase(Resolution.Second, SecurityType.Option)]
[TestCase(Resolution.Tick, SecurityType.Option)]
[TestCase(Resolution.Daily, SecurityType.IndexOption)]
[TestCase(Resolution.Hour, SecurityType.IndexOption)]
[TestCase(Resolution.Minute, SecurityType.IndexOption)]
[TestCase(Resolution.Second, SecurityType.IndexOption)]
[TestCase(Resolution.Tick, SecurityType.IndexOption)]
[TestCase(Resolution.Daily, SecurityType.Crypto)]
[TestCase(Resolution.Hour, SecurityType.Crypto)]
[TestCase(Resolution.Minute, SecurityType.Crypto)]
[TestCase(Resolution.Second, SecurityType.Crypto)]
[TestCase(Resolution.Tick, SecurityType.Crypto)]
[TestCase(Resolution.Daily, SecurityType.Future)]
[TestCase(Resolution.Hour, SecurityType.Future)]
[TestCase(Resolution.Minute, SecurityType.Future)]
[TestCase(Resolution.Second, SecurityType.Future)]
[TestCase(Resolution.Tick, SecurityType.Future)]
[TestCase(Resolution.Daily, SecurityType.FutureOption)]
[TestCase(Resolution.Hour, SecurityType.FutureOption)]
[TestCase(Resolution.Minute, SecurityType.FutureOption)]
[TestCase(Resolution.Second, SecurityType.FutureOption)]
[TestCase(Resolution.Tick, SecurityType.FutureOption)]
public void CorrectArguments(Resolution resolution, SecurityType securityType)
{
var downloader = new DataDownloaderTest();
using var dataProvider = new DataFeed.DownloaderDataProvider(downloader);
Symbol symbol = null;
Symbol expectedSymbol = null;
var expectedLowResolutionStart = new DateTime(1998, 1, 2);
var expectedLowResolutionEndUtc = DateTime.UtcNow.Date.AddDays(-1);
if (securityType == SecurityType.Equity)
{
symbol = expectedSymbol = Symbols.AAPL;
}
else if (securityType == SecurityType.Option)
{
symbol = Symbols.SPY_C_192_Feb19_2016;
expectedSymbol = symbol.Canonical;
}
else if (securityType == SecurityType.IndexOption)
{
symbol = Symbol.CreateOption(Symbols.SPX, Symbols.SPX.ID.Market, OptionStyle.European, OptionRight.Call, 38000m, new DateTime(2021, 01, 15));
expectedSymbol = symbol.Canonical;
}
else if (securityType == SecurityType.Crypto)
{
expectedLowResolutionStart = new DateTime(2009, 1, 1);
symbol = expectedSymbol = Symbols.BTCUSD;
}
else if (securityType == SecurityType.Future)
{
symbol = Symbols.Future_CLF19_Jan2019;
expectedSymbol = symbol.Canonical;
}
else if (securityType == SecurityType.FutureOption)
{
symbol = Symbols.CreateFutureOptionSymbol(Symbols.Future_CLF19_Jan2019, OptionRight.Call, 10, new DateTime(2019, 1, 21));
expectedSymbol = symbol.Canonical;
}
var date = new DateTime(2023, 3, 17);
var timezone = MarketHoursDatabase.FromDataFolder().GetDataTimeZone(symbol.ID.Market, symbol, symbol.SecurityType);
// For options and index option in hour or daily resolution, the whole year is downloaded
if (resolution > Resolution.Minute && (securityType == SecurityType.Option || securityType == SecurityType.IndexOption))
{
var expectedStartUtc = new DateTime(date.Year, 1, 1);
expectedLowResolutionStart = expectedStartUtc.ConvertFromUtc(timezone);
expectedLowResolutionEndUtc = expectedStartUtc.AddYears(1);
}
var path = LeanData.GenerateZipFilePath(Globals.DataFolder + "fake", symbol, date, resolution, TickType.Trade);
Assert.IsNull(dataProvider.Fetch(path));
var arguments = downloader.DataDownloaderGetParameters.Single();
Assert.AreEqual(expectedSymbol, arguments.Symbol);
Assert.AreEqual(resolution, arguments.Resolution);
if (resolution < Resolution.Hour)
{
// 1 day
Assert.AreEqual(date.ConvertToUtc(timezone), arguments.StartUtc);
Assert.AreEqual(date.AddDays(1).ConvertToUtc(timezone), arguments.EndUtc);
}
else
{
// the whole history
Assert.AreEqual(expectedLowResolutionStart.ConvertToUtc(timezone), arguments.StartUtc);
Assert.AreEqual(expectedLowResolutionEndUtc, arguments.EndUtc);
}
}
private class DataDownloaderTest : IDataDownloader
{
public List<BaseData> Data { get; } = new();
public List<DataDownloaderGetParameters > DataDownloaderGetParameters { get; } = new ();
public IEnumerable<BaseData> Get(DataDownloaderGetParameters dataDownloaderGetParameters)
{
lock(DataDownloaderGetParameters)
{
DataDownloaderGetParameters.Add(dataDownloaderGetParameters);
if (dataDownloaderGetParameters.Symbol.ID.Symbol.StartsWith("TEST", StringComparison.InvariantCultureIgnoreCase))
{
// let's create some more data based on the symbol, so we can assert it's merged
var id = int.Parse(dataDownloaderGetParameters.Symbol.ID.Symbol.RemoveFromStart("TEST"));
return Data.Select(bar =>
{
var result = bar.Clone();
result.Time = bar.Time.AddDays(id);
result.EndTime = bar.EndTime.AddDays(id);
return result;
});
}
return Data;
}
}
}
}
}
@@ -0,0 +1,103 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Tests.Common.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class AuxiliaryDataEnumeratorTests
{
private SubscriptionDataConfig _config;
private TestTradableDayNotifier _tradableDayNotifier;
private Delisting _delistingEvent;
[SetUp]
public void Setup()
{
_config = SecurityTests.CreateTradeBarConfig();
_tradableDayNotifier = new TestTradableDayNotifier();
_tradableDayNotifier.Symbol = _config.Symbol;
_delistingEvent = new Delisting(_config.Symbol, new DateTime(2009, 1, 1), 1, DelistingType.Delisted);
}
[Test]
public void IsSetToNullIfNoDataAlwaysReturnsTrue()
{
var eventProvider = new TestableEventProvider();
var enumerator = new AuxiliaryDataEnumerator(
_config,
null,
null,
new ITradableDateEventProvider[] { eventProvider },
_tradableDayNotifier,
DateTime.UtcNow
);
eventProvider.Data.Enqueue(_delistingEvent);
_tradableDayNotifier.TriggerEvent();
Assert.Null(enumerator.Current);
Assert.IsTrue(enumerator.MoveNext());
Assert.NotNull(enumerator.Current);
Assert.AreEqual(_delistingEvent, enumerator.Current);
Assert.IsTrue(enumerator.MoveNext());
Assert.Null(enumerator.Current);
Assert.IsTrue(enumerator.MoveNext());
Assert.Null(enumerator.Current);
enumerator.Dispose();
}
}
class TestableEventProvider : ITradableDateEventProvider
{
public readonly Queue<BaseData> Data = new Queue<BaseData>();
public IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
yield return Data.Dequeue();
}
public void Initialize(SubscriptionDataConfig config, IFactorFileProvider factorFileProvider, IMapFileProvider mapFileProvider, DateTime startTime)
{
}
}
class TestTradableDayNotifier : ITradableDatesNotifier
{
public event EventHandler<NewTradableDateEventArgs> NewTradableDate;
public DateTime TradableDate { get; set; }
public BaseData LastBaseData { get; set; }
public Symbol Symbol { get; set; }
public void TriggerEvent()
{
NewTradableDate?.Invoke(this, new NewTradableDateEventArgs(TradableDate, LastBaseData, Symbol, null));
}
}
}
@@ -0,0 +1,58 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class BaseDataCollectionAggregatorEnumeratorTests
{
[Test]
public void AggregatesUntilNull()
{
var time = new DateTime(2015, 10, 20);
var underlying = Enumerable.Range(0, 5).Select(x => new Tick { Time = time }).ToList();
underlying.AddRange(new Tick[] { null, null, null });
var aggregator = new BaseDataCollectionAggregatorEnumerator(underlying.GetEnumerator(), Symbols.SPY);
Assert.IsTrue(aggregator.MoveNext());
Assert.IsNotNull(aggregator.Current);
Assert.AreEqual(5, aggregator.Current.Data.Count);
aggregator.Dispose();
}
[Test]
public void AggregatesUntilTimeChange()
{
var time = new DateTime(2015, 10, 20);
var underlying = Enumerable.Range(0, 5).Select(x => new Tick { Time = time }).ToList();
underlying.AddRange(Enumerable.Range(0, 5).Select(x => new Tick {Time = time.AddSeconds(1)}));
var aggregator = new BaseDataCollectionAggregatorEnumerator(underlying.GetEnumerator(), Symbols.SPY);
Assert.IsTrue(aggregator.MoveNext());
Assert.IsNotNull(aggregator.Current);
Assert.AreEqual(5, aggregator.Current.Data.Count);
aggregator.Dispose();
}
}
}
@@ -0,0 +1,146 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using System.Collections;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class ConcatEnumeratorTests
{
[TestCase(true)]
[TestCase(false)]
public void SkipsBasedOnEndTime(bool skipsBasedOnEndTime)
{
var time = new DateTime(2020, 1, 1);
var enumerator1 = new List<BaseData> { new Tick(time, Symbols.SPY, 10, 10) }.GetEnumerator();
var enumerator2 = new List<BaseData>
{
new Tick(time.AddSeconds(-1), Symbols.SPY, 20, 20), //should be skipped because end time is before previous tick
new Tick(time.AddSeconds(1), Symbols.SPY, 30 , 30)
}.GetEnumerator();
var concat = new ConcatEnumerator(skipsBasedOnEndTime, enumerator1, enumerator2);
Assert.IsTrue(concat.MoveNext());
Assert.AreEqual(10, (concat.Current as Tick).AskPrice);
if (!skipsBasedOnEndTime)
{
Assert.IsTrue(concat.MoveNext());
Assert.AreEqual(20, (concat.Current as Tick).AskPrice);
}
Assert.IsTrue(concat.MoveNext());
Assert.AreEqual(30, (concat.Current as Tick).AskPrice);
Assert.IsFalse(concat.MoveNext());
Assert.IsNull(concat.Current);
concat.Dispose();
}
[TestCase(true)]
[TestCase(false)]
public void EmptyNullEnumerators(bool skipsBasedOnEndTime)
{
var time = new DateTime(2020, 1, 1);
// empty enumerators
var enumerator1 = new List<BaseData>().GetEnumerator();
var enumerator2 = new List<BaseData>().GetEnumerator();
var enumerator3 = new List<BaseData>
{
new Tick(time, Symbols.SPY, 10, 10),
new Tick(time.AddSeconds(-1), Symbols.SPY, 20, 20),
new Tick(time.AddSeconds(1), Symbols.SPY, 30 , 30)
}.GetEnumerator();
var concat = new ConcatEnumerator(skipsBasedOnEndTime, enumerator1, null, enumerator2, enumerator3);
Assert.IsTrue(concat.MoveNext());
Assert.AreEqual(10, (concat.Current as Tick).AskPrice);
Assert.IsTrue(concat.MoveNext());
Assert.AreEqual(20, (concat.Current as Tick).AskPrice);
Assert.IsTrue(concat.MoveNext());
Assert.AreEqual(30, (concat.Current as Tick).AskPrice);
Assert.IsFalse(concat.MoveNext());
Assert.IsNull(concat.Current);
concat.Dispose();
}
[TestCase(true)]
[TestCase(false)]
public void DropsEnumeratorsReturningNullAndTrue(bool skipsBasedOnEndTime)
{
var enumerator1 = new TestEnumerator();
var enumerator2 = new TestEnumerator();
var concat = new ConcatEnumerator(skipsBasedOnEndTime, enumerator1, null, enumerator2);
Assert.IsTrue(concat.MoveNext());
Assert.IsNull(concat.Current);
Assert.IsTrue(enumerator1.Disposed);
Assert.IsFalse(enumerator2.Disposed);
Assert.AreEqual(1, enumerator2.MoveNextCount);
Assert.IsTrue(concat.MoveNext());
// we assert it just keeps the last enumerator and drops the rest
Assert.IsTrue(enumerator1.Disposed);
Assert.IsFalse(enumerator2.Disposed);
Assert.IsNull(concat.Current);
Assert.AreEqual(2, enumerator2.MoveNextCount);
concat.Dispose();
}
private class TestEnumerator : IEnumerator<BaseData>
{
public bool Disposed { get; private set; }
public int MoveNextCount { get; private set; }
public BaseData Current => null;
object IEnumerator.Current => null;
public void Dispose()
{
Disposed = true;
}
public bool MoveNext()
{
MoveNextCount++;
return true;
}
public void Reset()
{
}
}
}
}
@@ -0,0 +1,245 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class DelistingEnumeratorTests
{
private SubscriptionDataConfig _config;
[SetUp]
public void SetUp()
{
var symbol = Symbol.CreateFuture("ASD", Market.USA, new DateTime(2018, 01, 01));
_config = new SubscriptionDataConfig(typeof(TradeBar),
symbol,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
}
[Test]
public void EmitsBothEventsIfDateIsPastDelisted()
{
var eventProvider = new DelistingEventProvider();
eventProvider.Initialize(_config,
null,
null,
DateTime.UtcNow);
var enumerator = eventProvider.GetEvents(
new NewTradableDateEventArgs(
DateTime.UtcNow,
new Tick(DateTime.UtcNow, _config.Symbol, 10, 5),
_config.Symbol,
null
)
).GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current as Delisting);
Assert.AreEqual(MarketDataType.Auxiliary, enumerator.Current.DataType);
Assert.AreEqual(DelistingType.Warning, (enumerator.Current as Delisting).Type);
Assert.AreEqual(_config.Symbol.ID.Date, (enumerator.Current as Delisting).Time.Date);
Assert.AreEqual(7.5, (enumerator.Current as Delisting).Price);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current as Delisting);
Assert.AreEqual(MarketDataType.Auxiliary, enumerator.Current.DataType);
Assert.AreEqual(DelistingType.Delisted, (enumerator.Current as Delisting).Type);
Assert.AreEqual(_config.Symbol.ID.Date.AddDays(1), (enumerator.Current as Delisting).Time.Date);
Assert.AreEqual(7.5, (enumerator.Current as Delisting).Price);
Assert.IsFalse(enumerator.MoveNext());
enumerator.Dispose();
}
[Test]
public void EmitsWarningAsOffDelistingDate()
{
var eventProvider = new DelistingEventProvider();
eventProvider.Initialize(_config,
null,
null,
DateTime.UtcNow);
// should NOT emit
var enumerator = eventProvider.GetEvents(
new NewTradableDateEventArgs(
_config.Symbol.ID.Date.Subtract(TimeSpan.FromMinutes(1)),
new Tick(DateTime.UtcNow, _config.Symbol, 10, 5),
_config.Symbol,
null
)
).GetEnumerator();
Assert.IsFalse(enumerator.MoveNext());
// should emit
enumerator = eventProvider.GetEvents(
new NewTradableDateEventArgs(
_config.Symbol.ID.Date,
new Tick(DateTime.UtcNow, _config.Symbol, 10, 5),
_config.Symbol,
null
)
).GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current as Delisting);
Assert.AreEqual(MarketDataType.Auxiliary, enumerator.Current.DataType);
Assert.AreEqual(DelistingType.Warning, (enumerator.Current as Delisting).Type);
Assert.AreEqual(_config.Symbol.ID.Date, (enumerator.Current as Delisting).Time.Date);
Assert.AreEqual(7.5, (enumerator.Current as Delisting).Price);
Assert.IsFalse(enumerator.MoveNext());
enumerator.Dispose();
}
[Test]
public void EmitsDelistedAfterDelistingDate()
{
var eventProvider = new DelistingEventProvider();
eventProvider.Initialize(_config,
null,
null,
DateTime.UtcNow);
// should emit warning
var enumerator = eventProvider.GetEvents(
new NewTradableDateEventArgs(
_config.Symbol.ID.Date,
new Tick(DateTime.UtcNow, _config.Symbol, 10, 5),
_config.Symbol,
null
)
).GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current as Delisting);
Assert.AreEqual(DelistingType.Warning, (enumerator.Current as Delisting).Type);
// should NOT emit if not AFTER delisting date
enumerator = eventProvider.GetEvents(
new NewTradableDateEventArgs(
_config.Symbol.ID.Date,
new Tick(DateTime.UtcNow, _config.Symbol, 10, 5),
_config.Symbol,
null
)
).GetEnumerator();
Assert.IsFalse(enumerator.MoveNext());
// should emit AFTER delisting date
enumerator = eventProvider.GetEvents(
new NewTradableDateEventArgs(
_config.Symbol.ID.Date.AddMinutes(1),
new Tick(DateTime.UtcNow, _config.Symbol, 10, 5),
_config.Symbol,
null
)
).GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current as Delisting);
Assert.AreEqual(MarketDataType.Auxiliary, enumerator.Current.DataType);
Assert.AreEqual(DelistingType.Delisted, (enumerator.Current as Delisting).Type);
Assert.AreEqual(_config.Symbol.ID.Date.AddDays(1), (enumerator.Current as Delisting).Time.Date);
Assert.AreEqual(7.5, (enumerator.Current as Delisting).Price);
Assert.IsFalse(enumerator.MoveNext());
enumerator.Dispose();
}
[Test]
public void EmitsDelistedWarningOnNonTradableDay()
{
// Unit test to simulate and reproduce #5545
// Give us two tradable days before and after expiration
var tradableDays = new List<DateTime> { new DateTime(2021, 01, 01), new DateTime(2021, 01, 04) };
// Set expiration as 1/2/21 a Saturday, not included in our tradble days
var expiration = new DateTime(2021, 01, 02);
var symbol = Symbol.CreateFuture("ASD", Market.USA, expiration);
var config = new SubscriptionDataConfig(typeof(TradeBar),
symbol,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var eventProvider = new DelistingEventProvider();
eventProvider.Initialize(config,
null,
null,
DateTime.UtcNow);
var tradableDateEvents = tradableDays.Select(day => new NewTradableDateEventArgs(
day,
new Tick(day, config.Symbol, 10, 5),
config.Symbol,
null
)).GetEnumerator();
// Pass in the day before expiration should be nothing
tradableDateEvents.MoveNext();
var enumerator = eventProvider.GetEvents(tradableDateEvents.Current).GetEnumerator();
Assert.IsFalse(enumerator.MoveNext());
// Pass in the following monday, should be both but warning first and still scheduled for saturday
tradableDateEvents.MoveNext();
enumerator = eventProvider.GetEvents(tradableDateEvents.Current).GetEnumerator();
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current as Delisting);
Assert.AreEqual(MarketDataType.Auxiliary, enumerator.Current.DataType);
Assert.AreEqual(DelistingType.Warning, (enumerator.Current as Delisting).Type);
Assert.AreEqual(config.Symbol.ID.Date, (enumerator.Current as Delisting).Time.Date);
Assert.AreEqual(7.5, (enumerator.Current as Delisting).Price);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current as Delisting);
Assert.AreEqual(MarketDataType.Auxiliary, enumerator.Current.DataType);
Assert.AreEqual(DelistingType.Delisted, (enumerator.Current as Delisting).Type);
Assert.AreEqual(config.Symbol.ID.Date.AddDays(1), (enumerator.Current as Delisting).Time.Date);
Assert.AreEqual(7.5, (enumerator.Current as Delisting).Price);
Assert.IsFalse(enumerator.MoveNext());
tradableDateEvents.Dispose();
enumerator.Dispose();
}
}
}
@@ -0,0 +1,137 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using System.Globalization;
using QuantConnect.Data.Market;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class DividendEventProviderTests
{
// From https://www.nasdaq.com/market-activity/stocks/aapl/dividend-history
[TestCase("20121106", 2.65)]
[TestCase("20130206", 2.65)]
[TestCase("20130508", 3.05)]
[TestCase("20130807", 3.05)]
[TestCase("20131105", 3.05)]
[TestCase("20140205", 3.05)]
[TestCase("20140507", 3.29)]
[TestCase("20140806", 0.47)]
[TestCase("20141105", 0.47)]
[TestCase("20150204", 0.47)]
[TestCase("20150506", 0.52)]
[TestCase("20150805", 0.52)]
[TestCase("20151104", 0.52)]
[TestCase("20160203", 0.52)]
[TestCase("20160504", 0.57)]
[TestCase("20160803", 0.57)]
[TestCase("20161102", 0.57)]
[TestCase("20170208", 0.57)]
[TestCase("20170510", 0.63)]
[TestCase("20170809", 0.63)]
[TestCase("20171109", 0.63)]
[TestCase("20180208", 0.63)]
[TestCase("20180510", 0.73)]
public void DividendsDistribution(string exDividendDateStr, decimal expectedDistribution)
{
var dividendProvider = new DividendEventProvider();
var config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.AAPL, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork,
false, false, false);
var start = new DateTime(1998, 01, 02);
dividendProvider.Initialize(config, TestGlobals.FactorFileProvider, TestGlobals.MapFileProvider, start);
var exDividendDate = DateTime.ParseExact(exDividendDateStr, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
var events = dividendProvider
.GetEvents(new NewTradableDateEventArgs(exDividendDate, null, Symbols.AAPL, null))
.ToList();
// ex dividend date does not emit anything
Assert.AreEqual(0, events.Count);
events = dividendProvider
.GetEvents(new NewTradableDateEventArgs(exDividendDate.AddDays(1), null, Symbols.AAPL, null))
.ToList();
Assert.AreEqual(1, events.Count);
var dividend = events[0] as Dividend;
Assert.IsNotNull(dividend);
Assert.AreEqual(expectedDistribution, dividend.Distribution);
}
[Test]
public void ThrowsWhenEmptyReferencePrice()
{
var dividendProvider = new DividendEventProvider();
var config = new SubscriptionDataConfig(typeof(TradeBar), Symbols.AAPL, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork,
false, false, false);
var start = new DateTime(1998, 01, 02);
var row1 = new DateTime(2000, 01, 02);
var row2 = new DateTime(2001, 01, 02);
var row3 = new DateTime(2002, 01, 02);
var factorFileProvider = new TestFactorFileProvider
{
FactorFile = new CorporateFactorProvider("AAPL", new []
{
new CorporateFactorRow(row1, 0.693m, 1),
new CorporateFactorRow(row2, 0.77m, 1),
new CorporateFactorRow(row3, 0.85555m, 1)
}, start)
};
dividendProvider.Initialize(config, factorFileProvider, TestGlobals.MapFileProvider, start);
foreach (var row in factorFileProvider.FactorFile.Take(1))
{
var lastRawPrice = 100;
var events = dividendProvider
.GetEvents(new NewTradableDateEventArgs(row.Date, null, Symbols.AAPL, lastRawPrice))
.ToList();
// ex dividend date does not emit anything
Assert.AreEqual(0, events.Count);
Assert.Throws<InvalidOperationException>(() =>
{
dividendProvider
.GetEvents(new NewTradableDateEventArgs(row.Date.AddDays(1), null, Symbols.AAPL, lastRawPrice))
.ToList();
});
}
}
private class TestFactorFileProvider : IFactorFileProvider
{
public CorporateFactorProvider FactorFile { get; set; }
public void Initialize(IMapFileProvider mapFileProvider, IDataProvider dataProvider)
{
}
public IFactorProvider Get(Symbol symbol)
{
return FactorFile;
}
}
}
}
@@ -0,0 +1,136 @@
/*
* 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.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class EnqueableEnumeratorTests
{
[Test]
public void PassesTicksStraightThrough()
{
var enumerator = new EnqueueableEnumerator<Tick>();
// add some ticks
var currentTime = new DateTime(2015, 10, 08);
// returns true even if no data present until stop is called
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick1 = new Tick(currentTime, Symbols.SPY, 199.55m, 199, 200) {Quantity = 10};
enumerator.Enqueue(tick1);
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(tick1, enumerator.Current);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick2 = new Tick(currentTime, Symbols.SPY, 199.56m, 199.21m, 200.02m) {Quantity = 5};
enumerator.Enqueue(tick2);
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(tick2, enumerator.Current);
enumerator.Stop();
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
enumerator.Dispose();
}
[Test]
public void RecordsInternalQueueCount()
{
var enumerator = new EnqueueableEnumerator<Tick>();
var currentTime = new DateTime(2015, 12, 01);
var tick = new Tick(currentTime, Symbols.SPY, 100, 101);
enumerator.Enqueue(tick);
Assert.AreEqual(1, enumerator.Count);
tick = new Tick(currentTime, Symbols.SPY, 100, 101);
enumerator.Enqueue(tick);
Assert.AreEqual(2, enumerator.Count);
enumerator.MoveNext();
Assert.AreEqual(1, enumerator.Count);
enumerator.MoveNext();
Assert.AreEqual(0, enumerator.Count);
enumerator.Dispose();
}
[Test, Category("TravisExclude")]
public void MoveNextBlocks()
{
using var finished = new ManualResetEvent(false);
var enumerator = new EnqueueableEnumerator<Tick>(true);
// producer
int count = 0;
Task.Run(() =>
{
while (!finished.WaitOne(TimeSpan.FromMilliseconds(50)))
{
enumerator.Enqueue(new Tick(DateTime.Now, Symbols.SPY, 100, 101));
count++;
// 5 data points is plenty
if (count > 5)
{
finished.Set();
enumerator.Stop();
}
}
});
// consumer
int dequeuedCount = 0;
bool encounteredError = false;
using var consumerTaskFinished = new ManualResetEvent(false);
Task.Run(() =>
{
while (enumerator.MoveNext())
{
dequeuedCount++;
if (enumerator.Current == null)
{
encounteredError = true;
}
}
consumerTaskFinished.Set();
});
finished.WaitOne(Timeout.Infinite);
consumerTaskFinished.WaitOne(Timeout.Infinite);
Assert.IsFalse(enumerator.MoveNext());
Assert.IsFalse(encounteredError);
Assert.AreEqual(count, dequeuedCount);
enumerator.Dispose();
}
}
}
@@ -0,0 +1,127 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Brokerages;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories;
using QuantConnect.Logging;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators.Factories
{
[TestFixture]
public class BaseDataCollectionSubscriptionEnumeratorFactoryTests
{
// This test reports higher memory usage when ran with Travis, so we exclude it for now
[Test, Category("TravisExclude")]
public void DoesNotLeakMemory()
{
var symbolFactory = new FundamentalUniverse();
var symbol = symbolFactory.UniverseSymbol();
var config = new SubscriptionDataConfig(typeof(FundamentalUniverse), symbol, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false, false, TickType.Trade, false);
var security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
config,
new Cash(Currencies.USD, 0, 1),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
var universeSettings = new UniverseSettings(Resolution.Daily, 2m, true, false, TimeSpan.FromDays(1));
var securityInitializer = new BrokerageModelSecurityInitializer(new DefaultBrokerageModel(), SecuritySeeder.Null);
using var universe = new CoarseFundamentalUniverse(universeSettings, x => new List<Symbol>{ Symbols.AAPL });
var factory = new BaseDataCollectionSubscriptionEnumeratorFactory(null);
GC.Collect();
var ramUsageBeforeLoop = OS.TotalPhysicalMemoryUsed;
var date = new DateTime(2014, 3, 25);
const int iterations = 1000;
for (var i = 0; i < iterations; i++)
{
var request = new SubscriptionRequest(true, universe, security, config, date, date);
using (var enumerator = factory.CreateEnumerator(request, TestGlobals.DataProvider))
{
enumerator.MoveNext();
}
}
GC.Collect();
var ramUsageAfterLoop = OS.TotalPhysicalMemoryUsed;
Log.Trace($"RAM usage - before: {ramUsageBeforeLoop} MB, after: {ramUsageAfterLoop} MB");
Assert.IsTrue(ramUsageAfterLoop - ramUsageBeforeLoop < 10);
}
[Test]
public void ReturnsExpectedTimestamps()
{
var symbolFactory = new FundamentalUniverse();
var symbol = symbolFactory.UniverseSymbol();
var config = new SubscriptionDataConfig(typeof(FundamentalUniverse), symbol, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false, false, TickType.Trade, false);
var security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
config,
new Cash(Currencies.USD, 0, 1),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
var universeSettings = new UniverseSettings(Resolution.Daily, 2m, true, false, TimeSpan.FromDays(1));
var securityInitializer = new BrokerageModelSecurityInitializer(new DefaultBrokerageModel(), SecuritySeeder.Null);
using var universe = new CoarseFundamentalUniverse(universeSettings, x => new List<Symbol> { Symbols.AAPL });
var factory = new BaseDataCollectionSubscriptionEnumeratorFactory(null);
var dateStart = new DateTime(2014, 3, 26);
var dateEnd = new DateTime(2014, 3, 27);
var days = (dateEnd - dateStart).Days + 1;
var request = new SubscriptionRequest(true, universe, security, config, dateStart, dateEnd);
using (var enumerator = factory.CreateEnumerator(request, TestGlobals.DataProvider))
{
for (var i = 0; i < days; i++)
{
Assert.IsTrue(enumerator.MoveNext());
var current = enumerator.Current as BaseDataCollection;
Assert.IsNotNull(current);
Assert.AreEqual(dateStart.AddDays(i), current.Time);
Assert.AreEqual(dateStart.AddDays(i), current.EndTime);
Assert.AreEqual(dateStart.AddDays(i - 1), current.Data[0].Time);
Assert.AreEqual(dateStart.AddDays(i), current.Data[0].EndTime);
}
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
}
}
}
}
;
@@ -0,0 +1,613 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators.Factories
{
[TestFixture]
public class LiveCustomDataSubscriptionEnumeratorFactoryTests
{
[TestFixture]
public class WhenCreatingEnumeratorForRestData
{
private readonly DateTime _referenceLocal = new DateTime(2017, 10, 12);
private readonly DateTime _referenceUtc = new DateTime(2017, 10, 12).ConvertToUtc(TimeZones.NewYork);
private ManualTimeProvider _timeProvider;
private IEnumerator<BaseData> _enumerator;
private Mock<ISubscriptionDataSourceReader> _dataSourceReader;
[SetUp]
public void Given()
{
_timeProvider = new ManualTimeProvider(_referenceUtc);
_dataSourceReader = new Mock<ISubscriptionDataSourceReader>();
_dataSourceReader.Setup(dsr => dsr.Read(It.Is<SubscriptionDataSource>(sds =>
sds.Source == "rest.source" &&
sds.TransportMedium == SubscriptionTransportMedium.Rest &&
sds.Format == FileFormat.Csv))
)
.Returns(Enumerable.Range(0, 100)
.Select(i => new RestData
{
EndTime = _referenceLocal.AddSeconds(i)
}))
.Verifiable();
var config = new SubscriptionDataConfig(typeof(RestData), Symbols.SPY, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var request = GetSubscriptionRequest(config, _referenceUtc.AddSeconds(-1), _referenceUtc.AddDays(1));
var factory = new TestableLiveCustomDataSubscriptionEnumeratorFactory(_timeProvider, _dataSourceReader.Object);
_enumerator = factory.CreateEnumerator(request, null);
}
[TearDown]
public void TearDown()
{
_enumerator?.DisposeSafely();
}
[Test]
public void YieldsDataEachSecondAsTimePasses()
{
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal, _enumerator.Current.EndTime);
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
_timeProvider.AdvanceSeconds(1);
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddSeconds(1), _enumerator.Current.EndTime);
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocationCount(_dataSourceReader, 1, "rest.source", SubscriptionTransportMedium.Rest, FileFormat.Csv);
}
}
[TestFixture]
public class WhenCreatingEnumeratorForRestCollectionData
{
private const int DataPerTimeStep = 3;
private readonly DateTime _referenceLocal = new DateTime(2017, 10, 12);
private readonly DateTime _referenceUtc = new DateTime(2017, 10, 12).ConvertToUtc(TimeZones.NewYork);
private ManualTimeProvider _timeProvider;
private IEnumerator<BaseData> _enumerator;
private Mock<ISubscriptionDataSourceReader> _dataSourceReader;
[SetUp]
public void Given()
{
_timeProvider = new ManualTimeProvider(_referenceUtc);
_dataSourceReader = new Mock<ISubscriptionDataSourceReader>();
_dataSourceReader.Setup(dsr => dsr.Read(It.Is<SubscriptionDataSource>(sds =>
sds.Source == "rest.collection.source" &&
sds.TransportMedium == SubscriptionTransportMedium.Rest &&
sds.Format == FileFormat.UnfoldingCollection))
)
.Returns(Enumerable.Range(0, 100)
.Select(i => new BaseDataCollection(_referenceLocal.AddSeconds(i), Symbols.SPY, Enumerable.Range(0, DataPerTimeStep)
.Select(_ => new RestCollectionData {EndTime = _referenceLocal.AddSeconds(i)})))
)
.Verifiable();
var config = new SubscriptionDataConfig(typeof(RestCollectionData), Symbols.SPY, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var request = GetSubscriptionRequest(config, _referenceUtc.AddSeconds(-4), _referenceUtc.AddDays(1));
var factory = new TestableLiveCustomDataSubscriptionEnumeratorFactory(_timeProvider, _dataSourceReader.Object);
_enumerator = factory.CreateEnumerator(request, null);
}
[TearDown]
public void TearDown()
{
_enumerator?.DisposeSafely();
}
[Test]
public void YieldsGroupOfDataEachSecond()
{
for (int i = 0; i < DataPerTimeStep; i++)
{
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current, $"Index {i} is null.");
Assert.AreEqual(_referenceLocal, _enumerator.Current.EndTime);
}
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
_timeProvider.AdvanceSeconds(1);
for (int i = 0; i < DataPerTimeStep; i++)
{
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddSeconds(1), _enumerator.Current.EndTime);
}
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocationCount(_dataSourceReader, 1, "rest.collection.source", SubscriptionTransportMedium.Rest, FileFormat.UnfoldingCollection);
}
}
[TestFixture]
public class WhenCreatingEnumeratorForRemoteCollectionData
{
private const int DataPerTimeStep = 3;
private readonly DateTime _referenceLocal = new DateTime(2017, 10, 12);
private readonly DateTime _referenceUtc = new DateTime(2017, 10, 12).ConvertToUtc(TimeZones.NewYork);
private ManualTimeProvider _timeProvider;
private IEnumerator<BaseData> _enumerator;
[SetUp]
public void Given()
{
_timeProvider = new ManualTimeProvider(_referenceUtc);
var dataSourceReader = new TestISubscriptionDataSourceReader
{
TimeProvider = _timeProvider
};
var config = new SubscriptionDataConfig(typeof(RemoteCollectionData), Symbols.SPY, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var request = GetSubscriptionRequest(config, _referenceUtc.AddSeconds(-4), _referenceUtc.AddDays(1));
var factory = new TestableLiveCustomDataSubscriptionEnumeratorFactory(_timeProvider, dataSourceReader);
_enumerator = factory.CreateEnumerator(request, null);
}
private class TestISubscriptionDataSourceReader : ISubscriptionDataSourceReader
{
public ManualTimeProvider TimeProvider;
public event EventHandler<InvalidSourceEventArgs> InvalidSource;
public IEnumerable<BaseData> Read(SubscriptionDataSource source)
{
var currentLocalTime = TimeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
var data = Enumerable.Range(0, DataPerTimeStep).Select(_ => new RemoteCollectionData { EndTime = currentLocalTime });
// let's add some old data which should be ignored
data = data.Concat(Enumerable.Range(0, DataPerTimeStep).Select(_ => new RemoteCollectionData { EndTime = currentLocalTime.AddSeconds(-1) }));
return new BaseDataCollection(currentLocalTime, Symbols.SPY, data);
}
}
[TearDown]
public void TearDown()
{
_enumerator?.DisposeSafely();
}
[Test]
public void YieldsGroupOfDataEachSecond()
{
for (int i = 0; i < DataPerTimeStep; i++)
{
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current, $"Index {i} is null.");
Assert.AreEqual(_referenceLocal, _enumerator.Current.EndTime);
}
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
_timeProvider.AdvanceSeconds(1);
for (int i = 0; i < DataPerTimeStep; i++)
{
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddSeconds(1), _enumerator.Current.EndTime);
}
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
}
}
[TestFixture]
public class WhenCreatingEnumeratorForSecondRemoteFileData
{
private readonly DateTime _referenceLocal = new DateTime(2017, 10, 12);
private readonly DateTime _referenceUtc = new DateTime(2017, 10, 12).ConvertToUtc(TimeZones.NewYork);
private ManualTimeProvider _timeProvider;
private IEnumerator<BaseData> _enumerator;
private Mock<ISubscriptionDataSourceReader> _dataSourceReader;
[SetUp]
public void Given()
{
_timeProvider = new ManualTimeProvider(_referenceUtc);
_dataSourceReader = new Mock<ISubscriptionDataSourceReader>();
_dataSourceReader.Setup(dsr => dsr.Read(It.Is<SubscriptionDataSource>(sds =>
sds.Source == "remote.file.source" &&
sds.TransportMedium == SubscriptionTransportMedium.RemoteFile &&
sds.Format == FileFormat.Csv))
)
.Returns(Enumerable.Range(0, 100)
.Select(i => new RemoteFileData
{
// include past data
EndTime = _referenceLocal.AddSeconds(i - 95)
}))
.Verifiable();
var config = new SubscriptionDataConfig(typeof(RemoteFileData), Symbols.SPY, Resolution.Second, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var request = GetSubscriptionRequest(config, _referenceUtc.AddSeconds(-6), _referenceUtc.AddDays(1));
var factory = new TestableLiveCustomDataSubscriptionEnumeratorFactory(_timeProvider, _dataSourceReader.Object);
_enumerator = factory.CreateEnumerator(request, null);
}
[TearDown]
public void TearDown()
{
_enumerator?.DisposeSafely();
}
[Test]
public void YieldsDataEachSecondAsTimePasses()
{
// most recent 5 seconds of data
for (int i = 5; i > 0; i--)
{
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddSeconds(-i), _enumerator.Current.EndTime);
}
// first data point
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal, _enumerator.Current.EndTime);
_timeProvider.AdvanceSeconds(1);
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddSeconds(1), _enumerator.Current.EndTime);
VerifyGetSourceInvocationCount(_dataSourceReader, 1, "remote.file.source", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv);
}
}
[TestFixture]
public class WhenCreatingEnumeratorForDailyRemoteFileData
{
private int _dataPointsAfterReference = 1;
private readonly DateTime _referenceLocal = new DateTime(2017, 10, 12);
private readonly DateTime _referenceUtc = new DateTime(2017, 10, 12).ConvertToUtc(TimeZones.NewYork);
private ManualTimeProvider _timeProvider;
private IEnumerator<BaseData> _enumerator;
private Mock<ISubscriptionDataSourceReader> _dataSourceReader;
[SetUp]
public void Given()
{
_timeProvider = new ManualTimeProvider(_referenceUtc);
_dataSourceReader = new Mock<ISubscriptionDataSourceReader>();
_dataSourceReader.Setup(dsr => dsr.Read(It.Is<SubscriptionDataSource>(sds =>
sds.Source == "remote.file.source" &&
sds.TransportMedium == SubscriptionTransportMedium.RemoteFile &&
sds.Format == FileFormat.Csv))
)
.Returns(() => Enumerable.Range(0, 100)
.Select(i => new RemoteFileData
{
// include past data
EndTime = _referenceLocal.Add(TimeSpan.FromDays(i - (100 - _dataPointsAfterReference - 1)))
}))
.Verifiable();
var config = new SubscriptionDataConfig(typeof(RemoteFileData), Symbols.SPY, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var request = GetSubscriptionRequest(config, _referenceUtc.AddDays(-2), _referenceUtc.AddDays(1));
var factory = new TestableLiveCustomDataSubscriptionEnumeratorFactory(_timeProvider, _dataSourceReader.Object);
_enumerator = factory.CreateEnumerator(request, null);
}
[TearDown]
public void TearDown()
{
_enumerator?.DisposeSafely();
}
[Test]
public void YieldsDataEachDayAsTimePasses()
{
// previous point is exactly one resolution step behind, so it emits
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddDays(-1), _enumerator.Current.EndTime);
VerifyGetSourceInvocation(1);
// yields the data for the current time
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal, _enumerator.Current.EndTime);
VerifyGetSourceInvocation(0);
_timeProvider.Advance(Time.OneDay);
// now we can yield the next data point as it has passed frontier time
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddDays(1), _enumerator.Current.EndTime);
VerifyGetSourceInvocation(0);
// this call exhaused the enumerator stack and yields a null result
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(0);
// this call refrshes the enumerator stack but finds no data ahead of the frontier
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(1);
_timeProvider.Advance(TimeSpan.FromMinutes(30));
// time advances 30 minutes so we'll try to refresh again
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(1);
_timeProvider.Advance(Time.OneDay);
// now to the next day, we'll try again and get data
_dataPointsAfterReference++;
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddDays(2), _enumerator.Current.EndTime);
VerifyGetSourceInvocation(1);
_timeProvider.Advance(TimeSpan.FromHours(1));
// out of data
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(0);
_timeProvider.Advance(TimeSpan.FromHours(1));
// time advanced so we'll try to refresh the souce again, but exhaust the stack because no data
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(1);
// move forward to next whole day, midnight
_timeProvider.Advance(Time.OneDay.Subtract(TimeSpan.FromHours(2.5)));
// the day elapsed but there's still no data available
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(1);
// this is rate limited by the 30 minute guard for daily data
_timeProvider.Advance(TimeSpan.FromMinutes(29));
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(0);
// another 30 minutes elapsed and now there's data available
_dataPointsAfterReference++;
_timeProvider.Advance(TimeSpan.FromMinutes(1));
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNotNull(_enumerator.Current);
Assert.AreEqual(_referenceLocal.AddDays(3), _enumerator.Current.EndTime);
VerifyGetSourceInvocation(1);
// exhausted the stack
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(0);
// rate limited
Assert.IsTrue(_enumerator.MoveNext());
Assert.IsNull(_enumerator.Current);
VerifyGetSourceInvocation(0);
}
private int _runningCount;
private void VerifyGetSourceInvocation(int count)
{
_runningCount += count;
VerifyGetSourceInvocationCount(_dataSourceReader, _runningCount, "remote.file.source", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv);
}
}
[TestCase(10)]
[TestCase(60)]
[TestCase(0)]
public void AllowsSpecifyingIntervalCheck(int intervalCheck)
{
var referenceLocal = new DateTime(2017, 10, 12);
var referenceUtc = new DateTime(2017, 10, 12).ConvertToUtc(TimeZones.NewYork);
var timeProvider = new ManualTimeProvider(referenceUtc);
var callCount = 0;
var dataSourceReader = new Mock<ISubscriptionDataSourceReader>();
dataSourceReader.Setup(dsr => dsr.Read(It.Is<SubscriptionDataSource>(sds =>
sds.Source == "local.file.source" &&
sds.TransportMedium == SubscriptionTransportMedium.LocalFile &&
sds.Format == FileFormat.Csv))
)
.Returns(() => new []{ new LocalFileData { EndTime = referenceLocal.AddSeconds(++callCount) } })
.Verifiable();
var config = new SubscriptionDataConfig(typeof(LocalFileData), Symbols.SPY, Resolution.Daily, TimeZones.NewYork, TimeZones.NewYork, false, false, false);
var request = GetSubscriptionRequest(config, referenceUtc.AddSeconds(-1), referenceUtc.AddDays(1));
var intervalCalls = intervalCheck == 0 ? (TimeSpan?) null : TimeSpan.FromMinutes(intervalCheck);
var factory = new TestableLiveCustomDataSubscriptionEnumeratorFactory(timeProvider, dataSourceReader.Object, intervalCalls);
var enumerator = factory.CreateEnumerator(request, null);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
Assert.AreEqual(referenceLocal.AddSeconds(callCount), enumerator.Current.EndTime);
VerifyGetSourceInvocationCount(dataSourceReader, 1, "local.file.source", SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
// time didn't pass so should refresh
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
VerifyGetSourceInvocationCount(dataSourceReader, 1, "local.file.source", SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
var expectedInterval = intervalCalls ?? TimeSpan.FromMinutes(30);
timeProvider.Advance(expectedInterval.Add(-TimeSpan.FromSeconds(2)));
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
VerifyGetSourceInvocationCount(dataSourceReader, 1, "local.file.source", SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
timeProvider.Advance(TimeSpan.FromSeconds(2));
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
Assert.AreEqual(referenceLocal.AddSeconds(callCount), enumerator.Current.EndTime);
VerifyGetSourceInvocationCount(dataSourceReader, 2, "local.file.source", SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
}
private static void VerifyGetSourceInvocationCount(Mock<ISubscriptionDataSourceReader> dataSourceReader, int count, string source, SubscriptionTransportMedium medium, FileFormat fileFormat)
{
dataSourceReader.Verify(dsr => dsr.Read(It.Is<SubscriptionDataSource>(sds =>
sds.Source == source && sds.TransportMedium == medium && sds.Format == fileFormat)), Times.Exactly(count));
}
private static SubscriptionRequest GetSubscriptionRequest(SubscriptionDataConfig config, DateTime startTime, DateTime endTime)
{
var quoteCurrency = new Cash(Currencies.USD, 0, 1);
var exchangeHours = MarketHoursDatabase.FromDataFolder().GetExchangeHours(Market.USA, Symbols.SPY, SecurityType.Equity);
var security = new Equity(
Symbols.SPY,
exchangeHours,
quoteCurrency,
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
return new SubscriptionRequest(false, null, security, config, startTime, endTime);
}
class RestData : BaseData
{
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("rest.source", SubscriptionTransportMedium.Rest);
}
}
class RemoteCollectionData : BaseData
{
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("remote.collection.source", SubscriptionTransportMedium.RemoteFile, FileFormat.UnfoldingCollection);
}
}
class RestCollectionData : BaseData
{
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("rest.collection.source", SubscriptionTransportMedium.Rest, FileFormat.UnfoldingCollection);
}
}
class RemoteFileData : BaseData
{
public override DateTime EndTime
{
get { return Time + QuantConnect.Time.OneDay; }
set { Time = value - QuantConnect.Time.OneDay; }
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("remote.file.source", SubscriptionTransportMedium.RemoteFile);
}
}
class LocalFileData : BaseData
{
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("local.file.source", SubscriptionTransportMedium.LocalFile);
}
}
class TestableLiveCustomDataSubscriptionEnumeratorFactory : LiveCustomDataSubscriptionEnumeratorFactory
{
private readonly ISubscriptionDataSourceReader _dataSourceReader;
public TestableLiveCustomDataSubscriptionEnumeratorFactory(ITimeProvider timeProvider, ISubscriptionDataSourceReader dataSourceReader, TimeSpan? minimumIntervalCheck = null)
: base(timeProvider, null, minimumIntervalCheck: minimumIntervalCheck)
{
_dataSourceReader = dataSourceReader;
}
protected override ISubscriptionDataSourceReader GetSubscriptionDataSourceReader(SubscriptionDataSource source,
IDataCacheProvider dataCacheProvider,
SubscriptionDataConfig config,
DateTime date,
BaseData baseData,
IDataProvider dataProvider)
{
return _dataSourceReader;
}
}
}
}
@@ -0,0 +1,111 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class FastForwardEnumeratorTests
{
[Test]
public void FastForwardsOldData()
{
var start = new DateTime(2015, 10, 10, 13, 0, 0);
var data = new List<Tick>
{
new Tick {Time = start.AddMinutes(-1)},
new Tick {Time = start.AddSeconds(-1)},
new Tick {Time = start.AddSeconds(0)},
new Tick {Time = start.AddSeconds(1)},
};
var timeProvider = new ManualTimeProvider(start, TimeZones.Utc);
var fastForward = new FastForwardEnumerator(data.GetEnumerator(), timeProvider, TimeZones.Utc, TimeSpan.FromSeconds(0.5));
Assert.IsTrue(fastForward.MoveNext());
Assert.AreEqual(start, fastForward.Current.Time);
fastForward.Dispose();
}
[Test]
public void FastForwardsOldDataAllowsEquals()
{
var start = new DateTime(2015, 10, 10, 13, 0, 0);
var data = new List<Tick>
{
new Tick {Time = start.AddMinutes(-1)},
new Tick {Time = start.AddSeconds(-1)},
new Tick {Time = start.AddSeconds(0)},
new Tick {Time = start.AddSeconds(1)},
};
var timeProvider = new ManualTimeProvider(start, TimeZones.Utc);
var fastForward = new FastForwardEnumerator(data.GetEnumerator(), timeProvider, TimeZones.Utc, TimeSpan.FromSeconds(1));
Assert.IsTrue(fastForward.MoveNext());
Assert.AreEqual(start.AddSeconds(-1), fastForward.Current.Time);
fastForward.Dispose();
}
[Test]
public void FiltersOutPastData()
{
var start = new DateTime(2015, 10, 10, 13, 0, 0);
var data = new List<Tick>
{
new Tick {Time = start.AddMinutes(-1)},
new Tick {Time = start.AddSeconds(-1)},
new Tick {Time = start.AddSeconds(1)},
new Tick {Time = start.AddSeconds(0)},
new Tick {Time = start.AddSeconds(2)}
};
var timeProvider = new ManualTimeProvider(start, TimeZones.Utc);
var fastForward = new FastForwardEnumerator(data.GetEnumerator(), timeProvider, TimeZones.Utc, TimeSpan.FromSeconds(0.5));
Assert.IsTrue(fastForward.MoveNext());
Assert.AreEqual(start.AddSeconds(1), fastForward.Current.Time);
Assert.IsTrue(fastForward.MoveNext());
Assert.AreEqual(start.AddSeconds(2), fastForward.Current.Time);
fastForward.Dispose();
}
[Test]
public void CurrentIsNullWhenEnumeratorReturnsFalse()
{
var start = new DateTime(2015, 10, 10, 13, 0, 0);
var data = new List<Tick>
{
new Tick {Time = start.AddSeconds(0)}
};
var timeProvider = new ManualTimeProvider(start, TimeZones.Utc);
var fastForward = new FastForwardEnumerator(data.GetEnumerator(), timeProvider, TimeZones.Utc, TimeSpan.FromSeconds(0.5));
Assert.IsTrue(fastForward.MoveNext());
Assert.IsNotNull(fastForward.Current);
Assert.IsFalse(fastForward.MoveNext());
Assert.IsNull(fastForward.Current);
fastForward.Dispose();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,99 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class FrontierAwareEnumeratorTests
{
[Test]
public void ReturnsTrueWhenNextDataIsAheadOfFrontier()
{
var currentTime = new DateTime(2015, 10, 13);
var timeProvider = new ManualTimeProvider(currentTime);
var underlying = new List<Tick>
{
new Tick {Time = currentTime.AddSeconds(1)}
};
var offsetProvider = new TimeZoneOffsetProvider(DateTimeZone.Utc, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1));
var frontierAware = new FrontierAwareEnumerator(underlying.GetEnumerator(), timeProvider, offsetProvider);
Assert.IsTrue(frontierAware.MoveNext());
Assert.IsNull(frontierAware.Current);
frontierAware.Dispose();
}
[Test]
public void YieldsDataWhenFrontierPasses()
{
var currentTime = new DateTime(2015, 10, 13);
var timeProvider = new ManualTimeProvider(currentTime);
var underlying = new List<Tick>
{
new Tick {Time = currentTime.AddSeconds(1)}
};
var offsetProvider = new TimeZoneOffsetProvider(DateTimeZone.Utc, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1));
var frontierAware = new FrontierAwareEnumerator(underlying.GetEnumerator(), timeProvider, offsetProvider);
timeProvider.AdvanceSeconds(1);
Assert.IsTrue(frontierAware.MoveNext());
Assert.IsNotNull(frontierAware.Current);
Assert.AreEqual(underlying[0], frontierAware.Current);
frontierAware.Dispose();
}
[Test]
public void YieldsFutureDataAtCorrectTime()
{
var currentTime = new DateTime(2015, 10, 13);
var timeProvider = new ManualTimeProvider(currentTime);
var underlying = new List<Tick>
{
new Tick {Time = currentTime.AddSeconds(10)}
};
var offsetProvider = new TimeZoneOffsetProvider(DateTimeZone.Utc, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1));
var frontierAware = new FrontierAwareEnumerator(underlying.GetEnumerator(), timeProvider, offsetProvider);
for (int i = 0; i < 10; i++)
{
timeProvider.AdvanceSeconds(1);
Assert.IsTrue(frontierAware.MoveNext());
if (i < 9)
{
Assert.IsNull(frontierAware.Current);
}
else
{
Assert.IsNotNull(frontierAware.Current);
Assert.AreEqual(underlying[0], frontierAware.Current);
}
}
frontierAware.Dispose();
}
}
}
@@ -0,0 +1,306 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using System.Globalization;
using QuantConnect.Securities;
using QuantConnect.Interfaces;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class LiveAuxiliaryDataEnumeratorTests
{
[TestCase(DataMappingMode.OpenInterest, "20130616", false)]
[TestCase(DataMappingMode.FirstDayMonth, "20130602", false)]
[TestCase(DataMappingMode.LastTradingDay, "20130623", false)]
[TestCase(DataMappingMode.OpenInterest, "20130616", true)]
[TestCase(DataMappingMode.FirstDayMonth, "20130602", true)]
[TestCase(DataMappingMode.LastTradingDay, "20130623", true)]
public void EmitsMappingEventsBasedOnCurrentMapFileAndTime(DataMappingMode dataMappingMode, string mappingDate, bool delayed)
{
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.ES_Future_Chain,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false,
dataMappingMode: dataMappingMode);
var symbolMaps = new List<SubscriptionDataConfig.NewSymbolEventArgs>();
config.NewSymbol += (sender, args) => symbolMaps.Add(args);
var time = new DateTime(2013, 05, 28);
var cache = new SecurityCache();
cache.AddData(new Tick(time, config.Symbol, 20, 10));
var timeProvider = new ManualTimeProvider(time);
var futureTicker1 = "es vhle2yxr5blt";
TestMapFileResolver.MapFile = new MapFile(Futures.Indices.SP500EMini, new[]
{
new MapFileRow(Time.BeginningOfTime, Futures.Indices.SP500EMini, Exchange.CME),
new MapFileRow(new DateTime(2013,06,01), futureTicker1, Exchange.CME, DataMappingMode.FirstDayMonth),
new MapFileRow(new DateTime(2013,06,15), futureTicker1, Exchange.CME, DataMappingMode.OpenInterest),
new MapFileRow(new DateTime(2013,06,22), futureTicker1, Exchange.CME, DataMappingMode.LastTradingDay),
});
IEnumerator<BaseData> enumerator;
Assert.IsTrue(LiveAuxiliaryDataEnumerator.TryCreate(config, timeProvider, cache, new TestMapFileProvider(), TestGlobals.FactorFileProvider, time, out enumerator));
// get's mapped right away!
Assert.AreEqual(futureTicker1.ToUpper(), config.MappedSymbol);
Assert.AreEqual(1, symbolMaps.Count);
Assert.AreEqual(Symbols.ES_Future_Chain, symbolMaps[0].Old);
Assert.AreEqual(Futures.Indices.SP500EMini, symbolMaps[0].Old.ID.Symbol);
Assert.AreEqual(Symbols.ES_Future_Chain, symbolMaps[0].New);
Assert.AreEqual(futureTicker1.ToUpper(), symbolMaps[0].New.Underlying.ID.ToString());
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var expectedMappingDate = DateTime.ParseExact(mappingDate, DateFormat.EightCharacter, CultureInfo.InvariantCulture);
if (delayed)
{
// we advance to the mapping date, without any new mapFile!
timeProvider.Advance(expectedMappingDate.ConvertToUtc(config.ExchangeTimeZone) - timeProvider.GetUtcNow() + Time.LiveAuxiliaryDataOffset);
}
else
{
// just advance a day to show nothing happens until mapping time
timeProvider.Advance(TimeSpan.FromDays(1));
}
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var futureTicker2 = "es vk2zrh843z7l";
TestMapFileResolver.MapFile = new MapFile(Futures.Indices.SP500EMini, TestMapFileResolver.MapFile.Concat(
new[]
{
new MapFileRow(new DateTime(2013,09,01), futureTicker2, Exchange.CME, DataMappingMode.FirstDayMonth),
new MapFileRow(new DateTime(2013,09,14), futureTicker2, Exchange.CME, DataMappingMode.OpenInterest),
new MapFileRow(new DateTime(2013,09,21), futureTicker2, Exchange.CME, DataMappingMode.LastTradingDay),
}));
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
if (delayed)
{
// we got a new mapFile! advance the date and expect mapping to have happened
timeProvider.Advance(TimeSpan.FromDays(1));
}
else
{
// we advance to the mapping date
timeProvider.Advance(expectedMappingDate.ConvertToUtc(config.ExchangeTimeZone) - timeProvider.GetUtcNow() + Time.LiveAuxiliaryDataOffset);
}
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
Assert.AreEqual(2, symbolMaps.Count);
Assert.AreEqual(Symbols.ES_Future_Chain, symbolMaps[1].Old);
Assert.AreEqual(futureTicker1.ToUpper(), symbolMaps[1].Old.Underlying.ID.ToString());
Assert.AreEqual(Symbols.ES_Future_Chain, symbolMaps[1].New);
Assert.AreEqual(futureTicker2.ToUpper(), symbolMaps[1].New.Underlying.ID.ToString());
Assert.AreEqual(futureTicker2.ToUpper(), config.MappedSymbol);
Assert.AreEqual(futureTicker2.ToUpper(), (enumerator.Current as SymbolChangedEvent).NewSymbol);
Assert.AreEqual(futureTicker1.ToUpper(), (enumerator.Current as SymbolChangedEvent).OldSymbol);
Assert.AreEqual(config.Symbol, (enumerator.Current as SymbolChangedEvent).Symbol);
Assert.AreEqual(timeProvider.GetUtcNow().Date, (enumerator.Current as SymbolChangedEvent).Time);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
enumerator.Dispose();
}
[Test]
public void EmitsDelistingEventsBasedOnCurrentTime()
{
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY_C_192_Feb19_2016,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var delistingDate = config.Symbol.GetDelistingDate();
var time = delistingDate.AddDays(-10);
var cache = new SecurityCache();
cache.AddData(new Tick(DateTime.UtcNow, config.Symbol, 20, 10));
var timeProvider = new ManualTimeProvider(time);
IEnumerator<BaseData> enumerator;
Assert.IsTrue(LiveAuxiliaryDataEnumerator.TryCreate(config, timeProvider, cache, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, config.Symbol.ID.Date, out enumerator));
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
// advance until delisting date, take into account 5 hour offset of NY + TradableDateOffset
timeProvider.Advance(TimeSpan.FromDays(10));
timeProvider.Advance(TimeSpan.FromHours(5));
timeProvider.Advance(Time.LiveAuxiliaryDataOffset);
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(DelistingType.Warning, (enumerator.Current as Delisting).Type);
Assert.AreEqual(config.Symbol, (enumerator.Current as Delisting).Symbol);
Assert.AreEqual(delistingDate, (enumerator.Current as Delisting).Time);
Assert.AreEqual(15, (enumerator.Current as Delisting).Price);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
// when the day ends the delisted event will pass through, respecting the offset
timeProvider.Advance(TimeSpan.FromDays(1));
cache.AddData(new Tick(DateTime.UtcNow, config.Symbol, 40, 20));
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(DelistingType.Delisted, (enumerator.Current as Delisting).Type);
Assert.AreEqual(config.Symbol, (enumerator.Current as Delisting).Symbol);
Assert.AreEqual(delistingDate.AddDays(1), (enumerator.Current as Delisting).Time);
Assert.AreEqual(30, (enumerator.Current as Delisting).Price);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
enumerator.Dispose();
}
[TestCase(false)]
[TestCase(true)]
public void EquityEmitsDelistingEventsBasedOnCurrentTime(bool delayed)
{
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var time = new DateTime(2013, 05, 28);
var cache = new SecurityCache();
cache.AddData(new Tick(time, config.Symbol, 20, 10));
var timeProvider = new ManualTimeProvider(time);
TestMapFileResolver.MapFile = new MapFile(config.Symbol.ID.Symbol, new[]
{
new MapFileRow(Time.BeginningOfTime, config.Symbol.ID.Symbol),
new MapFileRow(Time.EndOfTime, config.Symbol.ID.Symbol),
});
IEnumerator<BaseData> enumerator;
Assert.IsTrue(LiveAuxiliaryDataEnumerator.TryCreate(config, timeProvider, cache, new TestMapFileProvider(), TestGlobals.FactorFileProvider, time, out enumerator));
// get's mapped right away!
Assert.AreEqual("SPY", config.MappedSymbol);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var delistingDate = time.AddDays(2);
var delistedMapFile = new MapFile(config.Symbol.ID.Symbol, new[]
{
new MapFileRow(Time.BeginningOfTime, config.Symbol.ID.Symbol),
new MapFileRow(delistingDate, config.Symbol.ID.Symbol),
});
// just advance a day to show nothing happens until mapping time
timeProvider.Advance(TimeSpan.FromDays(1));
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
if (!delayed)
{
TestMapFileResolver.MapFile = delistedMapFile;
}
// we advance to the mapping date, without any new mapFile!
timeProvider.Advance(delistingDate.ConvertToUtc(config.ExchangeTimeZone) - timeProvider.GetUtcNow() + Time.LiveAuxiliaryDataOffset);
if (delayed)
{
// nothing happens
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
TestMapFileResolver.MapFile = delistedMapFile;
timeProvider.Advance(Time.OneDay);
}
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
var delisted = enumerator.Current as Delisting;
Assert.IsNotNull(delisted);
Assert.AreEqual(DelistingType.Warning, delisted.Type);
if (!delayed)
{
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
// delisting passed
timeProvider.Advance(TimeSpan.FromDays(1));
}
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
delisted = enumerator.Current as Delisting;
Assert.IsNotNull(delisted);
Assert.AreEqual(DelistingType.Delisted, delisted.Type);
enumerator.Dispose();
}
private class TestMapFileProvider : IMapFileProvider
{
public void Initialize(IDataProvider dataProvider)
{
}
public MapFileResolver Get(AuxiliaryDataKey auxiliaryDataKey)
{
return new TestMapFileResolver();
}
}
private class TestMapFileResolver : MapFileResolver
{
public static MapFile MapFile { get; set; }
public TestMapFileResolver()
: base(new[] { MapFile })
{
}
}
}
}
@@ -0,0 +1,80 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Logging;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class LiveEquityDataSynchronizingEnumeratorTests
{
// this test case generates data points in the past, will complete very quickly
[TestCase(-15, 1)]
// this test case generates data points in the future, will require at least 10 seconds to complete
[TestCase(0, 11)]
public void SynchronizesData(int timeOffsetSeconds, int testTimeSeconds)
{
var start = DateTime.UtcNow;
var end = start.AddSeconds(testTimeSeconds);
var time = start;
var tickList1 = Enumerable.Range(0, 10).Select(x => new Tick { Time = time.AddSeconds(x * 1 + timeOffsetSeconds), Value = x }).ToList();
var tickList2 = Enumerable.Range(0, 5).Select(x => new Tick { Time = time.AddSeconds(x * 2 + timeOffsetSeconds), Value = x + 100 }).ToList();
var stream1 = tickList1.GetEnumerator();
var stream2 = tickList2.GetEnumerator();
var count1 = 0;
var count2 = 0;
var previous = DateTime.MinValue;
var synchronizer = new LiveAuxiliaryDataSynchronizingEnumerator(new RealTimeProvider(), DateTimeZone.Utc, stream1, new List<IEnumerator<BaseData>> { stream2 });
while (synchronizer.MoveNext() && DateTime.UtcNow < end)
{
if (synchronizer.Current != null)
{
if (synchronizer.Current.Value < 100)
{
Assert.AreEqual(count1, synchronizer.Current.Value);
count1++;
}
else
{
Assert.AreEqual(count2 + 100, synchronizer.Current.Value);
count2++;
}
Assert.That(synchronizer.Current.EndTime, Is.GreaterThanOrEqualTo(previous));
previous = synchronizer.Current.EndTime;
Log.Trace($"Data point emitted: {synchronizer.Current.EndTime:O} - {synchronizer.Current}");
}
}
Log.Trace($"Total point count: {count1 + count2}");
Assert.AreEqual(tickList1.Count, count1);
Assert.AreEqual(tickList2.Count, count2);
synchronizer.Dispose();
}
}
}
@@ -0,0 +1,577 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Util;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class LiveFillForwardEnumeratorTests
{
[Test]
public void FillsForwardOnNulls()
{
var reference = new DateTime(2015, 10, 08);
var period = Time.OneSecond;
var underlying = new List<BaseData>
{
// 0 seconds
new TradeBar(reference, Symbols.SPY, 10, 20, 5, 15, 123456, period),
// 1 seconds
null,
// 3 seconds
new TradeBar(reference.AddSeconds(2), Symbols.SPY, 100, 200, 50, 150, 1234560, period),
null,
null,
null,
null
};
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(reference);
var exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
var fillForward = new LiveFillForwardEnumerator(timeProvider, underlying.GetEnumerator(), exchange, Ref.Create(Time.OneSecond), false, reference, Time.EndOfTime, Resolution.Second, exchange.TimeZone, false);
// first point is always emitted
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[0], fillForward.Current);
Assert.AreEqual(123456, ((TradeBar)fillForward.Current).Volume);
// stepping again without advancing time does nothing, but we'll still
// return true as per IEnumerator contract
Assert.IsTrue(fillForward.MoveNext());
Assert.IsNull(fillForward.Current);
timeProvider.SetCurrentTime(reference.AddSeconds(2));
// non-null next will fill forward in between
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[0].EndTime, fillForward.Current.Time);
Assert.AreEqual(underlying[0].Value, fillForward.Current.Value);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
// even without stepping the time this will advance since non-null data is ready
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2], fillForward.Current);
Assert.AreEqual(1234560, ((TradeBar)fillForward.Current).Volume);
// step ahead into null data territory
timeProvider.SetCurrentTime(reference.AddSeconds(4));
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2].Value, fillForward.Current.Value);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork).RoundDown(Time.OneSecond), fillForward.Current.EndTime);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
Assert.IsTrue(fillForward.MoveNext());
Assert.IsNull(fillForward.Current);
timeProvider.SetCurrentTime(reference.AddSeconds(5));
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2].Value, fillForward.Current.Value);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork).RoundDown(Time.OneSecond), fillForward.Current.EndTime);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
timeProvider.SetCurrentTime(reference.AddSeconds(6));
Assert.IsTrue(fillForward.MoveNext());
Assert.AreEqual(underlying[2].Value, fillForward.Current.Value);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork).RoundDown(Time.OneSecond), fillForward.Current.EndTime);
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
fillForward.Dispose();
}
[Test]
public void HandlesDaylightSavingTimeChange()
{
// In 2018, Daylight Saving Time (DST) began at 2 AM on Sunday, March 11
// This means that clocks were moved forward one hour on March 11
var reference = new DateTime(2018, 3, 10);
var period = Time.OneDay;
var underlying = new List<TradeBar>
{
new TradeBar(reference, Symbols.SPY, 10, 20, 5, 15, 123456, period),
// Daylight Saving Time change, the data still goes from midnight to midnight
new TradeBar(reference.AddDays(1), Symbols.SPY, 100, 200, 50, 150, 1234560, period)
};
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(reference);
var exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork));
var fillForward = new LiveFillForwardEnumerator(
timeProvider,
underlying.GetEnumerator(),
exchange,
Ref.Create(Time.OneDay),
false,
reference,
Time.EndOfTime,
Resolution.Daily,
exchange.TimeZone, false);
// first point is always emitted
Assert.IsTrue(fillForward.MoveNext());
Assert.IsFalse(fillForward.Current.IsFillForward);
Assert.AreEqual(underlying[0], fillForward.Current);
//Assert.AreEqual(underlying[0].EndTime, fillForward.Current.EndTime);
Assert.AreEqual(123456, ((TradeBar)fillForward.Current).Volume);
// Daylight Saving Time change -> add 1 hour
timeProvider.SetCurrentTime(reference.AddDays(1).AddHours(1));
// second data point emitted
Assert.IsTrue(fillForward.MoveNext());
Assert.IsFalse(fillForward.Current.IsFillForward);
Assert.AreEqual(underlying[1], fillForward.Current);
//Assert.AreEqual(underlying[1].EndTime, fillForward.Current.EndTime);
Assert.AreEqual(1234560, ((TradeBar)fillForward.Current).Volume);
Assert.IsTrue(fillForward.MoveNext());
Assert.IsTrue(fillForward.Current.IsFillForward);
Assert.AreEqual(underlying[1].EndTime, fillForward.Current.Time);
Assert.AreEqual(underlying[1].Value, fillForward.Current.Value);
Assert.AreEqual(0, ((TradeBar)fillForward.Current).Volume);
fillForward.Dispose();
}
[Test]
public void LiveFillForwardEnumeratorDoesNotStall()
{
var reference = new DateTime(2020, 5, 21, 9, 40, 0, 100);
var timeProvider = new ManualTimeProvider(reference, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, reference.Date, Resolution.Minute, out var enqueueableEnumerator, false);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
EndTime = new DateTime(2020, 5, 21, 9, 40, 0),
Symbol = Symbols.AAPL
};
var secondBar = new TradeBar
{
Open = 1m,
High = 2m,
Low = 1m,
Close = 2m,
Volume = 100,
EndTime = new DateTime(2020, 5, 21, 9, 42, 0),
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we expect a fill-forward bar.
timeProvider.SetCurrentTime(new DateTime(2020, 5, 21, 9, 41, 0, 100) + LiveFillForwardEnumerator.GetMaximumDataTimeout(Resolution.Minute));
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime.AddMinutes(1), fillForwardEnumerator.Current.EndTime);
// Now we expect data. The secondBar should be fill-forwarded from here on out after the MoveNext
timeProvider.SetCurrentTime(new DateTime(2020, 5, 21, 9, 42, 0, 100));
enqueueableEnumerator.Enqueue(secondBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
enqueueableEnumerator.Dispose();
}
private static IEnumerable<TestCaseData> TimeOutTestCases
{
get
{
// Hour resolution, fill forward to market open
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 10, 0, 0), true, true);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 10, 0, 0), false, false);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 10, 0, 0), null, true, false);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 10, 0, 0), null, false, false);
// over a weekend (22th is a Friday and 25th Monday is a holiday)
yield return new(Resolution.Hour, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 10, 0, 0), true, true);
yield return new(Resolution.Hour, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 10, 0, 0), false, false);
// market close
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 15, 0, 0), null, true, true);
yield return new(Resolution.Hour, new DateTime(2020, 5, 21, 15, 0, 0), null, false, false);
// Minute resolution, fill forward to market open
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 31, 0), true, true);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 31, 0), false, false);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 9, 31, 0), null, true, false);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 9, 31, 0), null, false, false);
// over a weekend
yield return new(Resolution.Minute, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 31, 0), true, true);
yield return new(Resolution.Minute, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 31, 0), false, false);
// market close
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 15, 59, 0), null, true, true);
yield return new(Resolution.Minute, new DateTime(2020, 5, 21, 15, 59, 0), null, false, false);
// Second resolution, fill forward to market open
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 30, 1), true, true);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 16, 0, 0), new DateTime(2020, 5, 22, 9, 30, 1), false, false);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 9, 30, 1), null, true, false);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 9, 30, 1), null, false, false);
// over a weekend
yield return new(Resolution.Second, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 30, 1), true, true);
yield return new(Resolution.Second, new DateTime(2020, 5, 22, 16, 0, 0), new DateTime(2020, 5, 26, 9, 30, 1), false, false);
// market close
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 15, 59, 59), null, true, true);
yield return new(Resolution.Second, new DateTime(2020, 5, 21, 15, 59, 59), null, false, false);
}
}
[TestCaseSource(nameof(TimeOutTestCases))]
public void TakesIntoAccountTimeOut(Resolution resolution, DateTime previousDataEndTime, DateTime? expectedNextBarEndTime, bool dataArrivedLate, bool shouldHaveTimeout)
{
var timeProvider = new ManualTimeProvider(previousDataEndTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, previousDataEndTime.Date, resolution, out var enqueueableEnumerator, dailyStrictEndTimeEnabled: false);
var period = resolution.ToTimeSpan();
var openingBar = new TradeBar(previousDataEndTime.Subtract(period), Symbols.AAPL, 0.01m, 0.01m, 0.01m, 0.01m, 1, period);
expectedNextBarEndTime ??= previousDataEndTime.Add(resolution.ToTimeSpan());
var secondBar = new TradeBar(openingBar.EndTime, Symbols.AAPL, 1m, 2m, 1m, 2m, 100, period);
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we don't expect a fill-forward bar because the timeout amount has not passed yet
timeProvider.SetCurrentTime(expectedNextBarEndTime.Value);
if (dataArrivedLate)
{
Assert.IsTrue(fillForwardEnumerator.MoveNext());
if (shouldHaveTimeout)
{
Assert.IsNull(fillForwardEnumerator.Current);
// Advance the time, including the expected timout, we expect a fill-forward bar.
timeProvider.Advance(LiveFillForwardEnumerator.GetMaximumDataTimeout(resolution));
Assert.IsTrue(fillForwardEnumerator.MoveNext());
}
Assert.IsNotNull(fillForwardEnumerator.Current);
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(expectedNextBarEndTime, fillForwardEnumerator.Current.EndTime);
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(expectedNextBarEndTime, fillForwardEnumerator.Current.EndTime);
}
// Now we expect data. The secondBar should be fill-forwarded from here on out after the MoveNext
enqueueableEnumerator.Enqueue(secondBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
enqueueableEnumerator.Dispose();
}
[Test]
public void TakesIntoAccountTimeOutWhenThereAreBigGaps([Values(Resolution.Second, Resolution.Minute, Resolution.Hour)] Resolution resolution)
{
var start = new DateTime(2020, 5, 20, 12, 0, 0);
var end = new DateTime(2020, 5, 22, 16, 0, 0);
var timeProvider = new ManualTimeProvider(start, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, start, resolution, out var enqueueableEnumerator, dailyStrictEndTimeEnabled: false);
var period = resolution.ToTimeSpan();
var openingBar = new TradeBar(start.Subtract(period), Symbols.AAPL, 0.01m, 0.01m, 0.01m, 0.01m, 1, period);
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
timeProvider.Advance(period);
var previous = (TradeBar)fillForwardEnumerator.Current;
var currentIsMarketOpen = false;
var currentIsMarketClose = false;
while (previous.EndTime < end)
{
var currentTime = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
Assert.IsTrue(fillForwardEnumerator.MoveNext(), $"Previous: {previous.EndTime}");
if (currentIsMarketOpen || currentIsMarketClose)
{
Assert.IsNull(fillForwardEnumerator.Current, $"Previous: {previous.EndTime}");
// Advance the time, including the expected timout, we expect a fill-forward bar.
timeProvider.Advance(LiveFillForwardEnumerator.GetMaximumDataTimeout(resolution));
Assert.IsTrue(fillForwardEnumerator.MoveNext(), $"Previous: {previous.EndTime}");
}
Assert.IsNotNull(fillForwardEnumerator.Current, $"Previous: {previous.EndTime}");
var current = (TradeBar)fillForwardEnumerator.Current;
Assert.IsTrue(current.IsFillForward, $"Current: {previous.EndTime}");
Assert.AreEqual(previous.Open, current.Open, $"Current: {previous.EndTime}");
Assert.AreEqual(previous.Price, current.Price, $"Current: {previous.EndTime}");
var expectedEndTime = previous.EndTime.Add(period);
if (currentIsMarketOpen)
{
expectedEndTime = expectedEndTime.Date.AddDays(1).AddHours(9).AddMinutes(30).Add(period);
if (resolution == Resolution.Hour)
{
expectedEndTime = expectedEndTime.RoundDown(period);
}
}
Assert.AreEqual(expectedEndTime, current.EndTime, $"Current: {previous.EndTime}");
currentIsMarketOpen = current.EndTime.TimeOfDay == TimeSpan.FromHours(16);
currentIsMarketClose = current.EndTime.TimeOfDay == TimeSpan.FromHours(16) - period;
previous = current;
// Advance the time provider
var nextTime = current.EndTime.Add(period);
if (nextTime.TimeOfDay > TimeSpan.FromHours(16))
{
// If the next time is after market close, we need to advance to the next day
nextTime = nextTime.Date.AddDays(1).AddHours(9).AddMinutes(30).Add(period);
if (resolution == Resolution.Hour)
{
nextTime = nextTime.RoundDown(period);
}
}
timeProvider.SetCurrentTime(nextTime);
}
enqueueableEnumerator.Dispose();
}
[TestCase(true, true)]
[TestCase(false, true)]
[TestCase(true, false)]
[TestCase(false, false)]
public void TakesIntoAccountTimeOutDaily(bool dailyStrictEndTimeEnabled, bool dataArrivedLate)
{
var resolution = Resolution.Daily;
var referenceOpenTime = new DateTime(2020, 5, 21, 9, 30, 0);
var referenceTime = new DateTime(2020, 5, 21, 16, 0, 0);
if (!dailyStrictEndTimeEnabled)
{
referenceOpenTime = referenceOpenTime.Date;
referenceTime = referenceTime.Date.AddDays(1);
}
var timeProvider = new ManualTimeProvider(referenceTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, referenceTime.Date, resolution, out var enqueueableEnumerator, dailyStrictEndTimeEnabled);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
Time = referenceOpenTime,
EndTime = referenceTime,
Symbol = Symbols.AAPL
};
var secondBar = new TradeBar
{
Open = 1m,
High = 2m,
Low = 1m,
Close = 2m,
Volume = 100,
Time = referenceOpenTime.AddDays(1),
EndTime = referenceTime.AddDays(1),
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we don't expect a fill-forward bar because the timeout amount has not passed yet
timeProvider.SetCurrentTime(secondBar.EndTime);
if (dataArrivedLate)
{
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsNull(fillForwardEnumerator.Current);
// Advance the time, including the expected timout, we expect a fill-forward bar.
timeProvider.Advance(LiveFillForwardEnumerator.GetMaximumDataTimeout(resolution));
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsTrue(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(secondBar.EndTime, fillForwardEnumerator.Current.EndTime);
}
// Now we expect data. The secondBar should be fill-forwarded from here on out after the MoveNext
enqueueableEnumerator.Enqueue(secondBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsFalse(fillForwardEnumerator.Current.IsFillForward);
Assert.AreEqual(secondBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(secondBar.EndTime, fillForwardEnumerator.Current.EndTime);
enqueueableEnumerator.Dispose();
}
[TestCase(Resolution.Minute)]
[TestCase(Resolution.Hour)]
public void MultiResolutionSmallerFillForwardResolution(Resolution resolution)
{
var ffResolution = Resolution.Second;
var referenceOpenTime = new DateTime(2020, 5, 21, 14, 0, 0);
if (resolution == Resolution.Minute)
{
referenceOpenTime = new DateTime(2020, 5, 21, 15, 59, 0);
}
var referenceTime = new DateTime(2020, 5, 21, 15, 0, 0);
var timeProvider = new ManualTimeProvider(referenceTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, referenceTime.Date, resolution, out var enqueueableEnumerator, true, ffResolution);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
Time = referenceOpenTime,
EndTime = referenceTime,
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
// Advance the time, we expect a fill-forward bar
for (var i = 0; i < 60; i++)
{
timeProvider.Advance(Time.OneSecond);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork), fillForwardEnumerator.Current.EndTime);
}
enqueueableEnumerator.Dispose();
}
[TestCase(Resolution.Daily, Resolution.Minute)]
[TestCase(Resolution.Hour, Resolution.Minute)]
[TestCase(Resolution.Daily, Resolution.Second)]
[TestCase(Resolution.Hour, Resolution.Second)]
public void MultiResolutionMarketClose(Resolution resolution, Resolution ffResolution)
{
var referenceOpenTime = new DateTime(2020, 5, 21, 9, 30, 0);
if (resolution == Resolution.Hour)
{
referenceOpenTime = new DateTime(2020, 5, 21, 15, 0, 0);
}
var referenceTime = new DateTime(2020, 5, 21, 16, 0, 0);
var timeProvider = new ManualTimeProvider(referenceTime, TimeZones.NewYork);
using var fillForwardEnumerator = GetLiveFillForwardEnumerator(timeProvider, referenceTime.Date, resolution, out var enqueueableEnumerator, true, ffResolution);
var openingBar = new TradeBar
{
Open = 0.01m,
High = 0.01m,
Low = 0.01m,
Close = 0.01m,
Volume = 1,
Time = referenceOpenTime,
EndTime = referenceTime,
Symbol = Symbols.AAPL
};
// Enqueue the first point, which will be emitted ASAP.
enqueueableEnumerator.Enqueue(openingBar);
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.NotNull(fillForwardEnumerator.Current);
Assert.AreEqual(openingBar.Open, ((TradeBar)fillForwardEnumerator.Current).Open);
Assert.AreEqual(openingBar.EndTime, fillForwardEnumerator.Current.EndTime);
for (var i = 0; i < 600; i++)
{
// Advance the time, we don't expect a fill-forward bar, we've emitted our daily bar already and market is closed
timeProvider.Advance(ffResolution.ToTimeSpan());
Assert.IsTrue(fillForwardEnumerator.MoveNext());
Assert.IsNull(fillForwardEnumerator.Current);
}
enqueueableEnumerator.Dispose();
}
private static LiveFillForwardEnumerator GetLiveFillForwardEnumerator(ITimeProvider timeProvider, DateTime startTime, Resolution resolution,
out EnqueueableEnumerator<BaseData> enqueueableEnumerator, bool dailyStrictEndTimeEnabled, Resolution? ffResolution = null)
{
enqueueableEnumerator = new EnqueueableEnumerator<BaseData>();
var fillForwardEnumerator = new LiveFillForwardEnumerator(
timeProvider,
enqueueableEnumerator,
new SecurityExchange(MarketHoursDatabase.FromDataFolder()
.ExchangeHoursListing
.First(kvp => kvp.Key.Market == Market.USA && kvp.Key.SecurityType == SecurityType.Equity)
.Value
.ExchangeHours),
Ref.CreateReadOnly(() => (ffResolution ?? resolution).ToTimeSpan()),
false,
startTime,
Time.EndOfTime,
resolution,
TimeZones.NewYork,
dailyStrictEndTimeEnabled: dailyStrictEndTimeEnabled
);
return fillForwardEnumerator;
}
}
}
@@ -0,0 +1,116 @@
/*
* 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 NodaTime;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Packets;
using QuantConnect.Interfaces;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class LiveSubscriptionEnumeratorTests
{
[Test]
public void HandlesSymbolMapping()
{
var canonical = Symbols.Fut_SPY_Feb19_2016.Canonical;
var dataQueue = new TestDataQueueHandler
{
DataPerSymbol = new Dictionary<Symbol, List<BaseData>>
{
{ Symbols.Fut_SPY_Feb19_2016,
new List<BaseData>{ new Tick(Time.BeginningOfTime, Symbols.Fut_SPY_Feb19_2016, 1, 1)} },
{ Symbols.Fut_SPY_Mar19_2016,
new List<BaseData>{ new Tick(Time.BeginningOfTime, Symbols.Fut_SPY_Mar19_2016, 2, 2)} },
}
};
var config = new SubscriptionDataConfig(typeof(Tick), canonical, Resolution.Tick,
DateTimeZone.Utc, DateTimeZone.Utc, false, false, false)
{
MappedSymbol = Symbols.Fut_SPY_Feb19_2016.ID.ToString()
};
var compositeDataQueueHandler = new TestDataQueueHandlerManager(new AlgorithmSettings());
compositeDataQueueHandler.ExposedDataHandlers.Add(dataQueue);
var data = new LiveSubscriptionEnumerator(config, compositeDataQueueHandler, (_, _) => {}, (_) => false);
Assert.IsTrue(data.MoveNext());
Assert.AreEqual(1, (data.Current as Tick).AskPrice);
Assert.AreEqual(canonical, (data.Current as Tick).Symbol);
Assert.IsFalse(data.MoveNext());
Assert.IsNull(data.Current);
Assert.AreEqual(2, dataQueue.DataPerSymbol.Count);
config.MappedSymbol = Symbols.Fut_SPY_Mar19_2016.ID.ToString();
Assert.AreEqual(1, dataQueue.DataPerSymbol.Count);
Assert.IsTrue(data.MoveNext());
Assert.AreEqual(2, (data.Current as Tick).AskPrice);
Assert.AreEqual(canonical, (data.Current as Tick).Symbol);
Assert.AreNotEqual(canonical, dataQueue.DataPerSymbol[Symbols.Fut_SPY_Mar19_2016].Single().Symbol);
Assert.IsFalse(data.MoveNext());
Assert.IsNull(data.Current);
Assert.AreEqual(1, dataQueue.DataPerSymbol.Count);
data.Dispose();
compositeDataQueueHandler.Dispose();
}
internal class TestDataQueueHandler : IDataQueueHandler
{
public bool IsConnected => true;
public Dictionary<Symbol, List<BaseData>> DataPerSymbol;
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
{
if (DataPerSymbol.TryGetValue(dataConfig.Symbol, out var baseDatas))
{
return baseDatas.GetEnumerator();
}
throw new Exception($"Failed to find a data enumerator for symbol {dataConfig.Symbol}!");
}
public void Unsubscribe(SubscriptionDataConfig dataConfig)
{
DataPerSymbol.Remove(dataConfig.Symbol);
}
public void SetJob(LiveNodePacket job)
{
}
public void Dispose()
{
}
}
private class TestDataQueueHandlerManager : DataQueueHandlerManager
{
public List<IDataQueueHandler> ExposedDataHandlers => DataHandlers;
public TestDataQueueHandlerManager(IAlgorithmSettings settings) : base(settings)
{
}
}
}
}
@@ -0,0 +1,84 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class MappingEventProviderTests
{
private SubscriptionDataConfig _config;
[SetUp]
public void SetUp()
{
var symbol = Symbol.Create("TFCFA", SecurityType.Equity, Market.USA);
_config = new SubscriptionDataConfig(typeof(TradeBar),
symbol,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
}
[Test]
public void InitialMapping()
{
var provider = new MappingEventProvider();
Assert.AreEqual("TFCFA", _config.MappedSymbol);
provider.Initialize(_config,
null,
TestGlobals.MapFileProvider,
new DateTime(2006, 1, 1));
Assert.AreEqual("NWSA", _config.MappedSymbol);
}
[Test]
public void MappingEvent()
{
var provider = new MappingEventProvider();
provider.Initialize(_config,
null,
TestGlobals.MapFileProvider,
new DateTime(2006, 1, 1));
Assert.AreEqual("NWSA", _config.MappedSymbol);
var symbolEvent = (SymbolChangedEvent)provider
.GetEvents(new NewTradableDateEventArgs(
new DateTime(2013, 6, 29),
null,
_config.Symbol,
null)).Single();
Assert.AreEqual("FOXA", symbolEvent.NewSymbol);
Assert.AreEqual("NWSA", symbolEvent.OldSymbol);
Assert.AreEqual("FOXA", _config.MappedSymbol);
}
}
}
@@ -0,0 +1,362 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using System.Collections;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using Tick = QuantConnect.Data.Market.Tick;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using System.Linq;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class PriceScaleFactorEnumeratorTests
{
private SubscriptionDataConfig _config;
private RawDataEnumerator _rawDataEnumerator;
[SetUp]
public void Setup()
{
_config = GetConfig(Symbols.SPY, Resolution.Daily);
_rawDataEnumerator = new RawDataEnumerator();
}
[Test]
public void EquityTradeBar()
{
var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
_config,
TestGlobals.FactorFileProvider);
_rawDataEnumerator.CurrentValue = new TradeBar(
new DateTime(2018, 1, 1),
_config.Symbol,
10,
10,
10,
10,
100);
Assert.IsTrue(enumerator.MoveNext());
var tradeBar = enumerator.Current as TradeBar;
var expectedValue = 10 * _config.PriceScaleFactor;
Assert.Less(expectedValue, 10);
Assert.AreEqual(expectedValue, tradeBar.Price);
Assert.AreEqual(expectedValue, tradeBar.Open);
Assert.AreEqual(expectedValue, tradeBar.Close);
Assert.AreEqual(expectedValue, tradeBar.High);
Assert.AreEqual(expectedValue, tradeBar.Low);
Assert.AreEqual(expectedValue, tradeBar.Value);
enumerator.Dispose();
}
[Test]
public void EquityQuoteBar()
{
var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
_config,
TestGlobals.FactorFileProvider);
_rawDataEnumerator.CurrentValue = new QuoteBar(
new DateTime(2018, 1, 1),
_config.Symbol,
new Bar(10, 10, 10, 10),
100,
new Bar(10, 10, 10, 10),
100);
Assert.IsTrue(enumerator.MoveNext());
var quoteBar = enumerator.Current as QuoteBar;
var expectedValue = 10 * _config.PriceScaleFactor;
Assert.Less(expectedValue, 10);
Assert.AreEqual(expectedValue, quoteBar.Price);
Assert.AreEqual(expectedValue, quoteBar.Value);
Assert.AreEqual(expectedValue, quoteBar.Open);
Assert.AreEqual(expectedValue, quoteBar.Close);
Assert.AreEqual(expectedValue, quoteBar.High);
Assert.AreEqual(expectedValue, quoteBar.Low);
// bid
Assert.AreEqual(expectedValue, quoteBar.Bid.Open);
Assert.AreEqual(expectedValue, quoteBar.Bid.Close);
Assert.AreEqual(expectedValue, quoteBar.Bid.High);
Assert.AreEqual(expectedValue, quoteBar.Bid.Low);
// ask
Assert.AreEqual(expectedValue, quoteBar.Ask.Open);
Assert.AreEqual(expectedValue, quoteBar.Ask.Close);
Assert.AreEqual(expectedValue, quoteBar.Ask.High);
Assert.AreEqual(expectedValue, quoteBar.Ask.Low);
enumerator.Dispose();
}
[Test]
public void EquityTick()
{
var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
_config,
TestGlobals.FactorFileProvider);
_rawDataEnumerator.CurrentValue = new Tick(
new DateTime(2018, 1, 1),
_config.Symbol,
10,
10,
10);
Assert.IsTrue(enumerator.MoveNext());
var tick = enumerator.Current as Tick;
var expectedValue = 10 * _config.PriceScaleFactor;
Assert.Less(expectedValue, 10);
Assert.AreEqual(expectedValue, tick.Price);
Assert.AreEqual(expectedValue, tick.Value);
enumerator.Dispose();
}
[Test]
public void FactorFileIsNull()
{
var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
_config,
null);
_rawDataEnumerator.CurrentValue = new Tick(
new DateTime(2018, 1, 1),
_config.Symbol,
10,
10,
10);
Assert.IsTrue(enumerator.MoveNext());
var tick = enumerator.Current as Tick;
Assert.AreEqual(10, tick.Price);
Assert.AreEqual(10, tick.Value);
enumerator.Dispose();
}
[Test]
public void RawEnumeratorReturnsFalse()
{
var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
_config,
TestGlobals.FactorFileProvider);
_rawDataEnumerator.CurrentValue = new Tick(
new DateTime(2018, 1, 1),
_config.Symbol,
10,
10,
10);
_rawDataEnumerator.MoveNextReturnValue = false;
Assert.IsFalse(enumerator.MoveNext());
Assert.AreEqual(_rawDataEnumerator.CurrentValue, enumerator.Current);
enumerator.Dispose();
}
[Test]
public void RawEnumeratorCurrentIsNull()
{
var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
_config,
TestGlobals.FactorFileProvider);
_rawDataEnumerator.CurrentValue = null;
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
enumerator.Dispose();
}
[Test]
public void UpdatesFactorFileCorrectly()
{
var dateBeforeUpadate = new DateTime(2018, 3, 14);
var dateAtUpadate = new DateTime(2018, 3, 15);
var dateAfterUpadate = new DateTime(2018, 3, 16);
var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
_config,
TestGlobals.FactorFileProvider);
// Before factor file update date (2018, 3, 15)
_rawDataEnumerator.CurrentValue = new Tick(
dateBeforeUpadate,
_config.Symbol,
10,
10,
10);
Assert.IsTrue(enumerator.MoveNext());
var factorFile = TestGlobals.FactorFileProvider.Get(_config.Symbol);
var expectedFactor = factorFile.GetPriceFactor(dateBeforeUpadate, DataNormalizationMode.Adjusted);
var tick = enumerator.Current as Tick;
Assert.AreEqual(expectedFactor, _config.PriceScaleFactor);
Assert.AreEqual(10 * expectedFactor, tick.Price);
Assert.AreEqual(10 * expectedFactor, tick.Value);
// At factor file update date (2018, 3, 15)
_rawDataEnumerator.CurrentValue = new Tick(
dateAtUpadate,
_config.Symbol,
10,
10,
10);
Assert.IsTrue(enumerator.MoveNext());
var expectedFactor2 = factorFile.GetPriceFactor(dateAtUpadate, DataNormalizationMode.Adjusted);
var tick2 = enumerator.Current as Tick;
Assert.AreEqual(expectedFactor2, _config.PriceScaleFactor);
Assert.AreEqual(10 * expectedFactor2, tick2.Price);
Assert.AreEqual(10 * expectedFactor2, tick2.Value);
// After factor file update date (2018, 3, 15)
_rawDataEnumerator.CurrentValue = new Tick(
dateAfterUpadate,
_config.Symbol,
10,
10,
10);
Assert.IsTrue(enumerator.MoveNext());
var expectedFactor3 = factorFile.GetPriceFactor(dateAfterUpadate, DataNormalizationMode.Adjusted);
var tick3 = enumerator.Current as Tick;
Assert.AreEqual(expectedFactor3, _config.PriceScaleFactor);
Assert.AreEqual(10 * expectedFactor3, tick3.Price);
Assert.AreEqual(10 * expectedFactor3, tick3.Value);
enumerator.Dispose();
}
[Test]
public void PricesAreProperlyAdjustedForLookAheadScaledRawDataNormalizationMode()
{
var factorFileEntries = new[]
{
new DateTime(2005, 02, 25),
new DateTime(2012, 08, 08),
new DateTime(2013, 05, 08),
new DateTime(2014, 08, 06),
new DateTime(2015, 08, 05)
};
var endDate = factorFileEntries.Last().AddDays(1);
var config = GetConfig(Symbols.AAPL, Resolution.Daily);
config.DataNormalizationMode = DataNormalizationMode.ScaledRaw;
using var enumerator = new PriceScaleFactorEnumerator(
_rawDataEnumerator,
config,
TestGlobals.FactorFileProvider,
endDate: endDate);
var price = 100m;
var factorFile = TestGlobals.FactorFileProvider.Get(config.Symbol);
var endDateFactor = factorFile.GetPriceFactor(endDate, config.DataNormalizationMode);
var performAssertions = (DateTime date) =>
{
var expectedFactor = factorFile.GetPriceFactor(date, config.DataNormalizationMode);
Assert.AreEqual(expectedFactor / endDateFactor, config.PriceScaleFactor);
var tradeBar = enumerator.Current as TradeBar;
var expectedValue = price * config.PriceScaleFactor;
Assert.AreEqual(expectedValue, tradeBar.Price);
Assert.AreEqual(expectedValue, tradeBar.Open);
Assert.AreEqual(expectedValue, tradeBar.Close);
Assert.AreEqual(expectedValue, tradeBar.High);
Assert.AreEqual(expectedValue, tradeBar.Low);
Assert.AreEqual(expectedValue, tradeBar.Value);
return expectedFactor;
};
foreach (var factorFileDate in factorFileEntries)
{
// before split
var dateBeforeSplit = factorFileDate.AddDays(-1);
_rawDataEnumerator.CurrentValue = new TradeBar(dateBeforeSplit, config.Symbol, price, price, price, price, price);
Assert.IsTrue(enumerator.MoveNext());
var expectedFactorBeforeSplit = performAssertions(dateBeforeSplit);
// at split
_rawDataEnumerator.CurrentValue = new TradeBar(factorFileDate, config.Symbol, price, price, price, price, price);
Assert.IsTrue(enumerator.MoveNext());
var expectedFactorAtSplit = performAssertions(factorFileDate);
Assert.AreEqual(expectedFactorBeforeSplit, expectedFactorAtSplit);
// after split
var dateAfterSplit = factorFileDate.AddDays(1);
_rawDataEnumerator.CurrentValue = new TradeBar(dateAfterSplit, config.Symbol, price, price, price, price, price);
Assert.IsTrue(enumerator.MoveNext());
var expectedFactorAfterSplit = performAssertions(dateAfterSplit);
Assert.AreNotEqual(expectedFactorAtSplit, expectedFactorAfterSplit);
if (factorFileDate == factorFileEntries.Last())
{
// prices should have been adjusted to the end date prices, instead of the latest factor file entry (today),
// So the last factor should be 1.
Assert.AreEqual(1m, config.PriceScaleFactor);
}
}
}
private static SubscriptionDataConfig GetConfig(Symbol symbol, Resolution resolution)
{
return new SubscriptionDataConfig(typeof(TradeBar),
symbol,
resolution,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
}
private class RawDataEnumerator : IEnumerator<BaseData>
{
public bool MoveNextReturnValue { get; set; }
public BaseData CurrentValue { get; set; }
public BaseData Current => CurrentValue;
object IEnumerator.Current => CurrentValue;
public RawDataEnumerator()
{
MoveNextReturnValue = true;
}
public bool MoveNext()
{
return MoveNextReturnValue;
}
public void Reset()
{
throw new NotImplementedException();
}
public void Dispose()
{
}
}
}
}
@@ -0,0 +1,63 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class QuoteBarFillForwardEnumeratorTests
{
[Test]
public void FillsForwardBidAskBars()
{
var bar1 = new QuoteBar
{
Bid = new Bar(3m, 4m, 1m, 2m),
Ask = new Bar(3.1m, 4.1m, 1.1m, 2.1m),
};
var bar2 = new QuoteBar
{
Bid = null,
Ask = null,
};
var data = new[] { bar1, bar2 }.ToList();
var enumerator = data.GetEnumerator();
var fillForwardEnumerator = new QuoteBarFillForwardEnumerator(enumerator);
// 9:31
Assert.IsTrue(fillForwardEnumerator.MoveNext());
var quoteBar1 = (QuoteBar)fillForwardEnumerator.Current;
Assert.AreSame(bar1.Bid, quoteBar1.Bid);
Assert.AreSame(bar1.Ask, quoteBar1.Ask);
// 9:32
Assert.IsTrue(fillForwardEnumerator.MoveNext());
var quoteBar2 = (QuoteBar)fillForwardEnumerator.Current;
Assert.AreSame(quoteBar1.Bid, quoteBar2.Bid);
Assert.AreSame(quoteBar1.Ask, quoteBar2.Ask);
fillForwardEnumerator.Dispose();
}
}
}
@@ -0,0 +1,62 @@
/*
* 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.Globalization;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class RateLimitEnumeratorTests
{
[Test]
public void LimitsBasedOnTimeBetweenCalls()
{
var currentTime = new DateTime(2015, 10, 10, 13, 6, 0);
var timeProvider = new ManualTimeProvider(currentTime, TimeZones.Utc);
var data = Enumerable.Range(0, 100).Select(x => new Tick {Symbol = CreateSymbol(x)}).GetEnumerator();
var rateLimit = new RateLimitEnumerator<BaseData>(data, timeProvider, Time.OneSecond);
Assert.IsTrue(rateLimit.MoveNext());
while (rateLimit.MoveNext() && rateLimit.Current == null)
{
timeProvider.AdvanceSeconds(0.1);
}
var delta = (timeProvider.GetUtcNow() - currentTime).TotalSeconds;
Assert.AreEqual(1, delta);
Assert.AreEqual("1", data.Current.Symbol.Value);
rateLimit.Dispose();
}
private static Symbol CreateSymbol(int x)
{
return new Symbol(
SecurityIdentifier.GenerateBase(null, x.ToString(CultureInfo.InvariantCulture), Market.USA),
x.ToString(CultureInfo.InvariantCulture));
}
}
}
@@ -0,0 +1,156 @@
/*
* 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 Moq;
using System.IO;
using NUnit.Framework;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class RefreshEnumeratorTests
{
[Test]
public void StaleFileHandleException()
{
var fakeEnumerator = new Mock<IEnumerator<int?>>();
fakeEnumerator.Setup(e => e.MoveNext()).Throws(new IOException("stale file handle"));
fakeEnumerator.Setup(e => e.Dispose()).Verifiable();
var refresher = new RefreshEnumerator<int?>(() => fakeEnumerator.Object);
// does not throw exception but disposes of enumerator
refresher.MoveNext();
fakeEnumerator.Verify(enumerator => enumerator.Dispose(), Times.Once);
refresher.Dispose();
}
[Test]
public void RefreshesEnumeratorOnFirstMoveNext()
{
var refreshed = false;
var refresher = new RefreshEnumerator<int?>(() =>
{
refreshed = true;
return new List<int?>().GetEnumerator();
});
refresher.MoveNext();
Assert.IsTrue(refreshed);
refresher.Dispose();
}
[Test]
public void MoveNextReturnsTrueWhenUnderlyingEnumeratorReturnsFalse()
{
var refresher = new RefreshEnumerator<int?>(() => new List<int?>().GetEnumerator());
Assert.IsTrue(refresher.MoveNext());
refresher.Dispose();
}
[Test]
public void CurrentIsDefault_T_WhenUnderlyingEnumeratorReturnsFalse()
{
var refresher = new RefreshEnumerator<int?>(() => new List<int?>().GetEnumerator());
refresher.MoveNext();
Assert.AreEqual(default(int?), refresher.Current);
refresher.Dispose();
}
[Test]
public void UnderlyingEnumeratorDisposed_WhenUnderlyingEnumeratorReturnsFalse()
{
var fakeEnumerator = new Mock<IEnumerator<int?>>();
fakeEnumerator.Setup(e => e.MoveNext()).Returns(false);
fakeEnumerator.Setup(e => e.Dispose()).Verifiable();
var refresher = new RefreshEnumerator<int?>(() => fakeEnumerator.Object);
refresher.MoveNext();
fakeEnumerator.Verify(enumerator => enumerator.Dispose(), Times.Once);
refresher.Dispose();
}
[Test]
public void DisposeCallsUnderlyingDispose()
{
var fakeEnumerator = new Mock<IEnumerator<int?>>();
fakeEnumerator.Setup(e => e.MoveNext()).Returns(true);
fakeEnumerator.Setup(e => e.Dispose()).Verifiable();
var refresher = new RefreshEnumerator<int?>(() => fakeEnumerator.Object);
refresher.MoveNext();
refresher.Dispose();
fakeEnumerator.Verify(enumerator => enumerator.Dispose(), Times.Once);
refresher.Dispose();
}
[Test]
public void ResetCallsUnderlyingReset()
{
var fakeEnumerator = new Mock<IEnumerator<int?>>();
fakeEnumerator.Setup(e => e.MoveNext()).Returns(true);
fakeEnumerator.Setup(e => e.Reset()).Verifiable();
var refresher = new RefreshEnumerator<int?>(() => fakeEnumerator.Object);
refresher.MoveNext();
refresher.Reset();
fakeEnumerator.Verify(enumerator => enumerator.Reset(), Times.Once);
refresher.Dispose();
}
[Test]
public void RefreshesAfterMoveNextReturnsFalse()
{
var refreshCount = 0;
var list = new List<int?> {1, 2};
var refresher = new RefreshEnumerator<int?>(() =>
{
refreshCount++;
return list.GetEnumerator();
});
Assert.IsTrue(refresher.MoveNext());
Assert.AreEqual(1, refreshCount);
Assert.AreEqual(1, refresher.Current);
Assert.IsTrue(refresher.MoveNext());
Assert.AreEqual(1, refreshCount);
Assert.AreEqual(2, refresher.Current);
Assert.IsTrue(refresher.MoveNext());
Assert.AreEqual(1, refreshCount);
Assert.AreEqual(default(int?), refresher.Current);
Assert.IsTrue(refresher.MoveNext());
Assert.AreEqual(2, refreshCount);
Assert.AreEqual(1, refresher.Current);
Assert.IsTrue(refresher.MoveNext());
Assert.AreEqual(2, refreshCount);
Assert.AreEqual(2, refresher.Current);
refresher.Dispose();
}
}
}
@@ -0,0 +1,355 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class ScannableEnumeratorTests
{
[Test]
public void PassesTicksStraightThrough()
{
var currentTime = new DateTime(2000, 01, 01);
using var identityDataConsolidator = new IdentityDataConsolidator<Tick>();
var enumerator = new ScannableEnumerator<Tick>(
identityDataConsolidator,
DateTimeZone.ForOffset(Offset.FromHours(-5)),
new ManualTimeProvider(currentTime),
(s, e) => { }
);
// returns true even if no data present until stop is called
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick1 = new Tick(currentTime, Symbols.SPY, 199.55m, 199, 200) { Quantity = 10 };
enumerator.Update(tick1);
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(tick1, enumerator.Current);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick2 = new Tick(currentTime, Symbols.SPY, 199.56m, 199.21m, 200.02m) { Quantity = 5 };
enumerator.Update(tick2);
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(tick2, enumerator.Current);
enumerator.Dispose();
}
[Test]
public void NewDataAvailableShouldFire()
{
var currentTime = new DateTime(2000, 01, 01);
var available = false;
using var identityDataConsolidator = new IdentityDataConsolidator<Tick>();
var enumerator = new ScannableEnumerator<Tick>(
identityDataConsolidator,
DateTimeZone.ForOffset(Offset.FromHours(-5)),
new ManualTimeProvider(currentTime),
(s, e) => { available = true; }
);
// returns true even if no data present until stop is called
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
Assert.IsFalse(available);
var tick1 = new Tick(currentTime, Symbols.SPY, 199.55m, 199, 200) { Quantity = 10 };
enumerator.Update(tick1);
Assert.IsTrue(available);
enumerator.Dispose();
}
[Test]
public void AggregatesNewQuoteBarProperly()
{
var reference = DateTime.Today;
using var tickQuoteBarConsolidator = new TickQuoteBarConsolidator(4);
using var enumerator = new ScannableEnumerator<Data.BaseData>(
tickQuoteBarConsolidator,
DateTimeZone.ForOffset(Offset.FromHours(-5)),
new ManualTimeProvider(reference),
(s, e) => { }
);
var tick1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference,
BidPrice = 10,
BidSize = 20,
TickType = TickType.Quote
};
enumerator.Update(tick1);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddMinutes(1),
AskPrice = 20,
AskSize = 10,
TickType = TickType.Quote
};
var badTick = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddMinutes(1),
AskPrice = 25,
AskSize = 100,
BidPrice = -100,
BidSize = 2,
Value = 50,
Quantity = 1234,
TickType = TickType.Trade
};
enumerator.Update(badTick);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
enumerator.Update(tick2);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddMinutes(2),
BidPrice = 12,
BidSize = 50,
TickType = TickType.Quote
};
enumerator.Update(tick3);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddMinutes(3),
AskPrice = 17,
AskSize = 15,
TickType = TickType.Quote
};
enumerator.Update(tick4);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
QuoteBar quoteBar = enumerator.Current as QuoteBar;
Assert.IsNotNull(quoteBar);
Assert.AreEqual(Symbols.SPY, quoteBar.Symbol);
Assert.AreEqual(tick1.Time, quoteBar.Time);
Assert.AreEqual(tick4.EndTime, quoteBar.EndTime);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Open);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Low);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.High);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.Close);
Assert.AreEqual(tick3.BidSize, quoteBar.LastBidSize);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Open);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Low);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.High);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Close);
Assert.AreEqual(tick4.AskSize, quoteBar.LastAskSize);
}
[Test]
public void ForceScanQuoteBar()
{
var reference = new DateTime(2020, 2, 2, 1, 0, 0);
var timeProvider = new ManualTimeProvider(reference);
var dateTimeZone = DateTimeZone.ForOffset(Offset.FromHours(-5));
using var tickQuoteBarConsolidator = new TickQuoteBarConsolidator(TimeSpan.FromMinutes(1));
using var enumerator = new ScannableEnumerator<Data.BaseData>(
tickQuoteBarConsolidator,
dateTimeZone,
timeProvider,
(s, e) => { }
);
var tick1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.ConvertFromUtc(dateTimeZone),
BidPrice = 10,
BidSize = 20,
TickType = TickType.Quote
};
enumerator.Update(tick1);
var tick2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(1).ConvertFromUtc(dateTimeZone),
AskPrice = 20,
AskSize = 10,
TickType = TickType.Quote
};
enumerator.Update(tick2);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(2).ConvertFromUtc(dateTimeZone),
BidPrice = 12,
BidSize = 50,
TickType = TickType.Quote
};
enumerator.Update(tick3);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.AddSeconds(3).ConvertFromUtc(dateTimeZone),
AskPrice = 17,
AskSize = 15,
TickType = TickType.Quote
};
enumerator.Update(tick4);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
timeProvider.AdvanceSeconds(120);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
QuoteBar quoteBar = enumerator.Current as QuoteBar;
Assert.IsNotNull(quoteBar);
Assert.AreEqual(Symbols.SPY, quoteBar.Symbol);
Assert.AreEqual(tick1.Time, quoteBar.Time);
Assert.AreNotEqual(tick4.EndTime, quoteBar.EndTime);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Open);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Low);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.High);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.Close);
Assert.AreEqual(tick3.BidSize, quoteBar.LastBidSize);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Open);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Low);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.High);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Close);
Assert.AreEqual(tick4.AskSize, quoteBar.LastAskSize);
}
[Test]
public void MoveNextScanQuoteBar()
{
var offset = Offset.FromHours(-5);
var timeZone = DateTimeZone.ForOffset(offset);
var utc = new DateTimeOffset(DateTime.Today);
var reference = utc.ToOffset(offset.ToTimeSpan());
var timeProvider = new ManualTimeProvider(reference.DateTime, timeZone);
using var tickQuoteBarConsolidator = new TickQuoteBarConsolidator(TimeSpan.FromMinutes(1));
using var enumerator = new ScannableEnumerator<Data.BaseData>(
tickQuoteBarConsolidator,
timeZone,
timeProvider,
(s, e) => { }
);
var tick1 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.DateTime,
BidPrice = 10,
BidSize = 20,
TickType = TickType.Quote
};
enumerator.Update(tick1);
var tick2 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.DateTime.AddSeconds(1),
AskPrice = 20,
AskSize = 10,
TickType = TickType.Quote
};
enumerator.Update(tick2);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick3 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.DateTime.AddSeconds(2),
BidPrice = 12,
BidSize = 50,
TickType = TickType.Quote
};
enumerator.Update(tick3);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
var tick4 = new Tick
{
Symbol = Symbols.SPY,
Time = reference.DateTime.AddSeconds(3),
AskPrice = 17,
AskSize = 15,
TickType = TickType.Quote
};
enumerator.Update(tick4);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
timeProvider.SetCurrentTime(reference.DateTime.AddMinutes(2));
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
QuoteBar quoteBar = enumerator.Current as QuoteBar;
Assert.IsNotNull(quoteBar);
Assert.AreEqual(Symbols.SPY, quoteBar.Symbol);
Assert.AreEqual(tick1.Time, quoteBar.Time);
Assert.AreNotEqual(tick4.EndTime, quoteBar.EndTime);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Open);
Assert.AreEqual(tick1.BidPrice, quoteBar.Bid.Low);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.High);
Assert.AreEqual(tick3.BidPrice, quoteBar.Bid.Close);
Assert.AreEqual(tick3.BidSize, quoteBar.LastBidSize);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.Open);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Low);
Assert.AreEqual(tick2.AskPrice, quoteBar.Ask.High);
Assert.AreEqual(tick4.AskPrice, quoteBar.Ask.Close);
Assert.AreEqual(tick4.AskSize, quoteBar.LastAskSize);
}
}
}
@@ -0,0 +1,393 @@
/*
* 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;
using System.Collections.Generic;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class ScheduledEnumeratorTests
{
private readonly DateTime _referenceTime = new DateTime(2019, 1, 1);
[TestCase(true)]
[TestCase(false)]
public void RespectsPredicateTimeProvider(bool newDataArrivedInTime)
{
var scheduledDate = _referenceTime.AddDays(1);
using var underlyingEnumerator = new TestEnumerator
{
MoveNextReturn = true,
MoveNextNewValues = new Queue<BaseData>(new List<BaseData>
{
new Tick(scheduledDate.AddDays(-1), Symbols.SPY, 1, 1)
})
};
var timeProvider = new ManualTimeProvider(_referenceTime);
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { scheduledDate },
new PredicateTimeProvider(timeProvider, (currentDateTime) => {
// will only let time advance after it's passed the 7/8 hour frontier
return currentDateTime.TimeOfDay > TimeSpan.FromMinutes(7 * 60 + DateTime.UtcNow.Second);
}),
TimeZones.Utc,
DateTime.MinValue);
// still null since frontier is still behind schedule
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
timeProvider.SetCurrentTimeUtc(scheduledDate);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
timeProvider.SetCurrentTimeUtc(scheduledDate.AddHours(2));
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
if (newDataArrivedInTime)
{
// New data comes in!
underlyingEnumerator.MoveNextNewValues.Enqueue(new Tick(scheduledDate, Symbols.SPY, 10, 10));
}
timeProvider.SetCurrentTimeUtc(scheduledDate.AddHours(8));
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(scheduledDate, enumerator.Current.Time);
Assert.AreEqual(newDataArrivedInTime ? 10 : 1, (enumerator.Current as Tick).BidPrice);
// schedule ended so enumerator will end too
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void ScheduleSkipsOldDates()
{
using var testEnumerator = new TestEnumerator();
using var enumerator = new ScheduledEnumerator(
testEnumerator,
new List<DateTime> { _referenceTime },
new ManualTimeProvider(_referenceTime),
TimeZones.Utc,
_referenceTime.AddDays(1));
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void EmptyScheduleThrowsNoException()
{
ScheduledEnumerator enumerator = null;
Assert.DoesNotThrow(() => enumerator = new ScheduledEnumerator(
new TestEnumerator(),
new List<DateTime>(),
new ManualTimeProvider(_referenceTime),
TimeZones.Utc,
DateTime.MinValue));
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void ReturnsTrueEvenIfUnderlyingIsNullButReturnsTrue()
{
using var underlyingEnumerator = new TestEnumerator { MoveNextReturn = true };
var timeProvider = new ManualTimeProvider(_referenceTime);
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { _referenceTime.AddDays(1) },
timeProvider,
TimeZones.Utc,
DateTime.MinValue);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void ReturnsFalseWhenUnderlyingReturnsFalse()
{
using var underlyingEnumerator = new TestEnumerator { MoveNextReturn = false };
var timeProvider = new ManualTimeProvider(_referenceTime);
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { _referenceTime.AddDays(1) },
timeProvider,
TimeZones.Utc,
DateTime.MinValue);
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void ForwardsDataToFitSchedule()
{
var scheduledDate = _referenceTime.AddDays(1);
using var underlyingEnumerator = new TestEnumerator
{
MoveNextReturn = true,
MoveNextNewValues = new Queue<BaseData>(new List<BaseData>
{
new Tick(scheduledDate, Symbols.SPY, 1, 1),
// way in the future compared with the schedule
new Tick(scheduledDate.AddYears(1), Symbols.SPY, 10, 10)
})
};
var timeProvider = new ManualTimeProvider(_referenceTime);
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { scheduledDate, scheduledDate.AddDays(1) },
timeProvider,
TimeZones.Utc,
DateTime.MinValue);
Assert.IsTrue(enumerator.MoveNext());
// still null since frontier is still behind schedule
Assert.IsNull(enumerator.Current);
timeProvider.SetCurrentTimeUtc(scheduledDate);
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(scheduledDate, enumerator.Current.Time);
Assert.AreEqual(1, (enumerator.Current as Tick).BidPrice);
// it will forward previous available value to fit the schedule
timeProvider.SetCurrentTimeUtc(scheduledDate.AddDays(1));
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(scheduledDate.AddDays(1), enumerator.Current.Time);
Assert.AreEqual(1, (enumerator.Current as Tick).BidPrice);
// schedule ended so enumerator will end too
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void UpdatesCurrentBasedOnSchedule()
{
var scheduledDate = _referenceTime.AddDays(1);
using var underlyingEnumerator = new TestEnumerator
{
MoveNextReturn = true,
MoveNextNewValues = new Queue<BaseData>(new List<BaseData>
{
new Tick(scheduledDate, Symbols.SPY, 1, 1)
})
};
var timeProvider = new ManualTimeProvider(_referenceTime);
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { scheduledDate },
timeProvider,
TimeZones.Utc,
DateTime.MinValue);
Assert.IsTrue(enumerator.MoveNext());
// still null since frontier is still behind schedule
Assert.IsNull(enumerator.Current);
timeProvider.SetCurrentTimeUtc(scheduledDate);
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(scheduledDate, enumerator.Current.Time);
Assert.AreEqual(1, (enumerator.Current as Tick).BidPrice);
// schedule ended so enumerator will end too
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void WillUseLatestDataPoint()
{
using var underlyingEnumerator = new TestEnumerator
{
MoveNextReturn = true,
MoveNextNewValues = new Queue<BaseData>(new List<BaseData>
{
new Tick(new DateTime(2019, 1, 15), Symbols.SPY, 1, 1),
new Tick(new DateTime(2019, 1, 20), Symbols.SPY, 2, 1),
new Tick(new DateTime(2019, 1, 25), Symbols.SPY, 3, 1)
})
};
var timeProvider = new ManualTimeProvider(_referenceTime);
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { new DateTime(2019, 2, 1) },
timeProvider,
TimeZones.Utc,
DateTime.MinValue);
Assert.IsTrue(enumerator.MoveNext());
// still null since frontier is still behind schedule
Assert.IsNull(enumerator.Current);
// frontier is now a month after the scheduled time!
timeProvider.SetCurrentTimeUtc(new DateTime(2019, 3, 1));
Assert.IsTrue(enumerator.MoveNext());
// it uses the last available data point in the enumerator
Assert.AreEqual(new DateTime(2019, 2, 1), enumerator.Current.Time);
Assert.AreEqual(3, (enumerator.Current as Tick).BidPrice);
Assert.IsNull(underlyingEnumerator.Current);
}
[Test]
public void WillUseLatestDataPointOnlyIfBeforeOrAtSchedule()
{
using var underlyingEnumerator = new TestEnumerator
{
MoveNextReturn = true,
MoveNextNewValues = new Queue<BaseData>(new List<BaseData>
{
new Tick(new DateTime(2019, 1, 20), Symbols.SPY, 2, 1),
new Tick(new DateTime(2019, 1, 25), Symbols.SPY, 3, 1),
// this guys is in 2020
new Tick(new DateTime(2020, 1, 1), Symbols.SPY, 4, 1)
})
};
var timeProvider = new ManualTimeProvider(_referenceTime);
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { new DateTime(2019, 2, 1), new DateTime(2020, 2, 1) },
timeProvider,
TimeZones.Utc,
DateTime.MinValue);
Assert.IsTrue(enumerator.MoveNext());
// still null since frontier is still behind schedule
Assert.IsNull(enumerator.Current);
// frontier is now a month after the scheduled time!
timeProvider.SetCurrentTimeUtc(new DateTime(2019, 3, 1));
Assert.IsTrue(enumerator.MoveNext());
// it uses the last available data point in the enumerator that is before the schedule
Assert.AreEqual(new DateTime(2019, 2, 1), enumerator.Current.Time);
Assert.AreEqual(3, (enumerator.Current as Tick).BidPrice);
// the underlying enumerator hold the next data point
Assert.AreEqual(new DateTime(2020, 1, 1), underlyingEnumerator.Current.Time);
// now lets test fetching the last data point
timeProvider.SetCurrentTimeUtc(new DateTime(2021, 3, 1));
// the underlying will end but should still emit the data point it has
underlyingEnumerator.MoveNextReturn = false;
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(new DateTime(2020, 2, 1), enumerator.Current.Time);
Assert.AreEqual(4, (enumerator.Current as Tick).BidPrice);
Assert.IsNull(underlyingEnumerator.Current);
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
[Test]
public void NoTimeProvider()
{
using var underlyingEnumerator = new TestEnumerator
{
MoveNextReturn = true,
MoveNextNewValues = new Queue<BaseData>(new List<BaseData>
{
new Tick(new DateTime(2019, 1, 20), Symbols.SPY, 2, 1),
new Tick(new DateTime(2019, 1, 25), Symbols.SPY, 3, 1),
new Tick(new DateTime(2020, 1, 1), Symbols.SPY, 4, 1)
})
};
using var enumerator = new ScheduledEnumerator(
underlyingEnumerator,
new List<DateTime> { new DateTime(2019, 2, 1), new DateTime(2020, 2, 1) },
null,
TimeZones.Utc,
DateTime.MinValue);
Assert.IsTrue(enumerator.MoveNext());
// it uses the last available data point in the enumerator that is before the schedule
Assert.AreEqual(new DateTime(2019, 2, 1), enumerator.Current.Time);
Assert.AreEqual(3, (enumerator.Current as Tick).BidPrice);
// the underlying enumerator hold the next data point
Assert.AreEqual(new DateTime(2020, 1, 1), underlyingEnumerator.Current.Time);
// the underlying will end but should still emit the data point it has
underlyingEnumerator.MoveNextReturn = false;
Assert.IsTrue(enumerator.MoveNext());
Assert.AreEqual(new DateTime(2020, 2, 1), enumerator.Current.Time);
Assert.AreEqual(4, (enumerator.Current as Tick).BidPrice);
Assert.IsNull(underlyingEnumerator.Current);
Assert.IsFalse(enumerator.MoveNext());
Assert.IsNull(enumerator.Current);
}
private class TestEnumerator : IEnumerator<BaseData>
{
public Queue<BaseData> MoveNextNewValues { get; set; }
public BaseData Current { get; private set; }
object IEnumerator.Current => Current;
public bool MoveNextReturn { get; set; }
public bool MoveNext()
{
if (MoveNextNewValues != null && MoveNextNewValues.Count > 0)
{
Current = MoveNextNewValues.Dequeue();
}
else
{
Current = null;
}
return MoveNextReturn;
}
public void Reset()
{}
public void Dispose()
{}
}
}
}
@@ -0,0 +1,89 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class SubscriptionDataEnumeratorTests
{
[TestCase(typeof(TradeBar), true)]
[TestCase(typeof(OpenInterest), false)]
[TestCase(typeof(QuoteBar), false)]
public void EnumeratorEmitsAuxData(Type typeOfConfig, bool shouldReceiveAuxData)
{
var config = CreateConfig(Resolution.Hour, typeOfConfig);
var security = GetSecurity(config);
var time = new DateTime(2010, 1, 1);
var tzOffsetProvider = new TimeZoneOffsetProvider(security.Exchange.TimeZone, time, time.AddDays(1));
// Make a aux data stream; for this testing case we will just use delisting data points
var totalPoints = 8;
var stream = Enumerable.Range(0, totalPoints).Select(x => new Delisting { Time = time.AddHours(x) }).GetEnumerator();
using var enumerator = new SubscriptionDataEnumerator(config, security.Exchange.Hours, tzOffsetProvider, stream, false, false);
// Test our SubscriptionDataEnumerator to see if it emits the aux data
int dataReceivedCount = 0;
while (enumerator.MoveNext())
{
dataReceivedCount++;
if (enumerator.Current != null && enumerator.Current.Data.DataType == MarketDataType.Auxiliary)
{
Assert.IsTrue(shouldReceiveAuxData);
}
}
// If it should receive aux data it should have emitted all points
// otherwise none should have been emitted
if (shouldReceiveAuxData)
{
Assert.AreEqual(totalPoints, dataReceivedCount);
}
else
{
Assert.AreEqual(0, dataReceivedCount);
}
}
private static Security GetSecurity(SubscriptionDataConfig config)
{
return new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
}
private static SubscriptionDataConfig CreateConfig(Resolution resolution, Type type)
{
return new SubscriptionDataConfig(type, Symbols.SPY, resolution, TimeZones.NewYork, TimeZones.NewYork, true, true, false);
}
}
}
@@ -0,0 +1,147 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class SynchronizingBaseDataEnumeratorTests
{
[Test]
public void SynchronizesData()
{
var time = new DateTime(2016, 03, 03, 12, 05, 00);
var stream1 = Enumerable.Range(0, 10).Select(x => new Tick {Time = time.AddSeconds(x * 1)}).GetEnumerator();
var stream2 = Enumerable.Range(0, 5).Select(x => new Tick {Time = time.AddSeconds(x * 2)}).GetEnumerator();
var stream3 = Enumerable.Range(0, 20).Select(x => new Tick {Time = time.AddSeconds(x * 0.5)}).GetEnumerator();
var previous = DateTime.MinValue;
var synchronizer = new SynchronizingBaseDataEnumerator(stream1, stream2, stream3);
while (synchronizer.MoveNext())
{
Assert.That(synchronizer.Current.EndTime, Is.GreaterThanOrEqualTo(previous));
previous = synchronizer.Current.EndTime;
}
synchronizer.Dispose();
}
[Test]
public void WontRemoveEnumeratorsReturningTrueWithCurrentNull()
{
var time = new DateTime(2016, 03, 03, 12, 05, 00);
var stream1 = Enumerable.Range(0, 20)
// return null except the last value and check if its emitted
.Select(x => x == 19 ? new Tick {Time = time.AddSeconds(x * 100), Quantity = 998877} : null
).GetEnumerator();
var stream2 = Enumerable.Range(0, 5).Select(x => new Tick { Time = time.AddSeconds(x * 2) }).GetEnumerator();
var stream3 = Enumerable.Range(0, 20).Select(x => new Tick { Time = time.AddSeconds(x * 0.5) }).GetEnumerator();
var previous = new Tick { Time = DateTime.MinValue };
var synchronizer = new SynchronizingBaseDataEnumerator(stream1, stream2, stream3);
while (synchronizer.MoveNext())
{
Assert.That(synchronizer.Current.EndTime, Is.GreaterThanOrEqualTo(previous.EndTime));
previous = synchronizer.Current as Tick;
}
Assert.AreEqual(998877, previous.Quantity);
synchronizer.Dispose();
}
[Test]
public void WillRemoveEnumeratorsReturningFalse()
{
var time = new DateTime(2016, 03, 03, 12, 05, 00);
var stream1 = new TestEnumerator { MoveNextReturnValue = false };
var stream2 = Enumerable.Range(0, 10).Select(x => new Tick { Time = time.AddSeconds(x * 2) }).GetEnumerator();
var synchronizer = new SynchronizingBaseDataEnumerator(stream1, stream2);
var emitted = false;
while (synchronizer.MoveNext())
{
emitted = true;
}
Assert.IsTrue(emitted);
Assert.IsTrue(stream1.MoveNextWasCalled);
Assert.AreEqual(1, stream1.MoveNextCallCount);
synchronizer.Dispose();
}
[Test]
public void WillStopIfAllEnumeratorsCurrentIsNullAndReturningTrue()
{
var stream1 = new TestEnumerator { MoveNextReturnValue = true };
var synchronizer = new SynchronizingBaseDataEnumerator(stream1);
while (synchronizer.MoveNext())
{
Assert.Fail();
}
Assert.IsTrue(stream1.MoveNextWasCalled);
Assert.Pass();
synchronizer.Dispose();
}
[Test]
public void WillStopIfAllEnumeratorsCurrentIsNullAndReturningFalse()
{
var stream1 = new TestEnumerator { MoveNextReturnValue = false };
var synchronizer = new SynchronizingBaseDataEnumerator(stream1);
while (synchronizer.MoveNext())
{
Assert.Fail();
}
Assert.IsTrue(stream1.MoveNextWasCalled);
Assert.Pass();
synchronizer.Dispose();
}
private class TestEnumerator : IEnumerator<BaseData>
{
public int MoveNextCallCount { get; set; }
public bool MoveNextReturnValue { get; set; }
public bool MoveNextWasCalled { get; set; }
public TestEnumerator()
{
MoveNextWasCalled = false;
}
public bool MoveNext()
{
MoveNextCallCount++;
MoveNextWasCalled = true;
return MoveNextReturnValue;
}
public BaseData Current { get; }
object IEnumerator.Current => Current;
public void Dispose() { }
public void Reset() { }
}
}
}
@@ -0,0 +1,146 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Tests.Engine.DataFeeds.Enumerators
{
[TestFixture]
public class SynchronizingSliceEnumeratorTests
{
[Test]
public void SynchronizesData()
{
var time = new DateTime(2016, 03, 03, 12, 05, 00);
var stream1 = Enumerable.Range(0, 10).Select(x => new Slice(time.AddSeconds(x * 1), new List<BaseData>(), utcTime: time.AddSeconds(x * 1))).GetEnumerator();
var stream2 = Enumerable.Range(0, 5).Select(x => new Slice(time.AddSeconds(x * 2), new List<BaseData>(), utcTime: time.AddSeconds(x * 2))).GetEnumerator();
var stream3 = Enumerable.Range(0, 20).Select(x => new Slice(time.AddSeconds(x * 0.5), new List<BaseData>(), utcTime: time.AddSeconds(x * 0.5))).GetEnumerator();
var previous = DateTime.MinValue;
var synchronizer = new SynchronizingSliceEnumerator(stream1, stream2, stream3);
while (synchronizer.MoveNext())
{
Assert.That(synchronizer.Current.UtcTime, Is.GreaterThanOrEqualTo(previous));
previous = synchronizer.Current.UtcTime;
}
synchronizer.Dispose();
}
[Test]
public void WontRemoveEnumeratorsReturningTrueWithCurrentNull()
{
var time = new DateTime(2016, 03, 03, 12, 05, 00);
var tradeBar1 = new TradeBar { Symbol = Symbols.SPY, Time = time };
var tradeBar2 = new TradeBar { Symbol = Symbols.AAPL, Time = time, Open = 23 };
var stream1 = Enumerable.Range(0, 20)
// return null except the last value and check if its emitted
.Select(x => x == 19 ? new Slice(time.AddSeconds(x * 1), new BaseData[] { tradeBar1, tradeBar2 }, utcTime: time.AddSeconds(x * 1)) : null
).GetEnumerator();
var stream2 = Enumerable.Range(0, 5).Select(x => new Slice(time.AddSeconds(x * 2), new List<BaseData>(), utcTime: time.AddSeconds(x * 2))).GetEnumerator();
var stream3 = Enumerable.Range(0, 20).Select(x => new Slice(time.AddSeconds(x * 0.5), new List<BaseData>(), utcTime: time.AddSeconds(x * 0.5))).GetEnumerator();
var previous = new Slice(DateTime.MinValue, new List<BaseData>(), DateTime.MinValue);
var synchronizer = new SynchronizingSliceEnumerator(stream1, stream2, stream3);
while (synchronizer.MoveNext())
{
Assert.That(synchronizer.Current.UtcTime, Is.GreaterThanOrEqualTo(previous.UtcTime));
previous = synchronizer.Current;
}
Assert.AreEqual(2, previous.Bars.Count);
synchronizer.Dispose();
}
[Test]
public void WillRemoveEnumeratorsReturningFalse()
{
var time = new DateTime(2016, 03, 03, 12, 05, 00);
var stream1 = new TestEnumerator { MoveNextReturnValue = false };
var stream2 = Enumerable.Range(0, 10).Select(x => new Slice(time.AddSeconds(x * 0.5), new List<BaseData>(), utcTime: time.AddSeconds(x * 0.5))).GetEnumerator();
var synchronizer = new SynchronizingSliceEnumerator(stream1, stream2);
var emitted = false;
while (synchronizer.MoveNext())
{
emitted = true;
}
Assert.IsTrue(emitted);
Assert.IsTrue(stream1.MoveNextWasCalled);
Assert.AreEqual(1, stream1.MoveNextCallCount);
synchronizer.Dispose();
}
[Test]
public void WillStopIfAllEnumeratorsCurrentIsNullAndReturningTrue()
{
var stream1 = new TestEnumerator { MoveNextReturnValue = true };
var synchronizer = new SynchronizingSliceEnumerator(stream1);
while (synchronizer.MoveNext())
{
Assert.Fail();
}
Assert.IsTrue(stream1.MoveNextWasCalled);
synchronizer.Dispose();
}
[Test]
public void WillStopIfAllEnumeratorsCurrentIsNullAndReturningFalse()
{
var stream1 = new TestEnumerator { MoveNextReturnValue = false };
var synchronizer = new SynchronizingSliceEnumerator(stream1);
while (synchronizer.MoveNext())
{
Assert.Fail();
}
Assert.IsTrue(stream1.MoveNextWasCalled);
synchronizer.Dispose();
}
private class TestEnumerator : IEnumerator<Slice>
{
public int MoveNextCallCount { get; set; }
public bool MoveNextReturnValue { get; set; }
public bool MoveNextWasCalled { get; set; }
public TestEnumerator()
{
MoveNextWasCalled = false;
}
public bool MoveNext()
{
MoveNextCallCount++;
MoveNextWasCalled = true;
return MoveNextReturnValue;
}
public Slice Current { get; }
object IEnumerator.Current => Current;
public void Dispose()
{ }
public void Reset()
{ }
}
}
}
@@ -0,0 +1,69 @@
/*
* 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 NUnit.Framework;
using System.Threading;
using QuantConnect.Data;
using QuantConnect.Logging;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Queues;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class FakeDataQueueTests
{
[Test]
public void GeneratesDataCorrectly()
{
using var aggregator = new AggregationManager();
var algorithm = new AlgorithmStub();
var dataQueue = new FakeDataQueue(aggregator, dataPointsPerSecondPerSymbol: 10);
algorithm.AddEquity("SPY", Resolution.Second);
List<IEnumerator<BaseData>> enumerators = new();
foreach (var config in algorithm.SubscriptionManager.Subscriptions)
{
using var newDataEvent = new ManualResetEvent(false);
enumerators.Add(dataQueue.Subscribe(config, (_, _) => {
try
{
newDataEvent.Set();
}
catch (ObjectDisposedException)
{
}
}));
Assert.IsTrue(newDataEvent.WaitOne(15000));
// let's just generate a single point
dataQueue.Unsubscribe(config);
}
dataQueue.Dispose();
foreach (var enumerator in enumerators)
{
// assert each data type generate data correctly
Assert.IsTrue(enumerator.MoveNext());
Assert.IsNotNull(enumerator.Current);
Log.Debug($"FakeDataQueueTests.GeneratesDataCorrectly(): {enumerator.Current}");
}
}
}
}
@@ -0,0 +1,407 @@
/*
* 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.Diagnostics;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Logging;
using QuantConnect.Packets;
using QuantConnect.Securities;
using QuantConnect.Tests.Common.Securities;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class FileSystemDataFeedTests
{
[Test]
public void TestsFileSystemDataFeedSpeed()
{
var job = new BacktestNodePacket();
var resultHandler = new BacktestingResultHandler();
var algorithm = PerformanceBenchmarkAlgorithms.SingleSecurity_Second;
var feed = new FileSystemDataFeed();
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var dataPermissionManager = new DataPermissionManager();
var dataManager = new DataManager(feed,
new UniverseSelection(
algorithm,
new SecurityService(algorithm.Portfolio.CashBook, marketHoursDatabase, symbolPropertiesDataBase, algorithm, RegisteredSecurityDataTypesProvider.Null, new SecurityCacheProvider(algorithm.Portfolio), algorithm: algorithm),
dataPermissionManager,
TestGlobals.DataProvider),
algorithm,
algorithm.TimeKeeper,
marketHoursDatabase,
false,
RegisteredSecurityDataTypesProvider.Null,
dataPermissionManager);
algorithm.SubscriptionManager.SetDataManager(dataManager);
using var synchronizer = new Synchronizer();
synchronizer.Initialize(algorithm, dataManager, new());
feed.Initialize(algorithm, job, resultHandler, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider, dataManager, synchronizer, dataPermissionManager.DataChannelProvider);
algorithm.Initialize();
algorithm.PostInitialize();
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var count = 0;
var stopwatch = Stopwatch.StartNew();
var lastMonth = algorithm.StartDate.Month;
foreach (var timeSlice in synchronizer.StreamData(cancellationTokenSource.Token))
{
if (timeSlice.Time.Month != lastMonth)
{
var elapsed = stopwatch.Elapsed.TotalSeconds;
var thousands = count / 1000d;
Log.Trace($"{DateTime.Now} - Time: {timeSlice.Time}: KPS: {thousands / elapsed}");
lastMonth = timeSlice.Time.Month;
}
count++;
}
Log.Trace("Count: " + count);
stopwatch.Stop();
feed.Exit();
dataManager.RemoveAllSubscriptions();
Log.Trace($"Elapsed time: {stopwatch.Elapsed} KPS: {count / 1000d / stopwatch.Elapsed.TotalSeconds}");
}
[Test]
public void TestDataFeedEnumeratorStackSpeed()
{
var algorithm = PerformanceBenchmarkAlgorithms.SingleSecurity_Second;
algorithm.Initialize();
algorithm.PostInitialize();
var resultHandler = new BacktestingResultHandler();
using var factory = new SubscriptionDataReaderSubscriptionEnumeratorFactory(resultHandler, TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider, TestGlobals.DataCacheProvider, algorithm, enablePriceScaling: false);
var universe = algorithm.UniverseManager.Single().Value;
var security = algorithm.Securities.Single().Value;
var securityConfig = security.Subscriptions.First();
var subscriptionRequest = new SubscriptionRequest(false, universe, security, securityConfig, algorithm.StartDate, algorithm.EndDate);
var enumerator = factory.CreateEnumerator(subscriptionRequest, TestGlobals.DataProvider);
var count = 0;
var stopwatch = Stopwatch.StartNew();
var lastMonth = algorithm.StartDate.Month;
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (current == null)
{
Log.Trace("ERROR: Current is null");
continue;
}
if (current.Time.Month != lastMonth)
{
var elapsed = stopwatch.Elapsed.TotalSeconds;
var thousands = count / 1000d;
Log.Trace($"{DateTime.Now} - Time: {current.Time}: KPS: {thousands / elapsed}");
lastMonth = current.Time.Month;
}
count++;
}
Log.Trace("Count: " + count);
stopwatch.Stop();
enumerator.Dispose();
factory.DisposeSafely();
Log.Trace($"Elapsed time: {stopwatch.Elapsed} KPS: {count / 1000d / stopwatch.Elapsed.TotalSeconds}");
}
[Test]
public void ChecksMapFileFirstDate()
{
var algorithm = PerformanceBenchmarkAlgorithms.SingleSecurity_Second;
algorithm.Initialize();
algorithm.PostInitialize();
var resultHandler = new TestResultHandler();
using var factory = new SubscriptionDataReaderSubscriptionEnumeratorFactory(resultHandler, TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider, TestGlobals.DataCacheProvider, algorithm, enablePriceScaling: false);
var universe = algorithm.UniverseManager.Single().Value;
var security = algorithm.AddEquity("AAA", Resolution.Daily);
var securityConfig = security.Subscriptions.First();
// start date is before the first date in the map file
var subscriptionRequest = new SubscriptionRequest(false, universe, security, securityConfig, new DateTime(2001, 12, 1),
new DateTime(2016, 11, 1));
var enumerator = factory.CreateEnumerator(subscriptionRequest, TestGlobals.DataProvider);
// should initialize the data source reader
enumerator.MoveNext();
enumerator.Dispose();
factory.DisposeSafely();
resultHandler.Exit();
var message = ((DebugPacket)resultHandler.Messages.Single()).Message;
Assert.IsTrue(message.Equals(
"The starting dates for the following symbols have been adjusted to match their map files first date: [AAA, 2020-09-09]"));
}
[TestCase(true)]
[TestCase(false)]
public void OptionChainEnumerator(bool fillForward)
{
var job = new BacktestNodePacket();
var resultHandler = new BacktestingResultHandler();
var feed = new FileSystemDataFeed();
var algorithm = new AlgorithmStub(feed);
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
algorithm.SetStartDate(new DateTime(2014, 06, 06));
algorithm.SetEndDate(new DateTime(2014, 06, 09));
var optionChainProvider = new BacktestingOptionChainProvider();
optionChainProvider.Initialize(new(TestGlobals.MapFileProvider, TestGlobals.HistoryProvider));
algorithm.SetOptionChainProvider(optionChainProvider);
var dataPermissionManager = new DataPermissionManager();
using var synchronizer = new Synchronizer();
synchronizer.Initialize(algorithm, algorithm.DataManager, new());
feed.Initialize(algorithm, job, resultHandler, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider, algorithm.DataManager, synchronizer, dataPermissionManager.DataChannelProvider);
var option = algorithm.AddOption("AAPL", fillForward: fillForward);
option.SetFilter(filter => filter.FrontMonth());
algorithm.PostInitialize();
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var count = 0;
var lastMonth = algorithm.StartDate.Month;
foreach (var timeSlice in synchronizer.StreamData(cancellationTokenSource.Token))
{
if (!timeSlice.IsTimePulse && timeSlice.UniverseData?.Count > 0 && timeSlice.Time.Date <= algorithm.EndDate)
{
var baseDataCollection = timeSlice.UniverseData.Where(x => x.Key is OptionChainUniverse).SingleOrDefault().Value;
if (baseDataCollection != null)
{
var nyTime = timeSlice.Time.ConvertFromUtc(algorithm.TimeZone);
Assert.AreEqual(new TimeSpan(0, 0, 0), nyTime.TimeOfDay, $"Failed on: {nyTime}");
Assert.AreEqual(nyTime.TimeOfDay, baseDataCollection.EndTime.ConvertFromUtc(algorithm.TimeZone).TimeOfDay);
Assert.IsNotNull(baseDataCollection.FilteredContracts);
CollectionAssert.IsNotEmpty(baseDataCollection.FilteredContracts);
count++;
}
}
}
feed.Exit();
algorithm.DataManager.RemoveAllSubscriptions();
// 2 tradable dates between 2014-06-06 and 2014-06-09 (the 6th and 9th)
Assert.AreEqual(2, count);
}
[TestCase(true)]
[TestCase(false)]
public void FutureChainEnumerator(bool fillForward)
{
var job = new BacktestNodePacket();
var resultHandler = new BacktestingResultHandler();
var feed = new FileSystemDataFeed();
var algorithm = new AlgorithmStub(feed);
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
algorithm.SetStartDate(new DateTime(2013, 10, 07));
algorithm.SetEndDate(new DateTime(2013, 10, 08));
var optionChainProvider = new BacktestingOptionChainProvider();
optionChainProvider.Initialize(new(TestGlobals.MapFileProvider, TestGlobals.HistoryProvider));
algorithm.SetOptionChainProvider(optionChainProvider);
var dataPermissionManager = new DataPermissionManager();
using var synchronizer = new Synchronizer();
synchronizer.Initialize(algorithm, algorithm.DataManager, new());
feed.Initialize(algorithm, job, resultHandler, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider,
algorithm.DataManager, synchronizer, dataPermissionManager.DataChannelProvider);
var future = algorithm.AddFuture("ES", fillForward: fillForward, extendedMarketHours: true);
future.SetFilter(0, 300);
algorithm.PostInitialize();
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var count = 0L;
var lastMonth = algorithm.StartDate.Month;
foreach (var timeSlice in synchronizer.StreamData(cancellationTokenSource.Token))
{
if (!timeSlice.IsTimePulse && timeSlice.UniverseData?.Count > 0 && timeSlice.Time.Date <= algorithm.EndDate)
{
var nyTime = timeSlice.Time.ConvertFromUtc(algorithm.TimeZone);
var universeData = timeSlice.UniverseData;
var chainData = universeData.Where(x => x.Key is FuturesChainUniverse).Single().Value;
Log.Trace($"{nyTime}. Count: {count}. Universe Data Count {universeData.Count}");
Assert.AreEqual(TimeSpan.Zero, nyTime.TimeOfDay, $"Failed on: {nyTime}. Count: {count}");
Assert.IsTrue(timeSlice.UniverseData.All(kvp => kvp.Value.EndTime.ConvertFromUtc(algorithm.TimeZone).TimeOfDay == nyTime.TimeOfDay));
if (chainData.FilteredContracts.IsNullOrEmpty())
{
Assert.AreEqual(new DateTime(2013, 10, 09), nyTime, $"Unexpected chain FilteredContracts was empty on {nyTime}");
}
if (universeData.Count == 1)
{
// the chain
Assert.IsTrue(universeData.Any(kvp => kvp.Key.Configuration.Symbol == future.Symbol));
}
else
{
// we have 2 universe data, the chain and the continuous future
Assert.AreEqual(2, universeData.Count);
Assert.IsTrue(universeData.All(kvp => kvp.Key.Configuration.Symbol.SecurityType == SecurityType.Future));
Assert.IsTrue(universeData.Any(kvp => kvp.Key.Configuration.Symbol == future.Symbol));
Assert.IsTrue(universeData.Any(kvp => kvp.Key.Configuration.Symbol.ID.Symbol.Contains("CONTINUOUS", StringComparison.InvariantCultureIgnoreCase)));
var continuousData = universeData.Where(x => x.Key is ContinuousContractUniverse).Single().Value;
Assert.AreEqual(TimeSpan.Zero, nyTime.TimeOfDay, $"Failed on: {nyTime}");
Assert.IsTrue(!chainData.FilteredContracts.IsNullOrEmpty());
}
count++;
}
}
feed.Exit();
algorithm.DataManager.RemoveAllSubscriptions();
// 2 tradable days
Assert.AreEqual(2, count);
}
[Test]
public void ContinuousFutureUniverseSelectionIsPerformedOnExtendedMarketHoursDates([Values] bool extendedMarketHours)
{
var job = new BacktestNodePacket();
var resultHandler = new BacktestingResultHandler();
var feed = new FileSystemDataFeed();
var algorithm = new AlgorithmStub(feed);
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
algorithm.SetStartDate(new DateTime(2019, 08, 01));
algorithm.SetEndDate(new DateTime(2019, 08, 08));
var dataPermissionManager = new DataPermissionManager();
using var synchronizer = new Synchronizer();
synchronizer.Initialize(algorithm, algorithm.DataManager, new());
feed.Initialize(algorithm, job, resultHandler, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider,
algorithm.DataManager, synchronizer, dataPermissionManager.DataChannelProvider);
var future = algorithm.AddFuture("GC", Resolution.Daily, extendedMarketHours: extendedMarketHours);
algorithm.PostInitialize();
var addedSecurities = new HashSet<Symbol>();
var mappingCounts = 0;
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
foreach (var timeSlice in synchronizer.StreamData(cancellationTokenSource.Token))
{
if (timeSlice.IsTimePulse) continue;
var addedSymbols = timeSlice.SecurityChanges.AddedSecurities.Select(x => x.Symbol).ToHashSet();
if (timeSlice.Slice.SymbolChangedEvents.TryGetValue(future.Symbol, out var symbolChangedEvent))
{
mappingCounts++;
var oldSymbol = algorithm.Symbol(symbolChangedEvent.OldSymbol);
var newSymbol = algorithm.Symbol(symbolChangedEvent.NewSymbol);
Assert.IsTrue(addedSecurities.Contains(oldSymbol));
Assert.IsTrue(addedSymbols.Contains(newSymbol));
}
addedSecurities.UnionWith(addedSymbols);
}
feed.Exit();
algorithm.DataManager.RemoveAllSubscriptions();
var expectedMappingCounts = extendedMarketHours ? 2 : 1;
Assert.AreEqual(expectedMappingCounts, mappingCounts);
}
[Test]
public void DataIsFillForwardedFromWarmupToNormalFeed()
{
var job = new BacktestNodePacket();
var resultHandler = new BacktestingResultHandler();
var feed = new FileSystemDataFeed();
var algorithm = new AlgorithmStub(feed);
algorithm.Transactions.SetOrderProcessor(new FakeOrderProcessor());
algorithm.SetStartDate(new DateTime(2013, 10, 15));
algorithm.SetEndDate(new DateTime(2013, 10, 16));
var dataPermissionManager = new DataPermissionManager();
using var synchronizer = new Synchronizer();
synchronizer.Initialize(algorithm, algorithm.DataManager, new());
feed.Initialize(algorithm, job, resultHandler, TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider, algorithm.DataManager, synchronizer, dataPermissionManager.DataChannelProvider);
var equity = algorithm.AddEquity("SPY", fillForward: true, dataNormalizationMode: DataNormalizationMode.Raw);
algorithm.SetWarmup(1000);
algorithm.PostInitialize();
QuoteBar lastWarmupQuoteBar = null;
TradeBar lastWarmupTradeBar = null;
QuoteBar lastQuoteBar = null;
TradeBar lastTradeBar = null;
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
foreach (var timeSlice in synchronizer.StreamData(cancellationTokenSource.Token))
{
if (!timeSlice.IsTimePulse && timeSlice.Time.Date <= algorithm.EndDate)
{
Assert.IsTrue(timeSlice.Slice.QuoteBars.TryGetValue(equity.Symbol, out var quoteBar));
Assert.IsTrue(timeSlice.Slice.Bars.TryGetValue(equity.Symbol, out var tradeBar));
if (timeSlice.Slice.Time <= algorithm.StartDate)
{
lastWarmupQuoteBar = quoteBar;
lastWarmupTradeBar = tradeBar;
}
else
{
lastQuoteBar = quoteBar;
lastTradeBar = tradeBar;
// We don't have local data for the start-end range, so we expect all data to be fill-forwarded
Assert.IsTrue(lastQuoteBar.IsFillForward);
Assert.IsTrue(lastTradeBar.IsFillForward);
}
}
}
feed.Exit();
algorithm.DataManager.RemoveAllSubscriptions();
// Assert we actually got warmup data
Assert.IsNotNull(lastWarmupQuoteBar);
Assert.IsNotNull(lastWarmupTradeBar);
// Assert we got normal data
Assert.IsNotNull(lastQuoteBar);
Assert.IsNotNull(lastTradeBar);
}
}
}
@@ -0,0 +1,173 @@
/*
* 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 System.Threading;
using System.Threading.Tasks;
using QuantConnect.Data;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
using QuantConnect.Packets;
using QuantConnect.Tests.Common.Data;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
/// <summary>
/// Provides an implementation of <see cref="IDataQueueHandler"/> that can be specified
/// via a function
/// </summary>
public class FuncDataQueueHandler : IDataQueueHandler
{
private readonly HashSet<SubscriptionDataConfig> _subscriptions;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly AggregationManager _aggregationManager;
private readonly DataQueueHandlerSubscriptionManager _subscriptionManager;
/// <summary>
/// Gets the subscriptions configurations currently being managed by the queue handler
/// </summary>
public List<SubscriptionDataConfig> SubscriptionDataConfigs
{
get { lock (_subscriptions) return _subscriptions.ToList(); }
}
/// <summary>
/// Gets the subscriptions Symbols currently being managed by the queue handler
/// </summary>
public List<Symbol> Subscriptions => _subscriptionManager.GetSubscribedSymbols().ToList();
/// <summary>
/// Returns whether the data provider is connected
/// </summary>
/// <returns>true if the data provider is connected</returns>
public bool IsConnected => true;
/// <summary>
/// Initializes a new instance of the <see cref="FuncDataQueueHandler"/> class
/// </summary>
/// <param name="getNextTicksFunction">The functional implementation to get ticks function</param>
/// <param name="timeProvider">The time provider to use</param>
public FuncDataQueueHandler(Func<FuncDataQueueHandler, IEnumerable<BaseData>> getNextTicksFunction, ITimeProvider timeProvider, IAlgorithmSettings algorithmSettings)
{
_subscriptions = new HashSet<SubscriptionDataConfig>();
_cancellationTokenSource = new CancellationTokenSource();
_aggregationManager = new TestAggregationManager(timeProvider);
_aggregationManager.Initialize(new DataAggregatorInitializeParameters() { AlgorithmSettings = algorithmSettings });
_subscriptionManager = new FakeDataQueuehandlerSubscriptionManager((t) => "quote-trade");
Task.Factory.StartNew(() =>
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
var emitted = false;
try
{
foreach (var baseData in getNextTicksFunction(this))
{
if (_cancellationTokenSource.IsCancellationRequested)
{
break;
}
emitted = true;
_aggregationManager.Update(baseData);
}
}
catch (Exception exception)
{
if (exception is ObjectDisposedException)
{
return;
}
Log.Error(exception);
}
if (!emitted)
{
Thread.Sleep(50);
}
else
{
Thread.Sleep(10);
}
}
}, TaskCreationOptions.LongRunning);
}
/// <summary>
/// Sets the job we're subscribing for
/// </summary>
/// <param name="job">Job we're subscribing for</param>
public void SetJob(LiveNodePacket job)
{
}
/// <summary>
/// Adds the specified symbols to the subscription
/// </summary>
/// <param name="dataConfig">defines the parameters to subscribe to a data feed</param>
/// <param name="newDataAvailableHandler">handler to be fired on new data available</param>
/// <returns>The new enumerator for this subscription request</returns>
public IEnumerator<BaseData> Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
{
var enumerator = _aggregationManager.Add(dataConfig, newDataAvailableHandler);
lock (_subscriptions)
{
_subscriptions.Add(dataConfig);
_subscriptionManager.Subscribe(dataConfig);
}
return enumerator;
}
/// <summary>
/// Removes the specified configuration
/// </summary>
/// <param name="dataConfig">The data config to remove</param>
public void Unsubscribe(SubscriptionDataConfig dataConfig)
{
lock (_subscriptions)
{
_subscriptions.Remove(dataConfig);
_subscriptionManager.Subscribe(dataConfig);
}
_aggregationManager.Remove(dataConfig);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (!_cancellationTokenSource.IsCancellationRequested)
{
_cancellationTokenSource.Cancel();
}
_aggregationManager.DisposeSafely();
_cancellationTokenSource.DisposeSafely();
}
private class TestAggregationManager : AggregationManager
{
public TestAggregationManager(ITimeProvider timeProvider)
{
TimeProvider = timeProvider;
}
}
}
}
@@ -0,0 +1,72 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Interfaces;
namespace QuantConnect.Tests.Engine.DataFeeds
{
/// <summary>
/// Provides an implementation of <see cref="IDataQueueHandler"/> and <see cref="IDataQueueUniverseProvider"/>
/// that can be specified via functions
/// </summary>
public class FuncDataQueueHandlerUniverseProvider : FuncDataQueueHandler, IDataQueueUniverseProvider
{
private readonly Func<Symbol, bool, string, IEnumerable<Symbol>> _lookupSymbolsFunction;
private readonly Func<bool> _canPerformSelectionFunction;
/// <summary>
/// Initializes a new instance of the <see cref="FuncDataQueueHandlerUniverseProvider"/> class
/// </summary>
/// <param name="getNextTicksFunction">The functional implementation for the <see cref="FuncDataQueueHandler.GetNextTicks"/> function</param>
/// <param name="lookupSymbolsFunction">The functional implementation for the <see cref="IDataQueueUniverseProvider.LookupSymbols"/> function</param>
/// <param name="canPerformSelectionFunction">The functional implementation for the <see cref="IDataQueueUniverseProvider.CanPerformSelection"/> function</param>
/// <param name="timeProvider">The time provider instance to use</param>
public FuncDataQueueHandlerUniverseProvider(
Func<FuncDataQueueHandler, IEnumerable<BaseData>> getNextTicksFunction,
Func<Symbol, bool, string, IEnumerable<Symbol>> lookupSymbolsFunction,
Func<bool> canPerformSelectionFunction,
ITimeProvider timeProvider, IAlgorithmSettings algorithmSettings)
: base(getNextTicksFunction, timeProvider, algorithmSettings)
{
_lookupSymbolsFunction = lookupSymbolsFunction;
_canPerformSelectionFunction = canPerformSelectionFunction;
}
/// <summary>
/// Method returns a collection of Symbols that are available at the data source.
/// </summary>
/// <param name="symbol">Symbol to lookup</param>
/// <param name="includeExpired">Include expired contracts</param>
/// <param name="securityCurrency">Expected security currency(if any)</param>
/// <returns></returns>
public IEnumerable<Symbol> LookupSymbols(Symbol symbol, bool includeExpired, string securityCurrency = null)
{
return _lookupSymbolsFunction(symbol, includeExpired, securityCurrency);
}
/// <summary>
/// Returns whether selection can take place or not.
/// </summary>
/// <returns>True if selection can take place</returns>
public bool CanPerformSelection()
{
return _canPerformSelectionFunction();
}
}
}
@@ -0,0 +1,155 @@
/*
* 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.IO;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class IndexSubscriptionDataSourceReaderTests
{
private DateTime _initialDate;
private TestDataCacheProvider _dataCacheProvider;
[SetUp]
public void SetUp()
{
_initialDate = new DateTime(2018, 1, 1);
_dataCacheProvider = new TestDataCacheProvider();
}
[TearDown]
public void TearDown()
{
_dataCacheProvider.DisposeSafely();
}
[Test]
public void ThrowsIfDataIsNotIndexBased()
{
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
Assert.Throws<ArgumentException>(() => new IndexSubscriptionDataSourceReader(
_dataCacheProvider,
config,
_initialDate,
false,
TestGlobals.DataProvider,
null));
}
[Test]
public void GetsIndexAndSource()
{
var config = new SubscriptionDataConfig(
typeof(TestIndexedBasedFactory),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var reader = new IndexSubscriptionDataSourceReader(
_dataCacheProvider,
config,
_initialDate,
false,
TestGlobals.DataProvider,
null);
var source = (new TradeBar()).GetSource(config, _initialDate, false);
_dataCacheProvider.Data = "20000101 00:00,2,2,2,2,2";
var dataBars = reader.Read(source).First();
Assert.IsNotNull(dataBars);
Assert.IsNotNull(dataBars.Symbol, Symbols.SPY.Value);
Assert.AreEqual("20000101 00:00,2,2,2,2,2", TestIndexedBasedFactory.IndexLine);
Assert.AreEqual("20000101 00:00,2,2,2,2,2", TestIndexedBasedFactory.ReaderLine);
}
private class TestIndexedBasedFactory : IndexedBaseData
{
public static string ReaderLine { get; set; }
public static string IndexLine { get; set; }
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
ReaderLine = line;
var bar = new TradeBar();
return bar.Reader(config, line, date, isLiveMode);
}
public override SubscriptionDataSource GetSourceForAnIndex(SubscriptionDataConfig config, DateTime date, string index, bool isLiveMode)
{
IndexLine = index;
return new SubscriptionDataSource("",
SubscriptionTransportMedium.LocalFile,
FileFormat.Csv);
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
return new SubscriptionDataSource("",
SubscriptionTransportMedium.LocalFile,
FileFormat.Csv);
}
}
private class TestDataCacheProvider : IDataCacheProvider
{
private StreamWriter _writer;
public string Data { set; get; }
public bool IsDataEphemeral => false;
public Stream Fetch(string key)
{
var stream = new MemoryStream();
_writer = new StreamWriter(stream);
_writer.Write(Data);
_writer.Flush();
stream.Position = 0;
return stream;
}
public void Store(string key, byte[] data)
{
}
public List<string> GetZipEntries(string zipFile)
{
throw new NotImplementedException();
}
public void Dispose()
{
_writer.DisposeSafely();
}
}
}
}
@@ -0,0 +1,451 @@
/*
* 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 System.Threading;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Brokerages.Paper;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Queues;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Lean.Engine.TransactionHandlers;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class InternalSubscriptionManagerTests
{
private IResultHandler _resultHandler;
private Synchronizer _synchronizer;
private DataManager _dataManager;
private QCAlgorithm _algorithm;
private IDataFeed _dataFeed;
private AggregationManager _aggregationManager;
private PaperBrokerage _paperBrokerage;
private ITransactionHandler _transactionHandler;
[SetUp]
public void Setup()
{
SetupImpl(null, null, null);
}
[TearDown]
public void TearDown()
{
_transactionHandler.Exit();
_dataFeed.Exit();
_dataManager.RemoveAllSubscriptions();
_resultHandler.Exit();
_synchronizer.Dispose();
_aggregationManager.DisposeSafely();
_paperBrokerage.DisposeSafely();
}
[TestCaseSource(nameof(DataTypeTestCases))]
public void CreatesSubscriptions(SubscriptionRequest subscriptionRequest, bool liveMode, bool expectNewSubscription, bool isWarmup)
{
_algorithm.SetLiveMode(liveMode);
if (isWarmup)
{
_algorithm.SetWarmUp(10, Resolution.Daily);
}
_algorithm.PostInitialize();
var added = false;
var start = DateTime.UtcNow;
using var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(15));
foreach (var timeSlice in _synchronizer.StreamData(tokenSource.Token))
{
if (!added)
{
_algorithm.AddSecurity(subscriptionRequest.Security.Symbol, subscriptionRequest.Configuration.Resolution);
}
else if (!timeSlice.IsTimePulse)
{
Assert.AreEqual(
expectNewSubscription,
_algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(Symbols.BTCUSD, includeInternalConfigs: true).Any(config => config.IsInternalFeed)
);
if (expectNewSubscription)
{
var utcStartTime = _dataManager.DataFeedSubscriptions
.Where(subscription => subscription.Configuration.IsInternalFeed && subscription.Configuration.Symbol == Symbols.BTCUSD)
.Select(subscription => subscription.UtcStartTime)
.First();
Assert.Greater(utcStartTime.Ticks, start.Ticks);
// let's wait for a data point
if (timeSlice.DataPointCount > 0)
{
break;
}
if (DateTime.UtcNow - start > TimeSpan.FromSeconds(5))
{
Assert.Fail("Timeout waiting for data point");
}
}
else
{
break;
}
}
_algorithm.OnEndOfTimeStep();
if (!added)
{
added = true;
// give time for the base exchange to pick up the data point that will trigger the universe selection
// so next step we assert the internal config is there
Thread.Sleep(100);
}
else
{
Thread.Sleep(10);
}
}
Assert.IsFalse(tokenSource.IsCancellationRequested);
tokenSource.DisposeSafely();
}
[TestCaseSource(nameof(DataTypeTestCases))]
public void RemoveSubscriptions(SubscriptionRequest subscriptionRequest, bool liveMode, bool expectNewSubscription, bool isWarmup)
{
if (!expectNewSubscription)
{
// we only test cases where we expect an internal subscription
return;
}
_algorithm.SetLiveMode(liveMode);
if (isWarmup)
{
_algorithm.SetWarmUp(10, Resolution.Daily);
}
_algorithm.PostInitialize();
var added = false;
var shouldRemoved = false;
var count = 0;
using var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(15));
foreach (var timeSlice in _synchronizer.StreamData(tokenSource.Token))
{
if (!added)
{
_algorithm.AddSecurity(subscriptionRequest.Security.Symbol, subscriptionRequest.Configuration.Resolution);
}
else if (!timeSlice.IsTimePulse && !shouldRemoved)
{
Assert.IsTrue(_algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(Symbols.BTCUSD, includeInternalConfigs: true).Any());
_algorithm.RemoveSecurity(subscriptionRequest.Security.Symbol);
shouldRemoved = true;
}
else if (!timeSlice.IsTimePulse && shouldRemoved)
{
var result = _algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(Symbols.BTCUSD, includeInternalConfigs: true).Any(config => config.IsInternalFeed);
// can take some extra loop till the base exchange thread picks up the data point that will trigger the universe selection
if (!result || count++ > 5)
{
Assert.IsFalse(result);
}
break;
}
_algorithm.OnEndOfTimeStep();
if (!added)
{
added = true;
// give time for the base exchange to pick up the data point that will trigger the universe selection
// so next step we assert the internal config is there
Thread.Sleep(100);
}
else
{
Thread.Sleep(10);
}
}
Assert.IsFalse(tokenSource.IsCancellationRequested);
}
[Test]
public void PreMarketDataSetsCache()
{
var dataQueueTest = new FakeDataQueueTest();
dataQueueTest.ManualTimeProvider.SetCurrentTimeUtc(new DateTime(2020, 09, 03, 10, 0, 0));
TearDown();
var liveSynchronizer = new TestableLiveSynchronizer(dataQueueTest.ManualTimeProvider);
using var dataAggregator = new TestAggregationManager(dataQueueTest.ManualTimeProvider);
SetupImpl(dataQueueTest, liveSynchronizer, dataAggregator);
_algorithm.SetDateTime(dataQueueTest.ManualTimeProvider.GetUtcNow());
_algorithm.SetLiveMode(true);
_algorithm.PostInitialize();
var added = false;
var first = true;
var internalDataCount = 0;
using var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
foreach (var timeSlice in _synchronizer.StreamData(tokenSource.Token))
{
dataQueueTest.ManualTimeProvider.AdvanceSeconds(60);
_algorithm.SetDateTime(dataQueueTest.ManualTimeProvider.GetUtcNow());
if (dataQueueTest.ManualTimeProvider.GetUtcNow() >= new DateTime(2020, 09, 03, 13, 0, 0))
{
Assert.Fail("Timeout expect pre market data to set security prices");
}
if (!added)
{
_algorithm.AddEquity("IBM", Resolution.Minute);
_algorithm.AddEquity("AAPL", Resolution.Hour);
}
else if (!timeSlice.IsTimePulse)
{
if (timeSlice.SecuritiesUpdateData.Count > 0)
{
internalDataCount += timeSlice.SecuritiesUpdateData.Count(
data => data.IsInternalConfig && data.Target.Symbol == Symbols.AAPL
);
}
if (first)
{
Assert.IsTrue(_algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(Symbols.AAPL, includeInternalConfigs: true).Any(config => config.Resolution == Resolution.Second));
Assert.IsFalse(_algorithm.SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(Symbols.IBM, includeInternalConfigs: true)
.Any(config => config.IsInternalFeed && config.Resolution == Resolution.Second));
first = false;
}
else if (_algorithm.Securities["AAPL"].Price != 0 && _algorithm.Securities["IBM"].Price != 0)
{
#pragma warning disable CS0618
_algorithm.SetHoldings("AAPL", 0.01);
_algorithm.SetHoldings("IBM", 0.01);
var orders = _algorithm.Transactions.GetOpenOrders("AAPL");
Assert.AreEqual(1, orders.Count);
Assert.AreEqual(Symbols.AAPL, orders[0].Symbol);
Assert.AreEqual(OrderStatus.Submitted, orders[0].Status);
orders = _algorithm.Transactions.GetOpenOrders("IBM");
#pragma warning restore CS0618
Assert.AreEqual(1, orders.Count);
Assert.AreEqual(Symbols.IBM, orders[0].Symbol);
Assert.AreEqual(OrderStatus.Submitted, orders[0].Status);
break;
}
}
_algorithm.OnEndOfTimeStep();
if (!added)
{
added = true;
Thread.Sleep(100);
}
else
{
Thread.Sleep(10);
}
}
Assert.IsFalse(tokenSource.IsCancellationRequested);
Assert.AreNotEqual(0, internalDataCount);
}
[Test, Category("TravisExclude")]
public void UniverseSelectionAddAndRemove()
{
_algorithm.SetLiveMode(true);
_algorithm.PostInitialize();
_algorithm.UniverseSettings.Resolution = Resolution.Hour;
_algorithm.UniverseSettings.MinimumTimeInUniverse = TimeSpan.Zero;
var added = false;
using var manualEvent = new ManualResetEvent(false);
using var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(15));
foreach (var timeSlice in _synchronizer.StreamData(tokenSource.Token))
{
if (!added)
{
_algorithm.AddUniverse(SecurityType.Equity,
"AUniverse",
Resolution.Second,
Market.USA,
_algorithm.UniverseSettings,
time =>
{
return !manualEvent.WaitOne(0) ? new[] { "IBM" } : new[] { "AAPL" };
}
);
}
else if (!timeSlice.IsTimePulse)
{
if (!manualEvent.WaitOne(0))
{
Assert.IsTrue(_algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(Symbols.IBM, includeInternalConfigs: true).Any(config => config.Resolution == Resolution.Second),
"IBM subscription was not found");
Assert.IsFalse(_algorithm.SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(Symbols.AAPL, includeInternalConfigs: true).Any(),
"Unexpected AAPL subscription was found");
manualEvent.Set();
}
else
{
Assert.IsTrue(_algorithm.SubscriptionManager.SubscriptionDataConfigService
.GetSubscriptionDataConfigs(Symbols.AAPL, includeInternalConfigs: true).Any(config => config.Resolution == Resolution.Second),
"AAPL subscription was not found");
Assert.IsFalse(_algorithm.SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(Symbols.IBM, includeInternalConfigs: true).Any(),
"Unexpected IBM subscription was found");
break;
}
}
_algorithm.OnEndOfTimeStep();
if (!added)
{
added = true;
// we need to give time for the base data exchange to pick up the new data point which will trigger the selection
Thread.Sleep(100);
}
else
{
Thread.Sleep(10);
}
}
Assert.IsFalse(tokenSource.IsCancellationRequested, "Test timed out");
}
private static TestCaseData[] DataTypeTestCases
{
get
{
var result = new List<TestCaseData>();
var config = GetConfig(Symbols.BTCUSD, Resolution.Second);
result.Add(new TestCaseData(new SubscriptionRequest(false, null, CreateSecurity(config), config, DateTime.UtcNow, DateTime.UtcNow), true, false, false));
config = GetConfig(Symbols.BTCUSD, Resolution.Minute);
result.Add(new TestCaseData(new SubscriptionRequest(false, null, CreateSecurity(config), config, DateTime.UtcNow, DateTime.UtcNow), true, false, false));
config = GetConfig(Symbols.BTCUSD, Resolution.Hour);
result.Add(new TestCaseData(new SubscriptionRequest(false, null, CreateSecurity(config), config, DateTime.UtcNow, DateTime.UtcNow), true, true, false));
config = GetConfig(Symbols.BTCUSD, Resolution.Daily);
result.Add(new TestCaseData(new SubscriptionRequest(false, null, CreateSecurity(config), config, DateTime.UtcNow, DateTime.UtcNow), true, true, false));
config = GetConfig(Symbols.BTCUSD, Resolution.Daily);
result.Add(new TestCaseData(new SubscriptionRequest(false, null, CreateSecurity(config), config, DateTime.UtcNow, DateTime.UtcNow), true, true, true));
result.Add(new TestCaseData(new SubscriptionRequest(false, null, CreateSecurity(config), config, DateTime.UtcNow, DateTime.UtcNow), false, false, false));
return result.ToArray();
}
}
private static Security CreateSecurity(SubscriptionDataConfig config)
{
return new Security(SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
}
private static SubscriptionDataConfig GetConfig(Symbol symbol, Resolution resolution)
{
return new SubscriptionDataConfig(typeof(TradeBar), symbol, resolution, TimeZones.Utc, TimeZones.Utc, false, false, false);
}
private class FakeDataQueueTest : FakeDataQueue
{
public ManualTimeProvider ManualTimeProvider { get; } = new ManualTimeProvider();
protected override ITimeProvider TimeProvider => ManualTimeProvider;
}
private void SetupImpl(IDataQueueHandler dataQueueHandler, Synchronizer synchronizer, IDataAggregator dataAggregator)
{
_algorithm = new AlgorithmStub(createDataManager: false);
_aggregationManager = new AggregationManager();
_dataFeed = new TestableLiveTradingDataFeed(_algorithm.Settings, dataQueueHandler ?? new FakeDataQueue(dataAggregator ?? _aggregationManager));
_synchronizer = synchronizer ?? new LiveSynchronizer();
_algorithm.SetStartDate(new DateTime(2022, 04, 13));
var registeredTypesProvider = new RegisteredSecurityDataTypesProvider();
var securityService = new SecurityService(_algorithm.Portfolio.CashBook,
MarketHoursDatabase.FromDataFolder(),
SymbolPropertiesDatabase.FromDataFolder(),
_algorithm,
registeredTypesProvider,
new SecurityCacheProvider(_algorithm.Portfolio),
algorithm: _algorithm);
var universeSelection = new UniverseSelection(
_algorithm,
securityService,
new DataPermissionManager(),
TestGlobals.DataProvider,
Resolution.Second);
_dataManager = new DataManager(_dataFeed, universeSelection, _algorithm, new TimeKeeper(DateTime.UtcNow, TimeZones.NewYork),
MarketHoursDatabase.FromDataFolder(),
true,
new RegisteredSecurityDataTypesProvider(),
new DataPermissionManager());
_resultHandler = new TestResultHandler();
_synchronizer.Initialize(_algorithm, _dataManager, new());
_dataFeed.Initialize(_algorithm,
new LiveNodePacket(),
_resultHandler,
TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider,
TestGlobals.DataProvider,
_dataManager,
_synchronizer,
new DataChannelProvider());
_algorithm.SubscriptionManager.SetDataManager(_dataManager);
_algorithm.Securities.SetSecurityService(securityService);
var backtestingTransactionHandler = new SynchronousBacktestingTransactionHandler();
_paperBrokerage = new PaperBrokerage(_algorithm, new LiveNodePacket());
backtestingTransactionHandler.Initialize(_algorithm, _paperBrokerage, _resultHandler);
_algorithm.Transactions.SetOrderProcessor(backtestingTransactionHandler);
if (_transactionHandler != null)
{
_transactionHandler.Exit();
}
_transactionHandler = backtestingTransactionHandler;
}
private class SynchronousBacktestingTransactionHandler : BacktestingTransactionHandler
{
protected override bool SynchronousProcessing => true;
}
private class TestAggregationManager : AggregationManager
{
public TestAggregationManager(ITimeProvider timeProvider)
{
TimeProvider = timeProvider;
}
}
}
}
@@ -0,0 +1,158 @@
/*
* 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 System.Threading;
using Moq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Lean.Engine.TransactionHandlers;
using QuantConnect.Logging;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class LiveCoarseUniverseTests
{
[Test]
public void CoarseUniverseRotatesActiveSecurity()
{
var startDate = new DateTime(2014, 3, 24);
var endDate = new DateTime(2014, 3, 31);
var timeProvider = new ManualTimeProvider(TimeZones.NewYork);
timeProvider.SetCurrentTime(startDate);
var coarseTimes = new List<DateTime>
{
// coarse files go from the 24th (Monday) to the 28th (Friday)
// they are emitted in the next day, excluding saturday
new DateTime(2014, 3, 25, 5, 0, 0, 0),
new DateTime(2014, 3, 26, 5, 0, 0, 0),
new DateTime(2014, 3, 27, 5, 0, 0, 0),
new DateTime(2014, 3, 28, 5, 0, 0, 0),
// 29th is Saturday
new DateTime(2014, 3, 30, 5, 0, 0, 0)
}.ToHashSet();
var coarseSymbols = new List<Symbol> { Symbols.SPY, Symbols.AAPL, Symbols.MSFT };
using var emitted = new AutoResetEvent(false);
using var dataQueueHandler = new FuncDataQueueHandler(fdqh => Enumerable.Empty<BaseData>(), timeProvider, new AlgorithmSettings());
var feed = new TestableLiveTradingDataFeed(new AlgorithmSettings(), dataQueueHandler);
var algorithm = new AlgorithmStub(feed);
algorithm.SetLiveMode(true);
var mock = new Mock<ITransactionHandler>();
mock.Setup(m => m.GetOpenOrders(It.IsAny<Func<Order, bool>>())).Returns(new List<Order>());
algorithm.Transactions.SetOrderProcessor(mock.Object);
using var synchronizer = new TestableLiveSynchronizer(timeProvider);
synchronizer.Initialize(algorithm, algorithm.DataManager, new());
feed.Initialize(algorithm, new LiveNodePacket(), new BacktestingResultHandler(),
TestGlobals.MapFileProvider, TestGlobals.FactorFileProvider, TestGlobals.DataProvider, algorithm.DataManager, synchronizer, new DataChannelProvider());
var symbolIndex = 0;
var coarseUniverseSelectionCount = 0;
algorithm.AddUniverse(
coarse =>
{
Log.Trace($"Emitted at {algorithm.Time}. Coarse {coarse.First().Time} to {coarse.First().EndTime}");
Interlocked.Increment(ref coarseUniverseSelectionCount);
emitted.Set();
// rotate single symbol in universe
if (symbolIndex == coarseSymbols.Count) symbolIndex = 0;
return new[] { coarseSymbols[symbolIndex++] };
});
algorithm.PostInitialize();
using var cancellationTokenSource = new CancellationTokenSource();
Exception exceptionThrown = null;
// create a timer to advance time much faster than realtime
var timerInterval = TimeSpan.FromMilliseconds(5);
var timer = Ref.Create<Timer>(null);
timer.Value = new Timer(state =>
{
try
{
var currentTime = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
if (currentTime.Date > endDate.Date)
{
feed.Exit();
cancellationTokenSource.Cancel();
return;
}
timeProvider.Advance(TimeSpan.FromHours(1));
var time = timeProvider.GetUtcNow().ConvertFromUtc(TimeZones.NewYork);
algorithm.SetDateTime(timeProvider.GetUtcNow());
if (coarseTimes.Contains(time))
{
// lets wait for coarse to emit
if (!emitted.WaitOne(TimeSpan.FromMilliseconds(15000)))
{
throw new TimeoutException($"Timeout waiting for coarse to emit at {time}");
}
}
var activeSecuritiesCount = algorithm.ActiveSecurities.Count;
Assert.That(activeSecuritiesCount <= 1);
// restart the timer
timer.Value.Change(timerInterval, Timeout.InfiniteTimeSpan);
}
catch (Exception exception)
{
Log.Error(exception);
exceptionThrown = exception;
feed.Exit();
cancellationTokenSource.Cancel();
}
}, null, timerInterval, Timeout.InfiniteTimeSpan);
foreach (var _ in synchronizer.StreamData(cancellationTokenSource.Token)) { }
timer.Value.DisposeSafely();
algorithm.DataManager.RemoveAllSubscriptions();
if (exceptionThrown != null)
{
throw new RegressionTestException("Exception in timer: ", exceptionThrown);
}
Assert.AreEqual(coarseTimes.Count, coarseUniverseSelectionCount, message: "coarseUniverseSelectionCount");
}
}
}
File diff suppressed because it is too large Load Diff
+60
View File
@@ -0,0 +1,60 @@
/*
* 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.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Packets;
namespace QuantConnect.Lean.Engine.DataFeeds
{
public class MockDataFeed : IDataFeed
{
private List<SubscriptionData> _dummyData = new List<SubscriptionData>();
public bool IsActive { get; }
public void Initialize(
IAlgorithm algorithm,
AlgorithmNodePacket job,
IResultHandler resultHandler,
IMapFileProvider mapFileProvider,
IFactorFileProvider factorFileProvider,
IDataProvider dataProvider,
IDataFeedSubscriptionManager subscriptionManager,
IDataFeedTimeProvider dataFeedTimeProvider,
IDataChannelProvider dataChannelProvider
)
{
}
public Subscription CreateSubscription(SubscriptionRequest request)
{
var offsetProvider = new TimeZoneOffsetProvider(request.Configuration.ExchangeTimeZone,
request.StartTimeUtc,
request.EndTimeUtc);
return new Subscription(request, _dummyData.GetEnumerator(), offsetProvider);
}
public void RemoveSubscription(Subscription subscription)
{
}
public void Exit()
{
}
}
}
@@ -0,0 +1,272 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Orders;
using QuantConnect.Securities;
using QuantConnect.Tests.Common.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class PendingRemovalsManagerTests
{
private static readonly DateTime Noon = new DateTime(2015, 11, 2, 12, 0, 0);
private static readonly TimeKeeper TimeKeeper = new TimeKeeper(Noon.ConvertToUtc(TimeZones.NewYork), new[] { TimeZones.NewYork });
[Test]
public void ReturnedRemoved_Add()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
var result = pendingRemovals.TryRemoveMember(member, universe);
Assert.IsTrue(result.Any());
Assert.AreEqual(universe, result.First().Universe);
Assert.AreEqual(security, result.First().Security);
Assert.IsFalse(pendingRemovals.CheckPendingRemovals(new HashSet<Symbol>(), universe).Any());
Assert.AreEqual(0, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(0, pendingRemovals.PendingRemovals.Values.Count());
}
[Test]
public void ReturnedRemoved_Check()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
orderProvider.AddOrder(new LimitOrder(security.Symbol, 1, 1, DateTime.UtcNow));
pendingRemovals.TryRemoveMember(member, universe);
orderProvider.Clear();
var result = pendingRemovals.CheckPendingRemovals(new HashSet<Symbol>(), universe);
Assert.IsTrue(result.Any());
Assert.AreEqual(universe, result.First().Universe);
Assert.AreEqual(security, result.First().Security);
Assert.IsFalse(pendingRemovals.CheckPendingRemovals(new HashSet<Symbol>(), universe).Any());
Assert.AreEqual(0, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(0, pendingRemovals.PendingRemovals.Values.Count());
}
[Test]
public void WontRemoveBecauseOfUnderlying()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var equity = CreateSecurity(Symbols.SPY);
var member = new Universe.Member(Noon, equity, false);
var equityOption = CreateSecurity(Symbols.SPY_C_192_Feb19_2016);
// we add an order of the equity option
orderProvider.AddOrder(new LimitOrder(equityOption.Symbol, 1, 1, DateTime.UtcNow));
using var universe = new TestUniverse();
universe.AddMember(DateTime.UtcNow, equity, false);
universe.AddMember(DateTime.UtcNow, equityOption, false);
// we try to remove the equity
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(equity, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
[Test]
public void WontRemoveBecauseOpenOrder_Add()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
orderProvider.AddOrder(new LimitOrder(security.Symbol, 1, 1, DateTime.UtcNow));
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(security, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
[Test]
public void WontRemoveBecauseOpenOrder_Check()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
orderProvider.AddOrder(new LimitOrder(security.Symbol, 1, 1, DateTime.UtcNow));
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.IsFalse(pendingRemovals.CheckPendingRemovals(new HashSet<Symbol>(), universe).Any());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(security, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
[Test]
public void WontRemoveBecauseHoldings_Add()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
security.Holdings.SetHoldings(10, 10);
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(security, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
[Test]
public void WontRemoveBecauseHoldings_Check()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
security.Holdings.SetHoldings(10, 10);
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.IsFalse(pendingRemovals.CheckPendingRemovals(new HashSet<Symbol>(), universe).Any());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(security, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
[Test]
public void WontRemoveBecauseTarget_Add()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
security.Holdings.Target = new PortfolioTarget(security.Symbol, 10);
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(security, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
[Test]
public void WontRemoveBecauseTarget_Check()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
security.Holdings.Target = new PortfolioTarget(security.Symbol, 10);
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.IsFalse(pendingRemovals.CheckPendingRemovals(new HashSet<Symbol>(), universe).Any());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(security, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
[Test]
public void WontBeReturnedBecauseReSelected()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
using var universe = new TestUniverse();
orderProvider.AddOrder(new LimitOrder(security.Symbol, 1, 1, DateTime.UtcNow));
pendingRemovals.TryRemoveMember(member, universe);
Assert.IsFalse(pendingRemovals.CheckPendingRemovals(
new HashSet<Symbol> { security.Symbol}, universe).Any());
// internally it was removed because it was reselected
Assert.AreEqual(0, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(0, pendingRemovals.PendingRemovals.Values.Count());
}
[Test]
public void WontRemoveBecauseUnsettledFunds()
{
var orderProvider = new FakeOrderProcessor();
var pendingRemovals = new PendingRemovalsManager(orderProvider);
using var universe = new TestUniverse();
var security = SecurityTests.GetSecurity();
var member = new Universe.Member(Noon, security, false);
security.SetSettlementModel(new DelayedSettlementModel(1, TimeSpan.FromHours(8)));
var securities = new SecurityManager(TimeKeeper);
var transactions = new SecurityTransactionManager(null, securities);
var portfolio = new SecurityPortfolioManager(securities, transactions, new AlgorithmSettings());
security.SettlementModel.ApplyFunds(new ApplyFundsSettlementModelParameters(portfolio, security, TimeKeeper.UtcTime.Date.AddDays(1),
new CashAmount(1000, Currencies.USD), null));
Assert.IsNull(pendingRemovals.TryRemoveMember(member, universe));
Assert.IsFalse(pendingRemovals.CheckPendingRemovals(new HashSet<Symbol>(), universe).Any());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Keys.Count());
Assert.AreEqual(1, pendingRemovals.PendingRemovals.Values.Count());
Assert.AreEqual(universe, pendingRemovals.PendingRemovals.Keys.First());
Assert.AreEqual(security, pendingRemovals.PendingRemovals.Values.First().First().Security);
}
private class TestUniverse : Universe
{
public TestUniverse()
: base(SecurityTests.CreateTradeBarConfig())
{
}
public override IEnumerable<Symbol> SelectSymbols(DateTime utcTime, BaseDataCollection data)
{
throw new NotImplementedException();
}
}
private static Security CreateSecurity(Symbol symbol)
{
return new Security(symbol,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
}
}
}
@@ -0,0 +1,102 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
using System;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class PrecalculatedSubscriptionDataTests
{
private SubscriptionDataConfig _config;
[SetUp]
public void Setup()
{
_config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
}
[Test]
public void ChangeDataNormalizationMode()
{
var tb = new TradeBar
{
Time = new DateTime(2020, 5, 21, 8, 9, 0),
Period = TimeSpan.FromHours(1),
Symbol = Symbols.SPY,
Open = 100,
High = 200,
Low = 300,
Close = 400
};
var factor = 0.5m;
var sumOfDividends = 100m;
var adjustedTb = tb.Clone(tb.IsFillForward).Normalize(factor, DataNormalizationMode.Adjusted, 0);
var exchangeHours = SecurityExchangeHours.AlwaysOpen(TimeZones.Utc);
var offsetProvider = new TimeZoneOffsetProvider(TimeZones.Utc, new DateTime(2020, 5, 21), new DateTime(2020, 5, 22));
var emitTimeUtc = offsetProvider.ConvertToUtc(tb.EndTime);
_config.SumOfDividends = sumOfDividends;
var subscriptionData = new PrecalculatedSubscriptionData(
_config,
tb,
adjustedTb,
DataNormalizationMode.Adjusted,
emitTimeUtc);
_config.DataNormalizationMode = DataNormalizationMode.Raw;
Assert.AreEqual(tb.Open, (subscriptionData.Data as TradeBar).Open);
Assert.AreEqual(tb.High, (subscriptionData.Data as TradeBar).High);
Assert.AreEqual(tb.Low, (subscriptionData.Data as TradeBar).Low);
Assert.AreEqual(tb.Close, (subscriptionData.Data as TradeBar).Close);
_config.DataNormalizationMode = DataNormalizationMode.Adjusted;
Assert.AreEqual(tb.Open * factor, (subscriptionData.Data as TradeBar).Open);
Assert.AreEqual(tb.High * factor, (subscriptionData.Data as TradeBar).High);
Assert.AreEqual(tb.Low * factor, (subscriptionData.Data as TradeBar).Low);
Assert.AreEqual(tb.Close * factor, (subscriptionData.Data as TradeBar).Close);
_config.DataNormalizationMode = DataNormalizationMode.TotalReturn;
Assert.Throws<ArgumentException>(() =>
{
var data = subscriptionData.Data;
}
);
_config.DataNormalizationMode = DataNormalizationMode.SplitAdjusted;
Assert.Throws<ArgumentException>(() =>
{
var data = subscriptionData.Data;
}
);
}
}
}
@@ -0,0 +1,47 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Lean.Engine.DataFeeds;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class PredicateTimeProviderTests
{
[Test]
public void RespectsCustomStepEvaluator()
{
var startTime = new DateTime(2018, 1, 1);
var manualTimeProvider = new ManualTimeProvider(startTime);
var stepTimeProvider = new PredicateTimeProvider(manualTimeProvider,
// only step when minute is a pair number
time => time.Minute % 2 == 0);
Assert.AreEqual(manualTimeProvider.GetUtcNow(), stepTimeProvider.GetUtcNow());
manualTimeProvider.AdvanceSeconds(45 * 60); // advance 45 minutes, past the interval
// still the same because 45 minutes isn't pair
Assert.AreEqual(startTime, stepTimeProvider.GetUtcNow());
Assert.AreNotEqual(manualTimeProvider.GetUtcNow(), stepTimeProvider.GetUtcNow());
manualTimeProvider.AdvanceSeconds(60);
Assert.AreEqual(manualTimeProvider.GetUtcNow(), stepTimeProvider.GetUtcNow());
}
}
}
@@ -0,0 +1,46 @@
/*
* 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.Threading;
using NUnit.Framework;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Explicit("Performance test")]
public class RealTimeScheduleEventServiceTests
{
[Test]
public void Accuracy()
{
using var scheduledEventService = new RealTimeScheduleEventService(RealTimeProvider.Instance);
EventHandler handler = (_, __) =>
{
Log.Trace($"{DateTime.UtcNow:O}");
var now = DateTime.UtcNow;
var nextEventTime = now.RoundDown(TimeSpan.FromSeconds(1)).Add(TimeSpan.FromSeconds(1) + TimeSpan.FromMilliseconds(101));
scheduledEventService.ScheduleEvent(nextEventTime - now, now);
};
scheduledEventService.NewEvent += handler;
handler(this, null);
Thread.Sleep(5000);
}
}
}
+70
View File
@@ -0,0 +1,70 @@
/*
* 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 Newtonsoft.Json;
using NodaTime;
using QuantConnect.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Tests.Engine.DataFeeds
{
/// <summary>
/// Custom data type that causes rest api calls
/// </summary>
public class RestApiBaseData : TradeBar
{
public static int ReaderCount = 0;
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
ReaderCount++;
//[{"symbol":"SPY","time":1444271505,"alpha":1,"beta":2}]
var array = JsonConvert.DeserializeObject<JsonSerialization[]>(line);
if (array.Length > 0)
{
return array[0].ToBaseData(config.DataTimeZone, config.Increment, config.Symbol);
}
return null;
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var remoteFileSource = @"https://www.quantconnect.com/live-test?type=rest&symbols=" + config.Symbol.Value;
//remoteFileSource = @"http://beta.quantconnect.com/live-test?type=rest&symbols=" + config.Symbol.Value;
return new SubscriptionDataSource(remoteFileSource, SubscriptionTransportMedium.Rest, FileFormat.Csv);
}
private class JsonSerialization
{
public string symbol = String.Empty;
public double time = 0;
public double alpha = 0;
public double beta = 0;
public RestApiBaseData ToBaseData(DateTimeZone timeZone, TimeSpan period, Symbol sym)
{
var dateTime = QuantConnect.Time.UnixTimeStampToDateTime(time).ConvertFromUtc(timeZone).Subtract(period);
return new RestApiBaseData
{
Symbol = sym,
Time = dateTime,
EndTime = dateTime.Add(period),
Value = (decimal) alpha
};
}
}
}
}
@@ -0,0 +1,419 @@
/*
* 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);
}
}
}
@@ -0,0 +1,189 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
using QuantConnect.Util;
using System;
using System.Collections.Generic;
using System.IO;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class SubscriptionDataReaderTests
{
// this is the core of this unit test:
// the stream will have two data points one of which does not correspond to the tradable date,
// and it shouldn't be emitted because we have a new source for the next tradable date
[TestCase(@"25980000,1679600,1679700,1679600,1679700,200
99900000,1679400,1679400,1679200,1679200,600", false, Resolution.Minute)]
// this test case has two data point which should be emitted because they correspond to the same tradable date
[TestCase(@"25980000,1679600,1679700,1679600,1679700,200
30980000,1679400,1679400,1679200,1679200,600", true, Resolution.Minute)]
// even if the second data point is another tradable date we emit it because daily resolution
// always uses the same source
[TestCase(@"20191209 00:00,956900,959700,947200,958100,3647000
20191211 00:00,956900,959700,947200,958100,3647000", true, Resolution.Daily)]
public void DoesNotEmitDataBeyondTradableDate(string data, bool shouldEmitSecondDataPoint, Resolution dataResolution)
{
var start = new DateTime(2019, 12, 9);
var end = new DateTime(2019, 12, 12);
var request = GetRequest(typeof(TradeBar), start, end, dataResolution, out var config);
using var testDataCacheProvider = new TestDataCacheProvider() { Data = data };
using var dataReader = new SubscriptionDataReader(config,
request,
TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider,
testDataCacheProvider,
TestGlobals.DataProvider,
null);
Assert.IsTrue(dataReader.MoveNext());
Assert.AreEqual(shouldEmitSecondDataPoint, dataReader.MoveNext());
}
[TestCase(typeof(TradeBar))]
[TestCase(typeof(QuoteBar))]
[TestCase(typeof(OpenInterest))]
public void EmitsNewTradableDateWhenDateAfterDelistingIsNonTradable(Type dataType)
{
var start = new DateTime(2023, 06, 30);
var end = new DateTime(2023, 08, 01);
var request = GetRequest(dataType, start, end, Resolution.Minute, out var config);
using var testDataCacheProvider = new TestDataCacheProvider();
using var dataReader = new SubscriptionDataReader(config,
request,
TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider,
testDataCacheProvider,
TestGlobals.DataProvider,
null);
var expectedLastTradableDate = new DateTime(2023, 07, 05);
var lastTradableDate = default(DateTime);
dataReader.NewTradableDate += (sender, args) =>
{
lastTradableDate = args.Date;
};
while (dataReader.MoveNext())
{
}
Assert.AreEqual(expectedLastTradableDate, lastTradableDate);
}
[Test]
public void DoesNotYieldDataWhenDelisted()
{
var start = new DateTime(2023, 8, 1);
var end = new DateTime(2023, 08, 10);
var request = GetRequest(typeof(TradeBar), start, end, Resolution.Minute, out var config);
using var testDataCacheProvider = new TestDataCacheProvider();
using var dataReader = new SubscriptionDataReader(config,
request,
TestGlobals.MapFileProvider,
TestGlobals.FactorFileProvider,
testDataCacheProvider,
TestGlobals.DataProvider,
null);
var dataYielded = false;
var newTradableDateCalled = false;
dataReader.NewTradableDate += (sender, args) =>
{
newTradableDateCalled = true;
};
while (dataReader.MoveNext())
{
dataYielded = true;
}
Assert.IsFalse(dataYielded);
Assert.IsFalse(newTradableDateCalled);
}
private static BaseDataRequest GetRequest(Type dataType, DateTime start, DateTime end, Resolution resolution, out SubscriptionDataConfig config)
{
var symbol = Symbol.CreateOption(
Symbols.SPX,
"SPXW",
Market.USA,
OptionStyle.European,
OptionRight.Call,
4445m,
// Next day is a holiday
new DateTime(2023, 7, 3));
var entry = MarketHoursDatabase.FromDataFolder().GetEntry(symbol.ID.Market, symbol, symbol.SecurityType);
config = new SubscriptionDataConfig(dataType,
symbol,
resolution,
entry.DataTimeZone,
entry.ExchangeHours.TimeZone,
false,
false,
false);
return new HistoryRequest(config, entry.ExchangeHours, start, end);
}
private class TestDataCacheProvider : IDataCacheProvider
{
private StreamWriter _writer;
private bool _alreadyEmitted;
public string Data { get; set; }
public void Dispose()
{
_writer.DisposeSafely();
}
public List<string> GetZipEntries(string zipFile)
{
throw new NotImplementedException();
}
public bool IsDataEphemeral => true;
public Stream Fetch(string key)
{
if (_alreadyEmitted)
{
return null;
}
_alreadyEmitted = true;
var stream = new MemoryStream();
_writer = new StreamWriter(stream);
_writer.Write(Data);
_writer.Flush();
stream.Position = 0;
return stream;
}
public void Store(string key, byte[] data)
{
}
}
}
}
@@ -0,0 +1,315 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
using System;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class SubscriptionDataTests
{
[Test]
public void CreatedSubscriptionRoundsTimeDownForDataWithPeriod()
{
var tb = new TradeBar
{
Time = new DateTime(2020, 5, 21, 8, 9, 0),
Period = TimeSpan.FromHours(1),
Symbol = Symbols.SPY
};
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.Utc,
TimeZones.Utc,
false,
false,
false
);
var exchangeHours = SecurityExchangeHours.AlwaysOpen(TimeZones.Utc);
var offsetProvider = new TimeZoneOffsetProvider(TimeZones.Utc, new DateTime(2020, 5, 21), new DateTime(2020, 5, 22));
var subscription = SubscriptionData.Create(false, config, exchangeHours, offsetProvider, tb, config.DataNormalizationMode);
Assert.AreEqual(new DateTime(2020, 5, 21, 8, 0, 0), subscription.Data.Time);
Assert.AreEqual(new DateTime(2020, 5, 21, 9, 0, 0), subscription.Data.EndTime);
}
[Test]
public void CreatedSubscriptionDoesNotRoundDownForPeriodLessData()
{
var data = new MyCustomData
{
Time = new DateTime(2020, 5, 21, 8, 9, 0),
Symbol = Symbols.SPY
};
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.Utc,
TimeZones.Utc,
false,
false,
false
);
var exchangeHours = SecurityExchangeHours.AlwaysOpen(TimeZones.Utc);
var offsetProvider = new TimeZoneOffsetProvider(TimeZones.Utc, new DateTime(2020, 5, 21), new DateTime(2020, 5, 22));
var subscription = SubscriptionData.Create(false, config, exchangeHours, offsetProvider, data, config.DataNormalizationMode);
Assert.AreEqual(new DateTime(2020, 5, 21, 8, 9, 0), subscription.Data.Time);
Assert.AreEqual(new DateTime(2020, 5, 21, 8, 9, 0), subscription.Data.EndTime);
}
[TestCase(1, 0)]
[TestCase(null, 0)]
[TestCase(null, 1000)]
public void CreateDefaults(decimal? scale, decimal dividends)
{
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.Utc,
TimeZones.Utc,
false,
false,
false
);
config.SumOfDividends = dividends;
var tb = new TradeBar
{
Time = new DateTime(2020, 5, 21, 8, 9, 0),
Period = TimeSpan.FromHours(1),
Symbol = Symbols.SPY,
Open = 100,
High = 200,
Low = 300,
Close = 400
};
var data = SubscriptionData.Create(false,
config,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new TimeZoneOffsetProvider(TimeZones.NewYork, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1)),
tb,
config.DataNormalizationMode,
scale);
Assert.True(data.GetType() == typeof(SubscriptionData));
Assert.AreEqual(tb.Open, (data.Data as TradeBar).Open);
Assert.AreEqual(tb.High, (data.Data as TradeBar).High);
Assert.AreEqual(tb.Low, (data.Data as TradeBar).Low);
Assert.AreEqual(tb.Close, (data.Data as TradeBar).Close);
}
[TestCase(typeof(SubscriptionData), 1)]
[TestCase(typeof(PrecalculatedSubscriptionData), 2)]
[TestCase(typeof(PrecalculatedSubscriptionData), 0.5)]
public void CreateZeroDividends(Type type, decimal? scale)
{
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.Utc,
TimeZones.Utc,
false,
false,
false
);
config.SumOfDividends = 0;
var tb = new TradeBar
{
Time = new DateTime(2020, 5, 21, 8, 9, 0),
Period = TimeSpan.FromHours(1),
Symbol = Symbols.SPY,
Open = 100,
High = 200,
Low = 300,
Close = 400
};
var data = SubscriptionData.Create(false,
config,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new TimeZoneOffsetProvider(TimeZones.NewYork, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1)),
tb,
config.DataNormalizationMode,
scale);
Assert.True(data.GetType() == type);
Assert.AreEqual(tb.Open * scale, (data.Data as TradeBar).Open);
Assert.AreEqual(tb.High * scale, (data.Data as TradeBar).High);
Assert.AreEqual(tb.Low * scale, (data.Data as TradeBar).Low);
Assert.AreEqual(tb.Close * scale, (data.Data as TradeBar).Close);
}
[TestCase(typeof(PrecalculatedSubscriptionData), 1)]
[TestCase(typeof(PrecalculatedSubscriptionData), 2)]
[TestCase(typeof(PrecalculatedSubscriptionData), 0.5)]
public void CreateAdjustedNotZeroDividends(Type type, decimal? scale)
{
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.Utc,
TimeZones.Utc,
false,
false,
false
);
config.SumOfDividends = 100;
var tb = new TradeBar
{
Time = new DateTime(2020, 5, 21, 8, 9, 0),
Period = TimeSpan.FromHours(1),
Symbol = Symbols.SPY,
Open = 100,
High = 200,
Low = 300,
Close = 400
};
var data = SubscriptionData.Create(false,
config,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new TimeZoneOffsetProvider(TimeZones.NewYork, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1)),
tb,
config.DataNormalizationMode,
scale);
Assert.True(data.GetType() == type);
Assert.AreEqual(tb.Open * scale, (data.Data as TradeBar).Open);
Assert.AreEqual(tb.High * scale, (data.Data as TradeBar).High);
Assert.AreEqual(tb.Low * scale, (data.Data as TradeBar).Low);
Assert.AreEqual(tb.Close * scale, (data.Data as TradeBar).Close);
}
[TestCase(typeof(PrecalculatedSubscriptionData), 1)]
[TestCase(typeof(PrecalculatedSubscriptionData), 2)]
[TestCase(typeof(PrecalculatedSubscriptionData), 0.5)]
public void CreateTotalNotZeroDividends(Type type, decimal? scale)
{
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.Utc,
TimeZones.Utc,
false,
false,
false
);
config.SumOfDividends = 100;
config.DataNormalizationMode = DataNormalizationMode.TotalReturn;
var tb = new TradeBar
{
Time = new DateTime(2020, 5, 21, 8, 9, 0),
Period = TimeSpan.FromHours(1),
Symbol = Symbols.SPY,
Open = 100,
High = 200,
Low = 300,
Close = 400
};
var data = SubscriptionData.Create(false,
config,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new TimeZoneOffsetProvider(TimeZones.NewYork, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1)),
tb,
config.DataNormalizationMode,
scale);
Assert.True(data.GetType() == type);
Assert.AreEqual(tb.Open * scale + config.SumOfDividends, (data.Data as TradeBar).Open);
Assert.AreEqual(tb.High * scale + config.SumOfDividends, (data.Data as TradeBar).High);
Assert.AreEqual(tb.Low * scale + config.SumOfDividends, (data.Data as TradeBar).Low);
Assert.AreEqual(tb.Close * scale + config.SumOfDividends, (data.Data as TradeBar).Close);
}
[TestCase(true, typeof(TradeBar))]
[TestCase(false, typeof(TradeBar))]
[TestCase(true, typeof(QuoteBar))]
[TestCase(false, typeof(QuoteBar))]
[TestCase(true, typeof(Tick))]
[TestCase(false, typeof(Tick))]
public void FillForwardFlagIsCorrectlySet(bool isFillForward, Type type)
{
var config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.Utc,
TimeZones.Utc,
false,
false,
false
);
var scale = 0.5m;
config.DataNormalizationMode = DataNormalizationMode.Adjusted;
var data = (BaseData)Activator.CreateInstance(type);
if (isFillForward)
{
data = data.Clone(isFillForward);
}
var subscriptionData = (PrecalculatedSubscriptionData) SubscriptionData.Create(false, config,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new TimeZoneOffsetProvider(TimeZones.NewYork, new DateTime(2015, 1, 1), new DateTime(2016, 1, 1)),
data,
config.DataNormalizationMode,
scale);
config.DataNormalizationMode = DataNormalizationMode.Raw;
Assert.AreEqual(isFillForward, subscriptionData.Data.IsFillForward);
config.DataNormalizationMode = DataNormalizationMode.Adjusted;
Assert.AreEqual(isFillForward, subscriptionData.Data.IsFillForward);
}
internal class MyCustomData : BaseData
{
}
}
}
@@ -0,0 +1,157 @@
/*
* 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.Diagnostics;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Category("TravisExclude"), Parallelizable(ParallelScope.All)]
public class SubscriptionSynchronizerTests
{
[Test]
[TestCase(1, Resolution.Second)]
[TestCase(20, Resolution.Minute)]
[TestCase(50, Resolution.Minute)]
[TestCase(100, Resolution.Minute)]
[TestCase(250, Resolution.Minute)]
[TestCase(500, Resolution.Hour)]
[TestCase(1000, Resolution.Hour)]
public void SubscriptionSynchronizerPerformance(int securityCount, Resolution resolution)
{
// since data is pre-generated, it's important to use the larger resolutions with large security counts
var algorithm = PerformanceBenchmarkAlgorithms.CreateBenchmarkAlgorithm(securityCount, resolution);
TestSubscriptionSynchronizerSpeed(algorithm);
}
private void TestSubscriptionSynchronizerSpeed(QCAlgorithm algorithm)
{
var feed = new MockDataFeed();
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
var symbolPropertiesDataBase = SymbolPropertiesDatabase.FromDataFolder();
var securityService = new SecurityService(
algorithm.Portfolio.CashBook,
marketHoursDatabase,
symbolPropertiesDataBase,
algorithm,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCacheProvider(algorithm.Portfolio),
algorithm: algorithm);
algorithm.Securities.SetSecurityService(securityService);
var dataPermissionManager = new DataPermissionManager();
var dataManager = new DataManager(feed,
new UniverseSelection(algorithm, securityService, dataPermissionManager, TestGlobals.DataProvider),
algorithm,
algorithm.TimeKeeper,
marketHoursDatabase,
false,
RegisteredSecurityDataTypesProvider.Null,
dataPermissionManager);
algorithm.SubscriptionManager.SetDataManager(dataManager);
algorithm.Initialize();
algorithm.PostInitialize();
// set exchanges to be always open
foreach (var kvp in algorithm.Securities)
{
var security = kvp.Value;
security.Exchange = new SecurityExchange(SecurityExchangeHours.AlwaysOpen(security.Exchange.TimeZone));
}
var endTimeUtc = algorithm.EndDate.ConvertToUtc(TimeZones.NewYork);
var startTimeUtc = algorithm.StartDate.ConvertToUtc(TimeZones.NewYork);
var subscriptionBasedTimeProvider = new SubscriptionFrontierTimeProvider(startTimeUtc, dataManager);
var timeSliceFactory = new TimeSliceFactory(algorithm.TimeZone);
var synchronizer = new SubscriptionSynchronizer(dataManager.UniverseSelection, new());
synchronizer.SetTimeProvider(subscriptionBasedTimeProvider);
synchronizer.SetTimeSliceFactory(timeSliceFactory);
var totalDataPoints = 0;
var subscriptions = dataManager.DataFeedSubscriptions;
foreach (var kvp in algorithm.Securities)
{
int dataPointCount;
# pragma warning disable CA2000
var subscription = CreateSubscription(algorithm, kvp.Value, startTimeUtc, endTimeUtc, out dataPointCount);
# pragma warning restore CA2000
subscriptions.TryAdd(subscription);
totalDataPoints += dataPointCount;
}
// log what we're doing
Log.Trace($"Running {subscriptions.Count()} subscriptions with a total of {totalDataPoints} data points. Start: {algorithm.StartDate:yyyy-MM-dd} End: {algorithm.EndDate:yyyy-MM-dd}");
var count = 0;
DateTime currentTime = DateTime.MaxValue;
DateTime previousValue;
var stopwatch = Stopwatch.StartNew();
var enumerator = synchronizer.Sync(subscriptions, CancellationToken.None).GetEnumerator();
do
{
previousValue = currentTime;
enumerator.MoveNext();
var timeSlice = enumerator.Current;
currentTime = timeSlice.Time;
count += timeSlice.DataPointCount;
}
while (currentTime != previousValue);
stopwatch.Stop();
enumerator.DisposeSafely();
var kps = count / 1000d / stopwatch.Elapsed.TotalSeconds;
Log.Trace($"Current Time: {currentTime:u} Elapsed time: {(int)stopwatch.Elapsed.TotalSeconds,4}s KPS: {kps,7:.00} COUNT: {count,10}");
Assert.GreaterOrEqual(count, 100); // this assert is for sanity purpose
dataManager.RemoveAllSubscriptions();
}
private Subscription CreateSubscription(QCAlgorithm algorithm, Security security, DateTime startTimeUtc, DateTime endTimeUtc, out int dataPointCount)
{
var universe = algorithm.UniverseManager.Values.OfType<UserDefinedUniverse>()
.Single(u => u.SelectSymbols(default(DateTime), null).Contains(security.Symbol));
var config = security.Subscriptions.First();
var offsetProvider = new TimeZoneOffsetProvider(TimeZones.NewYork, startTimeUtc, endTimeUtc);
var data = LinqExtensions.Range(algorithm.StartDate, algorithm.EndDate, c => c + config.Increment).Select(time => new DataPoint
{
Time = time,
EndTime = time + config.Increment
})
.Select(d => SubscriptionData.Create(false, config, security.Exchange.Hours, offsetProvider, d, config.DataNormalizationMode))
.ToList();
dataPointCount = data.Count;
var subscriptionRequest = new SubscriptionRequest(false, universe, security, config, startTimeUtc, endTimeUtc);
return new Subscription(subscriptionRequest, data.GetEnumerator(), offsetProvider);
}
private class DataPoint : BaseData
{
// bare bones base data to minimize memory footprint
}
}
}
+195
View File
@@ -0,0 +1,195 @@
/*
* 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 NodaTime;
using NUnit.Framework;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Tests.Common.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class SubscriptionTests
{
private readonly DateTime _start = DateTime.MinValue;
private readonly DateTime _end = DateTime.MinValue.AddDays(1);
[Test]
public void ConstructorNoUniverse()
{
var subscriptionRequest = GetSubscriptionRequest(false);
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
Assert.IsFalse(subscription.Universes.Any());
Assert.IsFalse(subscription.EndOfStream);
Assert.AreEqual(_start, subscription.UtcStartTime);
Assert.AreEqual(_end, subscription.UtcEndTime);
Assert.AreEqual(subscription.Configuration, subscriptionRequest.Configuration);
Assert.IsFalse(subscription.IsUniverseSelectionSubscription);
}
[Test]
public void Constructor()
{
var subscriptionRequest = GetSubscriptionRequest();
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
Assert.AreEqual(1, subscription.Universes.Count());
Assert.AreEqual(_start, subscription.UtcStartTime);
Assert.AreEqual(_end, subscription.UtcEndTime);
Assert.AreEqual(subscriptionRequest.Universe, subscription.Universes.First());
Assert.IsFalse(subscription.EndOfStream);
Assert.AreEqual(subscription.Configuration, subscriptionRequest.Configuration);
Assert.IsFalse(subscription.IsUniverseSelectionSubscription);
}
[Test]
public void AddSubscriptionRequestOncePerUniverse()
{
var subscriptionRequest = GetSubscriptionRequest();
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
Assert.AreEqual(1, subscription.Universes.Count());
Assert.AreEqual(subscriptionRequest.Universe, subscription.Universes.First());
subscription.AddSubscriptionRequest(subscriptionRequest);
Assert.AreEqual(1, subscription.Universes.Count());
Assert.AreEqual(subscriptionRequest.Universe, subscription.Universes.First());
}
[Test]
public void SubscriptionOnlyAcceptsOneUniverseSelection()
{
var subscriptionRequest = GetSubscriptionRequest(true, true);
var subscriptionRequest2 = GetSubscriptionRequest();
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
try
{
subscription.AddSubscriptionRequest(subscriptionRequest2);
Assert.Fail("Subscription should only accept one universe selection" +
" subscription request.");
}
catch (Exception)
{
Assert.Pass();
}
}
[Test]
public void SubscriptionOnlyAcceptsSameConfiguration()
{
var subscriptionRequest = GetSubscriptionRequest();
var subscriptionRequest2 = GetSubscriptionRequest(resolution: Resolution.Second);
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
try
{
subscription.AddSubscriptionRequest(subscriptionRequest2);
Assert.Fail("Subscription should only accept the same configuration");
}
catch (Exception)
{
Assert.Pass();
}
}
[Test]
public void RemoveSubscriptionRequest()
{
var subscriptionRequest = GetSubscriptionRequest();
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
var emptySubscription = subscription.RemoveSubscriptionRequest(subscriptionRequest.Universe);
Assert.IsTrue(emptySubscription);
Assert.IsFalse(subscription.Universes.Any());
}
[Test]
public void RemoveAllSubscriptionRequest()
{
var subscriptionRequest = GetSubscriptionRequest();
var subscriptionRequest2 = GetSubscriptionRequest();
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
subscription.AddSubscriptionRequest(subscriptionRequest2);
Assert.AreEqual(2, subscription.Universes.Count());
var emptySubscription = subscription.RemoveSubscriptionRequest();
Assert.IsTrue(emptySubscription);
Assert.IsFalse(subscription.Universes.Any());
}
[Test]
public void RemoveAllSubscriptionRequestNoUniverse()
{
var subscriptionRequest = GetSubscriptionRequest(false);
using var subscription = new Subscription(
subscriptionRequest,
null,
new TimeZoneOffsetProvider(DateTimeZone.Utc, _start, _end));
var emptySubscription = subscription.RemoveSubscriptionRequest();
Assert.IsTrue(emptySubscription);
Assert.IsFalse(subscription.Universes.Any());
}
private SubscriptionRequest GetSubscriptionRequest(
bool useUniverse = true,
bool isUniverseSelection = false,
Resolution resolution = Resolution.Minute)
{
var security = SecurityTests.GetSecurity();
var config = SecurityTests.CreateTradeBarConfig(resolution);
# pragma warning disable CA2000
var universe = new ManualUniverse(
config,
new UniverseSettings(Resolution.Daily, 1, true, true, TimeSpan.FromDays(1)),
new[] {security.Symbol}
);
#pragma warning restore CA2000
return new SubscriptionRequest(isUniverseSelection, useUniverse ? universe : null, security, config, _start, _end);
}
}
}
@@ -0,0 +1,346 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Moq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Parallelizable(ParallelScope.All)]
public class SubscriptionUtilsTests
{
private Security _security;
private SubscriptionDataConfig _config;
private IFactorFileProvider _factorFileProvider;
[SetUp]
public void SetUp()
{
_security = new Security(Symbols.SPY,
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
new Cash(Currencies.USD, 0, 0),
SymbolProperties.GetDefault(Currencies.USD),
new IdentityCurrencyConverter(Currencies.USD),
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache());
_config = new SubscriptionDataConfig(
typeof(TradeBar),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true, true, false);
_factorFileProvider = TestGlobals.FactorFileProvider;
}
[Test]
public void SubscriptionIsDisposed()
{
var dataPoints = 10;
using var enumerator = new TestDataEnumerator { MoveNextTrueCount = dataPoints };
#pragma warning disable CA2000
var subscription = SubscriptionUtils.CreateAndScheduleWorker(
new SubscriptionRequest(
false,
null,
_security,
_config,
DateTime.UtcNow,
Time.EndOfTime
),
enumerator,
_factorFileProvider,
false, false);
#pragma warning restore CA2000
var count = 0;
while (enumerator.MoveNextTrueCount > 8)
{
if (count++ > 100)
{
Assert.Fail($"Timeout waiting for producer. {enumerator.MoveNextTrueCount}");
}
Thread.Sleep(1);
}
subscription.DisposeSafely();
Assert.IsFalse(subscription.MoveNext());
}
[Test]
public void ThrowingEnumeratorStackDisposesOfSubscription()
{
using var enumerator = new TestDataEnumerator { MoveNextTrueCount = 10, ThrowException = true};
using var subscription = SubscriptionUtils.CreateAndScheduleWorker(
new SubscriptionRequest(
false,
null,
_security,
_config,
DateTime.UtcNow,
Time.EndOfTime
),
enumerator,
_factorFileProvider,
false, false);
var count = 0;
while (enumerator.MoveNextTrueCount != 9)
{
if (count++ > 100)
{
Assert.Fail("Timeout waiting for producer");
}
Thread.Sleep(1);
}
Assert.IsFalse(subscription.MoveNext());
Assert.IsTrue(subscription.EndOfStream);
// enumerator is disposed by the producer
count = 0;
while (!enumerator.Disposed)
{
if (count++ > 100)
{
Assert.Fail("Timeout waiting for producer");
}
Thread.Sleep(1);
}
}
[Test]
// This unit tests reproduces GH 3885 where the consumer hanged forever
public void ConsumerDoesNotHang()
{
for (var i = 0; i < 10000; i++)
{
var dataPoints = 10;
using var enumerator = new TestDataEnumerator {MoveNextTrueCount = dataPoints};
#pragma warning disable CA2000
var subscription = SubscriptionUtils.CreateAndScheduleWorker(
new SubscriptionRequest(
false,
null,
_security,
_config,
DateTime.UtcNow,
Time.EndOfTime
),
enumerator,
_factorFileProvider,
false, false);
#pragma warning restore CA2000
for (var j = 0; j < dataPoints; j++)
{
Assert.IsTrue(subscription.MoveNext());
}
Assert.IsFalse(subscription.MoveNext());
subscription.DisposeSafely();
}
}
[Test]
public void PriceScaleFirstFillForwardBar()
{
var referenceTime = new DateTime(2020, 08, 06);
var point = new Tick(referenceTime, Symbols.SPY, 1, 2);
var point2 = point.Clone(true);
point2.Time = referenceTime;
var point3 = point.Clone(false);
point3.Time = referenceTime.AddDays(1);
;
var enumerator = new List<BaseData> { point2, point3 }.GetEnumerator();
var factorFileProfider = new Mock<IFactorFileProvider>();
var factorFile = new CorporateFactorProvider(_security.Symbol.Value, new[]
{
new CorporateFactorRow(referenceTime, 0.5m, 1),
new CorporateFactorRow(referenceTime.AddDays(1), 1m, 1)
}, referenceTime);
factorFileProfider.Setup(s => s.Get(It.IsAny<Symbol>())).Returns(factorFile);
#pragma warning disable CA2000
var subscription = SubscriptionUtils.CreateAndScheduleWorker(
new SubscriptionRequest(
false,
null,
_security,
_config,
referenceTime,
Time.EndOfTime
),
enumerator,
factorFileProfider.Object,
true, false);
#pragma warning restore CA2000
Assert.IsTrue(subscription.MoveNext());
// we do expect it to pick up the prev factor file scale
Assert.AreEqual(1, (subscription.Current.Data as Tick).AskPrice);
Assert.IsTrue((subscription.Current.Data as Tick).IsFillForward);
Assert.IsTrue(subscription.MoveNext());
Assert.AreEqual(2, (subscription.Current.Data as Tick).AskPrice);
Assert.IsFalse((subscription.Current.Data as Tick).IsFillForward);
subscription.DisposeSafely();
}
[Test]
public void PriceScaleDoesNotUpdateForFillForwardBar()
{
var referenceTime = new DateTime(2020, 08, 06);
var point = new Tick(referenceTime, Symbols.SPY, 1, 2);
var point2 = point.Clone(true);
point2.Time = referenceTime.AddDays(1);
var point3 = point.Clone(false);
point3.Time = referenceTime.AddDays(2);
var enumerator = new List<BaseData> { point, point2, point3 }.GetEnumerator();
var factorFileProfider = new Mock<IFactorFileProvider>();
var factorFile = new CorporateFactorProvider(_security.Symbol.Value, new[]
{
new CorporateFactorRow(referenceTime, 0.5m, 1),
new CorporateFactorRow(referenceTime.AddDays(1), 1m, 1)
}, referenceTime);
factorFileProfider.Setup(s => s.Get(It.IsAny<Symbol>())).Returns(factorFile);
#pragma warning disable CA2000
var subscription = SubscriptionUtils.CreateAndScheduleWorker(
new SubscriptionRequest(
false,
null,
_security,
_config,
referenceTime,
Time.EndOfTime
),
enumerator,
factorFileProfider.Object,
true, false);
#pragma warning restore CA2000
Assert.IsTrue(subscription.MoveNext());
Assert.AreEqual(1, (subscription.Current.Data as Tick).AskPrice);
Assert.IsFalse((subscription.Current.Data as Tick).IsFillForward);
Assert.IsTrue(subscription.MoveNext());
Assert.AreEqual(1, (subscription.Current.Data as Tick).AskPrice);
Assert.IsTrue((subscription.Current.Data as Tick).IsFillForward);
Assert.IsTrue(subscription.MoveNext());
Assert.AreEqual(2, (subscription.Current.Data as Tick).AskPrice);
Assert.IsFalse((subscription.Current.Data as Tick).IsFillForward);
subscription.DisposeSafely();
}
[TestCase(typeof(TradeBar), true)]
[TestCase(typeof(OpenInterest), false)]
[TestCase(typeof(QuoteBar), false)]
public void SubscriptionEmitsAuxData(Type typeOfConfig, bool shouldReceiveAuxData)
{
var config = new SubscriptionDataConfig(typeOfConfig, _security.Symbol, Resolution.Hour, TimeZones.NewYork, TimeZones.NewYork, true, true, false);
var totalPoints = 8;
var time = new DateTime(2010, 1, 1);
var enumerator = Enumerable.Range(0, totalPoints).Select(x => new Delisting { Time = time.AddHours(x) }).GetEnumerator();
#pragma warning disable CA2000
var subscription = SubscriptionUtils.CreateAndScheduleWorker(
new SubscriptionRequest(
false,
null,
_security,
config,
DateTime.UtcNow,
Time.EndOfTime
),
enumerator,
_factorFileProvider,
false, false);
#pragma warning restore CA2000
// Test our subscription stream to see if it emits the aux data it should be filtered
// by the SubscriptionUtils produce function if the config isn't for a TradeBar
int dataReceivedCount = 0;
while (subscription.MoveNext())
{
dataReceivedCount++;
if (subscription.Current != null && subscription.Current.Data.DataType == MarketDataType.Auxiliary)
{
Assert.IsTrue(shouldReceiveAuxData);
}
}
// If it should receive aux data it should have emitted all points
// otherwise none should have been emitted
if (shouldReceiveAuxData)
{
Assert.AreEqual(totalPoints, dataReceivedCount);
}
else
{
Assert.AreEqual(0, dataReceivedCount);
}
}
private class TestDataEnumerator : IEnumerator<BaseData>
{
public bool ThrowException { get; set; }
public bool Disposed { get; set; }
public int MoveNextTrueCount { get; set; }
public void Dispose()
{
Disposed = true;
}
public bool MoveNext()
{
Current = new Tick(DateTime.UtcNow,Symbols.SPY, 1, 2);
var result = --MoveNextTrueCount >= 0;
if (ThrowException)
{
throw new RegressionTestException("TestDataEnumerator.MoveNext()");
}
return result;
}
public void Reset()
{
}
public BaseData Current { get; set; }
object IEnumerator.Current => Current;
}
}
}
@@ -0,0 +1,417 @@
/*
* 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Accord.Math.Comparers;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Logging;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture, Parallelizable(ParallelScope.Fixtures)]
public class TextSubscriptionDataSourceReaderTests
{
private SubscriptionDataConfig _config;
private DateTime _initialDate;
[SetUp]
public void SetUp()
{
_config = new SubscriptionDataConfig(
typeof(TestTradeBarFactory),
Symbols.SPY,
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
_initialDate = new DateTime(2018, 1, 1);
}
[Test]
public void CachedDataIsReturnedAsClone()
{
using var singleEntryDataCacheProvider = new SingleEntryDataCacheProvider(TestGlobals.DataProvider);
var reader = new TextSubscriptionDataSourceReader(
singleEntryDataCacheProvider,
_config,
_initialDate,
false,
null);
var source = (new TradeBar()).GetSource(_config, _initialDate, false);
var dataBars = reader.Read(source).First();
dataBars.Value = 0;
var dataBars2 = reader.Read(source).First();
Assert.AreNotEqual(dataBars.Price, dataBars2.Price);
}
[Test]
public void DataIsNotCachedForEphemeralDataCacheProvider()
{
var config = new SubscriptionDataConfig(
typeof(TestTradeBarFactory),
Symbol.Create("SymbolNonEphemeralTest1", SecurityType.Equity, Market.USA),
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
using var dataCacheProvider = new CustomEphemeralDataCacheProvider { IsDataEphemeral = true};
var reader = new TextSubscriptionDataSourceReader(
dataCacheProvider,
config,
_initialDate,
false,
null);
var source = (new TradeBar()).GetSource(config, _initialDate, false);
dataCacheProvider.Data = "20000101 00:00,1,1,1,1,1";
var dataBars = reader.Read(source).First();
dataCacheProvider.Data = "20000101 00:00,2,2,2,2,2";
var dataBars2 = reader.Read(source).First();
Assert.AreEqual(new DateTime(2000, 1, 1), dataBars.Time);
Assert.AreEqual(new DateTime(2000, 1, 1), dataBars2.Time);
Assert.AreNotEqual(dataBars.Price, dataBars2.Price);
}
[Test]
public void DataIsCachedForNonEphemeralDataCacheProvider()
{
var config = new SubscriptionDataConfig(
typeof(TestTradeBarFactory),
Symbol.Create("SymbolNonEphemeralTest2", SecurityType.Equity, Market.USA),
Resolution.Daily,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
using var dataCacheProvider = new CustomEphemeralDataCacheProvider { IsDataEphemeral = false };
var reader = new TextSubscriptionDataSourceReader(
dataCacheProvider,
config,
_initialDate,
false,
null);
var source = (new TradeBar()).GetSource(config, _initialDate, false);
dataCacheProvider.Data = "20000101 00:00,1,1,1,1,1";
var dataBars = reader.Read(source).First();
// even if the data changes it already cached
dataCacheProvider.Data = "20000101 00:00,2,2,2,2,2";
var dataBars2 = reader.Read(source).First();
Assert.AreEqual(new DateTime(2000, 1, 1), dataBars.Time);
Assert.AreEqual(new DateTime(2000, 1, 1), dataBars2.Time);
Assert.AreEqual(dataBars.Price, dataBars2.Price);
}
[Test]
public void DataIsCachedCorrectly()
{
using var singleEntryDataCacheProvider = new SingleEntryDataCacheProvider(TestGlobals.DataProvider);
var reader = new TextSubscriptionDataSourceReader(
singleEntryDataCacheProvider,
_config,
_initialDate,
false,
null);
var source = (new TradeBar()).GetSource(_config, _initialDate, false);
var dataBars = reader.Read(source).ToList();
var dataBars2 = reader.Read(source).ToList();
Assert.AreEqual(dataBars2.Count, dataBars.Count);
Assert.IsTrue(dataBars.SequenceEqual(dataBars2, new CustomComparer<BaseData>(
(data, baseData) =>
{
if (data.EndTime == baseData.EndTime
&& data.Time == baseData.Time
&& data.Symbol == baseData.Symbol
&& data.Price == baseData.Price
&& data.DataType == baseData.DataType
&& data.Value == baseData.Value)
{
return 0;
}
return 1;
})));
}
[Test]
public void RespectsInitialDate()
{
using var singleEntryDataCacheProvider = new SingleEntryDataCacheProvider(TestGlobals.DataProvider);
var reader = new TextSubscriptionDataSourceReader(
singleEntryDataCacheProvider,
_config,
_initialDate,
false,
null);
var source = (new TradeBar()).GetSource(_config, _initialDate, false);
var dataBars = reader.Read(source).First();
Assert.Less(dataBars.EndTime, _initialDate);
// 80 days after _initialDate
var initialDate2 = _initialDate.AddDays(80);
using var defaultDataProvider2 = new DefaultDataProvider();
using var singleEntryDataCacheProvider2 = new SingleEntryDataCacheProvider(defaultDataProvider2);
var reader2 = new TextSubscriptionDataSourceReader(
singleEntryDataCacheProvider2,
_config,
initialDate2,
false,
null);
var source2 = (new TradeBar()).GetSource(_config, initialDate2, false);
var dataBars2 = reader2.Read(source2).First();
Assert.Less(dataBars2.EndTime, initialDate2);
// 80 days before _initialDate
var initialDate3 = _initialDate.AddDays(-80);
using var defaultDataProvider3 = new DefaultDataProvider();
using var singleEntryDataCacheProvider3 = new SingleEntryDataCacheProvider(defaultDataProvider3);
var reader3 = new TextSubscriptionDataSourceReader(
singleEntryDataCacheProvider3,
_config,
initialDate3,
false,
null);
var source3 = (new TradeBar()).GetSource(_config, initialDate3, false);
var dataBars3 = reader3.Read(source3).First();
Assert.Less(dataBars3.EndTime, initialDate3);
}
[TestCase(Resolution.Daily, true)]
[TestCase(Resolution.Hour, true)]
[TestCase(Resolution.Minute, false)]
[TestCase(Resolution.Second, false)]
[TestCase(Resolution.Tick, false)]
public void CacheBehaviorDifferentResolutions(Resolution resolution, bool shouldBeCached)
{
_config = new SubscriptionDataConfig(
typeof(TestTradeBarFactory),
Symbols.SPY,
resolution,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
using var singleEntryDataCacheProvider = new SingleEntryDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: false);
var reader = new TextSubscriptionDataSourceReader(
singleEntryDataCacheProvider,
_config,
new DateTime(2013, 10, 07),
false,
null);
var source = (new TradeBar()).GetSource(_config, new DateTime(2013, 10, 07), false);
// first call should cache
reader.Read(source).First();
TestTradeBarFactory.ReaderWasCalled = false;
reader.Read(source).First();
Assert.AreEqual(!shouldBeCached, TestTradeBarFactory.ReaderWasCalled);
}
[Test, Explicit("Performance test")]
public void CacheMissPerformance()
{
long counter = 0;
var datas = new List<IEnumerable<BaseData>>();
var factory = new TradeBar();
using var cacheProvider = new CustomEphemeralDataCacheProvider();
// we load SPY hour zip into memory and use it as the source of different fake tickers
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var fakeSource = factory.GetSource(config, new DateTime(2013, 10, 07), false);
cacheProvider.Data = string.Join(Environment.NewLine, QuantConnect.Compression.ReadLines(fakeSource.Source));
for (var i = 0; i < 500; i++)
{
var ticker = $"{i}";
var fakeConfig = new SubscriptionDataConfig(
typeof(TradeBar),
Symbol.Create(ticker, SecurityType.Equity, Market.USA),
Resolution.Hour,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var reader = new TextSubscriptionDataSourceReader(cacheProvider, fakeConfig, Time.EndOfTime, false, null);
var source = factory.GetSource(fakeConfig, Time.BeginningOfTime, false);
datas.Add(reader.Read(source));
}
var timer = new Stopwatch();
timer.Start();
Parallel.ForEach(datas, enumerable =>
{
// after the first call should use the cache
foreach (var data in enumerable)
{
Interlocked.Increment(ref counter);
}
});
timer.Stop();
Log.Trace($"Took {timer.ElapsedMilliseconds}ms. Data count {counter}");
timer.Reset();
timer.Start();
Parallel.ForEach(datas, enumerable =>
{
// after the first call should use the cache
foreach (var data in enumerable)
{
Interlocked.Increment(ref counter);
}
});
timer.Stop();
Log.Trace($"Took2 {timer.ElapsedMilliseconds}ms. Data count {counter}");
}
[Test, Explicit("Performance test")]
public void CacheHappyPerformance()
{
long counter = 0;
var datas = new List<IEnumerable<BaseData>>();
var factory = new TradeBar();
using var cacheProvider = new CustomEphemeralDataCacheProvider();
// we load SPY hour zip into memory and use it as the source of different fake tickers
var config = new SubscriptionDataConfig(typeof(TradeBar),
Symbols.SPY,
Resolution.Hour,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var fakeSource = factory.GetSource(config, new DateTime(2013, 10, 07), false);
cacheProvider.Data = string.Join(Environment.NewLine, QuantConnect.Compression.ReadLines(fakeSource.Source));
for (var i = 0; i < 500; i++)
{
var ticker = $"{i}";
var fakeConfig = new SubscriptionDataConfig(
typeof(TradeBar),
Symbol.Create(ticker, SecurityType.Equity, Market.USA),
Resolution.Hour,
TimeZones.NewYork,
TimeZones.NewYork,
true,
true,
false);
var reader = new TextSubscriptionDataSourceReader(cacheProvider, fakeConfig, Time.EndOfTime, false, null);
var source = factory.GetSource(fakeConfig, Time.BeginningOfTime, false);
datas.Add(reader.Read(source));
}
var timer = new Stopwatch();
timer.Start();
Parallel.ForEach(datas, enumerable =>
{
// after the first call should use the cache
foreach (var data in enumerable)
{
Interlocked.Increment(ref counter);
}
foreach (var data in enumerable)
{
Interlocked.Increment(ref counter);
}
foreach (var data in enumerable)
{
Interlocked.Increment(ref counter);
}
});
timer.Stop();
Log.Trace($"Took {timer.ElapsedMilliseconds}ms. Data count {counter}");
}
private class TestTradeBarFactory : TradeBar
{
/// <summary>
/// Will be true when data is created from a parsed file line
/// </summary>
public static bool ReaderWasCalled { get; set; }
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
ReaderWasCalled = true;
return base.Reader(config, line, date, isLiveMode);
}
public override BaseData Reader(SubscriptionDataConfig config, StreamReader streamReader, DateTime date, bool isLiveMode)
{
ReaderWasCalled = true;
return base.Reader(config, streamReader, date, isLiveMode);
}
}
private class CustomEphemeralDataCacheProvider : IDataCacheProvider
{
public string Data { set; get; }
public bool IsDataEphemeral { set; get; }
public List<string> GetZipEntries(string zipFile)
{
throw new NotImplementedException();
}
public Stream Fetch(string key)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream, leaveOpen: true);
writer.Write(Data);
writer.Flush();
stream.Position = 0;
writer.Dispose();
return stream;
}
public void Store(string key, byte[] data)
{
}
public void Dispose()
{
}
}
}
}
+311
View File
@@ -0,0 +1,311 @@
/*
* 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 NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Custom.IconicTypes;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Securities;
using QuantConnect.Tests.Common.Data.UniverseSelection;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Equity;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class TimeSliceTests
{
private TimeSliceFactory _timeSliceFactory;
[SetUp]
public void SetUp()
{
_timeSliceFactory = new TimeSliceFactory(TimeZones.Utc);
}
[Test]
public void HandlesTicks_ExpectInOrderWithNoDuplicates()
{
var subscriptionDataConfig = new SubscriptionDataConfig(
typeof(Tick),
Symbols.EURUSD,
Resolution.Tick,
TimeZones.Utc,
TimeZones.Utc,
true,
true,
false);
var security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
subscriptionDataConfig,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
DateTime refTime = DateTime.UtcNow;
Tick[] rawTicks = Enumerable
.Range(0, 10)
.Select(i => new Tick(refTime.AddSeconds(i), Symbols.EURUSD, 1.3465m, 1.34652m))
.ToArray();
IEnumerable<TimeSlice> timeSlices = rawTicks.Select(t => _timeSliceFactory.Create(
t.Time,
new List<DataFeedPacket> { new DataFeedPacket(security, subscriptionDataConfig, new List<BaseData>() { t }) },
SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>()),
new Dictionary<Universe, BaseDataCollection>()));
Tick[] timeSliceTicks = timeSlices.SelectMany(ts => ts.Slice.Ticks.Values.SelectMany(x => x)).ToArray();
Assert.AreEqual(rawTicks.Length, timeSliceTicks.Length);
for (int i = 0; i < rawTicks.Length; i++)
{
Assert.IsTrue(Compare(rawTicks[i], timeSliceTicks[i]));
}
}
private bool Compare(Tick expected, Tick actual)
{
return expected.Time == actual.Time
&& expected.BidPrice == actual.BidPrice
&& expected.AskPrice == actual.AskPrice
&& expected.Quantity == actual.Quantity;
}
[Test]
public void HandlesMultipleCustomDataOfSameTypeWithDifferentSymbols()
{
var symbol1 = Symbol.Create("SCF/CBOE_VX1_EW", SecurityType.Base, Market.USA);
var symbol2 = Symbol.Create("SCF/CBOE_VX2_EW", SecurityType.Base, Market.USA);
var subscriptionDataConfig1 = new SubscriptionDataConfig(
typeof(UnlinkedData), symbol1, Resolution.Daily, TimeZones.Utc, TimeZones.Utc, true, true, false, isCustom: true);
var subscriptionDataConfig2 = new SubscriptionDataConfig(
typeof(UnlinkedData), symbol2, Resolution.Daily, TimeZones.Utc, TimeZones.Utc, true, true, false, isCustom: true);
var security1 = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
subscriptionDataConfig1,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
var security2 = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
subscriptionDataConfig1,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
var timeSlice = _timeSliceFactory.Create(DateTime.UtcNow,
new List<DataFeedPacket>
{
new DataFeedPacket(security1, subscriptionDataConfig1, new List<BaseData> {new UnlinkedData { Symbol = symbol1, Time = DateTime.UtcNow.Date, Value = 15 } }),
new DataFeedPacket(security2, subscriptionDataConfig2, new List<BaseData> {new UnlinkedData { Symbol = symbol2, Time = DateTime.UtcNow.Date, Value = 20 } }),
},
SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>()),
new Dictionary<Universe, BaseDataCollection>());
Assert.AreEqual(2, timeSlice.CustomData.Count);
var data1 = timeSlice.CustomData[0].Data[0];
var data2 = timeSlice.CustomData[1].Data[0];
Assert.IsInstanceOf(typeof(UnlinkedData), data1);
Assert.IsInstanceOf(typeof(UnlinkedData), data2);
Assert.AreEqual(symbol1, data1.Symbol);
Assert.AreEqual(symbol2, data2.Symbol);
Assert.AreEqual(15, data1.Value);
Assert.AreEqual(20, data2.Value);
}
[Test]
public void FutureDataHasVolume()
{
var initialVolume = 100;
var slices = GetSlices(Symbols.Fut_SPY_Mar19_2016, initialVolume).ToArray();
for (var i = 0; i < 10; i++)
{
var chain = slices[i].FutureChains.FirstOrDefault().Value;
var contract = chain.FirstOrDefault();
var expected = (i + 1) * initialVolume;
Assert.AreEqual(expected, contract.Volume);
}
}
[Test]
public void OptionsDataHasVolume()
{
var initialVolume = 150;
var slices = GetSlices(Symbols.SPY_C_192_Feb19_2016, initialVolume).ToArray();
for (var i = 0; i < 10; i++)
{
var chain = slices[i].OptionChains.FirstOrDefault().Value;
var contract = chain.FirstOrDefault();
var expected = (i + 1) * initialVolume;
Assert.AreEqual(expected, contract.Volume);
}
}
[Test]
public void SuspiciousTicksAreNotAddedToConsolidatorUpdateData()
{
var symbol = Symbols.SPY;
var subscriptionDataConfig = new SubscriptionDataConfig(
typeof(Tick), symbol, Resolution.Tick, TimeZones.Utc, TimeZones.Utc, true, true, false);
var security = new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
subscriptionDataConfig,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
var timeSlice = _timeSliceFactory.Create(DateTime.UtcNow,
new List<DataFeedPacket>
{
new DataFeedPacket(security, subscriptionDataConfig, new List<BaseData>
{
new Tick(DateTime.UtcNow, symbol, 280, 0, 0),
new Tick(DateTime.UtcNow, symbol, 500, 0, 0) { Suspicious = true },
new Tick(DateTime.UtcNow, symbol, 281, 0, 0)
})
},
SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>()),
new Dictionary<Universe, BaseDataCollection>());
Assert.AreEqual(1, timeSlice.ConsolidatorUpdateData.Count);
var data = timeSlice.ConsolidatorUpdateData[0].Data;
Assert.AreEqual(2, data.Count);
Assert.AreEqual(280, data[0].Value);
Assert.AreEqual(281, data[1].Value);
}
private IEnumerable<Slice> GetSlices(Symbol symbol, int initialVolume)
{
var dataType = symbol.SecurityType.IsOption() ? typeof(OptionUniverse) : typeof(FutureUniverse);
var subscriptionDataConfig = new SubscriptionDataConfig(dataType, symbol, Resolution.Second, TimeZones.Utc, TimeZones.Utc, true, true, false);
var security = GetSecurity(subscriptionDataConfig);
var refTime = DateTime.UtcNow;
return Enumerable
.Range(0, 10)
.Select(i =>
{
var time = refTime.AddSeconds(i);
var bid = new Bar(100, 100, 100, 100);
var ask = new Bar(110, 110, 110, 110);
var volume = (i + 1) * initialVolume;
var packets = new List<DataFeedPacket>();
var packet = new DataFeedPacket(security, subscriptionDataConfig, new List<BaseData>
{
new QuoteBar(time, symbol, bid, i*10, ask, (i + 1) * 11),
new TradeBar(time, symbol, 100, 100, 110, 106, volume)
});
if (symbol.SecurityType == SecurityType.Option)
{
var underlying = (security as Option).Underlying;
packets.Add(new DataFeedPacket(underlying, underlying.SubscriptionDataConfig, new List<BaseData>
{
new QuoteBar(time, underlying.Symbol, bid, i*10, ask, (i + 1) * 11),
new TradeBar(time, underlying.Symbol, 100, 100, 110, 106, volume)
}));
}
packets.Add(packet);
return _timeSliceFactory.Create(
time,
packets,
SecurityChangesTests.CreateNonInternal(Enumerable.Empty<Security>(), Enumerable.Empty<Security>()),
new Dictionary<Universe, BaseDataCollection>())
.Slice;
});
}
private Security GetSecurity(SubscriptionDataConfig config)
{
if (config.Symbol.SecurityType == SecurityType.Option)
{
var option = new Option(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
config,
new Cash(Currencies.USD, 0, 1m),
new OptionSymbolProperties(SymbolProperties.GetDefault(Currencies.USD)),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null);
var underlyingConfig = new SubscriptionDataConfig(typeof(TradeBar), config.Symbol.Underlying, Resolution.Second,
TimeZones.Utc, TimeZones.Utc, true, true, false);
var equity = new Equity(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
underlyingConfig,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null);
option.Underlying = equity;
return option;
}
if (config.Symbol.SecurityType == SecurityType.Future)
{
return new Future(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null);
}
return new Security(
SecurityExchangeHours.AlwaysOpen(TimeZones.Utc),
config,
new Cash(Currencies.USD, 0, 1m),
SymbolProperties.GetDefault(Currencies.USD),
ErrorCurrencyConverter.Instance,
RegisteredSecurityDataTypesProvider.Null,
new SecurityCache()
);
}
}
}
@@ -0,0 +1,259 @@
/*
* 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.Globalization;
using System.IO;
using System.Net;
using System.Text;
using NUnit.Framework;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.DataFeeds.Transport;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds.Transport
{
[TestFixture]
public class RemoteFileSubscriptionStreamReaderTests
{
private TestDownloadProvider _api;
[SetUp]
public void SetUp()
{
_api = new TestDownloadProvider();
_api.Initialize(Globals.UserId, Globals.UserToken, Globals.DataFolder);
RemoteFileSubscriptionStreamReader.SetDownloadProvider(_api);
TestDownloadProvider.DownloadCount = 0;
// create cache directory if not existing
if (!Directory.Exists(Globals.Cache))
{
Directory.CreateDirectory(Globals.Cache);
}
else
{
// clean old files out of the cache
Directory.Delete(Globals.Cache, true);
Directory.CreateDirectory(Globals.Cache);
}
}
[TearDown]
public void TearDown()
{
_api.DisposeSafely();
}
[TestCase(true)]
[TestCase(false)]
public void SingleEntryDataCacheProviderEphemeralDataIsRespected(bool isDataEphemeral)
{
using var cacheProvider = new SingleEntryDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: isDataEphemeral);
using var remoteReader = new RemoteFileSubscriptionStreamReader(
cacheProvider,
@"https://www.quantconnect.com/api/v2/proxy/quandl/api/v3/datasets/BCHARTS/BITSTAMPUSD.csv?order=asc&api_key=WyAazVXnq7ATy_fefTqm",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream);
Assert.AreEqual(1, TestDownloadProvider.DownloadCount);
using var cacheProvider2 = new SingleEntryDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: isDataEphemeral);
using var remoteReader2 = new RemoteFileSubscriptionStreamReader(
cacheProvider2,
@"https://www.quantconnect.com/api/v2/proxy/quandl/api/v3/datasets/BCHARTS/BITSTAMPUSD.csv?order=asc&api_key=WyAazVXnq7ATy_fefTqm",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream);
Assert.AreEqual(isDataEphemeral ? 2 : 1, TestDownloadProvider.DownloadCount);
}
[TestCase(true)]
[TestCase(false)]
public void ZipDataCacheProviderEphemeralDataIsRespected(bool isDataEphemeral)
{
using var cacheProvider = new ZipDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: isDataEphemeral);
using var remoteReader = new RemoteFileSubscriptionStreamReader(
cacheProvider,
@"https://www.quantconnect.com/api/v2/proxy/quandl/api/v3/datasets/BCHARTS/BITSTAMPUSD.csv?order=asc&api_key=WyAazVXnq7ATy_fefTqm",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream);
Assert.AreEqual(1, TestDownloadProvider.DownloadCount);
using var cacheProvider2 = new ZipDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: isDataEphemeral);
using var remoteReader2 = new RemoteFileSubscriptionStreamReader(
cacheProvider2,
@"https://www.quantconnect.com/api/v2/proxy/quandl/api/v3/datasets/BCHARTS/BITSTAMPUSD.csv?order=asc&api_key=WyAazVXnq7ATy_fefTqm",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream);
Assert.AreEqual(isDataEphemeral ? 2 : 1, TestDownloadProvider.DownloadCount);
}
[TestCase(true)]
[TestCase(false)]
public void ZipDataCacheProviderEphemeralDataIsRespectedForZippedData(bool isDataEphemeral)
{
using var cacheProvider = new ZipDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: isDataEphemeral);
using var remoteReader = new RemoteFileSubscriptionStreamReader(
cacheProvider,
@"https://cdn.quantconnect.com/uploads/multi_csv_zipped_file.zip",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream);
Assert.AreEqual(1, TestDownloadProvider.DownloadCount);
using var remoteReader2 = new RemoteFileSubscriptionStreamReader(
cacheProvider,
@"https://cdn.quantconnect.com/uploads/multi_csv_zipped_file.zip",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream);
Assert.AreEqual(isDataEphemeral ? 2 : 1, TestDownloadProvider.DownloadCount);
}
[TestCase(true, "", 78)] // No fragment, will read the first entry
[TestCase(false, "", 78)]
[TestCase(true, "#csv_file_10.csv", 1)]
[TestCase(false, "#csv_file_10.csv", 1)]
public void GetsZippedDataForUrlNotEndingWithZipExtension(bool withQuery, string entryName, int expectedLines)
{
using var cacheProvider = new ZipDataCacheProvider(TestGlobals.DataProvider);
var url = @"https://cdn.quantconnect.com/uploads/multi_csv_zipped_file.zip" + (withQuery ? "?some=query" : "") + entryName;
using var remoteReader = new RemoteFileSubscriptionStreamReader(cacheProvider, url, Globals.Cache, null);
Assert.IsFalse(remoteReader.EndOfStream);
Assert.AreEqual(1, TestDownloadProvider.DownloadCount);
var count = 0;
while (!remoteReader.EndOfStream)
{
var line = remoteReader.ReadLine();
count++;
var csv = line.ToCsv();
Assert.AreEqual(2, csv.Count);
Assert.IsTrue(int.TryParse(csv[0], NumberStyles.Number, CultureInfo.InvariantCulture, out _));
Assert.IsTrue(decimal.TryParse(csv[1], NumberStyles.Number, CultureInfo.InvariantCulture, out _));
}
Assert.AreEqual(expectedLines, count);
}
[Test]
public void InvalidDataSource()
{
using var cacheProvider = new ZipDataCacheProvider(TestGlobals.DataProvider);
using var remoteReader = new RemoteFileSubscriptionStreamReader(
cacheProvider,
@"http://quantconnect.com",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream);
// Fails to get quantconnect.com, missing http://
Assert.Throws<WebException>(() => new RemoteFileSubscriptionStreamReader(
new SingleEntryDataCacheProvider(new DefaultDataProvider()),
@"quantconnect.com",
Globals.Cache,
null),
"Api.Download(): Failed to download data from quantconnect.com. Please verify the source for missing http:// or https://"
);
}
[Test]
public void RetriesTransientEmptyDownload()
{
// the remote endpoint answers empty twice before returning the data; the reader should retry and recover
var provider = new TransientEmptyDownloadProvider(emptyResponses: 2, payload: "20131007 00:00,1,2,0,1,100");
RemoteFileSubscriptionStreamReader.SetDownloadProvider(provider);
using var cacheProvider = new SingleEntryDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: true);
using var remoteReader = new RemoteFileSubscriptionStreamReader(
cacheProvider,
"https://cdn.quantconnect.com/uploads/transient-empty.csv",
Globals.Cache,
null);
Assert.IsFalse(remoteReader.EndOfStream, "reader should have data after retrying past the empty responses");
Assert.AreEqual("20131007 00:00,1,2,0,1,100", remoteReader.ReadLine());
Assert.AreEqual(3, provider.DownloadCount);
}
[Test]
public void EmptyDownloadIsNotCached()
{
var provider = new TransientEmptyDownloadProvider(emptyResponses: int.MaxValue, payload: string.Empty);
RemoteFileSubscriptionStreamReader.SetDownloadProvider(provider);
var url = "https://cdn.quantconnect.com/uploads/always-empty.csv";
using var cacheProvider = new ZipDataCacheProvider(TestGlobals.DataProvider, isDataEphemeral: false);
using var remoteReader = new RemoteFileSubscriptionStreamReader(cacheProvider, url, Globals.Cache, null);
Assert.IsTrue(remoteReader.EndOfStream, "an empty download should yield no data");
// the empty response must not have been persisted to the cache
Assert.IsFalse(File.Exists(Path.Combine(Globals.Cache, url.ToMD5() + ".csv")),
"an empty download must not be cached");
}
private class TransientEmptyDownloadProvider : IDownloadProvider
{
private readonly int _emptyResponses;
private readonly byte[] _payload;
public int DownloadCount { get; private set; }
public TransientEmptyDownloadProvider(int emptyResponses, string payload)
{
_emptyResponses = emptyResponses;
_payload = Encoding.UTF8.GetBytes(payload);
}
public byte[] DownloadBytes(string address, IEnumerable<KeyValuePair<string, string>> headers, string userName, string password)
{
DownloadCount++;
return DownloadCount <= _emptyResponses ? Array.Empty<byte>() : _payload;
}
public string Download(string address, IEnumerable<KeyValuePair<string, string>> headers, string userName, string password)
{
return Encoding.UTF8.GetString(DownloadBytes(address, headers, userName, password));
}
}
private class TestDownloadProvider : QuantConnect.Api.Api
{
public static int DownloadCount { get; set; }
static TestDownloadProvider()
{
DownloadCount = 0;
}
public override byte[] DownloadBytes(string address, IEnumerable<KeyValuePair<string, string>> headers, string userName, string password)
{
DownloadCount++;
return base.DownloadBytes(address, headers, userName, password);
}
}
}
}
@@ -0,0 +1,260 @@
/*
* 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 Moq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Orders;
using QuantConnect.Packets;
using QuantConnect.Securities;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class UniverseSelectionTests
{
[Test]
public void CreatedEquityIsNotAddedToSymbolCache()
{
SymbolCache.Clear();
var algorithm = new AlgorithmStub(new MockDataFeed());
algorithm.SetEndDate(new DateTime(2024, 12, 13));
algorithm.SetStartDate(algorithm.EndDate.Subtract(TimeSpan.FromDays(10)));
algorithm.AddUniverse(CoarseSelectionFunction, FineSelectionFunction);
// OnEndOfTimeStep will add all pending universe additions
algorithm.OnEndOfTimeStep();
var universe = algorithm.UniverseManager.Values.First();
var securityChanges = algorithm.DataManager.UniverseSelection.ApplyUniverseSelection(
universe,
algorithm.EndDate.ConvertToUtc(algorithm.TimeZone).Subtract(TimeSpan.FromDays(1)),
new BaseDataCollection(
DateTime.UtcNow,
Symbols.AAPL,
new[]
{
new CoarseFundamental
{
Symbol = Symbols.AAPL,
Time = DateTime.UtcNow
},
new CoarseFundamental
{
Symbol = Symbols.SPY,
Time = DateTime.UtcNow
}
}
)
);
Symbol symbol;
Assert.AreEqual(1, securityChanges.AddedSecurities.Count);
Assert.AreEqual(Symbols.AAPL, securityChanges.AddedSecurities.First().Symbol);
Assert.IsFalse(SymbolCache.TryGetSymbol("AAPL", out symbol));
Assert.IsFalse(SymbolCache.TryGetSymbol("SPY", out symbol));
}
[Test]
public void RemovalFromUniverseAndDataFeedMakesSecurityNotTradable()
{
SymbolCache.Clear();
var algorithm = new AlgorithmStub(new MockDataFeedWithSubscription());
var orderProcessorMock = new Mock<IOrderProcessor>();
orderProcessorMock.Setup(m => m.GetOpenOrders(It.IsAny<Func<Order, bool>>())).Returns(new List<Order>());
algorithm.Transactions.SetOrderProcessor(orderProcessorMock.Object);
algorithm.SetStartDate(2012, 3, 27);
algorithm.SetEndDate(2012, 3, 30);
algorithm.AddUniverse("my-custom-universe", dt => dt.Day < 30 ? new List<string> { "CPRT" } : Enumerable.Empty<string>());
// OnEndOfTimeStep will add all pending universe additions
algorithm.OnEndOfTimeStep();
var universe = algorithm.UniverseManager.Values.First();
var securityChanges = algorithm.DataManager.UniverseSelection.ApplyUniverseSelection(
universe,
algorithm.EndDate.ConvertToUtc(algorithm.TimeZone).Subtract(TimeSpan.FromDays(2)),
new BaseDataCollection(
algorithm.UtcTime,
Symbol.Create("CPRT", SecurityType.Equity, Market.USA),
new List<BaseData>()
)
);
Assert.AreEqual(1, securityChanges.AddedSecurities.Count);
Assert.AreEqual(0, securityChanges.RemovedSecurities.Count);
var security = securityChanges.AddedSecurities.First();
Assert.IsTrue(security.IsTradable);
securityChanges = algorithm.DataManager.UniverseSelection.ApplyUniverseSelection(
universe,
algorithm.EndDate.ConvertToUtc(algorithm.TimeZone),
new BaseDataCollection(
algorithm.UtcTime,
Symbol.Create("CPRT", SecurityType.Equity, Market.USA),
new List<BaseData>()
)
);
Assert.AreEqual(0, securityChanges.AddedSecurities.Count);
Assert.AreEqual(1, securityChanges.RemovedSecurities.Count);
Assert.AreEqual(security.Symbol, securityChanges.RemovedSecurities.First().Symbol);
Assert.IsFalse(security.IsTradable);
}
[Test]
public void CoarseFundamentalHasFundamentalDataFalseExcludedInFineUniverseSelection()
{
var algorithm = new AlgorithmStub(new MockDataFeed());
algorithm.SetEndDate(new DateTime(2024, 12, 13));
algorithm.SetStartDate(algorithm.EndDate.Subtract(TimeSpan.FromDays(10)));
algorithm.AddUniverse(
coarse => coarse.Select(c => c.Symbol),
fine => fine.Select(f => f.Symbol).Where(x => x.ID.Symbol == "AAPL")
);
// OnEndOfTimeStep will add all pending universe additions
algorithm.OnEndOfTimeStep();
var universe = algorithm.UniverseManager.Values.First();
var securityChanges = algorithm.DataManager.UniverseSelection.ApplyUniverseSelection(
universe,
algorithm.EndDate.ConvertToUtc(algorithm.TimeZone).Subtract(TimeSpan.FromDays(1)),
new BaseDataCollection(
DateTime.UtcNow,
Symbols.AAPL,
new[]
{
new CoarseFundamental
{
Symbol = Symbols.AAPL,
Time = DateTime.UtcNow
},
new CoarseFundamental
{
Symbol = Symbols.SPY,
Time = DateTime.UtcNow
}
}
)
);
Assert.AreEqual(1, securityChanges.Count);
Assert.AreEqual(Symbols.AAPL, securityChanges.AddedSecurities.First().Symbol);
}
[Test]
public void DoesNotAddSelectedSecuritiesIfNoTradableDates()
{
var algorithm = new AlgorithmStub(new MockDataFeed());
algorithm.SetStartDate(2023, 12, 01);
algorithm.SetEndDate(2023, 12, 30); // Sunday
algorithm.AddUniverse(
coarse => coarse.Select(c => c.Symbol),
fine => fine.Select(f => f.Symbol));
algorithm.OnEndOfTimeStep();
var universe = algorithm.UniverseManager.Values.First();
var getUniverseData = (DateTime dt) => new BaseDataCollection(
dt,
Symbols.AAPL,
[
new CoarseFundamental
{
Symbol = Symbols.AAPL,
Time = dt
},
new CoarseFundamental
{
Symbol = Symbols.SPY,
Time = dt
}
]
);
// Friday, one tradeale day left before end date
var dateTime = new DateTime(2023, 12, 29).ConvertToUtc(algorithm.TimeZone);
var universeData = getUniverseData(dateTime);
var securityChanges = algorithm.DataManager.UniverseSelection.ApplyUniverseSelection(
universe,
dateTime,
universeData);
Assert.AreEqual(2, securityChanges.AddedSecurities.Count);
CollectionAssert.AreEquivalent(universeData.Select(x => x.Symbol), securityChanges.AddedSecurities.Select(x => x.Symbol));
// Saturday, no tradable days left before end date
dateTime += TimeSpan.FromDays(1);
universeData = getUniverseData(dateTime);
securityChanges = algorithm.DataManager.UniverseSelection.ApplyUniverseSelection(
universe,
dateTime,
universeData);
Assert.AreEqual(0, securityChanges.AddedSecurities.Count);
}
private IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
{
return new List<Symbol> {Symbols.AAPL, Symbols.SPY};
}
private IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
{
return new[] { fine.First(fundamental => fundamental.Symbol.Value == "AAPL").Symbol };
}
public class MockDataFeedWithSubscription : IDataFeed
{
public bool IsActive { get; }
public void Initialize(
IAlgorithm algorithm,
AlgorithmNodePacket job,
IResultHandler resultHandler,
IMapFileProvider mapFileProvider,
IFactorFileProvider factorFileProvider,
IDataProvider dataProvider,
IDataFeedSubscriptionManager subscriptionManager,
IDataFeedTimeProvider dataFeedTimeProvider,
IDataChannelProvider dataChannelProvider
)
{
}
public Subscription CreateSubscription(SubscriptionRequest request)
{
return new Subscription(request, Enumerable.Empty<SubscriptionData>().GetEnumerator(), null);
}
public void RemoveSubscription(Subscription subscription)
{
}
public void Exit()
{
}
}
}
}
@@ -0,0 +1,68 @@
/*
* 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.IO;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Util;
namespace QuantConnect.Tests.Engine.DataFeeds
{
[TestFixture]
public class ZipEntryNameSubscriptionFactoryTests
{
[Test]
public void ReadsZipEntryNames()
{
var time = new DateTime(2016, 03, 03, 12, 48, 15);
var source = Path.Combine("TestData", "20151224_quote_american.zip");
var config = new SubscriptionDataConfig(typeof (CustomData), Symbol.Create("XLRE", SecurityType.Option, Market.USA), Resolution.Tick,
TimeZones.NewYork, TimeZones.NewYork, false, false, false);
using var cacheProvider = new ZipDataCacheProvider(TestGlobals.DataProvider);
var factory = new ZipEntryNameSubscriptionDataSourceReader(cacheProvider, config, time, false);
var expected = new[]
{
Symbol.CreateOption("XLRE", Market.USA, OptionStyle.American, OptionRight.Call, 21m, new DateTime(2016, 08, 19)),
Symbol.CreateOption("XLRE", Market.USA, OptionStyle.American, OptionRight.Call, 22m, new DateTime(2016, 08, 19)),
Symbol.CreateOption("XLRE", Market.USA, OptionStyle.American, OptionRight.Put, 37m, new DateTime(2016, 08, 19)),
};
var actual = factory.Read(new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile, FileFormat.ZipEntryName)).ToList();
// we only really care about the symbols
CollectionAssert.AreEqual(expected, actual.Select(x => x.Symbol));
Assert.IsTrue(actual.All(x => x is CustomData));
}
private class CustomData : BaseData
{
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var symbol = LeanData.ReadSymbolFromZipEntry(config.Symbol, config.Resolution, line);
return new CustomData { Time = date, Symbol = symbol };
}
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var source = LeanData.GenerateZipFilePath(Globals.DataFolder, config.Symbol, date, config.Resolution, config.TickType);
return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile, FileFormat.ZipEntryName);
}
}
}
}