/** * 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.smile.fitting.interpolation; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; import com.opengamma.analytics.financial.model.finitedifference.applications.PDEUtilityTools; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.math.function.Function; import com.opengamma.analytics.math.minimization.ParameterLimitsTransform; import com.opengamma.analytics.math.minimization.ParameterLimitsTransform.LimitType; import com.opengamma.analytics.math.minimization.SingleRangeLimitTransform; import com.opengamma.analytics.math.surface.FunctionalDoublesSurface; import com.opengamma.util.test.TestGroup; /** * Test. */ @Test(groups = TestGroup.UNIT) public class ShiftedLogNormalTailExtrapolationTest { private static final ShiftedLogNormalTailExtrapolationFitter FITTER = new ShiftedLogNormalTailExtrapolationFitter(); private static final double FORWARD = 0.04; private static final double EXPIRY = 2.5; private static final double[][] LEFT_STRIKES = { {0.0057, 0.0061 }, {0.02, 0.025 }, {0.02, 0.021 }, {0.015, 0.035 } }; private static final double[][] RIGHT_STRIKES = { {0.041, 0.0415 }, {0.045, 0.09 }, {0.055, 0.065 } }; private static final double[][] LEFT_VOLS = { {0.7366, 0.7277 }, {0.35, 0.3 }, {0.4, 0.41 }, {0.31, 0.15 } }; private static final double[][] RIGHT_VOLS = { {0.35, 0.348 }, {0.4, 0.42 }, {0.31, 0.37 } }; private static final double[] LEFT_DD = {0.1, 0.1, 0.2, 0.1 }; private static final double[] RIGHT_DD = {-0.4, -0.4, -0.3 }; private static final double[] LEFT_DV_DK; private static final double[] RIGHT_DV_DK; private static final double[][] LEFT_PRICES; private static final double[][] RIGHT_PRICES; static { final int nl = LEFT_STRIKES.length; LEFT_PRICES = new double[nl][2]; LEFT_DV_DK = new double[nl]; for (int i = 0; i < nl; i++) { LEFT_DV_DK[i] = (LEFT_DD[i] - BlackFormulaRepository.dualDelta(FORWARD, LEFT_STRIKES[i][0], EXPIRY, LEFT_VOLS[i][0], false)) / BlackFormulaRepository.vega(FORWARD, LEFT_STRIKES[i][0], EXPIRY, LEFT_VOLS[i][0]); for (int j = 0; j < 2; j++) { LEFT_PRICES[i][j] = BlackFormulaRepository.price(FORWARD, LEFT_STRIKES[i][j], EXPIRY, LEFT_VOLS[i][j], false); } } final int nr = RIGHT_STRIKES.length; RIGHT_PRICES = new double[nl][2]; RIGHT_DV_DK = new double[nr]; for (int i = 0; i < nr; i++) { RIGHT_DV_DK[i] = (RIGHT_DD[i] - BlackFormulaRepository.dualDelta(FORWARD, RIGHT_STRIKES[i][0], EXPIRY, RIGHT_VOLS[i][0], true)) / BlackFormulaRepository.vega(FORWARD, RIGHT_STRIKES[i][0], EXPIRY, RIGHT_VOLS[i][0]); for (int j = 0; j < 2; j++) { RIGHT_PRICES[i][j] = BlackFormulaRepository.price(FORWARD, RIGHT_STRIKES[i][j], EXPIRY, RIGHT_VOLS[i][j], true); } } } @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 leftTailPriceTest() { final int nl = LEFT_STRIKES.length; for (int i = 0; i < nl; i++) { double[] res = FITTER.fitTwoPrices(FORWARD, LEFT_STRIKES[i], LEFT_PRICES[i], EXPIRY, false); assertEquals(2, res.length); for (int j = 0; j < 2; j++) { double price = ShiftedLogNormalTailExtrapolation.price(FORWARD, LEFT_STRIKES[i][j], EXPIRY, false, res[0], res[1]); assertEquals(LEFT_PRICES[i][j], price, 1e-9); } double pOld = ShiftedLogNormalTailExtrapolation.price(FORWARD, 0.0, EXPIRY, false, res[0], res[1]); assertEquals(0.0, pOld, 0.0); for (int j = 1; j < 5; j++) { double k = FORWARD * (j / 10.); double price = ShiftedLogNormalTailExtrapolation.price(FORWARD, k, EXPIRY, false, res[0], res[1]); assertTrue(price > pOld); pOld = price; } } } @Test public void leftTailPriceGradTest() { final boolean isCall = false; final int n = LEFT_STRIKES.length; for (int i = 0; i < n; i++) { double[] res = FITTER.fitPriceAndGrad(FORWARD, LEFT_STRIKES[i][0], LEFT_PRICES[i][0], LEFT_DD[i], EXPIRY, isCall); assertEquals(2, res.length); double price = ShiftedLogNormalTailExtrapolation.price(FORWARD, LEFT_STRIKES[i][0], EXPIRY, isCall, res[0], res[1]); assertEquals(LEFT_PRICES[i][0], price, 1e-9); double dd = ShiftedLogNormalTailExtrapolation.dualDelta(FORWARD, LEFT_STRIKES[i][0], EXPIRY, isCall, res[0], res[1]); assertEquals(LEFT_DD[i], dd, 1e-9); } } @Test public void leftTailVolGradTest() { final int n = LEFT_STRIKES.length; for (int i = 0; i < n; i++) { double[] res = FITTER.fitVolatilityAndGrad(FORWARD, LEFT_STRIKES[i][0], LEFT_VOLS[i][0], LEFT_DV_DK[i], 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); double dd = ShiftedLogNormalTailExtrapolation.dVdK(FORWARD, LEFT_STRIKES[i][0], EXPIRY, res[0], res[1], vol); assertEquals(LEFT_DV_DK[i], dd, 1e-6); } } @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 public void leftTailVolMaxGradTest() { int i = 2; final double strike = LEFT_STRIKES[i][0]; final double volInput = LEFT_VOLS[i][0]; final boolean isCall = strike >= FORWARD; final double blackDD = BlackFormulaRepository.dualDelta(FORWARD, strike, EXPIRY, volInput, isCall); final double blackVega = BlackFormulaRepository.vega(FORWARD, strike, EXPIRY, volInput); final double maxGrad = (isCall ? -blackDD : 1 - blackDD) / blackVega; double[] res = FITTER.fitVolatilityAndGrad(FORWARD, strike, volInput, maxGrad-1e-8, EXPIRY); double volOutput = ShiftedLogNormalTailExtrapolation.impliedVolatility(FORWARD, LEFT_STRIKES[i][0], EXPIRY, res[0], res[1]); assertEquals(volInput, volOutput, 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 rightTailPriceTest() { final int nl = RIGHT_STRIKES.length; for (int i = 0; i < nl; i++) { double[] res = FITTER.fitTwoPrices(FORWARD, RIGHT_STRIKES[i], RIGHT_PRICES[i], EXPIRY, true); assertEquals(2, res.length); for (int j = 0; j < 2; j++) { double price = ShiftedLogNormalTailExtrapolation.price(FORWARD, RIGHT_STRIKES[i][j], EXPIRY, true, res[0], res[1]); assertEquals(RIGHT_PRICES[i][j], price, 1e-9); } double pOld = ShiftedLogNormalTailExtrapolation.price(FORWARD, 2.0 * FORWARD, EXPIRY, true, res[0], res[1]); for (int j = 2; j < 6; j++) { double k = 2 * FORWARD * j; double price = ShiftedLogNormalTailExtrapolation.price(FORWARD, k, EXPIRY, true, res[0], res[1]); assertTrue(price < pOld); pOld = price; } } } @Test public void rightTailPriceGradTest() { final boolean isCall = true; final int nr = RIGHT_STRIKES.length; for (int i = 0; i < nr; i++) { double[] res = FITTER.fitPriceAndGrad(FORWARD, RIGHT_STRIKES[i][0], RIGHT_PRICES[i][0], RIGHT_DD[i], EXPIRY, isCall); assertEquals(2, res.length); double price = ShiftedLogNormalTailExtrapolation.price(FORWARD, RIGHT_STRIKES[i][0], EXPIRY, isCall, res[0], res[1]); assertEquals(RIGHT_PRICES[i][0], price, 1e-9); double dd = ShiftedLogNormalTailExtrapolation.dualDelta(FORWARD, RIGHT_STRIKES[i][0], EXPIRY, isCall, res[0], res[1]); assertEquals(RIGHT_DD[i], dd, 1e-7); } } @Test public void rightTailVolGradTest() { 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], RIGHT_DV_DK[i], 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); double dd = ShiftedLogNormalTailExtrapolation.dVdK(FORWARD, RIGHT_STRIKES[i][0], EXPIRY, res[0], res[1], vol); assertEquals(RIGHT_DV_DK[i], dd, 1e-6); } } @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.fitTwoVolatilities(FORWARD, LEFT_STRIKES[i], LEFT_VOLS[i], EXPIARY); 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); } @Test(enabled = false) public void debugTest() { double eps = 1e-300; double f = 1.0; double t = 2. / 52.; // double price = 0.0005; // double dd = -0.04; double k = 1.24; // double mu = 0.1; // double theta = 0.4; // double p = ShiftedLogNormalTailExtrapolation.price(f, k, t, true, mu, theta); // double dd = ShiftedLogNormalTailExtrapolation.dualDelta(f, k, t, true, mu, theta); // System.out.println(p + "\t" + dd); for (int i = 0; i < 100; i++) { double mu = 0.172 + 0.0005 * i / 100.; for (int j = 0; j < 100; j++) { double theta = 0.125 + 0.001 * j / 100.; double p = Math.max(eps, ShiftedLogNormalTailExtrapolation.price(f, k, t, true, mu, theta)); double dd = Math.min(-eps, ShiftedLogNormalTailExtrapolation.dualDelta(f, k, t, true, mu, theta)); System.out.println(mu + "\t" + theta + "\t" + p + "\t" + dd); } } // ShiftedLogNormalTailExtrapolationFitter fitter = new ShiftedLogNormalTailExtrapolationFitter(); // double[] res = fitter.fitPriceAndGrad(f, k, price, dd, t, true); // System.out.println(res[0] + "\t" + res[1]); } @Test (enabled = false) public void debugTest2() { final ParameterLimitsTransform trans = new SingleRangeLimitTransform(0.0, LimitType.GREATER_THAN); ShiftedLogNormalTailExtrapolationFitter fitter = new ShiftedLogNormalTailExtrapolationFitter(); double f = 1.0; double t = 2. / 52.; double k = 1.1; double mu = 0.133333; double theta = 0.16; double p = ShiftedLogNormalTailExtrapolation.price(f, k, t, true, mu, theta); double dd = ShiftedLogNormalTailExtrapolation.dualDelta(f, k, t, true, mu, theta); System.out.println("price and DD " + p + "\t" + dd); System.out.println("trans " + trans.inverseTransform(-2.2521684610628063)); double[] res = fitter.fitPriceAndGrad(f, k, p, dd, t, true); assertEquals("mu ", mu, res[0], 1e-8); assertEquals("theta", theta, res[1], 1e-8); } @Test public void roundTripTest() { ShiftedLogNormalTailExtrapolationFitter fitter = new ShiftedLogNormalTailExtrapolationFitter(); double f = 1.0; double t = 2. / 52.; for (int i = 0; i < 10; i++) { double mu = 0.0 + 0.2 * i / 9.; for (int j = 0; j < 10; j++) { double theta = 0.1 + 0.3 * j / 10.; for (int k = 0; k < 10; k++) { double strike = 1.1 + 0.1 * k; double p = ShiftedLogNormalTailExtrapolation.price(f, strike, t, true, mu, theta); double dd = ShiftedLogNormalTailExtrapolation.dualDelta(f, strike, t, true, mu, theta); // System.out.println(" ShiftedLogNormalTailExtrapolationTest " + mu + "\t" + theta + "\t" + strike + "\t" + p + "\t" + dd); double[] res = fitter.fitPriceAndGrad(f, strike, p, dd, t, true); assertEquals("mu (k = " + strike + ", mu = " + mu + ", theta = " + theta + ")", mu, res[0], 1e-7); assertEquals("theta (k = " + strike + ", mu = " + mu + ", theta = " + theta + ")", theta, res[1], 1e-7); } } } } @Test(enabled = false) public void failingTest() { double f = 1332.6440427977093; double k = 500.0; double t = 0.06557377049180328; double vol = 1.173565; double volGrad = -0.002302809210959822 / 1.1; ShiftedLogNormalTailExtrapolationFitter fitter = new ShiftedLogNormalTailExtrapolationFitter(); double[] res = fitter.fitVolatilityAndGrad(f, k, vol, volGrad, t); System.out.println(res[0] + "\t" + res[1]); } @Test (enabled = false) public void printSeachSurfaceTest2() { final double f = 1332.6440427977093; final double k = 500.0; final double t = 0.06557377049180328; final double vol = 1.173565; final double volGrad = -0.002302809210959822 / 1.0; final double p = BlackFormulaRepository.price(f, k, t, vol, false); final double dd = BlackFormulaRepository.dualDelta(f, k, t, vol, false) + BlackFormulaRepository.vega(f, k, t, vol) * volGrad; 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 sigma = 0.8 * mu + s; double p1 = Math.log(ShiftedLogNormalTailExtrapolation.price(f, k, t, false, mu, sigma) / p); double p2 = Math.log(ShiftedLogNormalTailExtrapolation.dualDelta(f, k, t, false, mu, sigma) / dd); double temp = p1 * p1 + p2 * p2; return Double.isInfinite(temp) ? 1e6 : temp; } }; FunctionalDoublesSurface surf = FunctionalDoublesSurface.from(func); PDEUtilityTools.printSurface("debug", surf, 250, 300, 70, 90.0, 200, 200); double mu = 0.1; double sigma = 0.1; double r1 = ShiftedLogNormalTailExtrapolation.impliedVolatility(f, k, t, mu, sigma); double r2 = ShiftedLogNormalTailExtrapolation.dVdK(f, k, t, mu, sigma); System.out.println(r1 + "\t" + r2); } }