/* * 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 Dictionary _reverseDairyReportDates = FuturesExpiryFunctions.DairyReportDates .ToDictionary(kvp => kvp.Value, kvp => kvp.Key); private static readonly HashSet _dairyUnderlying = new HashSet { "CB", "CSC", "DC", "DY", "GDK", "GNF" }; /// /// True to account for bank holidays which will adjust futures expiration dates /// public static bool BankHolidays { get; set; } /// /// 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; if (BankHolidays) { return exchangeHours.Holidays.Concat(exchangeHours.BankHolidays).ToHashSet(); } return exchangeHours.Holidays; } /// /// 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 last trade date corresponding to the contract month /// /// Contract month /// Enumerable of holidays to exclude. These should be sourced from the /// Time at which the dairy future contract stops trading (usually should be on 17:10:00 UTC) /// public static DateTime DairyLastTradeDate(DateTime time, IEnumerable holidayList, TimeSpan? lastTradeTime = null) { // Trading shall terminate on the business day immediately preceding the day on which the USDA announces the price for that contract month. (LTD 12:10 p.m.) var contractMonth = new DateTime(time.Year, time.Month, 1); var lastTradeTs = lastTradeTime ?? new TimeSpan(17, 10, 0); if (FuturesExpiryFunctions.DairyReportDates.TryGetValue(contractMonth, out DateTime publicationDate)) { do { publicationDate = publicationDate.AddDays(-1); } while (holidayList.Contains(publicationDate) || publicationDate.DayOfWeek == DayOfWeek.Saturday); } else { publicationDate = contractMonth.AddMonths(1); } // The USDA price announcements are erratic in their publication date. You can view the calendar the USDA announces prices here: https://www.ers.usda.gov/calendar/ // More specifically, the report you should be looking for has the name "National Dairy Products Sales Report". // To get the report dates found in FuturesExpiryFunctions.DairyReportDates, visit this website: https://mpr.datamart.ams.usda.gov/menu.do?path=Products\Dairy\All%20Dairy\(DY_CL102)%20National%20Dairy%20Products%20Prices%20-%20Monthly return publicationDate.Add(lastTradeTs); } /// /// 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) { if (futureExpiryDate != null && _dairyUnderlying.Contains(underlying)) { // Dairy can expire in the month following the contract month. var dairyReportDate = futureExpiryDate.Value.Date.AddDays(1); if (_reverseDairyReportDates.ContainsKey(dairyReportDate)) { var contractMonth = _reverseDairyReportDates[dairyReportDate]; // Gets the distance between two months in months return ((contractMonth.Year - dairyReportDate.Year) * 12) + contractMonth.Month - dairyReportDate.Month; } return 0; } return ExpiriesPriorMonth.TryGetValue(underlying, out int value) ? value : 0; } /// /// Calculates the date of Good Friday for a given year. /// /// Year to calculate Good Friday for /// Date of Good Friday public static DateTime GetGoodFriday(int year) { // Acknowledgement // Author: Jan Schreuder // Link: https://www.codeproject.com/Articles/10860/Calculating-Christian-Holidays // Calculates Easter Sunday as Easter is always celebrated on the Sunday immediately following the Paschal Full Moon date of the year int g = year % 19; int c = year / 100; int h = (c - c / 4 - (8 * c + 13) / 25 + 19 * g + 15) % 30; int i = h - h / 28 * (1 - h / 28 * (29 / (h + 1)) * ((21 - g) / 11)); int day = i - (year + year / 4 + i + 2 - c + c / 4) % 7 + 28; int month = 3; if (day > 31) { month++; day -= 31; } // Calculate Good Friday return new DateTime(year, month, day).AddDays(-2); } 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 } }; } }