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