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