diff --git a/Algorithm.Framework/Selection/OpenInterestFutureUniverseSelectionModel.cs b/Algorithm.Framework/Selection/OpenInterestFutureUniverseSelectionModel.cs index 952c83f31e04..c1bc3671b880 100644 --- a/Algorithm.Framework/Selection/OpenInterestFutureUniverseSelectionModel.cs +++ b/Algorithm.Framework/Selection/OpenInterestFutureUniverseSelectionModel.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; using System.Linq; -using NodaTime; using Python.Runtime; using QuantConnect.Data; using QuantConnect.Data.Market; @@ -31,6 +30,8 @@ namespace QuantConnect.Algorithm.Framework.Selection /// public class OpenInterestFutureUniverseSelectionModel : FutureUniverseSelectionModel { + private static readonly TimeSpan OpenInterestHistoryLookback = TimeSpan.FromDays(7); + private readonly int? _chainContractsLookupLimit; private readonly IAlgorithm _algorithm; private readonly int? _resultsLimit; @@ -116,11 +117,10 @@ private Dictionary GetOpenInterest(MarketHoursDatabase.Entry ma { var current = _algorithm.UtcTime; var exchangeHours = marketHours.ExchangeHours; - var endTime = Instant.FromDateTimeUtc(_algorithm.UtcTime).InZone(exchangeHours.TimeZone).ToDateTimeUnspecified(); - var previousDay = Time.GetStartTimeForTradeBars(exchangeHours, endTime, Time.OneDay, 1, true, marketHours.DataTimeZone); + var startTime = current - OpenInterestHistoryLookback; var requests = symbols.Select( symbol => new HistoryRequest( - previousDay, + startTime, current, typeof(Tick), symbol, @@ -139,7 +139,7 @@ private Dictionary GetOpenInterest(MarketHoursDatabase.Entry ma .Where(s => s.HasData && s.Ticks.Keys.Count > 0) .SelectMany(s => s.Ticks.Select(x => new Tuple(x.Key, x.Value.LastOrDefault()))) .GroupBy(x => x.Item1) - .ToDictionary(x => x.Key, x => x.OrderByDescending(i => i.Item2.Time).LastOrDefault().Item2.Value); + .ToDictionary(x => x.Key, x => x.OrderByDescending(i => i.Item2.Time).FirstOrDefault().Item2.Value); } /// diff --git a/Tests/Algorithm/Framework/Selection/OpenInterestFutureUniverseSelectionModelTests.cs b/Tests/Algorithm/Framework/Selection/OpenInterestFutureUniverseSelectionModelTests.cs index 0560621cb2d8..b318e0e794d5 100644 --- a/Tests/Algorithm/Framework/Selection/OpenInterestFutureUniverseSelectionModelTests.cs +++ b/Tests/Algorithm/Framework/Selection/OpenInterestFutureUniverseSelectionModelTests.cs @@ -35,7 +35,7 @@ public class OpenInterestFutureUniverseSelectionModelTests private static readonly Symbol March = Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 03, 01)); private static readonly Symbol April = Symbol.CreateFuture(Futures.Metals.Gold, Market.COMEX, new DateTime(2020, 04, 01)); private static readonly DateTime TestDate = new DateTime(2020, 05, 11, 0, 0, 0, DateTimeKind.Utc); - private static readonly DateTime ExpectedPreviousDate = new DateTime(2020, 05, 09, 20, 0, 0, DateTimeKind.Utc); + private static readonly DateTime ExpectedPreviousDate = TestDate.AddDays(-7); private static readonly IReadOnlyDictionary OpenInterestData = new Dictionary { [Jan] = 3, @@ -140,19 +140,68 @@ public void Limits_Do_Not_Need_To_Be_Provided() Assert.AreEqual(items.Keys, results); } - private static Slice CreateReplySlice(Symbol symbol, decimal openInterest) + [Test] + public void Selects_Most_Recent_Open_Interest() + { + SetupSubject(OpenInterestData.Count, OpenInterestData.Count); + _mockHistoryProvider.Setup(x => x.GetHistory(It.IsAny>(), It.IsAny())) + .Returns, DateTimeZone>((rq, tz) => + rq.SelectMany(r => new[] + { + CreateReplySlice(r.Symbol, 1, TestDate.AddDays(-5)), + CreateReplySlice(r.Symbol, OpenInterestData[r.Symbol], TestDate.AddDays(-1)) + }).ToArray()); + + var data = OpenInterestData.Keys.ToDictionary(x => x, x => MarketHours); + var results = _underTest.FilterByOpenInterest(data).ToList(); + + Assert.AreEqual(4, results.Count); + Assert.AreEqual(Feb, results[0]); + Assert.AreEqual(Jan, results[1]); + Assert.AreEqual(March, results[2]); + Assert.AreEqual(April, results[3]); + } + + [Test] + public void Requests_Open_Interest_Across_Non_Trading_Days() + { + var mondayMidnight = new DateTime(2023, 02, 27, 0, 0, 0, DateTimeKind.Utc); + SetupSubject(OpenInterestData.Count, OpenInterestData.Count, mondayMidnight); + + _mockHistoryProvider.Setup(x => x.GetHistory(It.IsAny>(), It.IsAny())) + .Returns, DateTimeZone>((requests, tz) => + { + foreach (var request in requests) + { + Assert.AreEqual(mondayMidnight.AddDays(-7), request.StartTimeUtc); + Assert.AreEqual(mondayMidnight, request.EndTimeUtc); + } + + return requests.Select(r => CreateReplySlice(r.Symbol, OpenInterestData[r.Symbol], mondayMidnight.AddDays(-3))).ToArray(); + }) + .Verifiable(); + + var data = OpenInterestData.Keys.ToDictionary(x => x, x => MarketHours); + var results = _underTest.FilterByOpenInterest(data).ToList(); + + _mockHistoryProvider.Verify(); + Assert.AreEqual(4, results.Count); + } + + private static Slice CreateReplySlice(Symbol symbol, decimal openInterest, DateTime? time = null) { - var ticks = new Ticks {{symbol, new List {new OpenInterest(TestDate, symbol, openInterest)}}}; - return new Slice(TestDate, null, null, null, ticks, null, null, null, null, null, null, null, TestDate, true); + var sliceTime = time ?? TestDate; + var ticks = new Ticks {{symbol, new List {new OpenInterest(sliceTime, symbol, openInterest)}}}; + return new Slice(sliceTime, null, null, null, ticks, null, null, null, null, null, null, null, sliceTime, true); } - private void SetupSubject(int? testChainContractLookupLimit, int? testResultsLimit) + private void SetupSubject(int? testChainContractLookupLimit, int? testResultsLimit, DateTime? utcTime = null) { _mockHistoryProvider = new Mock(); var mockAlgorithm = new Mock(); mockAlgorithm.SetupGet(x => x.HistoryProvider).Returns(_mockHistoryProvider.Object); - mockAlgorithm.SetupGet(x => x.UtcTime).Returns(TestDate); + mockAlgorithm.SetupGet(x => x.UtcTime).Returns(utcTime ?? TestDate); _underTest = new OpenInterestFutureUniverseSelectionModel(mockAlgorithm.Object, _ => OpenInterestData.Keys, testChainContractLookupLimit, testResultsLimit); } }