Lean  $LEAN_TAG$
OptionFilterUniverse.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 
17 using System;
18 using System.Collections.Generic;
19 using System.Linq;
20 using System.Runtime.CompilerServices;
21 using Python.Runtime;
22 using QuantConnect.Data;
27 
29 {
30  /// <summary>
31  /// Represents options symbols universe used in filtering.
32  /// </summary>
33  public class OptionFilterUniverse : ContractSecurityFilterUniverse<OptionFilterUniverse, OptionUniverse>
34  {
35  private Option.Option _option;
36 
37  // Fields used in relative strikes filter
38  private List<decimal> _uniqueStrikes;
39  private bool _refreshUniqueStrikes;
40  private DateTime _lastExchangeDate;
41  private readonly decimal _underlyingScaleFactor = 1;
42 
43  /// <summary>
44  /// The underlying price data
45  /// </summary>
46  protected BaseData UnderlyingInternal { get; set; }
47 
48  /// <summary>
49  /// The underlying price data
50  /// </summary>
51  public BaseData Underlying
52  {
53  get
54  {
55  return UnderlyingInternal;
56  }
57  }
58 
59  /// <summary>
60  /// Constructs OptionFilterUniverse
61  /// </summary>
62  /// <param name="option">The canonical option chain security</param>
64  {
65  _option = option;
66  _underlyingScaleFactor = option.SymbolProperties.StrikeMultiplier;
67  }
68 
69  /// <summary>
70  /// Constructs OptionFilterUniverse
71  /// </summary>
72  /// <remarks>Used for testing only</remarks>
73  public OptionFilterUniverse(Option.Option option, IEnumerable<OptionUniverse> allData, BaseData underlying, decimal underlyingScaleFactor = 1)
74  : base(allData, underlying.EndTime)
75  {
76  _option = option;
77  UnderlyingInternal = underlying;
78  _refreshUniqueStrikes = true;
79  _underlyingScaleFactor = underlyingScaleFactor;
80  }
81 
82  /// <summary>
83  /// Refreshes this option filter universe and allows specifying if the exchange date changed from last call
84  /// </summary>
85  /// <param name="allContractsData">All data for the option contracts</param>
86  /// <param name="underlying">The current underlying last data point</param>
87  /// <param name="localTime">The current local time</param>
88  public void Refresh(IEnumerable<OptionUniverse> allContractsData, BaseData underlying, DateTime localTime)
89  {
90  base.Refresh(allContractsData, localTime);
91 
92  UnderlyingInternal = underlying;
93  _refreshUniqueStrikes = _lastExchangeDate != localTime.Date;
94  _lastExchangeDate = localTime.Date;
95  }
96 
97  /// <summary>
98  /// Determine if the given Option contract symbol is standard
99  /// </summary>
100  /// <returns>True if standard</returns>
101  protected override bool IsStandard(Symbol symbol)
102  {
103  switch (symbol.SecurityType)
104  {
105  case SecurityType.FutureOption:
106  return FutureOptionSymbol.IsStandard(symbol);
107  case SecurityType.IndexOption:
108  return IndexOptionSymbol.IsStandard(symbol);
109  default:
110  return OptionSymbol.IsStandard(symbol);
111  }
112  }
113 
114  /// <summary>
115  /// Creates a new instance of the data type for the given symbol
116  /// </summary>
117  /// <returns>A data instance for the given symbol</returns>
118  protected override OptionUniverse CreateDataInstance(Symbol symbol)
119  {
120  return new OptionUniverse()
121  {
122  Symbol = symbol,
123  Time = LocalTime
124  };
125  }
126 
127  /// <summary>
128  /// Adjusts the date to the next trading day if the current date is not a trading day, so that expiration filter is properly applied.
129  /// e.g. Selection for Mondays happen on Friday midnight (Saturday start), so if the minimum time to expiration is, say 0,
130  /// contracts expiring on Monday would be filtered out if the date is not properly adjusted to the next trading day (Monday).
131  /// </summary>
132  /// <param name="referenceDate">The date to be adjusted</param>
133  /// <returns>The adjusted date</returns>
134  protected override DateTime AdjustExpirationReferenceDate(DateTime referenceDate)
135  {
136  // Check whether the reference time is a tradable date:
137  if (!_option.Exchange.Hours.IsDateOpen(referenceDate))
138  {
139  referenceDate = _option.Exchange.Hours.GetNextTradingDay(referenceDate);
140  }
141 
142  return referenceDate;
143  }
144 
145  /// <summary>
146  /// Applies filter selecting options contracts based on a range of strikes in relative terms
147  /// </summary>
148  /// <param name="minStrike">The minimum strike relative to the underlying price, for example, -1 would filter out contracts further than 1 strike below market price</param>
149  /// <param name="maxStrike">The maximum strike relative to the underlying price, for example, +1 would filter out contracts further than 1 strike above market price</param>
150  /// <returns>Universe with filter applied</returns>
151  public OptionFilterUniverse Strikes(int minStrike, int maxStrike)
152  {
153  if (UnderlyingInternal == null)
154  {
155  return this;
156  }
157 
158  if (_refreshUniqueStrikes || _uniqueStrikes == null)
159  {
160  // Each day we need to recompute the unique strikes list.
161  _uniqueStrikes = AllSymbols.Select(x => x.ID.StrikePrice)
162  .Distinct()
163  .OrderBy(strikePrice => strikePrice)
164  .ToList();
165  _refreshUniqueStrikes = false;
166  }
167 
168  // find the current price in the list of strikes
169  // When computing the strike prices we need to take into account
170  // that some option's strike prices are based on a fraction of
171  // the underlying. Thus we need to scale the underlying internal
172  // price so that we can find it among the strike prices
173  // using BinarySearch() method(as it is used below)
174  var exactPriceFound = true;
175  var index = _uniqueStrikes.BinarySearch(UnderlyingInternal.Price / _underlyingScaleFactor);
176 
177  // Return value of BinarySearch (from MSDN):
178  // The zero-based index of item in the sorted List<T>, if item is found;
179  // otherwise, a negative number that is the bitwise complement of the index of the next element that is larger than item
180  // or, if there is no larger element, the bitwise complement of Count.
181  if (index < 0)
182  {
183  // exact price not found
184  exactPriceFound = false;
185 
186  if (index == ~_uniqueStrikes.Count)
187  {
188  // there is no greater price, return empty
189  return Empty();
190  }
191 
192  index = ~index;
193  }
194 
195  // compute the bounds, no need to worry about rounding and such
196  var indexMinPrice = index + minStrike;
197  var indexMaxPrice = index + maxStrike;
198  if (!exactPriceFound)
199  {
200  if (minStrike < 0 && maxStrike > 0)
201  {
202  indexMaxPrice--;
203  }
204  else if (minStrike > 0)
205  {
206  indexMinPrice--;
207  indexMaxPrice--;
208  }
209  }
210 
211  if (indexMinPrice < 0)
212  {
213  indexMinPrice = 0;
214  }
215  else if (indexMinPrice >= _uniqueStrikes.Count)
216  {
217  // price out of range: return empty
218  return Empty();
219  }
220 
221  if (indexMaxPrice < 0)
222  {
223  // price out of range: return empty
224  return Empty();
225  }
226  if (indexMaxPrice >= _uniqueStrikes.Count)
227  {
228  indexMaxPrice = _uniqueStrikes.Count - 1;
229  }
230 
231  var minPrice = _uniqueStrikes[indexMinPrice];
232  var maxPrice = _uniqueStrikes[indexMaxPrice];
233 
234  Data = Data
235  .Where(data =>
236  {
237  var price = data.ID.StrikePrice;
238  return price >= minPrice && price <= maxPrice;
239  }
240  ).ToList();
241 
242  return this;
243  }
244 
245  /// <summary>
246  /// Sets universe of call options (if any) as a selection
247  /// </summary>
248  /// <returns>Universe with filter applied</returns>
250  {
251  return Contracts(contracts => contracts.Where(x => x.Symbol.ID.OptionRight == OptionRight.Call));
252  }
253 
254  /// <summary>
255  /// Sets universe of put options (if any) as a selection
256  /// </summary>
257  /// <returns>Universe with filter applied</returns>
259  {
260  return Contracts(contracts => contracts.Where(x => x.Symbol.ID.OptionRight == OptionRight.Put));
261  }
262 
263  /// <summary>
264  /// Sets universe of a single call contract with the closest match to criteria given
265  /// </summary>
266  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
267  /// <param name="strikeFromAtm">The desire strike price distance from the current underlying price</param>
268  /// <remarks>Applicable to Naked Call, Covered Call, and Protective Call Option Strategy</remarks>
269  /// <returns>Universe with filter applied</returns>
270  public OptionFilterUniverse NakedCall(int minDaysTillExpiry = 30, decimal strikeFromAtm = 0)
271  {
272  return SingleContract(OptionRight.Call, minDaysTillExpiry, strikeFromAtm);
273  }
274 
275  /// <summary>
276  /// Sets universe of a single put contract with the closest match to criteria given
277  /// </summary>
278  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
279  /// <param name="strikeFromAtm">The desire strike price distance from the current underlying price</param>
280  /// <remarks>Applicable to Naked Put, Covered Put, and Protective Put Option Strategy</remarks>
281  /// <returns>Universe with filter applied</returns>
282  public OptionFilterUniverse NakedPut(int minDaysTillExpiry = 30, decimal strikeFromAtm = 0)
283  {
284  return SingleContract(OptionRight.Put, minDaysTillExpiry, strikeFromAtm);
285  }
286 
287  private OptionFilterUniverse SingleContract(OptionRight right, int minDaysTillExpiry = 30, decimal strikeFromAtm = 0)
288  {
289  // Select the expiry as the nearest to set days later
290  var contractsForExpiry = GetContractsForExpiry(AllSymbols, minDaysTillExpiry);
291  var contracts = contractsForExpiry.Where(x => x.ID.OptionRight == right).ToList();
292  if (contracts.Count == 0)
293  {
294  return Empty();
295  }
296 
297  // Select strike price
298  var strike = GetStrike(contracts, strikeFromAtm);
299  var selected = contracts.Single(x => x.ID.StrikePrice == strike);
300 
301  return SymbolList(new List<Symbol> { selected });
302  }
303 
304  /// <summary>
305  /// Sets universe of 2 call contracts with the same expiry and different strike prices, with closest match to the criteria given
306  /// </summary>
307  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
308  /// <param name="higherStrikeFromAtm">The desire strike price distance from the current underlying price of the higher strike price</param>
309  /// <param name="lowerStrikeFromAtm">The desire strike price distance from the current underlying price of the lower strike price</param>
310  /// <remarks>Applicable to Bear Call Spread and Bull Call Spread Option Strategy</remarks>
311  /// <returns>Universe with filter applied</returns>
312  public OptionFilterUniverse CallSpread(int minDaysTillExpiry = 30, decimal higherStrikeFromAtm = 5, decimal? lowerStrikeFromAtm = null)
313  {
314  return Spread(OptionRight.Call, minDaysTillExpiry, higherStrikeFromAtm, lowerStrikeFromAtm);
315  }
316 
317  /// <summary>
318  /// Sets universe of 2 put contracts with the same expiry and different strike prices, with closest match to the criteria given
319  /// </summary>
320  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
321  /// <param name="higherStrikeFromAtm">The desire strike price distance from the current underlying price of the higher strike price</param>
322  /// <param name="lowerStrikeFromAtm">The desire strike price distance from the current underlying price of the lower strike price</param>
323  /// <remarks>Applicable to Bear Put Spread and Bull Put Spread Option Strategy</remarks>
324  /// <returns>Universe with filter applied</returns>
325  public OptionFilterUniverse PutSpread(int minDaysTillExpiry = 30, decimal higherStrikeFromAtm = 5, decimal? lowerStrikeFromAtm = null)
326  {
327  return Spread(OptionRight.Put, minDaysTillExpiry, higherStrikeFromAtm, lowerStrikeFromAtm);
328  }
329 
330  private OptionFilterUniverse Spread(OptionRight right, int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal? lowerStrikeFromAtm = null)
331  {
332  if (!lowerStrikeFromAtm.HasValue)
333  {
334  lowerStrikeFromAtm = -higherStrikeFromAtm;
335  }
336 
337  if (higherStrikeFromAtm <= lowerStrikeFromAtm)
338  {
339  throw new ArgumentException("Spread(): strike price arguments must be in descending order, "
340  + $"{nameof(higherStrikeFromAtm)}, {nameof(lowerStrikeFromAtm)}");
341  }
342 
343  // Select the expiry as the nearest to set days later
344  var contractsForExpiry = GetContractsForExpiry(AllSymbols, minDaysTillExpiry);
345  var contracts = contractsForExpiry.Where(x => x.ID.OptionRight == right).ToList();
346  if (contracts.Count == 0)
347  {
348  return Empty();
349  }
350 
351  // Select the strike prices with the set spread range
352  var lowerStrike = GetStrike(contracts, (decimal)lowerStrikeFromAtm);
353  var lowerStrikeContract = contracts.Single(x => x.ID.StrikePrice == lowerStrike);
354  var higherStrikeContracts = contracts.Where(x => x.ID.StrikePrice > lowerStrike).ToList();
355  if (higherStrikeContracts.Count == 0)
356  {
357  return Empty();
358  }
359 
360  var higherStrike = GetStrike(higherStrikeContracts, higherStrikeFromAtm);
361  var higherStrikeContract = higherStrikeContracts.Single(x => x.ID.StrikePrice == higherStrike);
362 
363  return SymbolList(new List<Symbol> { lowerStrikeContract, higherStrikeContract });
364  }
365 
366  /// <summary>
367  /// Sets universe of 2 call contracts with the same strike price and different expiration dates, with closest match to the criteria given
368  /// </summary>
369  /// <param name="strikeFromAtm">The desire strike price distance from the current underlying price</param>
370  /// <param name="minNearDaysTillExpiry">The mininum days till expiry of the closer contract from the current time, closest expiry will be selected</param>
371  /// <param name="minFarDaysTillExpiry">The mininum days till expiry of the further conrtact from the current time, closest expiry will be selected</param>
372  /// <remarks>Applicable to Long and Short Call Calendar Spread Option Strategy</remarks>
373  /// <returns>Universe with filter applied</returns>
374  public OptionFilterUniverse CallCalendarSpread(decimal strikeFromAtm = 0, int minNearDaysTillExpiry = 30, int minFarDaysTillExpiry = 60)
375  {
376  return CalendarSpread(OptionRight.Call, strikeFromAtm, minNearDaysTillExpiry, minFarDaysTillExpiry);
377  }
378 
379  /// <summary>
380  /// Sets universe of 2 put contracts with the same strike price and different expiration dates, with closest match to the criteria given
381  /// </summary>
382  /// <param name="strikeFromAtm">The desire strike price distance from the current underlying price</param>
383  /// <param name="minNearDaysTillExpiry">The mininum days till expiry of the closer contract from the current time, closest expiry will be selected</param>
384  /// <param name="minFarDaysTillExpiry">The mininum days till expiry of the further conrtact from the current time, closest expiry will be selected</param>
385  /// <remarks>Applicable to Long and Short Put Calendar Spread Option Strategy</remarks>
386  /// <returns>Universe with filter applied</returns>
387  public OptionFilterUniverse PutCalendarSpread(decimal strikeFromAtm = 0, int minNearDaysTillExpiry = 30, int minFarDaysTillExpiry = 60)
388  {
389  return CalendarSpread(OptionRight.Put, strikeFromAtm, minNearDaysTillExpiry, minFarDaysTillExpiry);
390  }
391 
392  private OptionFilterUniverse CalendarSpread(OptionRight right, decimal strikeFromAtm, int minNearDaysTillExpiry, int minFarDaysTillExpiry)
393  {
394  if (minFarDaysTillExpiry <= minNearDaysTillExpiry)
395  {
396  throw new ArgumentException("CalendarSpread(): expiry arguments must be in ascending order, "
397  + $"{nameof(minNearDaysTillExpiry)}, {nameof(minFarDaysTillExpiry)}");
398  }
399 
400  if (minNearDaysTillExpiry < 0)
401  {
402  throw new ArgumentException("CalendarSpread(): near expiry argument must be positive.");
403  }
404 
405  // Select the set strike
406  var strike = GetStrike(AllSymbols, strikeFromAtm);
407  var contracts = AllSymbols.Where(x => x.ID.StrikePrice == strike && x.ID.OptionRight == right).ToList();
408 
409  // Select the expiries
410  var nearExpiryContract = GetContractsForExpiry(contracts, minNearDaysTillExpiry).SingleOrDefault();
411  if (nearExpiryContract == null)
412  {
413  return Empty();
414  }
415 
416  var furtherContracts = contracts.Where(x => x.ID.Date > nearExpiryContract.ID.Date).ToList();
417  var farExpiryContract = GetContractsForExpiry(furtherContracts, minFarDaysTillExpiry).SingleOrDefault();
418  if (farExpiryContract == null)
419  {
420  return Empty();
421  }
422 
423  return SymbolList(new List<Symbol> { nearExpiryContract, farExpiryContract });
424  }
425 
426  /// <summary>
427  /// Sets universe of an OTM call contract and an OTM put contract with the same expiry, with closest match to the criteria given
428  /// </summary>
429  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
430  /// <param name="callStrikeFromAtm">The desire strike price distance from the current underlying price of the OTM call. It must be positive.</param>
431  /// <param name="putStrikeFromAtm">The desire strike price distance from the current underlying price of the OTM put. It must be negative.</param>
432  /// <remarks>Applicable to Long and Short Strangle Option Strategy</remarks>
433  /// <returns>Universe with filter applied</returns>
434  public OptionFilterUniverse Strangle(int minDaysTillExpiry = 30, decimal callStrikeFromAtm = 5, decimal putStrikeFromAtm = -5)
435  {
436  if (callStrikeFromAtm <= 0)
437  {
438  throw new ArgumentException($"Strangle(): {nameof(callStrikeFromAtm)} must be positive");
439  }
440 
441  if (putStrikeFromAtm >= 0)
442  {
443  throw new ArgumentException($"Strangle(): {nameof(putStrikeFromAtm)} must be negative");
444  }
445 
446  return CallPutSpread(minDaysTillExpiry, callStrikeFromAtm, putStrikeFromAtm, true);
447  }
448 
449  /// <summary>
450  /// Sets universe of an ATM call contract and an ATM put contract with the same expiry, with closest match to the criteria given
451  /// </summary>
452  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
453  /// <remarks>Applicable to Long and Short Straddle Option Strategy</remarks>
454  /// <returns>Universe with filter applied</returns>
455  public OptionFilterUniverse Straddle(int minDaysTillExpiry = 30)
456  {
457  return CallPutSpread(minDaysTillExpiry, 0, 0);
458  }
459 
460  /// <summary>
461  /// Sets universe of a call contract and a put contract with the same expiry but lower strike price, with closest match to the criteria given
462  /// </summary>
463  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
464  /// <param name="callStrikeFromAtm">The desire strike price distance from the current underlying price of the call.</param>
465  /// <param name="putStrikeFromAtm">The desire strike price distance from the current underlying price of the put.</param>
466  /// <remarks>Applicable to Protective Collar Option Strategy</remarks>
467  /// <returns>Universe with filter applied</returns>
468  public OptionFilterUniverse ProtectiveCollar(int minDaysTillExpiry = 30, decimal callStrikeFromAtm = 5, decimal putStrikeFromAtm = -5)
469  {
470  if (callStrikeFromAtm <= putStrikeFromAtm)
471  {
472  throw new ArgumentException("ProtectiveCollar(): strike price arguments must be in descending order, "
473  + $"{nameof(callStrikeFromAtm)}, {nameof(putStrikeFromAtm)}");
474  }
475 
476  var filtered = CallPutSpread(minDaysTillExpiry, callStrikeFromAtm, putStrikeFromAtm);
477 
478  var callStrike = filtered.Single(x => x.ID.OptionRight == OptionRight.Call).ID.StrikePrice;
479  var putStrike = filtered.Single(x => x.ID.OptionRight == OptionRight.Put).ID.StrikePrice;
480  if (callStrike <= putStrike)
481  {
482  return Empty();
483  }
484 
485  return filtered;
486  }
487 
488  /// <summary>
489  /// Sets universe of a call contract and a put contract with the same expiry and strike price, with closest match to the criteria given
490  /// </summary>
491  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
492  /// <param name="strikeFromAtm">The desire strike price distance from the current underlying price</param>
493  /// <remarks>Applicable to Conversion and Reverse Conversion Option Strategy</remarks>
494  /// <returns>Universe with filter applied</returns>
495  public OptionFilterUniverse Conversion(int minDaysTillExpiry = 30, decimal strikeFromAtm = 5)
496  {
497  return CallPutSpread(minDaysTillExpiry, strikeFromAtm, strikeFromAtm);
498  }
499 
500  private OptionFilterUniverse CallPutSpread(int minDaysTillExpiry, decimal callStrikeFromAtm, decimal putStrikeFromAtm, bool otm = false)
501  {
502  // Select the expiry as the nearest to set days later
503  var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
504 
505  var calls = contracts.Where(x => x.ID.OptionRight == OptionRight.Call).ToList();
506  var puts = contracts.Where(x => x.ID.OptionRight == OptionRight.Put).ToList();
507 
508  if (otm)
509  {
510  calls = calls.Where(x => x.ID.StrikePrice > Underlying.Price).ToList();
511  puts = puts.Where(x => x.ID.StrikePrice < Underlying.Price).ToList();
512  }
513 
514  if (calls.Count == 0 || puts.Count == 0)
515  {
516  return Empty();
517  }
518 
519  // Select the strike prices with the set spread range
520  var callStrike = GetStrike(calls, callStrikeFromAtm);
521  var call = calls.Single(x => x.ID.StrikePrice == callStrike);
522  var putStrike = GetStrike(puts, putStrikeFromAtm);
523  var put = puts.Single(x => x.ID.StrikePrice == putStrike);
524 
525  // Select the contracts
526  return SymbolList(new List<Symbol> { call, put });
527  }
528 
529  /// <summary>
530  /// Sets universe of an ITM call, an ATM call, and an OTM call with the same expiry and equal strike price distance, with closest match to the criteria given
531  /// </summary>
532  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
533  /// <param name="strikeSpread">The desire strike price distance of the ITM call and the OTM call from the current underlying price</param>
534  /// <remarks>Applicable to Long and Short Call Butterfly Option Strategy</remarks>
535  /// <returns>Universe with filter applied</returns>
536  public OptionFilterUniverse CallButterfly(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
537  {
538  return Butterfly(OptionRight.Call, minDaysTillExpiry, strikeSpread);
539  }
540 
541  /// <summary>
542  /// Sets universe of an ITM put, an ATM put, and an OTM put with the same expiry and equal strike price distance, with closest match to the criteria given
543  /// </summary>
544  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
545  /// <param name="strikeSpread">The desire strike price distance of the ITM put and the OTM put from the current underlying price</param>
546  /// <remarks>Applicable to Long and Short Put Butterfly Option Strategy</remarks>
547  /// <returns>Universe with filter applied</returns>
548  public OptionFilterUniverse PutButterfly(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
549  {
550  return Butterfly(OptionRight.Put, minDaysTillExpiry, strikeSpread);
551  }
552 
553  private OptionFilterUniverse Butterfly(OptionRight right, int minDaysTillExpiry, decimal strikeSpread)
554  {
555  if (strikeSpread <= 0)
556  {
557  throw new ArgumentException("ProtectiveCollar(): strikeSpread arguments must be positive");
558  }
559 
560  // Select the expiry as the nearest to set days later
561  var contractsForExpiry = GetContractsForExpiry(AllSymbols, minDaysTillExpiry);
562  var contracts = contractsForExpiry.Where(x => x.ID.OptionRight == right).ToList();
563  if (contracts.Count == 0)
564  {
565  return Empty();
566  }
567 
568  // Select the strike prices with the set spread range
569  var atmStrike = GetStrike(contracts, 0m);
570  var lowerStrike = GetStrike(contracts.Where(x => x.ID.StrikePrice < Underlying.Price && x.ID.StrikePrice < atmStrike), -strikeSpread);
571  var upperStrike = -1m;
572  if (lowerStrike != decimal.MaxValue)
573  {
574  upperStrike = atmStrike * 2 - lowerStrike;
575  }
576 
577  // Select the contracts
578  var filtered = this.Where(x =>
579  x.ID.Date == contracts[0].ID.Date && x.ID.OptionRight == right &&
580  (x.ID.StrikePrice == atmStrike || x.ID.StrikePrice == lowerStrike || x.ID.StrikePrice == upperStrike));
581  if (filtered.Count() != 3)
582  {
583  return Empty();
584  }
585  return filtered;
586  }
587 
588  /// <summary>
589  /// Sets universe of an OTM call, an ATM call, an ATM put, and an OTM put with the same expiry and equal strike price distance, with closest match to the criteria given
590  /// </summary>
591  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
592  /// <param name="strikeSpread">The desire strike price distance of the OTM call and the OTM put from the current underlying price</param>
593  /// <remarks>Applicable to Long and Short Iron Butterfly Option Strategy</remarks>
594  /// <returns>Universe with filter applied</returns>
595  public OptionFilterUniverse IronButterfly(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
596  {
597  if (strikeSpread <= 0)
598  {
599  throw new ArgumentException("IronButterfly(): strikeSpread arguments must be positive");
600  }
601 
602  // Select the expiry as the nearest to set days later
603  var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
604  var calls = contracts.Where(x => x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice > Underlying.Price).ToList();
605  var puts = contracts.Where(x => x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice < Underlying.Price).ToList();
606 
607  if (calls.Count == 0 || puts.Count == 0)
608  {
609  return Empty();
610  }
611 
612  // Select the strike prices with the set spread range
613  var atmStrike = GetStrike(contracts, 0);
614  var otmCallStrike = GetStrike(calls.Where(x => x.ID.StrikePrice > atmStrike), strikeSpread);
615  var otmPutStrike = -1m;
616  if (otmCallStrike != decimal.MaxValue)
617  {
618  otmPutStrike = atmStrike * 2 - otmCallStrike;
619  }
620 
621  var filtered = this.Where(x =>
622  x.ID.Date == contracts[0].ID.Date && (
623  x.ID.StrikePrice == atmStrike ||
624  (x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice == otmCallStrike) ||
625  (x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice == otmPutStrike)
626  ));
627  if (filtered.Count() != 4)
628  {
629  return Empty();
630  }
631  return filtered;
632  }
633 
634  /// <summary>
635  /// Sets universe of a far-OTM call, a near-OTM call, a near-OTM put, and a far-OTM put with the same expiry
636  /// and equal strike price distance between both calls and both puts, with closest match to the criteria given
637  /// </summary>
638  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
639  /// <param name="nearStrikeSpread">The desire strike price distance of the near-to-expiry call and the near-to-expiry put from the current underlying price</param>
640  /// <param name="farStrikeSpread">The desire strike price distance of the further-to-expiry call and the further-to-expiry put from the current underlying price</param>
641  /// <remarks>Applicable to Long and Short Iron Condor Option Strategy</remarks>
642  /// <returns>Universe with filter applied</returns>
643  public OptionFilterUniverse IronCondor(int minDaysTillExpiry = 30, decimal nearStrikeSpread = 5, decimal farStrikeSpread = 10)
644  {
645  if (nearStrikeSpread <= 0 || farStrikeSpread <= 0)
646  {
647  throw new ArgumentException("IronCondor(): strike arguments must be positive, "
648  + $"{nameof(nearStrikeSpread)}, {nameof(farStrikeSpread)}");
649  }
650 
651  if (nearStrikeSpread >= farStrikeSpread)
652  {
653  throw new ArgumentException("IronCondor(): strike arguments must be in ascending orders, "
654  + $"{nameof(nearStrikeSpread)}, {nameof(farStrikeSpread)}");
655  }
656 
657  // Select the expiry as the nearest to set days later
658  var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
659  var calls = contracts.Where(x => x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice > Underlying.Price).ToList();
660  var puts = contracts.Where(x => x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice < Underlying.Price).ToList();
661 
662  if (calls.Count == 0 || puts.Count == 0)
663  {
664  return Empty();
665  }
666 
667  // Select the strike prices with the set spread range
668  var nearCallStrike = GetStrike(calls, nearStrikeSpread);
669  var nearPutStrike = GetStrike(puts, -nearStrikeSpread);
670  var farCallStrike = GetStrike(calls.Where(x => x.ID.StrikePrice > nearCallStrike), farStrikeSpread);
671  var farPutStrike = -1m;
672  if (farCallStrike != decimal.MaxValue)
673  {
674  farPutStrike = nearPutStrike - farCallStrike + nearCallStrike;
675  }
676 
677  // Select the contracts
678  var filtered = this.Where(x =>
679  x.ID.Date == contracts[0].ID.Date && (
680  (x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice == nearCallStrike) ||
681  (x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice == nearPutStrike) ||
682  (x.ID.OptionRight == OptionRight.Call && x.ID.StrikePrice == farCallStrike) ||
683  (x.ID.OptionRight == OptionRight.Put && x.ID.StrikePrice == farPutStrike)
684  ));
685  if (filtered.Count() != 4)
686  {
687  return Empty();
688  }
689  return filtered;
690  }
691 
692  /// <summary>
693  /// Sets universe of an OTM call, an ITM call, an OTM put, and an ITM put with the same expiry with closest match to the criteria given.
694  /// The OTM call has the same strike as the ITM put, while the same holds for the ITM call and the OTM put
695  /// </summary>
696  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
697  /// <param name="strikeSpread">The desire strike price distance of the OTM call and the OTM put from the current underlying price</param>
698  /// <remarks>Applicable to Long and Short Box Spread Option Strategy</remarks>
699  /// <returns>Universe with filter applied</returns>
700  public OptionFilterUniverse BoxSpread(int minDaysTillExpiry = 30, decimal strikeSpread = 5)
701  {
702  if (strikeSpread <= 0)
703  {
704  throw new ArgumentException($"BoxSpread(): strike arguments must be positive, {nameof(strikeSpread)}");
705  }
706 
707  // Select the expiry as the nearest to set days later
708  var contracts = GetContractsForExpiry(AllSymbols, minDaysTillExpiry).ToList();
709  if (contracts.Count == 0)
710  {
711  return Empty();
712  }
713 
714  // Select the strike prices with the set spread range
715  var higherStrike = GetStrike(contracts.Where(x => x.ID.StrikePrice > Underlying.Price), strikeSpread);
716  var lowerStrike = GetStrike(contracts.Where(x => x.ID.StrikePrice < higherStrike && x.ID.StrikePrice < Underlying.Price), -strikeSpread);
717 
718  // Select the contracts
719  var filtered = this.Where(x =>
720  (x.ID.StrikePrice == higherStrike || x.ID.StrikePrice == lowerStrike) &&
721  x.ID.Date == contracts[0].ID.Date);
722  if (filtered.Count() != 4)
723  {
724  return Empty();
725  }
726  return filtered;
727  }
728 
729  /// <summary>
730  /// Sets universe of 2 call and 2 put contracts with the same strike price and 2 expiration dates, with closest match to the criteria given
731  /// </summary>
732  /// <param name="strikeFromAtm">The desire strike price distance from the current underlying price</param>
733  /// <param name="minNearDaysTillExpiry">The mininum days till expiry of the closer contract from the current time, closest expiry will be selected</param>
734  /// <param name="minFarDaysTillExpiry">The mininum days till expiry of the further conrtact from the current time, closest expiry will be selected</param>
735  /// <remarks>Applicable to Long and Short Jelly Roll Option Strategy</remarks>
736  /// <returns>Universe with filter applied</returns>
737  public OptionFilterUniverse JellyRoll(decimal strikeFromAtm = 0, int minNearDaysTillExpiry = 30, int minFarDaysTillExpiry = 60)
738  {
739  if (minFarDaysTillExpiry <= minNearDaysTillExpiry)
740  {
741  throw new ArgumentException("JellyRoll(): expiry arguments must be in ascending order, "
742  + $"{nameof(minNearDaysTillExpiry)}, {nameof(minFarDaysTillExpiry)}");
743  }
744 
745  if (minNearDaysTillExpiry < 0)
746  {
747  throw new ArgumentException("JellyRoll(): near expiry argument must be positive.");
748  }
749 
750  // Select the set strike
751  var strike = AllSymbols.OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + strikeFromAtm))
752  .First().ID.StrikePrice;
753  var contracts = AllSymbols.Where(x => x.ID.StrikePrice == strike && x.ID.OptionRight == OptionRight.Call).ToList();
754 
755  // Select the expiries
756  var nearExpiryContract = GetContractsForExpiry(contracts, minNearDaysTillExpiry).SingleOrDefault();
757  if (nearExpiryContract == null)
758  {
759  return Empty();
760  }
761  var nearExpiry = nearExpiryContract.ID.Date;
762 
763  var furtherContracts = contracts.Where(x => x.ID.Date > nearExpiryContract.ID.Date).ToList();
764  var farExpiryContract = GetContractsForExpiry(furtherContracts, minFarDaysTillExpiry).SingleOrDefault();
765  if (farExpiryContract == null)
766  {
767  return Empty();
768  }
769  var farExpiry = farExpiryContract.ID.Date;
770 
771  var filtered = this.Where(x => x.ID.StrikePrice == strike && (x.ID.Date == nearExpiry || x.ID.Date == farExpiry));
772  if (filtered.Count() != 4)
773  {
774  return Empty();
775  }
776  return filtered;
777  }
778 
779  /// <summary>
780  /// Sets universe of 3 call contracts with the same expiry and different strike prices, with closest match to the criteria given
781  /// </summary>
782  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
783  /// <param name="higherStrikeFromAtm">The desire strike price distance from the current underlying price of the higher strike price</param>
784  /// <param name="middleStrikeFromAtm">The desire strike price distance from the current underlying price of the middle strike price</param>
785  /// <param name="lowerStrikeFromAtm">The desire strike price distance from the current underlying price of the lower strike price</param>
786  /// <remarks>Applicable to Bear Call Ladder and Bull Call Ladder Option Strategy</remarks>
787  /// <returns>Universe with filter applied</returns>
788  public OptionFilterUniverse CallLadder(int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal middleStrikeFromAtm, decimal lowerStrikeFromAtm)
789  {
790  return Ladder(OptionRight.Call, minDaysTillExpiry, higherStrikeFromAtm, middleStrikeFromAtm, lowerStrikeFromAtm);
791  }
792 
793  /// <summary>
794  /// Sets universe of 3 put contracts with the same expiry and different strike prices, with closest match to the criteria given
795  /// </summary>
796  /// <param name="minDaysTillExpiry">The minimum days till expiry from the current time, closest expiry will be selected</param>
797  /// <param name="higherStrikeFromAtm">The desire strike price distance from the current underlying price of the higher strike price</param>
798  /// <param name="middleStrikeFromAtm">The desire strike price distance from the current underlying price of the middle strike price</param>
799  /// <param name="lowerStrikeFromAtm">The desire strike price distance from the current underlying price of the lower strike price</param>
800  /// <remarks>Applicable to Bear Put Ladder and Bull Put Ladder Option Strategy</remarks>
801  /// <returns>Universe with filter applied</returns>
802  public OptionFilterUniverse PutLadder(int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal middleStrikeFromAtm, decimal lowerStrikeFromAtm)
803  {
804  return Ladder(OptionRight.Put, minDaysTillExpiry, higherStrikeFromAtm, middleStrikeFromAtm, lowerStrikeFromAtm);
805  }
806 
807  /// <summary>
808  /// Applies the filter to the universe selecting the contracts with Delta between the given range
809  /// </summary>
810  /// <param name="min">The minimum Delta value</param>
811  /// <param name="max">The maximum Delta value</param>
812  /// <returns>Universe with filter applied</returns>
813  public OptionFilterUniverse Delta(decimal min, decimal max)
814  {
815  ValidateSecurityTypeForSupportedFilters(nameof(Delta));
816  return this.Where(contractData => contractData.Greeks.Delta >= min && contractData.Greeks.Delta <= max);
817  }
818 
819  /// <summary>
820  /// Applies the filter to the universe selecting the contracts with Delta between the given range.
821  /// Alias for <see cref="Delta(decimal, decimal)"/>
822  /// </summary>
823  /// <param name="min">The minimum Delta value</param>
824  /// <param name="max">The maximum Delta value</param>
825  /// <returns>Universe with filter applied</returns>
826  public OptionFilterUniverse D(decimal min, decimal max)
827  {
828  return Delta(min, max);
829  }
830 
831  /// <summary>
832  /// Applies the filter to the universe selecting the contracts with Gamma between the given range
833  /// </summary>
834  /// <param name="min">The minimum Gamma value</param>
835  /// <param name="max">The maximum Gamma value</param>
836  /// <returns>Universe with filter applied</returns>
837  public OptionFilterUniverse Gamma(decimal min, decimal max)
838  {
839  ValidateSecurityTypeForSupportedFilters(nameof(Gamma));
840  return this.Where(contractData => contractData.Greeks.Gamma >= min && contractData.Greeks.Gamma <= max);
841  }
842 
843  /// <summary>
844  /// Applies the filter to the universe selecting the contracts with Gamma between the given range.
845  /// Alias for <see cref="Gamma(decimal, decimal)"/>
846  /// </summary>
847  /// <param name="min">The minimum Gamma value</param>
848  /// <param name="max">The maximum Gamma value</param>
849  /// <returns>Universe with filter applied</returns>
850  public OptionFilterUniverse G(decimal min, decimal max)
851  {
852  return Gamma(min, max);
853  }
854 
855  /// <summary>
856  /// Applies the filter to the universe selecting the contracts with Theta between the given range
857  /// </summary>
858  /// <param name="min">The minimum Theta value</param>
859  /// <param name="max">The maximum Theta value</param>
860  /// <returns>Universe with filter applied</returns>
861  public OptionFilterUniverse Theta(decimal min, decimal max)
862  {
863  ValidateSecurityTypeForSupportedFilters(nameof(Theta));
864  return this.Where(contractData => contractData.Greeks.Theta >= min && contractData.Greeks.Theta <= max);
865  }
866 
867  /// <summary>
868  /// Applies the filter to the universe selecting the contracts with Theta between the given range.
869  /// Alias for <see cref="Theta(decimal, decimal)"/>
870  /// </summary>
871  /// <param name="min">The minimum Theta value</param>
872  /// <param name="max">The maximum Theta value</param>
873  /// <returns>Universe with filter applied</returns>
874  public OptionFilterUniverse T(decimal min, decimal max)
875  {
876  return Theta(min, max);
877  }
878 
879  /// <summary>
880  /// Applies the filter to the universe selecting the contracts with Vega between the given range
881  /// </summary>
882  /// <param name="min">The minimum Vega value</param>
883  /// <param name="max">The maximum Vega value</param>
884  /// <returns>Universe with filter applied</returns>
885  public OptionFilterUniverse Vega(decimal min, decimal max)
886  {
887  ValidateSecurityTypeForSupportedFilters(nameof(Vega));
888  return this.Where(contractData => contractData.Greeks.Vega >= min && contractData.Greeks.Vega <= max);
889  }
890 
891  /// <summary>
892  /// Applies the filter to the universe selecting the contracts with Vega between the given range.
893  /// Alias for <see cref="Vega(decimal, decimal)"/>
894  /// </summary>
895  /// <param name="min">The minimum Vega value</param>
896  /// <param name="max">The maximum Vega value</param>
897  /// <returns>Universe with filter applied</returns>
898  public OptionFilterUniverse V(decimal min, decimal max)
899  {
900  return Vega(min, max);
901  }
902 
903  /// <summary>
904  /// Applies the filter to the universe selecting the contracts with Rho between the given range
905  /// </summary>
906  /// <param name="min">The minimum Rho value</param>
907  /// <param name="max">The maximum Rho value</param>
908  /// <returns>Universe with filter applied</returns>
909  public OptionFilterUniverse Rho(decimal min, decimal max)
910  {
911  ValidateSecurityTypeForSupportedFilters(nameof(Rho));
912  return this.Where(contractData => contractData.Greeks.Rho >= min && contractData.Greeks.Rho <= max);
913  }
914 
915  /// <summary>
916  /// Applies the filter to the universe selecting the contracts with Rho between the given range.
917  /// Alias for <see cref="Rho(decimal, decimal)"/>
918  /// </summary>
919  /// <param name="min">The minimum Rho value</param>
920  /// <param name="max">The maximum Rho value</param>
921  /// <returns>Universe with filter applied</returns>
922  public OptionFilterUniverse R(decimal min, decimal max)
923  {
924  return Rho(min, max);
925  }
926 
927  /// <summary>
928  /// Applies the filter to the universe selecting the contracts with implied volatility between the given range
929  /// </summary>
930  /// <param name="min">The minimum implied volatility value</param>
931  /// <param name="max">The maximum implied volatility value</param>
932  /// <returns>Universe with filter applied</returns>
933  public OptionFilterUniverse ImpliedVolatility(decimal min, decimal max)
934  {
935  ValidateSecurityTypeForSupportedFilters(nameof(ImpliedVolatility));
936  return this.Where(contractData => contractData.ImpliedVolatility >= min && contractData.ImpliedVolatility <= max);
937  }
938 
939  /// <summary>
940  /// Applies the filter to the universe selecting the contracts with implied volatility between the given range.
941  /// Alias for <see cref="ImpliedVolatility(decimal, decimal)"/>
942  /// </summary>
943  /// <param name="min">The minimum implied volatility value</param>
944  /// <param name="max">The maximum implied volatility value</param>
945  /// <returns>Universe with filter applied</returns>
946  public OptionFilterUniverse IV(decimal min, decimal max)
947  {
948  return ImpliedVolatility(min, max);
949  }
950 
951  /// <summary>
952  /// Applies the filter to the universe selecting the contracts with open interest between the given range
953  /// </summary>
954  /// <param name="min">The minimum open interest value</param>
955  /// <param name="max">The maximum open interest value</param>
956  /// <returns>Universe with filter applied</returns>
957  public OptionFilterUniverse OpenInterest(long min, long max)
958  {
959  ValidateSecurityTypeForSupportedFilters(nameof(OpenInterest));
960  return this.Where(contractData => contractData.OpenInterest >= min && contractData.OpenInterest <= max);
961  }
962 
963  /// <summary>
964  /// Applies the filter to the universe selecting the contracts with open interest between the given range.
965  /// Alias for <see cref="OpenInterest(long, long)"/>
966  /// </summary>
967  /// <param name="min">The minimum open interest value</param>
968  /// <param name="max">The maximum open interest value</param>
969  /// <returns>Universe with filter applied</returns>
970  public OptionFilterUniverse OI(long min, long max)
971  {
972  return OpenInterest(min, max);
973  }
974 
975  /// <summary>
976  /// Implicitly convert the universe to a list of symbols
977  /// </summary>
978  /// <param name="universe"></param>
979 #pragma warning disable CA1002 // Do not expose generic lists
980 #pragma warning disable CA2225 // Operator overloads have named alternates
981  public static implicit operator List<Symbol>(OptionFilterUniverse universe)
982  {
983  return universe.AllSymbols.ToList();
984  }
985 #pragma warning restore CA2225 // Operator overloads have named alternates
986 #pragma warning restore CA1002 // Do not expose generic lists
987 
988  private OptionFilterUniverse Ladder(OptionRight right, int minDaysTillExpiry, decimal higherStrikeFromAtm, decimal middleStrikeFromAtm, decimal lowerStrikeFromAtm)
989  {
990  if (higherStrikeFromAtm <= lowerStrikeFromAtm || higherStrikeFromAtm <= middleStrikeFromAtm || middleStrikeFromAtm <= lowerStrikeFromAtm)
991  {
992  throw new ArgumentException("Ladder(): strike price arguments must be in descending order, "
993  + $"{nameof(higherStrikeFromAtm)}, {nameof(middleStrikeFromAtm)}, {nameof(lowerStrikeFromAtm)}");
994  }
995 
996  // Select the expiry as the nearest to set days later
997  var contracts = GetContractsForExpiry(AllSymbols.Where(x => x.ID.OptionRight == right).ToList(), minDaysTillExpiry);
998 
999  // Select the strike prices with the set ladder range
1000  var lowerStrikeContract = contracts.OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + lowerStrikeFromAtm)).First();
1001  var middleStrikeContract = contracts.Where(x => x.ID.StrikePrice > lowerStrikeContract.ID.StrikePrice)
1002  .OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + middleStrikeFromAtm)).FirstOrDefault();
1003  if (middleStrikeContract == default)
1004  {
1005  return Empty();
1006  }
1007  var higherStrikeContract = contracts.Where(x => x.ID.StrikePrice > middleStrikeContract.ID.StrikePrice)
1008  .OrderBy(x => Math.Abs(Underlying.Price - x.ID.StrikePrice + higherStrikeFromAtm)).FirstOrDefault();
1009  if (higherStrikeContract == default)
1010  {
1011  return Empty();
1012  }
1013 
1014  return this.WhereContains(new List<Symbol> { lowerStrikeContract, middleStrikeContract, higherStrikeContract });
1015  }
1016 
1017  /// <summary>
1018  /// Will provide all contracts that respect a specific expiration filter
1019  /// </summary>
1020  /// <param name="symbols">Symbols source to use</param>
1021  /// <param name="minDaysTillExpiry">The desired minimum days till expiry</param>
1022  /// <returns>All symbols that respect a single expiration date</returns>
1023  private IEnumerable<Symbol> GetContractsForExpiry(IEnumerable<Symbol> symbols, int minDaysTillExpiry)
1024  {
1025  var leastExpiryAccepted = _lastExchangeDate.AddDays(minDaysTillExpiry);
1026  return symbols.Where(x => x.ID.Date >= leastExpiryAccepted)
1027  .GroupBy(x => x.ID.Date)
1028  .OrderBy(x => x.Key)
1029  .FirstOrDefault()
1030  // let's order the symbols too, to guarantee determinism
1031  ?.OrderBy(x => x.ID) ?? Enumerable.Empty<Symbol>();
1032  }
1033 
1034  /// <summary>
1035  /// Helper method that will select no contract
1036  /// </summary>
1037  private OptionFilterUniverse Empty()
1038  {
1039  Data = Enumerable.Empty<OptionUniverse>();
1040  return this;
1041  }
1042 
1043  /// <summary>
1044  /// Helper method that will select the given contract list
1045  /// </summary>
1046  private OptionFilterUniverse SymbolList(List<Symbol> contracts)
1047  {
1048  AllSymbols = contracts;
1049  return this;
1050  }
1051 
1052  private decimal GetStrike(IEnumerable<Symbol> symbols, decimal strikeFromAtm)
1053  {
1054  return symbols.OrderBy(x => Math.Abs(Underlying.Price + strikeFromAtm - x.ID.StrikePrice))
1055  .Select(x => x.ID.StrikePrice)
1056  .DefaultIfEmpty(decimal.MaxValue)
1057  .First();
1058  }
1059 
1060  [MethodImpl(MethodImplOptions.AggressiveInlining)]
1061  private void ValidateSecurityTypeForSupportedFilters(string filterName)
1062  {
1063  if (_option.Symbol.SecurityType == SecurityType.FutureOption)
1064  {
1065  throw new InvalidOperationException($"{filterName} filter is not supported for future options.");
1066  }
1067  }
1068  }
1069 
1070  /// <summary>
1071  /// Extensions for Linq support
1072  /// </summary>
1073  public static class OptionFilterUniverseEx
1074  {
1075  /// <summary>
1076  /// Filters universe
1077  /// </summary>
1078  /// <param name="universe">Universe to apply the filter too</param>
1079  /// <param name="predicate">Bool function to determine which Symbol are filtered</param>
1080  /// <returns>Universe with filter applied</returns>
1081  public static OptionFilterUniverse Where(this OptionFilterUniverse universe, Func<OptionUniverse, bool> predicate)
1082  {
1083  universe.Data = universe.Data.Where(predicate).ToList();
1084  return universe;
1085  }
1086 
1087  /// <summary>
1088  /// Filters universe
1089  /// </summary>
1090  /// <param name="universe">Universe to apply the filter too</param>
1091  /// <param name="predicate">Bool function to determine which Symbol are filtered</param>
1092  /// <returns>Universe with filter applied</returns>
1093  public static OptionFilterUniverse Where(this OptionFilterUniverse universe, PyObject predicate)
1094  {
1095  universe.Data = universe.Data.Where(predicate.ConvertToDelegate<Func<OptionUniverse, bool>>()).ToList();
1096  return universe;
1097  }
1098 
1099  /// <summary>
1100  /// Maps universe
1101  /// </summary>
1102  /// <param name="universe">Universe to apply the filter too</param>
1103  /// <param name="mapFunc">Symbol function to determine which Symbols are filtered</param>
1104  /// <returns>Universe with filter applied</returns>
1105  public static OptionFilterUniverse Select(this OptionFilterUniverse universe, Func<OptionUniverse, Symbol> mapFunc)
1106  {
1107  universe.AllSymbols = universe.Data.Select(mapFunc).ToList();
1108  return universe;
1109  }
1110 
1111  /// <summary>
1112  /// Maps universe
1113  /// </summary>
1114  /// <param name="universe">Universe to apply the filter too</param>
1115  /// <param name="mapFunc">Symbol function to determine which Symbols are filtered</param>
1116  /// <returns>Universe with filter applied</returns>
1117  public static OptionFilterUniverse Select(this OptionFilterUniverse universe, PyObject mapFunc)
1118  {
1119  return universe.Select(mapFunc.ConvertToDelegate<Func<OptionUniverse, Symbol>>());
1120  }
1121 
1122  /// <summary>
1123  /// Binds universe
1124  /// </summary>
1125  /// <param name="universe">Universe to apply the filter too</param>
1126  /// <param name="mapFunc">Symbol function to determine which Symbols are filtered</param>
1127  /// <returns>Universe with filter applied</returns>
1128  public static OptionFilterUniverse SelectMany(this OptionFilterUniverse universe, Func<OptionUniverse, IEnumerable<Symbol>> mapFunc)
1129  {
1130  universe.AllSymbols = universe.Data.SelectMany(mapFunc).ToList();
1131  return universe;
1132  }
1133 
1134  /// <summary>
1135  /// Binds universe
1136  /// </summary>
1137  /// <param name="universe">Universe to apply the filter too</param>
1138  /// <param name="mapFunc">Symbol function to determine which Symbols are filtered</param>
1139  /// <returns>Universe with filter applied</returns>
1140  public static OptionFilterUniverse SelectMany(this OptionFilterUniverse universe, PyObject mapFunc)
1141  {
1142  return universe.SelectMany(mapFunc.ConvertToDelegate<Func<OptionUniverse, IEnumerable<Symbol>>>());
1143  }
1144 
1145  /// <summary>
1146  /// Updates universe to only contain the symbols in the list
1147  /// </summary>
1148  /// <param name="universe">Universe to apply the filter too</param>
1149  /// <param name="filterList">List of Symbols to keep in the Universe</param>
1150  /// <returns>Universe with filter applied</returns>
1151  public static OptionFilterUniverse WhereContains(this OptionFilterUniverse universe, List<Symbol> filterList)
1152  {
1153  universe.Data = universe.Data.Where(x => filterList.Contains(x)).ToList();
1154  return universe;
1155  }
1156 
1157  /// <summary>
1158  /// Updates universe to only contain the symbols in the list
1159  /// </summary>
1160  /// <param name="universe">Universe to apply the filter too</param>
1161  /// <param name="filterList">List of Symbols to keep in the Universe</param>
1162  /// <returns>Universe with filter applied</returns>
1163  public static OptionFilterUniverse WhereContains(this OptionFilterUniverse universe, PyObject filterList)
1164  {
1165  return universe.WhereContains(filterList.ConvertToSymbolEnumerable().ToList());
1166  }
1167  }
1168 }