Lean  $LEAN_TAG$
OptionSymbol.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;
20 
22 {
23  /// <summary>
24  /// Static class contains common utility methods specific to symbols representing the option contracts
25  /// </summary>
26  public static class OptionSymbol
27  {
28  private static readonly Dictionary<string, byte> _optionExpirationErrorLog = new();
29 
30  /// <summary>
31  /// Returns true if the option is a standard contract that expires 3rd Friday of the month
32  /// </summary>
33  /// <param name="symbol">Option symbol</param>
34  /// <returns></returns>
35  public static bool IsStandardContract(Symbol symbol)
36  {
37  return IsStandard(symbol);
38  }
39 
40  /// <summary>
41  /// Returns true if the option is a standard contract that expires 3rd Friday of the month
42  /// </summary>
43  /// <param name="symbol">Option symbol</param>
44  /// <returns></returns>
45  public static bool IsStandard(Symbol symbol)
46  {
47  var date = symbol.ID.Date;
48 
49  // first we find out the day of week of the first day in the month
50  var firstDayOfMonth = new DateTime(date.Year, date.Month, 1).DayOfWeek;
51 
52  // find out the day of first Friday in this month
53  var firstFriday = firstDayOfMonth == DayOfWeek.Saturday ? 7 : 6 - (int)firstDayOfMonth;
54 
55  // check if the expiration date is within the week containing 3rd Friday
56  // we exclude monday, wednesday, and friday weeklys
57  return firstFriday + 7 + 5 /*sat -> wed */ < date.Day && date.Day < firstFriday + 2 * 7 + 2 /* sat, sun*/;
58  }
59 
60  /// <summary>
61  /// Returns true if the option is a weekly contract that expires on Friday , except 3rd Friday of the month
62  /// </summary>
63  /// <param name="symbol">Option symbol</param>
64  /// <returns></returns>
65  public static bool IsWeekly(Symbol symbol)
66  {
67  return !IsStandard(symbol) && symbol.ID.Date.DayOfWeek == DayOfWeek.Friday;
68  }
69 
70  /// <summary>
71  /// Maps the option ticker to it's underlying
72  /// </summary>
73  /// <param name="optionTicker">The option ticker to map</param>
74  /// <param name="securityType">The security type of the option or underlying</param>
75  /// <returns>The underlying ticker</returns>
76  public static string MapToUnderlying(string optionTicker, SecurityType securityType)
77  {
78  if(securityType == SecurityType.FutureOption || securityType == SecurityType.Future)
79  {
80  return FuturesOptionsSymbolMappings.MapFromOption(optionTicker);
81  }
82  else if (securityType == SecurityType.IndexOption || securityType == SecurityType.Index)
83  {
84  return IndexOptionSymbol.MapToUnderlying(optionTicker);
85  }
86 
87  return optionTicker;
88  }
89 
90  /// <summary>
91  /// Returns the last trading date for the option contract
92  /// </summary>
93  /// <param name="symbol">Option symbol</param>
94  /// <returns></returns>
95  public static DateTime GetLastDayOfTrading(Symbol symbol)
96  {
97  // The OCC proposed rule change: starting from 1 Feb 2015 standard monthly contracts
98  // expire on 3rd Friday, not Saturday following 3rd Friday as it was before.
99  // More details: https://www.sec.gov/rules/sro/occ/2013/34-69480.pdf
100 
101  int daysBefore = 0;
102  var symbolDateTime = symbol.ID.Date;
103 
104  if (IsStandard(symbol) &&
105  symbolDateTime.DayOfWeek == DayOfWeek.Saturday &&
106  symbolDateTime < new DateTime(2015, 2, 1))
107  {
108  daysBefore--;
109  }
110 
111  var exchangeHours = MarketHoursDatabase.FromDataFolder()
112  .GetEntry(symbol.ID.Market, symbol, symbol.SecurityType)
113  .ExchangeHours;
114 
115  while (!exchangeHours.IsDateOpen(symbolDateTime.AddDays(daysBefore)))
116  {
117  daysBefore--;
118  }
119 
120  return symbolDateTime.AddDays(daysBefore).Date;
121  }
122 
123  /// <summary>
124  /// Returns true if the option contract is expired at the specified time
125  /// </summary>
126  /// <param name="symbol">The option contract symbol</param>
127  /// <param name="currentTimeUtc">The current time (UTC)</param>
128  /// <returns>True if the option contract is expired at the specified time, false otherwise</returns>
129  public static bool IsOptionContractExpired(Symbol symbol, DateTime currentTimeUtc)
130  {
131  if (!symbol.SecurityType.IsOption())
132  {
133  return false;
134  }
135 
136  var exchangeHours = MarketHoursDatabase.FromDataFolder()
137  .GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
138 
139  var currentTime = currentTimeUtc.ConvertFromUtc(exchangeHours.TimeZone);
140 
141  // Ideally we can calculate expiry on the date of the symbol ID, but if that exchange is not open on that day we
142  // will consider expired on the last trading day close before this; Example in AddOptionContractExpiresRegressionAlgorithm
143  var expiryDay = exchangeHours.IsDateOpen(symbol.ID.Date)
144  ? symbol.ID.Date
145  : exchangeHours.GetPreviousTradingDay(symbol.ID.Date);
146 
147  var expiryTime = exchangeHours.GetNextMarketClose(expiryDay, false);
148 
149  // Once bug 6189 was solved in ´GetNextMarketClose()´ there was found possible bugs on some futures symbol.ID.Date or delisting/liquidation handle event.
150  // Specifically see 'DelistingFutureOptionRegressionAlgorithm' where Symbol.ID.Date: 4/1/2012 00:00 ExpiryTime: 4/2/2012 16:00 for Milk 3 futures options.
151  // See 'bug-milk-class-3-future-options-expiration' branch. So let's limit the expiry time to up to end of day of expiration
152  if (expiryTime >= symbol.ID.Date.AddDays(1).Date)
153  {
154  lock (_optionExpirationErrorLog)
155  {
156  if (symbol.ID.Underlying != null
157  // let's log this once per underlying and expiration date: avoiding the same log for multiple option contracts with different strikes/rights
158  && _optionExpirationErrorLog.TryAdd($"{symbol.ID.Underlying}-{symbol.ID.Date}", 1))
159  {
160  Logging.Log.Error($"OptionSymbol.IsOptionContractExpired(): limiting unexpected option expiration time for symbol {symbol.ID}. Symbol.ID.Date {symbol.ID.Date}. ExpiryTime: {expiryTime}");
161  }
162  }
163  expiryTime = symbol.ID.Date.AddDays(1).Date;
164  }
165 
166  return currentTime >= expiryTime;
167  }
168  }
169 }