ShiftedLogNormalTailExtrapolation { public static double impliedVolatility(final double forward, final double strike, final double timeToExpiry, final double mu, final double theta) { boolean isCall = strike >= forward; if (strike == 0) { return theta; } if (mu == 0) { return theta; } double p = price(forward, strike, timeToExpiry, isCall, mu, theta); if (p <= 1e-100) { double c = Math.log(strike / forward); double a = timeToExpiry / 2; double b = (-c + mu - theta * theta * timeToExpiry / 2) / theta; double arg = b * b - 4 * a * c; if (arg < 0) { throw new MathException("cannot solve for sigma"); } double root = Math.sqrt(arg); double volGuess = isCall ? (-b - root) / 2 / a : (-b + root) / 2 / a; return volGuess; } if (!isCall && p >= strike) { double c = Math.log(strike / forward); double a = timeToExpiry / 2; double b = (c + mu - theta * theta * timeToExpiry / 2) / theta; double arg = b * b - 4 * a * c; if (arg < 0) { throw new MathException("cannot solve for sigma"); } double root = Math.sqrt(arg); double volGuess = isCall ? (-b - root) / 2 / a : (-b + root) / 2 / a; return volGuess; } return BlackFormulaRepository.impliedVolatility(p, forward, strike, timeToExpiry, isCall); }  static double price(final double forward, final double strike, final double timeToExpiry, final boolean isCall, final double mu, final double theta); static double impliedVolatility(final double forward, final double strike, final double timeToExpiry, final double mu, final double theta); static double dualDelta(final double forward, final double strike, final double timeToExpiry, final boolean isCall, final double mu, final double theta); static double dVdK(final double forward, final double strike, final double timeToExpiry, final double mu, final double theta);  }
@Test public void leftTailVolTest() { final int nl = LEFT_STRIKES.length; for (int i = 0; i < nl; i++) { double[] res = FITTER.fitTwoVolatilities(FORWARD, LEFT_STRIKES[i], LEFT_VOLS[i], EXPIRY); assertEquals(2, res.length); for (int j = 0; j < 2; j++) { double vol = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, LEFT_STRIKES[i][j], EXPIRY, res[0], res[1]); assertEquals(LEFT_VOLS[i][j], vol, 1e-9); } for (int j = 0; j < 5; j++) { double k = FORWARD * (j / 10.); double vol0 = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, k, EXPIRY, res[0], res[1]); assertTrue(vol0 > 0.0 && !Double.isInfinite(vol0) && !Double.isNaN(vol0)); } } }
@Test public void leftTailVolGradZeroTest() { final int n = LEFT_STRIKES.length; final double volGrad00 = 0.0; for (int i = 0; i < n; i++) { double[] res = FITTER.fitVolatilityAndGrad(FORWARD, LEFT_STRIKES[i][0], LEFT_VOLS[i][0], volGrad00, EXPIRY); assertEquals(2, res.length); double vol = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, LEFT_STRIKES[i][0], EXPIRY, res[0], res[1]); assertEquals(LEFT_VOLS[i][0], vol, 1e-8); } }
@Test(enabled = false) public void leftTailVolGradComparison() { final int i = 2; double[] shiftedParams = FITTER.fitVolatilityAndGrad(FORWARD, LEFT_STRIKES[i][0], LEFT_VOLS[i][0], LEFT_DV_DK[i], EXPIRY); double[] shiftedParamsZero = FITTER.fitVolatilityAndGrad(FORWARD, LEFT_STRIKES[i][0], LEFT_VOLS[i][0], 0.0, EXPIRY); final int nSteps = 200; final double kMax = LEFT_STRIKES[i][0]; final double sizeSteps = kMax / nSteps; double k = sizeSteps; System.out.println("Strike" + "," + "DualDelta" + "," + "Flat"); for (int j = 0; j < nSteps; j++) { double vol = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, k, EXPIRY, shiftedParams[0], shiftedParams[1]); double volZero = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, k, EXPIRY, shiftedParamsZero[0], shiftedParamsZero[1]); System.out.println(k + "," + vol + "," + volZero); k += sizeSteps; } }
@Test public void rightTailVolTest() { final int nl = RIGHT_STRIKES.length; for (int i = 0; i < nl; i++) { double[] res = FITTER.fitTwoVolatilities(FORWARD, RIGHT_STRIKES[i], RIGHT_VOLS[i], EXPIRY); assertEquals(2, res.length); for (int j = 0; j < 2; j++) { double vol = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, RIGHT_STRIKES[i][j], EXPIRY, res[0], res[1]); assertEquals(RIGHT_VOLS[i][j], vol, 1e-9); } for (int j = 1; j < 6; j++) { double k = 2 * FORWARD * j; double vol0 = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, k, EXPIRY, res[0], res[1]); assertTrue(vol0 > 0.0 && !Double.isInfinite(vol0) && !Double.isNaN(vol0)); } } }
@Test public void rightTailVolGradZeroTest() { final int n = RIGHT_STRIKES.length; for (int i = 0; i < n; i++) { double[] res = FITTER.fitVolatilityAndGrad(FORWARD, RIGHT_STRIKES[i][0], RIGHT_VOLS[i][0], 0.0, EXPIRY); assertEquals(2, res.length); double vol = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, RIGHT_STRIKES[i][0], EXPIRY, res[0], res[1]); assertEquals(RIGHT_VOLS[i][0], vol, 1e-7); } }
@Test (enabled = false) public void leftTailVolPrint() { final int nl = LEFT_STRIKES.length; for (int i = 0; i < nl; i++) { double[] res = FITTER.fitVolatilityAndGrad(FORWARD, LEFT_STRIKES[i][0], LEFT_VOLS[i][0], LEFT_DV_DK[i], EXPIRY); assertEquals(2, res.length); System.out.println("fit:\t" + res[0] + "\t" + res[1]); for (int j = 0; j < 101; j++) { double d = -5.0 + 4.8 * j / 100.; double k = FORWARD * Math.exp(d * Math.sqrt(EXPIRY)); double vol = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, k, EXPIRY, res[0], res[1]); System.out.println(k + "\t" + vol); } System.out.print("\n"); } }
@Test (enabled = false) public void rightTailVolPrint() { System.out.println("ShiftedLogNormalTailExtrapolationTest"); final int n = RIGHT_STRIKES.length; for (int i = 0; i < n; i++) { double[] res = FITTER.fitTwoVolatilities(FORWARD, RIGHT_STRIKES[i], RIGHT_VOLS[i], EXPIRY); assertEquals(2, res.length); System.out.println("fit:\t" + res[0] + "\t" + res[1]); for (int j = 0; j < 101; j++) { double d = 1.0 + 4.5 * j / 100.; double k = FORWARD * Math.exp(d * Math.sqrt(EXPIRY)); double vol = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, k, EXPIRY, res[0], res[1]); System.out.println(k + "\t" + vol); } System.out.print("\n"); } }
@Test(enabled = false) public void printSeachSurfaceTest() { System.out.println("ShiftedLogNormalTailExtrapolationTest"); Function<Double, Double> func = new Function<Double, Double>() { @Override public Double evaluate(Double... x) { double mu = x[0]; double sigma = x[1]; double vol1 = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, LEFT_STRIKES[1][0], EXPIRY, mu, sigma) - LEFT_VOLS[1][0]; double vol2 = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, LEFT_STRIKES[1][1], EXPIRY, mu, sigma) - LEFT_VOLS[1][1]; return vol1 * vol1 + vol2 * vol2; } }; FunctionalDoublesSurface surf = FunctionalDoublesSurface.from(func); PDEUtilityTools.printSurface("debug", surf, -0.7, -0.0, 0.01, 0.4, 200, 200); }