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 = FuturesExpiryUtilityFunctions.GetExpirationHolidays(_lo.ID.Market, _lo.Underlying.ID.Symbol);
50 
51  return FuturesExpiryUtilityFunctions.AddBusinessDays(twentySixthDayOfPreviousMonthFromContractMonth, -7, holidays);
52  }},
53  // 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).
54  // https://www.cmegroup.com/trading/energy/natural-gas/natural-gas_contractSpecs_options.html
55  // 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
56  { _on, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_on.Underlying, expiryMonth, 0, 0, noFridays: false) },
57  { _ozb, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozb.Underlying, expiryMonth) },
58  { _ozc, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozc.Underlying, expiryMonth) },
59  { _ozn, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozn.Underlying, expiryMonth) },
60  { _ozs, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozs.Underlying, expiryMonth) },
61  { _ozt, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozt.Underlying, expiryMonth) },
62  { _ozw, expiryMonth => FridayBeforeTwoBusinessDaysBeforeEndOfMonth(_ozw.Underlying, expiryMonth) },
63  { _hxe, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_hxe.Underlying, expiryMonth, 12, 0) },
64  { _og, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_og.Underlying, expiryMonth, 12, 30) },
65  { _so, expiryMonth => FourthLastBusinessDayInPrecedingMonthFromContractMonth(_so.Underlying, expiryMonth, 12, 25) },
66  };
67 
68  /// <summary>
69  /// Gets the Futures Options' expiry for the given contract month.
70  /// </summary>
71  /// <param name="canonicalFutureOptionSymbol">Canonical Futures Options Symbol. Will be made canonical if not provided a canonical</param>
72  /// <param name="futureContractMonth">Contract month of the underlying Future</param>
73  /// <returns>Expiry date/time</returns>
74  public static DateTime FuturesOptionExpiry(Symbol canonicalFutureOptionSymbol, DateTime futureContractMonth)
75  {
76  if (!canonicalFutureOptionSymbol.IsCanonical() || !canonicalFutureOptionSymbol.Underlying.IsCanonical())
77  {
78  canonicalFutureOptionSymbol = Symbol.CreateOption(
79  Symbol.Create(canonicalFutureOptionSymbol.Underlying.ID.Symbol, SecurityType.Future, canonicalFutureOptionSymbol.Underlying.ID.Market),
80  canonicalFutureOptionSymbol.ID.Market,
81  default(OptionStyle),
82  default(OptionRight),
83  default(decimal),
85  }
86 
87  Func<DateTime, DateTime> expiryFunction;
88  if (!_futuresOptionExpiryFunctions.TryGetValue(canonicalFutureOptionSymbol, out expiryFunction))
89  {
90  // No definition exists for this FOP. Let's default to futures expiry.
91  return FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureOptionSymbol.Underlying)(futureContractMonth);
92  }
93 
94  return expiryFunction(futureContractMonth);
95  }
96 
97  /// <summary>
98  /// Gets the Future Option's expiry from the Future Symbol provided
99  /// </summary>
100  /// <param name="futureSymbol">Future (non-canonical) Symbol</param>
101  /// <param name="canonicalFutureOption">The canonical Future Option Symbol</param>
102  /// <returns>Future Option Expiry for the Future with the same contract month</returns>
103  public static DateTime GetFutureOptionExpiryFromFutureExpiry(Symbol futureSymbol, Symbol canonicalFutureOption = null)
104  {
105  var futureContractMonthDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(futureSymbol.ID.Symbol, futureSymbol.ID.Date);
106  var futureContractMonth = new DateTime(
107  futureSymbol.ID.Date.Year,
108  futureSymbol.ID.Date.Month,
109  1)
110  .AddMonths(futureContractMonthDelta);
111 
112  if (canonicalFutureOption == null)
113  {
114  canonicalFutureOption = Symbol.CreateOption(
115  Symbol.Create(futureSymbol.ID.Symbol, SecurityType.Future, futureSymbol.ID.Market),
116  futureSymbol.ID.Market,
117  default(OptionStyle),
118  default(OptionRight),
119  default(decimal),
121  }
122 
123  return FuturesOptionExpiry(canonicalFutureOption, futureContractMonth);
124  }
125 
126  /// <summary>
127  /// Expiry function for CBOT Futures Options entries.
128  /// Returns the Friday before the 2nd last business day of the month preceding the future contract expiry month.
129  /// </summary>
130  /// <param name="underlyingFuture">Underlying future symbol</param>
131  /// <param name="expiryMonth">Expiry month date</param>
132  /// <returns>Expiry DateTime of the Future Option</returns>
133  private static DateTime FridayBeforeTwoBusinessDaysBeforeEndOfMonth(Symbol underlyingFuture, DateTime expiryMonth)
134  {
135  var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
136 
137  var expiryMonthPreceding = expiryMonth.AddMonths(-1).AddDays(-(expiryMonth.Day - 1));
138  var fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(
139  expiryMonthPreceding,
140  2,
141  holidays).AddDays(-1);
142 
143  while (fridayBeforeSecondLastBusinessDay.DayOfWeek != DayOfWeek.Friday)
144  {
145  fridayBeforeSecondLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fridayBeforeSecondLastBusinessDay, -1, holidays);
146  }
147 
148  return fridayBeforeSecondLastBusinessDay;
149  }
150 
151  /// <summary>
152  /// For Trading that terminates on the 4th last business day of the month prior to the contract month.
153  /// If the 4th last business day occurs on a Friday or the day before a holiday, trading terminates on the
154  /// prior business day. This applies to some NYMEX (with fridays), all COMEX.
155  /// </summary>
156  /// <param name="underlyingFuture">Underlying Future Symbol</param>
157  /// <param name="expiryMonth">Contract expiry month</param>
158  /// <param name="hour">Hour the contract expires at</param>
159  /// <param name="minutes">Minute the contract expires at</param>
160  /// <param name="noFridays">Exclude Friday expiration dates from consideration</param>
161  /// <returns>Expiry DateTime of the Future Option</returns>
162  private static DateTime FourthLastBusinessDayInPrecedingMonthFromContractMonth(Symbol underlyingFuture, DateTime expiryMonth, int hour, int minutes, bool noFridays = true)
163  {
164  var holidays = FuturesExpiryUtilityFunctions.GetExpirationHolidays(underlyingFuture.ID.Market, underlyingFuture.ID.Symbol);
165 
166  var expiryMonthPreceding = expiryMonth.AddMonths(-1);
167  var fourthLastBusinessDay = FuturesExpiryUtilityFunctions.NthLastBusinessDay(expiryMonthPreceding, 4, holidays);
168 
169  if (noFridays)
170  {
171  while (fourthLastBusinessDay.DayOfWeek == DayOfWeek.Friday || holidays.Contains(fourthLastBusinessDay.AddDays(1)))
172  {
173  fourthLastBusinessDay = FuturesExpiryUtilityFunctions.AddBusinessDays(fourthLastBusinessDay, -1, holidays);
174  }
175  }
176 
177  return fourthLastBusinessDay.AddHours(hour).AddMinutes(minutes);
178  }
179  }
180 }