/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.volatility;
import org.apache.commons.lang.Validate;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.NormalFunctionData;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.NormalPriceFunction;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.rootfinding.BisectionSingleRootFinder;
import com.opengamma.analytics.math.rootfinding.BracketRoot;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.CompareUtils;
/**
* Computes the implied volatility from the price in a normally distributed asset price world.
*/
public class NormalImpliedVolatilityFormula {
/**
* The function used to compute the price with normal hypothesis.
*/
private static final NormalPriceFunction NORMAL_PRICE_FUNCTION = new NormalPriceFunction();
/**
* The maximal number of iterations in the root solving algorithm.
*/
private static final int MAX_ITERATIONS = 100;
/**
* The solution precision.
*/
private static final double EPS = 1e-15;
/** Limit defining "close of ATM forward" to avoid the formula singularity. **/
private static final double ATM_LIMIT = 1.0E-3;
/**
* Computes the implied volatility from the price in a normally distributed asset price world.
* @param data The model data. The data volatility, if not zero, is used as a starting point for the volatility search.
* @param option The option.
* @param optionPrice The option price.
* @return The implied volatility.
*/
public double getImpliedVolatility(final NormalFunctionData data, final EuropeanVanillaOption option, final double optionPrice) {
final double numeraire = data.getNumeraire();
final boolean isCall = option.isCall();
final double f = data.getForward();
final double k = option.getStrike();
final double intrinsicPrice = numeraire * Math.max(0, (isCall ? 1 : -1) * (f - k));
Validate.isTrue(optionPrice > intrinsicPrice || CompareUtils.closeEquals(optionPrice, intrinsicPrice, 1e-6), "option price (" + optionPrice + ") less than intrinsic value (" + intrinsicPrice
+ ")");
if (Double.doubleToLongBits(optionPrice) == Double.doubleToLongBits(intrinsicPrice)) {
return 0.0;
}
double sigma = (Math.abs(data.getNormalVolatility()) < 1E-10 ? 0.3 * f : data.getNormalVolatility());
NormalFunctionData newData = new NormalFunctionData(f, numeraire, sigma);
final double maxChange = 0.5 * f;
double[] priceDerivative = new double[3];
double price = NORMAL_PRICE_FUNCTION.getPriceAdjoint(option, newData, priceDerivative);
double vega = priceDerivative[1];
double change = (price - optionPrice) / vega;
double sign = Math.signum(change);
change = sign * Math.min(maxChange, Math.abs(change));
if (change > 0 && change > sigma) {
change = sigma;
}
int count = 0;
while (Math.abs(change) > EPS) {
sigma -= change;
newData = new NormalFunctionData(f, numeraire, sigma);
price = NORMAL_PRICE_FUNCTION.getPriceAdjoint(option, newData, priceDerivative);
vega = priceDerivative[1];
change = (price - optionPrice) / vega;
sign = Math.signum(change);
change = sign * Math.min(maxChange, Math.abs(change));
if (change > 0 && change > sigma) {
change = sigma;
}
if (count++ > MAX_ITERATIONS) {
final BracketRoot bracketer = new BracketRoot();
final BisectionSingleRootFinder rootFinder = new BisectionSingleRootFinder(EPS);
final Function1D<Double, Double> func = new Function1D<Double, Double>() {
private static final long serialVersionUID = 1L;
@SuppressWarnings({"synthetic-access" })
@Override
public Double evaluate(final Double volatility) {
final NormalFunctionData myData = new NormalFunctionData(data.getForward(), data.getNumeraire(), volatility);
return NORMAL_PRICE_FUNCTION.getPriceFunction(option).evaluate(myData) - optionPrice;
}
};
final double[] range = bracketer.getBracketedPoints(func, 0.0, 10.0);
return rootFinder.getRoot(func, range[0], range[1]);
}
}
return sigma;
}
/**
* Computes the implied volatility using an approximate explicit transformation formula.
* <p>
* The forward and the strike must be positive.
* <p>
* Reference: Hagan, P. S. Volatility conversion calculator. Technical report, Bloomberg.
*
* @param forward the forward rate/price
* @param strike the option strike
* @param timeToExpiry the option time to maturity
* @param blackVolatility the Black implied volatility
* @return the implied volatility
*/
public static double impliedVolatilityFromBlackApproximated(
double forward,
double strike,
double timeToExpiry,
double blackVolatility) {
ArgumentChecker.isTrue(strike > 0, "strike must be strctly positive");
ArgumentChecker.isTrue(forward > 0, "strike must be strctly positive");
double lnFK = Math.log(forward / strike);
double s2t = blackVolatility * blackVolatility * timeToExpiry;
if (Math.abs((forward - strike) / strike) < ATM_LIMIT) {
double factor1 = Math.sqrt(forward * strike);
double factor2 = (1.0d + lnFK * lnFK / 24.0d) / (1.0d + s2t / 24.0d + s2t * s2t / 5670.0d);
return blackVolatility * factor1 * factor2;
}
double factor1 = (forward - strike) / lnFK;
double factor2 = 1.0d / (1.0d + (1.0d - lnFK * lnFK / 120.0d) / 24.0d * s2t + s2t * s2t / 5670.0d);
return blackVolatility * factor1 * factor2;
}
}