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 MathNet.Numerics.Distributions;
18 using QuantConnect.Util;
19 
21 {
22  /// <summary>
23  /// Helper clas for option greeks related indicators
24  /// </summary>
26  {
27  /// <summary>
28  /// Number of steps in binomial tree simulation to obtain Greeks/IV
29  /// </summary>
30  public const int Steps = 200;
31 
32  public static decimal BlackTheoreticalPrice(decimal volatility, decimal spotPrice, decimal strikePrice, decimal timeToExpiration, decimal riskFreeRate, decimal dividendYield, OptionRight optionType)
33  {
34  var d1 = CalculateD1(spotPrice, strikePrice, timeToExpiration, riskFreeRate, dividendYield, volatility);
35  var d2 = CalculateD2(d1, volatility, timeToExpiration);
36  var norm = new Normal();
37 
38  var optionPrice = 0.0m;
39 
40  if (optionType == OptionRight.Call)
41  {
42  optionPrice = spotPrice * DecimalMath(Math.Exp, -dividendYield * timeToExpiration) * DecimalMath(norm.CumulativeDistribution, d1)
43  - strikePrice * DecimalMath(Math.Exp, -riskFreeRate * timeToExpiration) * DecimalMath(norm.CumulativeDistribution, d2);
44  }
45  else if (optionType == OptionRight.Put)
46  {
47  optionPrice = strikePrice * DecimalMath(Math.Exp, -riskFreeRate * timeToExpiration) * DecimalMath(norm.CumulativeDistribution, -d2)
48  - spotPrice * DecimalMath(Math.Exp, -dividendYield * timeToExpiration) * DecimalMath(norm.CumulativeDistribution, -d1);
49  }
50  else
51  {
52  throw new ArgumentException("Invalid option right.");
53  }
54 
55  return optionPrice;
56  }
57 
58  internal static decimal CalculateD1(decimal spotPrice, decimal strikePrice, decimal timeToExpiration, decimal riskFreeRate, decimal dividendYield, decimal volatility)
59  {
60  var numerator = DecimalMath(Math.Log, spotPrice / strikePrice) + (riskFreeRate - dividendYield + 0.5m * volatility * volatility) * timeToExpiration;
61  var denominator = volatility * DecimalMath(Math.Sqrt, Math.Max(0m, timeToExpiration));
62  if (denominator == 0m)
63  {
64  // return a random variable large enough to produce normal probability density close to 1
65  return 10;
66  }
67  return numerator / denominator;
68  }
69 
70  internal static decimal CalculateD2(decimal d1, decimal volatility, decimal timeToExpiration)
71  {
72  return d1 - volatility * DecimalMath(Math.Sqrt, Math.Max(0m, timeToExpiration));
73  }
74 
75  // Reference: https://en.wikipedia.org/wiki/Binomial_options_pricing_model#Step_1:_Create_the_binomial_price_tree
76  public static decimal CRRTheoreticalPrice(decimal volatility, decimal spotPrice, decimal strikePrice,
77  decimal timeToExpiration, decimal riskFreeRate, decimal dividendYield, OptionRight optionType, int steps = Steps)
78  {
79  var deltaTime = timeToExpiration / steps;
80  var upFactor = DecimalMath(Math.Exp, volatility * DecimalMath(Math.Sqrt, deltaTime));
81  if (upFactor == 1m)
82  {
83  // Introduce a very small factor to avoid constant tree while staying low volatility
84  upFactor = 1.0001m;
85  }
86  var downFactor = 1m / upFactor;
87  var probUp = (DecimalMath(Math.Exp, (riskFreeRate - dividendYield) * deltaTime) - downFactor) / (upFactor - downFactor);
88 
89  return BinomialTheoreticalPrice(deltaTime, probUp, upFactor, riskFreeRate, spotPrice, strikePrice, optionType, steps);
90  }
91 
92  public static decimal ForwardTreeTheoreticalPrice(decimal volatility, decimal spotPrice, decimal strikePrice,
93  decimal timeToExpiration, decimal riskFreeRate, decimal dividendYield, OptionRight optionType, int steps = Steps)
94  {
95  var deltaTime = timeToExpiration / steps;
96  var discount = DecimalMath(Math.Exp, (riskFreeRate - dividendYield) * deltaTime);
97  var upFactor = DecimalMath(Math.Exp, volatility * DecimalMath(Math.Sqrt, deltaTime)) * discount;
98  var downFactor = DecimalMath(Math.Exp, -volatility * DecimalMath(Math.Sqrt, deltaTime)) * discount;
99  if (upFactor - downFactor == 0m)
100  {
101  // Introduce a very small factor
102  // to avoid constant tree while staying low volatility
103  upFactor = 1.0001m;
104  downFactor = 0.9999m;
105  }
106  var probUp = (discount - downFactor) / (upFactor - downFactor);
107 
108  return BinomialTheoreticalPrice(deltaTime, probUp, upFactor, riskFreeRate, spotPrice, strikePrice, optionType, steps);
109  }
110 
111  private static decimal BinomialTheoreticalPrice(decimal deltaTime, decimal probUp, decimal upFactor, decimal riskFreeRate,
112  decimal spotPrice, decimal strikePrice, OptionRight optionType, int steps = Steps)
113  {
114  var probDown = 1m - probUp;
115  var values = new decimal[steps + 1];
116 
117  for (int i = 0; i <= steps; i++)
118  {
119  var nextPrice = spotPrice * Convert.ToDecimal(Math.Pow((double)upFactor, 2 * i - steps));
120  values[i] = OptionPayoff.GetIntrinsicValue(nextPrice, strikePrice, optionType);
121  }
122 
123  for (int period = steps - 1; period >= 0; period--)
124  {
125  for (int i = 0; i <= period; i++)
126  {
127  var binomialValue = DecimalMath(Math.Exp, -riskFreeRate * deltaTime) * (values[i] * probDown + values[i + 1] * probUp);
128  // No advantage for American put option to exercise early in risk-neutral setting
129  if (optionType == OptionRight.Put)
130  {
131  values[i] = binomialValue;
132  continue;
133  }
134  var nextPrice = spotPrice * Convert.ToDecimal(Math.Pow((double)upFactor, 2 * i - period));
135  var exerciseValue = OptionPayoff.GetIntrinsicValue(nextPrice, strikePrice, optionType);
136  values[i] = Math.Max(binomialValue, exerciseValue);
137  }
138  }
139 
140  return values[0];
141  }
142 
143  internal static decimal DecimalMath(Func<double, double> function, decimal input)
144  {
145  return Convert.ToDecimal(function((double)input));
146  }
147  }
148 }