Lean  $LEAN_TAG$
FuturesOptionsExpiryFunctions.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;
19 
21 {
22  /// <summary>
23  /// Futures options expiry lookup utility class
24  /// </summary>
25  public static class FuturesOptionsExpiryFunctions
26  {
27  private static readonly MarketHoursDatabase _mhdb = MarketHoursDatabase.FromDataFolder();
28 
29  private static readonly Symbol _lo = Symbol.CreateOption(Symbol.Create("CL", SecurityType.Future, Market.NYMEX), Market.NYMEX, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
30  private static readonly Symbol _on = Symbol.CreateOption(Symbol.Create("NG", SecurityType.Future, Market.NYMEX), Market.NYMEX, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
31  private static readonly Symbol _ozb = Symbol.CreateOption(Symbol.Create("ZB", SecurityType.Future, Market.CBOT), Market.CBOT, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
32  private static readonly Symbol _ozc = Symbol.CreateOption(Symbol.Create("ZC", SecurityType.Future, Market.CBOT), Market.CBOT, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
33  private static readonly Symbol _ozn = Symbol.CreateOption(Symbol.Create("ZN", SecurityType.Future, Market.CBOT), Market.CBOT, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
34  private static readonly Symbol _ozs = Symbol.CreateOption(Symbol.Create("ZS", SecurityType.Future, Market.CBOT), Market.CBOT, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
35  private static readonly Symbol _ozt = Symbol.CreateOption(Symbol.Create("ZT", SecurityType.Future, Market.CBOT), Market.CBOT, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
36  private static readonly Symbol _ozw = Symbol.CreateOption(Symbol.Create("ZW", SecurityType.Future, Market.CBOT), Market.CBOT, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
37  private static readonly Symbol _hxe = Symbol.CreateOption(Symbol.Create("HG", SecurityType.Future, Market.COMEX), Market.COMEX, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
38  private static readonly Symbol _og = Symbol.CreateOption(Symbol.Create("GC", SecurityType.Future, Market.COMEX), Market.COMEX, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
39  private static readonly Symbol _so = Symbol.CreateOption(Symbol.Create("SI", SecurityType.Future, Market.COMEX), Market.COMEX, default(OptionStyle), default(OptionRight), default(decimal), SecurityIdentifier.DefaultDate);
40 
41  /// <summary>
42  /// Futures options expiry functions lookup table, keyed by canonical future option Symbol
43  /// </summary>
44  private static readonly IReadOnlyDictionary<Symbol, Func<DateTime, DateTime>> _futuresOptionExpiryFunctions = new Dictionary<Symbol, Func<DateTime,DateTime>>
45  {
46  // 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
47  {_lo, expiryMonth => {
48  var twentySixthDayOfPreviousMonthFromContractMonth = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1)).AddDays(25);
49  var holidays = _mhdb.GetEntry(_lo.ID.Market, _lo.Underlying, SecurityType.Future)
50  .ExchangeHours
51  .Holidays;
52 
53  return FuturesExpiryUtilityFunctions.AddBusinessDays(twentySixthDayOfPreviousMonthFromContractMonth, -7, holidays);
54  }},
55  // 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).
56  // https://www.cmegroup.com/trading/energy/natural-gas/natural-gas_contractSpecs_options.html
57  // Although not stated, this follows the same rules as seen in the COMEX markets, but without Fridays. Case: Dec 2020 expiry, Last Trade Date: 24 Nov 2020
58  { _on, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_on.Underlying, expiryMonth, 0, 0, noFridays: false) },
59  { _ozb, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozb.Underlying, expiryMonth) },
60  { _ozc, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozc.Underlying, expiryMonth) },
61  { _ozn, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozn.Underlying, expiryMonth) },
62  { _ozs, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozs.Underlying, expiryMonth) },
63  { _ozt, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozt.Underlying, expiryMonth) },
64  { _ozw, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozw.Underlying, expiryMonth) },
65  { _hxe, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_hxe.Underlying, expiryMonth, 12, 0) },
66  { _og, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_og.Underlying, expiryMonth, 12, 30) },
67  { _so, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_so.Underlying, expiryMonth, 12, 25) },
68  };
69 
70  /// <summary>
71  /// Gets the Futures Options' expiry for the given contract month.
72  /// </summary>
73  /// <param name="canonicalFutureOptionSymbol">Canonical Futures Options Symbol. Will be made canonical if not provided a canonical</param>
74  /// <param name="futureContractMonth">Contract month of the underlying Future</param>
75  /// <returns>Expiry date/time</returns>
76  public static DateTime FuturesOptionExpiry(Symbol canonicalFutureOptionSymbol, DateTime futureContractMonth)
77  {
78  if (!canonicalFutureOptionSymbol.IsCanonical() || !canonicalFutureOptionSymbol.Underlying.IsCanonical())
79  {
80  canonicalFutureOptionSymbol = Symbol.CreateOption(
81  Symbol.Create(canonicalFutureOptionSymbol.Underlying.ID.Symbol, SecurityType.Future, canonicalFutureOptionSymbol.Underlying.ID.Market),
82  canonicalFutureOptionSymbol.ID.Market,
83  default(OptionStyle),
84  default(OptionRight),
85  default(decimal),
87  }
88 
89  Func<DateTime, DateTime> expiryFunction;
90  if (!_futuresOptionExpiryFunctions.TryGetValue(canonicalFutureOptionSymbol, out expiryFunction))
91  {
92  // No definition exists for this FOP. Let's default to futures expiry.
93  return FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureOptionSymbol.Underlying)(futureContractMonth);
94  }
95 
96  return expiryFunction(futureContractMonth);
97  }
98 
99  /// <summary>
100  /// Gets the Future Option's expiry from the Future Symbol provided
101  /// </summary>
102  /// <param name="futureSymbol">Future (non-canonical) Symbol</param>
103  /// <param name="canonicalFutureOption">The canonical Future Option Symbol</param>
104  /// <returns>Future Option Expiry for the Future with the same contract month</returns>
105  public static DateTime GetFutureOptionExpiryFromFutureExpiry(Symbol futureSymbol, Symbol canonicalFutureOption = null)
106  {
107  var futureContractMonthDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(futureSymbol.ID.Symbol, futureSymbol.ID.Date);
108  var futureContractMonth = new DateTime(
109  futureSymbol.ID.Date.Year,
110  futureSymbol.ID.Date.Month,
111  1)
112  .AddMonths(futureContractMonthDelta);
113 
114  if (canonicalFutureOption == null)
115  {
116  canonicalFutureOption = Symbol.CreateOption(
117  Symbol.Create(futureSymbol.ID.Symbol, SecurityType.Future, futureSymbol.ID.Market),
118  futureSymbol.ID.Market,
119  default(OptionStyle),
120  default(OptionRight),
121  default(decimal),
123  }
124 
125  return FuturesOptionExpiry(canonicalFutureOption, futureContractMonth);
126  }
127 
128  /// <summary>
129  /// Expiry function for CBOT Futures Options entries.
130  /// Returns the Friday before the 2nd last business day of the month preceding the future contract expiry month.
131  /// </summary>
132  /// <param name="underlyingFuture">Underlying future symbol</param>
133  /// <param name="expiryMonth">Expiry month date</param>
134  /// <returns>Expiry DateTime of the Future Option</returns>
135  private static DateTime FridayBeforeTwoBusinessDaysBeforeEndOfMonth(Symbol underlyingFuture, DateTime expiryMonth)
136  {
137  var holidays = _mhdb.GetEntry(underlyingFuture.ID.Market, underlyingFuture, SecurityType.Future)
138  .ExchangeHours
139  .Holidays;
140 
141  var expiryMonthPreceding = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1));
142  var fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(
143  expiryMonthPreceding,
144  2,
145  holidayList: holidays).AddDays(-1);
146 
147  while (fridayBeforeSecondLastBusinessDay.DayOfWeek != DayOfWeek.Friday)
148  {
149  fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fridayBeforeSecondLastBusinessDay, -1, holidays);
150  }
151 
152  return fridayBeforeSecondLastBusinessDay;
153  }
154 
155  /// <summary>
156  /// For Trading that terminates on the 4th last business day of the month prior to the contract month.
157  /// If the 4th last business day occurs on a Friday or the day before a holiday, trading terminates on the
158  /// prior business day. This applies to some NYMEX (with fridays), all COMEX.
159  /// </summary>
160  /// <param name="underlyingFuture">Underlying Future Symbol</param>
161  /// <param name="expiryMonth">Contract expiry month</param>
162  /// <param name="hour">Hour the contract expires at</param>
163  /// <param name="minutes">Minute the contract expires at</param>
164  /// <param name="noFridays">Exclude Friday expiration dates from consideration</param>
165  /// <returns>Expiry DateTime of the Future Option</returns>
166  private static DateTime FourthLastBusinessDayInPrecedingMonthFromContractMonth(Symbol underlyingFuture, DateTime expiryMonth, int hour, int minutes, bool noFridays = true)
167  {
168  var holidays = _mhdb.GetEntry(underlyingFuture.ID.Market, underlyingFuture, SecurityType.Future)
169  .ExchangeHours
170  .Holidays;
171 
172  var expiryMonthPreceding = expiryMonth.AddMonths(-1);
173  var fourthLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonthPreceding, 4, holidayList: holidays);
174 
175  if (noFridays)
176  {
177  while (fourthLastBusinessDay.DayOfWeek == DayOfWeek.Friday || holidays.Contains(fourthLastBusinessDay.AddDays(1)))
178  {
179  fourthLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fourthLastBusinessDay, -1, holidays);
180  }
181  }
182 
183  return fourthLastBusinessDay.AddHours(hour).AddMinutes(minutes);
184  }
185  }
186 }