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