From 7e5f6558a2543fe0af0bbc4dcb77f9e55e441e60 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 8 Jan 2024 17:51:50 -0400 Subject: [PATCH] Add support for index historical data --- .../PolygonDataDownloaderTests.cs | 24 +++++++++++ .../PolygonDataQueueHandlerIndicesTests.cs | 2 +- .../PolygonHistoryTests.cs | 43 +++++++++++++++++++ QuantConnect.Polygon/PolygonSymbolMapper.cs | 7 +-- .../PolygonWebSocketClientWrapper.cs | 4 +- 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/QuantConnect.Polygon.Tests/PolygonDataDownloaderTests.cs b/QuantConnect.Polygon.Tests/PolygonDataDownloaderTests.cs index ceb7c79..420d581 100644 --- a/QuantConnect.Polygon.Tests/PolygonDataDownloaderTests.cs +++ b/QuantConnect.Polygon.Tests/PolygonDataDownloaderTests.cs @@ -57,6 +57,30 @@ public void DownloadsHistoricalData(Symbol symbol, Resolution resolution, TimeSp PolygonHistoryTests.AssertHistoricalDataResults(data, resolution); } + private static TestCaseData[] IndexHistoricalDataTestCases => PolygonHistoryTests.IndexHistoricalDataTestCases; + + [TestCaseSource(nameof(IndexHistoricalDataTestCases))] + [Explicit("This tests require a Polygon.io api key, requires internet and are long.")] + public void DownloadsIndexHistoricalData(Resolution resolution, TimeSpan period, TickType tickType, bool shouldBeEmpy) + { + var symbol = Symbol.Create("SPX", SecurityType.Index, Market.USA); + var request = PolygonHistoryTests.CreateHistoryRequest(symbol, resolution, tickType, period); + + var parameters = new DataDownloaderGetParameters(symbol, resolution, request.StartTimeUtc, request.EndTimeUtc, tickType); + var data = _downloader.Get(parameters).ToList(); + + Log.Trace("Data points retrieved: " + data.Count); + + if (shouldBeEmpy) + { + Assert.That(data, Is.Empty); + } + else + { + PolygonHistoryTests.AssertHistoricalDataResults(data, resolution); + } + } + [Test] [Explicit("This tests require a Polygon.io api key, requires internet and are long.")] public void DownloadsDataFromCanonicalOptionSymbol() diff --git a/QuantConnect.Polygon.Tests/PolygonDataQueueHandlerIndicesTests.cs b/QuantConnect.Polygon.Tests/PolygonDataQueueHandlerIndicesTests.cs index 4501d44..cb9e65a 100644 --- a/QuantConnect.Polygon.Tests/PolygonDataQueueHandlerIndicesTests.cs +++ b/QuantConnect.Polygon.Tests/PolygonDataQueueHandlerIndicesTests.cs @@ -38,7 +38,7 @@ public class PolygonDataQueueHandlerIndicesTests : PolygonDataQueueHandlerBaseTe [TestCase(Resolution.Hour, 3)] [Explicit("Tests are dependent on network and take long. " + "Also, this test will only pass if the subscribed securities are liquid enough to get data in the test run time.")] - public void StreamsDataForDifferentResolutions(Resolution resolution, int period) + public override void StreamsDataForDifferentResolutions(Resolution resolution, int period) { base.StreamsDataForDifferentResolutions(resolution, period); } diff --git a/QuantConnect.Polygon.Tests/PolygonHistoryTests.cs b/QuantConnect.Polygon.Tests/PolygonHistoryTests.cs index 8af2a2f..2928c78 100644 --- a/QuantConnect.Polygon.Tests/PolygonHistoryTests.cs +++ b/QuantConnect.Polygon.Tests/PolygonHistoryTests.cs @@ -120,6 +120,49 @@ internal static void AssertHistoricalDataResults(List history, Resolut } } + internal static TestCaseData[] IndexHistoricalDataTestCases + { + get + { + return new[] + { + // Trades + new TestCaseData(Resolution.Tick, TimeSpan.FromMinutes(5), TickType.Trade, true), // Tick data is not available for indexes + new TestCaseData(Resolution.Second, TimeSpan.FromMinutes(30), TickType.Trade, false), + new TestCaseData(Resolution.Minute, TimeSpan.FromDays(15), TickType.Trade, false), + new TestCaseData(Resolution.Hour, TimeSpan.FromDays(180), TickType.Trade, false), + new TestCaseData(Resolution.Daily, TimeSpan.FromDays(3650), TickType.Trade, false), + + // Quotes: quote data is not available for indexes + new TestCaseData(Resolution.Tick, TimeSpan.FromMinutes(5), TickType.Quote, true), + new TestCaseData(Resolution.Second, TimeSpan.FromMinutes(5), TickType.Quote, true), + new TestCaseData(Resolution.Minute, TimeSpan.FromMinutes(5), TickType.Quote, true), + new TestCaseData(Resolution.Hour, TimeSpan.FromMinutes(5), TickType.Quote, true), + new TestCaseData(Resolution.Daily, TimeSpan.FromMinutes(5), TickType.Quote, true), + }; + } + } + + [TestCaseSource(nameof(IndexHistoricalDataTestCases))] + [Explicit("This tests require a Polygon.io api key, requires internet and are long.")] + public void GetsIndexHistoricalData(Resolution resolution, TimeSpan period, TickType tickType, bool shouldBeEmpty) + { + var symbol = Symbol.Create("SPX", SecurityType.Index, Market.USA); + var requests = new List { CreateHistoryRequest(symbol, resolution, tickType, period) }; + var history = _historyProvider.GetHistory(requests, TimeZones.Utc).ToList(); + + Log.Trace("Data points retrieved: " + history.Count); + + if (shouldBeEmpty) + { + Assert.That(history, Is.Empty); + } + else + { + AssertHistoricalDataResults(history.Select(x => x.AllData).SelectMany(x => x).ToList(), resolution, _historyProvider.DataPointCount); + } + } + [Test] [Explicit("This tests require a Polygon.io api key, requires internet and are long.")] public void GetsSameBarCountForDifferentResponseLimits() diff --git a/QuantConnect.Polygon/PolygonSymbolMapper.cs b/QuantConnect.Polygon/PolygonSymbolMapper.cs index bb96e5e..95a6a9a 100644 --- a/QuantConnect.Polygon/PolygonSymbolMapper.cs +++ b/QuantConnect.Polygon/PolygonSymbolMapper.cs @@ -51,7 +51,6 @@ public string GetBrokerageSymbol(Symbol symbol) brokerageSymbol = ticker; break; - case SecurityType.Index: brokerageSymbol = $"I:{ticker}"; break; @@ -208,9 +207,8 @@ private Symbol GetLeanOptionSymbol(string polygonSymbol) var underlying = IndexOptionSymbol.IsIndexOption(ticker) ? Symbol.Create(IndexOptionSymbol.MapToUnderlying(ticker), SecurityType.Index, Market.USA) : Symbol.Create(ticker, SecurityType.Equity, Market.USA); - var symbol = Symbol.CreateOption(underlying, ticker, Market.USA, OptionStyle.American, optionRight, strike, expirationDate); - return symbol; + return Symbol.CreateOption(underlying, ticker, Market.USA, OptionStyle.American, optionRight, strike, expirationDate); } /// @@ -218,8 +216,7 @@ private Symbol GetLeanOptionSymbol(string polygonSymbol) /// private Symbol GetLeanIndexSymbol(string polygonSymbol) { - var symbol = Symbol.Create(polygonSymbol.Substring(2), SecurityType.Index, Market.USA); - return symbol; + return Symbol.Create(polygonSymbol.Substring(2), SecurityType.Index, Market.USA); } } } diff --git a/QuantConnect.Polygon/PolygonWebSocketClientWrapper.cs b/QuantConnect.Polygon/PolygonWebSocketClientWrapper.cs index bcea003..ec78627 100644 --- a/QuantConnect.Polygon/PolygonWebSocketClientWrapper.cs +++ b/QuantConnect.Polygon/PolygonWebSocketClientWrapper.cs @@ -126,6 +126,8 @@ public void Subscribe(SubscriptionDataConfig config, out bool usingAggregates) /// private void TrySubscribe(string ticker, SubscriptionDataConfig config, out bool usingAggregates) { + usingAggregates = false; + // We'll try subscribing assuming the highest subscription plan and work our way down if we get an error using var subscribedEvent = new ManualResetEventSlim(false); using var errorEvent = new ManualResetEventSlim(false); @@ -193,8 +195,6 @@ void ProcessMessage(object? _, WebSocketMessage wsMessage) throw new Exception($"PolygonWebSocketClientWrapper.Subscribe(): Failed to subscribe to {ticker}. " + $"Make sure your subscription plan allows streaming {config.TickType.ToString().ToLowerInvariant()} data."); } - - usingAggregates = false; } ///