/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.volatility.surface; import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve; import com.opengamma.analytics.math.MathException; import com.opengamma.analytics.math.function.Function; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.rootfinding.BisectionSingleRootFinder; import com.opengamma.analytics.math.rootfinding.BracketRoot; import com.opengamma.analytics.math.statistics.distribution.NormalDistribution; import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution; import com.opengamma.analytics.math.surface.FunctionalDoublesSurface; import com.opengamma.analytics.math.surface.Surface; import com.opengamma.util.ArgumentChecker; /** * */ public final class SurfaceConverter { /** The tolerance */ private static final double EPS = 1e-12; /** The root bracketer */ private static final BracketRoot BRACKETER = new BracketRoot(); /** The root-finder */ private static final BisectionSingleRootFinder ROOT_FINDER = new BisectionSingleRootFinder(EPS); /** A normal distribution */ private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1); /** A static instance of this class */ private static final SurfaceConverter INSTANCE = new SurfaceConverter(); private SurfaceConverter() { } /** * Gets the static instance of this class * @return The static instance */ public static SurfaceConverter getInstance() { return INSTANCE; } Surface<Double, Double, Double> deltaToLogMoneyness(final Surface<Double, Double, Double> deltaSurf) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { final double t = tx[0]; final double x = tx[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); //find the delta that gives the required log-moneyness (x) at time t final double delta = getDeltaForLogMoneyness(x, deltaSurf, t); return deltaSurf.getZValue(t, delta); } }; return FunctionalDoublesSurface.from(surFunc); } Surface<Double, Double, Double> deltaToMoneyness(final Surface<Double, Double, Double> deltaSurf) { final Surface<Double, Double, Double> logMoneynessSurf = deltaToLogMoneyness(deltaSurf); return logMoneynessToMoneyness(logMoneynessSurf); } Surface<Double, Double, Double> deltaToStrike(final Surface<Double, Double, Double> deltaSurf, final ForwardCurve forwardCurve) { final Surface<Double, Double, Double> moneynessSurf = deltaToMoneyness(deltaSurf); return moneynessToStrike(moneynessSurf, forwardCurve); } Surface<Double, Double, Double> logMoneynessToDelta(final Surface<Double, Double, Double> logMoneynessSurf) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... td) { final double t = td[0]; final double delta = td[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); ArgumentChecker.isTrue(delta > 0 && delta < 1.0, "Delta not in range (0,1)"); //find the log-moneyness that gives the required Black delta at the given time final double x = getlogMoneynessForDelta(delta, logMoneynessSurf, t); return logMoneynessSurf.getZValue(t, x); } }; return FunctionalDoublesSurface.from(surFunc); } Surface<Double, Double, Double> logMoneynessToMoneyness(final Surface<Double, Double, Double> logMoneynessSurf) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { final double t = tx[0]; final double m = tx[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); ArgumentChecker.isTrue(m > 0, "Must have moneyness > 0"); final double logM = Math.log(m); return logMoneynessSurf.getZValue(t, logM); } }; return FunctionalDoublesSurface.from(surFunc); } Surface<Double, Double, Double> logMoneynessToStrike(final Surface<Double, Double, Double> logMoneynessSurf, final ForwardCurve forwardCurve) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... tk) { final double t = tk[0]; final double k = tk[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); ArgumentChecker.isTrue(k > 0, "Must have strike > 0"); final double f = forwardCurve.getForward(t); final double logM = Math.log(k / f); return logMoneynessSurf.getZValue(t, logM); } }; return FunctionalDoublesSurface.from(surFunc); } Surface<Double, Double, Double> moneynessToDelta(final Surface<Double, Double, Double> moneynessSurf) { final Surface<Double, Double, Double> logMoneynessSurf = moneynessToLogMoneyness(moneynessSurf); return logMoneynessToDelta(logMoneynessSurf); } Surface<Double, Double, Double> moneynessToLogMoneyness(final Surface<Double, Double, Double> moneynessSurf) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { final double t = tx[0]; final double lm = tx[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); final double m = Math.exp(lm); return moneynessSurf.getZValue(t, m); } }; return FunctionalDoublesSurface.from(surFunc); } /** * Convert a volatility surface parameterised by moneyness (strike/forward) to one parameterised by strike * @param strikeSurf volatility surface parameterised by moneyness * @param forwardCurve The forward Curve * @return volatility surface parameterised by strike */ Surface<Double, Double, Double> moneynessToStrike(final Surface<Double, Double, Double> moneynessSurf, final ForwardCurve forwardCurve) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... tk) { final double t = tk[0]; final double k = tk[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); ArgumentChecker.isTrue(k > 0, "Must have strike > 0"); final double m = k / forwardCurve.getForward(t); return moneynessSurf.getZValue(t, m); } }; return FunctionalDoublesSurface.from(surFunc); } Surface<Double, Double, Double> strikeToDelta(final Surface<Double, Double, Double> strikeSurf, final ForwardCurve forwardCurve) { final Surface<Double, Double, Double> logMoneynessSurf = strikeToLogMoneyness(strikeSurf, forwardCurve); return logMoneynessToDelta(logMoneynessSurf); } /** * Convert a volatility surface parameterised by strike to one parameterised by moneyness (strike/forward) * @param strikeSurf volatility surface parameterised by strike * @param forwardCurve The forward Curve * @return volatility surface parameterised by moneyness */ Surface<Double, Double, Double> strikeToLogMoneyness(final Surface<Double, Double, Double> strikeSurf, final ForwardCurve forwardCurve) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... tx) { final double t = tx[0]; final double x = tx[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); final double k = Math.exp(x) * forwardCurve.getForward(t); return strikeSurf.getZValue(t, k); } }; return FunctionalDoublesSurface.from(surFunc); } /** * Convert a volatility surface parameterised by strike to one parameterised by moneyness (strike/forward) * @param strikeSurf volatility surface parameterised by strike * @param forwardCurve The forward Curve * @return volatility surface parameterised by moneyness */ Surface<Double, Double, Double> strikeToMoneyness(final Surface<Double, Double, Double> strikeSurf, final ForwardCurve forwardCurve) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @Override public Double evaluate(final Double... tm) { final double t = tm[0]; final double m = tm[1]; ArgumentChecker.isTrue(t >= 0, "Must have t >= 0.0"); ArgumentChecker.isTrue(m > 0, "Must have moneyness > 0"); final double k = m * forwardCurve.getForward(t); return strikeSurf.getZValue(t, k); } }; return FunctionalDoublesSurface.from(surFunc); } double getlogMoneynessForDelta(final double delta, final Surface<Double, Double, Double> logMoneynessSurface, final double t) { ArgumentChecker.isInRangeExclusive(0.0, 1.0, delta); ArgumentChecker.notNull(logMoneynessSurface, "null surface"); ArgumentChecker.isTrue(t > 0, "t must be possitive"); final double rootT = Math.sqrt(t); final double inDelta = NORMAL.getInverseCDF(delta); final Function1D<Double, Double> func = new Function1D<Double, Double>() { @Override public Double evaluate(final Double x) { final double sigma = logMoneynessSurface.getZValue(t, x); return sigma * sigma * t / 2 - inDelta * sigma * rootT - x; } }; final double xEst = func.evaluate(0.0); double l, u; double xMin, xMax; if (xEst < 0.0) { xMin = -100.0; xMax = 1.0; l = Math.max(xMin, 1.25 * xEst); u = 0.75 * xEst; } else { xMin = -1.0; xMax = 100.0; l = 0.75 * xEst; u = Math.min(xMax, 1.25 * xEst); } try { final double[] bracket = BRACKETER.getBracketedPoints(func, l, u, xMin, xMax); final double x = ROOT_FINDER.getRoot(func, bracket[0], bracket[1]); return x; } catch (final MathException e) { String error = "Cannot find a log-moneyness corresponding to a delta of " + delta; if (delta < 0.05) { error += " It is possible that the smile exhibits arbitrable for very high strikes. Check that the call price is always decreasing in strike. "; } else if (delta > 0.95) { error += " It is possible that the smile exhibits arbitrable for very low strikes. Check that the put price is always increasing in strike. "; } else { error += " It is likely that the smile exhibits arbitrage"; } throw new MathException(error + " " + e.getMessage()); } } double getDeltaForLogMoneyness(final double x, final Surface<Double, Double, Double> deltaSurface, final double t) { final double rootT = Math.sqrt(t); final Function1D<Double, Double> func = new Function1D<Double, Double>() { @Override public Double evaluate(final Double d1) { @SuppressWarnings("synthetic-access") final double delta = NORMAL.getCDF(d1); if (delta == 1.0 || delta == 0.0) { return -d1; } try { final double sigma = deltaSurface.getZValue(t, delta); return (-x + sigma * sigma * t / 2) / sigma / rootT - d1; } catch (final MathException e) { return -d1; } } }; final double dEst = func.evaluate(0.0); double l, u; if (dEst < 0.0) { l = 1.25 * dEst; u = 0.75 * dEst; } else { l = 0.75 * dEst; u = 1.25 * dEst; } final double[] bracket = BRACKETER.getBracketedPoints(func, l, u); final Double d1 = ROOT_FINDER.getRoot(func, bracket[0], bracket[1]); return NORMAL.getCDF(d1); } }