Lean  $LEAN_TAG$
FuturesExpiryUtilityFunctions.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections.Generic;
18 using System.Linq;
19 using static QuantConnect.StringExtensions;
20 
22 {
23  /// <summary>
24  /// Class to implement common functions used in FuturesExpiryFunctions
25  /// </summary>
26  public static class FuturesExpiryUtilityFunctions
27  {
28  private static readonly Dictionary<DateTime, DateTime> _reverseDairyReportDates = FuturesExpiryFunctions.DairyReportDates
29  .ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
30 
31  private static readonly HashSet<string> _dairyUnderlying = new HashSet<string>
32  {
33  "CB",
34  "CSC",
35  "DC",
36  "DY",
37  "GDK",
38  "GNF"
39  };
40 
41  /// <summary>
42  /// True to account for bank holidays which will adjust futures expiration dates
43  /// </summary>
44  public static bool BankHolidays { get; set; }
45 
46  /// <summary>
47  /// Get holiday list from the MHDB given the market and the symbol of the security
48  /// </summary>
49  /// <param name="market">The market the exchange resides in, i.e, 'usa', 'fxcm', ect...</param>
50  /// <param name="symbol">The particular symbol being traded</param>s
51  internal static HashSet<DateTime> GetExpirationHolidays(string market, string symbol)
52  {
53  var exchangeHours = MarketHoursDatabase.FromDataFolder()
54  .GetEntry(market, symbol, SecurityType.Future)
56  if (BankHolidays)
57  {
58  return exchangeHours.Holidays.Concat(exchangeHours.BankHolidays).ToHashSet();
59  }
60  return exchangeHours.Holidays;
61  }
62 
63  /// <summary>
64  /// Method to retrieve n^th succeeding/preceding business day for a given day
65  /// </summary>
66  /// <param name="time">The current Time</param>
67  /// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
68  /// <param name="holidays">Set of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
69  /// <returns>The date-time after adding n business days</returns>
70  public static DateTime AddBusinessDays(DateTime time, int n, HashSet<DateTime> holidays)
71  {
72  if (n < 0)
73  {
74  var businessDays = -n;
75  var totalDays = 1;
76  do
77  {
78  var previousDay = time.AddDays(-totalDays);
79  if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
80  {
81  businessDays--;
82  }
83 
84  if (businessDays > 0) totalDays++;
85  } while (businessDays > 0);
86 
87  return time.AddDays(-totalDays);
88  }
89  else
90  {
91  var businessDays = n;
92  var totalDays = 1;
93  do
94  {
95  var previousDay = time.AddDays(totalDays);
96  if (!holidays.Contains(previousDay.Date) && previousDay.IsCommonBusinessDay())
97  {
98  businessDays--;
99  }
100 
101  if (businessDays > 0) totalDays++;
102  } while (businessDays > 0);
103 
104  return time.AddDays(totalDays);
105  }
106  }
107 
108  /// <summary>
109  /// Method to retrieve n^th succeeding/preceding business day for a given day if there was a holiday on that day
110  /// </summary>
111  /// <param name="time">The current Time</param>
112  /// <param name="n">Number of business days succeeding current time. Use negative value for preceding business days</param>
113  /// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
114  /// <returns>The date-time after adding n business days</returns>
115  public static DateTime AddBusinessDaysIfHoliday(DateTime time, int n, HashSet<DateTime> holidayList)
116  {
117  if (holidayList.Contains(time))
118  {
119  return AddBusinessDays(time, n, holidayList);
120  }
121  else
122  {
123  return time;
124  }
125  }
126 
127  /// <summary>
128  /// Method to retrieve the n^th last business day of the delivery month.
129  /// </summary>
130  /// <param name="time">DateTime for delivery month</param>
131  /// <param name="n">Number of days</param>
132  /// <param name="holidayList">Holidays to use while calculating n^th business day. Useful for MHDB entries</param>
133  /// <returns>Nth Last Business day of the month</returns>
134  public static DateTime NthLastBusinessDay(DateTime time, int n, IEnumerable<DateTime> holidayList)
135  {
136  var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
137  var lastDayOfMonth = new DateTime(time.Year, time.Month, daysInMonth);
138  var holidays = holidayList.Select(x => x.Date);
139 
140  if(n > daysInMonth)
141  {
142  throw new ArgumentOutOfRangeException(nameof(n), Invariant(
143  $"Number of days ({n}) is larger than the size of month({daysInMonth})"
144  ));
145  }
146  // Count the number of days in the month after the third to last business day
147  var businessDays = n;
148  var totalDays = 0;
149  do
150  {
151  var previousDay = lastDayOfMonth.AddDays(-totalDays);
152  if (NotHoliday(previousDay, holidays) && !holidays.Contains(previousDay))
153  {
154  businessDays--;
155  }
156  if (businessDays > 0) totalDays++;
157  } while (businessDays > 0);
158 
159  return lastDayOfMonth.AddDays(-totalDays);
160  }
161 
162  /// <summary>
163  /// Calculates the n^th business day of the month (includes checking for holidays)
164  /// </summary>
165  /// <param name="time">Month to calculate business day for</param>
166  /// <param name="nthBusinessDay">n^th business day to get</param>
167  /// <param name="holidayList"> Holidays to not count as business days</param>
168  /// <returns>Nth business day of the month</returns>
169  public static DateTime NthBusinessDay(DateTime time, int nthBusinessDay, IEnumerable<DateTime> holidayList)
170  {
171  var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
172  var holidays = holidayList.Select(x => x.Date);
173  if (nthBusinessDay > daysInMonth)
174  {
175  throw new ArgumentOutOfRangeException(Invariant(
176  $"Argument nthBusinessDay (${nthBusinessDay}) is larger than the amount of days in the current month (${daysInMonth})"
177  ));
178  }
179  if (nthBusinessDay < 1)
180  {
181  throw new ArgumentOutOfRangeException(Invariant(
182  $"Argument nthBusinessDay (${nthBusinessDay}) is less than one. Provide a number greater than one and less than the days in month"
183  ));
184  }
185 
186  var calculatedTime = new DateTime(time.Year, time.Month, 1);
187 
188  var daysCounted = calculatedTime.IsCommonBusinessDay() ? 1 : 0;
189  var i = 0;
190 
191  // 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.
192  // We also want to make sure that we don't stop on a weekend.
193  while (daysCounted < nthBusinessDay || holidays.Contains(calculatedTime) || !calculatedTime.IsCommonBusinessDay())
194  {
195  // The asset continues trading on days contained within `USHoliday.Dates`, but
196  // the last trade date is affected by those holidays. We check for
197  // both MHDB entries and holidays to get accurate business days
198  if (holidays.Contains(calculatedTime))
199  {
200  // Catches edge case where first day is on a friday
201  if (i == 0 && calculatedTime.DayOfWeek == DayOfWeek.Friday)
202  {
203  daysCounted = 0;
204  }
205 
206  calculatedTime = calculatedTime.AddDays(1);
207 
208  if (i != 0 && calculatedTime.IsCommonBusinessDay())
209  {
210  daysCounted++;
211  }
212  i++;
213  continue;
214  }
215 
216  calculatedTime = calculatedTime.AddDays(1);
217 
218  if (!holidays.Contains(calculatedTime) && NotHoliday(calculatedTime, holidays))
219  {
220  daysCounted++;
221  }
222  i++;
223  }
224 
225  return calculatedTime;
226  }
227 
228  /// <summary>
229  /// Method to retrieve the 2nd Friday of the given month
230  /// </summary>
231  /// <param name="time">Date from the given month</param>
232  /// <returns>2nd Friday of given month</returns>
233  public static DateTime SecondFriday(DateTime time) => NthFriday(time, 2);
234 
235  /// <summary>
236  /// Method to retrieve the 3rd Friday of the given month
237  /// </summary>
238  /// <param name="time">Date from the given month</param>
239  /// <returns>3rd Friday of given month</returns>
240  public static DateTime ThirdFriday(DateTime time) => NthFriday(time, 3);
241 
242  /// <summary>
243  /// Method to retrieve the Nth Friday of the given month
244  /// </summary>
245  /// <param name="time">Date from the given month</param>
246  /// <param name="n">The order of the Friday in the period</param>
247  /// <returns>Nth Friday of given month</returns>
248  public static DateTime NthFriday(DateTime time, int n) => NthWeekday(time, n, DayOfWeek.Friday);
249 
250  /// <summary>
251  /// Method to retrieve third Wednesday of the given month (usually Monday).
252  /// </summary>
253  /// <param name="time">Date from the given month</param>
254  /// <returns>Third Wednesday of the given month</returns>
255  public static DateTime ThirdWednesday(DateTime time) => NthWeekday(time, 3, DayOfWeek.Wednesday);
256 
257  /// <summary>
258  /// Method to retrieve the Nth Weekday of the given month
259  /// </summary>
260  /// <param name="time">Date from the given month</param>
261  /// <param name="n">The order of the Weekday in the period</param>
262  /// <param name="dayOfWeek">The day of the week</param>
263  /// <returns>Nth Weekday of given month</returns>
264  public static DateTime NthWeekday(DateTime time, int n, DayOfWeek dayOfWeek)
265  {
266  if (n < 1 || n > 5)
267  {
268  throw new ArgumentOutOfRangeException(nameof(n), "'n' lower than 1 or greater than 5");
269  }
270 
271  var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
272  return (from day in Enumerable.Range(1, daysInMonth)
273  where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
274  select new DateTime(time.Year, time.Month, day)).ElementAt(n - 1);
275  }
276 
277 
278  /// <summary>
279  /// Method to retrieve the last weekday of any month
280  /// </summary>
281  /// <param name="time">Date from the given month</param>
282  /// <param name="dayOfWeek">the last weekday to be found</param>
283  /// <returns>Last day of the we</returns>
284  public static DateTime LastWeekday(DateTime time, DayOfWeek dayOfWeek)
285  {
286 
287  var daysInMonth = DateTime.DaysInMonth(time.Year, time.Month);
288  return (from day in Enumerable.Range(1, daysInMonth).Reverse()
289  where new DateTime(time.Year, time.Month, day).DayOfWeek == dayOfWeek
290  select new DateTime(time.Year, time.Month, day)).First();
291  }
292 
293  /// <summary>
294  /// Method to retrieve the last Thursday of any month
295  /// </summary>
296  /// <param name="time">Date from the given month</param>
297  /// <returns>Last Thursday of the given month</returns>
298  public static DateTime LastThursday(DateTime time) => LastWeekday(time, DayOfWeek.Thursday);
299 
300  /// <summary>
301  /// Method to retrieve the last Friday of any month
302  /// </summary>
303  /// <param name="time">Date from the given month</param>
304  /// <returns>Last Friday of the given month</returns>
305  public static DateTime LastFriday(DateTime time) => LastWeekday(time, DayOfWeek.Friday);
306 
307  /// <summary>
308  /// Method to check whether a given time is holiday or not
309  /// </summary>
310  /// <param name="time">The DateTime for consideration</param>
311  /// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
312  /// <returns>True if the time is not a holidays, otherwise returns false</returns>
313  public static bool NotHoliday(DateTime time, IEnumerable<DateTime> holidayList)
314  {
315  return time.IsCommonBusinessDay() && !holidayList.Contains(time.Date);
316  }
317 
318  /// <summary>
319  /// This function takes Thursday as input and returns true if four weekdays preceding it are not Holidays
320  /// </summary>
321  /// <param name="thursday">DateTime of a given Thursday</param>
322  /// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
323  /// <returns>False if DayOfWeek is not Thursday or is not preceded by four weekdays,Otherwise returns True</returns>
324  public static bool NotPrecededByHoliday(DateTime thursday, IEnumerable<DateTime> holidayList)
325  {
326  if (thursday.DayOfWeek != DayOfWeek.Thursday)
327  {
328  throw new ArgumentException("Input to NotPrecededByHolidays must be a Thursday");
329  }
330  var result = true;
331  // for Monday, Tuesday and Wednesday
332  for (var i = 1; i <= 3; i++)
333  {
334  if (!NotHoliday(thursday.AddDays(-i), holidayList))
335  {
336  result = false;
337  }
338  }
339  // for Friday
340  if (!NotHoliday(thursday.AddDays(-6), holidayList))
341  {
342  result = false;
343  }
344  return result;
345  }
346 
347  /// <summary>
348  /// Gets the last trade date corresponding to the contract month
349  /// </summary>
350  /// <param name="time">Contract month</param>
351  /// <param name="holidayList">Enumerable of holidays to exclude. These should be sourced from the <see cref="MarketHoursDatabase"/></param>
352  /// <param name="lastTradeTime">Time at which the dairy future contract stops trading (usually should be on 17:10:00 UTC)</param>
353  /// <returns></returns>
354  public static DateTime DairyLastTradeDate(DateTime time, IEnumerable<DateTime> holidayList, TimeSpan? lastTradeTime = null)
355  {
356  // 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.)
357  var contractMonth = new DateTime(time.Year, time.Month, 1);
358  var lastTradeTs = lastTradeTime ?? new TimeSpan(17, 10, 0);
359 
360  if (FuturesExpiryFunctions.DairyReportDates.TryGetValue(contractMonth, out DateTime publicationDate))
361  {
362  do
363  {
364  publicationDate = publicationDate.AddDays(-1);
365  }
366  while (holidayList.Contains(publicationDate) || publicationDate.DayOfWeek == DayOfWeek.Saturday);
367  }
368  else
369  {
370  publicationDate = contractMonth.AddMonths(1);
371  }
372 
373  // 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/
374  // More specifically, the report you should be looking for has the name "National Dairy Products Sales Report".
375  // 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
376 
377  return publicationDate.Add(lastTradeTs);
378  }
379 
380  /// <summary>
381  /// Gets the number of months between the contract month and the expiry date.
382  /// </summary>
383  /// <param name="underlying">The future symbol ticker</param>
384  /// <param name="futureExpiryDate">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.</param>
385  /// <returns>The number of months between the contract month and the contract expiry</returns>
386  public static int GetDeltaBetweenContractMonthAndContractExpiry(string underlying, DateTime? futureExpiryDate = null)
387  {
388  if (futureExpiryDate != null && _dairyUnderlying.Contains(underlying))
389  {
390  // Dairy can expire in the month following the contract month.
391  var dairyReportDate = futureExpiryDate.Value.Date.AddDays(1);
392  if (_reverseDairyReportDates.ContainsKey(dairyReportDate))
393  {
394  var contractMonth = _reverseDairyReportDates[dairyReportDate];
395  // Gets the distance between two months in months
396  return ((contractMonth.Year - dairyReportDate.Year) * 12) + contractMonth.Month - dairyReportDate.Month;
397  }
398 
399  return 0;
400  }
401 
402  return ExpiriesPriorMonth.TryGetValue(underlying, out int value) ? value : 0;
403  }
404 
405  /// <summary>
406  /// Calculates the date of Good Friday for a given year.
407  /// </summary>
408  /// <param name="year">Year to calculate Good Friday for</param>
409  /// <returns>Date of Good Friday</returns>
410  public static DateTime GetGoodFriday(int year)
411  {
412  // Acknowledgement
413  // Author: Jan Schreuder
414  // Link: https://www.codeproject.com/Articles/10860/Calculating-Christian-Holidays
415  // Calculates Easter Sunday as Easter is always celebrated on the Sunday immediately following the Paschal Full Moon date of the year
416  int g = year % 19;
417  int c = year / 100;
418  int h = (c - c / 4 - (8 * c + 13) / 25 + 19 * g + 15) % 30;
419  int i = h - h / 28 * (1 - h / 28 * (29 / (h + 1)) * ((21 - g) / 11));
420 
421  int day = i - (year + year / 4 + i + 2 - c + c / 4) % 7 + 28;
422  int month = 3;
423  if (day > 31)
424  {
425  month++;
426  day -= 31;
427  }
428 
429  // Calculate Good Friday
430  return new DateTime(year, month, day).AddDays(-2);
431  }
432 
433  private static readonly Dictionary<string, int> ExpiriesPriorMonth = new Dictionary<string, int>
434  {
437  { Futures.Energy.BrentCrude, 2 },
438  { Futures.Energy.BrentLastDayFinancial, 2 },
439  { Futures.Energy.CrudeOilWTI, 1 },
440  { Futures.Energy.MicroCrudeOilWTI, 1 },
441  { Futures.Energy.Gasoline, 1 },
442  { Futures.Energy.HeatingOil, 1 },
443  { Futures.Energy.MarsArgusVsWTITradeMonth, 1 },
444  { Futures.Energy.NaturalGas, 1 },
445  { Futures.Energy.NaturalGasHenryHubLastDayFinancial, 1 },
446  { Futures.Energy.NaturalGasHenryHubPenultimateFinancial, 1 },
447  { Futures.Energy.WTIHoustonArgusVsWTITradeMonth, 1 },
448  { Futures.Energy.WTIHoustonCrudeOil, 1 },
449  { Futures.Softs.Sugar11, 1 },
450  { Futures.Softs.Sugar11CME, 1 }
451  };
452  }
453 }