Lean  $LEAN_TAG$
Option.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using Python.Runtime;
17 using QuantConnect.Data;
21 using QuantConnect.Orders;
26 using QuantConnect.Python;
28 using QuantConnect.Util;
29 using System;
30 using System.Collections.Generic;
31 
33 {
34  /// <summary>
35  /// Option Security Object Implementation for Option Assets
36  /// </summary>
37  /// <seealso cref="Security"/>
39  {
40  /// <summary>
41  /// The default number of days required to settle an equity sale
42  /// </summary>
43  public const int DefaultSettlementDays = 1;
44 
45  /// <summary>
46  /// The default time of day for settlement
47  /// </summary>
48  public static readonly TimeSpan DefaultSettlementTime = new (8, 0, 0);
49 
50  /// <summary>
51  /// Constructor for the option security
52  /// </summary>
53  /// <param name="exchangeHours">Defines the hours this exchange is open</param>
54  /// <param name="quoteCurrency">The cash object that represent the quote currency</param>
55  /// <param name="config">The subscription configuration for this security</param>
56  /// <param name="symbolProperties">The symbol properties for this security</param>
57  /// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
58  /// instances into units of the account currency</param>
59  /// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
60  /// <remarks>Used in testing</remarks>
61  public Option(SecurityExchangeHours exchangeHours,
63  Cash quoteCurrency,
64  OptionSymbolProperties symbolProperties,
65  ICurrencyConverter currencyConverter,
67  : this(config.Symbol,
68  quoteCurrency,
69  symbolProperties,
70  new OptionExchange(exchangeHours),
71  new OptionCache(),
73  new ImmediateFillModel(),
75  NullSlippageModel.Instance,
77  Securities.VolatilityModel.Null,
78  new OptionMarginModel(),
79  new OptionDataFilter(),
81  currencyConverter,
82  registeredTypes,
83  null)
84  {
85  AddData(config);
87  }
88 
89  /// <summary>
90  /// Constructor for the option security
91  /// </summary>
92  /// <param name="symbol">The symbol of the security</param>
93  /// <param name="exchangeHours">Defines the hours this exchange is open</param>
94  /// <param name="quoteCurrency">The cash object that represent the quote currency</param>
95  /// <param name="symbolProperties">The symbol properties for this security</param>
96  /// <param name="currencyConverter">Currency converter used to convert <see cref="CashAmount"/>
97  /// instances into units of the account currency</param>
98  /// <param name="registeredTypes">Provides all data types registered in the algorithm</param>
99  /// <param name="securityCache">Cache to store security information</param>
100  /// <param name="underlying">Future underlying security</param>
101  public Option(Symbol symbol,
102  SecurityExchangeHours exchangeHours,
103  Cash quoteCurrency,
104  OptionSymbolProperties symbolProperties,
105  ICurrencyConverter currencyConverter,
106  IRegisteredSecurityDataTypesProvider registeredTypes,
107  SecurityCache securityCache,
108  Security underlying)
109  : this(symbol,
110  quoteCurrency,
111  symbolProperties,
112  new OptionExchange(exchangeHours),
113  securityCache,
114  new OptionPortfolioModel(),
115  new ImmediateFillModel(),
117  NullSlippageModel.Instance,
119  Securities.VolatilityModel.Null,
120  new OptionMarginModel(),
121  new OptionDataFilter(),
123  currencyConverter,
124  registeredTypes,
125  underlying)
126  {
127  }
128 
129  /// <summary>
130  /// Creates instance of the Option class.
131  /// </summary>
132  /// <remarks>
133  /// Allows for the forwarding of the security configuration to the
134  /// base Security constructor
135  /// </remarks>
136  protected Option(Symbol symbol,
137  Cash quoteCurrency,
138  SymbolProperties symbolProperties,
139  SecurityExchange exchange,
140  SecurityCache cache,
141  ISecurityPortfolioModel portfolioModel,
142  IFillModel fillModel,
143  IFeeModel feeModel,
144  ISlippageModel slippageModel,
145  ISettlementModel settlementModel,
146  IVolatilityModel volatilityModel,
147  IBuyingPowerModel buyingPowerModel,
148  ISecurityDataFilter dataFilter,
149  IPriceVariationModel priceVariationModel,
150  ICurrencyConverter currencyConverter,
151  IRegisteredSecurityDataTypesProvider registeredTypesProvider,
152  Security underlying
153  ) : base(
154  symbol,
155  quoteCurrency,
156  symbolProperties,
157  exchange,
158  cache,
159  portfolioModel,
160  fillModel,
161  feeModel,
162  slippageModel,
163  settlementModel,
164  volatilityModel,
165  buyingPowerModel,
166  dataFilter,
167  priceVariationModel,
168  currencyConverter,
169  registeredTypesProvider,
170  Securities.MarginInterestRateModel.Null
171  )
172  {
173  ExerciseSettlement = SettlementType.PhysicalDelivery;
176  PriceModel = symbol.ID.OptionStyle switch
177  {
178  // CRR model has the best accuracy and speed suggested by
179  // Branka, Zdravka & Tea (2014). Numerical Methods versus Bjerksund and Stensland Approximations for American Options Pricing.
180  // International Journal of Economics and Management Engineering. 8:4.
181  // Available via: https://downloads.dxfeed.com/specifications/dxLibOptions/Numerical-Methods-versus-Bjerksund-and-Stensland-Approximations-for-American-Options-Pricing-.pdf
182  // Also refer to OptionPriceModelTests.MatchesIBGreeksBulk() test,
183  // we select the most accurate and computational efficient model
186  _ => throw new ArgumentException("Invalid OptionStyle")
187  };
188  Holdings = new OptionHolding(this, currencyConverter);
189  _symbolProperties = (OptionSymbolProperties)symbolProperties;
190  SetFilter(-1, 1, TimeSpan.Zero, TimeSpan.FromDays(35));
191  Underlying = underlying;
194  }
195 
196  // save off a strongly typed version of symbol properties
197  private readonly OptionSymbolProperties _symbolProperties;
198 
199  /// <summary>
200  /// Returns true if this is the option chain security, false if it is a specific option contract
201  /// </summary>
202  public bool IsOptionChain => Symbol.IsCanonical();
203 
204  /// <summary>
205  /// Returns true if this is a specific option contract security, false if it is the option chain security
206  /// </summary>
208 
209  /// <summary>
210  /// Gets the strike price
211  /// </summary>
212  public decimal StrikePrice => Symbol.ID.StrikePrice;
213 
214  /// <summary>
215  /// Gets the strike price multiplied by the strike multiplier
216  /// </summary>
217  public decimal ScaledStrikePrice
218  {
219  get;
220  private set;
221  }
222 
223  /// <summary>
224  /// Gets the expiration date
225  /// </summary>
226  public DateTime Expiry => Symbol.ID.Date;
227 
228  /// <summary>
229  /// Gets the right being purchased (call [right to buy] or put [right to sell])
230  /// </summary>
232 
233  /// <summary>
234  /// Gets the option style
235  /// </summary>
237 
238  /// <summary>
239  /// Gets the most recent bid price if available
240  /// </summary>
241  public override decimal BidPrice => Cache.BidPrice;
242 
243  /// <summary>
244  /// Gets the most recent ask price if available
245  /// </summary>
246  public override decimal AskPrice => Cache.AskPrice;
247 
248  /// <summary>
249  /// When the holder of an equity option exercises one contract, or when the writer of an equity option is assigned
250  /// an exercise notice on one contract, this unit of trade, usually 100 shares of the underlying security, changes hands.
251  /// </summary>
252  public int ContractUnitOfTrade
253  {
254  get
255  {
256  return _symbolProperties.ContractUnitOfTrade;
257  }
258  set
259  {
260  _symbolProperties.SetContractUnitOfTrade(value);
261  }
262  }
263 
264  /// <summary>
265  /// The contract multiplier for the option security
266  /// </summary>
267  public int ContractMultiplier
268  {
269  get
270  {
271  return (int)_symbolProperties.ContractMultiplier;
272  }
273  set
274  {
275  _symbolProperties.SetContractMultiplier(value);
276  }
277  }
278 
279  /// <summary>
280  /// Aggregate exercise amount or aggregate contract value. It is the total amount of cash one will pay (or receive) for the shares of the
281  /// underlying stock if he/she decides to exercise (or is assigned an exercise notice). This amount is not the premium paid or received for an equity option.
282  /// </summary>
283  public decimal GetAggregateExerciseAmount()
284  {
286  }
287 
288  /// <summary>
289  /// Returns the directional quantity of underlying shares that are going to change hands on exercise/assignment of all
290  /// contracts held by this account, taking into account the contract's <see cref="Right"/> as well as the contract's current
291  /// <see cref="ContractUnitOfTrade"/>, which may have recently changed due to a split/reverse split in the underlying security.
292  /// </summary>
293  /// <remarks>
294  /// Long option positions result in exercise while short option positions result in assignment. This function uses the term
295  /// exercise loosely to refer to both situations.
296  /// </remarks>
297  public decimal GetExerciseQuantity()
298  {
299  // negate Holdings.Quantity to match an equivalent order
301  }
302 
303  /// <summary>
304  /// Returns the directional quantity of underlying shares that are going to change hands on exercise/assignment of the
305  /// specified <paramref name="exerciseOrderQuantity"/>, taking into account the contract's <see cref="Right"/> as well
306  /// as the contract's current <see cref="ContractUnitOfTrade"/>, which may have recently changed due to a split/reverse
307  /// split in the underlying security.
308  /// </summary>
309  /// <remarks>
310  /// Long option positions result in exercise while short option positions result in assignment. This function uses the term
311  /// exercise loosely to refer to both situations.
312  /// </remarks>
313  /// <paramref name="exerciseOrderQuantity">The quantity of contracts being exercised as provided by the <see cref="OptionExerciseOrder"/>.
314  /// A negative value indicates exercise (we are long and the order quantity is negative to bring us (closer) to zero.
315  /// A positive value indicates assignment (we are short and the order quantity is positive to bring us (closer) to zero.</paramref>
316  public decimal GetExerciseQuantity(decimal exerciseOrderQuantity)
317  {
318  // when exerciseOrderQuantity > 0 [ we are short ]
319  // && right == call => we sell to contract holder => negative
320  // && right == put => we buy from contract holder => positive
321 
322  // when exerciseOrderQuantity < 0 [ we are long ]
323  // && right == call => we buy from contract holder => positive
324  // && right == put => we sell to contract holder => negative
325 
326  var sign = Right == OptionRight.Call ? -1 : 1;
327  return sign * exerciseOrderQuantity * ContractUnitOfTrade;
328  }
329 
330  /// <summary>
331  /// Checks if option is eligible for automatic exercise on expiration
332  /// </summary>
333  public bool IsAutoExercised(decimal underlyingPrice)
334  {
335  return GetIntrinsicValue(underlyingPrice) >= 0.01m;
336  }
337 
338  /// <summary>
339  /// Intrinsic value function of the option
340  /// </summary>
341  public decimal GetIntrinsicValue(decimal underlyingPrice)
342  {
343  return OptionPayoff.GetIntrinsicValue(underlyingPrice, ScaledStrikePrice, Right);
344  }
345 
346  /// <summary>
347  /// Option payoff function at expiration time
348  /// </summary>
349  /// <param name="underlyingPrice">The price of the underlying</param>
350  /// <returns></returns>
351  public decimal GetPayOff(decimal underlyingPrice)
352  {
353  return OptionPayoff.GetPayOff(underlyingPrice, ScaledStrikePrice, Right);
354  }
355 
356  /// <summary>
357  /// Option out of the money function
358  /// </summary>
359  /// <param name="underlyingPrice">The price of the underlying</param>
360  /// <returns></returns>
361  public decimal OutOfTheMoneyAmount(decimal underlyingPrice)
362  {
363  return Math.Max(0, Right == OptionRight.Call ? ScaledStrikePrice - underlyingPrice : underlyingPrice - ScaledStrikePrice);
364  }
365 
366  /// <summary>
367  /// Specifies if option contract has physical or cash settlement on exercise
368  /// </summary>
370  {
371  get; set;
372  }
373 
374  /// <summary>
375  /// Gets or sets the underlying security object.
376  /// </summary>
377  public Security Underlying
378  {
379  get; set;
380  }
381 
382  /// <summary>
383  /// Gets a reduced interface of the underlying security object.
384  /// </summary>
386 
387  /// <summary>
388  /// For this option security object, evaluates the specified option
389  /// contract to compute a theoretical price, IV and greeks
390  /// </summary>
391  /// <param name="slice">The current data slice. This can be used to access other information
392  /// available to the algorithm</param>
393  /// <param name="contract">The option contract to evaluate</param>
394  /// <returns>An instance of <see cref="OptionPriceModelResult"/> containing the theoretical
395  /// price of the specified option contract</returns>
397  {
398  return PriceModel.Evaluate(this, slice, contract);
399  }
400 
401  /// <summary>
402  /// Gets or sets the price model for this option security
403  /// </summary>
405  {
406  get; set;
407  }
408 
409  /// <summary>
410  /// Fill model used to produce fill events for this security
411  /// </summary>
413  {
414  get; set;
415  }
416 
417  /// <summary>
418  /// The automatic option assignment model
419  /// </summary>
421  {
422  get; set;
423  }
424 
425  /// <summary>
426  /// When enabled, approximates Greeks if corresponding pricing model didn't calculate exact numbers
427  /// </summary>
428  [Obsolete("This property has been deprecated. Please use QLOptionPriceModel.EnableGreekApproximation instead.")]
429  public bool EnableGreekApproximation
430  {
431  get
432  {
433  var model = PriceModel as QLOptionPriceModel;
434  if (model != null)
435  {
436  return model.EnableGreekApproximation;
437  }
438  return false;
439  }
440 
441  set
442  {
443  var model = PriceModel as QLOptionPriceModel;
444  if (model != null)
445  {
446  model.EnableGreekApproximation = value;
447  }
448  }
449  }
450 
451  /// <summary>
452  /// Gets or sets the contract filter
453  /// </summary>
455  {
456  get; set;
457  }
458 
459  /// <summary>
460  /// Sets the automatic option assignment model
461  /// </summary>
462  /// <param name="pyObject">The option assignment model to use</param>
463  public void SetOptionAssignmentModel(PyObject pyObject)
464  {
465  if (pyObject.TryConvert<IOptionAssignmentModel>(out var optionAssignmentModel))
466  {
467  // pure C# implementation
468  SetOptionAssignmentModel(optionAssignmentModel);
469  }
470  else if (Extensions.TryConvert<IOptionAssignmentModel>(pyObject, out _, allowPythonDerivative: true))
471  {
473  }
474  else
475  {
476  using(Py.GIL())
477  {
478  throw new ArgumentException($"SetOptionAssignmentModel: {pyObject.Repr()} is not a valid argument.");
479  }
480  }
481  }
482 
483  /// <summary>
484  /// Sets the automatic option assignment model
485  /// </summary>
486  /// <param name="optionAssignmentModel">The option assignment model to use</param>
487  public void SetOptionAssignmentModel(IOptionAssignmentModel optionAssignmentModel)
488  {
489  OptionAssignmentModel = optionAssignmentModel;
490  }
491 
492  /// <summary>
493  /// Sets the option exercise model
494  /// </summary>
495  /// <param name="pyObject">The option exercise model to use</param>
496  public void SetOptionExerciseModel(PyObject pyObject)
497  {
498  if (pyObject.TryConvert<IOptionExerciseModel>(out var optionExerciseModel))
499  {
500  // pure C# implementation
501  SetOptionExerciseModel(optionExerciseModel);
502  }
503  else if (Extensions.TryConvert<IOptionExerciseModel>(pyObject, out _, allowPythonDerivative: true))
504  {
506  }
507  else
508  {
509  using (Py.GIL())
510  {
511  throw new ArgumentException($"SetOptionExerciseModel: {pyObject.Repr()} is not a valid argument.");
512  }
513  }
514  }
515 
516  /// <summary>
517  /// Sets the option exercise model
518  /// </summary>
519  /// <param name="optionExerciseModel">The option exercise model to use</param>
520  public void SetOptionExerciseModel(IOptionExerciseModel optionExerciseModel)
521  {
522  OptionExerciseModel = optionExerciseModel;
523  }
524 
525  /// <summary>
526  /// Sets the <see cref="ContractFilter"/> to a new instance of the filter
527  /// using the specified min and max strike values. Contracts with expirations further than 35
528  /// days out will also be filtered.
529  /// </summary>
530  /// <param name="minStrike">The min strike rank relative to market price, for example, -1 would put
531  /// a lower bound of one strike under market price, where a +1 would put a lower bound of one strike
532  /// over market price</param>
533  /// <param name="maxStrike">The max strike rank relative to market place, for example, -1 would put
534  /// an upper bound of on strike under market price, where a +1 would be an upper bound of one strike
535  /// over market price</param>
536  public void SetFilter(int minStrike, int maxStrike)
537  {
538  SetFilterImp(universe => universe.Strikes(minStrike, maxStrike));
539  }
540 
541  /// <summary>
542  /// Sets the <see cref="ContractFilter"/> to a new instance of the filter
543  /// using the specified min and max strike and expiration range values
544  /// </summary>
545  /// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
546  /// would exclude contracts expiring in less than 10 days</param>
547  /// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
548  /// would exclude contracts expiring in more than 10 days</param>
549  public void SetFilter(TimeSpan minExpiry, TimeSpan maxExpiry)
550  {
551  SetFilterImp(universe => universe.Expiration(minExpiry, maxExpiry));
552  }
553 
554  /// <summary>
555  /// Sets the <see cref="ContractFilter"/> to a new instance of the filter
556  /// using the specified min and max strike and expiration range values
557  /// </summary>
558  /// <param name="minStrike">The min strike rank relative to market price, for example, -1 would put
559  /// a lower bound of one strike under market price, where a +1 would put a lower bound of one strike
560  /// over market price</param>
561  /// <param name="maxStrike">The max strike rank relative to market place, for example, -1 would put
562  /// an upper bound of on strike under market price, where a +1 would be an upper bound of one strike
563  /// over market price</param>
564  /// <param name="minExpiry">The minimum time until expiry to include, for example, TimeSpan.FromDays(10)
565  /// would exclude contracts expiring in less than 10 days</param>
566  /// <param name="maxExpiry">The maximum time until expiry to include, for example, TimeSpan.FromDays(10)
567  /// would exclude contracts expiring in more than 10 days</param>
568  public void SetFilter(int minStrike, int maxStrike, TimeSpan minExpiry, TimeSpan maxExpiry)
569  {
570  SetFilterImp(universe => universe
571  .Strikes(minStrike, maxStrike)
572  .Expiration(minExpiry, maxExpiry));
573  }
574 
575  /// <summary>
576  /// Sets the <see cref="ContractFilter"/> to a new instance of the filter
577  /// using the specified min and max strike and expiration range values
578  /// </summary>
579  /// <param name="minStrike">The min strike rank relative to market price, for example, -1 would put
580  /// a lower bound of one strike under market price, where a +1 would put a lower bound of one strike
581  /// over market price</param>
582  /// <param name="maxStrike">The max strike rank relative to market place, for example, -1 would put
583  /// an upper bound of on strike under market price, where a +1 would be an upper bound of one strike
584  /// over market price</param>
585  /// <param name="minExpiryDays">The minimum time, expressed in days, until expiry to include, for example, 10
586  /// would exclude contracts expiring in less than 10 days</param>
587  /// <param name="maxExpiryDays">The maximum time, expressed in days, until expiry to include, for example, 10
588  /// would exclude contracts expiring in more than 10 days</param>
589  public void SetFilter(int minStrike, int maxStrike, int minExpiryDays, int maxExpiryDays)
590  {
591  SetFilterImp(universe => universe
592  .Strikes(minStrike, maxStrike)
593  .Expiration(minExpiryDays, maxExpiryDays));
594  }
595 
596  /// <summary>
597  /// Sets the <see cref="ContractFilter"/> to a new universe selection function
598  /// </summary>
599  /// <param name="universeFunc">new universe selection function</param>
600  public void SetFilter(Func<OptionFilterUniverse, OptionFilterUniverse> universeFunc)
601  {
603  {
604  var optionUniverse = universe as OptionFilterUniverse;
605  var result = universeFunc(optionUniverse);
606  return result.ApplyTypesFilter();
607  });
608  ContractFilter.Asynchronous = false;
609  }
610 
611  /// <summary>
612  /// Sets the <see cref="ContractFilter"/> to a new universe selection function
613  /// </summary>
614  /// <param name="universeFunc">new universe selection function</param>
615  public void SetFilter(PyObject universeFunc)
616  {
618  {
619  var optionUniverse = universe as OptionFilterUniverse;
620  using (Py.GIL())
621  {
622  PyObject result = (universeFunc as dynamic)(optionUniverse);
623 
624  //Try to convert it to the possible outcomes and process it
625  //Must try filter first, if it is a filter and you try and convert it to
626  //list, TryConvert() with catch an exception. Later Python algo will break on
627  //this exception because we are using Py.GIL() and it will see the error set
628  OptionFilterUniverse filter;
629  List<Symbol> list;
630 
631  if ((result).TryConvert(out filter))
632  {
633  optionUniverse = filter;
634  }
635  else if ((result).TryConvert(out list))
636  {
637  optionUniverse = optionUniverse.WhereContains(list);
638  }
639  else
640  {
641  throw new ArgumentException($"QCAlgorithm.SetFilter: result type {result.GetPythonType()} from " +
642  $"filter function is not a valid argument, please return either a OptionFilterUniverse or a list of symbols");
643  }
644  }
645  return optionUniverse.ApplyTypesFilter();
646  });
647  ContractFilter.Asynchronous = false;
648  }
649 
650  /// <summary>
651  /// Sets the data normalization mode to be used by this security
652  /// </summary>
654  {
655  if (mode != DataNormalizationMode.Raw)
656  {
657  throw new ArgumentException("DataNormalizationMode.Raw must be used with options");
658  }
659 
660  base.SetDataNormalizationMode(mode);
661  }
662 
663  private void SetFilterImp(Func<OptionFilterUniverse, OptionFilterUniverse> universeFunc)
664  {
666  {
667  var optionUniverse = universe as OptionFilterUniverse;
668  var result = universeFunc(optionUniverse);
669  return result.ApplyTypesFilter();
670  });
671  }
672 
673  /// <summary>
674  /// Updates the symbol properties of this security
675  /// </summary>
676  internal override void UpdateSymbolProperties(SymbolProperties symbolProperties)
677  {
678  if (symbolProperties != null)
679  {
680  SymbolProperties = new OptionSymbolProperties(symbolProperties);
681  }
682  }
683  }
684 }