/* * 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 { /// /// 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 /// public class SynchronizingEnumerator : IEnumerator { private IEnumerator _syncer; private readonly IEnumerator[] _enumerators; /// /// Gets the element in the collection at the current position of the enumerator. /// /// /// The element in the collection at the current position of the enumerator. /// public BaseData Current { get; private set; } /// /// Gets the current element in the collection. /// /// /// The current element in the collection. /// object IEnumerator.Current { get { return Current; } } /// /// Initializes a new instance of the class /// /// The enumerators to be synchronized. NOTE: Assumes the same time zone for all data public SynchronizingEnumerator(params IEnumerator[] enumerators) : this ((IEnumerable>)enumerators) { } /// /// Initializes a new instance of the class /// /// The enumerators to be synchronized. NOTE: Assumes the same time zone for all data public SynchronizingEnumerator(IEnumerable> enumerators) { _enumerators = enumerators.ToArray(); _syncer = GetSynchronizedEnumerator(_enumerators); } /// /// Advances the enumerator to the next element of the collection. /// /// /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. /// /// The collection was modified after the enumerator was created. public bool MoveNext() { var moveNext = _syncer.MoveNext(); Current = moveNext ? _syncer.Current : null; return moveNext; } /// /// Sets the enumerator to its initial position, which is before the first element in the collection. /// /// The collection was modified after the enumerator was created. public void Reset() { foreach (var enumerator in _enumerators) { enumerator.Reset(); } // don't call syncer.reset since the impl will just throw _syncer = GetSynchronizedEnumerator(_enumerators); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { foreach (var enumerator in _enumerators) { enumerator.Dispose(); } _syncer.Dispose(); } private struct SynchronizedEnumerator : IComparable { public DateTime Time; public IEnumerator Enumerator; public int CompareTo(SynchronizedEnumerator other) { return this.Time.CompareTo(other.Time); } } /// /// Synchronization system for the enumerator: /// /// /// private static IEnumerator GetSynchronizedEnumerator(IEnumerator[] enumerators) { var streamCount = enumerators.Length; if (streamCount < 500) { //Less than 50 streams use the brute force method: return GetBruteForceMethod(enumerators); } //More than 50 streams sort the enumerators before pulling from each: return GetBinarySearchMethod(enumerators); } /// /// Binary search for the enumerator stack synchronization /// /// /// private static IEnumerator GetBinarySearchMethod(IEnumerator[] enumerators) { //Create wrappers for the enumerator stack: var heads = new SynchronizedEnumerator[enumerators.Length]; for (var i = 0; i < enumerators.Length; i++) { heads[i] = new SynchronizedEnumerator() {Enumerator = enumerators[i]}; if (enumerators[i].Current == null) { enumerators[i].MoveNext(); } heads[i].Time = enumerators[i].Current.Time; } //Presort the stack for the first time. Array.Sort(heads); var headCount = heads.Length; while (headCount > 0) { var min = heads[0]; yield return min.Enumerator.Current; if (min.Enumerator.MoveNext()) { var point = min.Enumerator.Current; min.Time = point.Time; var index = Array.BinarySearch(heads, min); if (index < 0) index = ~index; ListInsert(heads, index - 1, min, headCount); } else { min.Time = DateTime.MaxValue; ListInsert(heads, headCount - 1, min, headCount); headCount--; } } } /// /// Shuffle the enumerator position in the list. /// private static void ListInsert(SynchronizedEnumerator[] list, int index, SynchronizedEnumerator t, int headCount) { if (index >= headCount) index = headCount - 1; if (index < 0) index = 0; for (var j = 1; j <= index; j++) list[j - 1] = list[j]; list[index] = t; } /// /// 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 /// private static IEnumerator GetBruteForceMethod(IEnumerator[] enumerators) { var ticks = DateTime.MaxValue.Ticks; var collection = new HashSet>(); foreach (var enumerator in enumerators) { if (enumerator.MoveNext()) { if (enumerator.Current != null) { ticks = Math.Min(ticks, enumerator.Current.EndTime.Ticks); } collection.Add(enumerator); } else { enumerator.Dispose(); } } var frontier = new DateTime(ticks); var toRemove = new List>(); while (collection.Count > 0) { var nextFrontierTicks = DateTime.MaxValue.Ticks; foreach (var enumerator in collection) { while (enumerator.Current == null || enumerator.Current.EndTime <= 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, enumerator.Current.EndTime.Ticks); } } if (toRemove.Count > 0) { foreach (var enumerator in toRemove) { collection.Remove(enumerator); } toRemove.Clear(); } frontier = new DateTime(nextFrontierTicks); if (frontier == DateTime.MaxValue) { break; } } } } }