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