Lean  $LEAN_TAG$
Slice.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections;
18 using System.Collections.Generic;
19 using System.Linq;
20 using System.Reflection;
25 using QuantConnect.Python;
26 
27 namespace QuantConnect.Data
28 {
29  /// <summary>
30  /// Provides a data structure for all of an algorithm's data at a single time step
31  /// </summary>
32  public class Slice : ExtendedDictionary<dynamic>, IEnumerable<KeyValuePair<Symbol, BaseData>>
33  {
34  private Ticks _ticks;
35  private TradeBars _bars;
36  private QuoteBars _quoteBars;
37  private OptionChains _optionChains;
38  private FuturesChains _futuresChains;
39 
40  // aux data
41  private Splits _splits;
42  private Dividends _dividends;
43  private Delistings _delistings;
44  private SymbolChangedEvents _symbolChangedEvents;
45  private MarginInterestRates _marginInterestRates;
46 
47  // string -> data for non-tick data
48  // string -> list{data} for tick data
49  private Lazy<DataDictionary<SymbolData>> _data;
50  // UnlinkedData -> DataDictonary<UnlinkedData>
51  private Dictionary<Type, object> _dataByType;
52 
53  /// <summary>
54  /// All the data hold in this slice
55  /// </summary>
56  public List<BaseData> AllData { get; private set; }
57 
58  /// <summary>
59  /// Gets the timestamp for this slice of data
60  /// </summary>
61  public DateTime Time
62  {
63  get; private set;
64  }
65 
66  /// <summary>
67  /// Gets the timestamp for this slice of data in UTC
68  /// </summary>
69  public DateTime UtcTime
70  {
71  get; private set;
72  }
73 
74  /// <summary>
75  /// Gets whether or not this slice has data
76  /// </summary>
77  public bool HasData
78  {
79  get; private set;
80  }
81 
82  /// <summary>
83  /// Gets the <see cref="TradeBars"/> for this slice of data
84  /// </summary>
85  public TradeBars Bars
86  {
87  get { return _bars; }
88  }
89 
90  /// <summary>
91  /// Gets the <see cref="QuoteBars"/> for this slice of data
92  /// </summary>
93  public QuoteBars QuoteBars
94  {
95  get { return _quoteBars; }
96  }
97 
98  /// <summary>
99  /// Gets the <see cref="Ticks"/> for this slice of data
100  /// </summary>
101  public Ticks Ticks
102  {
103  get { return _ticks; }
104  }
105 
106  /// <summary>
107  /// Gets the <see cref="OptionChains"/> for this slice of data
108  /// </summary>
110  {
111  get { return _optionChains; }
112  }
113 
114  /// <summary>
115  /// Gets the <see cref="FuturesChains"/> for this slice of data
116  /// </summary>
118  {
119  get { return _futuresChains; }
120  }
121 
122  /// <summary>
123  /// Gets the <see cref="FuturesChains"/> for this slice of data
124  /// </summary>
126  {
127  get { return _futuresChains; }
128  }
129 
130  /// <summary>
131  /// Gets the <see cref="Splits"/> for this slice of data
132  /// </summary>
133  public Splits Splits
134  {
135  get { return _splits; }
136  }
137 
138  /// <summary>
139  /// Gets the <see cref="Dividends"/> for this slice of data
140  /// </summary>
141  public Dividends Dividends
142  {
143  get { return _dividends; }
144  }
145 
146  /// <summary>
147  /// Gets the <see cref="Delistings"/> for this slice of data
148  /// </summary>
149  public Delistings Delistings
150  {
151  get { return _delistings; }
152  }
153 
154  /// <summary>
155  /// Gets the <see cref="Market.SymbolChangedEvents"/> for this slice of data
156  /// </summary>
158  {
159  get { return _symbolChangedEvents; }
160  }
161 
162  /// <summary>
163  /// Gets the <see cref="Market.MarginInterestRates"/> for this slice of data
164  /// </summary>
166  {
167  get { return _marginInterestRates; }
168  }
169 
170  /// <summary>
171  /// Gets the number of symbols held in this slice
172  /// </summary>
173  public virtual int Count
174  {
175  get { return _data.Value.Count; }
176  }
177 
178  /// <summary>
179  /// Gets all the symbols in this slice
180  /// </summary>
181  public virtual IReadOnlyList<Symbol> Keys
182  {
183  get { return new List<Symbol>(_data.Value.Keys); }
184  }
185 
186  /// <summary>
187  /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the Symbol objects of the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
188  /// </summary>
189  /// <returns>
190  /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the Symbol objects of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
191  /// </returns>
192  protected override IEnumerable<Symbol> GetKeys => _data.Value.Keys;
193 
194  /// <summary>
195  /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.
196  /// </summary>
197  /// <returns>
198  /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>.
199  /// </returns>
200  protected override IEnumerable<dynamic> GetValues => GetKeyValuePairEnumerable().Select(data => (dynamic)data.Value);
201 
202  /// <summary>
203  /// Gets a list of all the data in this slice
204  /// </summary>
205  public virtual IReadOnlyList<BaseData> Values
206  {
207  get { return GetKeyValuePairEnumerable().Select(x => x.Value).ToList(); }
208  }
209 
210  /// <summary>
211  /// Initializes a new instance of the <see cref="Slice"/> class, lazily
212  /// instantiating the <see cref="Slice.Bars"/> and <see cref="Slice.Ticks"/>
213  /// collections on demand
214  /// </summary>
215  /// <param name="time">The timestamp for this slice of data</param>
216  /// <param name="data">The raw data in this slice</param>
217  /// <param name="utcTime">The timestamp for this slice of data in UTC</param>
218  public Slice(DateTime time, IEnumerable<BaseData> data, DateTime utcTime)
219  : this(time, data.ToList(), utcTime: utcTime)
220  {
221  }
222 
223  /// <summary>
224  /// Initializes a new instance of the <see cref="Slice"/> class, lazily
225  /// instantiating the <see cref="Slice.Bars"/> and <see cref="Slice.Ticks"/>
226  /// collections on demand
227  /// </summary>
228  /// <param name="time">The timestamp for this slice of data</param>
229  /// <param name="data">The raw data in this slice</param>
230  /// <param name="utcTime">The timestamp for this slice of data in UTC</param>
231  public Slice(DateTime time, List<BaseData> data, DateTime utcTime)
232  : this(time, data, CreateCollection<TradeBars, TradeBar>(time, data),
233  CreateCollection<QuoteBars, QuoteBar>(time, data),
234  CreateTicksCollection(time, data),
235  CreateCollection<OptionChains, OptionChain>(time, data),
236  CreateCollection<FuturesChains, FuturesChain>(time, data),
237  CreateCollection<Splits, Split>(time, data),
238  CreateCollection<Dividends, Dividend>(time, data),
239  CreateCollection<Delistings, Delisting>(time, data),
240  CreateCollection<SymbolChangedEvents, SymbolChangedEvent>(time, data),
241  CreateCollection<MarginInterestRates, MarginInterestRate>(time, data),
242  utcTime: utcTime)
243  {
244  }
245 
246  /// <summary>
247  /// Initializes a new instance used by the <see cref="PythonSlice"/>
248  /// </summary>
249  /// <param name="slice">slice object to wrap</param>
250  /// <remarks>This is required so that python slice enumeration works correctly since it relies on the private <see cref="_data"/> collection</remarks>
251  protected Slice(Slice slice)
252  {
253  Time = slice.Time;
254  UtcTime = slice.UtcTime;
255  AllData = slice.AllData;
256  _dataByType = slice._dataByType;
257 
258  _data = slice._data;
259 
260  HasData = slice.HasData;
261 
262  _ticks = slice._ticks;
263  _bars = slice._bars;
264  _quoteBars = slice._quoteBars;
265  _optionChains = slice._optionChains;
266  _futuresChains = slice._futuresChains;
267 
268  // auxiliary data
269  _splits = slice._splits;
270  _dividends = slice._dividends;
271  _delistings = slice._delistings;
272  _symbolChangedEvents = slice._symbolChangedEvents;
273  _marginInterestRates = slice._marginInterestRates;
274  }
275 
276  /// <summary>
277  /// Initializes a new instance of the <see cref="Slice"/> class
278  /// </summary>
279  /// <param name="time">The timestamp for this slice of data</param>
280  /// <param name="data">The raw data in this slice</param>
281  /// <param name="tradeBars">The trade bars for this slice</param>
282  /// <param name="quoteBars">The quote bars for this slice</param>
283  /// <param name="ticks">This ticks for this slice</param>
284  /// <param name="optionChains">The option chains for this slice</param>
285  /// <param name="futuresChains">The futures chains for this slice</param>
286  /// <param name="splits">The splits for this slice</param>
287  /// <param name="dividends">The dividends for this slice</param>
288  /// <param name="delistings">The delistings for this slice</param>
289  /// <param name="symbolChanges">The symbol changed events for this slice</param>
290  /// <param name="marginInterestRates">The margin interest rates for this slice</param>
291  /// <param name="utcTime">The timestamp for this slice of data in UTC</param>
292  /// <param name="hasData">true if this slice contains data</param>
293  public Slice(DateTime time, List<BaseData> data, TradeBars tradeBars, QuoteBars quoteBars, Ticks ticks, OptionChains optionChains, FuturesChains futuresChains, Splits splits, Dividends dividends, Delistings delistings, SymbolChangedEvents symbolChanges, MarginInterestRates marginInterestRates, DateTime utcTime, bool? hasData = null)
294  {
295  Time = time;
296  UtcTime = utcTime;
297  AllData = data;
298  // market data
299  _data = new Lazy<DataDictionary<SymbolData>>(() => CreateDynamicDataDictionary(AllData));
300 
301  HasData = hasData ?? _data.Value.Count > 0;
302 
303  _ticks = ticks;
304  _bars = tradeBars;
305  _quoteBars = quoteBars;
306  _optionChains = optionChains;
307  _futuresChains = futuresChains;
308 
309  // auxiliary data
310  _splits = splits;
311  _dividends = dividends;
312  _delistings = delistings;
313  _symbolChangedEvents = symbolChanges;
314  _marginInterestRates = marginInterestRates;
315  }
316 
317  /// <summary>
318  /// Gets the data corresponding to the specified symbol. If the requested data
319  /// is of <see cref="MarketDataType.Tick"/>, then a <see cref="List{Tick}"/> will
320  /// be returned, otherwise, it will be the subscribed type, for example, <see cref="TradeBar"/>
321  /// or event <see cref="UnlinkedData"/> for custom data.
322  /// </summary>
323  /// <param name="symbol">The data's symbols</param>
324  /// <returns>The data for the specified symbol</returns>
325  public override dynamic this[Symbol symbol]
326  {
327  get
328  {
329  SymbolData value;
330  if (_data.Value.TryGetValue(symbol, out value))
331  {
332  return value.GetData();
333  }
334  throw new KeyNotFoundException($"'{symbol}' wasn't found in the Slice object, likely because there was no-data at this moment in time and it wasn't possible to fillforward historical data. Please check the data exists before accessing it with data.ContainsKey(\"{symbol}\")");
335  }
336  }
337 
338  /// <summary>
339  /// Gets the <see cref="DataDictionary{T}"/> for all data of the specified type
340  /// </summary>
341  /// <typeparam name="T">The type of data we want, for example, <see cref="TradeBar"/> or <see cref="UnlinkedData"/>, etc...</typeparam>
342  /// <returns>The <see cref="DataDictionary{T}"/> containing the data of the specified type</returns>
344  where T : IBaseData
345  {
346  return GetImpl(typeof(T), this);
347  }
348 
349  /// <summary>
350  /// Gets the data of the specified type.
351  /// </summary>
352  /// <param name="type">The type of data we seek</param>
353  /// <returns>The <see cref="DataDictionary{T}"/> instance for the requested type</returns>
354  public dynamic Get(Type type)
355  {
356  return GetImpl(type, this);
357  }
358 
359  /// <summary>
360  /// Gets the data of the specified type.
361  /// </summary>
362  /// <remarks>Supports both C# and Python use cases</remarks>
363  protected dynamic GetImpl(Type type, Slice instance)
364  {
365  if (type == typeof(Fundamentals))
366  {
367  // backwards compatibility for users doing a get of Fundamentals type
368  type = typeof(FundamentalUniverse);
369  }
370  else if (type == typeof(ETFConstituentData))
371  {
372  // backwards compatibility for users doing a get of ETFConstituentData type
373  type = typeof(ETFConstituentUniverse);
374  }
375 
376  if (instance._dataByType == null)
377  {
378  // for performance we only really create this collection if someone used it
379  instance._dataByType = new Dictionary<Type, object>(1);
380  }
381 
382  object dictionary;
383  if (!instance._dataByType.TryGetValue(type, out dictionary))
384  {
385  var requestedOpenInterest = type == typeof(OpenInterest);
386  if (type == typeof(Tick) || requestedOpenInterest)
387  {
388  var dataDictionaryCache = GenericDataDictionary.Get(type, isPythonData: false);
389  dictionary = Activator.CreateInstance(dataDictionaryCache.GenericType);
390  ((dynamic)dictionary).Time = Time;
391 
392  foreach (var data in instance.Ticks)
393  {
394  var symbol = data.Key;
395  // preserving existing behavior we will return the last data point, users expect a 'DataDictionary<Tick> : IDictionary<Symbol, Tick>'.
396  // openInterest is stored with the Ticks collection
397  var lastDataPoint = data.Value.LastOrDefault(tick => requestedOpenInterest && tick.TickType == TickType.OpenInterest || !requestedOpenInterest && tick.TickType != TickType.OpenInterest);
398  if (lastDataPoint == null)
399  {
400  continue;
401  }
402  dataDictionaryCache.MethodInfo.Invoke(dictionary, new object[] { symbol, lastDataPoint });
403  }
404  }
405  else if (type == typeof(TradeBar))
406  {
407  dictionary = instance.Bars;
408  }
409  else if (type == typeof(QuoteBar))
410  {
411  dictionary = instance.QuoteBars;
412  }
413  else if (type == typeof(Delisting))
414  {
415  dictionary = instance.Delistings;
416  }
417  else if (type == typeof(Split))
418  {
419  dictionary = instance.Splits;
420  }
421  else if (type == typeof(OptionChain))
422  {
423  dictionary = instance.OptionChains;
424  }
425  else if (type == typeof(FuturesChain))
426  {
427  dictionary = instance.FuturesChains;
428  }
429  else if (type == typeof(Dividend))
430  {
431  dictionary = instance.Dividends;
432  }
433  else if (type == typeof(SymbolChangedEvent))
434  {
435  dictionary = instance.SymbolChangedEvents;
436  }
437  else if (type == typeof(MarginInterestRate))
438  {
439  dictionary = instance.MarginInterestRates;
440  }
441  else
442  {
443  var isPythonData = type.IsAssignableTo(typeof(PythonData));
444 
445  var dataDictionaryCache = GenericDataDictionary.Get(type, isPythonData);
446  dictionary = Activator.CreateInstance(dataDictionaryCache.GenericType);
447  ((dynamic)dictionary).Time = Time;
448 
449  foreach (var data in instance._data.Value.Values)
450  {
451  // let's first check custom data, else double check the user isn't requesting auxiliary data we have
452  if (IsDataPointOfType(data.Custom, type, isPythonData))
453  {
454  dataDictionaryCache.MethodInfo.Invoke(dictionary, new object[] { data.Symbol, data.Custom });
455  }
456  else
457  {
458  foreach (var auxiliaryData in data.AuxilliaryData.Where(x => IsDataPointOfType(x, type, isPythonData)))
459  {
460  dataDictionaryCache.MethodInfo.Invoke(dictionary, new object[] { data.Symbol, auxiliaryData });
461  }
462  }
463  }
464  }
465 
466  instance._dataByType[type] = dictionary;
467  }
468  return dictionary;
469  }
470 
471  /// <summary>
472  /// Gets the data of the specified symbol and type.
473  /// </summary>
474  /// <typeparam name="T">The type of data we seek</typeparam>
475  /// <param name="symbol">The specific symbol was seek</param>
476  /// <returns>The data for the requested symbol</returns>
477  public T Get<T>(Symbol symbol)
478  where T : BaseData
479  {
480  return Get<T>()[symbol];
481  }
482 
483  /// <summary>
484  /// Determines whether this instance contains data for the specified symbol
485  /// </summary>
486  /// <param name="symbol">The symbol we seek data for</param>
487  /// <returns>True if this instance contains data for the symbol, false otherwise</returns>
488  public virtual bool ContainsKey(Symbol symbol)
489  {
490  return _data.Value.ContainsKey(symbol);
491  }
492 
493  /// <summary>
494  /// Gets the data associated with the specified symbol
495  /// </summary>
496  /// <param name="symbol">The symbol we want data for</param>
497  /// <param name="data">The data for the specifed symbol, or null if no data was found</param>
498  /// <returns>True if data was found, false otherwise</returns>
499  public override bool TryGetValue(Symbol symbol, out dynamic data)
500  {
501  data = null;
502  SymbolData symbolData;
503  if (_data.Value.TryGetValue(symbol, out symbolData))
504  {
505  data = symbolData.GetData();
506  return data != null;
507  }
508  return false;
509  }
510 
511  /// <summary>
512  /// Merge two slice with same Time
513  /// </summary>
514  /// <param name="inputSlice">slice instance</param>
515  /// <remarks> Will change the input collection for re-use</remarks>
516  public void MergeSlice(Slice inputSlice)
517  {
518  if (UtcTime != inputSlice.UtcTime)
519  {
520  throw new InvalidOperationException($"Slice with time {UtcTime} can't be merged with given slice with different {inputSlice.UtcTime}");
521  }
522 
523  _bars = (TradeBars)UpdateCollection(_bars, inputSlice.Bars);
524  _quoteBars = (QuoteBars)UpdateCollection(_quoteBars, inputSlice.QuoteBars);
525  _ticks = (Ticks)UpdateCollection(_ticks, inputSlice.Ticks);
526  _optionChains = (OptionChains)UpdateCollection(_optionChains, inputSlice.OptionChains);
527  _futuresChains = (FuturesChains)UpdateCollection(_futuresChains, inputSlice.FuturesChains);
528  _splits = (Splits)UpdateCollection(_splits, inputSlice.Splits);
529  _dividends = (Dividends)UpdateCollection(_dividends, inputSlice.Dividends);
530  _delistings = (Delistings)UpdateCollection(_delistings, inputSlice.Delistings);
531  _symbolChangedEvents = (SymbolChangedEvents)UpdateCollection(_symbolChangedEvents, inputSlice.SymbolChangedEvents);
532  _marginInterestRates = (MarginInterestRates)UpdateCollection(_marginInterestRates, inputSlice.MarginInterestRates);
533 
534  if (inputSlice.AllData.Count != 0)
535  {
536  if (AllData.Count == 0)
537  {
538  AllData = inputSlice.AllData;
539  _data = inputSlice._data;
540  }
541  else
542  {
543  // Should keep this._rawDataList last so that selected data points are not overriden
544  // while creating _data
545  inputSlice.AllData.AddRange(AllData);
546  AllData = inputSlice.AllData;
547  _data = new Lazy<DataDictionary<SymbolData>>(() => CreateDynamicDataDictionary(AllData));
548  }
549  }
550  }
551 
552  private static DataDictionary<T> UpdateCollection<T>(DataDictionary<T> baseCollection, DataDictionary<T> inputCollection)
553  {
554  if (baseCollection == null || baseCollection.Count == 0)
555  {
556  return inputCollection;
557  }
558  if (inputCollection?.Count > 0)
559  {
560  foreach (var kvp in inputCollection)
561  {
562  if (!baseCollection.ContainsKey(kvp.Key))
563  {
564  baseCollection.Add(kvp.Key, kvp.Value);
565  }
566  }
567  }
568  return baseCollection;
569  }
570 
571  /// <summary>
572  /// Produces the dynamic data dictionary from the input data
573  /// </summary>
574  private static DataDictionary<SymbolData> CreateDynamicDataDictionary(IEnumerable<BaseData> data)
575  {
576  var allData = new DataDictionary<SymbolData>();
577  foreach (var datum in data)
578  {
579  // we only will cache the default data type to preserve determinism and backwards compatibility
580  if (!SubscriptionManager.IsDefaultDataType(datum))
581  {
582  continue;
583  }
584  SymbolData symbolData;
585  if (!allData.TryGetValue(datum.Symbol, out symbolData))
586  {
587  symbolData = new SymbolData(datum.Symbol);
588  allData[datum.Symbol] = symbolData;
589  }
590 
591  switch (datum.DataType)
592  {
593  case MarketDataType.Base:
594  symbolData.Type = SubscriptionType.Custom;
595  symbolData.Custom = datum;
596  break;
597 
598  case MarketDataType.TradeBar:
599  symbolData.Type = SubscriptionType.TradeBar;
600  symbolData.TradeBar = (TradeBar)datum;
601  break;
602 
603  case MarketDataType.QuoteBar:
604  symbolData.Type = SubscriptionType.QuoteBar;
605  symbolData.QuoteBar = (QuoteBar)datum;
606  break;
607 
608  case MarketDataType.Tick:
609  symbolData.Type = SubscriptionType.Tick;
610  symbolData.Ticks.Add((Tick)datum);
611  break;
612 
613  case MarketDataType.Auxiliary:
614  symbolData.AuxilliaryData.Add(datum);
615  break;
616 
617  default:
618  throw new ArgumentOutOfRangeException();
619  }
620  }
621  return allData;
622  }
623 
624  /// <summary>
625  /// Dynamically produces a <see cref="Ticks"/> data dictionary using the provided data
626  /// </summary>
627  private static Ticks CreateTicksCollection(DateTime time, IEnumerable<BaseData> data)
628  {
629  var ticks = new Ticks(time);
630  foreach (var tick in data.OfType<Tick>())
631  {
632  List<Tick> listTicks;
633  if (!ticks.TryGetValue(tick.Symbol, out listTicks))
634  {
635  ticks[tick.Symbol] = listTicks = new List<Tick>();
636  }
637  listTicks.Add(tick);
638  }
639  return ticks;
640  }
641 
642  /// <summary>
643  /// Dynamically produces a data dictionary for the requested type using the provided data
644  /// </summary>
645  /// <typeparam name="T">The data dictionary type</typeparam>
646  /// <typeparam name="TItem">The item type of the data dictionary</typeparam>
647  /// <param name="time">The current slice time</param>
648  /// <param name="data">The data to create the collection</param>
649  /// <returns>The data dictionary of <typeparamref name="TItem"/> containing all the data of that type in this slice</returns>
650  private static T CreateCollection<T, TItem>(DateTime time, IEnumerable<BaseData> data)
651  where T : DataDictionary<TItem>, new()
652  where TItem : BaseData
653  {
654  var collection = new T
655  {
656 #pragma warning disable 618 // This assignment is left here until the Time property is removed.
657  Time = time
658 #pragma warning restore 618
659  };
660  foreach (var item in data.OfType<TItem>())
661  {
662  collection[item.Symbol] = item;
663  }
664  return collection;
665  }
666 
667  /// <summary>
668  /// Returns an enumerator that iterates through the collection.
669  /// </summary>
670  /// <returns>
671  /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
672  /// </returns>
673  /// <filterpriority>1</filterpriority>
674  public IEnumerator<KeyValuePair<Symbol, BaseData>> GetEnumerator()
675  {
676  return GetKeyValuePairEnumerable().GetEnumerator();
677  }
678 
679  /// <summary>
680  /// Returns an enumerator that iterates through a collection.
681  /// </summary>
682  /// <returns>
683  /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
684  /// </returns>
685  /// <filterpriority>2</filterpriority>
686  IEnumerator IEnumerable.GetEnumerator()
687  {
688  return GetEnumerator();
689  }
690 
691  private IEnumerable<KeyValuePair<Symbol, BaseData>> GetKeyValuePairEnumerable()
692  {
693  // this will not enumerate auxilliary data!
694 
695  foreach (var kvp in _data.Value)
696  {
697  var data = kvp.Value.GetData();
698 
699  var dataPoints = data as IEnumerable<BaseData>;
700  if (dataPoints != null)
701  {
702  foreach (var dataPoint in dataPoints)
703  {
704  yield return new KeyValuePair<Symbol, BaseData>(kvp.Key, dataPoint);
705  }
706  }
707  else if (data != null)
708  {
709  yield return new KeyValuePair<Symbol, BaseData>(kvp.Key, data);
710  }
711  }
712  }
713 
714  /// <summary>
715  /// Determines if the given data point is of a specific type
716  /// </summary>
717  private static bool IsDataPointOfType(BaseData o, Type type, bool isPythonData)
718  {
719  if (o == null)
720  {
721  return false;
722  }
723  if (isPythonData && o is PythonData data)
724  {
725  return data.IsOfType(type);
726  }
727  return o.GetType() == type;
728  }
729 
730  private enum SubscriptionType { TradeBar, QuoteBar, Tick, Custom };
731  private class SymbolData
732  {
733  public SubscriptionType Type;
734  public readonly Symbol Symbol;
735 
736  // data
737  public BaseData Custom;
738  public TradeBar TradeBar;
739  public QuoteBar QuoteBar;
740  public readonly List<Tick> Ticks;
741  public readonly List<BaseData> AuxilliaryData;
742 
743  public SymbolData(Symbol symbol)
744  {
745  Symbol = symbol;
746  Ticks = new List<Tick>();
747  AuxilliaryData = new List<BaseData>();
748  }
749 
750  public dynamic GetData()
751  {
752  switch (Type)
753  {
754  case SubscriptionType.TradeBar:
755  return TradeBar;
756  case SubscriptionType.QuoteBar:
757  return QuoteBar;
758  case SubscriptionType.Tick:
759  return Ticks;
760  case SubscriptionType.Custom:
761  return Custom;
762  default:
763  throw new ArgumentOutOfRangeException();
764  }
765  }
766  }
767 
768  /// <summary>
769  /// Helper class for generic <see cref="DataDictionary{T}"/>
770  /// </summary>
771  /// <remarks>The value of this class is primarily performance since it keeps a cache
772  /// of the generic types instances and there add methods.</remarks>
773  private class GenericDataDictionary
774  {
775  private static Dictionary<Type, GenericDataDictionary> _genericCache = new Dictionary<Type, GenericDataDictionary>();
776 
777  /// <summary>
778  /// The <see cref="DataDictionary{T}.Add(KeyValuePair{QuantConnect.Symbol,T})"/> method
779  /// </summary>
780  public MethodInfo MethodInfo { get; }
781 
782  /// <summary>
783  /// The <see cref="DataDictionary{T}"/> type
784  /// </summary>
785  public Type GenericType { get; }
786 
787  private GenericDataDictionary(Type genericType, MethodInfo methodInfo)
788  {
789  GenericType = genericType;
790  MethodInfo = methodInfo;
791  }
792 
793  /// <summary>
794  /// Provides a <see cref="GenericDataDictionary"/> instance for a given <see cref="Type"/>
795  /// </summary>
796  /// <param name="type">The requested data type</param>
797  /// <param name="isPythonData">True if data is of <see cref="PythonData"/> type</param>
798  /// <returns>A new instance or retrieved from the cache</returns>
799  public static GenericDataDictionary Get(Type type, bool isPythonData)
800  {
801  if (!_genericCache.TryGetValue(type, out var dataDictionaryCache))
802  {
803  var dictionaryType = type;
804  if (isPythonData)
805  {
806  // let's create a python data dictionary because the data itself will be a PythonData type in C#
807  dictionaryType = typeof(PythonData);
808  }
809  var generic = typeof(DataDictionary<>).MakeGenericType(dictionaryType);
810  var method = generic.GetMethod("Add", new[] { typeof(Symbol), dictionaryType });
811 
812  // Replace the cache instance with a new one instead of locking in order to avoid the overhead
813  var temp = new Dictionary<Type, GenericDataDictionary>(_genericCache);
814  temp[type] = dataDictionaryCache = new GenericDataDictionary(generic, method);
815  _genericCache = temp;
816  }
817 
818  return dataDictionaryCache;
819  }
820  }
821  }
822 }