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,74 @@
/*
* 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 QuantConnect.Interfaces;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories
{
/// <summary>
/// Provides an implementation of <see cref="ISubscriptionEnumeratorFactory"/> that reads
/// an entire <see cref="SubscriptionDataSource"/> into a single <see cref="BaseDataCollection"/>
/// to be emitted on the tradable date at midnight
/// </summary>
/// <remarks>This enumerator factory is currently only used in backtesting with coarse data</remarks>
public class BaseDataCollectionSubscriptionEnumeratorFactory : ISubscriptionEnumeratorFactory
{
private IObjectStore _objectStore;
/// <summary>
/// Instanciates a new <see cref="BaseDataCollectionSubscriptionEnumeratorFactory"/>
/// </summary>
/// <param name="objectStore">The object store to use</param>
public BaseDataCollectionSubscriptionEnumeratorFactory(IObjectStore objectStore)
{
_objectStore = objectStore;
}
/// <summary>
/// Creates an enumerator to read the specified request
/// </summary>
/// <param name="request">The subscription request to be read</param>
/// <param name="dataProvider">Provider used to get data when it is not present on disk</param>
/// <returns>An enumerator reading the subscription request</returns>
public IEnumerator<BaseData> CreateEnumerator(SubscriptionRequest request, IDataProvider dataProvider)
{
using (var dataCacheProvider = new SingleEntryDataCacheProvider(dataProvider))
{
var configuration = request.Configuration;
var sourceFactory = (BaseData)Activator.CreateInstance(request.Configuration.Type);
// Behaves in the same way as in live trading
// (i.e. only emit coarse data on dates following a trading day)
// The shifting of dates is needed to ensure we never emit coarse data on the same date,
// because it would enable look-ahead bias.
foreach (var date in request.TradableDaysInDataTimeZone)
{
var source = sourceFactory.GetSource(configuration, date, false);
var factory = SubscriptionDataSourceReader.ForSource(source, dataCacheProvider, configuration, date, false, sourceFactory,
dataProvider, _objectStore);
var coarseFundamentalForDate = factory.Read(source);
// shift all date of emitting the file forward one day to model emitting coarse midnight the next day.
yield return new BaseDataCollection(date.AddDays(1), configuration.Symbol, coarseFundamentalForDate);
}
}
}
}
}
@@ -0,0 +1,101 @@
/*
* 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.Data.Auxiliary;
using QuantConnect.Interfaces;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories
{
/// <summary>
/// Helper class used to create the corporate event providers
/// <see cref="MappingEventProvider"/>, <see cref="SplitEventProvider"/>,
/// <see cref="DividendEventProvider"/>, <see cref="DelistingEventProvider"/>
/// </summary>
public static class CorporateEventEnumeratorFactory
{
/// <summary>
/// Creates a new <see cref="AuxiliaryDataEnumerator"/> that will hold the
/// corporate event providers
/// </summary>
/// <param name="rawDataEnumerator">The underlying raw data enumerator</param>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">Used for getting factor files</param>
/// <param name="tradableDayNotifier">Tradable dates provider</param>
/// <param name="mapFileProvider">The <see cref="MapFile"/> provider to use</param>
/// <param name="startTime">Start date for the data request</param>
/// <param name="endTime">
/// End date for the data request.
/// This will be used for <see cref="DataNormalizationMode.ScaledRaw"/> data normalization mode to adjust prices to the given end date
/// </param>
/// <param name="enablePriceScaling">Applies price factor</param>
/// <returns>The new auxiliary data enumerator</returns>
public static IEnumerator<BaseData> CreateEnumerators(
IEnumerator<BaseData> rawDataEnumerator,
SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
ITradableDatesNotifier tradableDayNotifier,
IMapFileProvider mapFileProvider,
DateTime startTime,
DateTime endTime,
bool enablePriceScaling = true)
{
var tradableEventProviders = new List<ITradableDateEventProvider>();
if (config.EmitSplitsAndDividends())
{
tradableEventProviders.Add(new SplitEventProvider());
tradableEventProviders.Add(new DividendEventProvider());
}
if (config.TickerShouldBeMapped())
{
tradableEventProviders.Add(new MappingEventProvider());
}
if (config.CanBeDelisted())
{
tradableEventProviders.Add(new DelistingEventProvider());
}
var enumerator = new AuxiliaryDataEnumerator(
config,
factorFileProvider,
mapFileProvider,
tradableEventProviders.ToArray(),
tradableDayNotifier,
startTime);
// avoid price scaling for backtesting; calculate it directly in worker
// and allow subscription to extract the the data depending on config data mode
var dataEnumerator = rawDataEnumerator;
if (enablePriceScaling && config.PricesShouldBeScaled())
{
dataEnumerator = new PriceScaleFactorEnumerator(
rawDataEnumerator,
config,
factorFileProvider,
endDate: endTime);
}
return new SynchronizingBaseDataEnumerator(dataEnumerator, enumerator);
}
}
}
@@ -0,0 +1,216 @@
/*
* 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 Python.Runtime;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Util;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories
{
/// <summary>
/// Provides an implementation of <see cref="ISubscriptionEnumeratorFactory"/> to handle live custom data.
/// </summary>
public class LiveCustomDataSubscriptionEnumeratorFactory : ISubscriptionEnumeratorFactory
{
private readonly TimeSpan _minimumIntervalCheck;
private readonly ITimeProvider _timeProvider;
private readonly Func<DateTime, DateTime> _dateAdjustment;
private readonly IObjectStore _objectStore;
/// <summary>
/// Initializes a new instance of the <see cref="LiveCustomDataSubscriptionEnumeratorFactory"/> class
/// </summary>
/// <param name="timeProvider">Time provider from data feed</param>
/// <param name="objectStore">The object store to use</param>
/// <param name="dateAdjustment">Func that allows adjusting the datetime to use</param>
/// <param name="minimumIntervalCheck">Allows specifying the minimum interval between each enumerator refresh and data check, default is 30 minutes</param>
public LiveCustomDataSubscriptionEnumeratorFactory(ITimeProvider timeProvider, IObjectStore objectStore,
Func<DateTime, DateTime> dateAdjustment = null, TimeSpan? minimumIntervalCheck = null)
{
_timeProvider = timeProvider;
_dateAdjustment = dateAdjustment;
_minimumIntervalCheck = minimumIntervalCheck ?? TimeSpan.FromMinutes(30);
_objectStore = objectStore;
}
/// <summary>
/// Creates an enumerator to read the specified request.
/// </summary>
/// <param name="request">The subscription request to be read</param>
/// <param name="dataProvider">Provider used to get data when it is not present on disk</param>
/// <returns>An enumerator reading the subscription request</returns>
public IEnumerator<BaseData> CreateEnumerator(SubscriptionRequest request, IDataProvider dataProvider)
{
var config = request.Configuration;
// frontier value used to prevent emitting duplicate time stamps between refreshed enumerators
// also provides some immediate fast-forward to handle spooling through remote files quickly
var frontier = Ref.Create(_dateAdjustment?.Invoke(request.StartTimeLocal) ?? request.StartTimeLocal);
var lastSourceRefreshTime = DateTime.MinValue;
var sourceFactory = config.GetBaseDataInstance();
// this is refreshing the enumerator stack for each new source
var refresher = new RefreshEnumerator<BaseData>(() =>
{
// rate limit the refresh of this enumerator stack
var utcNow = _timeProvider.GetUtcNow();
var minimumTimeBetweenCalls = GetMinimumTimeBetweenCalls(config.Increment, _minimumIntervalCheck);
if (utcNow - lastSourceRefreshTime < minimumTimeBetweenCalls)
{
return Enumerable.Empty<BaseData>().GetEnumerator();
}
lastSourceRefreshTime = utcNow;
var localDate = _dateAdjustment?.Invoke(utcNow.ConvertFromUtc(config.ExchangeTimeZone).Date) ?? utcNow.ConvertFromUtc(config.ExchangeTimeZone).Date;
var source = sourceFactory.GetSource(config, localDate, true);
// fetch the new source and enumerate the data source reader
var enumerator = EnumerateDataSourceReader(config, dataProvider, frontier, source, localDate, sourceFactory);
if (SourceRequiresFastForward(source))
{
// The FastForwardEnumerator implements these two features:
// (1) make sure we never emit past data
// (2) data filtering based on a maximum data age
// For custom data we don't want feature (2) because we would reject data points emitted later
// (e.g. Quandl daily data after a weekend), so we disable it using a huge maximum data age.
// apply fast forward logic for file transport mediums
var maximumDataAge = GetMaximumDataAge(Time.MaxTimeSpan);
enumerator = new FastForwardEnumerator(enumerator, _timeProvider, config.ExchangeTimeZone, maximumDataAge);
}
else
{
// rate limit calls to this enumerator stack
enumerator = new RateLimitEnumerator<BaseData>(enumerator, _timeProvider, minimumTimeBetweenCalls);
}
if (source.Format == FileFormat.UnfoldingCollection)
{
// unroll collections into individual data points after fast forward/rate limiting applied
enumerator = enumerator.SelectMany(data =>
{
var collection = data as BaseDataCollection;
IEnumerator<BaseData> collectionEnumerator;
if (collection != null)
{
if (source.TransportMedium == SubscriptionTransportMedium.Rest || source.TransportMedium == SubscriptionTransportMedium.RemoteFile)
{
// we want to make sure the data points we *unroll* are not past
collectionEnumerator = collection.Data
.Where(baseData => baseData.EndTime > frontier.Value)
.GetEnumerator();
}
else
{
collectionEnumerator = collection.Data.GetEnumerator();
}
}
else
{
collectionEnumerator = new List<BaseData> { data }.GetEnumerator();
}
return collectionEnumerator;
});
}
return enumerator;
});
return refresher;
}
private IEnumerator<BaseData> EnumerateDataSourceReader(SubscriptionDataConfig config, IDataProvider dataProvider, Ref<DateTime> localFrontier, SubscriptionDataSource source, DateTime localDate, BaseData baseDataInstance)
{
using (var dataCacheProvider = new SingleEntryDataCacheProvider(dataProvider))
{
var newLocalFrontier = localFrontier.Value;
var dataSourceReader = GetSubscriptionDataSourceReader(source, dataCacheProvider, config, localDate, baseDataInstance, dataProvider);
using var subscriptionEnumerator = SortEnumerator<DateTime>.TryWrapSortEnumerator(source.Sort, dataSourceReader.Read(source));
foreach (var datum in subscriptionEnumerator)
{
// always skip past all times emitted on the previous invocation of this enumerator
// this allows data at the same time from the same refresh of the source while excluding
// data from different refreshes of the source
if (datum != null && datum.EndTime > localFrontier.Value)
{
yield return datum;
}
else if (!SourceRequiresFastForward(source))
{
// if the 'source' is Rest and there is no new value,
// we *break*, else we will be caught in a tight loop
// because Rest source never ends!
// edit: we 'break' vs 'return null' so that the source is refreshed
// allowing date changes to impact the source value
// note it will respect 'minimumTimeBetweenCalls'
break;
}
if (datum != null)
{
newLocalFrontier = Time.Max(datum.EndTime, newLocalFrontier);
if (!SourceRequiresFastForward(source))
{
// if the 'source' is Rest we need to update the localFrontier here
// because Rest source never ends!
// Should be advance frontier for all source types here?
localFrontier.Value = newLocalFrontier;
}
}
}
localFrontier.Value = newLocalFrontier;
}
}
/// <summary>
/// Gets the <see cref="ISubscriptionDataSourceReader"/> for the specified source
/// </summary>
protected virtual ISubscriptionDataSourceReader GetSubscriptionDataSourceReader(SubscriptionDataSource source,
IDataCacheProvider dataCacheProvider,
SubscriptionDataConfig config,
DateTime date,
BaseData baseDataInstance,
IDataProvider dataProvider
)
{
return SubscriptionDataSourceReader.ForSource(source, dataCacheProvider, config, date, true, baseDataInstance, dataProvider, _objectStore);
}
private bool SourceRequiresFastForward(SubscriptionDataSource source)
{
return source.TransportMedium == SubscriptionTransportMedium.LocalFile
|| source.TransportMedium == SubscriptionTransportMedium.RemoteFile;
}
private static TimeSpan GetMinimumTimeBetweenCalls(TimeSpan increment, TimeSpan minimumInterval)
{
return TimeSpan.FromTicks(Math.Min(increment.Ticks, minimumInterval.Ticks));
}
private static TimeSpan GetMaximumDataAge(TimeSpan increment)
{
return TimeSpan.FromTicks(Math.Max(increment.Ticks, TimeSpan.FromSeconds(5).Ticks));
}
}
}
@@ -0,0 +1,166 @@
/*
* 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 QuantConnect.Data;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using System.Collections.Generic;
using System.Collections.Concurrent;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories
{
/// <summary>
/// Provides an implementation of <see cref="ISubscriptionEnumeratorFactory"/> that used the <see cref="SubscriptionDataReader"/>
/// </summary>
/// <remarks>Only used on backtesting by the <see cref="FileSystemDataFeed"/></remarks>
public class SubscriptionDataReaderSubscriptionEnumeratorFactory : ISubscriptionEnumeratorFactory, IDisposable
{
private readonly IResultHandler _resultHandler;
private readonly IFactorFileProvider _factorFileProvider;
private readonly IDataCacheProvider _dataCacheProvider;
private readonly ConcurrentDictionary<Symbol, string> _numericalPrecisionLimitedWarnings;
private readonly int _numericalPrecisionLimitedWarningsMaxCount = 10;
private readonly ConcurrentDictionary<Symbol, string> _startDateLimitedWarnings;
private readonly int _startDateLimitedWarningsMaxCount = 10;
private readonly IMapFileProvider _mapFileProvider;
private readonly bool _enablePriceScaling;
private readonly IAlgorithm _algorithm;
/// <summary>
/// Initializes a new instance of the <see cref="SubscriptionDataReaderSubscriptionEnumeratorFactory"/> class
/// </summary>
/// <param name="resultHandler">The result handler for the algorithm</param>
/// <param name="mapFileProvider">The map file provider</param>
/// <param name="factorFileProvider">The factor file provider</param>
/// <param name="cacheProvider">Provider used to get data when it is not present on disk</param>
/// <param name="algorithm">The algorithm instance to use</param>
/// <param name="enablePriceScaling">Applies price factor</param>
public SubscriptionDataReaderSubscriptionEnumeratorFactory(IResultHandler resultHandler,
IMapFileProvider mapFileProvider,
IFactorFileProvider factorFileProvider,
IDataCacheProvider cacheProvider,
IAlgorithm algorithm,
bool enablePriceScaling = true
)
{
_algorithm = algorithm;
_resultHandler = resultHandler;
_mapFileProvider = mapFileProvider;
_factorFileProvider = factorFileProvider;
_dataCacheProvider = cacheProvider;
_numericalPrecisionLimitedWarnings = new ConcurrentDictionary<Symbol, string>();
_startDateLimitedWarnings = new ConcurrentDictionary<Symbol, string>();
_enablePriceScaling = enablePriceScaling;
}
/// <summary>
/// Creates a <see cref="SubscriptionDataReader"/> to read the specified request
/// </summary>
/// <param name="request">The subscription request to be read</param>
/// <param name="dataProvider">Provider used to get data when it is not present on disk</param>
/// <returns>An enumerator reading the subscription request</returns>
public IEnumerator<BaseData> CreateEnumerator(SubscriptionRequest request, IDataProvider dataProvider)
{
var dataReader = new SubscriptionDataReader(request.Configuration,
request,
_mapFileProvider,
_factorFileProvider,
_dataCacheProvider,
dataProvider,
_algorithm.ObjectStore);
dataReader.InvalidConfigurationDetected += (sender, args) => { _resultHandler.ErrorMessage(args.Message); };
dataReader.StartDateLimited += (sender, args) =>
{
// Queue this warning into our dictionary to report on dispose
if (_startDateLimitedWarnings.Count <= _startDateLimitedWarningsMaxCount)
{
_startDateLimitedWarnings.TryAdd(args.Symbol, args.Message);
}
};
dataReader.DownloadFailed += (sender, args) => { _resultHandler.ErrorMessage(args.Message, args.StackTrace); };
dataReader.ReaderErrorDetected += (sender, args) => { _resultHandler.RuntimeError(args.Message, args.StackTrace); };
dataReader.NumericalPrecisionLimited += (sender, args) =>
{
// Set a hard limit to keep this warning list from getting unnecessarily large
if (_numericalPrecisionLimitedWarnings.Count <= _numericalPrecisionLimitedWarningsMaxCount)
{
_numericalPrecisionLimitedWarnings.TryAdd(args.Symbol, args.Message);
}
};
IEnumerator<BaseData> enumerator = dataReader;
if (LeanData.UseDailyStrictEndTimes(_algorithm.Settings, request, request.Configuration.Symbol, request.Configuration.Increment))
{
// before corporate events which might yield data and we synchronize both feeds
enumerator = new StrictDailyEndTimesEnumerator(enumerator, request.ExchangeHours, request.StartTimeLocal);
}
enumerator = CorporateEventEnumeratorFactory.CreateEnumerators(
enumerator,
request.Configuration,
_factorFileProvider,
dataReader,
_mapFileProvider,
request.StartTimeLocal,
request.EndTimeLocal,
_enablePriceScaling);
return enumerator;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
// Log our numerical precision limited warnings if any
if (!_numericalPrecisionLimitedWarnings.IsNullOrEmpty())
{
var message = "Due to numerical precision issues in the factor file, data for the following" +
$" symbols was adjust to a later starting date: {string.Join(", ", _numericalPrecisionLimitedWarnings.Values.Take(_numericalPrecisionLimitedWarningsMaxCount))}";
// If we reached our max warnings count suggest that more may have been left out
if (_numericalPrecisionLimitedWarnings.Count >= _numericalPrecisionLimitedWarningsMaxCount)
{
message += "...";
}
_resultHandler.DebugMessage(message);
}
// Log our start date adjustments because of map files
if (!_startDateLimitedWarnings.IsNullOrEmpty())
{
var message = "The starting dates for the following symbols have been adjusted to match their" +
$" map files first date: {string.Join(", ", _startDateLimitedWarnings.Values.Take(_startDateLimitedWarningsMaxCount))}";
// If we reached our max warnings count suggest that more may have been left out
if (_startDateLimitedWarnings.Count >= _startDateLimitedWarningsMaxCount)
{
message += "...";
}
_resultHandler.DebugMessage(message);
}
}
}
}
@@ -0,0 +1,61 @@
/*
* 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 System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
using QuantConnect.Securities;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators.Factories
{
/// <summary>
/// Provides an implementation of <see cref="ISubscriptionEnumeratorFactory"/> to emit
/// ticks based on <see cref="UserDefinedUniverse.GetTriggerTimes"/>, allowing universe
/// selection to fire at planned times.
/// </summary>
public class TimeTriggeredUniverseSubscriptionEnumeratorFactory : ISubscriptionEnumeratorFactory
{
private readonly ITimeTriggeredUniverse _universe;
private readonly MarketHoursDatabase _marketHoursDatabase;
/// <summary>
/// Initializes a new instance of the <see cref="TimeTriggeredUniverseSubscriptionEnumeratorFactory"/> class
/// </summary>
/// <param name="universe">The user defined universe</param>
/// <param name="marketHoursDatabase">The market hours database</param>
public TimeTriggeredUniverseSubscriptionEnumeratorFactory(ITimeTriggeredUniverse universe, MarketHoursDatabase marketHoursDatabase)
{
_universe = universe;
_marketHoursDatabase = marketHoursDatabase;
}
/// <summary>
/// Creates an enumerator to read the specified request
/// </summary>
/// <param name="request">The subscription request to be read</param>
/// <param name="dataProvider">Provider used to get data when it is not present on disk</param>
/// <returns>An enumerator reading the subscription request</returns>
public IEnumerator<BaseData> CreateEnumerator(SubscriptionRequest request, IDataProvider dataProvider)
{
return _universe.GetTriggerTimes(request.StartTimeUtc, request.EndTimeUtc, _marketHoursDatabase)
.Select(x => new Tick { Time = x, Symbol = request.Configuration.Symbol })
.GetEnumerator();
}
}
}