Lean  $LEAN_TAG$
LocalMarketHours.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 System.Linq;
18 using System.Collections.Generic;
19 using System.Collections.ObjectModel;
20 
22 {
23  /// <summary>
24  /// Represents the market hours under normal conditions for an exchange and a specific day of the week in terms of local time
25  /// </summary>
26  public class LocalMarketHours
27  {
28  /// <summary>
29  /// Gets whether or not this exchange is closed all day
30  /// </summary>
31  public bool IsClosedAllDay { get; }
32 
33  /// <summary>
34  /// Gets whether or not this exchange is closed all day
35  /// </summary>
36  public bool IsOpenAllDay { get; }
37 
38  /// <summary>
39  /// Gets the day of week these hours apply to
40  /// </summary>
41  public DayOfWeek DayOfWeek { get; }
42 
43  /// <summary>
44  /// Gets the tradable time during the market day.
45  /// For a normal US equity trading day this is 6.5 hours.
46  /// This does NOT account for extended market hours and only
47  /// considers <see cref="MarketHoursState.Market"/>
48  /// </summary>
49  public TimeSpan MarketDuration { get; }
50 
51  /// <summary>
52  /// Gets the individual market hours segments that define the hours of operation for this day
53  /// </summary>
54  public ReadOnlyCollection<MarketHoursSegment> Segments { get; }
55 
56  /// <summary>
57  /// Initializes a new instance of the <see cref="LocalMarketHours"/> class
58  /// </summary>
59  /// <param name="day">The day of the week these hours are applicable</param>
60  /// <param name="segments">The open/close segments defining the market hours for one day</param>
61  public LocalMarketHours(DayOfWeek day, params MarketHoursSegment[] segments)
62  : this(day, (IEnumerable<MarketHoursSegment>) segments)
63  {
64  }
65 
66  /// <summary>
67  /// Initializes a new instance of the <see cref="LocalMarketHours"/> class
68  /// </summary>
69  /// <param name="day">The day of the week these hours are applicable</param>
70  /// <param name="segments">The open/close segments defining the market hours for one day</param>
71  public LocalMarketHours(DayOfWeek day, IEnumerable<MarketHoursSegment> segments)
72  {
73  DayOfWeek = day;
74  // filter out the closed states, we'll assume closed if no segment exists
75  Segments = new ReadOnlyCollection<MarketHoursSegment>((segments ?? Enumerable.Empty<MarketHoursSegment>()).Where(x => x.State != MarketHoursState.Closed).ToList());
76  IsClosedAllDay = Segments.Count == 0;
77  IsOpenAllDay = Segments.Count == 1
78  && Segments[0].Start == TimeSpan.Zero
79  && Segments[0].End == Time.OneDay
80  && Segments[0].State == MarketHoursState.Market;
81 
82  for (var i = 0; i < Segments.Count; i++)
83  {
84  var segment = Segments[i];
85  if (segment.State == MarketHoursState.Market)
86  {
87  MarketDuration += segment.End - segment.Start;
88  }
89  }
90  }
91 
92  /// <summary>
93  /// Initializes a new instance of the <see cref="LocalMarketHours"/> class from the specified open/close times
94  /// </summary>
95  /// <param name="day">The day of week these hours apply to</param>
96  /// <param name="extendedMarketOpen">The extended market open time</param>
97  /// <param name="marketOpen">The regular market open time, must be greater than or equal to the extended market open time</param>
98  /// <param name="marketClose">The regular market close time, must be greater than the regular market open time</param>
99  /// <param name="extendedMarketClose">The extended market close time, must be greater than or equal to the regular market close time</param>
100  public LocalMarketHours(DayOfWeek day, TimeSpan extendedMarketOpen, TimeSpan marketOpen, TimeSpan marketClose, TimeSpan extendedMarketClose)
101  : this(day, MarketHoursSegment.GetMarketHoursSegments(extendedMarketOpen, marketOpen, marketClose, extendedMarketClose))
102  {
103  }
104 
105  /// <summary>
106  /// Initializes a new instance of the <see cref="LocalMarketHours"/> class from the specified open/close times
107  /// using the market open as the extended market open and the market close as the extended market close, effectively
108  /// removing any 'extended' session from these exchange hours
109  /// </summary>
110  /// <param name="day">The day of week these hours apply to</param>
111  /// <param name="marketOpen">The regular market open time</param>
112  /// <param name="marketClose">The regular market close time, must be greater than the regular market open time</param>
113  public LocalMarketHours(DayOfWeek day, TimeSpan marketOpen, TimeSpan marketClose)
114  : this(day, marketOpen, marketOpen, marketClose, marketClose)
115  {
116  }
117 
118  /// <summary>
119  /// Gets the market opening time of day
120  /// </summary>
121  /// <param name="time">The reference time, the open returned will be the first open after the specified time if there are multiple market open segments</param>
122  /// <param name="extendedMarketHours">True to include extended market hours, false for regular market hours</param>
123  /// <param name="previousDayLastSegment">The previous days last segment. This is used when the potential next market open is the first segment of the day
124  /// so we need to check that segment is not part of previous day last segment. If null, it means there were no segments on the last day</param>
125  /// <returns>The market's opening time of day</returns>
126  public TimeSpan? GetMarketOpen(TimeSpan time, bool extendedMarketHours, TimeSpan? previousDayLastSegment = null)
127  {
128  var previousSegment = previousDayLastSegment;
129  bool prevSegmentIsFromPrevDay = true;
130  for (var i = 0; i < Segments.Count; i++)
131  {
132  var segment = Segments[i];
133  if (segment.State == MarketHoursState.Closed || segment.End <= time)
134  {
135  // update prev segment end time only if the current segment could have been taken into account
136  // (regular hours or, when enabled, extended hours segment)
137  if (segment.State == MarketHoursState.Market || extendedMarketHours)
138  {
139  previousSegment = segment.End;
140  prevSegmentIsFromPrevDay = false;
141  }
142 
143  continue;
144  }
145 
146  // let's try this segment if it's regular market hours or if it is extended market hours and extended market is allowed
147  if (segment.State == MarketHoursState.Market || extendedMarketHours)
148  {
149  if (!IsContinuousMarketOpen(previousSegment, segment.Start, prevSegmentIsFromPrevDay))
150  {
151  return segment.Start;
152  }
153 
154  previousSegment = segment.End;
155  prevSegmentIsFromPrevDay = false;
156  }
157  }
158 
159  // we couldn't locate an open segment after the specified time
160  return null;
161  }
162 
163  /// <summary>
164  /// Gets the market closing time of day
165  /// </summary>
166  /// <param name="time">The reference time, the close returned will be the first close after the specified time if there are multiple market open segments</param>
167  /// <param name="extendedMarketHours">True to include extended market hours, false for regular market hours</param>
168  /// <param name="nextDaySegmentStart">Next day first segment start. This is used when the potential next market close is
169  /// the last segment of the day so we need to check that segment is not continued on next day first segment.
170  /// If null, it means there are no segments on the next day</param>
171  /// <returns>The market's closing time of day</returns>
172  public TimeSpan? GetMarketClose(TimeSpan time, bool extendedMarketHours, TimeSpan? nextDaySegmentStart = null)
173  {
174  TimeSpan? nextSegment;
175  bool nextSegmentIsFromNextDay = false;
176  for (var i = 0; i < Segments.Count; i++)
177  {
178  var segment = Segments[i];
179  if (segment.State == MarketHoursState.Closed || segment.End <= time)
180  {
181  continue;
182  }
183 
184  if (i != Segments.Count - 1)
185  {
186  var potentialNextSegment = Segments[i+1];
187 
188  // Check whether we can consider PostMarket or not
189  if (potentialNextSegment.State != MarketHoursState.Market && !extendedMarketHours)
190  {
191  nextSegment = null;
192  }
193  else
194  {
195  nextSegment = Segments[i+1].Start;
196  }
197  }
198  else
199  {
200  nextSegment = nextDaySegmentStart;
201  nextSegmentIsFromNextDay = true;
202  }
203 
204  if ((segment.State == MarketHoursState.Market || extendedMarketHours) &&
205  !IsContinuousMarketOpen(segment.End, nextSegment, nextSegmentIsFromNextDay))
206  {
207  return segment.End;
208  }
209  }
210 
211  // we couldn't locate an open segment after the specified time
212  return null;
213  }
214 
215  /// <summary>
216  /// Determines if the exchange is open at the specified time
217  /// </summary>
218  /// <param name="time">The time of day to check</param>
219  /// <param name="extendedMarketHours">True to check exended market hours, false to check regular market hours</param>
220  /// <returns>True if the exchange is considered open, false otherwise</returns>
221  public bool IsOpen(TimeSpan time, bool extendedMarketHours)
222  {
223  for (var i = 0; i < Segments.Count; i++)
224  {
225  var segment = Segments[i];
226  if (segment.State == MarketHoursState.Closed)
227  {
228  continue;
229  }
230 
231  if (segment.Contains(time))
232  {
233  return extendedMarketHours || segment.State == MarketHoursState.Market;
234  }
235  }
236 
237  // if we didn't find a segment then we're closed
238  return false;
239  }
240 
241  /// <summary>
242  /// Determines if the exchange is open during the specified interval
243  /// </summary>
244  /// <param name="start">The start time of the interval</param>
245  /// <param name="end">The end time of the interval</param>
246  /// <param name="extendedMarketHours">True to check exended market hours, false to check regular market hours</param>
247  /// <returns>True if the exchange is considered open, false otherwise</returns>
248  public bool IsOpen(TimeSpan start, TimeSpan end, bool extendedMarketHours)
249  {
250  if (start == end)
251  {
252  return IsOpen(start, extendedMarketHours);
253  }
254 
255  for (var i = 0; i < Segments.Count; i++)
256  {
257  var segment = Segments[i];
258  if (segment.State == MarketHoursState.Closed)
259  {
260  continue;
261  }
262 
263  if (extendedMarketHours || segment.State == MarketHoursState.Market)
264  {
265  if (segment.Overlaps(start, end))
266  {
267  return true;
268  }
269  }
270  }
271 
272  // if we didn't find a segment then we're closed
273  return false;
274  }
275 
276  /// <summary>
277  /// Gets a <see cref="LocalMarketHours"/> instance that is always closed
278  /// </summary>
279  /// <param name="dayOfWeek">The day of week</param>
280  /// <returns>A <see cref="LocalMarketHours"/> instance that is always closed</returns>
281  public static LocalMarketHours ClosedAllDay(DayOfWeek dayOfWeek)
282  {
283  return new LocalMarketHours(dayOfWeek);
284  }
285 
286  /// <summary>
287  /// Gets a <see cref="LocalMarketHours"/> instance that is always open
288  /// </summary>
289  /// <param name="dayOfWeek">The day of week</param>
290  /// <returns>A <see cref="LocalMarketHours"/> instance that is always open</returns>
291  public static LocalMarketHours OpenAllDay(DayOfWeek dayOfWeek)
292  {
293  return new LocalMarketHours(dayOfWeek, new MarketHoursSegment(MarketHoursState.Market, TimeSpan.Zero, Time.OneDay));
294  }
295 
296  /// <summary>
297  /// Check the given segment is not part of the current previous segment
298  /// </summary>
299  /// <param name="previousSegmentEnd">Previous segment end time before the current segment</param>
300  /// <param name="nextSegmentStart">The next segment start time</param>
301  /// <param name="prevSegmentIsFromPrevDay">Indicated whether the previous segment is from the previous day or not
302  /// (then it is from the same day as the next segment). Defaults to true</param>
303  /// <returns>True if indeed the given segment is part of the last segment. False otherwise</returns>
304  public static bool IsContinuousMarketOpen(TimeSpan? previousSegmentEnd, TimeSpan? nextSegmentStart, bool prevSegmentIsFromPrevDay = true)
305  {
306  if (previousSegmentEnd != null && nextSegmentStart != null)
307  {
308  if (prevSegmentIsFromPrevDay)
309  {
310  // midnight passing to the next day
311  return previousSegmentEnd.Value == Time.OneDay && nextSegmentStart.Value == TimeSpan.Zero;
312  }
313 
314  // passing from one segment to another in the same day
315  return previousSegmentEnd.Value == nextSegmentStart.Value;
316  }
317  return false;
318  }
319 
320  /// <summary>
321  /// Returns a string that represents the current object.
322  /// </summary>
323  /// <returns>
324  /// A string that represents the current object.
325  /// </returns>
326  /// <filterpriority>2</filterpriority>
327  public override string ToString()
328  {
329  return Messages.LocalMarketHours.ToString(this);
330  }
331  }
332 }