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;
21 using QuantConnect.Logging;
22 using QuantConnect.Python;
23 using QuantConnect.Util;
24 
26 {
27  /// <summary>
28  /// Implied Volatility indicator that calculate the IV of an option using Black-Scholes Model
29  /// </summary>
31  {
32  private BaseDataConsolidator _consolidator;
33  private RateOfChange _roc;
34  private decimal _impliedVolatility;
35  private Func<decimal, decimal, decimal> SmoothingFunction;
36 
37  /// <summary>
38  /// Gets the historical volatility of the underlying
39  /// </summary>
41 
42  /// <summary>
43  /// Initializes a new instance of the ImpliedVolatility class
44  /// </summary>
45  /// <param name="name">The name of this indicator</param>
46  /// <param name="option">The option to be tracked</param>
47  /// <param name="riskFreeRateModel">Risk-free rate model</param>
48  /// <param name="dividendYieldModel">Dividend yield model</param>
49  /// <param name="mirrorOption">The mirror option for parity calculation</param>
50  /// <param name="optionModel">The option pricing model used to estimate IV</param>
51  /// <param name="period">The lookback period of historical volatility</param>
52  public ImpliedVolatility(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, IDividendYieldModel dividendYieldModel, Symbol mirrorOption = null,
53  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
54  : base(name, option, riskFreeRateModel, dividendYieldModel, mirrorOption, optionModel, period)
55  {
56  _roc = new(1);
59  new StandardDeviation(period),
60  _roc
61  ),
62  Convert.ToDecimal(Math.Sqrt(252))
63  );
64 
65  _consolidator = new(TimeSpan.FromDays(1));
66  _consolidator.DataConsolidated += (_, bar) => {
67  _roc.Update(bar.EndTime, bar.Price);
68  };
69 
70  if (mirrorOption != null)
71  {
72  // Default smoothing function will be assuming Law of One Price hold,
73  // so both call and put will have the same IV
74  // and using on OTM/ATM options to calculate the IV
75  // by assuming extra volatility coming from extrinsic value
76  SmoothingFunction = (impliedVol, mirrorImpliedVol) =>
77  {
78  if (Strike > UnderlyingPrice && Right == OptionRight.Put)
79  {
80  return mirrorImpliedVol;
81  }
82  else if (Strike < UnderlyingPrice && Right == OptionRight.Call)
83  {
84  return mirrorImpliedVol;
85  }
86  return impliedVol;
87  };
88  }
89  }
90 
91  /// <summary>
92  /// Initializes a new instance of the ImpliedVolatility class
93  /// </summary>
94  /// <param name="option">The option to be tracked</param>
95  /// <param name="riskFreeRateModel">Risk-free rate model</param>
96  /// <param name="dividendYieldModel">Dividend yield model</param>
97  /// <param name="mirrorOption">The mirror option for parity calculation</param>
98  /// <param name="optionModel">The option pricing model used to estimate IV</param>
99  /// <param name="period">The lookback period of historical volatility</param>
100  public ImpliedVolatility(Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, IDividendYieldModel dividendYieldModel,
101  Symbol mirrorOption = null, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
102  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYieldModel},{optionModel},{period})", option, riskFreeRateModel,
103  dividendYieldModel, mirrorOption, optionModel, period)
104  {
105  }
106 
107  /// <summary>
108  /// Initializes a new instance of the ImpliedVolatility class
109  /// </summary>
110  /// <param name="name">The name of this indicator</param>
111  /// <param name="option">The option to be tracked</param>
112  /// <param name="riskFreeRateModel">Risk-free rate model</param>
113  /// <param name="dividendYieldModel">Dividend yield model</param>
114  /// <param name="mirrorOption">The mirror option for parity calculation</param>
115  /// <param name="optionModel">The option pricing model used to estimate IV</param>
116  /// <param name="period">The lookback period of historical volatility</param>
117  public ImpliedVolatility(string name, Symbol option, PyObject riskFreeRateModel, PyObject dividendYieldModel, Symbol mirrorOption = null,
118  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
119  : this(name, option, RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel),
120  DividendYieldModelPythonWrapper.FromPyObject(dividendYieldModel), mirrorOption, optionModel, period)
121  {
122  }
123 
124  /// <summary>
125  /// Initializes a new instance of the ImpliedVolatility class
126  /// </summary>
127  /// <param name="option">The option to be tracked</param>
128  /// <param name="riskFreeRateModel">Risk-free rate model</param>
129  /// <param name="dividendYieldModel">Dividend yield model</param>
130  /// <param name="mirrorOption">The mirror option for parity calculation</param>
131  /// <param name="optionModel">The option pricing model used to estimate IV</param>
132  /// <param name="period">The lookback period of historical volatility</param>
133  public ImpliedVolatility(Symbol option, PyObject riskFreeRateModel, PyObject dividendYieldModel, Symbol mirrorOption = null,
134  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
135  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYieldModel},{optionModel},{period})", option,
136  riskFreeRateModel, dividendYieldModel, mirrorOption, optionModel, period)
137  {
138  }
139 
140  /// <summary>
141  /// Initializes a new instance of the ImpliedVolatility class
142  /// </summary>
143  /// <param name="name">The name of this indicator</param>
144  /// <param name="option">The option to be tracked</param>
145  /// <param name="riskFreeRateModel">Risk-free rate model</param>
146  /// <param name="dividendYield">Dividend yield, as a constant</param>
147  /// <param name="mirrorOption">The mirror option for parity calculation</param>
148  /// <param name="optionModel">The option pricing model used to estimate IV</param>
149  /// <param name="period">The lookback period of historical volatility</param>
150  public ImpliedVolatility(string name, Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
151  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
152  : this(name, option, riskFreeRateModel, new ConstantDividendYieldModel(dividendYield), mirrorOption, optionModel, period)
153  {
154  }
155 
156  /// <summary>
157  /// Initializes a new instance of the ImpliedVolatility class
158  /// </summary>
159  /// <param name="option">The option to be tracked</param>
160  /// <param name="riskFreeRateModel">Risk-free rate model</param>
161  /// <param name="dividendYield">Dividend yield, as a constant</param>
162  /// <param name="mirrorOption">The mirror option for parity calculation</param>
163  /// <param name="optionModel">The option pricing model used to estimate IV</param>
164  /// <param name="period">The lookback period of historical volatility</param>
165  public ImpliedVolatility(Symbol option, IRiskFreeInterestRateModel riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
166  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
167  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYield},{optionModel},{period})", option, riskFreeRateModel, dividendYield,
168  mirrorOption, optionModel, period)
169  {
170  }
171 
172  /// <summary>
173  /// Initializes a new instance of the ImpliedVolatility class
174  /// </summary>
175  /// <param name="name">The name of this indicator</param>
176  /// <param name="option">The option to be tracked</param>
177  /// <param name="riskFreeRateModel">Risk-free rate model</param>
178  /// <param name="dividendYield">Dividend yield, as a constant</param>
179  /// <param name="mirrorOption">The mirror option for parity calculation</param>
180  /// <param name="optionModel">The option pricing model used to estimate IV</param>
181  /// <param name="period">The lookback period of historical volatility</param>
182  public ImpliedVolatility(string name, Symbol option, PyObject riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
183  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
184  : this(name, option, RiskFreeInterestRateModelPythonWrapper.FromPyObject(riskFreeRateModel),
185  new ConstantDividendYieldModel(dividendYield), mirrorOption, optionModel, period)
186  {
187  }
188 
189  /// <summary>
190  /// Initializes a new instance of the ImpliedVolatility class
191  /// </summary>
192  /// <param name="option">The option to be tracked</param>
193  /// <param name="riskFreeRateModel">Risk-free rate model</param>
194  /// <param name="dividendYield">Dividend yield, as a constant</param>
195  /// <param name="mirrorOption">The mirror option for parity calculation</param>
196  /// <param name="optionModel">The option pricing model used to estimate IV</param>
197  /// <param name="period">The lookback period of historical volatility</param>
198  public ImpliedVolatility(Symbol option, PyObject riskFreeRateModel, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
199  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
200  : this($"IV({option},{mirrorOption},{riskFreeRateModel},{dividendYield},{optionModel},{period})", option, riskFreeRateModel,
201  dividendYield, mirrorOption, optionModel, period)
202  {
203  }
204 
205  /// <summary>
206  /// Initializes a new instance of the ImpliedVolatility class
207  /// </summary>
208  /// <param name="name">The name of this indicator</param>
209  /// <param name="option">The option to be tracked</param>
210  /// <param name="riskFreeRate">Risk-free rate, as a constant</param>
211  /// <param name="dividendYield">Dividend yield, as a constant</param>
212  /// <param name="mirrorOption">The mirror option for parity calculation</param>
213  /// <param name="optionModel">The option pricing model used to estimate IV</param>
214  /// <param name="period">The lookback period of historical volatility</param>
215  public ImpliedVolatility(string name, Symbol option, decimal riskFreeRate = 0.05m, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
216  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
217  : this(name, option, new ConstantRiskFreeRateInterestRateModel(riskFreeRate), new ConstantDividendYieldModel(dividendYield), mirrorOption, optionModel, period)
218  {
219  }
220 
221  /// <summary>
222  /// Initializes a new instance of the ImpliedVolatility class
223  /// </summary>
224  /// <param name="option">The option to be tracked</param>
225  /// <param name="riskFreeRate">Risk-free rate, as a constant</param>
226  /// <param name="dividendYield">Dividend yield, as a constant</param>
227  /// <param name="mirrorOption">The mirror option for parity calculation</param>
228  /// <param name="optionModel">The option pricing model used to estimate IV</param>
229  /// <param name="period">The lookback period of historical volatility</param>
230  public ImpliedVolatility(Symbol option, decimal riskFreeRate = 0.05m, decimal dividendYield = 0.0m, Symbol mirrorOption = null,
231  OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes, int period = 252)
232  : this($"IV({option},{mirrorOption},{riskFreeRate},{dividendYield},{optionModel},{period})", option, riskFreeRate,
233  dividendYield, mirrorOption, optionModel, period)
234  {
235  }
236 
237  /// <summary>
238  /// Set the smoothing function of IV, using both call and put IV value
239  /// </summary>
240  /// <param name="function">the smoothing function</param>
241  public void SetSmoothingFunction(Func<decimal, decimal, decimal> function)
242  {
243  SmoothingFunction = function;
244  }
245 
246  /// <summary>
247  /// Set the smoothing function of IV, using both call and put IV value
248  /// </summary>
249  /// <param name="function">the smoothing function</param>
250  public void SetSmoothingFunction(PyObject function)
251  {
252  SmoothingFunction = PythonUtil.ToFunc<decimal, decimal, decimal>(function);
253  }
254 
255  private bool _isReady => Price.Current.Time == UnderlyingPrice.Current.Time && Price.IsReady && UnderlyingPrice.IsReady;
256 
257  /// <summary>
258  /// Gets a flag indicating when this indicator is ready and fully initialized
259  /// </summary>
260  public override bool IsReady => UseMirrorContract ? _isReady && Price.Current.Time == OppositePrice.Current.Time && OppositePrice.IsReady : _isReady;
261 
262  /// <summary>
263  /// Computes the next value
264  /// </summary>
265  /// <param name="input">The input given to the indicator</param>
266  /// <returns>The input is returned unmodified.</returns>
267  protected override decimal ComputeNextValue(IndicatorDataPoint input)
268  {
269  if (input.Symbol == _optionSymbol)
270  {
271  Price.Update(input.EndTime, input.Price);
272  }
273  else if (input.Symbol == _oppositeOptionSymbol)
274  {
275  OppositePrice.Update(input.EndTime, input.Price);
276  }
277  else if (input.Symbol == _underlyingSymbol)
278  {
279  _consolidator.Update(input);
280  UnderlyingPrice.Update(input.EndTime, input.Price);
281  }
282  else
283  {
284  throw new ArgumentException("The given symbol was not target or reference symbol");
285  }
286 
287  var time = Price.Current.Time;
288  if (_isReady)
289  {
290  if (UseMirrorContract)
291  {
292  if (time != OppositePrice.Current.Time)
293  {
294  return _impliedVolatility;
295  }
296  }
297 
300 
301  var timeTillExpiry = Convert.ToDecimal((Expiry - time).TotalDays) / 365m;
302  _impliedVolatility = CalculateIV(timeTillExpiry);
303  }
304 
305  return _impliedVolatility;
306  }
307 
308  // Calculate the theoretical option price
309  private decimal TheoreticalPrice(decimal volatility, decimal spotPrice, decimal strikePrice, decimal timeTillExpiry, decimal riskFreeRate,
310  decimal dividendYield, OptionRight optionType, OptionPricingModelType optionModel = OptionPricingModelType.BlackScholes)
311  {
312  if (timeTillExpiry <= 0m)
313  {
314  return 0m;
315  }
316 
317  return optionModel switch
318  {
319  // Binomial model also follows BSM process (log-normal)
320  OptionPricingModelType.BinomialCoxRossRubinstein => OptionGreekIndicatorsHelper.CRRTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
321  OptionPricingModelType.ForwardTree => OptionGreekIndicatorsHelper.ForwardTreeTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
322  _ => OptionGreekIndicatorsHelper.BlackTheoreticalPrice(volatility, spotPrice, strikePrice, timeTillExpiry, riskFreeRate, dividendYield, optionType),
323  };
324  }
325 
326  /// <summary>
327  /// Computes the IV of the option
328  /// </summary>
329  /// <param name="timeTillExpiry">the time until expiration in years</param>
330  /// <returns>Smoothened IV of the option</returns>
331  protected virtual decimal CalculateIV(decimal timeTillExpiry)
332  {
333  var impliedVol = 0m;
334  try
335  {
336  Func<double, double> f = (vol) => (double)(TheoreticalPrice(
337  Convert.ToDecimal(vol), UnderlyingPrice, Strike, timeTillExpiry, RiskFreeRate, DividendYield, Right, _optionModel) - Price);
338  impliedVol = Convert.ToDecimal(Brent.FindRoot(f, 1e-7d, 2.0d, 1e-4d, 100));
339  }
340  catch
341  {
342  Log.Error("ImpliedVolatility.CalculateIV(): Fail to converge, returning 0.");
343  }
344 
345  if (UseMirrorContract)
346  {
347  var mirrorImpliedVol = 0m;
348  try
349  {
350  Func<double, double> f = (vol) => (double)(TheoreticalPrice(
352  mirrorImpliedVol = Convert.ToDecimal(Brent.FindRoot(f, 1e-7d, 2.0d, 1e-4d, 100));
353  }
354  catch
355  {
356  Log.Error("ImpliedVolatility.CalculateIV(): Fail to converge, returning 0.");
357  }
358 
359  return SmoothingFunction(impliedVol, mirrorImpliedVol);
360  }
361 
362  return impliedVol;
363  }
364 
365  /// <summary>
366  /// Resets this indicator and all sub-indicators
367  /// </summary>
368  public override void Reset()
369  {
370  _consolidator.Dispose();
371  _consolidator = new(TimeSpan.FromDays(1));
372  _consolidator.DataConsolidated += (_, bar) => {
373  _roc.Update(bar.EndTime, bar.Price);
374  };
375 
376  _roc.Reset();
377  HistoricalVolatility.Reset();
378  base.Reset();
379  }
380  }
381 }