Skip to content

Commit

Permalink
Address review
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin-Molinero committed Jan 15, 2025
1 parent ec0d55f commit daf9b5a
Show file tree
Hide file tree
Showing 6 changed files with 520 additions and 749 deletions.
1,126 changes: 464 additions & 662 deletions Common/Securities/Future/FuturesExpiryFunctions.cs

Large diffs are not rendered by default.

70 changes: 23 additions & 47 deletions Common/Securities/Future/FuturesExpiryUtilityFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,12 @@ public static class FuturesExpiryUtilityFunctions
/// </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> GetHolidays(string market, string symbol)
internal static HashSet<DateTime> GetExpirationHolidays(string market, string symbol)
{
return MarketHoursDatabase.FromDataFolder()
var exchangeHours = MarketHoursDatabase.FromDataFolder()
.GetEntry(market, symbol, SecurityType.Future)
.ExchangeHours
.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;
.ExchangeHours;
return exchangeHours.Holidays.Concat(exchangeHours.BankHolidays).ToHashSet();
}

/// <summary>
Expand All @@ -70,19 +57,17 @@ public static HashSet<DateTime> GetBankHolidays(string market, string symbol)
/// <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, HashSet<DateTime> bankHolidays)
public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> holidays)
{
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) && !bankHolidaySet.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
Expand All @@ -99,7 +84,7 @@ public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> h
do
{
var previousDay = time.AddDays(totalDays);
if (!holidays.Contains(previousDay.Date) && !bankHolidaySet.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
{
businessDays--;
}
Expand All @@ -117,13 +102,12 @@ 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, HashSet<DateTime> bankHolidayList)
public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet<DateTime> holidayList)
{
if (holidayList.Contains(time))
{
return AddBusinessDays(time, n, holidayList, bankHolidayList);
return AddBusinessDays(time, n, holidayList);
}
else
{
Expand All @@ -137,14 +121,12 @@ 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, IEnumerable<DateTime> bankHolidayList)
public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<DateTime> holidayList)
{
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 @@ -158,7 +140,7 @@ public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<Date
do
{
var previousDay = lastDayOfMonth.AddDays(-totalDays);
if (NotHoliday(previousDay, holidays, bankHolidays) && !holidays.Contains(previousDay) && !bankHolidays.Contains(previousDay))
if (NotHoliday(previousDay, holidays) && !holidays.Contains(previousDay))
{
businessDays--;
}
Expand All @@ -169,19 +151,16 @@ public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<Date
}

/// <summary>
/// Calculates the n^th business day of the month (includes checking for holidays and bankHolidays)
/// Calculates the n^th business day of the month (includes checking for holidays)
/// </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, IEnumerable<DateTime> bankHolidayList)
public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> holidayList)
{
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 @@ -202,12 +181,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) || bankHolidays.Contains(calculatedTime) || !calculatedTime.IsCommonBusinessDay())
while (daysCounted < nthBusinessDay || holidays.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) || bankHolidays.Contains(calculatedTime))
if (holidays.Contains(calculatedTime))
{
// Catches edge case where first day is on a friday
if (i == 0 && calculatedTime.DayOfWeek == DayOfWeek.Friday)
Expand All @@ -227,7 +206,7 @@ public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumer

calculatedTime = calculatedTime.AddDays(1);

if (!holidays.Contains(calculatedTime) && !bankHolidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays, bankHolidays))
if (!holidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays))
{
daysCounted++;
}
Expand Down Expand Up @@ -321,21 +300,19 @@ 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, IEnumerable<DateTime> bankHolidayList)
public static bool NotHoliday(DateTime time, IEnumerable<DateTime> holidayList)
{
return time.IsCommonBusinessDay() && !holidayList.Contains(time.Date) && (bankHolidayList == null || !bankHolidayList.Contains(time.Date));
return time.IsCommonBusinessDay() && !holidayList.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, IEnumerable<DateTime> bankHolidayList)
public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime> holidayList)
{
if (thursday.DayOfWeek != DayOfWeek.Thursday)
{
Expand All @@ -345,13 +322,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, bankHolidayList))
if (!NotHoliday(thursday.AddDays(-i), holidayList))
{
result = false;
}
}
// for Friday
if (!NotHoliday(thursday.AddDays(-6), holidayList, bankHolidayList))
if (!NotHoliday(thursday.AddDays(-6), holidayList))
{
result = false;
}
Expand All @@ -363,10 +340,9 @@ 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, IEnumerable<DateTime> bankHolidayList, TimeSpan? lastTradeTime = null)
public static DateTime DairyLastTradeDate(DateTime time, IEnumerable<DateTime> holidayList, 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 @@ -378,7 +354,7 @@ public static DateTime DairyLastTradeDate(DateTime time, IEnumerable<DateTime> h
{
publicationDate = publicationDate.AddDays(-1);
}
while (holidayList.Contains(publicationDate) || (bankHolidayList != null && bankHolidayList.Contains(publicationDate)) || publicationDate.DayOfWeek == DayOfWeek.Saturday);
while (holidayList.Contains(publicationDate) || publicationDate.DayOfWeek == DayOfWeek.Saturday);
}
else
{
Expand Down
33 changes: 9 additions & 24 deletions Common/Securities/FutureOption/FuturesOptionsExpiryFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,9 @@ public static class FuturesOptionsExpiryFunctions
// Trading terminates 7 business days before the 26th calendar of the month prior to the contract month. https://www.cmegroup.com/trading/energy/crude-oil/light-sweet-crude_contractSpecs_options.html#optionProductId=190
{_lo, expiryMonth => {
var twentySixthDayOfPreviousMonthFromContractMonth = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1)).AddDays(25);
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, bankHolidays);
var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(_lo.ID.Market, _lo.Underlying.ID.Symbol);

return FuturesExpiryUtilityFunctions.AddBusinessDays(twentySixthDayOfPreviousMonthFromContractMonth, -7, holidays);
}},
// 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,22 +132,17 @@ public static DateTime GetFutureOptionExpiryFromFutureExpiry(Symbol futureSymbol
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime FridayBeforeTwoBusinessDaysBeforeEndOfMonth(Symbol underlyingFuture, DateTime expiryMonth)
{
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 holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);

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

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

return fridayBeforeSecondLastBusinessDay;
Expand All @@ -171,21 +161,16 @@ private static DateTime FridayBeforeTwoBusinessDaysBeforeEndOfMonth(Symbol under
/// <returns>Expiry DateTime of the Future Option</returns>
private static DateTime FourthLastBusinessDayInPrecedingMonthFromContractMonth(Symbol underlyingFuture, DateTime expiryMonth, int hour, int minutes, bool noFridays = true)
{
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 holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);

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

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

Expand Down
2 changes: 2 additions & 0 deletions Common/Securities/SecurityExchangeHours.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public HashSet<DateTime> Holidays
/// <summary>
/// Gets the bank holidays for the exchange
/// </summary>
/// <remarks>In some markets and assets, like CME futures, there are tradable dates (market open) which
/// should not be considered for expiration rules due to banks being closed</remarks>
public HashSet<DateTime> BankHolidays
{
get { return _bankHolidays.ToHashSet(x => new DateTime(x)); }
Expand Down
Loading

0 comments on commit daf9b5a

Please sign in to comment.