Lean  $LEAN_TAG$
DateRules.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 System.Linq;
20 using System.Globalization;
22 using System.Collections.Generic;
23 
25 {
26  /// <summary>
27  /// Helper class used to provide better syntax when defining date rules
28  /// </summary>
30  {
31  /// <summary>
32  /// Initializes a new instance of the <see cref="DateRules"/> helper class
33  /// </summary>
34  /// <param name="securities">The security manager</param>
35  /// <param name="timeZone">The algorithm's default time zone</param>
36  /// <param name="marketHoursDatabase">The market hours database instance to use</param>
37  public DateRules(SecurityManager securities, DateTimeZone timeZone, MarketHoursDatabase marketHoursDatabase)
38  : base(securities, timeZone, marketHoursDatabase)
39  {
40  }
41 
42  /// <summary>
43  /// Sets the default time zone
44  /// </summary>
45  /// <param name="timeZone">The time zone to use for helper methods that can't resolve a time zone</param>
46  public void SetDefaultTimeZone(DateTimeZone timeZone)
47  {
48  TimeZone = timeZone;
49  }
50 
51  /// <summary>
52  /// Specifies an event should fire only on the specified day
53  /// </summary>
54  /// <param name="year">The year</param>
55  /// <param name="month">The month</param>
56  /// <param name="day">The day</param>
57  /// <returns></returns>
58  public IDateRule On(int year, int month, int day)
59  {
60  // make sure they're date objects
61  var dates = new[] {new DateTime(year, month, day)};
62  return new FuncDateRule(string.Join(",", dates.Select(x => x.ToShortDateString())), (start, end) => dates.Where(x => x >= start && x <= end));
63  }
64 
65  /// <summary>
66  /// Specifies an event should fire only on the specified days
67  /// </summary>
68  /// <param name="dates">The dates the event should fire</param>
69  public IDateRule On(params DateTime[] dates)
70  {
71  // make sure they're date objects
72  dates = dates.Select(x => x.Date).ToArray();
73  return new FuncDateRule(string.Join(",", dates.Select(x => x.ToShortDateString())), (start, end) => dates.Where(x => x >= start && x <= end));
74  }
75 
76  /// <summary>
77  /// Specifies an event should only fire today in the algorithm's time zone
78  /// using _securities.UtcTime instead of 'start' since ScheduleManager backs it up a day
79  /// </summary>
80  public IDateRule Today => new FuncDateRule("TodayOnly",
81  (start, e) => {
82  return new[] { Securities.UtcTime.ConvertFromUtc(TimeZone).Date };
83  }
84  );
85 
86  /// <summary>
87  /// Specifies an event should only fire tomorrow in the algorithm's time zone
88  /// using _securities.UtcTime instead of 'start' since ScheduleManager backs it up a day
89  /// </summary>
90  public IDateRule Tomorrow => new FuncDateRule("TomorrowOnly",
91  (start, e) => new[] {Securities.UtcTime.ConvertFromUtc(TimeZone).Date.AddDays(1)}
92  );
93 
94  /// <summary>
95  /// Specifies an event should fire on each of the specified days of week
96  /// </summary>
97  /// <param name="day">The day the event should fire</param>
98  /// <returns>A date rule that fires on every specified day of week</returns>
99  public IDateRule Every(DayOfWeek day) => Every(new[] { day });
100 
101  /// <summary>
102  /// Specifies an event should fire on each of the specified days of week
103  /// </summary>
104  /// <param name="days">The days the event should fire</param>
105  /// <returns>A date rule that fires on every specified day of week</returns>
106  public IDateRule Every(params DayOfWeek[] days)
107  {
108  var hash = days.ToHashSet();
109  return new FuncDateRule(string.Join(",", days), (start, end) => Time.EachDay(start, end).Where(date => hash.Contains(date.DayOfWeek)));
110  }
111 
112  /// <summary>
113  /// Specifies an event should fire every day
114  /// </summary>
115  /// <returns>A date rule that fires every day</returns>
117  {
118  return new FuncDateRule("EveryDay", Time.EachDay);
119  }
120 
121  /// <summary>
122  /// Specifies an event should fire every day the symbol is trading
123  /// </summary>
124  /// <param name="symbol">The symbol whose exchange is used to determine tradable dates</param>
125  /// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
126  /// <returns>A date rule that fires every day the specified symbol trades</returns>
127  public IDateRule EveryDay(Symbol symbol, bool extendedMarketHours = false)
128  {
129  var securitySchedule = GetSecurityExchangeHours(symbol);
130  return new FuncDateRule($"{symbol.Value}: EveryDay", (start, end) => Time.EachTradeableDay(securitySchedule, start, end, extendedMarketHours));
131  }
132 
133  /// <summary>
134  /// Specifies an event should fire on the first of each year + offset
135  /// </summary>
136  /// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 365.</param>
137  /// <returns>A date rule that fires on the first of each year + offset</returns>
138  public IDateRule YearStart(int daysOffset = 0)
139  {
140  return YearStart(null, daysOffset, false);
141  }
142 
143  /// <summary>
144  /// Specifies an event should fire on the first tradable date + offset for the specified symbol of each year
145  /// </summary>
146  /// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the year</param>
147  /// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 365</param>
148  /// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
149  /// <returns>A date rule that fires on the first tradable date + offset for the
150  /// specified security each year</returns>
151  public IDateRule YearStart(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
152  {
153  // Check that our offset is allowed
154  if (daysOffset < 0 || 365 < daysOffset)
155  {
156  throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.YearStart() : Offset must be between 0 and 365");
157  }
158 
159  SecurityExchangeHours securityExchangeHours = null;
160  if (symbol != null)
161  {
162  securityExchangeHours = GetSecurityExchangeHours(symbol);
163  }
164 
165  // Create the new DateRule and return it
166  return new FuncDateRule(GetName(symbol, "YearStart", daysOffset), (start, end) => YearIterator(securityExchangeHours, start, end, daysOffset, true, extendedMarketHours));
167  }
168 
169  /// <summary>
170  /// Specifies an event should fire on the last of each year
171  /// </summary>
172  /// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 365</param>
173  /// <returns>A date rule that fires on the last of each year - offset</returns>
174  public IDateRule YearEnd(int daysOffset = 0)
175  {
176  return YearEnd(null, daysOffset, false);
177  }
178 
179  /// <summary>
180  /// Specifies an event should fire on the last tradable date - offset for the specified symbol of each year
181  /// </summary>
182  /// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the year</param>
183  /// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 365.</param>
184  /// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
185  /// <returns>A date rule that fires on the last tradable date - offset for the specified security each year</returns>
186  public IDateRule YearEnd(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
187  {
188  // Check that our offset is allowed
189  if (daysOffset < 0 || 365 < daysOffset)
190  {
191  throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.YearEnd() : Offset must be between 0 and 365");
192  }
193 
194  SecurityExchangeHours securityExchangeHours = null;
195  if (symbol != null)
196  {
197  securityExchangeHours = GetSecurityExchangeHours(symbol);
198  }
199 
200  // Create the new DateRule and return it
201  return new FuncDateRule(GetName(symbol, "YearEnd", -daysOffset), (start, end) => YearIterator(securityExchangeHours, start, end, daysOffset, false, extendedMarketHours));
202  }
203 
204  /// <summary>
205  /// Specifies an event should fire on the first of each month + offset
206  /// </summary>
207  /// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 30.</param>
208  /// <returns>A date rule that fires on the first of each month + offset</returns>
209  public IDateRule MonthStart(int daysOffset = 0)
210  {
211  return new FuncDateRule(GetName(null, "MonthStart", daysOffset), (start, end) => MonthIterator(null, start, end, daysOffset, true, false));
212  }
213 
214  /// <summary>
215  /// Specifies an event should fire on the first tradable date + offset for the specified symbol of each month
216  /// </summary>
217  /// <param name="symbol">The symbol whose exchange is used to determine the first tradable date of the month</param>
218  /// <param name="daysOffset"> The amount of tradable days to offset the schedule by; must be between 0 and 30</param>
219  /// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
220  /// <returns>A date rule that fires on the first tradable date + offset for the
221  /// specified security each month</returns>
222  public IDateRule MonthStart(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
223  {
224  // Check that our offset is allowed
225  if (daysOffset < 0 || 30 < daysOffset)
226  {
227  throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.MonthStart() : Offset must be between 0 and 30");
228  }
229 
230  // Create the new DateRule and return it
231  return new FuncDateRule(GetName(symbol, "MonthStart", daysOffset), (start, end) => MonthIterator(GetSecurityExchangeHours(symbol), start, end, daysOffset, true, extendedMarketHours));
232  }
233 
234  /// <summary>
235  /// Specifies an event should fire on the last of each month
236  /// </summary>
237  /// <param name="daysOffset"> The amount of days to offset the schedule by; must be between 0 and 30</param>
238  /// <returns>A date rule that fires on the last of each month - offset</returns>
239  public IDateRule MonthEnd(int daysOffset = 0)
240  {
241  return new FuncDateRule(GetName(null, "MonthEnd", -daysOffset), (start, end) => MonthIterator(null, start, end, daysOffset, false, false));
242  }
243 
244  /// <summary>
245  /// Specifies an event should fire on the last tradable date - offset for the specified symbol of each month
246  /// </summary>
247  /// <param name="symbol">The symbol whose exchange is used to determine the last tradable date of the month</param>
248  /// <param name="daysOffset">The amount of tradable days to offset the schedule by; must be between 0 and 30.</param>
249  /// <param name="extendedMarketHours">True to include days with extended market hours only, like sunday for futures</param>
250  /// <returns>A date rule that fires on the last tradable date - offset for the specified security each month</returns>
251  public IDateRule MonthEnd(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
252  {
253  // Check that our offset is allowed
254  if (daysOffset < 0 || 30 < daysOffset)
255  {
256  throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.MonthEnd() : Offset must be between 0 and 30");
257  }
258 
259  // Create the new DateRule and return it
260  return new FuncDateRule(GetName(symbol, "MonthEnd", -daysOffset), (start, end) => MonthIterator(GetSecurityExchangeHours(symbol), start, end, daysOffset, false, extendedMarketHours));
261  }
262 
263  /// <summary>
264  /// Specifies an event should fire on Monday + offset each week
265  /// </summary>
266  /// <param name="daysOffset">The amount of days to offset monday by; must be between 0 and 6</param>
267  /// <returns>A date rule that fires on Monday + offset each week</returns>
268  public IDateRule WeekStart(int daysOffset = 0)
269  {
270  // Check that our offset is allowed
271  if (daysOffset < 0 || 6 < daysOffset)
272  {
273  throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.WeekStart() : Offset must be between 0 and 6");
274  }
275 
276  return new FuncDateRule(GetName(null, "WeekStart", daysOffset), (start, end) => WeekIterator(null, start, end, daysOffset, true, false));
277  }
278 
279  /// <summary>
280  /// Specifies an event should fire on the first tradable date + offset for the specified
281  /// symbol each week
282  /// </summary>
283  /// <param name="symbol">The symbol whose exchange is used to determine the first
284  /// tradeable date of the week</param>
285  /// <param name="daysOffset">The amount of tradable days to offset the first tradable day by</param>
286  /// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
287  /// <returns>A date rule that fires on the first + offset tradable date for the specified
288  /// security each week</returns>
289  public IDateRule WeekStart(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
290  {
291  var securitySchedule = GetSecurityExchangeHours(symbol);
292  var tradingDays = securitySchedule.MarketHours.Values
293  .Where(x => x.IsClosedAllDay == false).OrderBy(x => x.DayOfWeek).ToList();
294 
295  // Limit offsets to securities weekly schedule
296  if (daysOffset > tradingDays.Count - 1)
297  {
298  throw new ArgumentOutOfRangeException(nameof(daysOffset),
299  $"DateRules.WeekStart() : {tradingDays.First().DayOfWeek}+{daysOffset} is out of range for {symbol}'s schedule," +
300  $" please use an offset between 0 - {tradingDays.Count - 1}; Schedule : {string.Join(", ", tradingDays.Select(x => x.DayOfWeek))}");
301  }
302 
303  // Create the new DateRule and return it
304  return new FuncDateRule(GetName(symbol, "WeekStart", daysOffset), (start, end) => WeekIterator(securitySchedule, start, end, daysOffset, true, extendedMarketHours));
305  }
306 
307  /// <summary>
308  /// Specifies an event should fire on Friday - offset
309  /// </summary>
310  /// <param name="daysOffset"> The amount of days to offset Friday by; must be between 0 and 6 </param>
311  /// <returns>A date rule that fires on Friday each week</returns>
312  public IDateRule WeekEnd(int daysOffset = 0)
313  {
314  // Check that our offset is allowed
315  if (daysOffset < 0 || 6 < daysOffset)
316  {
317  throw new ArgumentOutOfRangeException(nameof(daysOffset), "DateRules.WeekEnd() : Offset must be between 0 and 6");
318  }
319 
320  return new FuncDateRule(GetName(null, "WeekEnd", -daysOffset), (start, end) => WeekIterator(null, start, end, daysOffset, false, false));
321  }
322 
323  /// <summary>
324  /// Specifies an event should fire on the last - offset tradable date for the specified
325  /// symbol of each week
326  /// </summary>
327  /// <param name="symbol">The symbol whose exchange is used to determine the last
328  /// tradable date of the week</param>
329  /// <param name="daysOffset"> The amount of tradable days to offset the last tradable day by each week</param>
330  /// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
331  /// <returns>A date rule that fires on the last - offset tradable date for the specified security each week</returns>
332  public IDateRule WeekEnd(Symbol symbol, int daysOffset = 0, bool extendedMarketHours = true)
333  {
334  var securitySchedule = GetSecurityExchangeHours(symbol);
335  var tradingDays = securitySchedule.MarketHours.Values
336  .Where(x => x.IsClosedAllDay == false).OrderBy(x => x.DayOfWeek).ToList();
337 
338  // Limit offsets to securities weekly schedule
339  if (daysOffset > tradingDays.Count - 1)
340  {
341  throw new ArgumentOutOfRangeException(nameof(daysOffset),
342  $"DateRules.WeekEnd() : {tradingDays.Last().DayOfWeek}-{daysOffset} is out of range for {symbol}'s schedule," +
343  $" please use an offset between 0 - {tradingDays.Count - 1}; Schedule : {string.Join(", ", tradingDays.Select(x => x.DayOfWeek))}");
344  }
345 
346  // Create the new DateRule and return it
347  return new FuncDateRule(GetName(symbol, "WeekEnd", -daysOffset), (start, end) => WeekIterator(securitySchedule, start, end, daysOffset, false, extendedMarketHours));
348  }
349 
350  /// <summary>
351  /// Determine the string representation for a given rule
352  /// </summary>
353  /// <param name="symbol">Symbol for the rule</param>
354  /// <param name="ruleType">Rule type in string form</param>
355  /// <param name="offset">The amount of offset on this rule</param>
356  /// <returns></returns>
357  private static string GetName(Symbol symbol, string ruleType, int offset)
358  {
359  // Convert our offset to +#, -#, or empty string if 0
360  var offsetString = offset.ToString("+#;-#;''", CultureInfo.InvariantCulture);
361  var name = symbol == null ? $"{ruleType}{offsetString}" : $"{symbol.Value}: {ruleType}{offsetString}";
362 
363  return name;
364  }
365 
366 
367  /// <summary>
368  /// Get the closest trading day to a given DateTime for a given <see cref="SecurityExchangeHours"/>.
369  /// </summary>
370  /// <param name="securityExchangeHours"><see cref="SecurityExchangeHours"/> object with schedule for this Security</param>
371  /// <param name="baseDay">The day to base our search from</param>
372  /// <param name="offset">Amount to offset the schedule by tradable days</param>
373  /// <param name="searchForward">Search into the future for the closest day if true; into the past if false</param>
374  /// <param name="boundary">The boundary DateTime on the resulting day</param>
375  /// <param name="extendedMarketHours">True to include extended market hours, false otherwise</param>
376  private static DateTime GetScheduledDay(SecurityExchangeHours securityExchangeHours, DateTime baseDay, int offset, bool searchForward, bool extendedMarketHours, DateTime? boundary = null)
377  {
378  // By default the scheduled date is the given day
379  var scheduledDate = baseDay;
380 
381  // If its not open on this day find the next trading day by searching in the given direction
382  if (!securityExchangeHours.IsDateOpen(scheduledDate, extendedMarketHours))
383  {
384  scheduledDate = searchForward
385  ? securityExchangeHours.GetNextTradingDay(scheduledDate)
386  : securityExchangeHours.GetPreviousTradingDay(scheduledDate);
387  }
388 
389  // Offset the scheduled day accordingly
390  for (var i = 0; i < offset; i++)
391  {
392  scheduledDate = searchForward
393  ? securityExchangeHours.GetNextTradingDay(scheduledDate)
394  : securityExchangeHours.GetPreviousTradingDay(scheduledDate);
395  }
396 
397  // If there is a boundary ensure we enforce it
398  if (boundary.HasValue)
399  {
400  // If we are searching forward and the resulting date is after this boundary we
401  // revert to the last tradable day equal to or less than boundary
402  if (searchForward && scheduledDate > boundary)
403  {
404  scheduledDate = GetScheduledDay(securityExchangeHours, (DateTime)boundary, 0, false, extendedMarketHours);
405  }
406 
407  // If we are searching backward and the resulting date is after this boundary we
408  // revert to the last tradable day equal to or greater than boundary
409  if (!searchForward && scheduledDate < boundary)
410  {
411  scheduledDate = GetScheduledDay(securityExchangeHours, (DateTime)boundary, 0, true, extendedMarketHours);
412  }
413  }
414 
415  return scheduledDate;
416  }
417 
418  private static IEnumerable<DateTime> BaseIterator(
419  SecurityExchangeHours securitySchedule,
420  DateTime start,
421  DateTime end,
422  int offset,
423  bool searchForward,
424  DateTime periodBegin,
425  DateTime periodEnd,
426  Func<DateTime, DateTime> baseDateFunc,
427  Func<DateTime, DateTime> boundaryDateFunc,
428  bool extendedMarketHours)
429  {
430  // No schedule means no security, set to open everyday
431  if (securitySchedule == null)
432  {
433  securitySchedule = SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork);
434  }
435 
436  foreach (var date in Time.EachDay(periodBegin, periodEnd))
437  {
438  var baseDate = baseDateFunc(date);
439  var boundaryDate = boundaryDateFunc(date);
440 
441  // Determine the scheduled day for this period
442  if (date == baseDate)
443  {
444  var scheduledDay = GetScheduledDay(securitySchedule, baseDate, offset, searchForward, extendedMarketHours, boundaryDate);
445 
446  // Ensure the date is within our schedules range
447  if (scheduledDay >= start && scheduledDay <= end)
448  {
449  yield return scheduledDay;
450  }
451  }
452  }
453  }
454 
455  private static IEnumerable<DateTime> MonthIterator(SecurityExchangeHours securitySchedule, DateTime start, DateTime end, int offset, bool searchForward, bool extendedMarketHours)
456  {
457  // Iterate all days between the beginning of "start" month, through end of "end" month.
458  // Necessary to ensure we schedule events in the month we start and end.
459  var beginningOfStartMonth = new DateTime(start.Year, start.Month, 1);
460  var endOfEndMonth = new DateTime(end.Year, end.Month, DateTime.DaysInMonth(end.Year, end.Month));
461 
462  // Searching forward the first of the month is baseDay, with boundary being the last
463  // Searching backward the last of the month is baseDay, with boundary being the first
464  Func<DateTime, DateTime> baseDateFunc = date => searchForward ? new DateTime(date.Year, date.Month, 1) : new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month));
465  Func<DateTime, DateTime> boundaryDateFunc = date => searchForward ? new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month)) : new DateTime(date.Year, date.Month, 1);
466 
467  return BaseIterator(securitySchedule, start, end, offset, searchForward, beginningOfStartMonth, endOfEndMonth, baseDateFunc, boundaryDateFunc, extendedMarketHours);
468  }
469 
470  private static IEnumerable<DateTime> YearIterator(SecurityExchangeHours securitySchedule, DateTime start, DateTime end, int offset, bool searchForward, bool extendedMarketHours)
471  {
472  // Iterate all days between the beginning of "start" year, through end of "end" year
473  // Necessary to ensure we schedule events in the year we start and end.
474  var beginningOfStartOfYear = new DateTime(start.Year, start.Month, 1);
475  var endOfEndYear = new DateTime(end.Year, end.Month, DateTime.DaysInMonth(end.Year, end.Month));
476 
477  // Searching forward the first of the year is baseDay, with boundary being the last
478  // Searching backward the last of the year is baseDay, with boundary being the first
479  Func<DateTime, DateTime> baseDateFunc = date => searchForward ? new DateTime(date.Year, 1, 1) : new DateTime(date.Year, 12, 31);
480  Func<DateTime, DateTime> boundaryDateFunc = date => searchForward ? new DateTime(date.Year, 12, 31) : new DateTime(date.Year, 1, 1);
481 
482  return BaseIterator(securitySchedule, start, end, offset, searchForward, beginningOfStartOfYear, endOfEndYear, baseDateFunc, boundaryDateFunc, extendedMarketHours);
483  }
484 
485  private static IEnumerable<DateTime> WeekIterator(SecurityExchangeHours securitySchedule, DateTime start, DateTime end, int offset, bool searchForward, bool extendedMarketHours)
486  {
487  // Determine the weekly base day and boundary to schedule off of
488  DayOfWeek weeklyBaseDay;
489  DayOfWeek weeklyBoundaryDay;
490  if (securitySchedule == null)
491  {
492  // No schedule means no security, set to open everyday
493  securitySchedule = SecurityExchangeHours.AlwaysOpen(TimeZones.NewYork);
494 
495  // Searching forward Monday is baseDay, with boundary being the following Sunday
496  // Searching backward Friday is baseDay, with boundary being the previous Saturday
497  weeklyBaseDay = searchForward ? DayOfWeek.Monday : DayOfWeek.Friday;
498  weeklyBoundaryDay = searchForward ? DayOfWeek.Saturday + 1 : DayOfWeek.Sunday - 1;
499  }
500  else
501  {
502  // Fetch the securities schedule
503  var weeklySchedule = securitySchedule.MarketHours.Values
504  .Where(x => x.IsClosedAllDay == false).OrderBy(x => x.DayOfWeek).ToList();
505 
506  // Determine our weekly base day and boundary for this security
507  weeklyBaseDay = searchForward ? weeklySchedule.First().DayOfWeek : weeklySchedule.Last().DayOfWeek;
508  weeklyBoundaryDay = searchForward ? weeklySchedule.Last().DayOfWeek : weeklySchedule.First().DayOfWeek;
509  }
510 
511  // Iterate all days between the beginning of "start" week, through end of "end" week.
512  // Necessary to ensure we schedule events in the week we start and end.
513  // Also if we have a sunday for start/end we need to adjust for it being the front of the week when we want it as the end of the week.
514  var startAdjustment = start.DayOfWeek == DayOfWeek.Sunday ? -7 : 0;
515  var beginningOfStartWeek = start.AddDays(-(int)start.DayOfWeek + 1 + startAdjustment); // Date - DayOfWeek + 1
516 
517  var endAdjustment = end.DayOfWeek == DayOfWeek.Sunday ? -7 : 0;
518  var endOfEndWeek = end.AddDays(-(int)end.DayOfWeek + 7 + endAdjustment); // Date - DayOfWeek + 7
519 
520  // Determine the schedule for each week in this range
521  foreach (var date in Time.EachDay(beginningOfStartWeek, endOfEndWeek).Where(x => x.DayOfWeek == weeklyBaseDay))
522  {
523  var boundary = date.AddDays(weeklyBoundaryDay - weeklyBaseDay);
524  var scheduledDay = GetScheduledDay(securitySchedule, date, offset, searchForward, extendedMarketHours, boundary);
525 
526  // Ensure the date is within our schedules range
527  if (scheduledDay >= start && scheduledDay <= end)
528  {
529  yield return scheduledDay;
530  }
531  }
532  }
533  }
534 }