Lean  $LEAN_TAG$
ImpliedVolatility.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using MathNet.Numerics.RootFinding;
18 using Python.Runtime;
19 using QuantConnect.Data;
20 using QuantConnect.Logging;
21 using QuantConnect.Python;
22 using QuantConnect.Util;
23 
25 {
26  /// <summary>
27  /// Implied Volatility indicator that calculate the IV of an option using Black-Scholes Model
28  /// </summary>
30  {
31  private decimal _impliedVolatility;
32  private Func<decimal, decimal, decimal> SmoothingFunction;
33 
34  /// <summary>
35  /// Initializes a new instance of the ImpliedVolatility class
36  /// </summary>
37  /// <param name="name">The name of this indicator</param>
38  /// <param name="option">The option to be tracked</param>
39  /// <param name="riskFreeRateModel">Risk-free rate model</param>
40  /// <param name="dividendYieldModel">Dividend yield model</param>
41  /// <param name="mirrorOption">The mirror option for parity calculation</param>
42  /// <param name="optionModel">The option pricing model used to estimate IV</param>
43  public ImpliedVolatility(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, IDividendYieldModel dividendYieldModel, Symbol mirrorOption = null,
44  OptionPricingModelType? optionModel = null)
45  : base(name, option, riskFreeRateModel, dividendYieldModel, mirrorOption, optionModel)
46  {
47  if (mirrorOption != null)
48  {
49  // Default smoothing function will be assuming Law of One Price hold,
50  // so both call and put will have the same IV
51  // and using on OTM/ATM options to calculate the IV
52  // by assuming extra volatility coming from extrinsic value
53  SmoothingFunction = (impliedVol, mirrorImpliedVol) =>
54  {
55  if (Strike > UnderlyingPrice && Right == OptionRight.Put)
56  {
57  return mirrorImpliedVol;
58  }
59  else if (Strike < UnderlyingPrice && Right == OptionRight.Call)
60  {
61  return mirrorImpliedVol;
62  }
63  return impliedVol;
64  };
65  }
66  }
67 
68  /// <summary>
69  /// Initializes a new instance of the ImpliedVolatility class
70  /// </summary>
71  /// <param name="option">The option to be tracked</param>
72  /// <param name="riskFreeRateModel">Risk-free rate model</param>
73  /// <param name="dividendYieldModel">Dividend yield model</param>
74  /// <param name="mirrorOption">The mirror option for parity calculation</param>
75  /// <param name="optionModel">The option pricing model used to estimate IV</param>
76  public ImpliedVolatility(Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, IDividendYieldModel dividendYieldModel,
77  Symbol mirrorOption = null, OptionPricingModelType? optionModel = null)
78  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYieldModel},{optionModel})", option, riskFreeRateModel,
79  dividendYieldModel, mirrorOption, optionModel)
80  {
81  }
82 
83  /// <summary>
84  /// Initializes a new instance of the ImpliedVolatility class
85  /// </summary>
86  /// <param name="name">The name of this indicator</param>
87  /// <param name="option">The option to be tracked</param>
88  /// <param name="riskFreeRateModel">Risk-free rate model</param>
89  /// <param name="dividendYieldModel">Dividend yield model</param>
90  /// <param name="mirrorOption">The mirror option for parity calculation</param>
91  /// <param name="optionModel">The option pricing model used to estimate IV</param>
92  public ImpliedVolatility(string name, Symbol option, PyObject riskFreeRateModel, PyObject dividendYieldModel, Symbol mirrorOption = null,
93  OptionPricingModelType? optionModel = null)
94  : this(name, option, RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel),
95  DividendYieldModelPythonWrapper.FromPyObject(dividendYieldModel), mirrorOption, optionModel)
96  {
97  }
98 
99  /// <summary>
100  /// Initializes a new instance of the ImpliedVolatility class
101  /// </summary>
102  /// <param name="option">The option to be tracked</param>
103  /// <param name="riskFreeRateModel">Risk-free rate model</param>
104  /// <param name="dividendYieldModel">Dividend yield model</param>
105  /// <param name="mirrorOption">The mirror option for parity calculation</param>
106  /// <param name="optionModel">The option pricing model used to estimate IV</param>
107  public ImpliedVolatility(Symbol option, PyObject riskFreeRateModel, PyObject dividendYieldModel, Symbol mirrorOption = null,
108  OptionPricingModelType? optionModel = null)
109  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYieldModel},{optionModel})", option,
110  riskFreeRateModel, dividendYieldModel, mirrorOption, optionModel)
111  {
112  }
113 
114  /// <summary>
115  /// Initializes a new instance of the ImpliedVolatility class
116  /// </summary>
117  /// <param name="name">The name of this indicator</param>
118  /// <param name="option">The option to be tracked</param>
119  /// <param name="riskFreeRateModel">Risk-free rate model</param>
120  /// <param name="dividendYield">Dividend yield, as a constant</param>
121  /// <param name="mirrorOption">The mirror option for parity calculation</param>
122  /// <param name="optionModel">The option pricing model used to estimate IV</param>
123  public ImpliedVolatility(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
124  OptionPricingModelType? optionModel = null)
125  : this(name, option, riskFreeRateModel, new ConstantDividendYieldModel(dividendYield), mirrorOption, optionModel)
126  {
127  }
128 
129  /// <summary>
130  /// Initializes a new instance of the ImpliedVolatility class
131  /// </summary>
132  /// <param name="option">The option to be tracked</param>
133  /// <param name="riskFreeRateModel">Risk-free rate model</param>
134  /// <param name="dividendYield">Dividend yield, as a constant</param>
135  /// <param name="mirrorOption">The mirror option for parity calculation</param>
136  /// <param name="optionModel">The option pricing model used to estimate IV</param>
137  public ImpliedVolatility(Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
138  OptionPricingModelType? optionModel = null)
139  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYield},{optionModel})", option, riskFreeRateModel, dividendYield,
140  mirrorOption, optionModel)
141  {
142  }
143 
144  /// <summary>
145  /// Initializes a new instance of the ImpliedVolatility class
146  /// </summary>
147  /// <param name="name">The name of this indicator</param>
148  /// <param name="option">The option to be tracked</param>
149  /// <param name="riskFreeRateModel">Risk-free rate model</param>
150  /// <param name="dividendYield">Dividend yield, as a constant</param>
151  /// <param name="mirrorOption">The mirror option for parity calculation</param>
152  /// <param name="optionModel">The option pricing model used to estimate IV</param>
153  public ImpliedVolatility(string name, Symbol option, PyObject riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
154  OptionPricingModelType? optionModel = null)
155  : this(name, option, RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel),
156  new ConstantDividendYieldModel(dividendYield), mirrorOption, optionModel)
157  {
158  }
159 
160  /// <summary>
161  /// Initializes a new instance of the ImpliedVolatility class
162  /// </summary>
163  /// <param name="option">The option to be tracked</param>
164  /// <param name="riskFreeRateModel">Risk-free rate model</param>
165  /// <param name="dividendYield">Dividend yield, as a constant</param>
166  /// <param name="mirrorOption">The mirror option for parity calculation</param>
167  /// <param name="optionModel">The option pricing model used to estimate IV</param>
168  public ImpliedVolatility(Symbol option, PyObject riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
169  OptionPricingModelType? optionModel = null)
170  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYield},{optionModel})", option, riskFreeRateModel,
171  dividendYield, mirrorOption, optionModel)
172  {
173  }
174 
175  /// <summary>
176  /// Initializes a new instance of the ImpliedVolatility class
177  /// </summary>
178  /// <param name="name">The name of this indicator</param>
179  /// <param name="option">The option to be tracked</param>
180  /// <param name="riskFreeRate">Risk-free rate, as a constant</param>
181  /// <param name="dividendYield">Dividend yield, as a constant</param>
182  /// <param name="mirrorOption">The mirror option for parity calculation</param>
183  /// <param name="optionModel">The option pricing model used to estimate IV</param>
184  public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05m, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
185  OptionPricingModelType? optionModel = null)
186  : this(name, option, new ConstantRiskFreeRateInterestRateModel(riskFreeRate), new ConstantDividendYieldModel(dividendYield), mirrorOption, optionModel)
187  {
188  }
189 
190  /// <summary>
191  /// Initializes a new instance of the ImpliedVolatility class
192  /// </summary>
193  /// <param name="option">The option to be tracked</param>
194  /// <param name="riskFreeRate">Risk-free rate, as a constant</param>
195  /// <param name="dividendYield">Dividend yield, as a constant</param>
196  /// <param name="mirrorOption">The mirror option for parity calculation</param>
197  /// <param name="optionModel">The option pricing model used to estimate IV</param>
198  public ImpliedVolatility(Symbol option, decimal riskFreeRate = 0.05m, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
199  OptionPricingModelType? optionModel = null)
200  : this($"IV({option},{mirrorOption},{riskFreeRate},{dividendYield},{optionModel})", option, riskFreeRate,
201  dividendYield, mirrorOption, optionModel)
202  {
203  }
204 
205  /// <summary>
206  /// Set the smoothing function of IV, using both call and put IV value
207  /// </summary>
208  /// <param name="function">the smoothing function</param>
209  public void SetSmoothingFunction(Func<decimal, decimal, decimal> function)
210  {
211  SmoothingFunction = function;
212  }
213 
214  /// <summary>
215  /// Set the smoothing function of IV, using both call and put IV value
216  /// </summary>
217  /// <param name="function">the smoothing function</param>
218  public void SetSmoothingFunction(PyObject function)
219  {
220  SmoothingFunction = PythonUtil.ToFunc<decimal, decimal, decimal>(function);
221  }
222 
223  private bool _isReady => Price.Current.Time == UnderlyingPrice.Current.Time && Price.IsReady && UnderlyingPrice.IsReady;
224 
225  /// <summary>
226  /// Gets a flag indicating when this indicator is ready and fully initialized
227  /// </summary>
228  public override bool IsReady => UseMirrorContract ? _isReady && Price.Current.Time == OppositePrice.Current.Time && OppositePrice.IsReady : _isReady;
229 
230  /// <summary>
231  /// Computes the next value
232  /// </summary>
233  /// <param name="input">The input given to the indicator</param>
234  /// <returns>The input is returned unmodified.</returns>
235  protected override decimal Calculate(IndicatorDataPoint input)
236  {
237  if (input.Symbol == OptionSymbol)
238  {
239  Price.Update(input.EndTime, input.Price);
240  }
241  else if (input.Symbol == _oppositeOptionSymbol)
242  {
243  OppositePrice.Update(input.EndTime, input.Price);
244  }
245  else if (input.Symbol == _underlyingSymbol)
246  {
247  UnderlyingPrice.Update(input.EndTime, input.Price);
248  }
249  else
250  {
251  throw new ArgumentException("The given symbol was not target or reference symbol");
252  }
253 
254  var time = Price.Current.Time;
255  if (_isReady)
256  {
257  if (UseMirrorContract)
258  {
259  if (time != OppositePrice.Current.Time)
260  {
261  return _impliedVolatility;
262  }
263  }
264 
266  DividendYield.Update(time, _dividendYieldModel.GetDividendYield(time, UnderlyingPrice.Current.Value));
267 
268  var timeTillExpiry = Convert.ToDecimal(OptionGreekIndicatorsHelper.TimeTillExpiry(Expiry, time));
269  _impliedVolatility = CalculateIV(timeTillExpiry);
270  }
271 
272  return _impliedVolatility;
273  }
274 
275  // Calculate the theoretical option price
276  private double TheoreticalPrice(double volatility, double spotPrice, double strikePrice, double timeTillExpiry, double riskFreeRate,
277  double dividendYield, OptionRight optionType, OptionPricingModelType? optionModel = null)
278  {
279  if (timeTillExpiry <= 0)
280  {
281  return 0;
282  }
283 
284  return optionModel switch
285  {
286  // Binomial model also follows BSM process (log-normal)
287  OptionPricingModelType.BinomialCoxRossRubinstein => OptionGreekIndicatorsHelper.CRRTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
288  OptionPricingModelType.ForwardTree => OptionGreekIndicatorsHelper.ForwardTreeTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
289  _ => OptionGreekIndicatorsHelper.BlackTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
290  };
291  }
292 
293  /// <summary>
294  /// Computes the IV of the option
295  /// </summary>
296  /// <param name="timeTillExpiry">the time until expiration in years</param>
297  /// <returns>Smoothened IV of the option</returns>
298  protected virtual decimal CalculateIV(decimal timeTillExpiry)
299  {
300  var underlyingPrice = (double)UnderlyingPrice.Current.Value;
301  var strike = (double)Strike;
302  var timeTillExpiryDouble = (double)timeTillExpiry;
303  var riskFreeRate = (double)RiskFreeRate.Current.Value;
304  var dividendYield = (double)DividendYield.Current.Value;
305  var optionPrice = (double)Price.Current.Value;
306 
307  var impliedVol = CalculateIV(OptionSymbol, strike, timeTillExpiryDouble, Right, optionPrice, underlyingPrice, riskFreeRate,
308  dividendYield, _optionModel);
309 
310  if (UseMirrorContract)
311  {
312  var mirrorOptionPrice = (double)OppositePrice.Current.Value;
313 
314  var mirrorImpliedVol = CalculateIV(_oppositeOptionSymbol, strike, timeTillExpiryDouble, _oppositeOptionSymbol.ID.OptionRight,
315  mirrorOptionPrice, underlyingPrice, riskFreeRate, dividendYield, _optionModel);
316 
317  if (mirrorImpliedVol.HasValue)
318  {
319  if (impliedVol.HasValue)
320  {
321  // use 'SmoothingFunction' if both calculations succeeded
322  return SmoothingFunction(impliedVol.Value, mirrorImpliedVol.Value);
323  }
324  return mirrorImpliedVol.Value;
325  }
326  }
327 
328  return impliedVol ?? 0;
329  }
330 
331  private decimal? CalculateIV(Symbol optionSymbol, double strike, double timeTillExpiry, OptionRight right, double optionPrice, double underlyingPrice,
332  double riskFreeRate, double dividendYield, OptionPricingModelType optionModel)
333  {
334  GetRootFindingMethodParameters(optionSymbol, strike, timeTillExpiry, optionPrice, underlyingPrice, riskFreeRate, dividendYield,
335  optionModel, out var accuracy, out var lowerBound, out var upperBound);
336 
337  decimal? impliedVol = null;
338  try
339  {
340  Func<double, double> f = (vol) => TheoreticalPrice(vol, underlyingPrice, strike, timeTillExpiry, riskFreeRate, dividendYield, right, optionModel) - optionPrice;
341  impliedVol = Convert.ToDecimal(Brent.FindRoot(f, lowerBound, upperBound, accuracy, 100));
342  }
343  catch
344  {
345  Log.Error("ImpliedVolatility.CalculateIV(): Fail to converge, returning 0.");
346  }
347 
348  return impliedVol;
349  }
350 
351  private void GetRootFindingMethodParameters(Symbol optionSymbol, double strike, double timeTillExpiry, double optionPrice,
352  double underlyingPrice, double riskFreeRate, double dividendYield, OptionPricingModelType optionModel,
353  out double accuracy, out double lowerBound, out double upperBound)
354  {
355  // Set the accuracy as a factor of the option price when possible
356  accuracy = Math.Max(1e-4, 1e-4 * optionPrice);
357  lowerBound = 1e-7;
358  upperBound = 4.0;
359 
360  // Use BSM as initial guess to get a better range for root finding
361  if (optionModel != OptionPricingModelType.BlackScholes)
362  {
363  var initialGuess = (double)(CalculateIV(optionSymbol, strike, timeTillExpiry, optionSymbol.ID.OptionRight, optionPrice,
364  underlyingPrice, riskFreeRate, dividendYield, OptionPricingModelType.BlackScholes) ?? 0);
365  if (initialGuess != 0)
366  {
367  lowerBound = Math.Max(lowerBound, initialGuess * 0.5);
368  upperBound = Math.Min(upperBound, initialGuess * 1.5);
369  }
370  }
371  }
372  }
373 }