Skip to content

Commit

Permalink
Modify FuturesExpiryFunctions
Browse files Browse the repository at this point in the history
  • Loading branch information
Marinovsky committed Jan 15, 2025
1 parent 6c40415 commit 7b4fa2f
Show file tree
Hide file tree
Showing 6 changed files with 564 additions and 308 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 7b4fa2f

Please sign in to comment.