/** * Copyright (C) 2014 - 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.assertFalse; import static org.testng.AssertJUnit.assertTrue; import java.util.List; import org.testng.annotations.Test; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.SABRExtrapolationRightFunction; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.financial.model.volatility.smile.function.SABRBerestyckiVolatilityFunction; import com.opengamma.analytics.financial.model.volatility.smile.function.SABRFormulaData; import com.opengamma.analytics.financial.model.volatility.smile.function.SABRHaganAlternativeVolatilityFunction; import com.opengamma.analytics.financial.model.volatility.smile.function.SABRHaganVolatilityFunction; import com.opengamma.analytics.financial.model.volatility.smile.function.SABRPaulotVolatilityFunction; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.util.test.TestGroup; /** * Test class for {@link SmileInterpolatorSABRWithExtrapolation} and its underlying class, * {@link SmileExtrapolationFunctionSABRProvider} */ @Test(groups = TestGroup.UNIT) public class SmileInterpolatorSABRWithExtrapolationTest { /** * Test interpolation part, essentially consistent with the super class (where extrapolation is absent) * iff local fitting is applied. * If global fit is used, resulting interpolation is tested with larger tolerance */ @Test public void interpolationTest() { double eps = 1.0e-14; double expiry = 1.2; double forward = 1.7; int nStrikes = 11; double[] strikes = new double[nStrikes]; double[] impliedVols = new double[] {2.17, 1.92, 1.702, 1.545, 1.281, 0.912, 0.9934, 1.0878, 1.1499, 1.2032, 1.242 }; for (int i = 0; i < nStrikes; ++i) { strikes[i] = forward * (0.85 + i * 0.05); } WeightingFunction weight = LinearWeightingFunction.getInstance(); int seed = 4729; double beta = 0.95; ShiftedLogNormalExtrapolationFunctionProvider extapQuiet = new ShiftedLogNormalExtrapolationFunctionProvider( "Quiet"); SmileInterpolatorSABRWithExtrapolation interpQuiet = new SmileInterpolatorSABRWithExtrapolation(seed, new SABRHaganVolatilityFunction(), beta, weight, extapQuiet); InterpolatedSmileFunction funcQuiet = new InterpolatedSmileFunction(interpQuiet, forward, strikes, expiry, impliedVols); SmileInterpolatorSABR sabr = new SmileInterpolatorSABR(seed, new SABRHaganVolatilityFunction(), beta, weight); Function1D<Double, Double> volFunc = sabr.getVolatilityFunction(forward, strikes, expiry, impliedVols); int nKeys = 20; for (int i = 0; i < nKeys + 1; ++i) { Double key = strikes[0] + (strikes[nStrikes - 1] - strikes[0]) * i / nKeys; assertEquals(volFunc.evaluate(key), funcQuiet.getVolatility(key), eps); } SmileInterpolatorSABRWithExtrapolation interpGlobal1 = new SmileInterpolatorSABRWithExtrapolation( new SABRPaulotVolatilityFunction(), extapQuiet); SmileInterpolatorSABRWithExtrapolation interpGlobal2 = new SmileInterpolatorSABRWithExtrapolation( new SABRBerestyckiVolatilityFunction(), extapQuiet); SmileInterpolatorSABRWithExtrapolation interpGlobal3 = new SmileInterpolatorSABRWithExtrapolation( new SABRHaganAlternativeVolatilityFunction(), extapQuiet); InterpolatedSmileFunction funcGlobal1 = new InterpolatedSmileFunction(interpGlobal1, forward, strikes, expiry, impliedVols); InterpolatedSmileFunction funcGlobal2 = new InterpolatedSmileFunction(interpGlobal2, forward, strikes, expiry, impliedVols); InterpolatedSmileFunction funcGlobal3 = new InterpolatedSmileFunction(interpGlobal3, forward, strikes, expiry, impliedVols); for (int i = 0; i < nKeys + 1; ++i) { Double key = strikes[0] + (strikes[nStrikes - 1] - strikes[0]) * i / nKeys; double ref = funcQuiet.getVolatility(key); assertEquals(ref, funcGlobal1.getVolatility(key), 1.5 * ref * 1.0e-1); assertEquals(ref, funcGlobal2.getVolatility(key), ref * 1.0e-1); assertEquals(ref, funcGlobal3.getVolatility(key), ref * 1.0e-1); } } /** * */ @Test public void hashCodeAndEqualsTest() { WeightingFunction weight = LinearWeightingFunction.getInstance(); int seed = 4729; double beta = 0.95; ShiftedLogNormalExtrapolationFunctionProvider extapQuiet = new ShiftedLogNormalExtrapolationFunctionProvider( "Quiet"); ShiftedLogNormalExtrapolationFunctionProvider extapFlat = new ShiftedLogNormalExtrapolationFunctionProvider("Flat"); SmileInterpolatorSABRWithExtrapolation interpQuiet1 = new SmileInterpolatorSABRWithExtrapolation(seed, new SABRHaganVolatilityFunction(), beta, weight, extapQuiet); SmileInterpolatorSABRWithExtrapolation interpQuiet2 = new SmileInterpolatorSABRWithExtrapolation(seed, new SABRHaganVolatilityFunction(), beta * 0.9, weight, extapQuiet); SmileInterpolatorSABRWithExtrapolation interpQuiet3 = interpQuiet1; SmileInterpolatorSABRWithExtrapolation interpQuiet4 = new SmileInterpolatorSABRWithExtrapolation(seed, new SABRHaganVolatilityFunction(), beta, weight, extapQuiet); SmileInterpolatorSABRWithExtrapolation interpFlat = new SmileInterpolatorSABRWithExtrapolation(seed, new SABRHaganVolatilityFunction(), beta, weight, extapFlat); assertTrue(interpQuiet1.equals(interpQuiet1)); assertTrue(interpQuiet1.hashCode() == interpQuiet3.hashCode()); assertTrue(interpQuiet1.equals(interpQuiet3)); assertTrue(interpQuiet3.equals(interpQuiet1)); assertFalse(interpQuiet1.equals(interpQuiet2)); assertFalse(interpQuiet1.hashCode() == interpQuiet2.hashCode()); assertFalse(interpQuiet1.equals(interpFlat)); assertFalse(interpQuiet1.hashCode() == interpFlat.hashCode()); assertTrue(interpQuiet1.hashCode() == interpQuiet4.hashCode()); assertTrue(interpQuiet1.equals(interpQuiet4)); assertTrue(interpQuiet4.equals(interpQuiet1)); assertFalse(interpQuiet1.equals(null)); assertFalse(interpQuiet1.equals(new SmileInterpolatorSABR())); } /** * Quiet and flat expected behavior for shifted lognormal model extrapolation */ @Test public void SLNQuietAndFlatTest() { double eps = 1.0e-6; double expiry = 1.5; double forward = 1.1; int nStrikes = 10; double[] strikes = new double[nStrikes]; double[] impliedVols = new double[] {1.17, 0.92, 0.802, 0.745, 0.781, 0.812, 0.8334, 0.878, 0.899, 0.9352 }; for (int i = 0; i < nStrikes; ++i) { strikes[i] = forward * (0.85 + i * 0.05); } ShiftedLogNormalExtrapolationFunctionProvider extapFlat = new ShiftedLogNormalExtrapolationFunctionProvider("Flat"); SmileInterpolatorSABRWithExtrapolation interpFlat = new SmileInterpolatorSABRWithExtrapolation(extapFlat); InterpolatedSmileFunction funcFlat = new InterpolatedSmileFunction(interpFlat, forward, strikes, expiry, impliedVols); double[] keys = new double[] {0.1 * strikes[0], 0.36 * strikes[0], 0.99 * strikes[0] }; for (int i = 0; i < keys.length; ++i) { assertEquals(impliedVols[0], funcFlat.getVolatility(keys[i]), 1.e-8); } keys = new double[] {1.1 * strikes[nStrikes - 1], 2.312 * strikes[nStrikes - 1], 12.99 * strikes[nStrikes - 1] }; for (int i = 0; i < keys.length; ++i) { assertEquals(impliedVols[nStrikes - 1], funcFlat.getVolatility(keys[i]), 1.e-8); } ShiftedLogNormalExtrapolationFunctionProvider extapQuiet = new ShiftedLogNormalExtrapolationFunctionProvider( "Quiet"); SmileInterpolatorSABRWithExtrapolation interpQuiet = new SmileInterpolatorSABRWithExtrapolation(extapQuiet); InterpolatedSmileFunction funcQuiet = new InterpolatedSmileFunction(interpQuiet, forward, strikes, expiry, impliedVols); /* * Only C0 continuity is kept */ { double CutoffUp = strikes[0] + eps; double CutoffDw = strikes[0] - eps; double volInt = funcQuiet.getVolatility(CutoffUp); double volExt = funcQuiet.getVolatility(CutoffDw); double volBoundary = funcQuiet.getVolatility(strikes[0]); assertEquals(volBoundary, volInt, eps * 10.0); assertEquals(volBoundary, volExt, eps * 10.0); } { double CutoffUp = strikes[nStrikes - 1] + eps; double CutoffDw = strikes[nStrikes - 1] - eps; double volInt = funcQuiet.getVolatility(CutoffDw); double volExt = funcQuiet.getVolatility(CutoffUp); double volBoundary = funcQuiet.getVolatility(strikes[nStrikes - 1]); assertEquals(volBoundary, volInt, eps * 10.0); assertEquals(volBoundary, volExt, eps * 10.0); } } /** * Check C1 smoothness of shifted lognormal model extrapolation */ @Test public void SLNSmoothnessTest() { double eps = 1.0e-5; double expiry = 1.5; double forward = 1.1; int nStrikes = 10; double[] strikes = new double[nStrikes]; double[] impliedVols = new double[] {0.97, 0.92, 0.802, 0.745, 0.781, 0.812, 0.8334, 0.878, 0.899, 0.9052 }; for (int i = 0; i < nStrikes; ++i) { strikes[i] = forward * (0.85 + i * 0.05); } ShiftedLogNormalExtrapolationFunctionProvider extapSLN = new ShiftedLogNormalExtrapolationFunctionProvider(); SmileInterpolatorSABRWithExtrapolation interpSLN = new SmileInterpolatorSABRWithExtrapolation( new SABRHaganVolatilityFunction(), extapSLN); InterpolatedSmileFunction funcSLN = new InterpolatedSmileFunction(interpSLN, forward, strikes, expiry, impliedVols); /* * left interpolation */ { double CutoffUp = strikes[0] + eps; double CutoffDw = strikes[0] - eps; // Checking volatility function double volInt = funcSLN.getVolatility(CutoffUp); double volExt = funcSLN.getVolatility(CutoffDw); double volBoundary = funcSLN.getVolatility(strikes[0]); assertEquals(volBoundary, volInt, eps); assertEquals(volBoundary, volExt, eps); double volExtDw = funcSLN.getVolatility(CutoffDw - eps); double volFirstExt = (1.5 * volBoundary + 0.5 * volExtDw - 2.0 * volExt) / eps; double volIntUp = funcSLN.getVolatility(CutoffUp + eps); double volFirstInt = (2.0 * volInt - 0.5 * volIntUp - 1.5 * volBoundary) / eps; assertEquals(volFirstInt, volFirstExt, eps); } /* * right interpolation */ { double CutoffUp = strikes[nStrikes - 1] + eps; double CutoffDw = strikes[nStrikes - 1] - eps; // Checking volatility function double volInt = funcSLN.getVolatility(CutoffDw); double volExt = funcSLN.getVolatility(CutoffUp); double volBoundary = funcSLN.getVolatility(strikes[nStrikes - 1]); assertEquals(volBoundary, volInt, eps); assertEquals(volBoundary, volExt, eps); double volExtUp = funcSLN.getVolatility(CutoffUp + eps); double volFirstExt = (2.0 * volExt - 0.5 * volExtUp - 1.5 * volBoundary) / eps; double volIntDw = funcSLN.getVolatility(CutoffDw - eps); double volFirstInt = (-2.0 * volInt + 1.5 * volBoundary + 0.5 * volIntDw) / eps; assertEquals(volFirstInt, volFirstExt, eps); } } /** * Check extrapolation is recovered for shifted lognormal model extrapolation */ @Test public void functionRecoverySLNTest() { final double forward = 1.0; final double expiry = 3.0; int nSamples = 11; double[] strikes = new double[nSamples]; double[] vols = new double[nSamples]; final double muLeft = 0.4; final double thetaLeft = 0.5; // Expected left extrapolation Function1D<Double, Double> left = new Function1D<Double, Double>() { @Override public Double evaluate(Double strike) { return ShiftedLogNormalTailExtrapolation.impliedVolatility(forward, strike, expiry, muLeft, thetaLeft); } }; final double muRight = -0.3; final double thetaRight = 0.5; // Expected right extrapolation Function1D<Double, Double> right = new Function1D<Double, Double>() { @Override public Double evaluate(Double strike) { return ShiftedLogNormalTailExtrapolation.impliedVolatility(forward, strike, expiry, muRight, thetaRight); } }; for (int i = 0; i < 5; ++i) { double strike = forward * (0.75 + 0.05 * i); vols[i] = left.evaluate(strike); strikes[i] = strike; } for (int i = 6; i < nSamples; ++i) { double strike = forward * (0.75 + 0.05 * i); vols[i] = right.evaluate(strike); strikes[i] = strike; } strikes[5] = forward; vols[5] = 0.5 * (vols[4] + vols[6]); ShiftedLogNormalExtrapolationFunctionProvider extapSLN = new ShiftedLogNormalExtrapolationFunctionProvider(); SmileInterpolatorSABRWithExtrapolation interpSLN = new SmileInterpolatorSABRWithExtrapolation(extapSLN); InterpolatedSmileFunction funcSLN = new InterpolatedSmileFunction(interpSLN, forward, strikes, expiry, vols); double[] keys = new double[] {forward * 0.1, forward * 0.5, forward * 0.66 }; for (int i = 0; i < keys.length; ++i) { assertEquals(left.evaluate(keys[i]), funcSLN.getVolatility(keys[i]), 1.e-2); } keys = new double[] {forward * 1.31, forward * 1.5, forward * 2.61, forward * 15.0 }; for (int i = 0; i < keys.length; ++i) { assertEquals(right.evaluate(keys[i]), funcSLN.getVolatility(keys[i]), 1.e-2); } } /** * Check C2 smoothness of Benaim-Dodgson-Kainth extrapolation */ @Test public void BDKSmoothnessGeneralTest() { double eps = 1.0e-5; double expiry = 1.5; double forward = 1.1; int nStrikes = 10; double[] strikes = new double[nStrikes]; double[] impliedVols = new double[] {0.97, 0.92, 0.802, 0.745, 0.781, 0.812, 0.8334, 0.878, 0.899, 0.9252 }; for (int i = 0; i < nStrikes; ++i) { strikes[i] = forward * (0.85 + i * 0.05); } double muLow = strikes[0] * BlackFormulaRepository.dualDelta(forward, strikes[0], expiry, impliedVols[0], false) / BlackFormulaRepository.price(forward, strikes[0], expiry, impliedVols[0], false); double muHigh = -strikes[nStrikes - 1] * BlackFormulaRepository.dualDelta(forward, strikes[nStrikes - 1], expiry, impliedVols[nStrikes - 1], true) / BlackFormulaRepository.price(forward, strikes[nStrikes - 1], expiry, impliedVols[nStrikes - 1], true); SmileExtrapolationFunctionSABRProvider extrapBDK = new BenaimDodgsonKainthExtrapolationFunctionProvider(muLow, muHigh); SmileInterpolatorSABRWithExtrapolation interpBDK = new SmileInterpolatorSABRWithExtrapolation( new SABRBerestyckiVolatilityFunction(), extrapBDK); InterpolatedSmileFunction funcBDK = new InterpolatedSmileFunction(interpBDK, forward, strikes, expiry, impliedVols); List<SABRFormulaData> modelParams = (new SmileInterpolatorSABR()).getFittedModelParameters(forward, strikes, expiry, impliedVols); SABRExtrapolationLeftFunction sabrLeftExtrapolation = new SABRExtrapolationLeftFunction(forward, modelParams.get(0), strikes[0], expiry, muLow, new SABRHaganVolatilityFunction()); SABRExtrapolationRightFunction sabrRightExtrapolation = new SABRExtrapolationRightFunction(forward, modelParams.get(nStrikes - 3), strikes[nStrikes - 1], expiry, muHigh, new SABRHaganVolatilityFunction()); /* * left interpolation */ { // Checking underlying extrapolation double boundaryValue = sabrLeftExtrapolation.price(new EuropeanVanillaOption(strikes[0], expiry, false)); double CutoffUp = strikes[0] + eps; double CutoffDw = strikes[0] - eps; double optionPriceExt = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffDw, expiry, false)); double optionPriceInt = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffUp, expiry, false)); assertEquals(boundaryValue, optionPriceExt, eps); assertEquals(boundaryValue, optionPriceInt, eps); double optionPriceExtDw = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffDw - eps, expiry, false)); double firstExt = (1.5 * boundaryValue + 0.5 * optionPriceExtDw - 2.0 * optionPriceExt) / eps; double optionPriceIntUp = sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffUp + eps, expiry, false)); double firstInt = (2.0 * optionPriceInt - 0.5 * optionPriceIntUp - 1.5 * boundaryValue) / eps; assertEquals(firstInt, firstExt, eps); double secondExt = (boundaryValue + optionPriceExtDw - 2.0 * optionPriceExt) / eps / eps; double secondInt = (optionPriceIntUp + boundaryValue - 2.0 * optionPriceInt) / eps / eps; assertEquals(secondInt, secondExt, Math.abs(secondInt) * 1.0e-3); // Checking volatility function double volInt = funcBDK.getVolatility(CutoffUp); double volExt = funcBDK.getVolatility(CutoffDw); double volBoundary = funcBDK.getVolatility(strikes[0]); assertEquals(volBoundary, volInt, eps); assertEquals(volBoundary, volExt, eps); double volExtDw = funcBDK.getVolatility(CutoffDw - eps); double volFirstExt = (1.5 * volBoundary + 0.5 * volExtDw - 2.0 * volExt) / eps; double volIntUp = funcBDK.getVolatility(CutoffUp + eps); double volFirstInt = (2.0 * volInt - 0.5 * volIntUp - 1.5 * volBoundary) / eps; assertEquals(volFirstInt, volFirstExt, eps); double volSecondExt = (volBoundary + volExtDw - 2.0 * volExt) / eps / eps; double volSecondInt = (volIntUp + volBoundary - 2.0 * volInt) / eps / eps; assertEquals(volSecondInt, volSecondExt, Math.abs(volSecondInt) * 1.0e-3); } /* * right interpolation */ { // Checking underlying extrapolation double boundaryValue = sabrRightExtrapolation .price(new EuropeanVanillaOption(strikes[nStrikes - 1], expiry, true)); double CutoffUp = strikes[nStrikes - 1] + eps; double CutoffDw = strikes[nStrikes - 1] - eps; double optionPriceExt = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffUp, expiry, true)); double optionPriceInt = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffDw, expiry, true)); assertEquals(boundaryValue, optionPriceExt, eps); assertEquals(boundaryValue, optionPriceInt, eps); double optionPriceExtUp = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffUp + eps, expiry, true)); double firstExt = (2.0 * optionPriceExt - 0.5 * optionPriceExtUp - 1.5 * boundaryValue) / eps; double optionPriceIntDw = sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffDw - eps, expiry, true)); double firstInt = (-2.0 * optionPriceInt + 1.5 * boundaryValue + 0.5 * optionPriceIntDw) / eps; assertEquals(firstInt, firstExt, eps); double secondExt = (optionPriceExtUp + boundaryValue - 2.0 * optionPriceExt) / eps / eps; double secondInt = (boundaryValue + optionPriceIntDw - 2.0 * optionPriceInt) / eps / eps; assertEquals(secondInt, secondExt, Math.abs(secondInt) * 1.0e-3); // Checking volatility function double volInt = funcBDK.getVolatility(CutoffDw); double volExt = funcBDK.getVolatility(CutoffUp); double volBoundary = funcBDK.getVolatility(strikes[nStrikes - 1]); assertEquals(volBoundary, volInt, eps); assertEquals(volBoundary, volExt, eps); double volExtUp = funcBDK.getVolatility(CutoffUp + eps); double volFirstExt = (2.0 * volExt - 0.5 * volExtUp - 1.5 * volBoundary) / eps; double volIntDw = funcBDK.getVolatility(CutoffDw - eps); double volFirstInt = (-2.0 * volInt + 1.5 * volBoundary + 0.5 * volIntDw) / eps; assertEquals(volFirstInt, volFirstExt, eps); double volSecondExt = (volBoundary + volExtUp - 2.0 * volExt) / eps / eps; double volSecondInt = (volIntDw + volBoundary - 2.0 * volInt) / eps / eps; assertEquals(volSecondInt, volSecondExt, Math.abs(volSecondInt) * 1.0e-3); } } /** * Check trivial extrapolation is recovered for Benaim-Dodgson-Kainth extrapolation */ @Test public void functionRecoveryBDKExtrapolationTest() { double forward = 1.0; double expiry = 3.0; int nSamples = 4; double[] strikes = new double[nSamples]; double[] vols = new double[nSamples]; final double mu = 1.0; final double a = -1.0; final double b = 0.0; final double c = 0.0; // Expected left extrapolation Function1D<Double, Double> left = new Function1D<Double, Double>() { @Override public Double evaluate(Double strike) { return Math.pow(strike, mu) * Math.exp(a + b * strike + c * strike * strike); } }; // Expected right extrapolation Function1D<Double, Double> right = new Function1D<Double, Double>() { @Override public Double evaluate(Double strike) { return Math.pow(strike, -mu) * Math.exp(a + b / strike + c / strike / strike); } }; for (int i = 0; i < nSamples; ++i) { double strike = forward * (0.75 + 0.05 * i); double price = left.evaluate(strike); double vol = BlackFormulaRepository.impliedVolatility(price, forward, strike, expiry, false); strikes[i] = strike; vols[i] = vol; } SmileExtrapolationFunctionSABRProvider extrapBDK = new BenaimDodgsonKainthExtrapolationFunctionProvider(mu, mu); SmileInterpolatorSABRWithExtrapolation interpBDK = new SmileInterpolatorSABRWithExtrapolation( new SABRBerestyckiVolatilityFunction(), extrapBDK); InterpolatedSmileFunction funcBDK = new InterpolatedSmileFunction(interpBDK, forward, strikes, expiry, vols); double[] keys = new double[] {forward * 0.1, forward * 0.5, forward * 0.66 }; for (int i = 0; i < keys.length; ++i) { double vol = funcBDK.getVolatility(keys[i]); double price = BlackFormulaRepository.price(forward, keys[i], expiry, vol, false); assertEquals(left.evaluate(keys[i]), price, 1.e-2); } for (int i = 0; i < nSamples; ++i) { double strike = forward * (1.1 + 0.05 * i); double price = right.evaluate(strike); double vol = BlackFormulaRepository.impliedVolatility(price, forward, strike, expiry, true); strikes[i] = strike; vols[i] = vol; } extrapBDK = new BenaimDodgsonKainthExtrapolationFunctionProvider(mu, mu); interpBDK = new SmileInterpolatorSABRWithExtrapolation(extrapBDK); funcBDK = new InterpolatedSmileFunction(interpBDK, forward, strikes, expiry, vols); keys = new double[] {forward * 1.31, forward * 1.5, forward * 2.61, forward * 15.0 }; for (int i = 0; i < keys.length; ++i) { double vol = funcBDK.getVolatility(keys[i]); double price = BlackFormulaRepository.price(forward, keys[i], expiry, vol, true); assertEquals(right.evaluate(keys[i]), price, 1.e-2); } } }