/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Linq; using static QuantConnect.StringExtensions; namespace QuantConnect.Securities.Future { /// /// Class to implement common functions used in FuturesExpiryFunctions /// public static class FuturesExpiryUtilityFunctions { private static readonly MarketHoursDatabase MarketHoursDatabase = MarketHoursDatabase.FromDataFolder(); /// /// Get holiday list from the MHDB given the market and the symbol of the security /// /// The market the exchange resides in, i.e, 'usa', 'fxcm', ect... /// The particular symbol being tradeds internal static HashSet GetExpirationHolidays(string market, string symbol) { var exchangeHours = MarketHoursDatabase.FromDataFolder() .GetEntry(market, symbol, SecurityType.Future) .ExchangeHours; return exchangeHours.Holidays.Concat(exchangeHours.BankHolidays).ToHashSet(); } /// /// Method to retrieve n^th succeeding/preceding business day for a given day /// /// The current Time /// Number of business days succeeding current time. Use negative value for preceding business days /// Set of holidays to exclude. These should be sourced from the /// The date-time after adding n business days public static DateTime AddBusinessDays(DateTime time, int n, HashSet holidays) { if (n < 0) { var businessDays = -n; var totalDays = 1; do { var previousDay = time.AddDays(-totalDays); if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay()) { businessDays--; } if (businessDays > 0) totalDays++; } while (businessDays > 0); return time.AddDays(-totalDays); } else { var businessDays = n; var totalDays = 1; do { var previousDay = time.AddDays(totalDays); if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay()) { businessDays--; } if (businessDays > 0) totalDays++; } while (businessDays > 0); return time.AddDays(totalDays); } } /// /// Method to retrieve n^th succeeding/preceding business day for a given day if there was a holiday on that day /// /// The current Time /// Number of business days succeeding current time. Use negative value for preceding business days /// Enumerable of holidays to exclude. These should be sourced from the /// The date-time after adding n business days public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet holidayList) { if (holidayList.Contains(time)) { return AddBusinessDays(time, n, holidayList); } else { return time; } } /// /// Method to retrieve the n^th last business day of the delivery month. /// /// DateTime for delivery month /// Number of days /// Holidays to use while calculating n^th business day. Useful for MHDB entries /// Nth Last Business day of the month public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable 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); if (n > daysInMonth) { throw new ArgumentOutOfRangeException(nameof(n), Invariant( $"Number of days ({n}) is larger than the size of month({daysInMonth})" )); } // Count the number of days in the month after the third to last business day var businessDays = n; var totalDays = 0; do { var previousDay = lastDayOfMonth.AddDays(-totalDays); if (NotHoliday(previousDay, holidays) && !holidays.Contains(previousDay)) { businessDays--; } if (businessDays > 0) totalDays++; } while (businessDays > 0); return lastDayOfMonth.AddDays(-totalDays); } /// /// Calculates the n^th business day of the month (includes checking for holidays) /// /// Month to calculate business day for /// n^th business day to get /// Holidays to not count as business days /// Nth business day of the month public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable holidayList) { var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month); var holidays = holidayList.Select(x => x.Date); if (nthBusinessDay > daysInMonth) { throw new ArgumentOutOfRangeException(Invariant( $"Argument nthBusinessDay (${nthBusinessDay}) is larger than the amount of days in the current month (${daysInMonth})" )); } if (nthBusinessDay < 1) { throw new ArgumentOutOfRangeException(Invariant( $"Argument nthBusinessDay (${nthBusinessDay}) is less than one. Provide a number greater than one and less than the days in month" )); } var calculatedTime = new DateTime(time.Year, time.Month, 1); var daysCounted = calculatedTime.IsCommonBusinessDay() ? 1 : 0; var i = 0; // 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()) { // 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)) { // Catches edge case where first day is on a friday if (i == 0 && calculatedTime.DayOfWeek == DayOfWeek.Friday) { daysCounted = 0; } calculatedTime = calculatedTime.AddDays(1); if (i != 0 && calculatedTime.IsCommonBusinessDay()) { daysCounted++; } i++; continue; } calculatedTime = calculatedTime.AddDays(1); if (!holidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays)) { daysCounted++; } i++; } return calculatedTime; } /// /// Method to retrieve the 2nd Friday of the given month /// /// Date from the given month /// 2nd Friday of given month public static DateTime SecondFriday(DateTime time) => NthFriday(time, 2); /// /// Method to retrieve the 3rd Friday of the given month /// /// Date from the given month /// 3rd Friday of given month public static DateTime ThirdFriday(DateTime time) => NthFriday(time, 3); /// /// Method to retrieve the Nth Friday of the given month /// /// Date from the given month /// The order of the Friday in the period /// Nth Friday of given month public static DateTime NthFriday(DateTime time, int n) => NthWeekday(time, n, DayOfWeek.Friday); /// /// Method to retrieve third Wednesday of the given month (usually Monday). /// /// Date from the given month /// Third Wednesday of the given month public static DateTime ThirdWednesday(DateTime time) => NthWeekday(time, 3, DayOfWeek.Wednesday); /// /// Method to retrieve the Nth Weekday of the given month /// /// Date from the given month /// The order of the Weekday in the period /// The day of the week /// Nth Weekday of given month public static DateTime NthWeekday(DateTime time, int n, DayOfWeek dayOfWeek) { if (n < 1 || n > 5) { throw new ArgumentOutOfRangeException(nameof(n), "'n' lower than 1 or greater than 5"); } var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month); return (from day in Enumerable.Range(1, daysInMonth) where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek select new DateTime(time.Year, time.Month, day)).ElementAt(n - 1); } /// /// Method to retrieve the last weekday of any month /// /// Date from the given month /// the last weekday to be found /// Last day of the we public static DateTime LastWeekday(DateTime time, DayOfWeek dayOfWeek) { var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month); return (from day in Enumerable.Range(1, daysInMonth).Reverse() where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek select new DateTime(time.Year, time.Month, day)).First(); } /// /// Method to retrieve the last Thursday of any month /// /// Date from the given month /// Last Thursday of the given month public static DateTime LastThursday(DateTime time) => LastWeekday(time, DayOfWeek.Thursday); /// /// Method to retrieve the last Friday of any month /// /// Date from the given month /// Last Friday of the given month public static DateTime LastFriday(DateTime time) => LastWeekday(time, DayOfWeek.Friday); /// /// Method to check whether a given time is holiday or not /// /// The DateTime for consideration /// Enumerable of holidays to exclude. These should be sourced from the /// True if the time is not a holidays, otherwise returns false public static bool NotHoliday(DateTime time, IEnumerable holidayList) { return time.IsCommonBusinessDay() && !holidayList.Contains(time.Date); } /// /// This function takes Thursday as input and returns true if four weekdays preceding it are not Holidays /// /// DateTime of a given Thursday /// Enumerable of holidays to exclude. These should be sourced from the /// False if DayOfWeek is not Thursday or is not preceded by four weekdays,Otherwise returns True public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable holidayList) { if (thursday.DayOfWeek != DayOfWeek.Thursday) { throw new ArgumentException("Input to NotPrecededByHolidays must be a Thursday"); } var result = true; // for Monday, Tuesday and Wednesday for (var i = 1; i <= 3; i++) { if (!NotHoliday(thursday.AddDays(-i), holidayList)) { result = false; } } // for Friday if (!NotHoliday(thursday.AddDays(-6), holidayList)) { result = false; } return result; } /// /// Gets the number of months between the contract month and the expiry date. /// /// The future symbol ticker /// Expiry date to use to look up contract month delta. Only used for dairy, since we need to lookup its contract month in a pre-defined table. /// The number of months between the contract month and the contract expiry public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? futureExpiryDate = null) { return ExpiriesPriorMonth.TryGetValue(underlying, out int value) ? value : 0; } /// /// Helper method to retrieve the futures contract month /// public static DateTime GetFutureContractMonth(Symbol symbol) { if (symbol.SecurityType == SecurityType.FutureOption) { symbol = symbol.Underlying; } var contractExpirationDate = symbol.ID.Date.Date; var monthsToAdd = GetDeltaBetweenContractMonthAndContractExpiry(symbol.ID.Symbol, contractExpirationDate); var contractMonth = contractExpirationDate.AddDays(-(contractExpirationDate.Day - 1)) .AddMonths(monthsToAdd); return contractMonth; } /// /// Helper method to resolve a future expiration from it's contract month /// public static DateTime GetFutureExpirationFromContractMonth(string symbol, string market, DateTime contractMonth) { return GetFutureExpirationFromContractMonth(Symbol.CreateFuture(symbol, market, SecurityIdentifier.DefaultDate), contractMonth); } /// /// Helper method to resolve a future expiration from it's contract month /// public static DateTime GetFutureExpirationFromContractMonth(Symbol future, DateTime contractMonth) { var futureExpiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(future.Canonical); var futureExpiry = futureExpiryFunc(contractMonth); return futureExpiry; } /// /// This function returns the third Friday of the month, adjusted for holidays and weekends. /// public static DateTime ThirdFriday(DateTime time, Symbol contract) { if (contract.ID.SecurityType.IsOption()) { return ThirdFriday(time, contract.Underlying); } var thirdFriday = ThirdFriday(time); var holidays = GetExpirationHolidays(contract.ID.Market, contract.ID.Symbol); return AddBusinessDaysIfHoliday(thirdFriday, -1, holidays); } /// /// Checks if the future contract is expired. /// public static bool IsFutureContractExpired(Symbol symbol, DateTime currentUtcTime, MarketHoursDatabase marketHoursDatabase = null) { var exchangeHours = (marketHoursDatabase ?? MarketHoursDatabase).GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType); var currentTimeInExchangeTz = currentUtcTime.ConvertFromUtc(exchangeHours.TimeZone); if (currentTimeInExchangeTz >= symbol.ID.Date) { return true; } return false; } private static readonly Dictionary ExpiriesPriorMonth = new Dictionary { { Futures.Energy.ArgusLLSvsWTIArgusTradeMonth, 1 }, { Futures.Energy.ArgusPropaneSaudiAramco, 1 }, { Futures.Energy.BrentCrude, 2 }, { Futures.Energy.BrentLastDayFinancial, 2 }, { Futures.Energy.CrudeOilWTI, 1 }, { Futures.Energy.MicroCrudeOilWTI, 1 }, { Futures.Energy.Gasoline, 1 }, { Futures.Energy.HeatingOil, 1 }, { Futures.Energy.MarsArgusVsWTITradeMonth, 1 }, { Futures.Energy.NaturalGas, 1 }, { Futures.Energy.NaturalGasHenryHubLastDayFinancial, 1 }, { Futures.Energy.NaturalGasHenryHubPenultimateFinancial, 1 }, { Futures.Energy.WTIHoustonArgusVsWTITradeMonth, 1 }, { Futures.Energy.WTIHoustonCrudeOil, 1 }, { Futures.Softs.Sugar11, 1 }, { Futures.Softs.Sugar11CME, 1 } }; } }