/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.impl.option; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import org.testng.annotations.Test; import com.opengamma.strata.basics.value.ValueDerivatives; import com.opengamma.strata.pricer.impl.volatility.smile.SabrFormulaData; import com.opengamma.strata.pricer.impl.volatility.smile.SabrHaganVolatilityFunctionProvider; import com.opengamma.strata.pricer.impl.volatility.smile.VolatilityFunctionProvider; import com.opengamma.strata.product.common.PutCall; /** * Test {@link SabrExtrapolationRightFunction}. */ @Test public class SabrExtrapolationRightFunctionTest { private static final double NU = 0.50; private static final double RHO = -0.25; private static final double BETA = 0.50; private static final double ALPHA = 0.05; private static final double FORWARD = 0.05; private static final SabrFormulaData SABR_DATA = SabrFormulaData.of(ALPHA, BETA, RHO, NU); private static final double CUT_OFF_STRIKE = 0.10; // Set low for the test private static final double MU = 4.0; private static final double TIME_TO_EXPIRY = 2.0; private static final SabrExtrapolationRightFunction SABR_EXTRAPOLATION = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, SABR_DATA, CUT_OFF_STRIKE, MU); private static final SabrHaganVolatilityFunctionProvider SABR_FUNCTION = SabrHaganVolatilityFunctionProvider.DEFAULT; private static final double TOLERANCE_PRICE = 1.0E-10; /** * Tests getter. */ public void getter() { SabrExtrapolationRightFunction func = SabrExtrapolationRightFunction.of( FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, SabrHaganVolatilityFunctionProvider.DEFAULT); assertEquals(func.getCutOffStrike(), CUT_OFF_STRIKE); assertEquals(func.getMu(), MU); assertEquals(func.getSabrData(), SABR_DATA); assertEquals(func.getTimeToExpiry(), TIME_TO_EXPIRY); } /** * Tests the price for options in SABR model with extrapolation. */ public void price() { double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; double volatilityIn = SABR_FUNCTION.volatility(FORWARD, strikeIn, TIME_TO_EXPIRY, SABR_DATA); double priceExpectedIn = BlackFormulaRepository.price(FORWARD, strikeIn, TIME_TO_EXPIRY, volatilityIn, true); double priceIn = SABR_EXTRAPOLATION.price(strikeIn, PutCall.CALL); assertEquals(priceExpectedIn, priceIn, TOLERANCE_PRICE); double volatilityAt = SABR_FUNCTION.volatility(FORWARD, strikeAt, TIME_TO_EXPIRY, SABR_DATA); double priceExpectedAt = BlackFormulaRepository.price(FORWARD, strikeAt, TIME_TO_EXPIRY, volatilityAt, true); double priceAt = SABR_EXTRAPOLATION.price(strikeAt, PutCall.CALL); assertEquals(priceExpectedAt, priceAt, TOLERANCE_PRICE); double priceOut = SABR_EXTRAPOLATION.price(strikeOut, PutCall.CALL); double priceExpectedOut = 5.427104E-5; // From previous run assertEquals(priceExpectedOut, priceOut, TOLERANCE_PRICE); } /** * Tests the price for options in SABR model with extrapolation. */ public void priceCloseToExpiry() { double[] timeToExpiry = {1.0 / 365, 0.0}; // One day and on expiry day. double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; for (int loopexp = 0; loopexp < timeToExpiry.length; loopexp++) { SabrExtrapolationRightFunction sabrExtra = SabrExtrapolationRightFunction.of(FORWARD, timeToExpiry[loopexp], SABR_DATA, CUT_OFF_STRIKE, MU); double volatilityIn = SABR_FUNCTION.volatility(FORWARD, strikeIn, timeToExpiry[loopexp], SABR_DATA); double priceExpectedIn = BlackFormulaRepository.price(FORWARD, strikeIn, timeToExpiry[loopexp], volatilityIn, true); double priceIn = sabrExtra.price(strikeIn, PutCall.CALL); assertEquals(priceExpectedIn, priceIn, TOLERANCE_PRICE); double volatilityAt = SABR_FUNCTION.volatility(FORWARD, strikeAt, timeToExpiry[loopexp], SABR_DATA); double priceExpectedAt = BlackFormulaRepository.price(FORWARD, strikeAt, timeToExpiry[loopexp], volatilityAt, true); double priceAt = sabrExtra.price(strikeAt, PutCall.CALL); assertEquals(priceExpectedAt, priceAt, TOLERANCE_PRICE); double priceOut = sabrExtra.price(strikeOut, PutCall.CALL); double priceExpectedOut = 0.0; // From previous run assertEquals(priceExpectedOut, priceOut, TOLERANCE_PRICE); } } /** * Tests the price derivative with respect to forward for options in SABR model with extrapolation. */ public void priceDerivativeForwardCall() { double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; EuropeanVanillaOption optionIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.CALL); double shiftF = 0.000001; SabrFormulaData sabrDataFP = SabrFormulaData.of(ALPHA, BETA, RHO, NU); SabrExtrapolationRightFunction sabrExtrapolationFP = SabrExtrapolationRightFunction.of(FORWARD + shiftF, TIME_TO_EXPIRY, sabrDataFP, CUT_OFF_STRIKE, MU); // Below cut-off strike double priceIn = SABR_EXTRAPOLATION.price(optionIn.getStrike(), optionIn.getPutCall()); double priceInFP = sabrExtrapolationFP.price(optionIn.getStrike(), optionIn.getPutCall()); double priceInDF = SABR_EXTRAPOLATION.priceDerivativeForward(optionIn.getStrike(), optionIn.getPutCall()); double priceInDFExpected = (priceInFP - priceIn) / shiftF; assertEquals(priceInDFExpected, priceInDF, 1E-5); // At cut-off strike double priceAt = SABR_EXTRAPOLATION.price(optionAt.getStrike(), optionAt.getPutCall()); double priceAtFP = sabrExtrapolationFP.price(optionAt.getStrike(), optionAt.getPutCall()); double priceAtDF = SABR_EXTRAPOLATION.priceDerivativeForward(optionAt.getStrike(), optionAt.getPutCall()); double priceAtDFExpected = (priceAtFP - priceAt) / shiftF; assertEquals(priceAtDFExpected, priceAtDF, 1E-6); // Above cut-off strike double[] abc = SABR_EXTRAPOLATION.getParameter(); double[] abcDF = SABR_EXTRAPOLATION.getParameterDerivativeForward(); double[] abcFP = sabrExtrapolationFP.getParameter(); double[] abcDFExpected = new double[3]; for (int loopparam = 0; loopparam < 3; loopparam++) { abcDFExpected[loopparam] = (abcFP[loopparam] - abc[loopparam]) / shiftF; assertEquals(1.0, abcDFExpected[loopparam] / abcDF[loopparam], 5E-2); } double priceOut = SABR_EXTRAPOLATION.price(optionOut.getStrike(), optionOut.getPutCall()); double priceOutFP = sabrExtrapolationFP.price(optionOut.getStrike(), optionOut.getPutCall()); double priceOutDF = SABR_EXTRAPOLATION.priceDerivativeForward(optionOut.getStrike(), optionOut.getPutCall()); double priceOutDFExpected = (priceOutFP - priceOut) / shiftF; assertEquals(priceOutDFExpected, priceOutDF, 1E-5); } /** * Tests the price derivative with respect to forward for options in SABR model with extrapolation. */ public void priceDerivativeForwardPut() { SabrExtrapolationRightFunction func = SabrExtrapolationRightFunction.of( FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, SabrHaganVolatilityFunctionProvider.DEFAULT); double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; EuropeanVanillaOption optionIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.PUT); double shiftF = 0.000001; SabrFormulaData sabrDataFP = SabrFormulaData.of(ALPHA, BETA, RHO, NU); SabrExtrapolationRightFunction sabrExtrapolationFP = SabrExtrapolationRightFunction.of(FORWARD + shiftF, TIME_TO_EXPIRY, sabrDataFP, CUT_OFF_STRIKE, MU); // Below cut-off strike double priceIn = func.price(optionIn.getStrike(), optionIn.getPutCall()); double priceInFP = sabrExtrapolationFP.price(optionIn.getStrike(), optionIn.getPutCall()); double priceInDF = func.priceDerivativeForward(optionIn.getStrike(), optionIn.getPutCall()); double priceInDFExpected = (priceInFP - priceIn) / shiftF; assertEquals(priceInDFExpected, priceInDF, 1E-5); // At cut-off strike double priceAt = func.price(optionAt.getStrike(), optionAt.getPutCall()); double priceAtFP = sabrExtrapolationFP.price(optionAt.getStrike(), optionAt.getPutCall()); double priceAtDF = func.priceDerivativeForward(optionAt.getStrike(), optionAt.getPutCall()); double priceAtDFExpected = (priceAtFP - priceAt) / shiftF; assertEquals(priceAtDFExpected, priceAtDF, 1E-6); // Above cut-off strike double priceOut = func.price(optionOut.getStrike(), optionOut.getPutCall()); double priceOutFP = sabrExtrapolationFP.price(optionOut.getStrike(), optionOut.getPutCall()); double priceOutDF = func.priceDerivativeForward(optionOut.getStrike(), optionOut.getPutCall()); double priceOutDFExpected = (priceOutFP - priceOut) / shiftF; assertEquals(priceOutDFExpected, priceOutDF, 1E-5); double[] abc = func.getParameter(); double[] abcDF = func.getParameterDerivativeForward(); double[] abcFP = sabrExtrapolationFP.getParameter(); double[] abcDFExpected = new double[3]; for (int loopparam = 0; loopparam < 3; loopparam++) { abcDFExpected[loopparam] = (abcFP[loopparam] - abc[loopparam]) / shiftF; assertEquals(1.0, abcDFExpected[loopparam] / abcDF[loopparam], 5E-2); } } /** * Tests the price derivative with respect to forward for options in SABR model with extrapolation. */ public void priceDerivativeStrikeCall() { double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; double shiftK = 0.000001; EuropeanVanillaOption optionIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionInKP = EuropeanVanillaOption.of(strikeIn + shiftK, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionAtKP = EuropeanVanillaOption.of(strikeAt + shiftK, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionOutKP = EuropeanVanillaOption.of(strikeOut + shiftK, TIME_TO_EXPIRY, PutCall.CALL); // Below cut-off strike double priceIn = SABR_EXTRAPOLATION.price(optionIn.getStrike(), optionIn.getPutCall()); double priceInKP = SABR_EXTRAPOLATION.price(optionInKP.getStrike(), optionInKP.getPutCall()); double priceInDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionIn.getStrike(), optionIn.getPutCall()); double priceInDFExpected = (priceInKP - priceIn) / shiftK; assertEquals(priceInDFExpected, priceInDK, 1E-5); // At cut-off strike double priceAt = SABR_EXTRAPOLATION.price(optionAt.getStrike(), optionAt.getPutCall()); double priceAtKP = SABR_EXTRAPOLATION.price(optionAtKP.getStrike(), optionAtKP.getPutCall()); double priceAtDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionAt.getStrike(), optionAt.getPutCall()); double priceAtDFExpected = (priceAtKP - priceAt) / shiftK; assertEquals(priceAtDFExpected, priceAtDK, 1E-5); // At cut-off strike double priceOut = SABR_EXTRAPOLATION.price(optionOut.getStrike(), optionOut.getPutCall()); double priceOutKP = SABR_EXTRAPOLATION.price(optionOutKP.getStrike(), optionOutKP.getPutCall()); double priceOutDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionOut.getStrike(), optionOut.getPutCall()); double priceOutDFExpected = (priceOutKP - priceOut) / shiftK; assertEquals(priceOutDFExpected, priceOutDK, 1E-5); } /** * Tests the price derivative with respect to forward for options in SABR model with extrapolation. */ public void priceDerivativeStrikePut() { double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; double shiftK = 0.000001; EuropeanVanillaOption optionIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionInKP = EuropeanVanillaOption.of(strikeIn + shiftK, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionAtKP = EuropeanVanillaOption.of(strikeAt + shiftK, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionOutKP = EuropeanVanillaOption.of(strikeOut + shiftK, TIME_TO_EXPIRY, PutCall.PUT); // Below cut-off strike double priceIn = SABR_EXTRAPOLATION.price(optionIn.getStrike(), optionIn.getPutCall()); double priceInKP = SABR_EXTRAPOLATION.price(optionInKP.getStrike(), optionInKP.getPutCall()); double priceInDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionIn.getStrike(), optionIn.getPutCall()); double priceInDFExpected = (priceInKP - priceIn) / shiftK; assertEquals(priceInDFExpected, priceInDK, 1E-5); // At cut-off strike double priceAt = SABR_EXTRAPOLATION.price(optionAt.getStrike(), optionAt.getPutCall()); double priceAtKP = SABR_EXTRAPOLATION.price(optionAtKP.getStrike(), optionAtKP.getPutCall()); double priceAtDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionAt.getStrike(), optionAt.getPutCall()); double priceAtDFExpected = (priceAtKP - priceAt) / shiftK; assertEquals(priceAtDFExpected, priceAtDK, 1E-5); // At cut-off strike double priceOut = SABR_EXTRAPOLATION.price(optionOut.getStrike(), optionOut.getPutCall()); double priceOutKP = SABR_EXTRAPOLATION.price(optionOutKP.getStrike(), optionOutKP.getPutCall()); double priceOutDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionOut.getStrike(), optionOut.getPutCall()); double priceOutDFExpected = (priceOutKP - priceOut) / shiftK; assertEquals(priceOutDFExpected, priceOutDK, 1E-5); } /** * Tests the price derivative with respect to forward for options in SABR model with extrapolation. */ public void priceDerivativeSabrCall() { double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; EuropeanVanillaOption optionIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.CALL); double shift = 0.000001; SabrFormulaData sabrDataAP = SabrFormulaData.of(ALPHA + shift, BETA, RHO, NU); SabrFormulaData sabrDataBP = SabrFormulaData.of(ALPHA, BETA + shift, RHO, NU); SabrFormulaData sabrDataRP = SabrFormulaData.of(ALPHA, BETA, RHO + shift, NU); SabrFormulaData sabrDataNP = SabrFormulaData.of(ALPHA, BETA, RHO, NU + shift); SabrExtrapolationRightFunction sabrExtrapolationAP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataAP, CUT_OFF_STRIKE, MU); SabrExtrapolationRightFunction sabrExtrapolationBP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataBP, CUT_OFF_STRIKE, MU); SabrExtrapolationRightFunction sabrExtrapolationRP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataRP, CUT_OFF_STRIKE, MU); SabrExtrapolationRightFunction sabrExtrapolationNP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataNP, CUT_OFF_STRIKE, MU); // Below cut-off strike double priceInExpected = SABR_EXTRAPOLATION.price(optionIn.getStrike(), optionIn.getPutCall()); double[] priceInPP = new double[4]; priceInPP[0] = sabrExtrapolationAP.price(optionIn.getStrike(), optionIn.getPutCall()); priceInPP[1] = sabrExtrapolationBP.price(optionIn.getStrike(), optionIn.getPutCall()); priceInPP[2] = sabrExtrapolationRP.price(optionIn.getStrike(), optionIn.getPutCall()); priceInPP[3] = sabrExtrapolationNP.price(optionIn.getStrike(), optionIn.getPutCall()); ValueDerivatives resIn = SABR_EXTRAPOLATION.priceAdjointSabr(optionIn.getStrike(), optionIn.getPutCall()); double priceIn = resIn.getValue(); double[] priceInDsabr = resIn.getDerivatives().toArray(); assertEquals(priceInExpected, priceIn, TOLERANCE_PRICE); double[] priceInDsabrExpected = new double[4]; for (int loopparam = 0; loopparam < 3; loopparam++) { priceInDsabrExpected[loopparam] = (priceInPP[loopparam] - priceIn) / shift; assertEquals(priceInDsabrExpected[loopparam], priceInDsabr[loopparam], 1E-5); } // At cut-off strike double priceAtExpected = SABR_EXTRAPOLATION.price(optionAt.getStrike(), optionAt.getPutCall()); double[] priceAtPP = new double[4]; priceAtPP[0] = sabrExtrapolationAP.price(optionAt.getStrike(), optionAt.getPutCall()); priceAtPP[1] = sabrExtrapolationBP.price(optionAt.getStrike(), optionAt.getPutCall()); priceAtPP[2] = sabrExtrapolationRP.price(optionAt.getStrike(), optionAt.getPutCall()); priceAtPP[3] = sabrExtrapolationNP.price(optionAt.getStrike(), optionAt.getPutCall()); ValueDerivatives resAt = SABR_EXTRAPOLATION.priceAdjointSabr(optionAt.getStrike(), optionAt.getPutCall()); double priceAt = resAt.getValue(); double[] priceAtDsabr = resAt.getDerivatives().toArray(); assertEquals(priceAtExpected, priceAt, TOLERANCE_PRICE); double[] priceAtDsabrExpected = new double[4]; for (int loopparam = 0; loopparam < 3; loopparam++) { priceAtDsabrExpected[loopparam] = (priceAtPP[loopparam] - priceAt) / shift; assertEquals(priceAtDsabrExpected[loopparam], priceAtDsabr[loopparam], 1E-5); } // Above cut-off strike double[] abc = SABR_EXTRAPOLATION.getParameter(); double[][] abcDP = SABR_EXTRAPOLATION.getParameterDerivativeSabr(); double[][] abcPP = new double[4][3]; abcPP[0] = sabrExtrapolationAP.getParameter(); abcPP[1] = sabrExtrapolationBP.getParameter(); abcPP[2] = sabrExtrapolationRP.getParameter(); abcPP[3] = sabrExtrapolationNP.getParameter(); double[][] abcDPExpected = new double[4][3]; for (int loopparam = 0; loopparam < 4; loopparam++) { for (int loopabc = 0; loopabc < 3; loopabc++) { abcDPExpected[loopparam][loopabc] = (abcPP[loopparam][loopabc] - abc[loopabc]) / shift; assertEquals(1.0, abcDPExpected[loopparam][loopabc] / abcDP[loopparam][loopabc], 5.0E-2); } } double priceOutExpected = SABR_EXTRAPOLATION.price(optionOut.getStrike(), optionOut.getPutCall()); double[] priceOutPP = new double[4]; priceOutPP[0] = sabrExtrapolationAP.price(optionOut.getStrike(), optionOut.getPutCall()); priceOutPP[1] = sabrExtrapolationBP.price(optionOut.getStrike(), optionOut.getPutCall()); priceOutPP[2] = sabrExtrapolationRP.price(optionOut.getStrike(), optionOut.getPutCall()); priceOutPP[3] = sabrExtrapolationNP.price(optionOut.getStrike(), optionOut.getPutCall()); ValueDerivatives resOut = SABR_EXTRAPOLATION.priceAdjointSabr(optionOut.getStrike(), optionOut.getPutCall()); double priceOut = resOut.getValue(); double[] priceOutDsabr = resOut.getDerivatives().toArray(); assertEquals(priceOutExpected, priceOut, TOLERANCE_PRICE); double[] priceOutDsabrExpected = new double[4]; for (int loopparam = 0; loopparam < 4; loopparam++) { priceOutDsabrExpected[loopparam] = (priceOutPP[loopparam] - priceOut) / shift; assertEquals(1.0, priceOutDsabrExpected[loopparam] / priceOutDsabr[loopparam], 4.0E-4); } } /** * Tests the price derivative with respect to forward for options in SABR model with extrapolation. */ public void priceDerivativeSabrPut() { SabrExtrapolationRightFunction func = SabrExtrapolationRightFunction.of( FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, SabrHaganVolatilityFunctionProvider.DEFAULT); double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; EuropeanVanillaOption optionIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption optionOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.PUT); double shift = 0.000001; SabrFormulaData sabrDataAP = SabrFormulaData.of(ALPHA + shift, BETA, RHO, NU); SabrFormulaData sabrDataBP = SabrFormulaData.of(ALPHA, BETA + shift, RHO, NU); SabrFormulaData sabrDataRP = SabrFormulaData.of(ALPHA, BETA, RHO + shift, NU); SabrFormulaData sabrDataNP = SabrFormulaData.of(ALPHA, BETA, RHO, NU + shift); SabrExtrapolationRightFunction sabrExtrapolationAP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataAP, CUT_OFF_STRIKE, MU); SabrExtrapolationRightFunction sabrExtrapolationBP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataBP, CUT_OFF_STRIKE, MU); SabrExtrapolationRightFunction sabrExtrapolationRP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataRP, CUT_OFF_STRIKE, MU); SabrExtrapolationRightFunction sabrExtrapolationNP = SabrExtrapolationRightFunction.of(FORWARD, TIME_TO_EXPIRY, sabrDataNP, CUT_OFF_STRIKE, MU); // Below cut-off strike double priceInExpected = func.price(optionIn.getStrike(), optionIn.getPutCall()); double[] priceInPP = new double[4]; priceInPP[0] = sabrExtrapolationAP.price(optionIn.getStrike(), optionIn.getPutCall()); priceInPP[1] = sabrExtrapolationBP.price(optionIn.getStrike(), optionIn.getPutCall()); priceInPP[2] = sabrExtrapolationRP.price(optionIn.getStrike(), optionIn.getPutCall()); priceInPP[3] = sabrExtrapolationNP.price(optionIn.getStrike(), optionIn.getPutCall()); ValueDerivatives resIn = func.priceAdjointSabr(optionIn.getStrike(), optionIn.getPutCall()); double priceIn = resIn.getValue(); double[] priceInDsabr = resIn.getDerivatives().toArray(); assertEquals(priceInExpected, priceIn, TOLERANCE_PRICE); double[] priceInDsabrExpected = new double[4]; for (int loopparam = 0; loopparam < 3; loopparam++) { priceInDsabrExpected[loopparam] = (priceInPP[loopparam] - priceIn) / shift; assertEquals(priceInDsabrExpected[loopparam], priceInDsabr[loopparam], 1E-5); } // At cut-off strike double priceAtExpected = func.price(optionAt.getStrike(), optionAt.getPutCall()); double[] priceAtPP = new double[4]; priceAtPP[0] = sabrExtrapolationAP.price(optionAt.getStrike(), optionAt.getPutCall()); priceAtPP[1] = sabrExtrapolationBP.price(optionAt.getStrike(), optionAt.getPutCall()); priceAtPP[2] = sabrExtrapolationRP.price(optionAt.getStrike(), optionAt.getPutCall()); priceAtPP[3] = sabrExtrapolationNP.price(optionAt.getStrike(), optionAt.getPutCall()); ValueDerivatives resAt = func.priceAdjointSabr(optionAt.getStrike(), optionAt.getPutCall()); double priceAt = resAt.getValue(); double[] priceAtDsabr = resAt.getDerivatives().toArray(); assertEquals(priceAtExpected, priceAt, TOLERANCE_PRICE); double[] priceAtDsabrExpected = new double[4]; for (int loopparam = 0; loopparam < 3; loopparam++) { priceAtDsabrExpected[loopparam] = (priceAtPP[loopparam] - priceAt) / shift; assertEquals(priceAtDsabrExpected[loopparam], priceAtDsabr[loopparam], 1E-5); } // Above cut-off strike double priceOutExpected = func.price(optionOut.getStrike(), optionOut.getPutCall()); double[] priceOutPP = new double[4]; priceOutPP[0] = sabrExtrapolationAP.price(optionOut.getStrike(), optionOut.getPutCall()); priceOutPP[1] = sabrExtrapolationBP.price(optionOut.getStrike(), optionOut.getPutCall()); priceOutPP[2] = sabrExtrapolationRP.price(optionOut.getStrike(), optionOut.getPutCall()); priceOutPP[3] = sabrExtrapolationNP.price(optionOut.getStrike(), optionOut.getPutCall()); ValueDerivatives resOut = func.priceAdjointSabr(optionOut.getStrike(), optionOut.getPutCall()); double priceOut = resOut.getValue(); double[] priceOutDsabr = resOut.getDerivatives().toArray(); assertEquals(priceOutExpected, priceOut, TOLERANCE_PRICE); double[] abc = func.getParameter(); double[][] abcDP = func.getParameterDerivativeSabr(); double[][] abcPP = new double[4][3]; abcPP[0] = sabrExtrapolationAP.getParameter(); abcPP[1] = sabrExtrapolationBP.getParameter(); abcPP[2] = sabrExtrapolationRP.getParameter(); abcPP[3] = sabrExtrapolationNP.getParameter(); double[][] abcDPExpected = new double[4][3]; for (int loopparam = 0; loopparam < 4; loopparam++) { for (int loopabc = 0; loopabc < 3; loopabc++) { abcDPExpected[loopparam][loopabc] = (abcPP[loopparam][loopabc] - abc[loopabc]) / shift; assertEquals(1.0, abcDPExpected[loopparam][loopabc] / abcDP[loopparam][loopabc], 5.0E-2); } } double[] priceOutDsabrExpected = new double[4]; for (int loopparam = 0; loopparam < 4; loopparam++) { priceOutDsabrExpected[loopparam] = (priceOutPP[loopparam] - priceOut) / shift; assertEquals(1.0, priceOutDsabrExpected[loopparam] / priceOutDsabr[loopparam], 4.0E-4); } } /** * Tests the price derivative with respect to forward for options in SABR model with extrapolation. Other data. */ public void priceDerivativeSABR2() { double alpha = 0.06; double beta = 0.5; double rho = 0.0; double nu = 0.3; double cutOff = 0.10; double mu = 2.5; double strike = 0.15; double t = 2.366105247; EuropeanVanillaOption option = EuropeanVanillaOption.of(strike, t, PutCall.CALL); SabrFormulaData sabrData = SabrFormulaData.of(alpha, beta, rho, nu); double forward = 0.0404500579038675; SabrExtrapolationRightFunction sabrExtrapolation = SabrExtrapolationRightFunction.of(forward, t, sabrData, cutOff, mu); double shift = 0.000001; SabrFormulaData sabrDataAP = SabrFormulaData.of(alpha + shift, beta, rho, nu); SabrFormulaData sabrDataBP = SabrFormulaData.of(alpha, beta + shift, rho, nu); SabrFormulaData sabrDataRP = SabrFormulaData.of(alpha, beta, rho + shift, nu); SabrFormulaData sabrDataNP = SabrFormulaData.of(alpha, beta, rho, nu + shift); SabrExtrapolationRightFunction sabrExtrapolationAP = SabrExtrapolationRightFunction.of(forward, t, sabrDataAP, cutOff, mu); SabrExtrapolationRightFunction sabrExtrapolationBP = SabrExtrapolationRightFunction.of(forward, t, sabrDataBP, cutOff, mu); SabrExtrapolationRightFunction sabrExtrapolationRP = SabrExtrapolationRightFunction.of(forward, t, sabrDataRP, cutOff, mu); SabrExtrapolationRightFunction sabrExtrapolationNP = SabrExtrapolationRightFunction.of(forward, t, sabrDataNP, cutOff, mu); // Above cut-off strike double[] abc = sabrExtrapolation.getParameter(); double[][] abcDP = sabrExtrapolation.getParameterDerivativeSabr(); double[][] abcPP = new double[4][3]; abcPP[0] = sabrExtrapolationAP.getParameter(); abcPP[1] = sabrExtrapolationBP.getParameter(); abcPP[2] = sabrExtrapolationRP.getParameter(); abcPP[3] = sabrExtrapolationNP.getParameter(); double[][] abcDPExpected = new double[4][3]; for (int loopparam = 0; loopparam < 4; loopparam++) { for (int loopabc = 0; loopabc < 3; loopabc++) { abcDPExpected[loopparam][loopabc] = (abcPP[loopparam][loopabc] - abc[loopabc]) / shift; assertEquals(1.0, abcDPExpected[loopparam][loopabc] / abcDP[loopparam][loopabc], 5.0E-2); } } double priceOutExpected = sabrExtrapolation.price(option.getStrike(), option.getPutCall()); double[] priceOutPP = new double[4]; priceOutPP[0] = sabrExtrapolationAP.price(option.getStrike(), option.getPutCall()); priceOutPP[1] = sabrExtrapolationBP.price(option.getStrike(), option.getPutCall()); priceOutPP[2] = sabrExtrapolationRP.price(option.getStrike(), option.getPutCall()); priceOutPP[3] = sabrExtrapolationNP.price(option.getStrike(), option.getPutCall()); ValueDerivatives resOut = sabrExtrapolation.priceAdjointSabr(option.getStrike(), option.getPutCall()); double priceOut = resOut.getValue(); double[] priceOutDsabr = resOut.getDerivatives().toArray(); assertEquals(priceOutExpected, priceOut, 1E-5); double[] priceOutDsabrExpected = new double[4]; for (int loopparam = 0; loopparam < 4; loopparam++) { priceOutDsabrExpected[loopparam] = (priceOutPP[loopparam] - priceOut) / shift; assertEquals(1.0, priceOutDsabrExpected[loopparam] / priceOutDsabr[loopparam], 4.0E-4); } } /** * Tests the price put/call parity for options in SABR model with extrapolation. */ public void pricePutCallParity() { double strikeIn = 0.08; double strikeAt = CUT_OFF_STRIKE; double strikeOut = 0.12; EuropeanVanillaOption callIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption putIn = EuropeanVanillaOption.of(strikeIn, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption callAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption putAt = EuropeanVanillaOption.of(strikeAt, TIME_TO_EXPIRY, PutCall.PUT); EuropeanVanillaOption callOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.CALL); EuropeanVanillaOption putOut = EuropeanVanillaOption.of(strikeOut, TIME_TO_EXPIRY, PutCall.PUT); double priceCallIn = SABR_EXTRAPOLATION.price(callIn.getStrike(), callIn.getPutCall()); double pricePutIn = SABR_EXTRAPOLATION.price(putIn.getStrike(), putIn.getPutCall()); assertEquals(FORWARD - strikeIn, priceCallIn - pricePutIn, TOLERANCE_PRICE); double priceCallAt = SABR_EXTRAPOLATION.price(callAt.getStrike(), callAt.getPutCall()); double pricePutAt = SABR_EXTRAPOLATION.price(putAt.getStrike(), putAt.getPutCall()); assertEquals(FORWARD - strikeAt, priceCallAt - pricePutAt, TOLERANCE_PRICE); double priceCallOut = SABR_EXTRAPOLATION.price(callOut.getStrike(), callOut.getPutCall()); double pricePutOut = SABR_EXTRAPOLATION.price(putOut.getStrike(), putOut.getPutCall()); assertEquals(FORWARD - strikeOut, priceCallOut - pricePutOut, TOLERANCE_PRICE); } /** * Tests that the smile and its derivatives are smooth enough in SABR model with extrapolation. */ public void smileSmooth() { int nbPts = 100; double rangeStrike = 0.02; double[] price = new double[nbPts + 1]; double[] strike = new double[nbPts + 1]; for (int looppts = 0; looppts <= nbPts; looppts++) { strike[looppts] = CUT_OFF_STRIKE - rangeStrike + looppts * 2.0 * rangeStrike / nbPts; EuropeanVanillaOption option = EuropeanVanillaOption.of(strike[looppts], TIME_TO_EXPIRY, PutCall.CALL); price[looppts] = SABR_EXTRAPOLATION.price(option.getStrike(), option.getPutCall()); } double[] priceD = new double[nbPts]; double[] priceD2 = new double[nbPts]; for (int looppts = 1; looppts < nbPts; looppts++) { priceD[looppts] = (price[looppts + 1] - price[looppts - 1]) / (strike[looppts + 1] - strike[looppts - 1]); priceD2[looppts] = (price[looppts + 1] + price[looppts - 1] - 2 * price[looppts]) / ((strike[looppts + 1] - strike[looppts]) * (strike[looppts + 1] - strike[looppts])); } for (int looppts = 2; looppts < nbPts; looppts++) { assertEquals(priceD[looppts - 1], priceD[looppts], 1.5E-3); assertEquals(priceD2[looppts - 1], priceD2[looppts], 1.5E-1); } } /** * Tests that the smile and its derivatives are smooth enough in SABR model with extrapolation * for different time to maturity (in particular close to maturity). */ public void smileSmoothMaturity() { int nbPts = 100; double[] timeToExpiry = new double[] {2.0, 1.0, 0.50, 0.25, 1.0d / 12.0d, 1.0d / 52.0d, 1.0d / 365d}; int nbTTM = timeToExpiry.length; double rangeStrike = 0.02; double[] strike = new double[nbPts + 1]; for (int looppts = 0; looppts <= nbPts; looppts++) { strike[looppts] = CUT_OFF_STRIKE - rangeStrike + looppts * 2.0 * rangeStrike / nbPts; } SabrExtrapolationRightFunction[] sabrExtrapolation = new SabrExtrapolationRightFunction[nbTTM]; for (int loopmat = 0; loopmat < nbTTM; loopmat++) { sabrExtrapolation[loopmat] = SabrExtrapolationRightFunction.of(FORWARD, timeToExpiry[loopmat], SABR_DATA, CUT_OFF_STRIKE, MU); } double[][] price = new double[nbTTM][nbPts + 1]; for (int loopmat = 0; loopmat < nbTTM; loopmat++) { for (int looppts = 0; looppts <= nbPts; looppts++) { EuropeanVanillaOption option = EuropeanVanillaOption.of(strike[looppts], timeToExpiry[loopmat], PutCall.CALL); price[loopmat][looppts] = sabrExtrapolation[loopmat].price(option.getStrike(), option.getPutCall()); } } double[][] priceD = new double[nbTTM][nbPts - 1]; double[][] priceD2 = new double[nbTTM][nbPts - 1]; for (int loopmat = 0; loopmat < nbTTM; loopmat++) { for (int looppts = 1; looppts < nbPts; looppts++) { priceD[loopmat][looppts - 1] = (price[loopmat][looppts + 1] - price[loopmat][looppts - 1]) / (strike[looppts + 1] - strike[looppts - 1]); priceD2[loopmat][looppts - 1] = (price[loopmat][looppts + 1] + price[loopmat][looppts - 1] - 2 * price[loopmat][looppts]) / ((strike[looppts + 1] - strike[looppts]) * (strike[looppts + 1] - strike[looppts])); } } double epsDensity = 1.0E-20; // Conditions are not checked when the density is very small. for (int loopmat = 0; loopmat < nbTTM; loopmat++) { for (int looppts = 1; looppts < nbPts - 1; looppts++) { assertTrue(((priceD[loopmat][looppts] / priceD[loopmat][looppts - 1] < 1) && (priceD[loopmat][looppts] / priceD[loopmat][looppts - 1] > 0.50)) || Math.abs(priceD2[loopmat][looppts]) < epsDensity); assertTrue(priceD2[loopmat][looppts] > 0 || Math.abs(priceD2[loopmat][looppts]) < epsDensity); assertTrue((priceD2[loopmat][looppts] / priceD2[loopmat][looppts - 1] < 1 && priceD2[loopmat][looppts] / priceD2[loopmat][looppts - 1] > 0.50) || Math.abs(priceD2[loopmat][looppts]) < epsDensity); } } } private static final double EPS = 1.0e-6; private static final SabrHaganVolatilityFunctionProvider FUNC_HAGAN = SabrHaganVolatilityFunctionProvider.DEFAULT; @SuppressWarnings("unchecked") private static final VolatilityFunctionProvider<SabrFormulaData>[] FUNCTIONS = new VolatilityFunctionProvider[] {FUNC_HAGAN}; // other volatility functions to be added /** * Testing C2 continuity. */ @Test public void smoothnessTest() { for (VolatilityFunctionProvider<SabrFormulaData> func : FUNCTIONS) { SabrExtrapolationRightFunction extrapolation = SabrExtrapolationRightFunction.of(FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, func); for (PutCall isCall : new PutCall[] {PutCall.CALL, PutCall.PUT}) { double priceBase = extrapolation.price(CUT_OFF_STRIKE, isCall); double priceUp = extrapolation.price(CUT_OFF_STRIKE + EPS, isCall); double priceDw = extrapolation.price(CUT_OFF_STRIKE - EPS, isCall); assertEquals(priceBase, priceUp, EPS); assertEquals(priceBase, priceDw, EPS); double priceUpUp = extrapolation.price(CUT_OFF_STRIKE + 2.0 * EPS, isCall); double priceDwDw = extrapolation.price(CUT_OFF_STRIKE - 2.0 * EPS, isCall); 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); } } } /** * Test small forward. */ @Test public void smallForwardTest() { double smallForward = 0.1e-6; double smallCutoff = 0.9e-6; for (VolatilityFunctionProvider<SabrFormulaData> func : FUNCTIONS) { SabrExtrapolationRightFunction right = SabrExtrapolationRightFunction.of(smallForward, SABR_DATA, smallCutoff, TIME_TO_EXPIRY, MU, func); for (PutCall isCall : new PutCall[] {PutCall.CALL, PutCall.PUT}) { double priceBase = right.price(smallCutoff, isCall); double priceUp = right.price(smallCutoff + EPS * 0.1, isCall); double priceDw = right.price(smallCutoff - EPS * 0.1, isCall); assertEquals(priceBase, priceUp, EPS * 10.0); assertEquals(priceBase, priceDw, EPS * 10.0); } } } /** * Extrapolator is not calibrated in this case, then the gap may be produced at the cutoff. */ @Test public void smallExpiryTest() { double smallExpiry = 0.5e-6; for (VolatilityFunctionProvider<SabrFormulaData> func : FUNCTIONS) { SabrExtrapolationRightFunction right = SabrExtrapolationRightFunction.of(FORWARD * 0.01, SABR_DATA, CUT_OFF_STRIKE, smallExpiry, MU, func); for (PutCall isCall : new PutCall[] {PutCall.CALL, PutCall.PUT}) { double priceBase = right.price(CUT_OFF_STRIKE, isCall); double priceUp = right.price(CUT_OFF_STRIKE + EPS * 0.1, isCall); double priceDw = right.price(CUT_OFF_STRIKE - EPS * 0.1, isCall); assertEquals(priceBase, priceUp, EPS); assertEquals(priceBase, priceDw, EPS); assertEquals(right.getParameter()[0], -1.0E4, 1.e-12); assertEquals(right.getParameter()[1], 0.0, 1.e-12); assertEquals(right.getParameter()[2], 0.0, 1.e-12); } } } }