Lean  $LEAN_TAG$
SubscriptionData.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 QuantConnect.Data;
20 using QuantConnect.Util;
21 
23 {
24  /// <summary>
25  /// Store data (either raw or adjusted) and the time at which it should be synchronized
26  /// </summary>
27  public class SubscriptionData
28  {
29  /// <summary>
30  /// Data
31  /// </summary>
32  protected BaseData _data { get; set; }
33 
34  /// <summary>
35  /// Gets the data
36  /// </summary>
37  public virtual BaseData Data => _data;
38 
39  /// <summary>
40  /// Gets the UTC emit time for this data
41  /// </summary>
42  public DateTime EmitTimeUtc { get; }
43 
44  /// <summary>
45  /// Initializes a new instance of the <see cref="SubscriptionData"/> class
46  /// </summary>
47  /// <param name="data">The base data</param>
48  /// <param name="emitTimeUtc">The emit time for the data</param>
49  public SubscriptionData(BaseData data, DateTime emitTimeUtc)
50  {
51  _data = data;
52  EmitTimeUtc = emitTimeUtc;
53  }
54 
55  /// <summary>
56  /// Clones the data, computes the utc emit time and performs exchange round down behavior, storing the result in a new <see cref="SubscriptionData"/> instance
57  /// </summary>
58  /// <param name="configuration">The subscription's configuration</param>
59  /// <param name="exchangeHours">The exchange hours of the security</param>
60  /// <param name="offsetProvider">The subscription's offset provider</param>
61  /// <param name="data">The data being emitted</param>
62  /// <param name="normalizationMode">Specifies how data is normalized</param>
63  /// <param name="factor">price scale factor</param>
64  /// <returns>A new <see cref="SubscriptionData"/> containing the specified data</returns>
65  public static SubscriptionData Create(bool dailyStrictEndTimeEnabled, SubscriptionDataConfig configuration, SecurityExchangeHours exchangeHours, TimeZoneOffsetProvider offsetProvider, BaseData data, DataNormalizationMode normalizationMode, decimal? factor = null)
66  {
67  if (data == null)
68  {
69  return null;
70  }
71 
72  data = data.Clone(data.IsFillForward);
73  var emitTimeUtc = offsetProvider.ConvertToUtc(data.EndTime);
74  // during warmup, data might be emitted with a different span based on the warmup resolution, so let's get the actual bar span here
75  var barSpan = data.EndTime - data.Time;
76  // rounding down does not make sense for daily increments using strict end times
77  if (!LeanData.UseDailyStrictEndTimes(dailyStrictEndTimeEnabled, configuration.Type, configuration.Symbol, barSpan, exchangeHours))
78  {
79  // Let's round down for any data source that implements a time delta between
80  // the start of the data and end of the data (usually used with Bars).
81  // The time delta ensures that the time collected from `EndTime` has
82  // no look-ahead bias, and is point-in-time.
83  // When fill forwarding time and endtime might not respect the original ends times, here we will enforce it
84  // note we do this after fetching the 'emitTimeUtc' which should use the end time set by the fill forward enumerator
85  if (barSpan != TimeSpan.Zero)
86  {
87  if (barSpan != configuration.Increment)
88  {
89  // when we detect a difference let's refetch the span in utc using noda time 'ConvertToUtc' that will not take into account day light savings difference
90  // we don't do this always above because it's expensive, only do it if we need to.
91  // Behavior asserted by tests 'FillsForwardBarsAroundDaylightMovementForDifferentResolutions_Algorithm' && 'ConvertToUtcAndDayLightSavings'.
92  // Note: we don't use 'configuration.Increment' because during warmup, if the warmup resolution is set, we will emit data respecting it instead of the 'configuration'
93  barSpan = data.EndTime.ConvertToUtc(configuration.ExchangeTimeZone) - data.Time.ConvertToUtc(configuration.ExchangeTimeZone);
94  }
95  data.Time = data.Time.ExchangeRoundDownInTimeZone(barSpan, exchangeHours, configuration.DataTimeZone, configuration.ExtendedMarketHours);
96  }
97  }
98  else if (data.IsFillForward)
99  {
100  // we need to adjust the time for a strict end time daily bar:
101  // If this is fill-forwarded with a lower resolution, the daily calendar for data.Time will be for the previous date
102  // (which is correct, since the last daily bar belongs to the previous date).
103  // If this is a fill-forwarded complete daily bar (ending at market close),
104  // the daily calendar will have the same time/end time so the bar times will not be adjusted.
105  // TODO: What about extended market hours? How to handle non-adjacent market hour segments in a day? Same in FillForwardEnumerator
106  var calendar = LeanData.GetDailyCalendar(data.Time, exchangeHours, false);
107  data.Time = calendar.Start;
108  data.EndTime = calendar.End;
109  }
110 
111  if (factor.HasValue && (configuration.SecurityType != SecurityType.Equity || (factor.Value != 1 || configuration.SumOfDividends != 0)))
112  {
113  var normalizedData = data.Clone(data.IsFillForward).Normalize(factor.Value, normalizationMode, configuration.SumOfDividends);
114 
115  return new PrecalculatedSubscriptionData(configuration, data, normalizedData, normalizationMode, emitTimeUtc);
116  }
117 
118  return new SubscriptionData(data, emitTimeUtc);
119  }
120  }
121 }