/** * 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 org.testng.annotations.Test; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption; 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.SABRJohnsonVolatilityFunction; import com.opengamma.analytics.financial.model.volatility.smile.function.SABRPaulotVolatilityFunction; import com.opengamma.analytics.financial.model.volatility.smile.function.VolatilityFunctionProvider; import com.opengamma.util.test.TestGroup; /** * */ @Test(groups = TestGroup.UNIT) @SuppressWarnings("unchecked") public class SABRExtrapolationLeftFunctionTest { private static final double EPS = 1.0e-6; private static final double FORWARD = 0.13; private static final double CUTOFF = 0.08; private static final double EXPIRY = 1.5; private static final double MU = 1.1; private static final double NU = 0.8; private static final double RHO = -0.65; private static final double BETA = 0.76; private static final double ALPHA = 1.6; private static final SABRFormulaData DATA = new SABRFormulaData(ALPHA, BETA, RHO, NU); private static final SABRHaganVolatilityFunction FUNC_HAGAN = new SABRHaganVolatilityFunction(); private static final SABRJohnsonVolatilityFunction FUNC_JOHNSON = new SABRJohnsonVolatilityFunction(); private static final SABRHaganAlternativeVolatilityFunction FUNC_HAGAN_ALT = new SABRHaganAlternativeVolatilityFunction(); private static final SABRBerestyckiVolatilityFunction FUNC_BERESTYCKI = new SABRBerestyckiVolatilityFunction(); private static final SABRPaulotVolatilityFunction FUNC_PAULOT = new SABRPaulotVolatilityFunction(); private static final VolatilityFunctionProvider<SABRFormulaData>[] FUNCTIONS = new VolatilityFunctionProvider[] { FUNC_HAGAN, FUNC_JOHNSON, FUNC_HAGAN_ALT, FUNC_BERESTYCKI, FUNC_PAULOT }; /** * C2 continuity and accessors are tested */ @Test public void smoothnessAndAccessorTest() { for (VolatilityFunctionProvider<SABRFormulaData> func : FUNCTIONS) { SABRExtrapolationLeftFunction left = new SABRExtrapolationLeftFunction(FORWARD, DATA, CUTOFF, EXPIRY, MU, func); for (boolean isCall : new boolean[] {true, false }) { EuropeanVanillaOption optionBase = new EuropeanVanillaOption(CUTOFF, EXPIRY, isCall); EuropeanVanillaOption optionUp = new EuropeanVanillaOption(CUTOFF + EPS, EXPIRY, isCall); EuropeanVanillaOption optionDw = new EuropeanVanillaOption(CUTOFF - EPS, EXPIRY, isCall); double priceBase = left.price(optionBase); double priceUp = left.price(optionUp); double priceDw = left.price(optionDw); assertEquals(priceBase, priceUp, EPS); assertEquals(priceBase, priceDw, EPS); EuropeanVanillaOption optionUpUp = new EuropeanVanillaOption(CUTOFF + 2.0 * EPS, EXPIRY, isCall); EuropeanVanillaOption optionDwDw = new EuropeanVanillaOption(CUTOFF - 2.0 * EPS, EXPIRY, isCall); double priceUpUp = left.price(optionUpUp); double priceDwDw = left.price(optionDwDw); double firstUp = (-0.5 * priceUpUp + 2.0 * priceUp - 1.5 * priceBase) / EPS; double firstDw = (-2.0 * priceDw + 0.5 * priceDwDw + 1.5 * priceBase) / EPS; assertEquals(firstDw, firstUp, EPS); // The second derivative values are poorly connected due to finite difference approximation double firstUpUp = 0.5 * (priceUpUp - priceBase) / EPS; double firstDwDw = 0.5 * (priceBase - priceDwDw) / EPS; double secondUp = (firstUpUp - firstUp) / EPS; double secondDw = (firstDw - firstDwDw) / EPS; double secondRef = 0.5 * (firstUpUp - firstDwDw) / EPS; assertEquals(secondRef, secondUp, secondRef * 0.15); assertEquals(secondRef, secondDw, secondRef * 0.15); } assertTrue(left.getVolatilityFunction().equals(func)); assertEquals(EXPIRY, left.getTimeToExpiry()); assertEquals(MU, left.getMu()); assertEquals(CUTOFF, left.getCutOffStrike()); assertEquals(DATA, left.getSabrData()); } } /** * Due to large numerical error with this setup, only C0 is checked. * Also if Hagan formula (or its kind) is used, negative vols are returned and they are modified as 0.0 internally. */ @Test public void smallCutoffTest() { double smallCutoff = 0.5e-6; for (VolatilityFunctionProvider<SABRFormulaData> func : FUNCTIONS) { SABRExtrapolationLeftFunction left = new SABRExtrapolationLeftFunction(FORWARD * 0.01, DATA, smallCutoff, EXPIRY, MU, func); EuropeanVanillaOption optionBase = new EuropeanVanillaOption(smallCutoff, EXPIRY, false); EuropeanVanillaOption optionUp = new EuropeanVanillaOption(smallCutoff + EPS * 0.1, EXPIRY, false); EuropeanVanillaOption optionDw = new EuropeanVanillaOption(smallCutoff - EPS * 0.1, EXPIRY, false); double priceBase = left.price(optionBase); double priceUp = left.price(optionUp); double priceDw = left.price(optionDw); assertEquals(priceBase, priceUp, EPS); assertEquals(priceBase, priceDw, EPS); if (priceBase == 0.0) { assertEquals(left.getParameter()[0], -100.0, 1.e-12); assertEquals(left.getParameter()[1], 0.0, 1.e-12); assertEquals(left.getParameter()[2], 0.0, 1.e-12); } } } /** * Due to large numerical error with this setup, only C0 is checked. * Also if Hagan formula (or its kind) is used, negative vols are returned and they are modified as 0.0 internally. */ @Test public void smallForwardTest() { double smallForward = 0.9e-6; double smallCutoff = 0.5e-6; for (VolatilityFunctionProvider<SABRFormulaData> func : FUNCTIONS) { SABRExtrapolationLeftFunction left = new SABRExtrapolationLeftFunction(smallForward, DATA, smallCutoff, EXPIRY, MU, func); EuropeanVanillaOption optionBase = new EuropeanVanillaOption(smallCutoff, EXPIRY, false); EuropeanVanillaOption optionUp = new EuropeanVanillaOption(smallCutoff + EPS * 0.1, EXPIRY, false); EuropeanVanillaOption optionDw = new EuropeanVanillaOption(smallCutoff - EPS * 0.1, EXPIRY, false); double priceBase = left.price(optionBase); double priceUp = left.price(optionUp); double priceDw = left.price(optionDw); assertEquals(priceBase, priceUp, EPS * 10.0); assertEquals(priceBase, priceDw, EPS * 10.0); } } /** * Extrapolator is not calibrated in this case, then the gap is produced at the cutoff. */ @Test public void smallExpiryTest() { double smallExpiry = 0.5e-6; for (VolatilityFunctionProvider<SABRFormulaData> func : FUNCTIONS) { SABRExtrapolationLeftFunction left = new SABRExtrapolationLeftFunction(FORWARD * 0.01, DATA, CUTOFF, smallExpiry, MU, func); EuropeanVanillaOption optionBase = new EuropeanVanillaOption(CUTOFF, smallExpiry, false); EuropeanVanillaOption optionUp = new EuropeanVanillaOption(CUTOFF + EPS * 0.1, smallExpiry, false); EuropeanVanillaOption optionDw = new EuropeanVanillaOption(CUTOFF - EPS * 0.1, smallExpiry, false); double priceBase = left.price(optionBase); double priceUp = left.price(optionUp); double priceDw = left.price(optionDw); assertEquals(priceBase, priceUp, EPS); assertEquals(0.0, priceDw, EPS); if (priceBase == 0.0) { assertEquals(left.getParameter()[0], -1.0E4, 1.e-12); assertEquals(left.getParameter()[1], 0.0, 1.e-12); assertEquals(left.getParameter()[2], 0.0, 1.e-12); } } } /** * */ @Test public void hashCodeAndEqualsTest() { SABRExtrapolationLeftFunction func1 = new SABRExtrapolationLeftFunction(FORWARD, DATA, CUTOFF, EXPIRY, MU, FUNCTIONS[0]); SABRExtrapolationLeftFunction func2 = new SABRExtrapolationLeftFunction(FORWARD, DATA, CUTOFF, EXPIRY, MU * 0.9, FUNCTIONS[0]); SABRExtrapolationLeftFunction func3 = new SABRExtrapolationLeftFunction(FORWARD, DATA, CUTOFF, EXPIRY * 0.9, MU, FUNCTIONS[0]); SABRExtrapolationLeftFunction func4 = new SABRExtrapolationLeftFunction(FORWARD, DATA, CUTOFF * 0.9, EXPIRY, MU, FUNCTIONS[0]); SABRExtrapolationLeftFunction func5 = new SABRExtrapolationLeftFunction(FORWARD * 0.9, DATA, CUTOFF, EXPIRY, MU, FUNCTIONS[0]); SABRExtrapolationLeftFunction func6 = new SABRExtrapolationLeftFunction(FORWARD, DATA.withAlpha(ALPHA * 0.97), CUTOFF, EXPIRY, MU, FUNCTIONS[0]); SABRExtrapolationLeftFunction func7 = func1; SABRExtrapolationLeftFunction func8 = new SABRExtrapolationLeftFunction(FORWARD, DATA, CUTOFF, EXPIRY, MU, FUNCTIONS[0]); SABRExtrapolationLeftFunction func9 = new SABRExtrapolationLeftFunction(FORWARD, DATA, CUTOFF, EXPIRY, MU, FUNCTIONS[1]); assertTrue(func1.equals(func1)); assertFalse(func1.equals(func2)); assertFalse(func2.equals(func1)); assertFalse(func1.hashCode() == func2.hashCode()); assertFalse(func1.hashCode() == func3.hashCode()); assertFalse(func3.equals(func1)); assertFalse(func1.equals(func3)); assertFalse(func1.hashCode() == func4.hashCode()); assertFalse(func4.equals(func1)); assertFalse(func1.equals(func4)); assertFalse(func1.hashCode() == func5.hashCode()); assertFalse(func5.equals(func1)); assertFalse(func1.equals(func5)); assertFalse(func1.hashCode() == func6.hashCode()); assertFalse(func6.equals(func1)); assertFalse(func1.equals(func6)); assertTrue(func7.equals(func1)); assertTrue(func1.equals(func7)); assertTrue(func1.hashCode() == func7.hashCode()); assertTrue(func8.equals(func1)); assertTrue(func1.equals(func8)); assertTrue(func8.hashCode() == func8.hashCode()); assertFalse(func1.hashCode() == func9.hashCode()); assertFalse(func9.equals(func1)); assertFalse(func1.equals(func9)); assertFalse(func1.equals(null)); assertFalse(func1.equals(new double[] {1.2 })); } }