20 using System.Globalization;
22 using System.Collections.Generic;
38 : base(securities, timeZone, marketHoursDatabase)
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));
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));
108 var hash = days.ToHashSet();
109 return new FuncDateRule(
string.Join(
",", days), (start, end) =>
Time.
EachDay(start, end).Where(date => hash.Contains(date.DayOfWeek)));
140 return YearStart(
null, daysOffset,
false);
154 if (daysOffset < 0 || 365 < daysOffset)
156 throw new ArgumentOutOfRangeException(nameof(daysOffset),
"DateRules.YearStart() : Offset must be between 0 and 365");
166 return new FuncDateRule(GetName(symbol,
"YearStart", daysOffset), (start, end) => YearIterator(securityExchangeHours, start, end, daysOffset,
true, extendedMarketHours));
176 return YearEnd(
null, daysOffset,
false);
189 if (daysOffset < 0 || 365 < daysOffset)
191 throw new ArgumentOutOfRangeException(nameof(daysOffset),
"DateRules.YearEnd() : Offset must be between 0 and 365");
201 return new FuncDateRule(GetName(symbol,
"YearEnd", -daysOffset), (start, end) => YearIterator(securityExchangeHours, start, end, daysOffset,
false, extendedMarketHours));
211 return new FuncDateRule(GetName(
null,
"MonthStart", daysOffset), (start, end) => MonthIterator(
null, start, end, daysOffset,
true,
false));
225 if (daysOffset < 0 || 30 < daysOffset)
227 throw new ArgumentOutOfRangeException(nameof(daysOffset),
"DateRules.MonthStart() : Offset must be between 0 and 30");
231 return new FuncDateRule(GetName(symbol,
"MonthStart", daysOffset), (start, end) => MonthIterator(
GetSecurityExchangeHours(symbol), start, end, daysOffset,
true, extendedMarketHours));
241 return new FuncDateRule(GetName(
null,
"MonthEnd", -daysOffset), (start, end) => MonthIterator(
null, start, end, daysOffset,
false,
false));
254 if (daysOffset < 0 || 30 < daysOffset)
256 throw new ArgumentOutOfRangeException(nameof(daysOffset),
"DateRules.MonthEnd() : Offset must be between 0 and 30");
260 return new FuncDateRule(GetName(symbol,
"MonthEnd", -daysOffset), (start, end) => MonthIterator(
GetSecurityExchangeHours(symbol), start, end, daysOffset,
false, extendedMarketHours));
271 if (daysOffset < 0 || 6 < daysOffset)
273 throw new ArgumentOutOfRangeException(nameof(daysOffset),
"DateRules.WeekStart() : Offset must be between 0 and 6");
276 return new FuncDateRule(GetName(
null,
"WeekStart", daysOffset), (start, end) => WeekIterator(
null, start, end, daysOffset,
true,
false));
292 var tradingDays = securitySchedule.MarketHours.Values
293 .Where(x => x.IsClosedAllDay ==
false).OrderBy(x => x.DayOfWeek).ToList();
296 if (daysOffset > tradingDays.Count - 1)
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))}");
304 return new FuncDateRule(GetName(symbol,
"WeekStart", daysOffset), (start, end) => WeekIterator(securitySchedule, start, end, daysOffset,
true, extendedMarketHours));
315 if (daysOffset < 0 || 6 < daysOffset)
317 throw new ArgumentOutOfRangeException(nameof(daysOffset),
"DateRules.WeekEnd() : Offset must be between 0 and 6");
320 return new FuncDateRule(GetName(
null,
"WeekEnd", -daysOffset), (start, end) => WeekIterator(
null, start, end, daysOffset,
false,
false));
335 var tradingDays = securitySchedule.MarketHours.Values
336 .Where(x => x.IsClosedAllDay ==
false).OrderBy(x => x.DayOfWeek).ToList();
339 if (daysOffset > tradingDays.Count - 1)
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))}");
347 return new FuncDateRule(GetName(symbol,
"WeekEnd", -daysOffset), (start, end) => WeekIterator(securitySchedule, start, end, daysOffset,
false, extendedMarketHours));
357 private static string GetName(
Symbol symbol,
string ruleType,
int offset)
360 var offsetString = offset.ToString(
"+#;-#;''", CultureInfo.InvariantCulture);
361 var name = symbol ==
null ? $
"{ruleType}{offsetString}" : $
"{symbol.Value}: {ruleType}{offsetString}";
376 private static DateTime GetScheduledDay(
SecurityExchangeHours securityExchangeHours, DateTime baseDay,
int offset,
bool searchForward,
bool extendedMarketHours, DateTime? boundary =
null)
379 var scheduledDate = baseDay;
382 if (!securityExchangeHours.
IsDateOpen(scheduledDate, extendedMarketHours))
384 scheduledDate = searchForward
390 for (var i = 0; i < offset; i++)
392 scheduledDate = searchForward
398 if (boundary.HasValue)
402 if (searchForward && scheduledDate > boundary)
404 scheduledDate = GetScheduledDay(securityExchangeHours, (DateTime)boundary, 0,
false, extendedMarketHours);
409 if (!searchForward && scheduledDate < boundary)
411 scheduledDate = GetScheduledDay(securityExchangeHours, (DateTime)boundary, 0,
true, extendedMarketHours);
415 return scheduledDate;
418 private static IEnumerable<DateTime> BaseIterator(
424 DateTime periodBegin,
426 Func<DateTime, DateTime> baseDateFunc,
427 Func<DateTime, DateTime> boundaryDateFunc,
428 bool extendedMarketHours)
431 if (securitySchedule ==
null)
436 foreach (var date
in Time.EachDay(periodBegin, periodEnd))
438 var baseDate = baseDateFunc(date);
439 var boundaryDate = boundaryDateFunc(date);
442 if (date == baseDate)
444 var scheduledDay = GetScheduledDay(securitySchedule, baseDate, offset, searchForward, extendedMarketHours, boundaryDate);
447 if (scheduledDay >= start && scheduledDay <= end)
449 yield
return scheduledDay;
455 private static IEnumerable<DateTime> MonthIterator(
SecurityExchangeHours securitySchedule, DateTime start, DateTime end,
int offset,
bool searchForward,
bool extendedMarketHours)
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));
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);
467 return BaseIterator(securitySchedule, start, end, offset, searchForward, beginningOfStartMonth, endOfEndMonth, baseDateFunc, boundaryDateFunc, extendedMarketHours);
470 private static IEnumerable<DateTime> YearIterator(
SecurityExchangeHours securitySchedule, DateTime start, DateTime end,
int offset,
bool searchForward,
bool extendedMarketHours)
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));
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);
482 return BaseIterator(securitySchedule, start, end, offset, searchForward, beginningOfStartOfYear, endOfEndYear, baseDateFunc, boundaryDateFunc, extendedMarketHours);
485 private static IEnumerable<DateTime> WeekIterator(
SecurityExchangeHours securitySchedule, DateTime start, DateTime end,
int offset,
bool searchForward,
bool extendedMarketHours)
488 DayOfWeek weeklyBaseDay;
489 DayOfWeek weeklyBoundaryDay;
490 if (securitySchedule ==
null)
497 weeklyBaseDay = searchForward ? DayOfWeek.Monday : DayOfWeek.Friday;
498 weeklyBoundaryDay = searchForward ? DayOfWeek.Saturday + 1 : DayOfWeek.Sunday - 1;
503 var weeklySchedule = securitySchedule.
MarketHours.Values
504 .Where(x => x.IsClosedAllDay ==
false).OrderBy(x => x.DayOfWeek).ToList();
507 weeklyBaseDay = searchForward ? weeklySchedule.First().DayOfWeek : weeklySchedule.Last().DayOfWeek;
508 weeklyBoundaryDay = searchForward ? weeklySchedule.Last().DayOfWeek : weeklySchedule.First().DayOfWeek;
514 var startAdjustment = start.DayOfWeek == DayOfWeek.Sunday ? -7 : 0;
515 var beginningOfStartWeek = start.AddDays(-(
int)start.DayOfWeek + 1 + startAdjustment);
517 var endAdjustment = end.DayOfWeek == DayOfWeek.Sunday ? -7 : 0;
518 var endOfEndWeek = end.AddDays(-(
int)end.DayOfWeek + 7 + endAdjustment);
521 foreach (var date
in Time.EachDay(beginningOfStartWeek, endOfEndWeek).Where(x => x.DayOfWeek == weeklyBaseDay))
523 var boundary = date.AddDays(weeklyBoundaryDay - weeklyBaseDay);
524 var scheduledDay = GetScheduledDay(securitySchedule, date, offset, searchForward, extendedMarketHours, boundary);
527 if (scheduledDay >= start && scheduledDay <= end)
529 yield
return scheduledDay;