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  /// <summary>
224  /// Computes the next value
225  /// </summary>
226  /// <returns>The input is returned unmodified.</returns>
227  protected override decimal ComputeIndicator()
228  {
229  var time = Price.Current.Time;
230 
232  DividendYield.Update(time, _dividendYieldModel.GetDividendYield(time, UnderlyingPrice.Current.Value));
233 
234  var timeTillExpiry = Convert.ToDecimal(OptionGreekIndicatorsHelper.TimeTillExpiry(Expiry, time));
235  _impliedVolatility = CalculateIV(timeTillExpiry);
236 
237  return _impliedVolatility;
238  }
239 
240  // Calculate the theoretical option price
241  private static double TheoreticalPrice(double volatility, double spotPrice, double strikePrice, double timeTillExpiry, double riskFreeRate,
242  double dividendYield, OptionRight optionType, OptionPricingModelType? optionModel = null)
243  {
244  if (timeTillExpiry <= 0)
245  {
246  return 0;
247  }
248 
249  return optionModel switch
250  {
251  // Binomial model also follows BSM process (log-normal)
252  OptionPricingModelType.BinomialCoxRossRubinstein => OptionGreekIndicatorsHelper.CRRTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
253  OptionPricingModelType.ForwardTree => OptionGreekIndicatorsHelper.ForwardTreeTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
254  _ => OptionGreekIndicatorsHelper.BlackTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
255  };
256  }
257 
258  /// <summary>
259  /// Computes the IV of the option
260  /// </summary>
261  /// <param name="timeTillExpiry">the time until expiration in years</param>
262  /// <returns>Smoothened IV of the option</returns>
263  protected virtual decimal CalculateIV(decimal timeTillExpiry)
264  {
265  var underlyingPrice = (double)UnderlyingPrice.Current.Value;
266  var strike = (double)Strike;
267  var timeTillExpiryDouble = (double)timeTillExpiry;
268  var riskFreeRate = (double)RiskFreeRate.Current.Value;
269  var dividendYield = (double)DividendYield.Current.Value;
270  var optionPrice = (double)Price.Current.Value;
271 
272  var impliedVol = CalculateIV(OptionSymbol, strike, timeTillExpiryDouble, Right, optionPrice, underlyingPrice, riskFreeRate,
273  dividendYield, _optionModel);
274 
275  if (UseMirrorContract)
276  {
277  var mirrorOptionPrice = (double)OppositePrice.Current.Value;
278 
279  var mirrorImpliedVol = CalculateIV(_oppositeOptionSymbol, strike, timeTillExpiryDouble, _oppositeOptionSymbol.ID.OptionRight,
280  mirrorOptionPrice, underlyingPrice, riskFreeRate, dividendYield, _optionModel);
281 
282  if (mirrorImpliedVol.HasValue)
283  {
284  if (impliedVol.HasValue)
285  {
286  // use 'SmoothingFunction' if both calculations succeeded
287  return SmoothingFunction(impliedVol.Value, mirrorImpliedVol.Value);
288  }
289  return mirrorImpliedVol.Value;
290  }
291  }
292 
293  return impliedVol ?? 0;
294  }
295 
296  private decimal? CalculateIV(Symbol optionSymbol, double strike, double timeTillExpiry, OptionRight right, double optionPrice, double underlyingPrice,
297  double riskFreeRate, double dividendYield, OptionPricingModelType optionModel)
298  {
299  GetRootFindingMethodParameters(optionSymbol, strike, timeTillExpiry, optionPrice, underlyingPrice, riskFreeRate, dividendYield,
300  optionModel, out var accuracy, out var lowerBound, out var upperBound);
301 
302  decimal? impliedVol = null;
303  try
304  {
305  Func<double, double> f = (vol) => TheoreticalPrice(vol, underlyingPrice, strike, timeTillExpiry, riskFreeRate, dividendYield, right, optionModel) - optionPrice;
306  impliedVol = Convert.ToDecimal(Brent.FindRoot(f, lowerBound, upperBound, accuracy, 100));
307  }
308  catch
309  {
310  Log.Error("ImpliedVolatility.CalculateIV(): Fail to converge, returning 0.");
311  }
312 
313  return impliedVol;
314  }
315 
316  private void GetRootFindingMethodParameters(Symbol optionSymbol, double strike, double timeTillExpiry, double optionPrice,
317  double underlyingPrice, double riskFreeRate, double dividendYield, OptionPricingModelType optionModel,
318  out double accuracy, out double lowerBound, out double upperBound)
319  {
320  // Set the accuracy as a factor of the option price when possible
321  accuracy = Math.Max(1e-4, 1e-4 * optionPrice);
322  lowerBound = 1e-7;
323  upperBound = 4.0;
324 
325  // Use BSM as initial guess to get a better range for root finding
326  if (optionModel != OptionPricingModelType.BlackScholes)
327  {
328  var initialGuess = (double)(CalculateIV(optionSymbol, strike, timeTillExpiry, optionSymbol.ID.OptionRight, optionPrice,
329  underlyingPrice, riskFreeRate, dividendYield, OptionPricingModelType.BlackScholes) ?? 0);
330  if (initialGuess != 0)
331  {
332  lowerBound = Math.Max(lowerBound, initialGuess * 0.5);
333  upperBound = Math.Min(upperBound, initialGuess * 1.5);
334  }
335  }
336  }
337  }
338 }