Java tutorial
/** * 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 org.apache.commons.lang.Validate; 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 { private static final double EPS = 1e-12; private static final BracketRoot BRACKETER = new BracketRoot(); private static final BisectionSingleRootFinder ROOT_FINDER = new BisectionSingleRootFinder(EPS); private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1); private static final SurfaceConverter INSTANCE = new SurfaceConverter(); private SurfaceConverter() { } 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>() { @SuppressWarnings("synthetic-access") @Override public Double evaluate(final Double... tx) { final double t = tx[0]; final double x = tx[1]; Validate.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); } /** * Convert a volatility surface parameterised by log-moneyness to one parameterised by delta * @param logMoneynessSurf * @return */ // Surface<Double, Double, Double> logMoneynessToDelta(final Surface<Double, Double, Double> logMoneynessSurf) { // final Function<Double, Double> surFunc = new Function<Double, Double>() { // @SuppressWarnings("synthetic-access") // @Override // public Double evaluate(final Double... td) { // final double t = td[0]; // final double delta = td[1]; // Validate.isTrue(t >= 0, "Must have t >= 0.0"); // Validate.isTrue(delta > 0 && delta < 1.0, "Delta not in range (0,1)"); // 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 = logMoneynessSurf.getZValue(t, x); // return sigma * sigma * t / 2 - sigma * rootT * inDelta - x; // } // }; // // final double logMoneynessApprox = func.evaluate(0.0); // // double l, u; // if (logMoneynessApprox > 0) { // l = 0.8 * logMoneynessApprox; // u = 1.2 * logMoneynessApprox; // } else { // l = 1.2 * logMoneynessApprox; // u = 0.8 * logMoneynessApprox; // } // // final double[] range = BRACKETER.getBracketedPoints(func, l, u); // final double logMoneyness = ROOT_FINDER.getRoot(func, range[0], range[1]); // return logMoneynessSurf.getZValue(t, logMoneyness); // } // }; // return FunctionalDoublesSurface.from(surFunc); // } Surface<Double, Double, Double> logMoneynessToDelta(final Surface<Double, Double, Double> logMoneynessSurf) { final Function<Double, Double> surFunc = new Function<Double, Double>() { @SuppressWarnings("synthetic-access") @Override public Double evaluate(final Double... td) { final double t = td[0]; final double delta = td[1]; Validate.isTrue(t >= 0, "Must have t >= 0.0"); Validate.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]; Validate.isTrue(t >= 0, "Must have t >= 0.0"); Validate.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]; Validate.isTrue(t >= 0, "Must have t >= 0.0"); Validate.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]; Validate.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]; Validate.isTrue(t >= 0, "Must have t >= 0.0"); Validate.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]; Validate.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]; Validate.isTrue(t >= 0, "Must have t >= 0.0"); Validate.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); } }