Lean  $LEAN_TAG$
FuturesOptionsUnderlyingMapper.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;
20 
22 {
23  /// <summary>
24  /// Creates the underlying Symbol that corresponds to a futures options contract
25  /// </summary>
26  /// <remarks>
27  /// Because there can exist futures options (FOP) contracts that have an underlying Future
28  /// that does not have the same contract month as FOPs contract month, we need a way to resolve
29  /// the underlying Symbol of the FOP to the specific future contract it belongs to.
30  ///
31  /// Luckily, these FOPs all follow a pattern as to how the underlying is determined. The
32  /// method <see cref="GetUnderlyingFutureFromFutureOption"/> will automatically resolve the FOP contract's
33  /// underlying Future, and will ensure that the rules of the underlying are being followed.
34  ///
35  /// An example of a contract that this happens to is Gold Futures (FUT=GC, FOP=OG). OG FOPs
36  /// underlying Symbols are not determined by the contract month of the FOP itself, but rather
37  /// by the closest contract to it in an even month.
38  ///
39  /// Examples:
40  /// OGH21 would have an underlying of GCJ21
41  /// OGJ21 would have an underlying of GCJ21
42  /// OGK21 would have an underlying of GCM21
43  /// OGM21 would have an underlying of GCM21...
44  /// </remarks>
45  public static class FuturesOptionsUnderlyingMapper
46  {
47  private static readonly Dictionary<string, Func<DateTime, DateTime?, DateTime?>> _underlyingFuturesOptionsRules = new Dictionary<string, Func<DateTime, DateTime?, DateTime?>>
48  {
49  // CBOT
50  { "ZB", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZB", SecurityType.Future, Market.CBOT), d, ld.Value) },
51  { "ZC", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZC", SecurityType.Future, Market.CBOT), d, ld.Value) },
52  { "ZN", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZN", SecurityType.Future, Market.CBOT), d, ld.Value) },
53  { "ZS", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZS", SecurityType.Future, Market.CBOT), d, ld.Value) },
54  { "ZT", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZT", SecurityType.Future, Market.CBOT), d, ld.Value) },
55  { "ZW", (d, ld) => ContractMonthSerialLookupRule(Symbol.Create("ZW", SecurityType.Future, Market.CBOT), d, ld.Value) },
56 
57  // COMEX
58  { "HG", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
59  { "SI", (d, _) => ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(d, true) },
60  { "GC", (d, _) => ContractMonthEvenOddMonth(d, false) }
61  };
62 
63  /// <summary>
64  /// The difference in months for the Futures expiry month minus the Futures Options expiry month. This assumes
65  /// that the underlying Future follows a 1-1 mapping between the FOP and future, i.e. this will result in incorrect
66  /// results, but is needed as an intermediate step to resolve the actual expiry.
67  /// </summary>
68  private static readonly IReadOnlyDictionary<string, int> _futuresOptionsExpiryDelta = new Dictionary<string, int>
69  {
70  { "ZB", 1 },
71  { "ZC", 1 },
72  { "ZN", 1 },
73  { "ZS", 1 },
74  { "ZT", 1 },
75  { "ZW", 1 },
76  { "HG", 1 },
77  { "GC", 1 },
78  { "SI", 1 }
79  };
80 
81  /// <summary>
82  /// Gets the FOP's underlying Future. The underlying Future's contract month might not match
83  /// the contract month of the Future Option when providing CBOT or COMEX based FOPs contracts to this method.
84  /// </summary>
85  /// <param name="futureOptionTicker">Future option ticker</param>
86  /// <param name="market">Market of the Future Option</param>
87  /// <param name="futureOptionExpiration">Expiration date of the future option</param>
88  /// <param name="date">Date to search the future chain provider with. Optional, but required for CBOT based contracts</param>
89  /// <returns>Symbol if there is an underlying for the FOP, null if there's no underlying found for the Future Option</returns>
90  public static Symbol GetUnderlyingFutureFromFutureOption(string futureOptionTicker, string market, DateTime futureOptionExpiration, DateTime? date = null)
91  {
92  var futureTicker = FuturesOptionsSymbolMappings.MapFromOption(futureOptionTicker);
93  var canonicalFuture = Symbol.Create(futureTicker, SecurityType.Future, market);
94  // Get the contract month of the FOP to use when searching for the underlying.
95  // If the FOP and Future share the same contract month, this is reused as the future's
96  // contract month so that we can resolve the Future's expiry.
97  var contractMonth = GetFutureContractMonthNoRulesApplied(canonicalFuture, futureOptionExpiration);
98 
99  if (_underlyingFuturesOptionsRules.ContainsKey(futureTicker))
100  {
101  // The provided ticker follows some sort of rule. Let's figure out the underlying's contract month.
102  var newFutureContractMonth = _underlyingFuturesOptionsRules[futureTicker](contractMonth, date);
103  if (newFutureContractMonth == null)
104  {
105  // This will only happen when we search the Futures chain for a given contract and no
106  // closest match could be made, i.e. there are no futures in the chain that come after the FOP's
107  // contract month.
108  return null;
109  }
110 
111  contractMonth = newFutureContractMonth.Value;
112  }
113 
114  var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture)(contractMonth);
115  return Symbol.CreateFuture(futureTicker, market, futureExpiry);
116  }
117 
118  /// <summary>
119  /// Searches the futures chain for the next matching futures contract, and resolves the underlying
120  /// as the closest future we can find during or after the contract month.
121  /// </summary>
122  /// <param name="canonicalFutureSymbol">Canonical future Symbol</param>
123  /// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
124  /// <param name="lookupDate">The date that we'll be using to look at the Future chain</param>
125  /// <returns>The underlying future's contract month, or null if no closest contract was found</returns>
126  private static DateTime? ContractMonthSerialLookupRule(Symbol canonicalFutureSymbol, DateTime futureOptionContractMonth, DateTime lookupDate)
127  {
128  var futureChain = FuturesListings.ListedContracts(canonicalFutureSymbol.ID.Symbol, lookupDate);
129  if (futureChain == null)
130  {
131  // No matching contract listing rules entry was found
132  return null;
133  }
134 
135  foreach (var future in futureChain.OrderBy(s => s.ID.Date))
136  {
137  // Normalize by date first, normalize to a contract month date, then we want to get the contract
138  // month of the Future contract so we normalize by getting the delta between the expiration
139  // and the contract month.
140  var futureContractMonth = future.ID.Date.Date
141  .AddDays(-future.ID.Date.Day + 1)
142  .AddMonths(FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(future.ID.Symbol, future.ID.Date));
143 
144  // We want a contract that is either the same as the contract month or greater
145  if (futureContractMonth < futureOptionContractMonth)
146  {
147  continue;
148  }
149 
150  return futureContractMonth;
151  }
152 
153  // No matching/closest contract was found in the futures chain.
154  return null;
155  }
156 
157  /// <summary>
158  /// Searches for the closest future's contract month depending on whether the Future Option's contract month is
159  /// on an even or odd month.
160  /// </summary>
161  /// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
162  /// <param name="oddMonths">True if the Future Option's underlying future contract month is on odd months, false if on even months</param>
163  /// <returns>The underlying Future's contract month</returns>
164  private static DateTime ContractMonthEvenOddMonth(DateTime futureOptionContractMonth, bool oddMonths)
165  {
166  var monthEven = futureOptionContractMonth.Month % 2 == 0;
167  if (oddMonths && monthEven)
168  {
169  return futureOptionContractMonth.AddMonths(1);
170  }
171  if (!oddMonths && !monthEven)
172  {
173  return futureOptionContractMonth.AddMonths(1);
174  }
175 
176  return futureOptionContractMonth;
177  }
178 
179  /// <summary>
180  /// Sets the contract month to the third month for the first 3 months, then begins using the <see cref="ContractMonthEvenOddMonth"/> rule.
181  /// </summary>
182  /// <param name="futureOptionContractMonth">Future option contract month. Note that this is not the expiry of the Future Option.</param>
183  /// <param name="oddMonths">True if the Future Option's underlying future contract month is on odd months, false if on even months. Only used for months greater than 3 months</param>
184  /// <returns></returns>
185  private static DateTime ContractMonthYearStartThreeMonthsThenEvenOddMonthsSkipRule(DateTime futureOptionContractMonth, bool oddMonths)
186  {
187  if (futureOptionContractMonth.Month <= 3)
188  {
189  return new DateTime(futureOptionContractMonth.Year, 3, 1);
190  }
191 
192  return ContractMonthEvenOddMonth(futureOptionContractMonth, oddMonths);
193  }
194 
195  /// <summary>
196  /// Gets the theoretical (i.e. intermediate/naive) future contract month if we assumed a 1-1 mapping
197  /// between FOPs contract months and Futures contract months, i.e. they share the same contract month.
198  /// </summary>
199  /// <param name="canonicalFutureSymbol">Canonical future Symbol</param>
200  /// <param name="futureOptionExpirationDate">Future Option Expiration Date</param>
201  /// <returns>Contract month assuming that the Future Option and Future share the same contract month</returns>
202  private static DateTime GetFutureContractMonthNoRulesApplied(Symbol canonicalFutureSymbol, DateTime futureOptionExpirationDate)
203  {
204  var baseOptionExpiryMonthDate = new DateTime(futureOptionExpirationDate.Year, futureOptionExpirationDate.Month, 1);
205  if (!_futuresOptionsExpiryDelta.ContainsKey(canonicalFutureSymbol.ID.Symbol))
206  {
207  // For contracts like CL, they have no expiry delta between the Futures and FOPs, so we hit this path.
208  // However, it does have a delta between its expiry and contract month, which we adjust here before
209  // claiming that `baseOptionExpiryMonthDate` is the future's contract month.
210  var futuresExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFutureSymbol)(baseOptionExpiryMonthDate);
211  var futuresDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(canonicalFutureSymbol.ID.Symbol, futuresExpiry);
212 
213  return baseOptionExpiryMonthDate.AddMonths(futuresDelta);
214  }
215 
216  return baseOptionExpiryMonthDate.AddMonths(_futuresOptionsExpiryDelta[canonicalFutureSymbol.ID.Symbol]);
217  }
218  }
219 }