Lean  $LEAN_TAG$
MarketHourAwareConsolidator.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2024 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 NodaTime;
18 using QuantConnect.Util;
22 
24 {
25  /// <summary>
26  /// Consolidator for open markets bar only, extended hours bar are not consolidated.
27  /// </summary>
29  {
30  private readonly bool _dailyStrictEndTimeEnabled;
31  private readonly bool _extendedMarketHours;
32  private bool _useStrictEndTime;
33 
34  /// <summary>
35  /// The consolidation period requested
36  /// </summary>
37  protected TimeSpan Period { get; }
38 
39  /// <summary>
40  /// The consolidator instance
41  /// </summary>
42  protected IDataConsolidator Consolidator { get; }
43 
44  /// <summary>
45  /// The associated security exchange hours instance
46  /// </summary>
47  protected SecurityExchangeHours ExchangeHours { get; set; }
48 
49  /// <summary>
50  /// The associated data time zone
51  /// </summary>
52  protected DateTimeZone DataTimeZone { get; set; }
53 
54  /// <summary>
55  /// Gets the most recently consolidated piece of data. This will be null if this consolidator
56  /// has not produced any data yet.
57  /// </summary>
59 
60  /// <summary>
61  /// Gets the type consumed by this consolidator
62  /// </summary>
64 
65  /// <summary>
66  /// Gets a clone of the data being currently consolidated
67  /// </summary>
69 
70  /// <summary>
71  /// Gets the type produced by this consolidator
72  /// </summary>
74 
75  /// <summary>
76  /// Initializes a new instance of the <see cref="MarketHourAwareConsolidator"/> class.
77  /// </summary>
78  /// <param name="resolution">The resolution.</param>
79  /// <param name="dataType">The target data type</param>
80  /// <param name="tickType">The target tick type</param>
81  /// <param name="extendedMarketHours">True if extended market hours should be consolidated</param>
82  public MarketHourAwareConsolidator(bool dailyStrictEndTimeEnabled, Resolution resolution, Type dataType, TickType tickType, bool extendedMarketHours)
83  {
84  _dailyStrictEndTimeEnabled = dailyStrictEndTimeEnabled;
85  Period = resolution.ToTimeSpan();
86  _extendedMarketHours = extendedMarketHours;
87 
88  if (dataType == typeof(Tick))
89  {
90  if (tickType == TickType.Trade)
91  {
92  Consolidator = resolution == Resolution.Daily
94  : new TickConsolidator(Period);
95  }
96  else
97  {
98  Consolidator = resolution == Resolution.Daily
101  }
102  }
103  else if (dataType == typeof(TradeBar))
104  {
105  Consolidator = resolution == Resolution.Daily
108  }
109  else if (dataType == typeof(QuoteBar))
110  {
111  Consolidator = resolution == Resolution.Daily
114  }
115  else
116  {
117  throw new ArgumentNullException(nameof(dataType), $"{dataType.Name} not supported");
118  }
120  }
121 
122  /// <summary>
123  /// Event handler that fires when a new piece of data is produced
124  /// </summary>
126 
127  /// <summary>
128  /// Updates this consolidator with the specified data
129  /// </summary>
130  /// <param name="data">The new data for the consolidator</param>
131  public virtual void Update(IBaseData data)
132  {
133  Initialize(data);
134 
135  // US equity hour data from the database starts at 9am but the exchange opens at 9:30am. Thus, we need to handle
136  // this case specifically to avoid skipping the first hourly bar. To avoid this, we assert the period is daily,
137  // the data resolution is hour and the exchange opens at any point in time over the data.Time to data.EndTime interval
138  if (_extendedMarketHours ||
139  ExchangeHours.IsOpen(data.Time, false) ||
140  (Period == Time.OneDay && (data.EndTime - data.Time == Time.OneHour) && ExchangeHours.IsOpen(data.Time, data.EndTime, false)))
141  {
142  Consolidator.Update(data);
143  }
144  }
145 
146  /// <summary>
147  /// Scans this consolidator to see if it should emit a bar due to time passing
148  /// </summary>
149  /// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="P:QuantConnect.Data.BaseData.Time" />)</param>
150  public void Scan(DateTime currentLocalTime)
151  {
152  Consolidator.Scan(currentLocalTime);
153  }
154 
155  /// <summary>
156  /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
157  /// </summary>
158  public void Dispose()
159  {
161  Consolidator.Dispose();
162  }
163 
164  /// <summary>
165  /// Resets the consolidator
166  /// </summary>
167  public void Reset()
168  {
169  _useStrictEndTime = false;
170  ExchangeHours = null;
171  DataTimeZone = null;
173  }
174 
175  /// <summary>
176  /// Perform late initialization based on the datas symbol
177  /// </summary>
178  protected void Initialize(IBaseData data)
179  {
180  if (ExchangeHours == null)
181  {
182  var symbol = data.Symbol;
183  var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
184  ExchangeHours = marketHoursDatabase.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
185  DataTimeZone = marketHoursDatabase.GetDataTimeZone(symbol.ID.Market, symbol, symbol.SecurityType);
186 
187  _useStrictEndTime = UseStrictEndTime(data.Symbol);
188  }
189  }
190 
191  /// <summary>
192  /// Determines a bar start time and period
193  /// </summary>
194  protected virtual CalendarInfo DailyStrictEndTime(DateTime dateTime)
195  {
196  if (!_useStrictEndTime)
197  {
198  return new (Period > Time.OneDay ? dateTime : dateTime.RoundDown(Period), Period);
199  }
200  return LeanData.GetDailyCalendar(dateTime, ExchangeHours, _extendedMarketHours);
201  }
202 
203  /// <summary>
204  /// Useful for testing
205  /// </summary>
206  protected virtual bool UseStrictEndTime(Symbol symbol)
207  {
208  return LeanData.UseStrictEndTime(_dailyStrictEndTimeEnabled, symbol, Period, ExchangeHours);
209  }
210 
211  /// <summary>
212  /// Will forward the underlying consolidated bar to consumers on this object
213  /// </summary>
214  protected virtual void ForwardConsolidatedBar(object sender, IBaseData consolidated)
215  {
216  DataConsolidated?.Invoke(this, consolidated);
217  }
218  }
219 }