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,149 @@
/*
* 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.Util;
using QuantConnect.Data;
using System.Collections;
using QuantConnect.Interfaces;
using System.Collections.Generic;
using QuantConnect.Data.Auxiliary;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Auxiliary data enumerator that will, initialize and call the <see cref="ITradableDateEventProvider.GetEvents"/>
/// implementation each time there is a new tradable day for every <see cref="ITradableDateEventProvider"/>
/// provided.
/// </summary>
public class AuxiliaryDataEnumerator : IEnumerator<BaseData>
{
private readonly Queue<BaseData> _auxiliaryData;
private bool _initialized;
private DateTime _startTime;
private IMapFileProvider _mapFileProvider;
private IFactorFileProvider _factorFileProvider;
private ITradableDateEventProvider[] _tradableDateEventProviders;
/// <summary>
/// The associated data configuration
/// </summary>
protected SubscriptionDataConfig Config { get; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">The factor file provider to use</param>
/// <param name="mapFileProvider">The <see cref="MapFile"/> provider to use</param>
/// <param name="tradableDateEventProviders">The tradable dates event providers</param>
/// <param name="tradableDayNotifier">Tradable dates provider</param>
/// <param name="startTime">Start date for the data request</param>
public AuxiliaryDataEnumerator(
SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
IMapFileProvider mapFileProvider,
ITradableDateEventProvider []tradableDateEventProviders,
ITradableDatesNotifier tradableDayNotifier,
DateTime startTime)
{
Config = config;
_startTime = startTime;
_mapFileProvider = mapFileProvider;
_auxiliaryData = new Queue<BaseData>();
_factorFileProvider = factorFileProvider;
_tradableDateEventProviders = tradableDateEventProviders;
if (tradableDayNotifier != null)
{
tradableDayNotifier.NewTradableDate += NewTradableDate;
}
}
/// <summary>
/// Advances the enumerator to the next element.
/// </summary>
/// <returns>Always true</returns>
public virtual bool MoveNext()
{
Current = _auxiliaryData.Count != 0 ? _auxiliaryData.Dequeue() : null;
return true;
}
/// <summary>
/// Handle a new tradable date, drives the <see cref="ITradableDateEventProvider"/> instances
/// </summary>
protected void NewTradableDate(object sender, NewTradableDateEventArgs eventArgs)
{
Initialize();
for (var i = 0; i < _tradableDateEventProviders.Length; i++)
{
foreach (var newEvent in _tradableDateEventProviders[i].GetEvents(eventArgs))
{
_auxiliaryData.Enqueue(newEvent);
}
}
}
/// <summary>
/// Initializes the underlying tradable data event providers
/// </summary>
protected void Initialize()
{
if (!_initialized)
{
_initialized = true;
// Late initialization so it is performed in the data feed stack
for (var i = 0; i < _tradableDateEventProviders.Length; i++)
{
_tradableDateEventProviders[i].Initialize(Config, _factorFileProvider, _mapFileProvider, _startTime);
}
}
}
/// <summary>
/// Dispose of the Stream Reader and close out the source stream and file connections.
/// </summary>
public void Dispose()
{
for (var i = 0; i < _tradableDateEventProviders.Length; i++)
{
var disposable =_tradableDateEventProviders[i] as IDisposable;
disposable?.DisposeSafely();
}
}
/// <summary>
/// Reset the IEnumeration
/// </summary>
/// <remarks>Not used</remarks>
public void Reset()
{
throw new NotImplementedException("Reset method not implemented. Assumes loop will only be used once.");
}
object IEnumerator.Current => Current;
/// <summary>
/// Last read BaseData object from this type and source
/// </summary>
public BaseData Current
{
get;
private set;
}
}
}
@@ -0,0 +1,234 @@
/*
* 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;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Provides an implementation of <see cref="IEnumerator{BaseDataCollection}"/>
/// that aggregates an underlying <see cref="IEnumerator{BaseData}"/> into a single
/// data packet
/// </summary>
public class BaseDataCollectionAggregatorEnumerator : IEnumerator<BaseDataCollection>
{
private bool _endOfStream;
private bool _needsMoveNext;
private bool _liveMode;
private readonly Symbol _symbol;
private readonly IEnumerator<BaseData> _enumerator;
/// <summary>
/// Initializes a new instance of the <see cref="BaseDataCollectionAggregatorEnumerator"/> class
/// This will aggregate instances emitted from the underlying enumerator and tag them with the
/// specified symbol
/// </summary>
/// <param name="enumerator">The underlying enumerator to aggregate</param>
/// <param name="symbol">The symbol to place on the aggregated collection</param>
/// <param name="liveMode">True if running in live mode</param>
public BaseDataCollectionAggregatorEnumerator(IEnumerator<BaseData> enumerator, Symbol symbol, bool liveMode = false)
{
_symbol = symbol;
_enumerator = enumerator;
_liveMode = liveMode;
_needsMoveNext = true;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
if (_endOfStream)
{
return false;
}
BaseDataCollection collection = null;
while (true)
{
if (_needsMoveNext)
{
// move next if we dequeued the last item last time we were invoked
if (!_enumerator.MoveNext())
{
_endOfStream = true;
if (!IsValid(collection))
{
// we don't emit
collection = null;
}
break;
}
}
if (_enumerator.Current == null)
{
// the underlying returned null, stop here and start again on the next call
_needsMoveNext = true;
break;
}
if (collection == null)
{
// we have new data, set the collection's symbol/times
var current = _enumerator.Current;
collection = CreateCollection(_symbol, current.Time, current.EndTime);
}
if (collection.EndTime != _enumerator.Current.EndTime)
{
// the data from the underlying is at a different time, stop here
_needsMoveNext = false;
if (IsValid(collection))
{
// we emit
break;
}
// we try again
collection = null;
continue;
}
// this data belongs in this collection, keep going until null or bad time
Add(collection, _enumerator.Current);
_needsMoveNext = true;
}
Current = collection;
return _liveMode || collection != null;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
_enumerator.Reset();
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public BaseDataCollection Current
{
get; private set;
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_enumerator.Dispose();
}
/// <summary>
/// Creates a new, empty <see cref="BaseDataCollection"/>.
/// </summary>
/// <param name="symbol">The base data collection symbol</param>
/// <param name="time">The start time of the collection</param>
/// <param name="endTime">The end time of the collection</param>
/// <returns>A new, empty <see cref="BaseDataCollection"/></returns>
private BaseDataCollection CreateCollection(Symbol symbol, DateTime time, DateTime endTime)
{
return new BaseDataCollection
{
Symbol = symbol,
Time = time,
EndTime = endTime
};
}
/// <summary>
/// Adds the specified instance of <see cref="BaseData"/> to the current collection
/// </summary>
/// <param name="collection">The collection to be added to</param>
/// <param name="current">The data to be added</param>
private void Add(BaseDataCollection collection, BaseData current)
{
var baseDataCollection = current as BaseDataCollection;
if (_symbol.HasUnderlying && _symbol.Underlying == current.Symbol)
{
// if the underlying has been aggregated, even if it shouldn't need to be, let's handle it nicely
if (baseDataCollection != null)
{
collection.Underlying = baseDataCollection.Data[0];
}
else
{
collection.Underlying = current;
}
}
else
{
if (baseDataCollection != null)
{
// datapoint is already aggregated, let's see if it's a single point or a collection we can use already
if(baseDataCollection.Data.Count > 1)
{
collection.Data = baseDataCollection.Data;
}
else
{
collection.Data.Add(baseDataCollection.Data[0]);
}
// Let's keep the underlying in case it's already there
collection.Underlying ??= baseDataCollection.Underlying;
}
else
{
collection.Data.Add(current);
}
}
}
/// <summary>
/// Determines if a given data point is valid and can be emitted
/// </summary>
/// <param name="collection">The collection to be emitted</param>
/// <returns>True if its a valid data point</returns>
private static bool IsValid(BaseDataCollection collection)
{
return collection != null && collection.Data?.Count > 0;
}
}
}
@@ -0,0 +1,133 @@
/*
* 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 System.Collections;
using QuantConnect.Logging;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Enumerator that will concatenate enumerators together sequentially enumerating them in the provided order
/// </summary>
public class ConcatEnumerator : IEnumerator<BaseData>
{
private readonly List<IEnumerator<BaseData>> _enumerators;
private readonly bool _skipDuplicateEndTimes;
private DateTime? _lastEnumeratorEndTime;
private int _currentIndex;
/// <summary>
/// The current BaseData object
/// </summary>
public BaseData Current { get; set; }
/// <summary>
/// True if emitting a null data point is expected
/// </summary>
/// <remarks>Warmup enumerators are not allowed to return true and setting current to Null, this is because it's not a valid behavior for backtesting enumerators,
/// for example <see cref="FillForwardEnumerator"/></remarks>
public bool CanEmitNull { get; set; }
object IEnumerator.Current => Current;
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="skipDuplicateEndTimes">True will skip data points from enumerators if before or at the last end time</param>
/// <param name="enumerators">The sequence of enumerators to concatenate. Note that the order here matters, it will consume enumerators
/// and dispose of them, even if they return true and their current is null, except for the last which will be kept!</param>
public ConcatEnumerator(bool skipDuplicateEndTimes,
params IEnumerator<BaseData>[] enumerators
)
{
CanEmitNull = true;
_skipDuplicateEndTimes = skipDuplicateEndTimes;
_enumerators = enumerators.Where(enumerator => enumerator != null).ToList();
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>True if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
public bool MoveNext()
{
for (; _currentIndex < _enumerators.Count; _currentIndex++)
{
var enumerator = _enumerators[_currentIndex];
while (enumerator.MoveNext())
{
if (enumerator.Current == null && (_currentIndex < _enumerators.Count - 1 || !CanEmitNull))
{
// if there are more enumerators and the current stopped providing data drop it
// in live trading, some enumerators will always return true (see TimeTriggeredUniverseSubscriptionEnumeratorFactory & InjectionEnumerator)
// but unless it's the last enumerator we drop it, because these first are the warmup enumerators
// or we are not allowed to return null
break;
}
if (_skipDuplicateEndTimes
&& _lastEnumeratorEndTime.HasValue
&& enumerator.Current != null
&& enumerator.Current.EndTime <= _lastEnumeratorEndTime)
{
continue;
}
Current = enumerator.Current;
return true;
}
_lastEnumeratorEndTime = Current?.EndTime;
if (Log.DebuggingEnabled)
{
Log.Debug($"ConcatEnumerator.MoveNext(): disposing enumerator at position: {_currentIndex} Name: {enumerator.GetType().Name}");
}
// we wont be using this enumerator again, dispose of it and clear reference
enumerator.DisposeSafely();
_enumerators[_currentIndex] = null;
}
Current = null;
return false;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
public void Reset()
{
throw new InvalidOperationException($"Can not reset {nameof(ConcatEnumerator)}");
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
foreach (var enumerator in _enumerators)
{
enumerator.DisposeSafely();
}
}
}
}
@@ -0,0 +1,118 @@
/*
* 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.Util;
using QuantConnect.Interfaces;
using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Data.Auxiliary;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event provider who will emit <see cref="Delisting"/> events
/// </summary>
public class DelistingEventProvider : ITradableDateEventProvider
{
// we'll use these flags to denote we've already fired off the DelistingType.Warning
// and a DelistedType.Delisted Delisting object, the _delistingType object is save here
// since we need to wait for the next trading day before emitting
private bool _delisted;
private bool _delistedWarning;
private IMapFileProvider _mapFileProvider;
/// <summary>
/// The delisting date
/// </summary>
protected ReferenceWrapper<DateTime> DelistingDate { get; set; }
/// <summary>
/// The current instance being used
/// </summary>
protected MapFile MapFile { get; private set; }
/// <summary>
/// The associated configuration
/// </summary>
protected SubscriptionDataConfig Config { get; private set; }
/// <summary>
/// Initializes this instance
/// </summary>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">The factor file provider to use</param>
/// <param name="mapFileProvider">The <see cref="Data.Auxiliary.MapFile"/> provider to use</param>
/// <param name="startTime">Start date for the data request</param>
public virtual void Initialize(
SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
IMapFileProvider mapFileProvider,
DateTime startTime)
{
Config = config;
_mapFileProvider = mapFileProvider;
InitializeMapFile();
}
/// <summary>
/// Check for delistings
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New delisting event if any</returns>
public virtual IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
if (Config.Symbol == eventArgs.Symbol)
{
// we send the delisting warning when we reach the delisting date, here we make sure we compare using the date component
// of the delisting date since for example some futures can trade a few hours in their delisting date, else we would skip on
// emitting the delisting warning, which triggers us to handle liquidation once delisted
if (!_delistedWarning && eventArgs.Date >= DelistingDate.Value.Date)
{
_delistedWarning = true;
var price = eventArgs.LastBaseData?.Price ?? 0;
yield return new Delisting(
eventArgs.Symbol,
DelistingDate.Value.Date,
price,
DelistingType.Warning);
}
if (!_delisted && eventArgs.Date > DelistingDate.Value)
{
_delisted = true;
var price = eventArgs.LastBaseData?.Price ?? 0;
// delisted at EOD
yield return new Delisting(
eventArgs.Symbol,
DelistingDate.Value.AddDays(1),
price,
DelistingType.Delisted);
}
}
}
/// <summary>
/// Initializes the factor file to use
/// </summary>
protected void InitializeMapFile()
{
MapFile = _mapFileProvider.ResolveMapFile(Config);
DelistingDate = new ReferenceWrapper<DateTime>(Config.Symbol.GetDelistingDate(MapFile));
}
}
}
@@ -0,0 +1,118 @@
/*
* 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.Data.Market;
using QuantConnect.Interfaces;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event provider who will emit <see cref="Dividend"/> events
/// </summary>
public class DividendEventProvider : ITradableDateEventProvider
{
// we set the price factor ratio when we encounter a dividend in the factor file
// and on the next trading day we use this data to produce the dividend instance
private decimal? _priceFactorRatio;
private decimal _referencePrice;
private IFactorFileProvider _factorFileProvider;
private MapFile _mapFile;
/// <summary>
/// The current instance being used
/// </summary>
protected CorporateFactorProvider FactorFile { get; private set; }
/// <summary>
/// The associated configuration
/// </summary>
protected SubscriptionDataConfig Config { get; private set; }
/// <summary>
/// Initializes this instance
/// </summary>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">The factor file provider to use</param>
/// <param name="mapFileProvider">The <see cref="Data.Auxiliary.MapFile"/> provider to use</param>
/// <param name="startTime">Start date for the data request</param>
public void Initialize(
SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
IMapFileProvider mapFileProvider,
DateTime startTime)
{
Config = config;
_factorFileProvider = factorFileProvider;
_mapFile = mapFileProvider.ResolveMapFile(Config);
InitializeFactorFile();
}
/// <summary>
/// Check for dividends and returns them
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New Dividend event if any</returns>
public virtual IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
if (Config.Symbol == eventArgs.Symbol
&& FactorFile != null
&& _mapFile.HasData(eventArgs.Date))
{
if (_priceFactorRatio != null)
{
if (_referencePrice == 0)
{
throw new InvalidOperationException($"Zero reference price for {Config.Symbol} dividend at {eventArgs.Date}");
}
var baseData = Dividend.Create(
Config.Symbol,
eventArgs.Date,
_referencePrice,
_priceFactorRatio.Value
);
// let the config know about it for normalization
Config.SumOfDividends += baseData.Distribution;
_priceFactorRatio = null;
_referencePrice = 0;
yield return baseData;
}
// check the factor file to see if we have a dividend event tomorrow
decimal priceFactorRatio;
decimal referencePrice;
if (FactorFile.HasDividendEventOnNextTradingDay(eventArgs.Date, out priceFactorRatio, out referencePrice))
{
_priceFactorRatio = priceFactorRatio;
_referencePrice = referencePrice;
}
}
}
/// <summary>
/// Initializes the factor file to use
/// </summary>
protected void InitializeFactorFile()
{
FactorFile = _factorFileProvider.Get(Config.Symbol) as CorporateFactorProvider;
}
}
}
@@ -0,0 +1,214 @@
/*
* 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.Threading;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// An implementation of <see cref="IEnumerator{T}"/> that relies on the
/// <see cref="Enqueue"/> method being called and only ends when <see cref="Stop"/>
/// is called
/// </summary>
/// <typeparam name="T">The item type yielded by the enumerator</typeparam>
public class EnqueueableEnumerator<T> : IEnumerator<T>
{
private T _current;
private bool _end;
private readonly bool _isBlocking;
private long _consumerCount;
private Queue<T> _consumer = new();
private Queue<T> _producer = new();
private readonly object _lock = new object();
private readonly ManualResetEventSlim _resetEvent = new(false);
/// <summary>
/// Gets the current number of items held in the internal queue
/// </summary>
public int Count
{
get
{
lock (_lock)
{
if (_end) return 0;
return _producer.Count + (int)Interlocked.Read(ref _consumerCount);
}
}
}
/// <summary>
/// Returns true if the enumerator has finished and will not accept any more data
/// </summary>
public bool HasFinished
{
get { return _end; }
}
/// <summary>
/// Initializes a new instance of the <see cref="EnqueueableEnumerator{T}"/> class
/// </summary>
/// <param name="blocking">Specifies whether or not to use the blocking behavior</param>
public EnqueueableEnumerator(bool blocking = false)
{
_isBlocking = blocking;
}
/// <summary>
/// Enqueues the new data into this enumerator
/// </summary>
/// <param name="data">The data to be enqueued</param>
public void Enqueue(T data)
{
lock (_lock)
{
_producer.Enqueue(data);
// most of the time this will be set
if(!_resetEvent.IsSet)
{
_resetEvent.Set();
}
}
}
/// <summary>
/// Signals the enumerator to stop enumerating when the items currently
/// held inside are gone. No more items will be added to this enumerator.
/// </summary>
public void Stop()
{
lock (_lock)
{
if (_end) return;
_end = true;
// no more items can be added, so no need to wait anymore
_resetEvent.Set();
_resetEvent.Dispose();
}
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
// we read with no lock most of the time
if (_consumer.TryDequeue(out _current))
{
Interlocked.Decrement(ref _consumerCount);
return true;
}
bool ended;
do
{
var producer = _producer;
lock (_lock)
{
// swap queues
ended = _end;
_producer = _consumer;
}
_consumer = producer;
if(_consumer.Count > 0)
{
_current = _consumer.Dequeue();
Interlocked.Exchange(ref _consumerCount, _consumer.Count);
break;
}
// if we are here no queue has data
if (ended)
{
return false;
}
if (_isBlocking)
{
try
{
_resetEvent.Wait(Timeout.Infinite);
_resetEvent.Reset();
}
catch (ObjectDisposedException)
{
// can happen if disposed
}
}
else
{
break;
}
}
while (!ended);
// even if we don't have data to return, we haven't technically
// passed the end of the collection, so always return true until
// the enumerator is explicitly disposed or ended
return true;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
throw new NotImplementedException("EnqueableEnumerator.Reset() has not been implemented yet.");
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public T Current
{
get { return _current; }
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
Stop();
}
}
}
@@ -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();
}
}
}
@@ -0,0 +1,132 @@
/*
* 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;
using System.Collections.Generic;
using NodaTime;
using QuantConnect.Data;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Provides the ability to fast forward an enumerator based on the age of the data
/// </summary>
public class FastForwardEnumerator : IEnumerator<BaseData>
{
private BaseData _current;
private readonly DateTimeZone _timeZone;
private readonly TimeSpan _maximumDataAge;
private readonly ITimeProvider _timeProvider;
private readonly IEnumerator<BaseData> _enumerator;
/// <summary>
/// Initializes a new instance of the <see cref="FastForwardEnumerator"/> class
/// </summary>
/// <param name="enumerator">The source enumerator</param>
/// <param name="timeProvider">A time provider used to determine age of data</param>
/// <param name="timeZone">The data's time zone</param>
/// <param name="maximumDataAge">The maximum age of data allowed</param>
public FastForwardEnumerator(IEnumerator<BaseData> enumerator, ITimeProvider timeProvider, DateTimeZone timeZone, TimeSpan maximumDataAge)
{
_enumerator = enumerator;
_timeProvider = timeProvider;
_timeZone = timeZone;
_maximumDataAge = maximumDataAge;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
public bool MoveNext()
{
// keep churning until recent data or null
while (_enumerator.MoveNext())
{
// we can't fast forward nulls or bad times
if (_enumerator.Current == null || _enumerator.Current.Time == DateTime.MinValue)
{
_current = null;
return true;
}
// make sure we never emit past data
if (_current != null && _current.EndTime > _enumerator.Current.EndTime)
{
continue;
}
// comute the age of the data, if within limits we're done
var age = _timeProvider.GetUtcNow().ConvertFromUtc(_timeZone) - _enumerator.Current.EndTime;
if (age <= _maximumDataAge)
{
_current = _enumerator.Current;
return true;
}
}
// we've exhausted the underlying enumerator, iterator completed
_current = null;
return false;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
_enumerator.Reset();
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public BaseData Current
{
get { return _current; }
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return _current; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_enumerator.Dispose();
}
}
}
@@ -0,0 +1,638 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using NodaTime;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Util;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// The FillForwardEnumerator wraps an existing base data enumerator and inserts extra 'base data' instances
/// on a specified fill forward resolution
/// </summary>
public class FillForwardEnumerator : IEnumerator<BaseData>
{
private DateTime? _delistedTime;
private BaseData _previous;
private bool _ended;
private bool _isFillingForward;
private bool _initialized;
/// <summary>
/// Whether to use strict daily end times
/// </summary>
protected bool UseStrictEndTime { get; }
private readonly TimeSpan _dataResolution;
private readonly DateTimeZone _dataTimeZone;
private readonly bool _isExtendedMarketHours;
private readonly DateTime _subscriptionStartTime;
private readonly DateTime _subscriptionEndTime;
private readonly CalendarInfo _subscriptionEndDataCalendar;
private readonly IEnumerator<BaseData> _enumerator;
private readonly IReadOnlyRef<TimeSpan> _fillForwardResolution;
private readonly bool _strictEndTimeIntraDayFillForward;
/// <summary>
/// The exchange used to determine when to insert fill forward data
/// </summary>
protected SecurityExchange Exchange { get; init; }
/// <summary>
/// A reference to the last point emitted for the subscription.
/// This is used to feed the last point of a previous enumerator in cases like concatenated enumerators.
/// For instance, if this enumerator is concatenated to a warm up one, we can use this to feed
/// the last point of the warm up enumerator to this one, so that it can use it to fill forward if
/// the first actual point of this enumerator is ahead of the subscription start time or the first market open after it.
/// </summary>
private LastPointTracker _lastPointTracker;
/// <summary>
/// Initializes a new instance of the <see cref="FillForwardEnumerator"/> class that accepts
/// a reference to the fill forward resolution, useful if the fill forward resolution is dynamic
/// and changing as the enumeration progresses
/// </summary>
/// <param name="enumerator">The source enumerator to be filled forward</param>
/// <param name="exchange">The exchange used to determine when to insert fill forward data</param>
/// <param name="fillForwardResolution">The resolution we'd like to receive data on</param>
/// <param name="isExtendedMarketHours">True to use the exchange's extended market hours, false to use the regular market hours</param>
/// <param name="subscriptionStartTime">The start time of the subscription</param>
/// <param name="subscriptionEndTime">The end time of the subscription, once passing this date the enumerator will stop</param>
/// <param name="dataResolution">The source enumerator's data resolution</param>
/// <param name="dataTimeZone">The time zone of the underlying source data. This is used for rounding calculations and
/// is NOT the time zone on the BaseData instances (unless of course data time zone equals the exchange time zone)</param>
/// <param name="dailyStrictEndTimeEnabled">True if daily strict end times are enabled</param>
/// <param name="dataType">The configuration data type this enumerator is for</param>
/// <param name="lastPointTracker">A reference to the last point emitted before this enumerator is first enumerated</param>
public FillForwardEnumerator(IEnumerator<BaseData> enumerator,
SecurityExchange exchange,
IReadOnlyRef<TimeSpan> fillForwardResolution,
bool isExtendedMarketHours,
DateTime subscriptionStartTime,
DateTime subscriptionEndTime,
TimeSpan dataResolution,
DateTimeZone dataTimeZone,
bool dailyStrictEndTimeEnabled,
Type dataType = null,
LastPointTracker lastPointTracker = null
)
{
_subscriptionStartTime = subscriptionStartTime;
_subscriptionEndTime = subscriptionEndTime;
Exchange = exchange;
_enumerator = enumerator;
_dataResolution = dataResolution;
_dataTimeZone = dataTimeZone;
_fillForwardResolution = fillForwardResolution;
_isExtendedMarketHours = isExtendedMarketHours;
_lastPointTracker = lastPointTracker;
UseStrictEndTime = dailyStrictEndTimeEnabled;
// OI data is fill-forwarded to the market close time when strict end times is enabled.
// Open interest data can arrive at any time and this would allow to synchronize it with trades and quotes when daily
// strict end times is enabled
_strictEndTimeIntraDayFillForward = dailyStrictEndTimeEnabled && dataType != null && dataType == typeof(OpenInterest);
// '_dataResolution' and '_subscriptionEndTime' are readonly they won't change, so lets calculate this once here since it's expensive.
// if UseStrictEndTime and also _strictEndTimeIntraDayFillForward, this is a subscription with data that is not adjusted
// for the strict end time (like open interest) but require fill forward to synchronize with other data.
// Use the non strict end time calendar for the last day of data so that all data for that date is emitted.
if (UseStrictEndTime && !_strictEndTimeIntraDayFillForward)
{
var lastDayCalendar = GetDailyCalendar(_subscriptionEndTime);
while (lastDayCalendar.End > _subscriptionEndTime)
{
lastDayCalendar = GetDailyCalendar(lastDayCalendar.Start.AddDays(-1));
}
_subscriptionEndDataCalendar = lastDayCalendar;
}
else
{
_subscriptionEndDataCalendar = new (RoundDown(_subscriptionEndTime, _dataResolution), _dataResolution);
}
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public BaseData Current
{
get;
private set;
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current => Current;
private void Initialize()
{
if (_initialized)
{
return;
}
if (_lastPointTracker?.LastDataPoint != null)
{
// adjust the previous data point to the subscription start time to
// avoid emitting fill forward data before that
_previous = _lastPointTracker.LastDataPoint.Clone();
_previous.Time = _subscriptionStartTime - _dataResolution;
}
_initialized = true;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
Initialize();
if (_delistedTime.HasValue)
{
// don't fill forward after data after the delisted date
if (_previous == null || _previous.EndTime >= _delistedTime.Value)
{
return false;
}
}
if (Current != null && Current.DataType != MarketDataType.Auxiliary)
{
// only set the _previous if the last item we emitted was NOT auxilliary data,
// since _previous is used for fill forward behavior
_previous = Current;
}
BaseData fillForward;
if (!_isFillingForward)
{
// if we're filling forward we don't need to move next since we haven't emitted _enumerator.Current yet
if (!_enumerator.MoveNext())
{
_ended = true;
if (_delistedTime.HasValue)
{
// don't fill forward delisted data
return false;
}
// check to see if we ran out of data before the end of the subscription
if (_previous == null || _previous.EndTime >= _subscriptionEndTime)
{
// we passed the end of subscription, we're finished
return false;
}
// we can fill forward the rest of this subscription if required
var endOfSubscription = (Current ?? _previous).Clone(true);
endOfSubscription.Time = _subscriptionEndDataCalendar.Start;
endOfSubscription.EndTime = endOfSubscription.Time + _subscriptionEndDataCalendar.Period;
if (RequiresFillForwardData(_fillForwardResolution.Value, _previous, endOfSubscription, out fillForward))
{
// don't mark as filling forward so we come back into this block, subscription is done
//_isFillingForward = true;
Current = fillForward;
return true;
}
// don't emit the last bar if the market isn't considered open!
if (!Exchange.IsOpenDuringBar(endOfSubscription.Time, endOfSubscription.EndTime, _isExtendedMarketHours))
{
return false;
}
if (Current != null && Current.EndTime == endOfSubscription.EndTime
// TODO this changes stats, why would the FF enumerator emit a data point beyoned the end time he was requested
//|| endOfSubscription.EndTime > _subscriptionEndTime
)
{
return false;
}
Current = endOfSubscription;
return true;
}
}
// If we are filling forward and the underlying is null, let's MoveNext() as long as it didn't end.
// This only applies for live trading, so that the LiveFillForwardEnumerator does not stall whenever
// we generate a fill-forward bar. The underlying enumerator is advanced so that we don't get stuck
// in a cycle of generating infinite fill-forward bars.
else if (_enumerator.Current == null && !_ended)
{
_ended = _enumerator.MoveNext();
}
var underlyingCurrent = _enumerator.Current;
if (underlyingCurrent != null && underlyingCurrent.DataType == MarketDataType.Auxiliary)
{
var delisting = underlyingCurrent as Delisting;
if (delisting?.Type == DelistingType.Delisted)
{
_delistedTime = delisting.EndTime;
}
}
if (_previous == null)
{
// first data point we dutifully emit without modification
Current = underlyingCurrent;
return true;
}
if (RequiresFillForwardData(_fillForwardResolution.Value, _previous, underlyingCurrent, out fillForward))
{
if (_previous.EndTime >= _subscriptionEndTime)
{
// we passed the end of subscription, we're finished
return false;
}
// we require fill forward data because the _enumerator.Current is too far in future
_isFillingForward = true;
Current = fillForward;
return true;
}
_isFillingForward = false;
Current = underlyingCurrent;
return true;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_enumerator.Dispose();
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
_enumerator.Reset();
}
/// <summary>
/// Determines whether or not fill forward is required, and if true, will produce the new fill forward data
/// </summary>
/// <param name="fillForwardResolution"></param>
/// <param name="previous">The last piece of data emitted by this enumerator</param>
/// <param name="next">The next piece of data on the source enumerator</param>
/// <param name="fillForward">When this function returns true, this will have a non-null value, null when the function returns false</param>
/// <returns>True when a new fill forward piece of data was produced and should be emitted by this enumerator</returns>
protected virtual bool RequiresFillForwardData(TimeSpan fillForwardResolution, BaseData previous, BaseData next, out BaseData fillForward)
{
// in live trading next can be null, in which case we create a potential FF bar and the live FF enumerator will decide what to do
var nextCalculatedEndTimeUtc = DateTime.MaxValue;
if (next != null)
{
// convert times to UTC for accurate comparisons and differences across DST changes
var previousTimeUtc = previous.Time.ConvertToUtc(Exchange.TimeZone);
var nextTimeUtc = next.Time.ConvertToUtc(Exchange.TimeZone);
var nextEndTimeUtc = next.EndTime.ConvertToUtc(Exchange.TimeZone);
if (nextEndTimeUtc < previousTimeUtc)
{
if (_lastPointTracker == null || next.EndTime > _subscriptionStartTime)
{
// in some cases we might emit auxiliary data even before our actual start time, which can happen in some cases during warmup
// where previous was initialized through the last point tracker, this point will be filtered out
// but in any other case though let's log it, shouldn't happen
Log.Error("FillForwardEnumerator received data out of order. Symbol: " + previous.Symbol.ID);
}
fillForward = null;
return false;
}
// check to see if the gap between previous and next warrants fill forward behavior
if (!ShouldFillForward(previousTimeUtc, nextTimeUtc, fillForwardResolution))
{
fillForward = null;
return false;
}
// Double check!
// This might be the last FF bar before the next data point, and it might not be to be
// emitted because it will overlap with the next point.
// If the previous point was fill forwarded, its time might have been rounded down,
// we need to compare apples to apples.
// (e.g. daily bars with times != midnight and without strict end times)
var nextPeriod = nextEndTimeUtc - nextTimeUtc;
if (previous.IsFillForward && (!UseStrictEndTime || nextPeriod <= Time.OneHour))
{
var roundedNextTimeUtc = RoundDown(next.Time, nextPeriod).ConvertToUtc(Exchange.TimeZone);
if (!ShouldFillForward(previousTimeUtc, roundedNextTimeUtc, fillForwardResolution))
{
fillForward = null;
return false;
}
}
var period = _dataResolution;
if (UseStrictEndTime)
{
// the period is not the data resolution (1 day) and can actually change dynamically, for example early close/late open
period = next.EndTime - next.Time;
}
else if (next.Time == next.EndTime)
{
// we merge corporate event data points (mapping, delisting, splits, dividend) which do not have
// a period or resolution
period = TimeSpan.Zero;
}
nextCalculatedEndTimeUtc = nextTimeUtc + period;
}
// every bar emitted MUST be of the data resolution.
// compute end times of the four potential fill forward scenarios
// 1. the next fill forward bar. 09:00-10:00 followed by 10:00-11:00 where 01:00 is the fill forward resolution
// 2. the next data resolution bar, same as above but with the data resolution instead
// 3. the next fill forward bar following the next market open, 15:00-16:00 followed by 09:00-10:00 the following open market day
// 4. the next data resolution bar following the next market open, same as above but with the data resolution instead
// the precedence for validation is based on the order of the end times, obviously if a potential match
// is before a later match, the earliest match should win.
foreach (var item in GetSortedReferenceDateIntervals(previous, fillForwardResolution, _dataResolution))
{
// issue GH 4925 , more description https://github.com/QuantConnect/Lean/pull/4941
// To build Time/EndTime we always use '+'/'-' dataResolution
// DataTime TZ = UTC -5; Exchange TZ = America/New York (-5/-4)
// Standard TimeZone 00:00:00 + 1 day = 1.00:00:00
// Daylight Time 01:00:00 + 1 day = 1.01:00:00
// daylight saving time starts/end at 2 a.m. on Sunday
// Having this information we find that the specific bar of Sunday
// Starts in one TZ (Standard TZ), but finishes in another (Daylight TZ) (consider winter => summer)
// During simple arithmetic operations like +/- we shift the time, but not the time zone
// which is sensitive for specific dates (daylight movement) if we are in Exchange TimeZone, for example
// We have 00:00:00 + 1 day = 1.00:00:00, so both are in Standard TZ, but we expect endTime in Daylight, i.e. 1.01:00:00
// futher down double Convert (Exchange TZ => data TZ => Exchange TZ)
// allows us to calculate Time using it's own TZ (aka reapply)
// and don't rely on TZ of bar start/end time
// i.e. 00:00:00 + 1 day = 1.01:00:00, both start and end are in their own TZ
// it's interesting that NodaTime consider next
// if time great or equal than 01:00 AM it's considered as "moved" (Standard, not Daylight)
// when time less than 01:00 AM it's considered as previous TZ (Standard, not Daylight)
// it's easy to fix this behavior by substract 1 tick before first convert, and then return it back.
// so we work with 0:59:59.. AM instead.
// but now follow native behavior
// all above means, that all Time values, calculated using simple +/- operations
// sticks to original Time Zone, swallowing its own TZ and movement i.e.
// EndTime = Time + resolution, both Time and EndTime in the TZ of Time (Standard/Daylight)
// Time = EndTime - resolution, both Time and EndTime in the TZ of EndTime (Standard/Daylight)
// next.EndTime sticks to Time TZ,
// potentialBarEndTime should be calculated in the same way as bar.EndTime, i.e. Time + resolution
// round down doesn't make sense for daily data using strict times
var startTime = (UseStrictEndTime && item.Period > Time.OneHour) ? item.Start : RoundDown(item.Start, item.Period);
var potentialBarEndTime = startTime.ConvertToUtc(Exchange.TimeZone) + item.Period;
// to avoid duality it's necessary to compare potentialBarEndTime with
// next.EndTime calculated as Time + resolution,
// and both should be based on the same TZ (for example UTC)
if (potentialBarEndTime < nextCalculatedEndTimeUtc
// let's fill forward based on previous (which isn't auxiliary) if next is auxiliary and they share the end time
// we do allow emitting both an auxiliary data point and a Filled Forwared data for the same end time
|| next != null && next.DataType == MarketDataType.Auxiliary && potentialBarEndTime == nextCalculatedEndTimeUtc)
{
// to check open hours we need to convert potential
// bar EndTime into exchange time zone
var potentialBarEndTimeInExchangeTZ =
potentialBarEndTime.ConvertFromUtc(Exchange.TimeZone);
var nextFillForwardBarStartTime = potentialBarEndTimeInExchangeTZ - item.Period;
if (Exchange.IsOpenDuringBar(nextFillForwardBarStartTime, potentialBarEndTimeInExchangeTZ, _isExtendedMarketHours))
{
fillForward = previous.Clone(true);
// bar are ALWAYS of the data resolution
var expectedPeriod = _dataResolution;
if (UseStrictEndTime)
{
// TODO: what about extended market hours
// NOTE: Not using Exchange.Hours.RegularMarketDuration so we can handle things like early closes.
// The earliest start time would be endTime - regularMarketDuration,
// we use that as the potential time to get the exchange hours.
// We don't use directly nextFillForwardBarStartTime because there might be cases where there are
// adjacent extended and regular market hours segments that might cause the calendar start to be
// in the previous date, and if it's an extended hours-only date like a Sunday for futures,
// the market duration would be zero.
var marketHoursDateTime = potentialBarEndTimeInExchangeTZ - Exchange.Hours.RegularMarketDuration;
// That potential start is even before the calendar start, so we use the calendar start
if (marketHoursDateTime < item.Start)
{
marketHoursDateTime = item.Start;
}
var marketHours = Exchange.Hours.GetMarketHours(marketHoursDateTime);
expectedPeriod = marketHours.MarketDuration;
}
fillForward.Time = (potentialBarEndTime - expectedPeriod).ConvertFromUtc(Exchange.TimeZone);
fillForward.EndTime = potentialBarEndTimeInExchangeTZ;
return true;
}
}
else
{
break;
}
}
// the next is before the next fill forward time, so do nothing
fillForward = null;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool ShouldFillForward(DateTime previousTimeUtc, DateTime nextTimeUtc, TimeSpan fillForwardResolution)
{
var nextPreviousTimeUtcDelta = nextTimeUtc - previousTimeUtc;
return nextPreviousTimeUtcDelta > fillForwardResolution ||
nextPreviousTimeUtcDelta > _dataResolution ||
// even if there is no gap between the two data points, we still fill forward to ensure a FF bar is emitted at strict end time
_strictEndTimeIntraDayFillForward;
}
private IEnumerable<CalendarInfo> GetSortedReferenceDateIntervals(BaseData previous, TimeSpan fillForwardResolution, TimeSpan dataResolution)
{
if (fillForwardResolution < dataResolution)
{
return GetReferenceDateIntervals(previous.EndTime, fillForwardResolution, dataResolution);
}
if (fillForwardResolution > dataResolution)
{
return GetReferenceDateIntervals(previous.EndTime, dataResolution, fillForwardResolution);
}
return GetReferenceDateIntervals(previous.EndTime, fillForwardResolution);
}
/// <summary>
/// Get potential next fill forward bars.
/// </summary>
/// <remarks>Special case where fill forward resolution and data resolution are equal</remarks>
private IEnumerable<CalendarInfo> GetReferenceDateIntervals(DateTime previousEndTime, TimeSpan resolution)
{
// say daily bar goes from 9:30 to 16:00, if resolution is 1 day, IsOpenDuringBar can return true but it's not what we want
if (!UseStrictEndTime && Exchange.IsOpenDuringBar(previousEndTime, previousEndTime + resolution, _isExtendedMarketHours))
{
// if next in market us it
yield return new (previousEndTime, resolution);
}
if (UseStrictEndTime)
{
// If we're using strict end times for open interest data, for instance, the actual data comes at any time
// but we want to emit a ff point at market close. If extended market hours are enabled, and previousEndTime
// is Thursday after last segment open time, the daily calendar will be for Monday, because a next market open
// won't be found for Friday. So we use the Date of the previousEndTime to get calendar starting that day (Thursday)
// and ending the next one (Friday).
if (_strictEndTimeIntraDayFillForward)
{
var firtMarketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime.Date, _isExtendedMarketHours);
var firstCalendar = LeanData.GetDailyCalendar(firtMarketOpen, Exchange.Hours, false);
if (firstCalendar.End > previousEndTime)
{
yield return firstCalendar;
}
}
// now we can try the bar after next market open
var marketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime, false);
yield return GetDailyCalendar(marketOpen);
}
else
{
// now we can try the bar after next market open
var marketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime, _isExtendedMarketHours);
yield return new(marketOpen, resolution);
}
}
/// <summary>
/// Get potential next fill forward bars.
/// </summary>
private IEnumerable<CalendarInfo> GetReferenceDateIntervals(DateTime previousEndTime, TimeSpan smallerResolution, TimeSpan largerResolution)
{
List<CalendarInfo> result = null;
if (Exchange.IsOpenDuringBar(previousEndTime, previousEndTime + smallerResolution, _isExtendedMarketHours))
{
if (UseStrictEndTime)
{
// case A
result = new()
{
new(previousEndTime, smallerResolution)
};
}
else
{
// at the end of this method we perform an OrderBy which does not apply for this case because the consumer of this method
// will perform a round down that will end up using an unexpected FF bar. This behavior is covered by tests
yield return new (previousEndTime, smallerResolution);
}
}
result ??= new List<CalendarInfo>(4);
// we need to round down because previous end time could be of the smaller resolution, in data TZ!
if (UseStrictEndTime)
{
// case B: say smaller resolution (FF res) is 1 hour, larget resolution (daily data resolution) is 1 day
// For example for SPX we need to emit the daily FF bar from 8:30->15:15, even before the 'A' case above which would be 15->16 bar
var dailyCalendar = GetDailyCalendar(previousEndTime);
if (previousEndTime < (dailyCalendar.Start + dailyCalendar.Period))
{
result.Add(new(dailyCalendar.Start, dailyCalendar.Period));
}
}
else
{
var start = RoundDown(previousEndTime, largerResolution);
if (Exchange.IsOpenDuringBar(start, start + largerResolution, _isExtendedMarketHours))
{
result.Add(new(start, largerResolution));
}
}
// this is typically daily data being filled forward on a higher resolution
// since the previous bar was not in market hours then we can just fast forward
// to the next market open
var marketOpen = Exchange.Hours.GetNextMarketOpen(previousEndTime, _isExtendedMarketHours);
result.Add(new (marketOpen, smallerResolution));
if (UseStrictEndTime)
{
result.Add(GetDailyCalendar(Exchange.Hours.GetNextMarketOpen(previousEndTime, false)));
}
// we need to order them because they might not be in an incremental order and consumer expects them to be
foreach (var referenceDateInterval in result.OrderBy(interval => interval.Start + interval.Period))
{
yield return referenceDateInterval;
}
}
/// <summary>
/// We need to round down in data timezone.
/// For example GH issue 4392: Forex daily data, exchange tz time is 8PM, but time in data tz is 12AM
/// so rounding down on exchange tz will crop it, while rounding on data tz will return the same data point time.
/// Why are we even doing this? being able to determine the next valid data point for a resolution from a data point that might be in another resolution
/// </summary>
private DateTime RoundDown(DateTime value, TimeSpan interval)
{
return value.RoundDownInTimeZone(interval, Exchange.TimeZone, _dataTimeZone);
}
private CalendarInfo GetDailyCalendar(DateTime localReferenceTime)
{
// daily data does not have extended market hours, even if requested
// and it's times are always market hours if using strict end times see 'SetStrictEndTimes'
return LeanData.GetDailyCalendar(localReferenceTime, Exchange.Hours, extendedMarketHours: false);
}
}
}
@@ -0,0 +1,96 @@
/*
* 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;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Enumerator that allow applying a filtering function
/// </summary>
/// <typeparam name="T"></typeparam>
public class FilterEnumerator<T> : IEnumerator<T>
{
private readonly IEnumerator<T> _enumerator;
private readonly Func<T, bool> _filter;
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="enumerator">The underlying enumerator to filter on</param>
/// <param name="filter">The filter to apply</param>
public FilterEnumerator(IEnumerator<T> enumerator, Func<T, bool> filter)
{
_enumerator = enumerator;
_filter = filter;
}
#region Implementation of IDisposable
/// <summary>
/// Disposes the FilterEnumerator
/// </summary>
public void Dispose()
{
_enumerator.Dispose();
}
#endregion
#region Implementation of IEnumerator
/// <summary>
/// Moves the FilterEnumerator to the next item
/// </summary>
public bool MoveNext()
{
// run the enumerator until it passes the specified filter
while (_enumerator.MoveNext())
{
if (_filter(_enumerator.Current))
{
return true;
}
}
return false;
}
/// <summary>
/// Resets the FilterEnumerator
/// </summary>
public void Reset()
{
_enumerator.Reset();
}
/// <summary>
/// Gets the current item in the FilterEnumerator
/// </summary>
public T Current
{
get { return _enumerator.Current; }
}
object IEnumerator.Current
{
get { return _enumerator.Current; }
}
#endregion
}
}
@@ -0,0 +1,149 @@
/*
* 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;
using System.Collections.Generic;
using QuantConnect.Data;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Provides an implementation of <see cref="IEnumerator{BaseData}"/> that will not emit
/// data ahead of the frontier as specified by an instance of <see cref="ITimeProvider"/>.
/// An instance of <see cref="TimeZoneOffsetProvider"/> is used to convert between UTC
/// and the data's native time zone
/// </summary>
public class FrontierAwareEnumerator : IEnumerator<BaseData>
{
private BaseData _current;
private bool _needsMoveNext = true;
private readonly ITimeProvider _timeProvider;
private readonly IEnumerator<BaseData> _enumerator;
private readonly TimeZoneOffsetProvider _offsetProvider;
/// <summary>
/// Initializes a new instance of the <see cref="FrontierAwareEnumerator"/> class
/// </summary>
/// <param name="enumerator">The underlying enumerator to make frontier aware</param>
/// <param name="timeProvider">The time provider used for resolving the current frontier time</param>
/// <param name="offsetProvider">An offset provider used for converting the frontier UTC time into the data's native time zone</param>
public FrontierAwareEnumerator(IEnumerator<BaseData> enumerator, ITimeProvider timeProvider, TimeZoneOffsetProvider offsetProvider)
{
_enumerator = enumerator;
_timeProvider = timeProvider;
_offsetProvider = offsetProvider;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
var underlyingCurrent = _enumerator.Current;
var frontier = _timeProvider.GetUtcNow();
var localFrontier = new DateTime(frontier.Ticks + _offsetProvider.GetOffsetTicks(frontier));
// if we moved next, but didn't emit, check to see if it's time to emit yet
if (!_needsMoveNext && underlyingCurrent != null)
{
if (underlyingCurrent.EndTime <= localFrontier)
{
// we can now emit the underlyingCurrent as part of this time slice
_current = underlyingCurrent;
_needsMoveNext = true;
}
else
{
// it's still not time to emit the underlyingCurrent, keep waiting for time to advance
_current = null;
_needsMoveNext = false;
}
return true;
}
// we've exhausted the underlying enumerator, iteration completed
if (_needsMoveNext && !_enumerator.MoveNext())
{
_needsMoveNext = true;
_current = null;
return false;
}
underlyingCurrent = _enumerator.Current;
if (underlyingCurrent != null && underlyingCurrent.EndTime <= localFrontier)
{
_needsMoveNext = true;
_current = underlyingCurrent;
}
else
{
_current = null;
_needsMoveNext = underlyingCurrent == null;
}
// technically we still need to return true since the iteration is not completed,
// however, Current may be null follow a true result here
return true;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
_enumerator.Reset();
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public BaseData Current
{
get { return _current; }
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_enumerator.Dispose();
}
}
}
@@ -0,0 +1,49 @@
/*
* 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.Auxiliary;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Interface for event providers for new tradable dates
/// </summary>
public interface ITradableDateEventProvider
{
/// <summary>
/// Called each time there is a new tradable day
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New corporate event if any</returns>
IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs);
/// <summary>
/// Initializes the event provider instance
/// </summary>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">The factor file provider to use</param>
/// <param name="mapFileProvider">The <see cref="MapFile"/> provider to use</param>
/// <param name="startTime">Start date for the data request</param>
void Initialize(SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
IMapFileProvider mapFileProvider,
DateTime startTime);
}
}
@@ -0,0 +1,32 @@
/*
* 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;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Interface which will provide an event handler
/// who will be fired with each new tradable day
/// </summary>
public interface ITradableDatesNotifier
{
/// <summary>
/// Event fired when there is a new tradable date
/// </summary>
event EventHandler<NewTradableDateEventArgs> NewTradableDate;
}
}
@@ -0,0 +1,43 @@
/*
* 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 QuantConnect.Data;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Tracks the last data point received by an enumerator.
/// </summary>
public class LastPointTracker
{
private BaseData _lastPoint;
/// <summary>
/// Tracks the last data point received by the enumerator.
/// </summary>
public BaseData LastDataPoint
{
get => _lastPoint;
set
{
if (value != null && !value.IsFillForward && value.DataType != MarketDataType.Auxiliary)
{
_lastPoint = value;
}
}
}
}
}
@@ -0,0 +1,114 @@
/*
* 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 QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Data.Auxiliary;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Auxiliary data enumerator that will trigger new tradable dates event accordingly
/// </summary>
public class LiveAuxiliaryDataEnumerator : AuxiliaryDataEnumerator
{
private DateTime _lastTime;
private ITimeProvider _timeProvider;
private SecurityCache _securityCache;
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">The factor file provider to use</param>
/// <param name="mapFileProvider">The <see cref="MapFile"/> provider to use</param>
/// <param name="tradableDateEventProviders">The tradable dates event providers</param>
/// <param name="startTime">Start date for the data request</param>
/// <param name="timeProvider">The time provider to use</param>
/// <param name="securityCache">The security cache</param>
public LiveAuxiliaryDataEnumerator(SubscriptionDataConfig config, IFactorFileProvider factorFileProvider,
IMapFileProvider mapFileProvider, ITradableDateEventProvider[] tradableDateEventProviders,
DateTime startTime,
ITimeProvider timeProvider,
SecurityCache securityCache)
// tradableDayNotifier: null -> we are going to trigger the new tradables events for the base implementation
: base(config, factorFileProvider, mapFileProvider, tradableDateEventProviders, tradableDayNotifier:null, startTime)
{
_securityCache = securityCache;
_timeProvider = timeProvider;
// initialize providers right away so mapping happens before we subscribe
Initialize();
}
/// <summary>
/// Moves the LiveAuxiliaryDataEnumerator to the next item
/// </summary>
public override bool MoveNext()
{
var currentDate = _timeProvider.GetUtcNow().ConvertFromUtc(Config.ExchangeTimeZone).Add(-Time.LiveAuxiliaryDataOffset).Date;
if (currentDate != _lastTime)
{
// when the date changes for the security we trigger a new tradable date event
var newDayEvent = new NewTradableDateEventArgs(currentDate, _securityCache.GetData(), Config.Symbol, null);
NewTradableDate(this, newDayEvent);
// update last time
_lastTime = currentDate;
}
return base.MoveNext();
}
/// <summary>
/// Helper method to create a new instance.
/// Knows which security types should create one and determines the appropriate delisting event provider to use
/// </summary>
public static bool TryCreate(SubscriptionDataConfig dataConfig, ITimeProvider timeProvider,
SecurityCache securityCache, IMapFileProvider mapFileProvider, IFactorFileProvider fileProvider, DateTime startTime,
out IEnumerator<BaseData> enumerator)
{
enumerator = null;
var securityType = dataConfig.SecurityType;
if (securityType.IsOption() || securityType == SecurityType.Future || securityType == SecurityType.Equity)
{
var providers = new List<ITradableDateEventProvider>
{
securityType == SecurityType.Equity
? new LiveDelistingEventProvider()
: new DelistingEventProvider()
};
if (dataConfig.TickerShouldBeMapped())
{
providers.Add(new LiveMappingEventProvider());
}
if (dataConfig.EmitSplitsAndDividends())
{
providers.Add(new LiveDividendEventProvider());
providers.Add(new LiveSplitEventProvider());
}
enumerator = new LiveAuxiliaryDataEnumerator(dataConfig, fileProvider, mapFileProvider,
providers.ToArray(), startTime, timeProvider, securityCache);
}
return enumerator != null;
}
}
}
@@ -0,0 +1,191 @@
/*
* 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;
using System.Collections.Generic;
using NodaTime;
using QuantConnect.Data;
using QuantConnect.Util;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Represents an enumerator capable of synchronizing live equity data enumerators in time.
/// This assumes that all enumerators have data time stamped in the same time zone.
/// </summary>
public class LiveAuxiliaryDataSynchronizingEnumerator : IEnumerator<BaseData>
{
private readonly ITimeProvider _timeProvider;
private readonly DateTimeZone _exchangeTimeZone;
private readonly List<IEnumerator<BaseData>> _auxDataEnumerators;
private readonly IEnumerator<BaseData> _tradeBarAggregator;
/// <summary>
/// Initializes a new instance of the <see cref="LiveAuxiliaryDataSynchronizingEnumerator"/> class
/// </summary>
/// <param name="timeProvider">The source of time used to gauge when this enumerator should emit extra bars when null data is returned from the source enumerator</param>
/// <param name="exchangeTimeZone">The time zone the raw data is time stamped in</param>
/// <param name="tradeBarAggregator">The trade bar aggregator enumerator</param>
/// <param name="auxDataEnumerators">The auxiliary data enumerators</param>
public LiveAuxiliaryDataSynchronizingEnumerator(ITimeProvider timeProvider, DateTimeZone exchangeTimeZone, IEnumerator<BaseData> tradeBarAggregator, List<IEnumerator<BaseData>> auxDataEnumerators)
{
_timeProvider = timeProvider;
_exchangeTimeZone = exchangeTimeZone;
_auxDataEnumerators = auxDataEnumerators;
_tradeBarAggregator = tradeBarAggregator;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns> true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
public bool MoveNext()
{
// use manual time provider from LiveTradingDataFeed
var frontierUtc = _timeProvider.GetUtcNow();
// check if any enumerator is ready to emit
if (DataPointEmitted(frontierUtc))
return true;
// advance enumerators with no current data
for (var i = 0; i < _auxDataEnumerators.Count; i++)
{
if (_auxDataEnumerators[i].Current == null)
{
_auxDataEnumerators[i].MoveNext();
}
}
if (_tradeBarAggregator.Current == null) _tradeBarAggregator.MoveNext();
// check if any enumerator is ready to emit
if (DataPointEmitted(frontierUtc))
return true;
Current = null;
// IEnumerator contract dictates that we return true unless we're actually
// finished with the 'collection' and since this is live, we're never finished
return true;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
public void Reset()
{
foreach (var auxDataEnumerator in _auxDataEnumerators)
{
auxDataEnumerator.Reset();
}
_tradeBarAggregator.Reset();
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>The element in the collection at the current position of the enumerator.</returns>
public BaseData Current { get; private set; }
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>The current element in the collection.</returns>
object IEnumerator.Current => Current;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
foreach (var auxDataEnumerator in _auxDataEnumerators)
{
auxDataEnumerator.DisposeSafely();
}
_tradeBarAggregator.DisposeSafely();
}
private bool DataPointEmitted(DateTime frontierUtc)
{
// we get the aux enumerator that has the smallest endTime if any
IEnumerator<BaseData> auxDataEnumerator = null;
for (var i = 0; i < _auxDataEnumerators.Count; i++)
{
var currentEnum = _auxDataEnumerators[i];
if (currentEnum.Current != null)
{
if (auxDataEnumerator == null)
{
auxDataEnumerator = currentEnum;
}
else
{
auxDataEnumerator = auxDataEnumerator.Current.EndTime > currentEnum.Current.EndTime ? currentEnum : auxDataEnumerator;
}
}
}
// check if any enumerator is ready to emit
if (auxDataEnumerator?.Current != null && _tradeBarAggregator.Current != null)
{
var auxDataEndTime = auxDataEnumerator.Current.EndTime.ConvertToUtc(_exchangeTimeZone);
var tradeBarEndTime = _tradeBarAggregator.Current.EndTime.ConvertToUtc(_exchangeTimeZone);
if (auxDataEndTime < tradeBarEndTime)
{
if (auxDataEndTime <= frontierUtc)
{
Current = auxDataEnumerator.Current;
auxDataEnumerator.MoveNext();
return true;
}
}
else
{
if (tradeBarEndTime <= frontierUtc)
{
Current = _tradeBarAggregator.Current;
_tradeBarAggregator.MoveNext();
return true;
}
}
}
else if (auxDataEnumerator?.Current != null)
{
var auxDataEndTime = auxDataEnumerator.Current.EndTime.ConvertToUtc(_exchangeTimeZone);
if (auxDataEndTime <= frontierUtc)
{
Current = auxDataEnumerator.Current;
auxDataEnumerator.MoveNext();
return true;
}
}
else if (_tradeBarAggregator.Current != null)
{
var tradeBarEndTime = _tradeBarAggregator.Current.EndTime.ConvertToUtc(_exchangeTimeZone);
if (tradeBarEndTime <= frontierUtc)
{
Current = _tradeBarAggregator.Current;
_tradeBarAggregator.MoveNext();
return true;
}
}
return false;
}
}
}
@@ -0,0 +1,54 @@
/*
* 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.Linq;
using QuantConnect.Data;
using QuantConnect.Logging;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Delisting event provider implementation which will source the delisting date based on new map files
/// </summary>
public class LiveDelistingEventProvider : DelistingEventProvider
{
/// <summary>
/// Check for delistings
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New delisting event if any</returns>
public override IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
var currentInstance = MapFile;
// refresh map file instance
InitializeMapFile();
var newInstance = MapFile;
if (currentInstance?.LastOrDefault()?.Date != newInstance?.LastOrDefault()?.Date)
{
// All future and option contracts sharing the same canonical symbol, share the same configuration too. Thus, in
// order to reduce logs, we log the configuration using the canonical symbol. See the optional parameter
// "overrideMessageFloodProtection" in Log.Trace() method for more information
var symbol = Config.Symbol.HasCanonical() ? Config.Symbol.Canonical.Value : Config.Symbol.Value;
Log.Trace($"LiveDelistingEventProvider({Config.ToString(symbol)}): new tradable date {eventArgs.Date:yyyyMMdd}. " +
$"MapFile.LastDate Old: {currentInstance?.LastOrDefault()?.Date:yyyyMMdd} New: {newInstance?.LastOrDefault()?.Date:yyyyMMdd}");
}
return base.GetEvents(eventArgs);
}
}
}
@@ -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.Linq;
using QuantConnect.Data;
using QuantConnect.Logging;
using QuantConnect.Data.Market;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event provider who will emit <see cref="SymbolChangedEvent"/> events
/// </summary>
/// <remarks>Only special behavior is that it will refresh factor file on each new tradable date event</remarks>
public class LiveDividendEventProvider : DividendEventProvider
{
/// <summary>
/// Check for dividends and returns them
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New Dividend event if any</returns>
public override IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
var currentInstance = FactorFile;
// refresh factor file instance
InitializeFactorFile();
var newInstance = FactorFile;
if (currentInstance?.Count() != newInstance?.Count())
{
// All future and option contracts sharing the same canonical symbol, share the same configuration too. Thus, in
// order to reduce logs, we log the configuration using the canonical symbol. See the optional parameter
// "overrideMessageFloodProtection" in Log.Trace() method for more information
var symbol = Config.Symbol.HasCanonical() ? Config.Symbol.Canonical.Value : Config.Symbol.Value;
Log.Trace($"LiveDividendEventProvider({Config.ToString(symbol)}): new tradable date {eventArgs.Date:yyyyMMdd}. " +
$"New FactorFile: {!ReferenceEquals(currentInstance, newInstance)}. " +
$"FactorFile.Count Old: {currentInstance?.Count()} New: {newInstance?.Count()}");
}
return base.GetEvents(eventArgs);
}
}
}
@@ -0,0 +1,157 @@
/*
* 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.Securities;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// An implementation of the <see cref="FillForwardEnumerator"/> that uses an <see cref="ITimeProvider"/>
/// to determine if a fill forward bar needs to be emitted
/// </summary>
public class LiveFillForwardEnumerator : FillForwardEnumerator
{
private readonly TimeSpan _dataResolution;
private readonly TimeSpan _underlyingTimeout;
private readonly ITimeProvider _timeProvider;
private TimeSpan _marketCloseTimeSpan;
private TimeSpan _marketOpenTimeSpan;
private DateTime _lastDate;
/// <summary>
/// Initializes a new instance of the <see cref="LiveFillForwardEnumerator"/> class that accepts
/// a reference to the fill forward resolution, useful if the fill forward resolution is dynamic
/// and changing as the enumeration progresses
/// </summary>
/// <param name="timeProvider">The source of time used to gauage when this enumerator should emit extra bars when
/// null data is returned from the source enumerator</param>
/// <param name="enumerator">The source enumerator to be filled forward</param>
/// <param name="exchange">The exchange used to determine when to insert fill forward data</param>
/// <param name="fillForwardResolution">The resolution we'd like to receive data on</param>
/// <param name="isExtendedMarketHours">True to use the exchange's extended market hours, false to use the regular market hours</param>
/// <param name="subscriptionStartTime">The start time of the subscription</param>
/// <param name="subscriptionEndTime">The end time of the subscription, once passing this date the enumerator will stop</param>
/// <param name="dataResolution">The source enumerator's data resolution</param>
/// <param name="dataTimeZone">Time zone of the underlying source data</param>
/// <param name="dailyStrictEndTimeEnabled">True if daily strict end times are enabled</param>
/// <param name="dataType">The configuration data type this enumerator is for</param>
/// <param name="lastPointTracker">A reference to the last point emitted before this enumerator is first enumerated</param>
public LiveFillForwardEnumerator(ITimeProvider timeProvider, IEnumerator<BaseData> enumerator, SecurityExchange exchange, IReadOnlyRef<TimeSpan> fillForwardResolution,
bool isExtendedMarketHours, DateTime subscriptionStartTime, DateTime subscriptionEndTime, Resolution dataResolution, DateTimeZone dataTimeZone, bool dailyStrictEndTimeEnabled,
Type dataType = null, LastPointTracker lastPointTracker = null)
: base(enumerator, exchange, fillForwardResolution, isExtendedMarketHours, subscriptionStartTime, subscriptionEndTime, dataResolution.ToTimeSpan(), dataTimeZone,
dailyStrictEndTimeEnabled, dataType, lastPointTracker)
{
_timeProvider = timeProvider;
_dataResolution = dataResolution.ToTimeSpan();
_underlyingTimeout = GetMaximumDataTimeout(dataResolution);
}
/// <summary>
/// Determines whether or not fill forward is required, and if true, will produce the new fill forward data
/// </summary>
/// <param name="fillForwardResolution"></param>
/// <param name="previous">The last piece of data emitted by this enumerator</param>
/// <param name="next">The next piece of data on the source enumerator, this may be null</param>
/// <param name="fillForward">When this function returns true, this will have a non-null value, null when the function returns false</param>
/// <returns>True when a new fill forward piece of data was produced and should be emitted by this enumerator</returns>
protected override bool RequiresFillForwardData(TimeSpan fillForwardResolution, BaseData previous, BaseData next, out BaseData fillForward)
{
if (base.RequiresFillForwardData(fillForwardResolution, previous, next, out fillForward))
{
var underlyingTimeout = TimeSpan.Zero;
if (fillForwardResolution >= _dataResolution && ShouldWaitForData(fillForward))
{
// we enforce the underlying FF timeout when the FF resolution matches it or is bigger, not the other way round, for example:
// this is a daily enumerator and FF resolution is second, we are expected to emit a bar every second, we can't wait until the timeout each time
underlyingTimeout = _underlyingTimeout;
}
var nextEndTimeUtc = (fillForward.EndTime + underlyingTimeout).ConvertToUtc(Exchange.TimeZone);
if (next != null || nextEndTimeUtc <= _timeProvider.GetUtcNow())
{
// we FF if next is here but in the future or next has not come yet and we've wait enough time
return true;
}
}
return false;
}
/// <summary>
/// Helper method to determine if we should wait for data before emitting a fill forward bar.
/// We only wait for data if the fill forward bar is either in the market open or close time.
/// </summary>
private bool ShouldWaitForData(BaseData fillForward)
{
if (fillForward.Symbol.SecurityType != SecurityType.Equity || Exchange.Hours.IsMarketAlwaysOpen)
{
return false;
}
// Update market open and close daily
if (_lastDate != fillForward.EndTime.Date ||
// Update market open and close for days with multiple sessions, e.g. early close and then late open
fillForward.Time.TimeOfDay > _marketCloseTimeSpan)
{
_lastDate = fillForward.EndTime.Date;
var marketOpen = Exchange.Hours.GetNextMarketOpen(_lastDate, false);
var marketClose = Exchange.Hours.GetNextMarketClose(_lastDate, false);
if (_dataResolution == Time.OneHour || (_dataResolution == Time.OneDay && !UseStrictEndTime))
{
marketOpen = marketOpen.RoundDown(_dataResolution);
marketClose = marketClose.RoundUp(_dataResolution);
}
_marketOpenTimeSpan = marketOpen.TimeOfDay;
_marketCloseTimeSpan = marketClose.TimeOfDay;
}
// we only wait for data if the fill forward bar is not in the market open or close time
return fillForward.Time.TimeOfDay == _marketOpenTimeSpan || fillForward.EndTime.TimeOfDay == _marketCloseTimeSpan;
}
/// <summary>
/// Helper method to know how much we should wait before fill forwarding a bar in live trading
/// </summary>
/// <remarks>This allows us to create bars taking into account the market auction close and open official prices. Also it will
/// allow data providers which might have some delay on creating the bars on their end, to be consumed correctly, when available, by Lean</remarks>
public static TimeSpan GetMaximumDataTimeout(Resolution resolution)
{
switch (resolution)
{
case Resolution.Tick:
return TimeSpan.Zero;
case Resolution.Second:
return TimeSpan.FromSeconds(0.9);
case Resolution.Minute:
return TimeSpan.FromMinutes(0.9);
case Resolution.Hour:
return TimeSpan.FromMinutes(10);
case Resolution.Daily:
return TimeSpan.FromMinutes(10);
default:
throw new ArgumentOutOfRangeException(nameof(resolution), resolution, null);
}
}
}
}
@@ -0,0 +1,52 @@
/*
* 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.Linq;
using QuantConnect.Data;
using QuantConnect.Logging;
using QuantConnect.Data.Market;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event provider who will emit <see cref="SymbolChangedEvent"/> events
/// </summary>
/// <remarks>Only special behavior is that it will refresh map file on each new tradable date event</remarks>
public class LiveMappingEventProvider : MappingEventProvider
{
/// <summary>
/// Check for new mappings
/// </summary>
public override IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
var currentInstance = MapFile;
// refresh map file instance
InitializeMapFile();
var newInstance = MapFile;
// All future and option contracts sharing the same canonical symbol, share the same configuration too. Thus, in
// order to reduce logs, we log the configuration using the canonical symbol. See the optional parameter
// "overrideMessageFloodProtection" in Log.Trace() method for more information
var symbol = Config.Symbol.HasCanonical() ? Config.Symbol.Canonical.Value : Config.Symbol.Value;
Log.Trace($"LiveMappingEventProvider({Config.ToString(symbol)}): new tradable date {eventArgs.Date:yyyyMMdd}. " +
$"New MapFile: {!ReferenceEquals(currentInstance, newInstance)}. " +
$"MapFile.Count Old: {currentInstance?.Count()} New: {newInstance?.Count()}");
return base.GetEvents(eventArgs);
}
}
}
@@ -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.Linq;
using QuantConnect.Data;
using QuantConnect.Logging;
using QuantConnect.Data.Market;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event provider who will emit <see cref="SymbolChangedEvent"/> events
/// </summary>
/// <remarks>Only special behavior is that it will refresh factor file on each new tradable date event</remarks>
public class LiveSplitEventProvider : SplitEventProvider
{
/// <summary>
/// Check for dividends and returns them
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New Dividend event if any</returns>
public override IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
var currentInstance = FactorFile;
// refresh factor file instance
InitializeFactorFile();
var newInstance = FactorFile;
if(currentInstance?.Count() != newInstance?.Count())
{
// All future and option contracts sharing the same canonical symbol, share the same configuration too. Thus, in
// order to reduce logs, we log the configuration using the canonical symbol. See the optional parameter
// "overrideMessageFloodProtection" in Log.Trace() method for more information
var symbol = Config.Symbol.HasCanonical() ? Config.Symbol.Canonical.Value : Config.Symbol.Value;
Log.Trace($"LiveSplitEventProvider({Config.ToString(symbol)}): new tradable date {eventArgs.Date:yyyyMMdd}. " +
$"New FactorFile: {!ReferenceEquals(currentInstance, newInstance)}. " +
$"FactorFile.Count Old: {currentInstance?.Count()} New: {newInstance?.Count()}");
}
return base.GetEvents(eventArgs);
}
}
}
@@ -0,0 +1,120 @@
/*
* 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.Util;
using System.Collections;
using QuantConnect.Logging;
using QuantConnect.Interfaces;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Enumerator that will subscribe through the provided data queue handler and refresh the subscription if any mapping occurs
/// </summary>
public class LiveSubscriptionEnumerator : IEnumerator<BaseData>
{
private BaseData _current;
private readonly Symbol _requestedSymbol;
private SubscriptionDataConfig _currentConfig;
private IEnumerator<BaseData> _previousEnumerator;
private IEnumerator<BaseData> _underlyingEnumerator;
/// <summary>
/// The current data object instance
/// </summary>
public BaseData Current => _current;
/// <summary>
/// The current data object instance
/// </summary>
object IEnumerator.Current => Current;
/// <summary>
/// Creates a new instance
/// </summary>
public LiveSubscriptionEnumerator(SubscriptionDataConfig dataConfig, IDataQueueHandler dataQueueHandler, EventHandler handler, Func<SubscriptionDataConfig, bool> isExpired)
{
_requestedSymbol = dataConfig.Symbol;
_underlyingEnumerator = dataQueueHandler.SubscribeWithMapping(dataConfig, handler, isExpired, out _currentConfig);
// for any mapping event we will re subscribe
dataConfig.NewSymbol += (_, _) =>
{
dataQueueHandler.Unsubscribe(_currentConfig);
_previousEnumerator = _underlyingEnumerator;
var oldSymbol = _currentConfig.Symbol;
_underlyingEnumerator = dataQueueHandler.SubscribeWithMapping(dataConfig, handler, isExpired, out _currentConfig);
Log.Trace($"LiveSubscriptionEnumerator({_requestedSymbol}): " +
$"resubscribing old: '{oldSymbol.Value}' new '{_currentConfig.Symbol.Value}'");
};
}
/// <summary>
/// Advances the enumerator to the next element.
/// </summary>
public bool MoveNext()
{
if (_previousEnumerator != null)
{
// if previous is set we dispose of it here since we are the consumers of it
_previousEnumerator.DisposeSafely();
_previousEnumerator = null;
}
var result = _underlyingEnumerator.MoveNext();
if (result)
{
_current = _underlyingEnumerator.Current;
}
else
{
_current = null;
}
if (_current != null && _current.Symbol != _requestedSymbol)
{
// if we've done some mapping at this layer let's clone the underlying and set the requested symbol,
// don't trust the IDQH implementations for data uniqueness, since the configuration could be shared
_current = _current.Clone();
_current.Symbol = _requestedSymbol;
}
return result;
}
/// <summary>
/// Reset the IEnumeration
/// </summary>
public void Reset()
{
_underlyingEnumerator.Reset();
}
/// <summary>
/// Disposes of the used enumerators
/// </summary>
public void Dispose()
{
_previousEnumerator.DisposeSafely();
_underlyingEnumerator.DisposeSafely();
}
}
}
@@ -0,0 +1,102 @@
/*
* 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 QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Data.Auxiliary;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event provider who will emit <see cref="SymbolChangedEvent"/> events
/// </summary>
public class MappingEventProvider : ITradableDateEventProvider
{
private IMapFileProvider _mapFileProvider;
/// <summary>
/// The associated configuration
/// </summary>
protected SubscriptionDataConfig Config { get; private set; }
/// <summary>
/// The current instance being used
/// </summary>
protected MapFile MapFile { get; private set; }
/// <summary>
/// Initializes this instance
/// </summary>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">The factor file provider to use</param>
/// <param name="mapFileProvider">The <see cref="Data.Auxiliary.MapFile"/> provider to use</param>
/// <param name="startTime">Start date for the data request</param>
public virtual void Initialize(
SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
IMapFileProvider mapFileProvider,
DateTime startTime)
{
_mapFileProvider = mapFileProvider;
Config = config;
InitializeMapFile();
if (MapFile.HasData(startTime.Date))
{
// initialize mapped symbol using request start date
Config.MappedSymbol = MapFile.GetMappedSymbol(startTime.Date, Config.MappedSymbol, Config.DataMappingMode);
}
}
/// <summary>
/// Check for new mappings
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New mapping event if any</returns>
public virtual IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
if (Config.Symbol == eventArgs.Symbol
&& MapFile.HasData(eventArgs.Date))
{
var old = Config.MappedSymbol;
var newSymbol = MapFile.GetMappedSymbol(eventArgs.Date, Config.MappedSymbol, Config.DataMappingMode);
Config.MappedSymbol = newSymbol;
// check to see if the symbol was remapped
if (old != Config.MappedSymbol)
{
var changed = new SymbolChangedEvent(
Config.Symbol,
eventArgs.Date,
old,
Config.MappedSymbol);
yield return changed;
}
}
}
/// <summary>
/// Initializes the map file to use
/// </summary>
protected void InitializeMapFile()
{
MapFile = _mapFileProvider.ResolveMapFile(Config);
}
}
}
@@ -0,0 +1,32 @@
/*
* 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;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event args for when a new data point is ready to be emitted
/// </summary>
public class NewDataAvailableEventArgs : EventArgs
{
/// <summary>
/// The new data point
/// </summary>
public IBaseData DataPoint { get; set; }
}
}
@@ -0,0 +1,138 @@
/*
* 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;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Interfaces;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// This enumerator will update the <see cref="SubscriptionDataConfig.PriceScaleFactor"/> when required
/// and adjust the raw <see cref="BaseData"/> prices based on the provided <see cref="SubscriptionDataConfig"/>.
/// Assumes the prices of the provided <see cref="IEnumerator"/> are in raw mode.
/// </summary>
public class PriceScaleFactorEnumerator : IEnumerator<BaseData>
{
private readonly IEnumerator<BaseData> _rawDataEnumerator;
private readonly SubscriptionDataConfig _config;
private readonly IFactorFileProvider _factorFileProvider;
private DateTime _nextTradableDate;
private IFactorProvider _factorFile;
private bool _liveMode;
private DateTime? _endDate;
/// <summary>
/// Explicit interface implementation for <see cref="Current"/>
/// </summary>
object IEnumerator.Current => Current;
/// <summary>
/// Last read <see cref="BaseData"/> object from this type and source
/// </summary>
public BaseData Current
{
get;
private set;
}
/// <summary>
/// Creates a new instance of the <see cref="PriceScaleFactorEnumerator"/>.
/// </summary>
/// <param name="rawDataEnumerator">The underlying raw data enumerator</param>
/// <param name="config">The <see cref="SubscriptionDataConfig"/> to enumerate for.
/// Will determine the <see cref="DataNormalizationMode"/> to use.</param>
/// <param name="factorFileProvider">The <see cref="IFactorFileProvider"/> instance to use</param>
/// <param name="liveMode">True, is this is a live mode data stream</param>
/// <param name="endDate">The enumerator end date</param>
/// <remarks>
/// For <see cref="DataNormalizationMode.ScaledRaw"/> normalization mode,
/// the prices are scaled to the prices on the <paramref name="endDate"/>
/// </remarks>
public PriceScaleFactorEnumerator(
IEnumerator<BaseData> rawDataEnumerator,
SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
bool liveMode = false,
DateTime? endDate = null)
{
_config = config;
_liveMode = liveMode;
_nextTradableDate = DateTime.MinValue;
_rawDataEnumerator = rawDataEnumerator;
_factorFileProvider = factorFileProvider;
_endDate = endDate;
}
/// <summary>
/// Dispose of the underlying enumerator.
/// </summary>
public void Dispose()
{
_rawDataEnumerator.Dispose();
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// True if the enumerator was successfully advanced to the next element;
/// False if the enumerator has passed the end of the collection.
/// </returns>
public bool MoveNext()
{
var underlyingReturnValue = _rawDataEnumerator.MoveNext();
Current = _rawDataEnumerator.Current;
if (underlyingReturnValue
&& Current != null
&& _factorFileProvider != null
&& _config.DataNormalizationMode != DataNormalizationMode.Raw)
{
var priceScaleFrontier = Current.GetUpdatePriceScaleFrontier();
if (priceScaleFrontier >= _nextTradableDate)
{
_factorFile = _factorFileProvider.Get(_config.Symbol);
_config.PriceScaleFactor = _factorFile.GetPriceScale(priceScaleFrontier.Date, _config.DataNormalizationMode, _config.ContractDepthOffset, _config.DataMappingMode, _endDate);
// update factor files every day
_nextTradableDate = priceScaleFrontier.Date.AddDays(1);
if (_liveMode)
{
// in live trading we add a offset to make sure new factor files are available
_nextTradableDate = _nextTradableDate.Add(Time.LiveAuxiliaryDataOffset);
}
}
Current = Current.Normalize(_config.PriceScaleFactor, _config.DataNormalizationMode, _config.SumOfDividends);
}
return underlyingReturnValue;
}
/// <summary>
/// Reset the IEnumeration
/// </summary>
/// <remarks>Not used</remarks>
public void Reset()
{
throw new NotImplementedException("Reset method not implemented. Assumes loop will only be used once.");
}
}
}
@@ -0,0 +1,113 @@
/*
* 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;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Data.Market;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// The QuoteBarFillForwardEnumerator wraps an existing base data enumerator
/// If the current QuoteBar has null Bid and/or Ask bars, it copies them from the previous QuoteBar
/// </summary>
public class QuoteBarFillForwardEnumerator : IEnumerator<BaseData>
{
private QuoteBar _previous;
private readonly IEnumerator<BaseData> _enumerator;
/// <summary>
/// Initializes a new instance of the <see cref="FillForwardEnumerator"/> class
/// </summary>
public QuoteBarFillForwardEnumerator(IEnumerator<BaseData> enumerator)
{
_enumerator = enumerator;
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public BaseData Current
{
get;
private set;
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
object IEnumerator.Current => Current;
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
public bool MoveNext()
{
if (!_enumerator.MoveNext()) return false;
var bar = _enumerator.Current as QuoteBar;
if (bar != null)
{
if (_previous != null)
{
if (bar.Bid == null)
{
bar.Bid = _previous.Bid;
}
if (bar.Ask == null)
{
bar.Ask = _previous.Ask;
}
}
_previous = bar;
}
Current = _enumerator.Current;
return true;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_enumerator.Dispose();
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
public void Reset()
{
_enumerator.Reset();
}
}
}
@@ -0,0 +1,128 @@
/*
* 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;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Provides augmentation of how often an enumerator can be called. Time is measured using
/// an <see cref="ITimeProvider"/> instance and calls to the underlying enumerator are limited
/// to a minimum time between each call.
/// </summary>
public class RateLimitEnumerator<T> : IEnumerator<T>
{
private T _current;
private DateTime _lastCallTime;
private readonly ITimeProvider _timeProvider;
private readonly IEnumerator<T> _enumerator;
private readonly TimeSpan _minimumTimeBetweenCalls;
/// <summary>
/// Initializes a new instance of the <see cref="RateLimitEnumerator{T}"/> class
/// </summary>
/// <param name="enumerator">The underlying enumerator to place rate limits on</param>
/// <param name="timeProvider">Time provider used for determing the time between calls</param>
/// <param name="minimumTimeBetweenCalls">The minimum time allowed between calls to the underlying enumerator</param>
public RateLimitEnumerator(IEnumerator<T> enumerator, ITimeProvider timeProvider, TimeSpan minimumTimeBetweenCalls)
{
_enumerator = enumerator;
_timeProvider = timeProvider;
_minimumTimeBetweenCalls = minimumTimeBetweenCalls;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
// determine time since last successful call, do this on units of the minimum time
// this will give us nice round emit times
var currentTime = _timeProvider.GetUtcNow().RoundDown(_minimumTimeBetweenCalls);
var timeBetweenCalls = currentTime - _lastCallTime;
// if within limits, patch it through to move next
if (timeBetweenCalls >= _minimumTimeBetweenCalls)
{
if (!_enumerator.MoveNext())
{
// our underlying is finished
_current = default(T);
return false;
}
// only update last call time on non rate limited requests
_lastCallTime = currentTime;
_current = _enumerator.Current;
}
else
{
// we've been rate limitted
_current = default(T);
}
return true;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
_enumerator.Reset();
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public T Current
{
get { return _current; }
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return _current; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_enumerator.Dispose();
}
}
}
@@ -0,0 +1,135 @@
/*
* 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 QuantConnect.Util;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Provides an implementation of <see cref="IEnumerator{T}"/> that will
/// always return true via MoveNext.
/// </summary>
/// <typeparam name="T"></typeparam>
public class RefreshEnumerator<T> : IEnumerator<T>
{
private T _current;
private IEnumerator<T> _enumerator;
private readonly Func<IEnumerator<T>> _enumeratorFactory;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshEnumerator{T}"/> class
/// </summary>
/// <param name="enumeratorFactory">Enumerator factory used to regenerate the underlying
/// enumerator when it ends</param>
public RefreshEnumerator(Func<IEnumerator<T>> enumeratorFactory)
{
_enumeratorFactory = enumeratorFactory;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
if (_enumerator == null)
{
_enumerator = _enumeratorFactory.Invoke();
}
var moveNext = false;
try
{
moveNext = _enumerator.MoveNext();
if (moveNext)
{
_current = _enumerator.Current;
}
}
catch (IOException exception)
{
// we will ignore stale file handle exceptions and retry instead, enumerator will be refreshed
if (exception.Message == null || !exception.Message.Contains("Stale file handle", StringComparison.InvariantCultureIgnoreCase))
{
throw;
}
}
if (!moveNext)
{
_enumerator.DisposeSafely();
_enumerator = null;
_current = default(T);
}
return true;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
if (_enumerator != null)
{
_enumerator.Reset();
}
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public T Current
{
get { return _current; }
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
if (_enumerator != null)
{
_enumerator.Dispose();
}
}
}
}
@@ -0,0 +1,174 @@
/*
* 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 NodaTime;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// An implementation of <see cref="IEnumerator{T}"/> that relies on "consolidated" data
/// </summary>
/// <typeparam name="T">The item type yielded by the enumerator</typeparam>
public class ScannableEnumerator<T> : IEnumerator<T> where T : class, IBaseData
{
private T _current;
private bool _consolidated;
private bool _isPeriodBase;
private bool _validateInputType;
private Type _consolidatorInputType;
private readonly DateTimeZone _timeZone;
private readonly ConcurrentQueue<T> _queue;
private readonly ITimeProvider _timeProvider;
private readonly EventHandler _newDataAvailableHandler;
private readonly IDataConsolidator _consolidator;
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public T Current => _current;
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current => Current;
/// <summary>
/// Initializes a new instance of the <see cref="ScannableEnumerator{T}"/> class
/// </summary>
/// <param name="consolidator">Consolidator taking BaseData updates and firing events containing new 'consolidated' data</param>
/// <param name="timeZone">The time zone the raw data is time stamped in</param>
/// <param name="timeProvider">The time provider instance used to determine when bars are completed and can be emitted</param>
/// <param name="newDataAvailableHandler">The event handler for a new available data point</param>
/// <param name="isPeriodBased">The consolidator is period based, this will enable scanning on <see cref="MoveNext"/></param>
public ScannableEnumerator(IDataConsolidator consolidator, DateTimeZone timeZone, ITimeProvider timeProvider, EventHandler newDataAvailableHandler, bool isPeriodBased = true)
{
_timeZone = timeZone;
_timeProvider = timeProvider;
_consolidator = consolidator;
_isPeriodBase = isPeriodBased;
_queue = new ConcurrentQueue<T>();
_consolidatorInputType = consolidator.InputType;
_validateInputType = _consolidatorInputType != typeof(BaseData);
_newDataAvailableHandler = newDataAvailableHandler ?? ((s, e) => { });
_consolidator.DataConsolidated += DataConsolidatedHandler;
}
/// <summary>
/// Updates the consolidator
/// </summary>
/// <param name="data">The data to consolidate</param>
public void Update(T data)
{
// if the input type of the consolidator isn't generic we validate it's correct before sending it in
if (_validateInputType && data.GetType() != _consolidatorInputType)
{
return;
}
if (_isPeriodBase)
{
// we only need to lock if it's period base since the move next call could trigger a scan
lock (_consolidator)
{
_consolidator.Update(data);
}
}
else
{
_consolidator.Update(data);
}
}
/// <summary>
/// Enqueues the new data into this enumerator
/// </summary>
/// <param name="data">The data to be enqueued</param>
private void Enqueue(T data)
{
_queue.Enqueue(data);
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
if (!_queue.TryDequeue(out _current) && _isPeriodBase)
{
_consolidated = false;
lock (_consolidator)
{
// if there is a working bar we will try to pull it out if the time is right, each consolidator knows when it's right
var localTime = _timeProvider.GetUtcNow().ConvertFromUtc(_timeZone);
_consolidator.Scan(localTime);
}
if (_consolidated)
{
_queue.TryDequeue(out _current);
}
}
// even if we don't have data to return, we haven't technically
// passed the end of the collection, so always return true until
// the enumerator is explicitly disposed or ended
return true;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_consolidator.DataConsolidated -= DataConsolidatedHandler;
}
private void DataConsolidatedHandler(object sender, IBaseData data)
{
var dataPoint = data as T;
_consolidated = true;
Enqueue(dataPoint);
_newDataAvailableHandler(sender, new NewDataAvailableEventArgs { DataPoint = dataPoint });
}
}
}
@@ -0,0 +1,197 @@
/*
* 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 System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// This enumerator will filter out data of the underlying enumerator based on a provided schedule.
/// Will respect the schedule above the data, meaning will let older data through if the underlying provides none for the schedule date
/// </summary>
public class ScheduledEnumerator : IEnumerator<BaseData>
{
private readonly IEnumerator<BaseData> _underlyingEnumerator;
private readonly IEnumerator<DateTime> _scheduledTimes;
private readonly ITimeProvider _frontierTimeProvider;
private readonly DateTimeZone _scheduleTimeZone;
private BaseData _underlyingCandidateDataPoint;
private bool _scheduledTimesEnded;
/// <summary>
/// The current data point
/// </summary>
public BaseData Current { get; private set; }
object IEnumerator.Current => Current;
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="underlyingEnumerator">The underlying enumerator to filter</param>
/// <param name="scheduledTimes">The scheduled times to emit new data points</param>
/// <param name="frontierTimeProvider"></param>
/// <param name="scheduleTimeZone"></param>
/// <param name="startTime">the underlying request start time</param>
public ScheduledEnumerator(IEnumerator<BaseData> underlyingEnumerator,
IEnumerable<DateTime> scheduledTimes,
ITimeProvider frontierTimeProvider,
DateTimeZone scheduleTimeZone,
DateTime startTime)
{
_scheduleTimeZone = scheduleTimeZone;
_frontierTimeProvider = frontierTimeProvider;
_underlyingEnumerator = underlyingEnumerator;
_scheduledTimes = scheduledTimes.GetEnumerator();
// move our schedule enumerator to current start time
MoveScheduleForward(startTime);
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns> True if the enumerator was successfully advanced to the next element;
/// false if the enumerator has passed the end of the collection.
/// </returns>
public bool MoveNext()
{
if (_scheduledTimesEnded)
{
Current = null;
return false;
}
// lets get our candidate data point to emit
if (_underlyingCandidateDataPoint == null)
{
if (_underlyingEnumerator.Current != null && _underlyingEnumerator.Current.EndTime <= _scheduledTimes.Current)
{
_underlyingCandidateDataPoint = _underlyingEnumerator.Current;
}
else if (Current != null)
{
// we will keep the last data point, even if we already emitted it, there could be a case where the user has a schedule in a
// period where there's not new data (or it's far in the future) so let's just FF the previous point
_underlyingCandidateDataPoint = Current.Clone(fillForward: true);
}
}
// lets try to get a better candidate
if (_underlyingEnumerator.Current == null
|| _underlyingEnumerator.Current.EndTime < _scheduledTimes.Current)
{
bool pullAgain;
do
{
pullAgain = false;
if (!_underlyingEnumerator.MoveNext())
{
if (_underlyingCandidateDataPoint != null)
{
// if we still have a candidate wait till we emit him before stopping
break;
}
Current = null;
return false;
}
if (_underlyingEnumerator.Current != null)
{
if (_underlyingEnumerator.Current.EndTime <= _scheduledTimes.Current)
{
// lets try again
pullAgain = true;
// we got another data point which is a newer candidate to emit so let use it instead
// and drop the previous
_underlyingCandidateDataPoint = _underlyingEnumerator.Current;
}
else if (_underlyingCandidateDataPoint == null)
{
// this is the first data point we got and it's After our schedule, let's move our schedule forward
_underlyingCandidateDataPoint = _underlyingEnumerator.Current;
MoveScheduleForward();
}
}
} while (pullAgain);
}
if (_underlyingCandidateDataPoint != null
// if we are at or past the schedule time we try to emit, in backtest this emits right away, since time is data driven, in live though
// we don't emit right away because the underlying might provide us with a newer data point
&& _scheduledTimes.Current.ConvertToUtc(_scheduleTimeZone) <= GetUtcNow())
{
Current = _underlyingCandidateDataPoint;
// we align the data endtime with the schedule, we respect the schedule above the data time. In backtesting,
// time is driven by the data, so let's make sure we emit at the scheduled time even if the data is older
Current.EndTime = _scheduledTimes.Current;
if (Current.Time > Current.EndTime)
{
Current.Time = _scheduledTimes.Current;
}
MoveScheduleForward();
_underlyingCandidateDataPoint = null;
return true;
}
Current = null;
return true;
}
/// <summary>
/// Resets the underlying enumerator
/// </summary>
public void Reset()
{
_underlyingEnumerator.Reset();
}
/// <summary>
/// Disposes of the underlying enumerator
/// </summary>
public void Dispose()
{
_scheduledTimes.Dispose();
_underlyingEnumerator.Dispose();
}
/// <summary>
/// Available in live trading only, in backtesting frontier is driven and sycned already by the data itself
/// so we can't hold data here based on it
/// </summary>
private DateTime GetUtcNow()
{
if (_frontierTimeProvider != null)
{
return _frontierTimeProvider.GetUtcNow();
}
return DateTime.MaxValue;
}
private void MoveScheduleForward(DateTime? frontier = null)
{
do
{
_scheduledTimesEnded = !_scheduledTimes.MoveNext();
}
while (!_scheduledTimesEnded && frontier.HasValue && _scheduledTimes.Current < frontier.Value);
}
}
}
@@ -0,0 +1,113 @@
/*
* 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.Util;
using QuantConnect.Data;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Provides an enumerator for sorting collections of <see cref="BaseData"/> objects based on a specified property.
/// The sorting occurs lazily, only when enumeration begins.
/// </summary>
/// <typeparam name="TKey">The type of the key used for sorting.</typeparam>
public sealed class SortEnumerator<TKey> : IEnumerator<BaseData>, IDisposable
{
private readonly IEnumerable<BaseData> _data;
#pragma warning disable CA2213 // call csutom DisposeSafely() in Dispose()
private IEnumerator<BaseData> _sortedEnumerator;
#pragma warning restore CA2213 // call csutom DisposeSafely() in Dispose()
private readonly Func<BaseData, TKey> _keySelector;
/// <summary>
/// Initializes a new instance of the <see cref="SortEnumerator{TKey}"/> class.
/// </summary>
/// <param name="data">The collection of <see cref="BaseData"/> to enumerate over.</param>
/// <param name="keySelector">A function that defines the key to sort by. Defaults to sorting by <see cref="BaseData.EndTime"/>.</param>
public SortEnumerator(IEnumerable<BaseData> data, Func<BaseData, TKey> keySelector = null)
{
_data = data;
_sortedEnumerator = GetSortedData().GetEnumerator();
_keySelector = keySelector ??= baseData => (TKey)(object)baseData.EndTime;
}
/// <summary>
/// Static method to wrap an enumerable with the sort enumerator.
/// </summary>
/// <param name="preSorted">Indicates if the data is pre-sorted.</param>
/// <param name="data">The data to be wrapped into the enumerator.</param>
/// <returns>An enumerator over the <see cref="BaseData"/>.</returns>
public static IEnumerator<BaseData> TryWrapSortEnumerator(bool preSorted, IEnumerable<BaseData> data)
{
return preSorted ? new SortEnumerator<TKey>(data) : data.GetEnumerator();
}
/// <summary>
/// Lazily retrieves the sorted data.
/// </summary>
/// <returns>An enumerable collection of <see cref="BaseData"/>.</returns>
private IEnumerable<BaseData> GetSortedData()
{
foreach (var item in _data.OrderBy(_keySelector))
{
yield return item;
}
}
object IEnumerator.Current => Current;
/// <summary>
/// Gets the current <see cref="BaseData"/> element in the collection.
/// </summary>
public BaseData Current
{
get => _sortedEnumerator.Current;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// <c>true</c> if the enumerator was successfully advanced to the next element;
/// <c>false</c> if the enumerator has passed the end of the collection.
/// </returns>
public bool MoveNext()
{
return _sortedEnumerator.MoveNext();
}
/// <summary>
/// Resets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
public void Reset()
{
_sortedEnumerator = null;
}
/// <summary>
/// Releases all resources used by the <see cref="SortEnumerator{TKey}"/> and suppresses finalization.
/// </summary>
public void Dispose()
{
_sortedEnumerator?.DisposeSafely();
}
}
}
@@ -0,0 +1,121 @@
/*
* 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.Data.Market;
using QuantConnect.Interfaces;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Event provider who will emit <see cref="Split"/> events
/// </summary>
public class SplitEventProvider : ITradableDateEventProvider
{
// we set the split factor when we encounter a split in the factor file
// and on the next trading day we use this data to produce the split instance
private decimal? _splitFactor;
private decimal _referencePrice;
private IFactorFileProvider _factorFileProvider;
private MapFile _mapFile;
/// <summary>
/// The current instance being used
/// </summary>
protected CorporateFactorProvider FactorFile { get; private set; }
/// <summary>
/// The associated configuration
/// </summary>
protected SubscriptionDataConfig Config { get; private set; }
/// <summary>
/// Initializes this instance
/// </summary>
/// <param name="config">The <see cref="SubscriptionDataConfig"/></param>
/// <param name="factorFileProvider">The factor file provider to use</param>
/// <param name="mapFileProvider">The <see cref="Data.Auxiliary.MapFile"/> provider to use</param>
/// <param name="startTime">Start date for the data request</param>
public void Initialize(
SubscriptionDataConfig config,
IFactorFileProvider factorFileProvider,
IMapFileProvider mapFileProvider,
DateTime startTime)
{
Config = config;
_factorFileProvider = factorFileProvider;
_mapFile = mapFileProvider.ResolveMapFile(Config);
InitializeFactorFile();
}
/// <summary>
/// Check for new splits
/// </summary>
/// <param name="eventArgs">The new tradable day event arguments</param>
/// <returns>New split event if any</returns>
public virtual IEnumerable<BaseData> GetEvents(NewTradableDateEventArgs eventArgs)
{
if (Config.Symbol == eventArgs.Symbol
&& FactorFile != null
&& _mapFile.HasData(eventArgs.Date))
{
var factor = _splitFactor;
if (factor != null)
{
var close = _referencePrice;
if (close == 0)
{
throw new InvalidOperationException($"Zero reference price for {Config.Symbol} split at {eventArgs.Date}");
}
_splitFactor = null;
_referencePrice = 0;
yield return new Split(
eventArgs.Symbol,
eventArgs.Date,
close,
factor.Value,
SplitType.SplitOccurred);
}
decimal splitFactor;
decimal referencePrice;
if (FactorFile.HasSplitEventOnNextTradingDay(eventArgs.Date, out splitFactor, out referencePrice))
{
_splitFactor = splitFactor;
_referencePrice = referencePrice;
yield return new Split(
eventArgs.Symbol,
eventArgs.Date,
eventArgs.LastRawPrice ?? 0,
splitFactor,
SplitType.Warning);
}
}
}
/// <summary>
/// Initializes the factor file to use
/// </summary>
protected void InitializeFactorFile()
{
FactorFile = _factorFileProvider.Get(Config.Symbol) as CorporateFactorProvider;
}
}
}
@@ -0,0 +1,95 @@
/*
* 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.Util;
using System.Collections;
using QuantConnect.Securities;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Enumerator that will handle adjusting daily strict end times if appropriate
/// </summary>
public class StrictDailyEndTimesEnumerator : IEnumerator<BaseData>
{
private readonly DateTime _localStartTime;
private readonly SecurityExchangeHours _securityExchange;
private readonly IEnumerator<BaseData> _underlying;
/// <summary>
/// Current value of the enumerator
/// </summary>
public BaseData Current { get; private set; }
object IEnumerator.Current => Current;
/// <summary>
/// Creates a new instance
/// </summary>
public StrictDailyEndTimesEnumerator(IEnumerator<BaseData> underlying, SecurityExchangeHours securityExchangeHours, DateTime localStartTime)
{
_underlying = underlying;
_localStartTime = localStartTime;
_securityExchange = securityExchangeHours;
}
/// <summary>
/// Move to the next date
/// </summary>
public bool MoveNext()
{
Current = null;
bool result;
do
{
result = _underlying.MoveNext();
if (!result || !LeanData.UseDailyStrictEndTimes(_underlying.Current?.GetType()))
{
break;
}
// before setting the strict daily end times, let's clone it because underlying enumerator (SubscriptionDataReader) might be using it
var pontentialNewBar = _underlying.Current.Clone();
if (LeanData.SetStrictEndTimes(pontentialNewBar, _securityExchange) && pontentialNewBar.EndTime >= _localStartTime)
{
Current = pontentialNewBar;
break;
}
}
while (true);
return result;
}
/// <summary>
/// Reset the enumerator
/// </summary>
public void Reset()
{
_underlying.Reset();
}
/// <summary>
/// Dispose the enumerator
/// </summary>
public void Dispose()
{
_underlying.Dispose();
}
}
}
@@ -0,0 +1,106 @@
/*
* 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;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Securities;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// An <see cref="IEnumerator{SubscriptionData}"/> which wraps an existing <see cref="IEnumerator{BaseData}"/>.
/// </summary>
/// <remarks>Using this class is important, versus directly yielding, because we setup the <see cref="Dispose"/> chain</remarks>
public class SubscriptionDataEnumerator : IEnumerator<SubscriptionData>
{
private readonly IEnumerator<BaseData> _enumerator;
private readonly SubscriptionDataConfig _configuration;
private readonly SecurityExchangeHours _exchangeHours;
private readonly TimeZoneOffsetProvider _offsetProvider;
private readonly bool _isUniverse;
private readonly bool _dailyStrictEndTimeEnabled;
object IEnumerator.Current => Current;
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
public SubscriptionData Current { get; private set; }
/// <summary>
/// Creates a new instance
/// </summary>
/// <param name="configuration">The subscription's configuration</param>
/// <param name="exchangeHours">The security's exchange hours</param>
/// <param name="offsetProvider">The subscription's time zone offset provider</param>
/// <param name="enumerator">The underlying data enumerator</param>
/// <param name="isUniverse">The subscription is a universe subscription</param>
/// <returns>A subscription data enumerator</returns>
public SubscriptionDataEnumerator(SubscriptionDataConfig configuration,
SecurityExchangeHours exchangeHours,
TimeZoneOffsetProvider offsetProvider,
IEnumerator<BaseData> enumerator,
bool isUniverse,
bool dailyStrictEndTimeEnabled)
{
_enumerator = enumerator;
_offsetProvider = offsetProvider;
_exchangeHours = exchangeHours;
_configuration = configuration;
_isUniverse = isUniverse;
_dailyStrictEndTimeEnabled = dailyStrictEndTimeEnabled;
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>True if the enumerator was successfully advanced to the next element;
/// False if the enumerator has passed the end of the collection.</returns>
public bool MoveNext()
{
var result = _enumerator.MoveNext();
if (result)
{
// Use our config filter to see if we should emit this
// This currently catches Auxiliary data that we don't want to emit
if (_enumerator.Current != null && !_configuration.ShouldEmitData(_enumerator.Current, _isUniverse))
{
// We shouldn't emit this data, so we will MoveNext() again.
return MoveNext();
}
Current = SubscriptionData.Create(_dailyStrictEndTimeEnabled, _configuration, _exchangeHours, _offsetProvider, _enumerator.Current, _configuration.DataNormalizationMode);
}
return result;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_enumerator.Dispose();
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
public void Reset()
{
_enumerator.Reset();
}
}
}
@@ -0,0 +1,195 @@
/*
* 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;
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Lean.Engine.Results;
using QuantConnect.Logging;
using QuantConnect.Securities;
using QuantConnect.Securities.Interfaces;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Implements a wrapper around a base data enumerator to provide a final filtering step
/// </summary>
public class SubscriptionFilterEnumerator : IEnumerator<BaseData>
{
/// <summary>
/// Fired when there's an error executing a user's data filter
/// </summary>
public event EventHandler<Exception> DataFilterError;
private readonly bool _liveMode;
private readonly Security _security;
private readonly DateTime _endTime;
private readonly bool _extendedMarketHours;
private readonly SecurityExchangeHours _exchangeHours;
private readonly ISecurityDataFilter _dataFilter;
private readonly IEnumerator<BaseData> _enumerator;
/// <summary>
/// Convenience method to wrap the enumerator and attach the data filter event to log and alery users of errors
/// </summary>
/// <param name="resultHandler">Result handler reference used to send errors</param>
/// <param name="enumerator">The source enumerator to be wrapped</param>
/// <param name="security">The security who's data is being enumerated</param>
/// <param name="endTime">The end time of the subscription</param>
/// <param name="extendedMarketHours">True if extended market hours are enabled</param>
/// <param name="liveMode">True if live mode</param>
/// <param name="securityExchangeHours">The security exchange hours instance to use</param>
/// <returns>A new instance of the <see cref="SubscriptionFilterEnumerator"/> class that has had it's <see cref="DataFilterError"/>
/// event subscribed to to send errors to the result handler</returns>
public static SubscriptionFilterEnumerator WrapForDataFeed(IResultHandler resultHandler, IEnumerator<BaseData> enumerator, Security security, DateTime endTime, bool extendedMarketHours, bool liveMode,
SecurityExchangeHours securityExchangeHours)
{
var filter = new SubscriptionFilterEnumerator(enumerator, security, endTime, extendedMarketHours, liveMode, securityExchangeHours);
filter.DataFilterError += (sender, exception) =>
{
Log.Error(exception, "WrapForDataFeed");
resultHandler.RuntimeError("Runtime error applying data filter. Assuming filter pass: " + exception.Message, exception.StackTrace);
};
return filter;
}
/// <summary>
/// Initializes a new instance of the <see cref="SubscriptionFilterEnumerator"/> class
/// </summary>
/// <param name="enumerator">The source enumerator to be wrapped</param>
/// <param name="security">The security containing an exchange and data filter</param>
/// <param name="endTime">The end time of the subscription</param>
/// <param name="extendedMarketHours">True if extended market hours are enabled</param>
/// <param name="liveMode">True if live mode</param>
/// <param name="securityExchangeHours">The security exchange hours instance to use</param>
public SubscriptionFilterEnumerator(IEnumerator<BaseData> enumerator, Security security, DateTime endTime, bool extendedMarketHours, bool liveMode, SecurityExchangeHours securityExchangeHours)
{
_liveMode = liveMode;
_enumerator = enumerator;
_security = security;
_endTime = endTime;
_exchangeHours = securityExchangeHours;
_dataFilter = _security.DataFilter;
_extendedMarketHours = extendedMarketHours;
}
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public BaseData Current
{
get;
private set;
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
object IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public bool MoveNext()
{
while (_enumerator.MoveNext())
{
var current = _enumerator.Current;
if (current != null)
{
try
{
// execute user data filters
if (current.DataType != MarketDataType.Auxiliary && !_dataFilter.Filter(_security, current))
{
continue;
}
}
catch (Exception err)
{
OnDataFilterError(err);
continue;
}
// verify that the bar is within the exchange's market hours
if (current.DataType != MarketDataType.Auxiliary && !_exchangeHours.IsOpen(current.Time, current.EndTime, _extendedMarketHours))
{
if (_liveMode && !current.IsFillForward)
{
// TODO: replace for setting security.RealTimePrice not to modify security cache data directly
_security.SetMarketPrice(current);
}
continue;
}
// make sure we haven't passed the end
if (current.Time > _endTime)
{
return false;
}
}
Current = current;
return true;
}
return false;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
_enumerator.Dispose();
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception><filterpriority>2</filterpriority>
public void Reset()
{
_enumerator.Reset();
}
/// <summary>
/// Event invocated for the <see cref="DataFilterError"/> event
/// </summary>
/// <param name="exception">The exception that was thrown when trying to perform data filtering</param>
private void OnDataFilterError(Exception exception)
{
var handler = DataFilterError;
if (handler != null) handler(this, exception);
}
}
}
@@ -0,0 +1,55 @@
/*
* 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 QuantConnect.Data;
using System;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Represents an enumerator capable of synchronizing other base data enumerators in time.
/// This assumes that all enumerators have data time stamped in the same time zone
/// </summary>
public class SynchronizingBaseDataEnumerator : SynchronizingEnumerator<BaseData>
{
/// <summary>
/// Initializes a new instance of the <see cref="SynchronizingBaseDataEnumerator"/> class
/// </summary>
/// <param name="enumerators">The enumerators to be synchronized. NOTE: Assumes the same time zone for all data</param>
public SynchronizingBaseDataEnumerator(params IEnumerator<BaseData>[] enumerators)
: this((IEnumerable<IEnumerator>)enumerators)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SynchronizingBaseDataEnumerator"/> class
/// </summary>
/// <param name="enumerators">The enumerators to be synchronized. NOTE: Assumes the same time zone for all data</param>
public SynchronizingBaseDataEnumerator(IEnumerable<IEnumerator> enumerators) : base((IEnumerable<IEnumerator<BaseData>>)enumerators)
{
}
/// <summary>
/// Gets the Timestamp for the data
/// </summary>
protected override DateTime GetInstanceTime(BaseData instance)
{
return instance.EndTime;
}
}
}
@@ -0,0 +1,204 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Represents an enumerator capable of synchronizing other enumerators of type T in time.
/// This assumes that all enumerators have data time stamped in the same time zone
/// </summary>
public abstract class SynchronizingEnumerator<T> : IEnumerator<T>
{
private IEnumerator<T> _syncer;
private readonly IEnumerator<T>[] _enumerators;
/// <summary>
/// Gets the Timestamp for the data
/// </summary>
protected abstract DateTime GetInstanceTime(T instance);
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
public T Current
{
get; private set;
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
object IEnumerator.Current
{
get { return Current; }
}
/// <summary>
/// Initializes a new instance of the <see cref="SynchronizingEnumerator{T}"/> class
/// </summary>
/// <param name="enumerators">The enumerators to be synchronized. NOTE: Assumes the same time zone for all data</param>
/// <remark>The type of data we want, for example, <see cref="BaseData"/> or <see cref="Slice"/>, ect...</remark>
protected SynchronizingEnumerator(params IEnumerator<T>[] enumerators)
: this ((IEnumerable<IEnumerator<T>>)enumerators)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SynchronizingEnumerator{T}"/> class
/// </summary>
/// <param name="enumerators">The enumerators to be synchronized. NOTE: Assumes the same time zone for all data</param>
/// <remark>The type of data we want, for example, <see cref="BaseData"/> or <see cref="Slice"/>, ect...</remark>
protected SynchronizingEnumerator(IEnumerable<IEnumerator<T>> enumerators)
{
_enumerators = enumerators.ToArray();
_syncer = GetSynchronizedEnumerator(_enumerators);
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
public bool MoveNext()
{
var moveNext = _syncer.MoveNext();
Current = moveNext ? _syncer.Current : default(T);
return moveNext;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
public void Reset()
{
foreach (var enumerator in _enumerators)
{
enumerator.Reset();
}
// don't call syncer.reset since the impl will just throw
_syncer = GetSynchronizedEnumerator(_enumerators);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
foreach (var enumerator in _enumerators)
{
enumerator.Dispose();
}
_syncer.Dispose();
}
/// <summary>
/// Synchronization system for the enumerator:
/// </summary>
/// <param name="enumerators"></param>
/// <returns></returns>
private IEnumerator<T> GetSynchronizedEnumerator(IEnumerator<T>[] enumerators)
{
return GetBruteForceMethod(enumerators);
}
/// <summary>
/// Brute force implementation for synchronizing the enumerator.
/// Will remove enumerators returning false to the call to MoveNext.
/// Will not remove enumerators with Current Null returning true to the call to MoveNext
/// </summary>
private IEnumerator<T> GetBruteForceMethod(IEnumerator<T>[] enumerators)
{
var ticks = DateTime.MaxValue.Ticks;
var collection = new HashSet<IEnumerator<T>>();
foreach (var enumerator in enumerators)
{
if (enumerator.MoveNext())
{
if (enumerator.Current != null)
{
ticks = Math.Min(ticks, GetInstanceTime(enumerator.Current).Ticks);
}
collection.Add(enumerator);
}
else
{
enumerator.Dispose();
}
}
var frontier = new DateTime(ticks);
var toRemove = new List<IEnumerator<T>>();
while (collection.Count > 0)
{
var nextFrontierTicks = DateTime.MaxValue.Ticks;
foreach (var enumerator in collection)
{
while (enumerator.Current == null || GetInstanceTime(enumerator.Current) <= frontier)
{
if (enumerator.Current != null)
{
yield return enumerator.Current;
}
if (!enumerator.MoveNext())
{
toRemove.Add(enumerator);
break;
}
if (enumerator.Current == null)
{
break;
}
}
if (enumerator.Current != null)
{
nextFrontierTicks = Math.Min(nextFrontierTicks, GetInstanceTime(enumerator.Current).Ticks);
}
}
if (toRemove.Count > 0)
{
foreach (var enumerator in toRemove)
{
collection.Remove(enumerator);
}
toRemove.Clear();
}
frontier = new DateTime(nextFrontierTicks);
if (frontier == DateTime.MaxValue)
{
break;
}
}
}
}
}
@@ -0,0 +1,55 @@
/*
* 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 QuantConnect.Data;
using System;
using System.Collections;
using System.Collections.Generic;
namespace QuantConnect.Lean.Engine.DataFeeds.Enumerators
{
/// <summary>
/// Represents an enumerator capable of synchronizing other slice enumerators in time.
/// This assumes that all enumerators have data time stamped in the same time zone
/// </summary>
public class SynchronizingSliceEnumerator : SynchronizingEnumerator<Slice>
{
/// <summary>
/// Initializes a new instance of the <see cref="SynchronizingSliceEnumerator"/> class
/// </summary>
/// <param name="enumerators">The enumerators to be synchronized. NOTE: Assumes the same time zone for all data</param>
public SynchronizingSliceEnumerator(params IEnumerator<Slice>[] enumerators)
: this((IEnumerable<IEnumerator>)enumerators)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SynchronizingSliceEnumerator"/> class
/// </summary>
/// <param name="enumerators">The enumerators to be synchronized. NOTE: Assumes the same time zone for all data</param>
public SynchronizingSliceEnumerator(IEnumerable<IEnumerator> enumerators) : base((IEnumerable<IEnumerator<Slice>>)enumerators)
{
}
/// <summary>
/// Gets the Timestamp for the data
/// </summary>
protected override DateTime GetInstanceTime(Slice instance)
{
return instance.UtcTime;
}
}
}