Lean  $LEAN_TAG$
OptionGreekIndicatorsHelper.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 System.Runtime.CompilerServices;
18 using MathNet.Numerics.Distributions;
19 using QuantConnect.Util;
20 
22 {
23  /// <summary>
24  /// Helper class for option greeks related indicators
25  /// </summary>
27  {
28  /// <summary>
29  /// Number of steps in binomial tree simulation to obtain Greeks/IV
30  /// </summary>
31  public const int Steps = 200;
32 
33  /// <summary>
34  /// Returns the Black theoretical price for the given arguments
35  /// </summary>
36  public static double BlackTheoreticalPrice(double volatility, double spotPrice, double strikePrice, double timeToExpiration, double riskFreeRate, double dividendYield, OptionRight optionType)
37  {
38  var d1 = CalculateD1(spotPrice, strikePrice, timeToExpiration, riskFreeRate, dividendYield, volatility);
39  var d2 = CalculateD2(d1, volatility, timeToExpiration);
40  var norm = new Normal();
41 
42  var optionPrice = 0.0;
43  if (optionType == OptionRight.Call)
44  {
45  optionPrice = spotPrice * Math.Exp(-dividendYield * timeToExpiration) * norm.CumulativeDistribution(d1)
46  - strikePrice * Math.Exp(-riskFreeRate * timeToExpiration) * norm.CumulativeDistribution(d2);
47  }
48  else if (optionType == OptionRight.Put)
49  {
50  optionPrice = strikePrice * Math.Exp(-riskFreeRate * timeToExpiration) * norm.CumulativeDistribution(-d2)
51  - spotPrice * Math.Exp(-dividendYield * timeToExpiration) * norm.CumulativeDistribution(-d1);
52  }
53  else
54  {
55  throw new ArgumentException("Invalid option right.");
56  }
57 
58  return optionPrice;
59  }
60 
61  internal static double CalculateD1(double spotPrice, double strikePrice, double timeToExpiration, double riskFreeRate, double dividendYield, double volatility)
62  {
63  var numerator = Math.Log(spotPrice / strikePrice) + (riskFreeRate - dividendYield + 0.5 * volatility * volatility) * timeToExpiration;
64  var denominator = volatility * Math.Sqrt(Math.Max(0, timeToExpiration));
65  if (denominator == 0)
66  {
67  // return a random variable large enough to produce normal probability density close to 1
68  return 10;
69  }
70  return numerator / denominator;
71  }
72 
73  internal static double CalculateD2(double d1, double volatility, double timeToExpiration)
74  {
75  return d1 - volatility * Math.Sqrt(Math.Max(0, timeToExpiration));
76  }
77 
78  /// <summary>
79  /// Creates a Binomial Theoretical Price Tree from the given parameters
80  /// </summary>
81  /// <remarks>Reference: https://en.wikipedia.org/wiki/Binomial_options_pricing_model#Step_1:_Create_the_binomial_price_tree</remarks>
82  public static double CRRTheoreticalPrice(double volatility, double spotPrice, double strikePrice,
83  double timeToExpiration, double riskFreeRate, double dividendYield, OptionRight optionType, int steps = Steps)
84  {
85  var deltaTime = timeToExpiration / steps;
86  var upFactor = Math.Exp(volatility * Math.Sqrt(deltaTime));
87  if (upFactor == 1)
88  {
89  // Introduce a very small factor to avoid constant tree while staying low volatility
90  upFactor = 1.0001;
91  }
92  var downFactor = 1 / upFactor;
93  var probUp = (Math.Exp((riskFreeRate - dividendYield) * deltaTime) - downFactor) / (upFactor - downFactor);
94 
95  return BinomialTheoreticalPrice(deltaTime, probUp, upFactor, riskFreeRate, spotPrice, strikePrice, optionType, steps);
96  }
97 
98  /// <summary>
99  /// Creates the Forward Binomial Theoretical Price Tree from the given parameters
100  /// </summary>
101  public static double ForwardTreeTheoreticalPrice(double volatility, double spotPrice, double strikePrice,
102  double timeToExpiration, double riskFreeRate, double dividendYield, OptionRight optionType, int steps = Steps)
103  {
104  var deltaTime = timeToExpiration / steps;
105  var discount = Math.Exp((riskFreeRate - dividendYield) * deltaTime);
106  var volatilityTimeSqrtDeltaTime = volatility * Math.Sqrt(deltaTime);
107  var upFactor = Math.Exp(volatilityTimeSqrtDeltaTime) * discount;
108  var downFactor = Math.Exp(-volatilityTimeSqrtDeltaTime) * discount;
109  if (upFactor - downFactor == 0)
110  {
111  // Introduce a very small factor
112  // to avoid constant tree while staying low volatility
113  upFactor = 1.0001;
114  downFactor = 0.9999;
115  }
116  var probUp = (discount - downFactor) / (upFactor - downFactor);
117 
118  return BinomialTheoreticalPrice(deltaTime, probUp, upFactor, riskFreeRate, spotPrice, strikePrice, optionType, steps);
119  }
120 
121  private static double BinomialTheoreticalPrice(double deltaTime, double probUp, double upFactor, double riskFreeRate,
122  double spotPrice, double strikePrice, OptionRight optionType, int steps = Steps)
123  {
124  var probDown = 1 - probUp;
125  var values = new double[steps + 1];
126  // Cache for exercise values for Call options to avoid recalculating them
127  var exerciseValues = optionType == OptionRight.Call ? new double[2 * steps] : null;
128 
129  for (int i = 0; i < (exerciseValues?.Length ?? values.Length); i++)
130  {
131  if (i < values.Length)
132  {
133  var nextPrice = spotPrice * Math.Pow(upFactor, 2 * i - steps);
134  values[i] = OptionPayoff.GetIntrinsicValue(nextPrice, strikePrice, optionType);
135  }
136 
137  if (optionType == OptionRight.Call)
138  {
139  var nextPrice = spotPrice * Math.Pow(upFactor, i - steps);
140  exerciseValues[i] = OptionPayoff.GetIntrinsicValue(nextPrice, strikePrice, optionType);
141  }
142  }
143 
144  var factor = Math.Exp(-riskFreeRate * deltaTime);
145  var factorA = factor * probDown;
146  var factorB = factor * probUp;
147 
148  for (var period = steps - 1; period >= 0; period--)
149  {
150  for (var i = 0; i <= period; i++)
151  {
152  var binomialValue = values[i] * factorA + values[i + 1] * factorB;
153  // No advantage for American put option to exercise early in risk-neutral setting
154  if (optionType == OptionRight.Put)
155  {
156  values[i] = binomialValue;
157  continue;
158  }
159  values[i] = Math.Max(binomialValue, exerciseValues[2 * i - period + steps]);
160  }
161  }
162 
163  return values[0];
164  }
165 
166  [MethodImpl(MethodImplOptions.AggressiveInlining)]
167  public static double TimeTillExpiry(DateTime expiry, DateTime referenceDate)
168  {
169  return (expiry - referenceDate).TotalDays / 365d;
170  }
171 
172  [MethodImpl(MethodImplOptions.AggressiveInlining)]
173  internal static double Divide(double numerator, double denominator)
174  {
175  if (denominator != 0)
176  {
177  return numerator / denominator;
178  }
179 
180  //Log.Error("OptionGreekIndicatorsHelper.Divide(): Division by zero detected. Returning 0.");
181  return 0;
182  }
183  }
184 }