/// <summary>Gets the implied Black volatility of a specific european call option. /// </summary> /// <param name="strike">The strike.</param> /// <param name="forward">The forward.</param> /// <param name="timeToExpiry">The time span between valuation date and expiry date in its <see cref="System.Double"/> representation.</param> /// <param name="c0">The dimensionless option price, i.e. option price divided by forward and discount factor.</param> /// <param name="value">The implied Black volatility (output).</param> /// <returns>A value indicating whether <paramref name="value"/> contains valid data.</returns> private ImpliedCalculationResultState TryGetImpliedCallVolatility(double strike, double forward, double timeToExpiry, double c0, out double value) { var x = Math.Log(forward / strike); var expOfMinusx = strike / forward; if (x > 0) // "in-out" duality { x = -x; expOfMinusx = forward / strike; c0 = expOfMinusx * c0 + 1 - expOfMinusx; // expOfMinusx = exp( [original] x ) } var v0 = GetInitialCallOptionTotalVolatility(x, c0); var oneOverOnePlusW = 1.0 / (1 + m_RelaxationParameter); var v = v0; for (int j = 0; j < m_MaxNumberOfIterations; j++) { var nPlus = StandardNormalDistribution.GetCdfValue(x / v + 0.5 * v); var nMinus = expOfMinusx * StandardNormalDistribution.GetCdfValue(x / v - 0.5 * v); double c = nPlus - nMinus; // undiscounted normalized option value var temp = StandardNormalDistribution.GetInverseCdfValue((c0 + nMinus + m_RelaxationParameter * nPlus) * oneOverOnePlusW); v = temp + Math.Sqrt(temp * temp + 2.0 * Math.Abs(x)); if (Math.Abs(c - c0) < m_Tolerance) { value = v / Math.Sqrt(timeToExpiry); // here, we assume that the 'new' total volatility estimation is better than the one used for the calculation of 'c' return(ImpliedCalculationResultState.ProperResult); } } value = v / Math.Sqrt(timeToExpiry); return(ImpliedCalculationResultState.NoProperResult); }
/// <summary>Computes the inverse cumulative normal distribution function values of vector elements. /// </summary> /// <param name="n">The number of elements to be calculated.</param> /// <param name="a">The input vector a.</param> /// <param name="y">The output vector N^{-1}(a[j]), j=0,...,<paramref name="n"/>-1, where N(x) =\int_{-\infty}^x 1/\sqrt(2*PI) * exp(-1/2 *t^2) dt.</param> public void CdfNormInv(int n, double[] a, double[] y) { for (int j = 0; j < n; j++) { y[j] = StandardNormalDistribution.GetInverseCdfValue(a[j]); } }
/// <summary>Computes the inverse cumulative normal distribution function values of vector elements. /// </summary> /// <param name="n">The number of elements to be calculated.</param> /// <param name="a">The input vector a.</param> /// <param name="y">The output vector y with y[<paramref name="startIndexY"/> + j] := N^{-1}(a[<paramref name="startIndexA"/> + j]) for j = 0,...,<paramref name="n"/>-1, where N(x) =\int_{-\infty}^x 1/\sqrt(2*PI) * exp(-1/2 *t^2) dt.</param> /// <param name="startIndexA">The null-based start index of <paramref name="a"/>.</param> /// <param name="startIndexY">The null-based start index of <paramref name="y"/>.</param> public void CdfNormInv(int n, double[] a, double[] y, int startIndexA, int startIndexY) { for (int j = 0; j < n; j++) { y[j + startIndexY] = StandardNormalDistribution.GetInverseCdfValue(a[j + startIndexA]); } }
/// <summary>Gets the implied strike for a specific option price. /// </summary> /// <param name="optionValue">The value of the option.</param> /// <param name="volatility">The volatility.</param> /// <param name="impliedStrike">The implied strike (output).</param> /// <returns>A value indicating whether <paramref name="impliedStrike"/> contains valid data.</returns> public ImpliedCalculationResultState TryGetImpliedStrike(double optionValue, double volatility, out double impliedStrike) { if (m_TimeToExpiration < MachineConsts.Epsilon) { impliedStrike = m_Forward; return(ImpliedCalculationResultState.ProperResult); } impliedStrike = m_Forward * Math.Exp(StandardNormalDistribution.GetInverseCdfValue(optionValue / m_DiscountFactor) * volatility * Math.Sqrt(m_TimeToExpiration) - 0.5 * volatility * volatility * m_TimeToExpiration); return((Double.IsNaN(impliedStrike) == false) ? ImpliedCalculationResultState.ProperResult : ImpliedCalculationResultState.NoProperResult); }
/// <summary>Gets the value of the copula at some point. /// </summary> /// <param name="x">The argument x.</param> /// <param name="y">The argument y.</param> /// <returns> /// The value of the copula at (<paramref name="x"/>,<paramref name="y"/>). /// </returns> public double GetValue(double x, double y) { if ((x < MachineConsts.SuperTinyEpsilon) || (y < MachineConsts.SuperTinyEpsilon)) { return(0.0); } if (1.0 - x < MachineConsts.SuperTinyEpsilon) { if (1.0 - y < MachineConsts.SuperTinyEpsilon) { return(x); } return(y); } if (1.0 - y < MachineConsts.SuperTinyEpsilon) { return(x); } return(BivariateNormalDistribution.StandardCDFValue(StandardNormalDistribution.GetInverseCdfValue(x), StandardNormalDistribution.GetInverseCdfValue(y), m_Correlation)); }
/// <summary>Gets the implied volatility for a specific non-discounted option price. /// </summary> /// <param name="noneDiscountedValue">The value of the option at the time of expiry, thus the price but <b>not</b> discounted to time 0.</param> /// <param name="impliedVolatility">The implied volatility (output).</param> /// <returns>A value indicating whether <paramref name="impliedVolatility" /> contains valid data.</returns> /// <remarks>This method is the inverse function of <see cref="IConstantVolatilityStandardEuropeanOption.GetNoneDiscountedValue(double)" />.</remarks> public ImpliedCalculationResultState TryGetImpliedVolatilityOfNonDiscountedValue(double noneDiscountedValue, out double impliedVolatility) { double x = Math.Log(m_Forward / m_Strike); double NInverse = StandardNormalDistribution.GetInverseCdfValue(noneDiscountedValue); if (NInverse * NInverse + 2 * x >= 0) { double root = Math.Sqrt(NInverse * NInverse + 2 * x); impliedVolatility = NInverse + root; if (NInverse - root >= 0) { impliedVolatility = NInverse - root; } impliedVolatility /= Math.Sqrt(m_TimeToExpiration); return(ImpliedCalculationResultState.ProperResult); } impliedVolatility = 0.0; return(ImpliedCalculationResultState.NoProperResult); }
/// <summary>Gets the implied strike for a specific option price. /// </summary> /// <param name="optionValue">The value of the option.</param> /// <param name="volatility">The volatility.</param> /// <param name="impliedStrike">The implied strike (output).</param> /// <returns>A value indicating whether <paramref name="impliedStrike"/> contains valid data.</returns> public ImpliedCalculationResultState TryGetImpliedStrike(double optionValue, double volatility, out double impliedStrike) { impliedStrike = m_Forward + volatility * SqrtOfTimeToExpiration * StandardNormalDistribution.GetInverseCdfValue(optionValue / m_DiscountFactor); return((Double.IsInfinity(impliedStrike) || Double.IsNaN(impliedStrike)) ? ImpliedCalculationResultState.InputError : ImpliedCalculationResultState.ProperResult); }
/// <summary>Gets the implied volatility for a specific non-discounted option price. /// </summary> /// <param name="noneDiscountedValue">The value of the option at the time of expiry, thus the price but <b>not</b> discounted to time 0.</param> /// <param name="impliedVolatility">The implied volatility (output).</param> /// <returns>A value indicating whether <paramref name="impliedVolatility" /> contains valid data.</returns> /// <remarks>This method is the inverse function of <see cref="IConstantVolatilityStandardEuropeanOption.GetNoneDiscountedValue(double)" />.</remarks> public ImpliedCalculationResultState TryGetImpliedVolatilityOfNonDiscountedValue(double noneDiscountedValue, out double impliedVolatility) { impliedVolatility = (m_Strike - m_Forward) / (SqrtOfTimeToExpiration * StandardNormalDistribution.GetInverseCdfValue(noneDiscountedValue)); return((Double.IsInfinity(impliedVolatility) || Double.IsNaN(impliedVolatility)) ? ImpliedCalculationResultState.InputError : ImpliedCalculationResultState.ProperResult); }
/// <summary>Gets the implied Black-Scholes volatility for a given value (price) of a specific call or put option. /// </summary> /// <param name="theta">A value indicating whether a call (=1) or put (=-1) is given.</param> /// <param name="strike">The strike.</param> /// <param name="forward">The forward.</param> /// <param name="timeToExpiry">The time span between valuation date and expiry date in its <see cref="System.Double"/> representation.</param> /// <param name="noneDiscountedValue">The value of the option at the time of expiry, thus the price but <b>not</b> discounted to time 0.</param> /// <param name="impliedBlackVolatility">The implied Black volatility (output).</param> /// <returns>A value indicating whether <paramref name="impliedBlackVolatility"/> contains valid data.</returns> private ImpliedCalculationResultState TryGetPlainVanillaImpliedVolatility(int theta, double strike, double forward, double timeToExpiry, double noneDiscountedValue, out double impliedBlackVolatility) { impliedBlackVolatility = 0.0; /* calculate some constants only once: */ double x = Math.Log(forward / strike); if (Double.IsNaN(x)) // strike <= 0? { return(ImpliedCalculationResultState.InputError); } double beta = noneDiscountedValue / Math.Sqrt(forward * strike); // normalized value: the given value is already discounted /* 1. case: at-the-money option: * Compute the implied vola via inverse commulative distribution function */ if (Math.Abs(x) < MachineConsts.Epsilon) // at-the-money option { impliedBlackVolatility = -2.0 * StandardNormalDistribution.GetInverseCdfValue(0.5 - 0.5 * beta); return(ImpliedCalculationResultState.ProperResult); } /* 2. case: in-the-money option: * subtracting the normalized intrinsic value from the normalized value if necessary */ double expOfXDivTwo = Math.Sqrt(forward / strike); // = Math.Exp(x / 2.0); double iota = theta * DoMath.HeavisideFunction(theta * x) * (expOfXDivTwo - 1 / expOfXDivTwo); /* the normalized value has to be an element of the interval * [iota, exp( \theta * x/2)], * otherwise no implied value can be calculated! */ if (beta < iota) { return(ImpliedCalculationResultState.InputError); // one may sets 'beta = iota;' } if (beta > ((theta == 1) ? expOfXDivTwo : 1 / expOfXDivTwo)) { return(ImpliedCalculationResultState.InputError); // one may sets 'beta = ((theta == 1) ? expOfXDivTwo : 1 / expOfXDivTwo);' } bool isInTheMoneyOption = false; // indicates if it is a in-the-money option if (theta == -1) { if (x < 0) { isInTheMoneyOption = true; } } else { if (x > 0) // strike < forward { isInTheMoneyOption = true; } } if (isInTheMoneyOption == true) { beta -= iota; theta = 1 - 2 * DoMath.HeavisideFunction(x); /* now consider some out-of-the money option and recalculate the intrinsic value */ iota = theta * DoMath.HeavisideFunction(theta * x) * (expOfXDivTwo - 1 / expOfXDivTwo); } /* 3.) Set initual guess: */ double bc = NormalizedCallPutValue(theta, x, expOfXDivTwo, Math.Sqrt(2 * Math.Abs(x))); /* 3a.) sigmaLow and sigmaHigh are given by formula (3.6)-(3.7) */ double tempForSigmaHighExp = (theta == 1) ? expOfXDivTwo : 1 / expOfXDivTwo; // = Math.Exp(theta * x / 2.0) double tempForSigmaHighCDF = StandardNormalDistribution.GetCdfValue(-Math.Sqrt(Math.Abs(x) / 2.0)); double sigmaLow = Math.Sqrt(2 * x * x / (Math.Abs(x) - 4 * Math.Log((beta - iota) / (bc - iota)))); double sigmaHigh = -2 * StandardNormalDistribution.GetInverseCdfValue((tempForSigmaHighExp - beta) / (tempForSigmaHighExp - bc) * tempForSigmaHighCDF); /* 2b.) calculate the gamma which is given in formula (4.7): */ double sigmaStar = -2 * StandardNormalDistribution.GetInverseCdfValue(tempForSigmaHighExp / (tempForSigmaHighExp - bc) * tempForSigmaHighCDF); double bStar = NormalizedCallPutValue(theta, x, expOfXDivTwo, sigmaStar); double sigmaLowStar = Math.Sqrt(2 * x * x / (Math.Abs(x) - 4 * Math.Log((bStar - iota) / (bc - iota)))); double sigmaHighStar = -2 * StandardNormalDistribution.GetInverseCdfValue((tempForSigmaHighExp - bStar) / (tempForSigmaHighExp - bc) * tempForSigmaHighCDF); // the weight w^* given by formula (4.8) double w = Math.Pow(Math.Min(Math.Max((sigmaStar - sigmaLowStar) / (sigmaHighStar - sigmaLowStar), 0), 1.0), Math.Log(bc / beta) / Math.Log(bc / bStar)); /* the code based on the 2006 edition of the paper, i.e. using formula (4.1) in "By implication", 24.11.2010: * // double gamma = Math.Log((sigmaStar - sigmaLowStar) / (sigmaHighStar - sigmaLowStar)) / Math.Log(bStar / bc); * // double w = Math.Min(1, Math.Pow(beta / bc, gamma)); // its the weight given by (4.2) * * /* 2c.) now the initial value is given; see formula (4.1): */ if (Double.IsNaN(sigmaLow) == false) { impliedBlackVolatility = sigmaLow * (1 - w) + sigmaHigh * w; } else { /* it is not hard to see that if \sigma_low is not defined then 'beta/bc' > 1 * which implies w = 1.0 */ impliedBlackVolatility = sigmaHigh; } /* 3.) Do the Halley's iteration method: */ double terminatingCondition = 1.0; for (int i = 1; i <= m_MaxNumberOfIterations; i++) { /* A.) compute nu^_n(x,\sigma_n,\Theta) via formula (4.10) & (4.13): */ // first compute nu_n (without 'hat') via (3.10): double b = NormalizedCallPutValue(theta, x, expOfXDivTwo, impliedBlackVolatility); double impliedBSVolaSquare = impliedBlackVolatility * impliedBlackVolatility; double bDash = MathConsts.OneOverSqrtTwoPi * Math.Exp(-0.5 * x * x / impliedBSVolaSquare - 0.125 * impliedBSVolaSquare); double nuN = 0.0; if (beta < bc) { nuN = Math.Log((beta - iota) / (b - iota)) * Math.Log(b - iota) / Math.Log(beta - iota) * (b - iota) / bDash; } else { nuN = (beta - b) / bDash; } double nuHat = Math.Max(nuN, -impliedBlackVolatility / 2.0); /* B. compute eta^ via formula (4.14) & (4.15) & (4.11): */ double etaHat = x * x / (impliedBSVolaSquare * impliedBlackVolatility) - impliedBlackVolatility / 4.0; if (beta < bc) { double logOfbMinusIota = Math.Log(b - iota); etaHat -= (2 + logOfbMinusIota) / logOfbMinusIota * bDash / (b - iota); } etaHat = Math.Max(-0.75, etaHat / 2.0 * nuHat); /* C. next step and store the terminating condition */ terminatingCondition = impliedBlackVolatility; // store the volatility which is used in the step before impliedBlackVolatility = impliedBlackVolatility + Math.Max(nuHat / (1 + etaHat), -impliedBlackVolatility / 2.0); terminatingCondition = Math.Abs(impliedBlackVolatility / terminatingCondition - 1); if (Double.IsNaN(terminatingCondition)) { impliedBlackVolatility = Double.NaN; return(ImpliedCalculationResultState.NoProperResult); } if (terminatingCondition < m_Tolerance) { impliedBlackVolatility /= Math.Sqrt(timeToExpiry); return(ImpliedCalculationResultState.ProperResult); } } impliedBlackVolatility /= Math.Sqrt(timeToExpiry); return(ImpliedCalculationResultState.NoProperResult); }