diff --git a/DemonstrationUniverse.cs b/DemonstrationUniverse.cs
index d8b962c..15c89ed 100644
--- a/DemonstrationUniverse.cs
+++ b/DemonstrationUniverse.cs
@@ -14,9 +14,7 @@
*
*/
-using System;
using System.Linq;
-using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.DataSource;
diff --git a/MyCustomDataDownloader.cs b/MyCustomDataDownloader.cs
new file mode 100644
index 0000000..c545329
--- /dev/null
+++ b/MyCustomDataDownloader.cs
@@ -0,0 +1,59 @@
+/*
+ * 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;
+using System.Collections.Generic;
+using QuantConnect.Util;
+
+namespace QuantConnect.Lean.DataSource.MyCustom
+{
+ ///
+ /// Data downloader class for pulling data from Data Provider
+ ///
+ public class MyCustomDataDownloader : IDataDownloader, IDisposable
+ {
+ ///
+ private readonly MyCustomDataProvider _myCustomDataProvider;
+
+ ///
+ /// Initializes a new instance of the
+ ///
+ public MyCustomDataDownloader()
+ {
+ _myCustomDataProvider = new MyCustomDataProvider();
+ }
+
+ ///
+ /// Get historical data enumerable for a single symbol, type and resolution given this start and end time (in UTC).
+ ///
+ /// Parameters for the historical data request
+ /// Enumerable of base data for this symbol
+ ///
+ public IEnumerable Get(DataDownloaderGetParameters dataDownloaderGetParameters)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ _myCustomDataProvider?.DisposeSafely();
+ }
+ }
+}
diff --git a/MyCustomDataProvider.cs b/MyCustomDataProvider.cs
new file mode 100644
index 0000000..2ff770d
--- /dev/null
+++ b/MyCustomDataProvider.cs
@@ -0,0 +1,161 @@
+/*
+ * 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 QuantConnect.Data;
+using QuantConnect.Util;
+using QuantConnect.Interfaces;
+using System.Collections.Generic;
+using QuantConnect.Lean.Engine.DataFeeds;
+using QuantConnect.Lean.Engine.HistoricalData;
+
+namespace QuantConnect.Lean.DataSource.MyCustom
+{
+ ///
+ /// Implementation of Custom Data Provider
+ ///
+ public class MyCustomDataProvider : SynchronizingHistoryProvider, IDataQueueHandler
+ {
+ ///
+ ///
+ ///
+ private readonly IDataAggregator _dataAggregator;
+
+ ///
+ ///
+ ///
+ private readonly EventBasedDataQueueHandlerSubscriptionManager _subscriptionManager;
+
+ ///
+ /// Returns true if we're currently connected to the Data Provider
+ ///
+ public bool IsConnected { get; }
+
+ ///
+ public override void Initialize(HistoryProviderInitializeParameters parameters)
+ { }
+
+ ///
+ public override IEnumerable GetHistory(IEnumerable requests, DateTimeZone sliceTimeZone)
+ {
+ // Create subscription objects from the configs
+ var subscriptions = new List();
+ foreach (var request in requests)
+ {
+ // Retrieve the history for the current request
+ var history = GetHistory(request);
+
+ if (history == null)
+ {
+ // If history is null, it indicates that the request contains wrong parameters
+ // Handle the case where the request parameters are incorrect
+ continue;
+ }
+
+ var subscription = CreateSubscription(request, history);
+ subscriptions.Add(subscription);
+ }
+
+ // Validate that at least one subscription is valid; otherwise, return null
+ if (subscriptions.Count == 0)
+ {
+ return null;
+ }
+
+ return CreateSliceEnumerableFromSubscriptions(subscriptions, sliceTimeZone);
+ }
+
+ ///
+ /// Subscribe to the specified configuration
+ ///
+ /// defines the parameters to subscribe to a data feed
+ /// handler to be fired on new data available
+ /// The new enumerator for this subscription request
+ public IEnumerator Subscribe(SubscriptionDataConfig dataConfig, EventHandler newDataAvailableHandler)
+ {
+ if (!CanSubscribe(dataConfig.Symbol))
+ {
+ return null;
+ }
+
+ var enumerator = _dataAggregator.Add(dataConfig, newDataAvailableHandler);
+ _subscriptionManager.Subscribe(dataConfig);
+
+ return enumerator;
+ }
+
+ ///
+ /// Removes the specified configuration
+ ///
+ /// Subscription config to be removed
+ public void Unsubscribe(SubscriptionDataConfig dataConfig)
+ {
+ _subscriptionManager.Unsubscribe(dataConfig);
+ _dataAggregator.Remove(dataConfig);
+ }
+
+ ///
+ /// Sets the job we're subscribing for
+ ///
+ /// Job we're subscribing for
+ ///
+ public void SetJob(Packets.LiveNodePacket job)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Dispose of unmanaged resources.
+ ///
+ public void Dispose()
+ {
+ _dataAggregator?.DisposeSafely();
+ _subscriptionManager?.DisposeSafely();
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Gets the history for the requested security
+ ///
+ /// The historical data request
+ /// An enumerable of BaseData points
+ private IEnumerable GetHistory(HistoryRequest request)
+ {
+ if (!CanSubscribe(request.Symbol))
+ {
+ return null;
+ }
+
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Checks if this Data provider supports the specified symbol
+ ///
+ /// The symbol
+ /// returns true if Data Provider supports the specified symbol; otherwise false
+ private bool CanSubscribe(Symbol symbol)
+ {
+ if (symbol.Value.IndexOfInvariant("universe", true) != -1 || symbol.IsCanonical())
+ {
+ return false;
+ }
+
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/QuantConnect.DataSource.csproj b/QuantConnect.DataSource.csproj
index f379576..d181134 100644
--- a/QuantConnect.DataSource.csproj
+++ b/QuantConnect.DataSource.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
QuantConnect.DataSource
@@ -9,6 +9,7 @@
+
diff --git a/tests/MyCustomDataDownloaderTests.cs b/tests/MyCustomDataDownloaderTests.cs
new file mode 100644
index 0000000..d2dde6c
--- /dev/null
+++ b/tests/MyCustomDataDownloaderTests.cs
@@ -0,0 +1,41 @@
+/*
+ * 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.Collections.Generic;
+using QuantConnect.Lean.DataSource.MyCustom;
+
+namespace QuantConnect.DataLibrary.Tests
+{
+ [TestFixture]
+ public class MyCustomDataDownloaderTests
+ {
+ private static IEnumerable DownloadTestParameters => MyCustomDataProviderHistoryTests.TestParameters;
+
+ [TestCaseSource(nameof(DownloadTestParameters))]
+ public void DownloadHistory(Symbol symbol, Resolution resolution, TickType tickType, TimeSpan period, bool isThrowNotImplementedException)
+ {
+ var myCustomDownloader = new MyCustomDataDownloader();
+
+ var request = MyCustomDataProviderHistoryTests.GetHistoryRequest(resolution, tickType, symbol, period);
+
+ var parameters = new DataDownloaderGetParameters(symbol, resolution, request.StartTimeUtc, request.EndTimeUtc, tickType);
+
+ Assert.Throws(() => myCustomDownloader.Get(parameters));
+ }
+ }
+}
diff --git a/tests/MyCustomDataProviderHistoryTests.cs b/tests/MyCustomDataProviderHistoryTests.cs
new file mode 100644
index 0000000..7ce68ae
--- /dev/null
+++ b/tests/MyCustomDataProviderHistoryTests.cs
@@ -0,0 +1,109 @@
+/*
+ * 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.Tests;
+using QuantConnect.Lean.DataSource.MyCustom;
+using QuantConnect.Securities;
+using System.Collections.Generic;
+using QuantConnect.Tests.Common.Exceptions;
+
+namespace QuantConnect.DataLibrary.Tests
+{
+ [TestFixture]
+ public class MyCustomDataProviderHistoryTests
+ {
+ ///
+ private readonly MyCustomDataProvider _historyDataProvider = new();
+
+ internal static IEnumerable TestParameters
+ {
+ get
+ {
+ TestGlobals.Initialize();
+ var equity = Symbol.Create("SPY", SecurityType.Equity, Market.USA);
+ var option = Symbol.Create("SPY", SecurityType.Option, Market.USA);
+
+ yield return new TestCaseData(equity, Resolution.Daily, TickType.Trade, TimeSpan.FromDays(15), true)
+ .SetDescription("Valid parameters - Daily resolution, 15 days period.")
+ .SetCategory("Valid");
+
+ yield return new TestCaseData(equity, Resolution.Hour, TickType.Quote, TimeSpan.FromDays(2), true)
+ .SetDescription("Valid parameters - Hour resolution, 2 days period.")
+ .SetCategory("Valid");
+
+ yield return new TestCaseData(option, Resolution.Second, TickType.Trade, TimeSpan.FromMinutes(60), false)
+ .SetDescription("Invalid Symbol - Canonical doesn't support")
+ .SetCategory("Invalid");
+
+ ///
+ yield return new TestCaseData(Symbol.Create("AAA.1", SecurityType.Equity, Market.USA), Resolution.Hour, TickType.Trade, TimeSpan.FromDays(2), true)
+ .SetDescription("Delisted Symbol - the DataSource supports the history of delisted ones or not")
+ .SetCategory("Valid/Invalid");
+
+ ///
+ yield return new TestCaseData(Symbol.Create("SPWR", SecurityType.Equity, Market.USA), Resolution.Hour, TickType.Trade, TimeSpan.FromDays(2), true)
+ .SetDescription("Mapping Symbol")
+ .SetCategory("Valid");
+ }
+ }
+
+ [Test, TestCaseSource(nameof(TestParameters))]
+ public void GetsHistory(Symbol symbol, Resolution resolution, TickType tickType, TimeSpan period, bool isThrowNotImplementedException)
+ {
+ var request = GetHistoryRequest(resolution, tickType, symbol, period);
+
+ try
+ {
+ IEnumerable slices = _historyDataProvider.GetHistory(new[] { request }, TimeZones.Utc)?.ToList();
+ Assert.IsNull(slices);
+ }
+ catch (NotImplementedException)
+ {
+ Assert.IsTrue(isThrowNotImplementedException);
+ }
+ }
+
+ internal static HistoryRequest GetHistoryRequest(Resolution resolution, TickType tickType, Symbol symbol, TimeSpan period)
+ {
+ var utcNow = DateTime.UtcNow;
+ var dataType = LeanData.GetDataType(resolution, tickType);
+ var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
+
+ var exchangeHours = marketHoursDatabase.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
+ var dataTimeZone = marketHoursDatabase.GetDataTimeZone(symbol.ID.Market, symbol, symbol.SecurityType);
+
+ return new HistoryRequest(
+ startTimeUtc: utcNow.Add(-period),
+ endTimeUtc: utcNow,
+ dataType: dataType,
+ symbol: symbol,
+ resolution: resolution,
+ exchangeHours: exchangeHours,
+ dataTimeZone: dataTimeZone,
+ fillForwardResolution: resolution,
+ includeExtendedMarketHours: true,
+ isCustomData: false,
+ DataNormalizationMode.Raw,
+ tickType: tickType
+ );
+ }
+ }
+}
diff --git a/tests/MyCustomDataQueueHandlerTests.cs b/tests/MyCustomDataQueueHandlerTests.cs
new file mode 100644
index 0000000..2e0afbd
--- /dev/null
+++ b/tests/MyCustomDataQueueHandlerTests.cs
@@ -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.Linq;
+using NUnit.Framework;
+using System.Threading;
+using QuantConnect.Data;
+using QuantConnect.Tests;
+using QuantConnect.Logging;
+using System.Threading.Tasks;
+using QuantConnect.Data.Market;
+using System.Collections.Generic;
+using QuantConnect.Lean.DataSource.MyCustom;
+
+namespace QuantConnect.DataLibrary.Tests
+{
+ [TestFixture]
+ public class MyCustomDataQueueHandlerTests
+ {
+ private static IEnumerable TestParameters
+ {
+ get
+ {
+ yield return new TestCaseData(Symbols.AAPL, Resolution.Tick);
+ yield return new TestCaseData(Symbols.SPY, Resolution.Second);
+ yield return new TestCaseData(Symbols.BTCUSD, Resolution.Minute);
+ }
+ }
+
+ [Test, TestCaseSource(nameof(TestParameters))]
+ public void StreamsData(Symbol symbol, Resolution resolution)
+ {
+ Assert.Pass();
+
+ var dataQueueHandlerProvider = new MyCustomDataProvider();
+
+ var configs = GetSubscriptionDataConfigs(symbol, resolution).ToList();
+
+ foreach (var config in configs)
+ {
+ ProcessFeed(
+ dataQueueHandlerProvider.Subscribe(config, (s, e) => { }),
+ (baseData) =>
+ {
+ if (baseData != null)
+ {
+ Log.Trace($"{baseData}");
+ }
+ });
+ }
+
+ Thread.Sleep(20_000);
+
+ foreach (var config in configs)
+ {
+ dataQueueHandlerProvider.Unsubscribe(config);
+ }
+
+ Thread.Sleep(1_000);
+ }
+
+ private IEnumerable GetSubscriptionDataConfigs(Symbol symbol, Resolution resolution)
+ {
+ if (resolution == Resolution.Tick)
+ {
+ yield return new SubscriptionDataConfig(GetSubscriptionDataConfig(symbol, resolution), tickType: TickType.Trade);
+ yield return new SubscriptionDataConfig(GetSubscriptionDataConfig(symbol, resolution), tickType: TickType.Quote);
+ }
+ else
+ {
+ yield return GetSubscriptionDataConfig(symbol, resolution);
+ yield return GetSubscriptionDataConfig(symbol, resolution);
+ }
+ }
+
+ private static SubscriptionDataConfig GetSubscriptionDataConfig(Symbol symbol, Resolution resolution)
+ {
+ return new SubscriptionDataConfig(
+ typeof(T),
+ symbol,
+ resolution,
+ TimeZones.Utc,
+ TimeZones.Utc,
+ true,
+ extendedHours: false,
+ false);
+ }
+
+ private Task ProcessFeed(IEnumerator enumerator, Action callback = null)
+ {
+ return Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ while (enumerator.MoveNext())
+ {
+ BaseData tick = enumerator.Current;
+
+ if (tick != null)
+ {
+ callback?.Invoke(tick);
+ }
+ }
+ }
+ catch
+ {
+ throw;
+ }
+ });
+ }
+ }
+}
diff --git a/tests/TestSetup.cs b/tests/TestSetup.cs
new file mode 100644
index 0000000..fa84f3b
--- /dev/null
+++ b/tests/TestSetup.cs
@@ -0,0 +1,79 @@
+/*
+ * 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 System.Collections;
+using QuantConnect.Logging;
+using QuantConnect.Configuration;
+
+namespace QuantConnect.DataLibrary.Tests
+{
+ [TestFixture]
+ public class TestSetup
+ {
+ [Test, TestCaseSource(nameof(TestParameters))]
+ public void TestSetupCase()
+ {
+ }
+
+ public static void ReloadConfiguration()
+ {
+ // nunit 3 sets the current folder to a temp folder we need it to be the test bin output folder
+ var dir = TestContext.CurrentContext.TestDirectory;
+ Environment.CurrentDirectory = dir;
+ Directory.SetCurrentDirectory(dir);
+ // reload config from current path
+ Config.Reset();
+
+ var environment = Environment.GetEnvironmentVariables();
+ foreach (DictionaryEntry entry in environment)
+ {
+ var envKey = entry.Key.ToString();
+ var value = entry.Value.ToString();
+
+ if (envKey.StartsWith("QC_"))
+ {
+ var key = envKey.Substring(3).Replace("_", "-").ToLower();
+
+ Log.Trace($"TestSetup(): Updating config setting '{key}' from environment var '{envKey}'");
+ Config.Set(key, value);
+ }
+ }
+
+ // resets the version among other things
+ Globals.Reset();
+ }
+
+ private static void SetUp()
+ {
+ Log.LogHandler = new CompositeLogHandler();
+ Log.Trace("TestSetup(): starting...");
+ ReloadConfiguration();
+ Log.DebuggingEnabled = Config.GetBool("debug-mode");
+ }
+
+ private static TestCaseData[] TestParameters
+ {
+ get
+ {
+ SetUp();
+ return new[] { new TestCaseData() };
+ }
+ }
+ }
+}
diff --git a/tests/Tests.csproj b/tests/Tests.csproj
index 8e308d0..b271b23 100644
--- a/tests/Tests.csproj
+++ b/tests/Tests.csproj
@@ -19,5 +19,11 @@
+
+
+
+
+ PreserveNewest
+
diff --git a/tests/config.json b/tests/config.json
new file mode 100644
index 0000000..7c2043b
--- /dev/null
+++ b/tests/config.json
@@ -0,0 +1,3 @@
+{
+ "data-folder": "../../../../../Lean/Data/"
+}