Skip to content

Commit

Permalink
Add bank holidays
Browse files Browse the repository at this point in the history
Add missing holidays to MHDB

Add 2025 new year's eve bank holiday

Add Columbus day and veterans day to bank holidays

Add missing bank holidays entry

Modify FuturesExpiryFunctions

Add missing change

Remove empty bankHolidays lists from MHDB
  • Loading branch information
Marinovsky committed Jan 15, 2025
1 parent 265fd8b commit 42930a2
Show file tree
Hide file tree
Showing 8 changed files with 2,914 additions and 480 deletions.
736 changes: 467 additions & 269 deletions Common/Securities/Future/FuturesExpiryFunctions.cs

Large diffs are not rendered by default.

62 changes: 43 additions & 19 deletions Common/Securities/Future/FuturesExpiryUtilityFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,38 @@ public static HashSet<DateTime> GetHolidays(string market, string symbol)
.Holidays;
}

/// <summary>
/// Get bank holiday list from the MHDB given the market and the symbol of the security
/// </summary>
/// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
/// <param name="symbol">The particular symbol being traded</param>s
public static HashSet<DateTime> GetBankHolidays(string market, string symbol)
{
return MarketHoursDatabase.FromDataFolder()
.GetEntry(market, symbol, SecurityType.Future)
.ExchangeHours
.BankHolidays;
}

/// <summary>
/// Method to retrieve n^th succeeding/preceding business day for a given day
/// </summary>
/// <param name="time">The current Time</param>
/// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
/// <param name="holidays">Set of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// /// <param name="bankHolidays">Set of bank holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>The date-time after adding n business days</returns>
public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> holidays)
public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> holidays, HashSet<DateTime> bankHolidays)
{
var bankHolidaySet = bankHolidays ?? new HashSet<DateTime>();
if (n < 0)
{
var businessDays = -n;
var totalDays = 1;
do
{
var previousDay = time.AddDays(-totalDays);
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
if (!holidays.Contains(previousDay.Date) && !bankHolidaySet.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
Expand All @@ -84,7 +99,7 @@ public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> h
do
{
var previousDay = time.AddDays(totalDays);
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
if (!holidays.Contains(previousDay.Date) && !bankHolidaySet.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
Expand All @@ -102,12 +117,13 @@ public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> h
/// <param name="time">The current Time</param>
/// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <param name="bankHolidayList">Enumerable of bank holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>The date-time after adding n business days</returns>
public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet<DateTime> holidayList)
public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet<DateTime> holidayList, HashSet<DateTime> bankHolidayList)
{
if (holidayList.Contains(time))
{
return AddBusinessDays(time, n, holidayList);
return AddBusinessDays(time, n, holidayList, bankHolidayList);
}
else
{
Expand All @@ -121,12 +137,14 @@ public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet<Da
/// <param name="time">DateTime for delivery month</param>
/// <param name="n">Number of days</param>
/// <param name="holidayList">Holidays to use while calculating n^th business day. Useful for MHDB entries</param>
/// <param name="bankHolidayList">Bank holidays to use while calculating n^th business day. Useful for MHDB entries</param>
/// <returns>Nth Last Business day of the month</returns>
public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<DateTime> holidayList)
public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<DateTime> holidayList, IEnumerable<DateTime> bankHolidayList)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
var lastDayOfMonth = new DateTime(time.Year, time.Month, daysInMonth);
var holidays = holidayList.Select(x => x.Date);
var bankHolidays = (bankHolidayList ?? Enumerable.Empty<DateTime>()).Select(x => x.Date);

if(n > daysInMonth)
{
Expand All @@ -140,7 +158,7 @@ public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<Date
do
{
var previousDay = lastDayOfMonth.AddDays(-totalDays);
if (NotHoliday(previousDay, holidays) && !holidays.Contains(previousDay))
if (NotHoliday(previousDay, holidays, bankHolidays) && !holidays.Contains(previousDay) && !bankHolidays.Contains(previousDay))
{
businessDays--;
}
Expand All @@ -151,16 +169,19 @@ public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<Date
}

/// <summary>
/// Calculates the n^th business day of the month (includes checking for holidays)
/// Calculates the n^th business day of the month (includes checking for holidays and bankHolidays)
/// </summary>
/// <param name="time">Month to calculate business day for</param>
/// <param name="nthBusinessDay">n^th business day to get</param>
/// <param name="holidayList"> Holidays to not count as business days</param>
/// <param name="bankHolidayList"> Bank holidays to not count as business days</param>
/// <returns>Nth business day of the month</returns>
public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> holidayList)
public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> holidayList, IEnumerable<DateTime> bankHolidayList)
{
var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
var holidays = holidayList.Select(x => x.Date);
var bankHolidays = (bankHolidayList ?? Enumerable.Empty<DateTime>()).Select(x => x.Date);

if (nthBusinessDay > daysInMonth)
{
throw new ArgumentOutOfRangeException(Invariant(
Expand All @@ -181,12 +202,12 @@ public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumer

// Check for holiday up here in case we want the first business day and it is a holiday so that we don't skip over it.
// We also want to make sure that we don't stop on a weekend.
while (daysCounted < nthBusinessDay || holidays.Contains(calculatedTime) || !calculatedTime.IsCommonBusinessDay())
while (daysCounted < nthBusinessDay || holidays.Contains(calculatedTime) || bankHolidays.Contains(calculatedTime) || !calculatedTime.IsCommonBusinessDay())
{
// The asset continues trading on days contained within `USHoliday.Dates`, but
// the last trade date is affected by those holidays. We check for
// both MHDB entries and holidays to get accurate business days
if (holidays.Contains(calculatedTime))
if (holidays.Contains(calculatedTime) || bankHolidays.Contains(calculatedTime))
{
// Catches edge case where first day is on a friday
if (i == 0 && calculatedTime.DayOfWeek == DayOfWeek.Friday)
Expand All @@ -206,7 +227,7 @@ public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumer

calculatedTime = calculatedTime.AddDays(1);

if (!holidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays))
if (!holidays.Contains(calculatedTime) && !bankHolidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays, bankHolidays))
{
daysCounted++;
}
Expand Down Expand Up @@ -300,19 +321,21 @@ public static DateTime LastWeekday(DateTime time, DayOfWeek dayOfWeek)
/// </summary>
/// <param name="time">The DateTime for consideration</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <param name="bankHolidayList">Enumerable of bank holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>True if the time is not a holidays, otherwise returns false</returns>
public static bool NotHoliday(DateTime time, IEnumerable<DateTime> holidayList)
public static bool NotHoliday(DateTime time, IEnumerable<DateTime> holidayList, IEnumerable<DateTime> bankHolidayList)
{
return time.IsCommonBusinessDay() && !holidayList.Contains(time.Date);
return time.IsCommonBusinessDay() && !holidayList.Contains(time.Date) && (bankHolidayList == null || !bankHolidayList.Contains(time.Date));
}

/// <summary>
/// This function takes Thursday as input and returns true if four weekdays preceding it are not Holidays
/// </summary>
/// <param name="thursday">DateTime of a given Thursday</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <param name="bankHolidayList">Enumerable of bank holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <returns>False if DayOfWeek is not Thursday or is not preceded by four weekdays,Otherwise returns True</returns>
public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime> holidayList)
public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime> holidayList, IEnumerable<DateTime> bankHolidayList)
{
if (thursday.DayOfWeek != DayOfWeek.Thursday)
{
Expand All @@ -322,13 +345,13 @@ public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime>
// for Monday, Tuesday and Wednesday
for (var i = 1; i <= 3; i++)
{
if (!NotHoliday(thursday.AddDays(-i), holidayList))
if (!NotHoliday(thursday.AddDays(-i), holidayList, bankHolidayList))
{
result = false;
}
}
// for Friday
if (!NotHoliday(thursday.AddDays(-6), holidayList))
if (!NotHoliday(thursday.AddDays(-6), holidayList, bankHolidayList))
{
result = false;
}
Expand All @@ -340,9 +363,10 @@ public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime>
/// </summary>
/// <param name="time">Contract month</param>
/// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <param name="bankHolidayList">Enumerable of bank holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
/// <param name="lastTradeTime">Time at which the dairy future contract stops trading (usually should be on 17:10:00 UTC)</param>
/// <returns></returns>
public static DateTime DairyLastTradeDate(DateTime time, IEnumerable<DateTime> holidayList, TimeSpan? lastTradeTime = null)
public static DateTime DairyLastTradeDate(DateTime time, IEnumerable<DateTime> holidayList, IEnumerable<DateTime> bankHolidayList, TimeSpan? lastTradeTime = null)
{
// Trading shall terminate on the business day immediately preceding the day on which the USDA announces the <DAIRY_PRODUCT> price for that contract month. (LTD 12:10 p.m.)
var contractMonth = new DateTime(time.Year, time.Month, 1);
Expand All @@ -354,7 +378,7 @@ public static DateTime DairyLastTradeDate(DateTime time, IEnumerable<DateTime> h
{
publicationDate = publicationDate.AddDays(-1);
}
while (holidayList.Contains(publicationDate) || publicationDate.DayOfWeek == DayOfWeek.Saturday);
while (holidayList.Contains(publicationDate) || (bankHolidayList != null && bankHolidayList.Contains(publicationDate)) || publicationDate.DayOfWeek == DayOfWeek.Saturday);
}
else
{
Expand Down
19 changes: 14 additions & 5 deletions Common/Securities/FutureOption/FuturesOptionsExpiryFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ public static class FuturesOptionsExpiryFunctions
var holidays = _mhdb.GetEntry(_lo.ID.Market, _lo.Underlying, SecurityType.Future)
.ExchangeHours
.Holidays;
var bankHolidays = _mhdb.GetEntry(_lo.ID.Market, _lo.Underlying, SecurityType.Future)
.ExchangeHours
.BankHolidays;

return FuturesExpiryUtilityFunctions.AddBusinessDays(twentySixthDayOfPreviousMonthFromContractMonth, -7, holidays);
return FuturesExpiryUtilityFunctions.AddBusinessDays(twentySixthDayOfPreviousMonthFromContractMonth, -7, holidays, bankHolidays);
}},
// Trading terminates on the 4th last business day of the month prior to the contract month (1 business day prior to the expiration of the underlying futures corresponding contract month).
// https://www.cmegroup.com/trading/energy/natural-gas/natural-gas_contractSpecs_options.html
Expand Down Expand Up @@ -137,16 +140,19 @@ private static DateTime FridayBeforeTwoBusinessDaysBeforeEndOfMonth(Symbol under
var holidays = _mhdb.GetEntry(underlyingFuture.ID.Market, underlyingFuture, SecurityType.Future)
.ExchangeHours
.Holidays;
var bankHolidays = _mhdb.GetEntry(underlyingFuture.ID.Market, underlyingFuture, SecurityType.Future)
.ExchangeHours
.BankHolidays;

var expiryMonthPreceding = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1));
var fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(
expiryMonthPreceding,
2,
holidayList: holidays).AddDays(-1);
holidayList: holidays, bankHolidays).AddDays(-1);

while (fridayBeforeSecondLastBusinessDay.DayOfWeek != DayOfWeek.Friday)
{
fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fridayBeforeSecondLastBusinessDay, -1, holidays);
fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fridayBeforeSecondLastBusinessDay, -1, holidays, bankHolidays);
}

return fridayBeforeSecondLastBusinessDay;
Expand All @@ -168,15 +174,18 @@ private static DateTime FourthLastBusinessDayInPrecedingMonthFromContractMonth(S
var holidays = _mhdb.GetEntry(underlyingFuture.ID.Market, underlyingFuture, SecurityType.Future)
.ExchangeHours
.Holidays;
var bankHolidays = _mhdb.GetEntry(underlyingFuture.ID.Market, underlyingFuture, SecurityType.Future)
.ExchangeHours
.BankHolidays;

var expiryMonthPreceding = expiryMonth.AddMonths(-1);
var fourthLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonthPreceding, 4, holidayList: holidays);
var fourthLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonthPreceding, 4, holidayList: holidays, bankHolidays);

if (noFridays)
{
while (fourthLastBusinessDay.DayOfWeek == DayOfWeek.Friday || holidays.Contains(fourthLastBusinessDay.AddDays(1)))
{
fourthLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fourthLastBusinessDay, -1, holidays);
fourthLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fourthLastBusinessDay, -1, holidays, bankHolidays);
}
}

Expand Down
13 changes: 12 additions & 1 deletion Common/Securities/SecurityExchangeHours.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace QuantConnect.Securities
public class SecurityExchangeHours
{
private HashSet<long> _holidays;
private HashSet<long> _bankHolidays;
private IReadOnlyDictionary<DateTime, TimeSpan> _earlyCloses;
private IReadOnlyDictionary<DateTime, TimeSpan> _lateOpens;

Expand Down Expand Up @@ -68,6 +69,14 @@ public HashSet<DateTime> Holidays
get { return _holidays.ToHashSet(x => new DateTime(x)); }
}

/// <summary>
/// Gets the bank holidays for the exchange
/// </summary>
public HashSet<DateTime> BankHolidays
{
get { return _bankHolidays.ToHashSet(x => new DateTime(x)); }
}

/// <summary>
/// Gets the market hours for this exchange
/// </summary>
Expand Down Expand Up @@ -128,10 +137,12 @@ public SecurityExchangeHours(
IEnumerable<DateTime> holidayDates,
Dictionary<DayOfWeek, LocalMarketHours> marketHoursForEachDayOfWeek,
IReadOnlyDictionary<DateTime, TimeSpan> earlyCloses,
IReadOnlyDictionary<DateTime, TimeSpan> lateOpens)
IReadOnlyDictionary<DateTime, TimeSpan> lateOpens,
IEnumerable<DateTime> bankHolidayDates = null)
{
TimeZone = timeZone;
_holidays = holidayDates.Select(x => x.Date.Ticks).ToHashSet();
_bankHolidays = (bankHolidayDates ?? Enumerable.Empty<DateTime>()).Select(x => x.Date.Ticks).ToHashSet();
_earlyCloses = earlyCloses;
_lateOpens = lateOpens;
_openHoursByDay = marketHoursForEachDayOfWeek;
Expand Down
Loading

0 comments on commit 42930a2

Please sign in to comment.