Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5736bb4
feat: update symbol-ma
Romazes Jul 28, 2025
c887308
refactor: load Future symbol
Romazes Jul 28, 2025
7228147
test:feat: DataQueueUniverseProvider
Romazes Jul 28, 2025
6bf423a
test:feat: SymbolMapper
Romazes Jul 28, 2025
1ddb333
fix: FuturesExchanges collection
Romazes Jul 29, 2025
1aef286
feat: implement canonical downloading
Romazes Jul 29, 2025
220efbf
test:feat: download Canonical Future symbols
Romazes Jul 29, 2025
ce97b85
refactor: LoadSymbol Future part
Romazes Jul 30, 2025
9488e63
refactor: normalizeFutureSymbol method
Romazes Jul 30, 2025
dad1b87
test:refactor: download future canonical with multiple symbols
Romazes Jul 30, 2025
804909a
test:feat; test LookupSymbols with Equity
Romazes Jul 30, 2025
9b80b97
test:feat: unsupported Canonical Option History request
Romazes Jul 30, 2025
fca4b41
rollback: lazy object in Downloader class
Romazes Jul 30, 2025
0b12af3
remove: extra log trace in Downloader
Romazes Jul 30, 2025
d505376
refactor: naming/description in Downloader
Romazes Jul 30, 2025
1d6bc40
test:fix: use canonical future instead expiry ones
Romazes Jul 30, 2025
e14b217
rename: variable to more fit in Data Downloader
Romazes Jul 31, 2025
4505cdf
refactor: handle history request
Romazes Jul 31, 2025
a2d6d96
remove: convert time from file
Romazes Jul 31, 2025
b5e0557
refactor: handle history data responses
Romazes Aug 1, 2025
237dd4d
refactor: handle received history data
Romazes Aug 1, 2025
6d3c4bd
refactor: use ConvertTo(IQFeedDataProvider.TimeZoneIQFeed, exchangeTi…
Romazes Aug 1, 2025
70d42ff
test:feat: Data Queue Handler
Romazes Aug 1, 2025
883aa87
fix: use convertFromUtc instead of ConvertTo in DQH
Romazes Aug 1, 2025
6393747
fix: history request
Romazes Aug 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions QuantConnect.IQFeed.Tests/IQFeedDataDownloaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Configuration;

namespace QuantConnect.Lean.DataSource.IQFeed.Tests
{
Expand All @@ -26,9 +28,16 @@ public class IQFeedDataDownloaderTest
{
private IQFeedDataDownloader _downloader;

[SetUp]
public void SetUp()
[OneTimeSetUp]
public void OneTimeSetUp()
{
if (OS.IsWindows)
{
// IQConnect is only supported on Windows
var connector = new IQConnect(Config.Get("iqfeed-productName"), "1.0");
Assert.IsTrue(connector.Launch(), "Failed to launch IQConnect on Windows. Ensure IQFeed is installed and configured properly.");
}

_downloader = new IQFeedDataDownloader();
}

Expand All @@ -50,5 +59,35 @@ public void DownloadsHistoricalData(Symbol symbol, Resolution resolution, TickTy

IQFeedHistoryProviderTests.AssertHistoricalDataResponse(resolution, downloadResponse);
}

private static IEnumerable<TestCaseData> CanonicalFutureSymbolTestCases
{
get
{
var startDateUtc = new DateTime(2025, 03, 03);
var endDateUtc = new DateTime(2025, 04, 04);

var naturalGas = Symbol.Create(Futures.Energy.NaturalGas, SecurityType.Future, Market.NYMEX);
yield return new TestCaseData(naturalGas, Resolution.Daily, TickType.Trade, startDateUtc, endDateUtc);

var nasdaq100EMini = Symbol.Create(Futures.Indices.NASDAQ100EMini, SecurityType.Future, Market.CME);
yield return new TestCaseData(nasdaq100EMini, Resolution.Daily, TickType.Trade, startDateUtc, endDateUtc);
}
}


[TestCaseSource(nameof(CanonicalFutureSymbolTestCases))]
public void DownloadCanonicalFutureHistoricalData(Symbol symbol, Resolution resolution, TickType tickType, DateTime startDateUtc, DateTime endDateUtc)
{
var parameters = new DataDownloaderGetParameters(symbol, resolution, startDateUtc, endDateUtc, tickType);
var downloadResponse = _downloader.Get(parameters)?.ToList();

Assert.IsNotNull(downloadResponse);
Assert.IsNotEmpty(downloadResponse);

var uniqueFutureSymbols = downloadResponse.Select(x => x.Symbol).Distinct().ToList();

Assert.That(uniqueFutureSymbols.Count, Is.GreaterThan(1), $"Expected more than 1 unique future symbol, but got {uniqueFutureSymbols.Count}: {string.Join(", ", uniqueFutureSymbols)}");
}
}
}
25 changes: 17 additions & 8 deletions QuantConnect.IQFeed.Tests/IQFeedDataProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using QuantConnect.Tests;
using QuantConnect.Logging;
using System.Threading.Tasks;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Lean.Engine.DataFeeds.Enumerators;
Expand Down Expand Up @@ -52,6 +53,11 @@ private static IEnumerable<TestCaseData> SubscribeTestCaseData
yield return new TestCaseData(Symbols.AAPL);
yield return new TestCaseData(Symbol.Create("SMCI", SecurityType.Equity, Market.USA));
yield return new TestCaseData(Symbol.Create("IRBT", SecurityType.Equity, Market.USA));
var nasdaq100EMini = Symbol.CreateFuture(Futures.Indices.NASDAQ100EMini, Market.CME, new DateTime(2025, 09, 19));
yield return new TestCaseData(nasdaq100EMini);
var naturalGasAug2025 = Symbol.CreateFuture(Futures.Energy.NaturalGas, Market.NYMEX, new DateTime(2025, 08, 27));
yield return new TestCaseData(naturalGasAug2025);

// yield return new TestCaseData(Symbols.SPY);

// Not supported.
Expand Down Expand Up @@ -127,19 +133,22 @@ private void SubscribeOnData(Symbol symbol, Resolution resolution, int minimumRe

Action<BaseData> callback = (dataPoint) =>
{
if (dataPoint == null)
if (dataPoint is null)
{
return;
}

switch (dataPoint)
if (dataPoint is Tick tick)
{
case TradeBar _:
secondDataReceived[typeof(TradeBar)] += 1;
break;
case QuoteBar _:
secondDataReceived[typeof(QuoteBar)] += 1;
break;
switch (tick.TickType)
{
case TickType.Trade:
secondDataReceived[typeof(TradeBar)] += 1;
break;
case TickType.Quote:
secondDataReceived[typeof(QuoteBar)] += 1;
break;
}
}

if (secondDataReceived.All(type => type.Value >= minimumReturnDataAmount))
Expand Down
57 changes: 57 additions & 0 deletions QuantConnect.IQFeed.Tests/IQFeedDataQueueUniverseProviderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.Tests;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
using System.Collections.Generic;

namespace QuantConnect.Lean.DataSource.IQFeed.Tests;

[TestFixture]
public class IQFeedDataQueueUniverseProviderTests
{
private IDataQueueUniverseProvider _iQFeedDataProvider;

[OneTimeSetUp]
public void OneTimeSetUp()
{
_iQFeedDataProvider = new IQFeedDataProvider();
}

private static IEnumerable<TestCaseData> LookUpSymbolsTestParameters
{
get
{
yield return new TestCaseData(Symbol.Create(Futures.Energy.NaturalGas, SecurityType.Future, Market.NYMEX));
yield return new TestCaseData(Symbol.Create(Futures.Indices.NASDAQ100EMini, SecurityType.Future, Market.CME));
yield return new TestCaseData(Symbol.CreateCanonicalOption(Symbols.AAPL));
yield return new TestCaseData(Symbol.CreateCanonicalOption(Symbols.SPY));
}
}

[Test, TestCaseSource(nameof(LookUpSymbolsTestParameters))]
public void LookUpSymbols(Symbol symbol)
{
var symbols = _iQFeedDataProvider.LookupSymbols(symbol, true, default).ToList();
Assert.IsNotEmpty(symbols);
Assert.Greater(symbols.Count, 1);
Assert.AreEqual(symbols.Count, symbols.Distinct().Count());
}
}
39 changes: 27 additions & 12 deletions QuantConnect.IQFeed.Tests/IQFeedHistoryProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
using QuantConnect.Util;
using QuantConnect.Tests;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.Market;
using System.Collections.Generic;

namespace QuantConnect.Lean.DataSource.IQFeed.Tests
{
Expand All @@ -30,8 +30,8 @@ public class IQFeedHistoryProviderTests
{
private IQFeedDataProvider _historyProvider;

[SetUp]
public void SetUp()
[OneTimeSetUp]
public void OneTimeSetUp()
{
_historyProvider = new IQFeedDataProvider();
_historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null, null, null, null, null, null, false, null, null, null));
Expand Down Expand Up @@ -61,13 +61,23 @@ internal static IEnumerable<TestCaseData> HistoricalTestParameters
yield return new TestCaseData(AAPL, Resolution.Hour, TickType.Quote, TimeSpan.FromDays(180), true);
yield return new TestCaseData(AAPL, Resolution.Daily, TickType.Quote, TimeSpan.FromDays(365), true);

// TickType.OpenInterest is not maintained
yield return new TestCaseData(AAPL, Resolution.Tick, TickType.OpenInterest, TimeSpan.FromMinutes(5), true);
// Future
var nasdaq100EMini = Symbol.CreateFuture(Futures.Indices.NASDAQ100EMini, Market.CME, new DateTime(2025, 09, 19));
yield return new TestCaseData(nasdaq100EMini, Resolution.Daily, TickType.Trade, TimeSpan.FromDays(180), false);
yield return new TestCaseData(nasdaq100EMini, Resolution.Hour, TickType.Trade, TimeSpan.FromDays(180), false);
yield return new TestCaseData(nasdaq100EMini, Resolution.Minute, TickType.Trade, TimeSpan.FromDays(10), false);
yield return new TestCaseData(nasdaq100EMini, Resolution.Second, TickType.Trade, TimeSpan.FromMinutes(10), false);
yield return new TestCaseData(nasdaq100EMini, Resolution.Tick, TickType.Trade, TimeSpan.FromMinutes(5), false);

yield return new TestCaseData(AAPL, Resolution.Tick, TickType.OpenInterest, TimeSpan.FromMinutes(5), true).SetDescription("TickType.OpenInterest is not maintained");

// Not supported Security Types
yield return new TestCaseData(Symbol.Create("SPX.XO", SecurityType.Index, Market.CBOE), Resolution.Tick, TickType.Trade, TimeSpan.FromMinutes(5), true);
yield return new TestCaseData(Symbol.CreateFuture("@ESGH24", Market.CME, new DateTime(2024, 3, 21)), Resolution.Tick, TickType.Trade, TimeSpan.FromMinutes(5), true);

var naturalGasAug2025 = Symbol.CreateFuture(Futures.Energy.NaturalGas, Market.NYMEX, new DateTime(2025, 08, 27));
yield return new TestCaseData(naturalGasAug2025, Resolution.Daily, TickType.Trade, TimeSpan.FromDays(100), false);

yield return new TestCaseData(Symbol.CreateCanonicalOption(Symbols.AAPL), default, default, default, true).SetDescription("Canonical symbol requests are unsupported and return null.");
}
}

Expand Down Expand Up @@ -99,7 +109,7 @@ internal static void AssertTicksHaveAppropriateTickType(Resolution resolution, T
case (Resolution.Tick, TickType.Trade):
Assert.IsTrue(historyResponse.Any(x => x.Ticks.Any(xx => xx.Value.Count > 0 && xx.Value.Any(t => t.TickType == TickType.Trade))));
break;
};
}
}

internal static void AssertHistoricalDataResponse(Resolution resolution, List<BaseData> historyResponse)
Expand All @@ -126,17 +136,22 @@ internal static void AssertHistoricalDataResponse(Resolution resolution, List<Ba

internal static HistoryRequest CreateHistoryRequest(Symbol symbol, Resolution resolution, TickType tickType, TimeSpan period)
{
var end = new DateTime(2024, 01, 22, 12, 0, 0);
var end = DateTime.UtcNow.Date;

if (resolution == Resolution.Daily)
{
end = end.Date.AddDays(1);
}
var dataType = LeanData.GetDataType(resolution, tickType);

return new HistoryRequest(end.Subtract(period),
end,
dataType,
return CreateHistoryRequest(symbol, resolution, tickType, end.Subtract(period), end);
}

internal static HistoryRequest CreateHistoryRequest(Symbol symbol, Resolution resolution, TickType tickType, DateTime startDateTimeUtc, DateTime endDateTimeUtc)
{
return new HistoryRequest(
startDateTimeUtc,
endDateTimeUtc,
LeanData.GetDataType(resolution, tickType),
symbol,
resolution,
SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork),
Expand Down
63 changes: 62 additions & 1 deletion QuantConnect.IQFeed.Tests/IQFeedSymbolRepresentationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,30 @@

using System;
using NUnit.Framework;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Configuration;

namespace QuantConnect.Lean.DataSource.IQFeed.Tests
{
[TestFixture]
[TestFixture, Explicit("Requires locally installed and configured IQFeed client, including login credentials and active data subscription.")]
public class IQFeedSymbolRepresentationTests
{
private IQFeedDataQueueUniverseProvider _dataQueueUniverseProvider;

[OneTimeSetUp]
public void OneTimeSetUp()
{
if (OS.IsWindows)
{
// IQConnect is only supported on Windows
var connector = new IQConnect(Config.Get("iqfeed-productName"), "1.0");
Assert.IsTrue(connector.Launch(), "Failed to launch IQConnect on Windows. Ensure IQFeed is installed and configured properly.");
}

_dataQueueUniverseProvider = new IQFeedDataQueueUniverseProvider();
}

[Test]
public void ParseOptionIQFeedTicker()
{
Expand Down Expand Up @@ -50,5 +68,48 @@ public void IQFeedTickerRoundTrip(string encodedOption, string underlying, Optio

Assert.AreEqual(encodedOption, result);
}

public static IEnumerable<TestCaseData> GetFutureSymbolsTestCases()
{
// Natural gas futures expire the month previous to the contract month:
// Expiry: August -> Contract month: September (U)
yield return new TestCaseData("QNGU25", Symbol.CreateFuture(Futures.Energy.NaturalGas, Market.NYMEX, new DateTime(2025, 08, 27)));
// Expiry: December 2025 -> Contract month: January (U) 2026 (26)
yield return new TestCaseData("QNGF26", Symbol.CreateFuture(Futures.Energy.NaturalGas, Market.NYMEX, new DateTime(2025, 12, 29)));

// BrentLastDayFinancial futures expire two months previous to the contract month:
// Expiry: August -> Contract month: October (V)
yield return new TestCaseData("QBZV25", Symbol.CreateFuture(Futures.Energy.BrentLastDayFinancial, Market.NYMEX, new DateTime(2025, 08, 29)));
// Expiry: November 2025 -> Contract month: January (F) 2026 (26)
yield return new TestCaseData("QBZF26", Symbol.CreateFuture(Futures.Energy.BrentLastDayFinancial, Market.NYMEX, new DateTime(2025, 11, 28)));
// Expiry: December 2025 -> Contract month: February (G) 2026 (26)
yield return new TestCaseData("QBZG26", Symbol.CreateFuture(Futures.Energy.BrentLastDayFinancial, Market.NYMEX, new DateTime(2025, 12, 31)));

yield return new TestCaseData("@NQU25", Symbol.CreateFuture(Futures.Indices.NASDAQ100EMini, Market.CME, new DateTime(2025, 09, 19)));

yield return new TestCaseData("@ADU25", Symbol.CreateFuture(Futures.Currencies.AUD, Market.CME, new DateTime(2025, 09, 15)))
.SetDescription("IQFeed returns 'AD' for Australian Dollar; Lean expects '6A'. Verifies symbol mapping from 'IQFeed-symbol-map.json'.");

yield return new TestCaseData("@BOU25", Symbol.CreateFuture(Futures.Grains.SoybeanOil, Market.CBOT, new DateTime(2025, 09, 12)))
.SetDescription("Brokerage uses 'BO' for Soybean Oil; Lean maps it as 'ZL'. Validates symbol translation via 'IQFeed-symbol-map.json'.");
}

[TestCaseSource(nameof(GetFutureSymbolsTestCases))]
public void ConvertsFutureSymbolRoundTrip(string brokerageSymbol, Symbol leanSymbol)
{
var actualBrokerageSymbol = _dataQueueUniverseProvider.GetBrokerageSymbol(leanSymbol);
var actualLeanSymbol = _dataQueueUniverseProvider.GetLeanSymbol(brokerageSymbol, default, default);

Assert.AreEqual(brokerageSymbol, actualBrokerageSymbol);
Assert.AreEqual(leanSymbol, actualLeanSymbol);
}

[TestCase("NYMEX_GBX", "QQA", "QA")]
[TestCase("NYMEX_GBX", "QQAN25", "QAN25")]
public void NormalizeFuturesTickerRemovesFirstQForNymexGbxExchange(string exchange, string brokerageSymbol, string expectedNormalizedFutureSymbol)
{
var actualNormalizedFutureSymbol = IQFeedDataQueueUniverseProvider.NormalizeFuturesTicker(exchange, brokerageSymbol);
Assert.AreEqual(expectedNormalizedFutureSymbol, actualNormalizedFutureSymbol);
}
}
}
Loading
Loading