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  // We emit aux data for non internal subscriptions only, except for delistings which are required in case
320  // of holdings in the algorithm that may require liquidation, or just for marking the security as delisted and not tradable
321  else if ((delisting = baseData as Delisting) != null || !packet.Configuration.IsInternalFeed)
322  {
323  // include checks for various aux types so we don't have to construct the dictionaries in Slice
324  if (delisting != null)
325  {
326  if (delistings == null)
327  {
328  delistings = new Delistings(algorithmTime);
329  }
330  delistings[symbol] = delisting;
331  }
332  else if ((dividend = baseData as Dividend) != null)
333  {
334  if (dividends == null)
335  {
336  dividends = new Dividends(algorithmTime);
337  }
338  dividends[symbol] = dividend;
339  }
340  else if ((split = baseData as Split) != null)
341  {
342  if (splits == null)
343  {
344  splits = new Splits(algorithmTime);
345  }
346  splits[symbol] = split;
347  }
348  else if ((symbolChange = baseData as SymbolChangedEvent) != null)
349  {
350  if (symbolChanges == null)
351  {
352  symbolChanges = new SymbolChangedEvents(algorithmTime);
353  }
354  // symbol changes is keyed by the requested symbol
355  symbolChanges[packet.Configuration.Symbol] = symbolChange;
356  }
357  else if ((marginInterestRate = baseData as MarginInterestRate) != null)
358  {
359  if (marginInterestRates == null)
360  {
361  marginInterestRates = new MarginInterestRates(algorithmTime);
362  }
363  marginInterestRates[packet.Configuration.Symbol] = marginInterestRate;
364  }
365 
366  // let's make it available to the user through the cache
367  security.Add(new UpdateData<ISecurityPrice>(packet.Security, baseData.GetType(), new List<BaseData> { baseData }, packet.Configuration.IsInternalFeed, baseData.IsFillForward));
368  }
369  }
370 
371  if (securityUpdate.Count > 0)
372  {
373  security.Add(new UpdateData<ISecurityPrice>(packet.Security, packet.Configuration.Type, securityUpdate, packet.Configuration.IsInternalFeed, containsFillForwardData));
374  }
375  if (consolidatorUpdate.Count > 0)
376  {
377  consolidator.Add(new UpdateData<SubscriptionDataConfig>(packet.Configuration, packet.Configuration.Type, consolidatorUpdate, packet.Configuration.IsInternalFeed, containsFillForwardData));
378  }
379  }
380 
381  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);
382 
383  return new TimeSlice(utcDateTime, count, slice, data, security, consolidator, custom ?? _emptyCustom, changes, universeData);
384  }
385 
386  private void UpdateEmptyCollections(DateTime algorithmTime)
387  {
388  // just in case
389  _emptyTradeBars.Clear();
390  _emptyQuoteBars.Clear();
391  _emptyTicks.Clear();
392  _emptySplits.Clear();
393  _emptyDividends.Clear();
394  _emptyDelistings.Clear();
395  _emptyOptionChains.Clear();
396  _emptyFuturesChains.Clear();
397  _emptySymbolChangedEvents.Clear();
398  _emptyMarginInterestRates.Clear();
399 
400 #pragma warning disable 0618 // DataDictionary.Time is deprecated, ignore until removed entirely
401  _emptyTradeBars.Time
402  = _emptyQuoteBars.Time
403  = _emptyTicks.Time
404  = _emptySplits.Time
405  = _emptyDividends.Time
406  = _emptyDelistings.Time
407  = _emptyOptionChains.Time
408  = _emptyFuturesChains.Time
409  = _emptySymbolChangedEvents.Time
410  = _emptyMarginInterestRates.Time = algorithmTime;
411 #pragma warning restore 0618
412  }
413 
414  private bool HandleOptionData(DateTime algorithmTime, BaseData baseData, OptionChains optionChains, ISecurityPrice security, Lazy<Slice> sliceFuture, IReadOnlyDictionary<Symbol, BaseData> optionUnderlyingUpdates)
415  {
416  var symbol = baseData.Symbol;
417 
418  OptionChain chain;
419  var canonical = symbol.Canonical;
420  if (!optionChains.TryGetValue(canonical, out chain))
421  {
422  chain = new OptionChain(canonical, algorithmTime);
423  optionChains[canonical] = chain;
424  }
425 
426  // set the underlying current data point in the option chain
427  var option = security as IOptionPrice;
428  if (option != null)
429  {
430  if (option.Underlying == null)
431  {
432  Log.Error($"TimeSlice.HandleOptionData(): {algorithmTime}: Option underlying is null");
433  return false;
434  }
435 
436  BaseData underlyingData;
437  if (!optionUnderlyingUpdates.TryGetValue(option.Underlying.Symbol, out underlyingData))
438  {
439  underlyingData = option.Underlying.GetLastData();
440  }
441 
442  if (underlyingData == null)
443  {
444  Log.Error($"TimeSlice.HandleOptionData(): {algorithmTime}: Option underlying GetLastData returned null");
445  return false;
446  }
447  chain.Underlying = underlyingData;
448  }
449 
450  var universeData = baseData as BaseDataCollection;
451  if (universeData != null)
452  {
453  if (universeData.Underlying != null)
454  {
455  foreach (var addedContract in chain.Contracts)
456  {
457  addedContract.Value.Update(chain.Underlying);
458  }
459  }
460  foreach (var contractSymbol in universeData.FilteredContracts ?? Enumerable.Empty<Symbol>())
461  {
462  chain.FilteredContracts.Add(contractSymbol);
463  }
464  return false;
465  }
466 
467  OptionContract contract;
468  if (!chain.Contracts.TryGetValue(baseData.Symbol, out contract))
469  {
470  contract = OptionContract.Create(baseData, security, chain.Underlying);
471 
472  chain.Contracts[baseData.Symbol] = contract;
473 
474  if (option != null)
475  {
476  contract.SetOptionPriceModel(() => option.EvaluatePriceModel(sliceFuture.Value, contract));
477  }
478  }
479 
480  // populate ticks and tradebars dictionaries with no aux data
481  switch (baseData.DataType)
482  {
483  case MarketDataType.Tick:
484  var tick = (Tick)baseData;
485  chain.Ticks.Add(tick.Symbol, tick);
486  contract.Update(tick);
487  break;
488 
489  case MarketDataType.TradeBar:
490  var tradeBar = (TradeBar)baseData;
491  chain.TradeBars[symbol] = tradeBar;
492  contract.Update(tradeBar);
493  break;
494 
495  case MarketDataType.QuoteBar:
496  var quote = (QuoteBar)baseData;
497  chain.QuoteBars[symbol] = quote;
498  contract.Update(quote);
499  break;
500 
501  case MarketDataType.Base:
502  chain.AddAuxData(baseData);
503  break;
504  }
505  return true;
506  }
507 
508 
509  private bool HandleFuturesData(DateTime algorithmTime, BaseData baseData, FuturesChains futuresChains, ISecurityPrice security)
510  {
511  var symbol = baseData.Symbol;
512 
513  FuturesChain chain;
514  var canonical = symbol.Canonical;
515  if (!futuresChains.TryGetValue(canonical, out chain))
516  {
517  chain = new FuturesChain(canonical, algorithmTime);
518  futuresChains[canonical] = chain;
519  }
520 
521  var universeData = baseData as BaseDataCollection;
522  if (universeData != null)
523  {
524  foreach (var contractSymbol in universeData.FilteredContracts ?? Enumerable.Empty<Symbol>())
525  {
526  chain.FilteredContracts.Add(contractSymbol);
527  }
528  return false;
529  }
530 
531  FuturesContract contract;
532  if (!chain.Contracts.TryGetValue(baseData.Symbol, out contract))
533  {
534  var underlyingSymbol = baseData.Symbol.Underlying;
535  contract = new FuturesContract(baseData.Symbol, underlyingSymbol)
536  {
537  Time = baseData.EndTime,
538  LastPrice = security.Close,
539  Volume = (long)security.Volume,
540  BidPrice = security.BidPrice,
541  BidSize = (long)security.BidSize,
542  AskPrice = security.AskPrice,
543  AskSize = (long)security.AskSize,
544  OpenInterest = security.OpenInterest
545  };
546  chain.Contracts[baseData.Symbol] = contract;
547  }
548 
549  // populate ticks and tradebars dictionaries with no aux data
550  switch (baseData.DataType)
551  {
552  case MarketDataType.Tick:
553  var tick = (Tick)baseData;
554  chain.Ticks.Add(tick.Symbol, tick);
555  UpdateContract(contract, tick);
556  break;
557 
558  case MarketDataType.TradeBar:
559  var tradeBar = (TradeBar)baseData;
560  chain.TradeBars[symbol] = tradeBar;
561  UpdateContract(contract, tradeBar);
562  break;
563 
564  case MarketDataType.QuoteBar:
565  var quote = (QuoteBar)baseData;
566  chain.QuoteBars[symbol] = quote;
567  UpdateContract(contract, quote);
568  break;
569 
570  case MarketDataType.Base:
571  chain.AddAuxData(baseData);
572  break;
573  }
574  return true;
575  }
576 
577  private static void UpdateContract(FuturesContract contract, QuoteBar quote)
578  {
579  if (quote.Ask != null && quote.Ask.Close != 0m)
580  {
581  contract.AskPrice = quote.Ask.Close;
582  contract.AskSize = (long)quote.LastAskSize;
583  }
584  if (quote.Bid != null && quote.Bid.Close != 0m)
585  {
586  contract.BidPrice = quote.Bid.Close;
587  contract.BidSize = (long)quote.LastBidSize;
588  }
589  }
590 
591  private static void UpdateContract(FuturesContract contract, Tick tick)
592  {
593  if (tick.TickType == TickType.Trade)
594  {
595  contract.LastPrice = tick.Price;
596  }
597  else if (tick.TickType == TickType.Quote)
598  {
599  if (tick.AskPrice != 0m)
600  {
601  contract.AskPrice = tick.AskPrice;
602  contract.AskSize = (long)tick.AskSize;
603  }
604  if (tick.BidPrice != 0m)
605  {
606  contract.BidPrice = tick.BidPrice;
607  contract.BidSize = (long)tick.BidSize;
608  }
609  }
610  else if (tick.TickType == TickType.OpenInterest)
611  {
612  if (tick.Value != 0m)
613  {
614  contract.OpenInterest = tick.Value;
615  }
616  }
617  }
618 
619  private static void UpdateContract(FuturesContract contract, TradeBar tradeBar)
620  {
621  if (tradeBar.Close == 0m) return;
622  contract.LastPrice = tradeBar.Close;
623  contract.Volume = (long)tradeBar.Volume;
624  }
625  }
626 }