/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.option;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.Validate;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
import com.opengamma.util.CompareUtils;
/**
* Given a model that returns the implied volatility of a European option as a function of strike (e.g. SABR), this find the implied
* terminal distribution
*/
public class DistributionFromImpliedVolatility implements ProbabilityDistribution<Double> {
private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
private static final double EPS = 1e-2;
private final double _f;
private final double _rootT;
private final Function1D<Double, Double> _volFunc;
public DistributionFromImpliedVolatility(final double forward, final double maturity, final Function1D<Double, Double> impliedVolFunction) {
Validate.isTrue(maturity > 0.0, "maturity <= 0");
Validate.isTrue(forward > 0.0, "forward <= 0");
Validate.notNull(impliedVolFunction, "implied vol function");
_f = forward;
_volFunc = impliedVolFunction;
_rootT = Math.sqrt(maturity);
}
@Override
public double getPDF(final Double x) {
final double[] sigmas = getSigmas(x);
final double d1 = getD1(x, sigmas[1] * _rootT);
final double sigmaPrime = getSigmaPrime(sigmas, x);
final double sigmaDoublePrime = getSigmaDoublePrime(sigmas, x);
final double d1Prime = getD1Prime(x, sigmas[1], sigmaPrime);
final double d2Prime = d1Prime - _rootT * sigmaPrime;
return -_f * NORMAL.getPDF(d1) * (_rootT * (d1 * d1Prime * sigmaPrime - sigmaDoublePrime) + d2Prime / x);
}
@Override
public double getCDF(final Double x) {
final double[] sigmas = getSigmas(x);
final double d1 = getD1(x, sigmas[1] * _rootT);
final double d2 = d1 - sigmas[1] * _rootT;
final double sigmaPrime = getSigmaPrime(sigmas, x);
return NORMAL.getCDF(-d2) + _f * NORMAL.getPDF(d1) * sigmaPrime * _rootT;
}
@Override
public double getInverseCDF(final Double p) {
throw new NotImplementedException();
}
@Override
public double nextRandom() {
throw new NotImplementedException();
}
private double[] getSigmas(final double k) {
final double[] res = new double[3];
res[1] = _volFunc.evaluate(k);
final double kUp = k * (1 + EPS);
final double kDown = k * (1 - EPS);
res[2] = _volFunc.evaluate(kUp);
res[0] = _volFunc.evaluate(kDown);
return res;
}
private double getSigmaPrime(final double[] sigmas, final double k) {
return (sigmas[2] - sigmas[0]) / 2 / k / EPS;
}
private double getSigmaDoublePrime(final double[] sigmas, final double k) {
return (sigmas[2] + sigmas[0] - 2 * sigmas[1]) / k / k / EPS / EPS;
}
private double getD1(final double k, final double sigmaRootT) {
final double numerator = (Math.log(_f / k) + sigmaRootT * sigmaRootT / 2);
if (CompareUtils.closeEquals(numerator, 0, 1e-16)) {
return 0;
}
return numerator / sigmaRootT;
}
private double getD1Prime(final double k, final double sigma, final double sigmaPrime) {
final double res = -1 / k / sigma / _rootT - (Math.log(_f / k) / sigma / sigma / _rootT - 0.5 * _rootT) * sigmaPrime;
return res;
}
}