Lean  $LEAN_TAG$
TimeSliceFactory.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 
17 using System;
18 using NodaTime;
19 using QuantConnect.Data;
20 using QuantConnect.Logging;
23 using System.Collections.Generic;
25 
27 {
28  /// <summary>
29  /// Instance base class that will provide methods for creating new <see cref="TimeSlice"/>
30  /// </summary>
31  public class TimeSliceFactory
32  {
33  private readonly DateTimeZone _timeZone;
34 
35  // performance: these collections are not always used so keep a reference to an empty
36  // instance to use and avoid unnecessary constructors and allocations
37  private readonly List<UpdateData<ISecurityPrice>> _emptyCustom = new List<UpdateData<ISecurityPrice>>();
38  private readonly TradeBars _emptyTradeBars = new TradeBars();
39  private readonly QuoteBars _emptyQuoteBars = new QuoteBars();
40  private readonly Ticks _emptyTicks = new Ticks();
41  private readonly Splits _emptySplits = new Splits();
42  private readonly Dividends _emptyDividends = new Dividends();
43  private readonly Delistings _emptyDelistings = new Delistings();
44  private readonly OptionChains _emptyOptionChains = new OptionChains();
45  private readonly FuturesChains _emptyFuturesChains = new FuturesChains();
46  private readonly SymbolChangedEvents _emptySymbolChangedEvents = new SymbolChangedEvents();
47  private readonly MarginInterestRates _emptyMarginInterestRates = new MarginInterestRates();
48 
49  /// <summary>
50  /// Creates a new instance
51  /// </summary>
52  /// <param name="timeZone">The time zone required for computing algorithm and slice time</param>
53  public TimeSliceFactory(DateTimeZone timeZone)
54  {
55  _timeZone = timeZone;
56  }
57 
58  /// <summary>
59  /// Creates a new empty <see cref="TimeSlice"/> to be used as a time pulse
60  /// </summary>
61  /// <remarks>The objective of this method is to standardize the time pulse creation</remarks>
62  /// <param name="utcDateTime">The UTC frontier date time</param>
63  /// <returns>A new <see cref="TimeSlice"/> time pulse</returns>
64  public TimeSlice CreateTimePulse(DateTime utcDateTime)
65  {
66  // setting all data collections to null, this time slice shouldn't be used
67  // for its data, we want to see fireworks it someone tries
68  return new TimeSlice(utcDateTime,
69  0,
70  null,
71  null,
72  null,
73  null,
74  null,
76  null,
77  isTimePulse:true);
78  }
79 
80  /// <summary>
81  /// Creates a new <see cref="TimeSlice"/> for the specified time using the specified data
82  /// </summary>
83  /// <param name="utcDateTime">The UTC frontier date time</param>
84  /// <param name="data">The data in this <see cref="TimeSlice"/></param>
85  /// <param name="changes">The new changes that are seen in this time slice as a result of universe selection</param>
86  /// <param name="universeData"></param>
87  /// <returns>A new <see cref="TimeSlice"/> containing the specified data</returns>
88  public TimeSlice Create(DateTime utcDateTime,
89  List<DataFeedPacket> data,
90  SecurityChanges changes,
91  Dictionary<Universe, BaseDataCollection> universeData)
92  {
93  int count = 0;
94  var security = new List<UpdateData<ISecurityPrice>>(data.Count);
95  List<UpdateData<ISecurityPrice>> custom = null;
96  var consolidator = new List<UpdateData<SubscriptionDataConfig>>(data.Count);
97  var allDataForAlgorithm = new List<BaseData>(data.Count);
98  var optionUnderlyingUpdates = new Dictionary<Symbol, BaseData>();
99 
100  Split split;
101  Dividend dividend;
102  Delisting delisting;
103  SymbolChangedEvent symbolChange;
104  MarginInterestRate marginInterestRate;
105 
106  // we need to be able to reference the slice being created in order to define the
107  // evaluation of option price models, so we define a 'future' that can be referenced
108  // in the option price model evaluation delegates for each contract
109  Slice slice = null;
110  var sliceFuture = new Lazy<Slice>(() => slice);
111 
112  var algorithmTime = utcDateTime.ConvertFromUtc(_timeZone);
113  TradeBars tradeBars = null;
114  QuoteBars quoteBars = null;
115  Ticks ticks = null;
116  Splits splits = null;
117  Dividends dividends = null;
118  Delistings delistings = null;
119  OptionChains optionChains = null;
120  FuturesChains futuresChains = null;
121  SymbolChangedEvents symbolChanges = null;
122  MarginInterestRates marginInterestRates = null;
123 
124  UpdateEmptyCollections(algorithmTime);
125 
126  if (universeData.Count > 0)
127  {
128  // count universe data
129  foreach (var kvp in universeData)
130  {
131  count += kvp.Value.Data.Count;
132  }
133  }
134 
135  // ensure we read equity data before option data, so we can set the current underlying price
136  foreach (var packet in data)
137  {
138  // filter out packets for removed subscriptions
139  if (packet.IsSubscriptionRemoved)
140  {
141  continue;
142  }
143 
144  var list = packet.Data;
145  var symbol = packet.Configuration.Symbol;
146 
147  if (list.Count == 0) continue;
148 
149  // keep count of all data points
150  if (list.Count == 1 && list[0] is BaseDataCollection)
151  {
152  var baseDataCollectionCount = ((BaseDataCollection)list[0]).Data.Count;
153  if (baseDataCollectionCount == 0)
154  {
155  continue;
156  }
157  count += baseDataCollectionCount;
158  }
159  else
160  {
161  count += list.Count;
162  }
163 
164  if (!packet.Configuration.IsInternalFeed && packet.Configuration.IsCustomData)
165  {
166  if (custom == null)
167  {
168  custom = new List<UpdateData<ISecurityPrice>>(1);
169  }
170  // This is all the custom data
171  custom.Add(new UpdateData<ISecurityPrice>(packet.Security, packet.Configuration.Type, list, packet.Configuration.IsInternalFeed));
172  }
173 
174  var securityUpdate = new List<BaseData>(list.Count);
175  var consolidatorUpdate = new List<BaseData>(list.Count);
176  var containsFillForwardData = false;
177  for (var i = 0; i < list.Count; i++)
178  {
179  var baseData = list[i];
180  if (!packet.Configuration.IsInternalFeed)
181  {
182  // this is all the data that goes into the algorithm
183  allDataForAlgorithm.Add(baseData);
184  }
185 
186  containsFillForwardData |= baseData.IsFillForward;
187 
188  // don't add internal feed data to ticks/bars objects
189  if (baseData.DataType != MarketDataType.Auxiliary)
190  {
191  var tick = baseData as Tick;
192 
193  if (!packet.Configuration.IsInternalFeed)
194  {
195  // populate data dictionaries
196  switch (baseData.DataType)
197  {
198  case MarketDataType.Tick:
199  if (ticks == null)
200  {
201  ticks = new Ticks(algorithmTime);
202  }
203  ticks.Add(baseData.Symbol, (Tick)baseData);
204  break;
205 
206  case MarketDataType.TradeBar:
207  if (tradeBars == null)
208  {
209  tradeBars = new TradeBars(algorithmTime);
210  }
211 
212  var newTradeBar = (TradeBar)baseData;
213  TradeBar existingTradeBar;
214  // if we have an existing bar keep the highest resolution one
215  // e.g Hour and Minute resolution subscriptions for the same symbol
216  // see CustomUniverseWithBenchmarkRegressionAlgorithm
217  if (!tradeBars.TryGetValue(baseData.Symbol, out existingTradeBar)
218  || existingTradeBar.Period > newTradeBar.Period)
219  {
220  tradeBars[baseData.Symbol] = newTradeBar;
221  }
222  break;
223 
224  case MarketDataType.QuoteBar:
225  if (quoteBars == null)
226  {
227  quoteBars = new QuoteBars(algorithmTime);
228  }
229 
230  var newQuoteBar = (QuoteBar)baseData;
231  QuoteBar existingQuoteBar;
232  // if we have an existing bar keep the highest resolution one
233  // e.g Hour and Minute resolution subscriptions for the same symbol
234  // see CustomUniverseWithBenchmarkRegressionAlgorithm
235  if (!quoteBars.TryGetValue(baseData.Symbol, out existingQuoteBar)
236  || existingQuoteBar.Period > newQuoteBar.Period)
237  {
238  quoteBars[baseData.Symbol] = newQuoteBar;
239  }
240  break;
241 
242  case MarketDataType.OptionChain:
243  if (optionChains == null)
244  {
245  optionChains = new OptionChains(algorithmTime);
246  }
247  optionChains[baseData.Symbol] = (OptionChain)baseData;
248  break;
249 
250  case MarketDataType.FuturesChain:
251  if (futuresChains == null)
252  {
253  futuresChains = new FuturesChains(algorithmTime);
254  }
255  futuresChains[baseData.Symbol] = (FuturesChain)baseData;
256  break;
257  }
258 
259  // this is data used to update consolidators
260  // do not add it if it is a Suspicious tick
261  if (tick == null || !tick.Suspicious)
262  {
263  consolidatorUpdate.Add(baseData);
264  }
265  }
266 
267  // special handling of options data to build the option chain
268  if (symbol.SecurityType.IsOption())
269  {
270  // internal feeds, like open interest, will not create the chain but will update it if it exists
271  // this is because the open interest could arrive at some closed market hours in which there is no other data and we don't
272  // want to generate a chain object in this case
273  if (optionChains == null && !packet.Configuration.IsInternalFeed)
274  {
275  optionChains = new OptionChains(algorithmTime);
276  }
277 
278  if (baseData.DataType == MarketDataType.OptionChain)
279  {
280  optionChains[baseData.Symbol] = (OptionChain)baseData;
281  }
282  else if (optionChains != null && !HandleOptionData(algorithmTime, baseData, optionChains, packet.Security, sliceFuture, optionUnderlyingUpdates))
283  {
284  continue;
285  }
286  }
287 
288  // special handling of futures data to build the futures chain. Don't push canonical continuous contract
289  // We don't push internal feeds because it could be a continuous mapping future not part of the requested chain
290  if (symbol.SecurityType == SecurityType.Future && !symbol.IsCanonical() && !packet.Configuration.IsInternalFeed)
291  {
292  if (futuresChains == null)
293  {
294  futuresChains = new FuturesChains(algorithmTime);
295  }
296  if (baseData.DataType == MarketDataType.FuturesChain)
297  {
298  futuresChains[baseData.Symbol] = (FuturesChain)baseData;
299  }
300  else if (futuresChains != null && !HandleFuturesData(algorithmTime, baseData, futuresChains, packet.Security))
301  {
302  continue;
303  }
304  }
305 
306  // this is the data used set market prices
307  // do not add it if it is a Suspicious tick
308  if (tick != null && tick.Suspicious) continue;
309 
310  securityUpdate.Add(baseData);
311 
312  // option underlying security update
313  if (!packet.Configuration.IsInternalFeed)
314  {
315  optionUnderlyingUpdates[symbol] = baseData;
316  }
317  }
318  else if (!packet.Configuration.IsInternalFeed)
319  {
320  // include checks for various aux types so we don't have to construct the dictionaries in Slice
321  if ((delisting = baseData as Delisting) != null)
322  {
323  if (delistings == null)
324  {
325  delistings = new Delistings(algorithmTime);
326  }
327  delistings[symbol] = delisting;
328  }
329  else if ((dividend = baseData as Dividend) != null)
330  {
331  if (dividends == null)
332  {
333  dividends = new Dividends(algorithmTime);
334  }
335  dividends[symbol] = dividend;
336  }
337  else if ((split = baseData as Split) != null)
338  {
339  if (splits == null)
340  {
341  splits = new Splits(algorithmTime);
342  }
343  splits[symbol] = split;
344  }
345  else if ((symbolChange = baseData as SymbolChangedEvent) != null)
346  {
347  if (symbolChanges == null)
348  {
349  symbolChanges = new SymbolChangedEvents(algorithmTime);
350  }
351  // symbol changes is keyed by the requested symbol
352  symbolChanges[packet.Configuration.Symbol] = symbolChange;
353  }
354  else if ((marginInterestRate = baseData as MarginInterestRate) != null)
355  {
356  if (marginInterestRates == null)
357  {
358  marginInterestRates = new MarginInterestRates(algorithmTime);
359  }
360  marginInterestRates[packet.Configuration.Symbol] = marginInterestRate;
361  }
362 
363  // let's make it available to the user through the cache
364  security.Add(new UpdateData<ISecurityPrice>(packet.Security, baseData.GetType(), new List<BaseData> { baseData }, packet.Configuration.IsInternalFeed, baseData.IsFillForward));
365  }
366  }
367 
368  if (securityUpdate.Count > 0)
369  {
370  security.Add(new UpdateData<ISecurityPrice>(packet.Security, packet.Configuration.Type, securityUpdate, packet.Configuration.IsInternalFeed, containsFillForwardData));
371  }
372  if (consolidatorUpdate.Count > 0)
373  {
374  consolidator.Add(new UpdateData<SubscriptionDataConfig>(packet.Configuration, packet.Configuration.Type, consolidatorUpdate, packet.Configuration.IsInternalFeed, containsFillForwardData));
375  }
376  }
377 
378  slice = new Slice(algorithmTime, allDataForAlgorithm, tradeBars ?? _emptyTradeBars, quoteBars ?? _emptyQuoteBars, ticks ?? _emptyTicks, optionChains ?? _emptyOptionChains, futuresChains ?? _emptyFuturesChains, splits ?? _emptySplits, dividends ?? _emptyDividends, delistings ?? _emptyDelistings, symbolChanges ?? _emptySymbolChangedEvents, marginInterestRates ?? _emptyMarginInterestRates, utcDateTime, allDataForAlgorithm.Count > 0);
379 
380  return new TimeSlice(utcDateTime, count, slice, data, security, consolidator, custom ?? _emptyCustom, changes, universeData);
381  }
382 
383  private void UpdateEmptyCollections(DateTime algorithmTime)
384  {
385  // just in case
386  _emptyTradeBars.Clear();
387  _emptyQuoteBars.Clear();
388  _emptyTicks.Clear();
389  _emptySplits.Clear();
390  _emptyDividends.Clear();
391  _emptyDelistings.Clear();
392  _emptyOptionChains.Clear();
393  _emptyFuturesChains.Clear();
394  _emptySymbolChangedEvents.Clear();
395  _emptyMarginInterestRates.Clear();
396 
397 #pragma warning disable 0618 // DataDictionary.Time is deprecated, ignore until removed entirely
398  _emptyTradeBars.Time
399  = _emptyQuoteBars.Time
400  = _emptyTicks.Time
401  = _emptySplits.Time
402  = _emptyDividends.Time
403  = _emptyDelistings.Time
404  = _emptyOptionChains.Time
405  = _emptyFuturesChains.Time
406  = _emptySymbolChangedEvents.Time
407  = _emptyMarginInterestRates.Time = algorithmTime;
408 #pragma warning restore 0618
409  }
410 
411  private bool HandleOptionData(DateTime algorithmTime, BaseData baseData, OptionChains optionChains, ISecurityPrice security, Lazy<Slice> sliceFuture, IReadOnlyDictionary<Symbol, BaseData> optionUnderlyingUpdates)
412  {
413  var symbol = baseData.Symbol;
414 
415  OptionChain chain;
416  var canonical = symbol.Canonical;
417  if (!optionChains.TryGetValue(canonical, out chain))
418  {
419  chain = new OptionChain(canonical, algorithmTime);
420  optionChains[canonical] = chain;
421  }
422 
423  // set the underlying current data point in the option chain
424  var option = security as IOptionPrice;
425  if (option != null)
426  {
427  if (option.Underlying == null)
428  {
429  Log.Error($"TimeSlice.HandleOptionData(): {algorithmTime}: Option underlying is null");
430  return false;
431  }
432 
433  BaseData underlyingData;
434  if (!optionUnderlyingUpdates.TryGetValue(option.Underlying.Symbol, out underlyingData))
435  {
436  underlyingData = option.Underlying.GetLastData();
437  }
438 
439  if (underlyingData == null)
440  {
441  Log.Error($"TimeSlice.HandleOptionData(): {algorithmTime}: Option underlying GetLastData returned null");
442  return false;
443  }
444  chain.Underlying = underlyingData;
445  }
446 
447  var universeData = baseData as BaseDataCollection;
448  if (universeData != null)
449  {
450  if (universeData.Underlying != null)
451  {
452  foreach (var addedContract in chain.Contracts)
453  {
454  addedContract.Value.UnderlyingLastPrice = chain.Underlying.Price;
455  }
456  }
457  foreach (var contractSymbol in universeData.FilteredContracts)
458  {
459  chain.FilteredContracts.Add(contractSymbol);
460  }
461  return false;
462  }
463 
464  OptionContract contract;
465  if (!chain.Contracts.TryGetValue(baseData.Symbol, out contract))
466  {
467  contract = OptionContract.Create(baseData, security, chain.Underlying.Price);
468 
469  chain.Contracts[baseData.Symbol] = contract;
470 
471  if (option != null)
472  {
473  contract.SetOptionPriceModel(() => option.EvaluatePriceModel(sliceFuture.Value, contract));
474  }
475  }
476 
477  // populate ticks and tradebars dictionaries with no aux data
478  switch (baseData.DataType)
479  {
480  case MarketDataType.Tick:
481  var tick = (Tick)baseData;
482  chain.Ticks.Add(tick.Symbol, tick);
483  UpdateContract(contract, tick);
484  break;
485 
486  case MarketDataType.TradeBar:
487  var tradeBar = (TradeBar)baseData;
488  chain.TradeBars[symbol] = tradeBar;
489  UpdateContract(contract, tradeBar);
490  break;
491 
492  case MarketDataType.QuoteBar:
493  var quote = (QuoteBar)baseData;
494  chain.QuoteBars[symbol] = quote;
495  UpdateContract(contract, quote);
496  break;
497 
498  case MarketDataType.Base:
499  chain.AddAuxData(baseData);
500  break;
501  }
502  return true;
503  }
504 
505 
506  private bool HandleFuturesData(DateTime algorithmTime, BaseData baseData, FuturesChains futuresChains, ISecurityPrice security)
507  {
508  var symbol = baseData.Symbol;
509 
510  FuturesChain chain;
511  var canonical = symbol.Canonical;
512  if (!futuresChains.TryGetValue(canonical, out chain))
513  {
514  chain = new FuturesChain(canonical, algorithmTime);
515  futuresChains[canonical] = chain;
516  }
517 
518  var universeData = baseData as BaseDataCollection;
519  if (universeData != null)
520  {
521  foreach (var contractSymbol in universeData.FilteredContracts)
522  {
523  chain.FilteredContracts.Add(contractSymbol);
524  }
525  return false;
526  }
527 
528  FuturesContract contract;
529  if (!chain.Contracts.TryGetValue(baseData.Symbol, out contract))
530  {
531  var underlyingSymbol = baseData.Symbol.Underlying;
532  contract = new FuturesContract(baseData.Symbol, underlyingSymbol)
533  {
534  Time = baseData.EndTime,
535  LastPrice = security.Close,
536  Volume = (long)security.Volume,
537  BidPrice = security.BidPrice,
538  BidSize = (long)security.BidSize,
539  AskPrice = security.AskPrice,
540  AskSize = (long)security.AskSize,
541  OpenInterest = security.OpenInterest
542  };
543  chain.Contracts[baseData.Symbol] = contract;
544  }
545 
546  // populate ticks and tradebars dictionaries with no aux data
547  switch (baseData.DataType)
548  {
549  case MarketDataType.Tick:
550  var tick = (Tick)baseData;
551  chain.Ticks.Add(tick.Symbol, tick);
552  UpdateContract(contract, tick);
553  break;
554 
555  case MarketDataType.TradeBar:
556  var tradeBar = (TradeBar)baseData;
557  chain.TradeBars[symbol] = tradeBar;
558  UpdateContract(contract, tradeBar);
559  break;
560 
561  case MarketDataType.QuoteBar:
562  var quote = (QuoteBar)baseData;
563  chain.QuoteBars[symbol] = quote;
564  UpdateContract(contract, quote);
565  break;
566 
567  case MarketDataType.Base:
568  chain.AddAuxData(baseData);
569  break;
570  }
571  return true;
572  }
573 
574  private static void UpdateContract(OptionContract contract, QuoteBar quote)
575  {
576  if (quote.Ask != null && quote.Ask.Close != 0m)
577  {
578  contract.AskPrice = quote.Ask.Close;
579  contract.AskSize = (long)quote.LastAskSize;
580  }
581  if (quote.Bid != null && quote.Bid.Close != 0m)
582  {
583  contract.BidPrice = quote.Bid.Close;
584  contract.BidSize = (long)quote.LastBidSize;
585  }
586  }
587 
588  private static void UpdateContract(OptionContract contract, Tick tick)
589  {
590  if (tick.TickType == TickType.Trade)
591  {
592  contract.LastPrice = tick.Price;
593  }
594  else if (tick.TickType == TickType.Quote)
595  {
596  if (tick.AskPrice != 0m)
597  {
598  contract.AskPrice = tick.AskPrice;
599  contract.AskSize = (long)tick.AskSize;
600  }
601  if (tick.BidPrice != 0m)
602  {
603  contract.BidPrice = tick.BidPrice;
604  contract.BidSize = (long)tick.BidSize;
605  }
606  }
607  else if (tick.TickType == TickType.OpenInterest)
608  {
609  if (tick.Value != 0m)
610  {
611  contract.OpenInterest = tick.Value;
612  }
613  }
614  }
615 
616  private static void UpdateContract(OptionContract contract, TradeBar tradeBar)
617  {
618  if (tradeBar.Close == 0m) return;
619  contract.LastPrice = tradeBar.Close;
620  contract.Volume = (long)tradeBar.Volume;
621  }
622 
623  private static void UpdateContract(FuturesContract contract, QuoteBar quote)
624  {
625  if (quote.Ask != null && quote.Ask.Close != 0m)
626  {
627  contract.AskPrice = quote.Ask.Close;
628  contract.AskSize = (long)quote.LastAskSize;
629  }
630  if (quote.Bid != null && quote.Bid.Close != 0m)
631  {
632  contract.BidPrice = quote.Bid.Close;
633  contract.BidSize = (long)quote.LastBidSize;
634  }
635  }
636 
637  private static void UpdateContract(FuturesContract contract, Tick tick)
638  {
639  if (tick.TickType == TickType.Trade)
640  {
641  contract.LastPrice = tick.Price;
642  }
643  else if (tick.TickType == TickType.Quote)
644  {
645  if (tick.AskPrice != 0m)
646  {
647  contract.AskPrice = tick.AskPrice;
648  contract.AskSize = (long)tick.AskSize;
649  }
650  if (tick.BidPrice != 0m)
651  {
652  contract.BidPrice = tick.BidPrice;
653  contract.BidSize = (long)tick.BidSize;
654  }
655  }
656  else if (tick.TickType == TickType.OpenInterest)
657  {
658  if (tick.Value != 0m)
659  {
660  contract.OpenInterest = tick.Value;
661  }
662  }
663  }
664 
665  private static void UpdateContract(FuturesContract contract, TradeBar tradeBar)
666  {
667  if (tradeBar.Close == 0m) return;
668  contract.LastPrice = tradeBar.Close;
669  contract.Volume = (long)tradeBar.Volume;
670  }
671  }
672 }