/**
* Copyright (C) 2009 - 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.BlackFunctionData;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.rootfinding.NewtonRaphsonSingleRootFinder;
import com.opengamma.util.ArgumentChecker;
/**
* Computes the implied volatility in a log-normally (Black) distributed asset price world.
*/
public class BlackImpliedVolatilityFormula {
/** Limit defining "close of ATM forward" to avoid the formula singularity. **/
private static final double ATM_LIMIT = 1.0E-3;
private static final double ROOT_ACCURACY = 1.0E-7;
private static final NewtonRaphsonSingleRootFinder ROOT_FINDER = new NewtonRaphsonSingleRootFinder(ROOT_ACCURACY);
/**
* Computes the implied volatility from the price in a log-normally distributed asset price world.
* @param data The model data. The data volatility is not used.
* @param option The option.
* @param optionPrice The option price.
* @return The implied volatility.
*/
public double getImpliedVolatility(
BlackFunctionData data,
EuropeanVanillaOption option,
double optionPrice) {
Validate.notNull(data, "null data");
Validate.notNull(option, "null option");
final double discountFactor = data.getDiscountFactor();
final boolean isCall = option.isCall();
final double f = data.getForward();
final double k = option.getStrike();
final double t = option.getTimeToExpiry();
final double fwdPrice = optionPrice / discountFactor;
return BlackFormulaRepository.impliedVolatility(fwdPrice, f, k, t, isCall);
}
/**
* Compute the implied volatility from a normal volatility using an approximate initial guess and a root-finder.
* <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 normalVolatility the Black implied volatility
* @return the implied volatility
*/
public static double impliedVolatilityFromNormalApproximated(
final double forward,
final double strike,
final double timeToExpiry,
final double normalVolatility) {
ArgumentChecker.isTrue(strike > 0, "strike must be strictly positive");
ArgumentChecker.isTrue(forward > 0, "strike must be strictly positive");
// initial guess
double guess = impliedVolatilityFromNormalApproximated2(forward, strike, timeToExpiry, normalVolatility);
// Newton-Raphson method
final Function1D<Double, Double> func = new Function1D<Double, Double>() {
private static final long serialVersionUID = 1L;
@Override
public Double evaluate(Double volatility) {
return NormalImpliedVolatilityFormula
.impliedVolatilityFromBlackApproximated(forward, strike, timeToExpiry, volatility) - normalVolatility;
}
};
return ROOT_FINDER.getRoot(func, guess);
}
/**
* Compute the implied volatility from a normal volatility using an approximate explicit.
* <p>
* The formula is usually not good enough to be used as such, but provide a good initial guess for a
* root-finding procedure. Use {@link BlackImpliedVolatilityFormula#impliedVolatilityFromNormalApproximated} for
* more precision.
* <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 normalVolatility the Black implied volatility
* @return the implied volatility
*/
public static double impliedVolatilityFromNormalApproximated2(
double forward,
double strike,
double timeToExpiry,
double normalVolatility) {
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 = normalVolatility * normalVolatility * timeToExpiry;
if (Math.abs((forward - strike) / strike) < ATM_LIMIT) {
double factor1 = 1.0d / Math.sqrt(forward * strike);
double factor2 = (1.0d + s2t / (24.0d * forward * strike)) / (1.0d + lnFK * lnFK / 24.0d);
return normalVolatility * factor1 * factor2;
}
double factor1 = lnFK / (forward - strike);
double factor2 = (1.0d + (1.0d - lnFK * lnFK / 120.0d) * s2t / (24.0d * forward * strike));
return normalVolatility * factor1 * factor2;
}
}