/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.option.pricing.analytic.formula;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.model.volatility.BlackImpliedVolatilityFormula;
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.analytics.math.function.Function1D;
import com.opengamma.util.test.TestGroup;
/**
* Tests of the SABR valuation of options with extrapolation on the right (for high strikes). The SABR pricing is through Black formula with implied volatility.
*/
@Test(groups = TestGroup.UNIT)
public class SABRExtrapolationRightFunctionTest {
// Data
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 = new SABRFormulaData(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 = new SABRExtrapolationRightFunction(FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU);
// Function
private static final BlackPriceFunction BLACK_FUNCTION = new BlackPriceFunction();
private static final SABRHaganVolatilityFunction SABR_FUNCTION = new SABRHaganVolatilityFunction();
private static final double TOLERANCE_PRICE = 1.0E-10;
@Test
/**
* 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;
EuropeanVanillaOption optionIn = new EuropeanVanillaOption(strikeIn, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionAt = new EuropeanVanillaOption(strikeAt, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionOut = new EuropeanVanillaOption(strikeOut, TIME_TO_EXPIRY, true);
Function1D<SABRFormulaData, Double> funcSabrIn = SABR_FUNCTION.getVolatilityFunction(optionIn, FORWARD);
double volatilityIn = funcSabrIn.evaluate(SABR_DATA);
BlackFunctionData dataBlackIn = new BlackFunctionData(FORWARD, 1.0, volatilityIn);
Function1D<BlackFunctionData, Double> funcBlackIn = BLACK_FUNCTION.getPriceFunction(optionIn);
double priceExpectedIn = funcBlackIn.evaluate(dataBlackIn);
double priceIn = SABR_EXTRAPOLATION.price(optionIn);
assertEquals("SABR extrapolation, below cut-off", priceExpectedIn, priceIn, 1E-10);
Function1D<SABRFormulaData, Double> funcSabrAt = SABR_FUNCTION.getVolatilityFunction(optionAt, FORWARD);
double volatilityAt = funcSabrAt.evaluate(SABR_DATA);
BlackFunctionData dataBlackAt = new BlackFunctionData(FORWARD, 1.0, volatilityAt);
Function1D<BlackFunctionData, Double> funcBlackAt = BLACK_FUNCTION.getPriceFunction(optionAt);
double priceExpectedAt = funcBlackAt.evaluate(dataBlackAt);
double priceAt = SABR_EXTRAPOLATION.price(optionAt);
assertEquals("SABR extrapolation, at cut-off", priceExpectedAt, priceAt, 1E-10);
double priceOut = SABR_EXTRAPOLATION.price(optionOut);
double priceExpectedOut = 5.427104E-5; // From previous run
assertEquals("SABR extrapolation, above cut-off", priceExpectedOut, priceOut, 1E-10);
}
@Test
/**
* 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 = new SABRExtrapolationRightFunction(FORWARD, SABR_DATA, CUT_OFF_STRIKE, timeToExpiry[loopexp], MU);
EuropeanVanillaOption optionIn = new EuropeanVanillaOption(strikeIn, timeToExpiry[loopexp], true);
EuropeanVanillaOption optionAt = new EuropeanVanillaOption(strikeAt, timeToExpiry[loopexp], true);
EuropeanVanillaOption optionOut = new EuropeanVanillaOption(strikeOut, timeToExpiry[loopexp], true);
Function1D<SABRFormulaData, Double> funcSabrIn = SABR_FUNCTION.getVolatilityFunction(optionIn, FORWARD);
double volatilityIn = funcSabrIn.evaluate(SABR_DATA);
BlackFunctionData dataBlackIn = new BlackFunctionData(FORWARD, 1.0, volatilityIn);
Function1D<BlackFunctionData, Double> funcBlackIn = BLACK_FUNCTION.getPriceFunction(optionIn);
double priceExpectedIn = funcBlackIn.evaluate(dataBlackIn);
double priceIn = sabrExtra.price(optionIn);
assertEquals("SABR extrapolation, below cut-off", priceExpectedIn, priceIn, TOLERANCE_PRICE);
Function1D<SABRFormulaData, Double> funcSabrAt = SABR_FUNCTION.getVolatilityFunction(optionAt, FORWARD);
double volatilityAt = funcSabrAt.evaluate(SABR_DATA);
BlackFunctionData dataBlackAt = new BlackFunctionData(FORWARD, 1.0, volatilityAt);
Function1D<BlackFunctionData, Double> funcBlackAt = BLACK_FUNCTION.getPriceFunction(optionAt);
double priceExpectedAt = funcBlackAt.evaluate(dataBlackAt);
double priceAt = sabrExtra.price(optionAt);
assertEquals("SABR extrapolation, at cut-off", priceExpectedAt, priceAt, TOLERANCE_PRICE);
double priceOut = sabrExtra.price(optionOut);
double priceExpectedOut = 0.0; // From previous run
assertEquals("SABR extrapolation, above cut-off", priceExpectedOut, priceOut, TOLERANCE_PRICE);
}
}
@Test
/**
* Tests the price derivative with respect to forward for options in SABR model with extrapolation.
*/
public void priceDerivativeForward() {
double strikeIn = 0.08;
double strikeAt = CUT_OFF_STRIKE;
double strikeOut = 0.12;
EuropeanVanillaOption optionIn = new EuropeanVanillaOption(strikeIn, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionAt = new EuropeanVanillaOption(strikeAt, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionOut = new EuropeanVanillaOption(strikeOut, TIME_TO_EXPIRY, true);
double shiftF = 0.000001;
SABRFormulaData sabrDataFP = new SABRFormulaData(ALPHA, BETA, RHO, NU);
SABRExtrapolationRightFunction sabrExtrapolationFP = new SABRExtrapolationRightFunction(FORWARD + shiftF, sabrDataFP, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU);
// Below cut-off strike
double priceIn = SABR_EXTRAPOLATION.price(optionIn);
double priceInFP = sabrExtrapolationFP.price(optionIn);
double priceInDF = SABR_EXTRAPOLATION.priceDerivativeForward(optionIn);
double priceInDFExpected = (priceInFP - priceIn) / shiftF;
assertEquals("SABR extrapolation: derivative with respect to forward, below cut-off", priceInDFExpected, priceInDF, 1E-5);
// At cut-off strike
double priceAt = SABR_EXTRAPOLATION.price(optionAt);
double priceAtFP = sabrExtrapolationFP.price(optionAt);
double priceAtDF = SABR_EXTRAPOLATION.priceDerivativeForward(optionAt);
double priceAtDFExpected = (priceAtFP - priceAt) / shiftF;
assertEquals("SABR extrapolation: derivative with respect to forward, at cut-off", 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("SABR extrapolation: parameters derivative " + loopparam, 1.0, abcDFExpected[loopparam] / abcDF[loopparam], 5E-2);
}
double priceOut = SABR_EXTRAPOLATION.price(optionOut);
double priceOutFP = sabrExtrapolationFP.price(optionOut);
double priceOutDF = SABR_EXTRAPOLATION.priceDerivativeForward(optionOut);
double priceOutDFExpected = (priceOutFP - priceOut) / shiftF;
assertEquals("SABR extrapolation: derivative with respect to forward, above cut-off", priceOutDFExpected, priceOutDF, 1E-5);
}
@Test
/**
* Tests the price derivative with respect to forward for options in SABR model with extrapolation.
*/
public void priceDerivativeStrike() {
double strikeIn = 0.08;
double strikeAt = CUT_OFF_STRIKE;
double strikeOut = 0.12;
double shiftK = 0.000001;
EuropeanVanillaOption optionIn = new EuropeanVanillaOption(strikeIn, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionAt = new EuropeanVanillaOption(strikeAt, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionOut = new EuropeanVanillaOption(strikeOut, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionInKP = new EuropeanVanillaOption(strikeIn + shiftK, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionAtKP = new EuropeanVanillaOption(strikeAt + shiftK, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionOutKP = new EuropeanVanillaOption(strikeOut + shiftK, TIME_TO_EXPIRY, true);
// Below cut-off strike
double priceIn = SABR_EXTRAPOLATION.price(optionIn);
double priceInKP = SABR_EXTRAPOLATION.price(optionInKP);
double priceInDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionIn);
double priceInDFExpected = (priceInKP - priceIn) / shiftK;
assertEquals("SABR extrapolation: derivative with respect to strike, below cut-off", priceInDFExpected, priceInDK, 1E-5);
// At cut-off strike
double priceAt = SABR_EXTRAPOLATION.price(optionAt);
double priceAtKP = SABR_EXTRAPOLATION.price(optionAtKP);
double priceAtDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionAt);
double priceAtDFExpected = (priceAtKP - priceAt) / shiftK;
assertEquals("SABR extrapolation: derivative with respect to strike, at cut-off", priceAtDFExpected, priceAtDK, 1E-5);
// At cut-off strike
double priceOut = SABR_EXTRAPOLATION.price(optionOut);
double priceOutKP = SABR_EXTRAPOLATION.price(optionOutKP);
double priceOutDK = SABR_EXTRAPOLATION.priceDerivativeStrike(optionOut);
double priceOutDFExpected = (priceOutKP - priceOut) / shiftK;
assertEquals("SABR extrapolation: derivative with respect to strike, above cut-off", priceOutDFExpected, priceOutDK, 1E-5);
}
@Test
/**
* Tests the price derivative with respect to forward for options in SABR model with extrapolation.
*/
public void priceDerivativeSABR() {
double strikeIn = 0.08;
double strikeAt = CUT_OFF_STRIKE;
double strikeOut = 0.12;
EuropeanVanillaOption optionIn = new EuropeanVanillaOption(strikeIn, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionAt = new EuropeanVanillaOption(strikeAt, TIME_TO_EXPIRY, true);
EuropeanVanillaOption optionOut = new EuropeanVanillaOption(strikeOut, TIME_TO_EXPIRY, true);
double shift = 0.000001;
SABRFormulaData sabrDataAP = new SABRFormulaData(ALPHA + shift, BETA, RHO, NU);
SABRFormulaData sabrDataBP = new SABRFormulaData(ALPHA, BETA + shift, RHO, NU);
SABRFormulaData sabrDataRP = new SABRFormulaData(ALPHA, BETA, RHO + shift, NU);
SABRFormulaData sabrDataNP = new SABRFormulaData(ALPHA, BETA, RHO, NU + shift);
SABRExtrapolationRightFunction sabrExtrapolationAP = new SABRExtrapolationRightFunction(FORWARD, sabrDataAP, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU);
SABRExtrapolationRightFunction sabrExtrapolationBP = new SABRExtrapolationRightFunction(FORWARD, sabrDataBP, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU);
SABRExtrapolationRightFunction sabrExtrapolationRP = new SABRExtrapolationRightFunction(FORWARD, sabrDataRP, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU);
SABRExtrapolationRightFunction sabrExtrapolationNP = new SABRExtrapolationRightFunction(FORWARD, sabrDataNP, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU);
// Below cut-off strike
double priceInExpected = SABR_EXTRAPOLATION.price(optionIn);
double[] priceInPP = new double[4];
priceInPP[0] = sabrExtrapolationAP.price(optionIn);
priceInPP[1] = sabrExtrapolationBP.price(optionIn);
priceInPP[2] = sabrExtrapolationRP.price(optionIn);
priceInPP[3] = sabrExtrapolationNP.price(optionIn);
double[] priceInDsabr = new double[4];
double priceIn = SABR_EXTRAPOLATION.priceAdjointSABR(optionIn, priceInDsabr);
assertEquals("SABR extrapolation below cut-off: price in adjoint", priceInExpected, priceIn, 1E-5);
double[] priceInDsabrExpected = new double[4];
for (int loopparam = 0; loopparam < 3; loopparam++) {
priceInDsabrExpected[loopparam] = (priceInPP[loopparam] - priceIn) / shift;
assertEquals("SABR extrapolation below cut-off: derivative with respect to SABR parameter " + loopparam, priceInDsabrExpected[loopparam], priceInDsabr[loopparam], 1E-5);
}
// At cut-off strike
double priceAtExpected = SABR_EXTRAPOLATION.price(optionAt);
double[] priceAtPP = new double[4];
priceAtPP[0] = sabrExtrapolationAP.price(optionAt);
priceAtPP[1] = sabrExtrapolationBP.price(optionAt);
priceAtPP[2] = sabrExtrapolationRP.price(optionAt);
priceAtPP[3] = sabrExtrapolationNP.price(optionAt);
double[] priceAtDsabr = new double[4];
double priceAt = SABR_EXTRAPOLATION.priceAdjointSABR(optionAt, priceAtDsabr);
assertEquals("SABR extrapolation at cut-off: price in adjoint", priceAtExpected, priceAt, 1E-5);
double[] priceAtDsabrExpected = new double[4];
for (int loopparam = 0; loopparam < 3; loopparam++) {
priceAtDsabrExpected[loopparam] = (priceAtPP[loopparam] - priceAt) / shift;
assertEquals("SABR extrapolation at cut-off: derivative with respect to SABR parameter " + loopparam, 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("SABR extrapolation: parameters derivative " + loopparam + " / " + loopabc, 1.0, abcDPExpected[loopparam][loopabc] / abcDP[loopparam][loopabc], 5.0E-2);
}
}
double priceOutExpected = SABR_EXTRAPOLATION.price(optionOut);
double[] priceOutPP = new double[4];
priceOutPP[0] = sabrExtrapolationAP.price(optionOut);
priceOutPP[1] = sabrExtrapolationBP.price(optionOut);
priceOutPP[2] = sabrExtrapolationRP.price(optionOut);
priceOutPP[3] = sabrExtrapolationNP.price(optionOut);
double[] priceOutDsabr = new double[4];
double priceOut = SABR_EXTRAPOLATION.priceAdjointSABR(optionOut, priceOutDsabr);
assertEquals("SABR extrapolation above cut-off: price in adjoint", priceOutExpected, priceOut, 1E-5);
double[] priceOutDsabrExpected = new double[4];
for (int loopparam = 0; loopparam < 4; loopparam++) {
priceOutDsabrExpected[loopparam] = (priceOutPP[loopparam] - priceOut) / shift;
assertEquals("SABR extrapolation above cut-off: derivative with respect to SABR parameter " + loopparam, 1.0, priceOutDsabrExpected[loopparam] / priceOutDsabr[loopparam], 4.0E-4);
}
}
@Test
/**
* 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 = new EuropeanVanillaOption(strike, t, true);
SABRFormulaData sabrData = new SABRFormulaData(alpha, beta, rho, nu);
double forward = 0.0404500579038675;
SABRExtrapolationRightFunction sabrExtrapolation = new SABRExtrapolationRightFunction(forward, sabrData, cutOff, t, mu);
double shift = 0.000001;
SABRFormulaData sabrDataAP = new SABRFormulaData(alpha + shift, beta, rho, nu);
SABRFormulaData sabrDataBP = new SABRFormulaData(alpha, beta + shift, rho, nu);
SABRFormulaData sabrDataRP = new SABRFormulaData(alpha, beta, rho + shift, nu);
SABRFormulaData sabrDataNP = new SABRFormulaData(alpha, beta, rho, nu + shift);
SABRExtrapolationRightFunction sabrExtrapolationAP = new SABRExtrapolationRightFunction(forward, sabrDataAP, cutOff, t, mu);
SABRExtrapolationRightFunction sabrExtrapolationBP = new SABRExtrapolationRightFunction(forward, sabrDataBP, cutOff, t, mu);
SABRExtrapolationRightFunction sabrExtrapolationRP = new SABRExtrapolationRightFunction(forward, sabrDataRP, cutOff, t, mu);
SABRExtrapolationRightFunction sabrExtrapolationNP = new SABRExtrapolationRightFunction(forward, sabrDataNP, cutOff, t, 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("SABR extrapolation: parameters derivative " + loopparam + " / " + loopabc, 1.0, abcDPExpected[loopparam][loopabc] / abcDP[loopparam][loopabc], 5.0E-2);
}
}
double priceOutExpected = sabrExtrapolation.price(option);
double[] priceOutPP = new double[4];
priceOutPP[0] = sabrExtrapolationAP.price(option);
priceOutPP[1] = sabrExtrapolationBP.price(option);
priceOutPP[2] = sabrExtrapolationRP.price(option);
priceOutPP[3] = sabrExtrapolationNP.price(option);
double[] priceOutDsabr = new double[4];
double priceOut = sabrExtrapolation.priceAdjointSABR(option, priceOutDsabr);
assertEquals("SABR extrapolation above cut-off: price in adjoint", priceOutExpected, priceOut, 1E-5);
double[] priceOutDsabrExpected = new double[4];
for (int loopparam = 0; loopparam < 4; loopparam++) {
priceOutDsabrExpected[loopparam] = (priceOutPP[loopparam] - priceOut) / shift;
assertEquals("SABR extrapolation above cut-off: derivative with respect to SABR parameter " + loopparam, 1.0, priceOutDsabrExpected[loopparam] / priceOutDsabr[loopparam], 4.0E-4);
}
}
@Test
/**
* 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 = new EuropeanVanillaOption(strikeIn, TIME_TO_EXPIRY, true);
EuropeanVanillaOption putIn = new EuropeanVanillaOption(strikeIn, TIME_TO_EXPIRY, false);
EuropeanVanillaOption callAt = new EuropeanVanillaOption(strikeAt, TIME_TO_EXPIRY, true);
EuropeanVanillaOption putAt = new EuropeanVanillaOption(strikeAt, TIME_TO_EXPIRY, false);
EuropeanVanillaOption callOut = new EuropeanVanillaOption(strikeOut, TIME_TO_EXPIRY, true);
EuropeanVanillaOption putOut = new EuropeanVanillaOption(strikeOut, TIME_TO_EXPIRY, false);
double priceCallIn = SABR_EXTRAPOLATION.price(callIn);
double pricePutIn = SABR_EXTRAPOLATION.price(putIn);
assertEquals("SABR extrapolation, below cut-off: put/call parity", FORWARD - strikeIn, priceCallIn - pricePutIn, 1E-10);
double priceCallAt = SABR_EXTRAPOLATION.price(callAt);
double pricePutAt = SABR_EXTRAPOLATION.price(putAt);
assertEquals("SABR extrapolation, at cut-off: put/call parity", FORWARD - strikeAt, priceCallAt - pricePutAt, 1E-10);
double priceCallOut = SABR_EXTRAPOLATION.price(callOut);
double pricePutOut = SABR_EXTRAPOLATION.price(putOut);
assertEquals("SABR extrapolation, above cut-off: put/call parity", FORWARD - strikeOut, priceCallOut - pricePutOut, 1E-10);
}
@Test
/**
* 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 = new EuropeanVanillaOption(strike[looppts], TIME_TO_EXPIRY, true);
price[looppts] = SABR_EXTRAPOLATION.price(option);
}
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("SABR extrapolation, smooth first derivative", priceD[looppts - 1], priceD[looppts], 1.5E-3);
assertEquals("SABR extrapolation, smooth second derivative", priceD2[looppts - 1], priceD2[looppts], 1.5E-1);
}
}
@Test
/**
* 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] = new SABRExtrapolationRightFunction(FORWARD, SABR_DATA, CUT_OFF_STRIKE, timeToExpiry[loopmat], MU);
}
double[][] price = new double[nbTTM][nbPts + 1];
for (int loopmat = 0; loopmat < nbTTM; loopmat++) {
for (int looppts = 0; looppts <= nbPts; looppts++) {
EuropeanVanillaOption option = new EuropeanVanillaOption(strike[looppts], timeToExpiry[loopmat], true);
price[loopmat][looppts] = sabrExtrapolation[loopmat].price(option);
}
}
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("SABR extrapolation, smooth first derivative - mat " + loopmat + " / pt " + looppts + " [" + priceD[loopmat][looppts] + "/" + priceD[loopmat][looppts - 1] + "]",
((priceD[loopmat][looppts] / priceD[loopmat][looppts - 1] < 1) && (priceD[loopmat][looppts] / priceD[loopmat][looppts - 1] > 0.50)) || Math.abs(priceD2[loopmat][looppts]) < epsDensity);
assertTrue("SABR extrapolation, positive second derivative - mat " + loopmat + " / pt " + looppts + " [" + priceD2[loopmat][looppts] + "]",
priceD2[loopmat][looppts] > 0 || Math.abs(priceD2[loopmat][looppts]) < epsDensity);
assertTrue("SABR extrapolation, smooth second derivative - mat " + loopmat + " / pt " + looppts + " [" + priceD2[loopmat][looppts] + "/" + priceD2[loopmat][looppts - 1] + "]",
(priceD2[loopmat][looppts] / priceD2[loopmat][looppts - 1] < 1 && priceD2[loopmat][looppts] / priceD2[loopmat][looppts - 1] > 0.50) || Math.abs(priceD2[loopmat][looppts]) < epsDensity);
}
}
}
@Test(enabled = false)
/**
* To graph the smile for different tail parameters.
*/
public void smileMultiMu() {
double[] mu = new double[] {5.0, 40.0, 90.0, 150.0 };
int nbMu = mu.length;
int nbPts = 100;
double rangeStrike = 0.02;
double[] strike = new double[nbPts + 1];
double[][] price = new double[nbMu][nbPts + 1];
double[][] impliedVolatility = new double[nbMu][nbPts + 1];
BlackImpliedVolatilityFormula implied = new BlackImpliedVolatilityFormula();
BlackFunctionData blackData = new BlackFunctionData(FORWARD, 1.0, 0.0);
for (int loopmu = 0; loopmu < nbMu; loopmu++) {
SABRExtrapolationRightFunction sabrExtra = new SABRExtrapolationRightFunction(FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, mu[loopmu]);
for (int looppts = 0; looppts <= nbPts; looppts++) {
strike[looppts] = CUT_OFF_STRIKE - rangeStrike + looppts * 4.0 * rangeStrike / nbPts;
EuropeanVanillaOption option = new EuropeanVanillaOption(strike[looppts], TIME_TO_EXPIRY, true);
price[loopmu][looppts] = sabrExtra.price(option);
impliedVolatility[loopmu][looppts] = implied.getImpliedVolatility(blackData, option, price[loopmu][looppts]);
}
}
}
/***************************************************************************************************
* Tests below are for newly added functionalities allowing one to use various volatility formulas
***************************************************************************************************/
private static final double EPS = 1.0e-6;
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();
@SuppressWarnings("unchecked")
private static final VolatilityFunctionProvider<SABRFormulaData>[] FUNCTIONS = new VolatilityFunctionProvider[] {FUNC_HAGAN, FUNC_JOHNSON, FUNC_HAGAN_ALT, FUNC_BERESTYCKI, FUNC_PAULOT };
/**
* Testing C2 continuity
*/
@Test
public void smoothnessTest() {
for (VolatilityFunctionProvider<SABRFormulaData> func : FUNCTIONS) {
SABRExtrapolationRightFunction extrapolation = new SABRExtrapolationRightFunction(FORWARD, SABR_DATA, CUT_OFF_STRIKE, TIME_TO_EXPIRY, MU, func);
for (boolean isCall : new boolean[] {true, false }) {
EuropeanVanillaOption optionBase = new EuropeanVanillaOption(CUT_OFF_STRIKE, TIME_TO_EXPIRY, isCall);
EuropeanVanillaOption optionUp = new EuropeanVanillaOption(CUT_OFF_STRIKE + EPS, TIME_TO_EXPIRY, isCall);
EuropeanVanillaOption optionDw = new EuropeanVanillaOption(CUT_OFF_STRIKE - EPS, TIME_TO_EXPIRY, isCall);
double priceBase = extrapolation.price(optionBase);
double priceUp = extrapolation.price(optionUp);
double priceDw = extrapolation.price(optionDw);
assertEquals(priceBase, priceUp, EPS);
assertEquals(priceBase, priceDw, EPS);
EuropeanVanillaOption optionUpUp = new EuropeanVanillaOption(CUT_OFF_STRIKE + 2.0 * EPS, TIME_TO_EXPIRY, isCall);
EuropeanVanillaOption optionDwDw = new EuropeanVanillaOption(CUT_OFF_STRIKE - 2.0 * EPS, TIME_TO_EXPIRY, isCall);
double priceUpUp = extrapolation.price(optionUpUp);
double priceDwDw = extrapolation.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);
}
}
}
/**
*
*/
@Test
public void smallForwardTest() {
double smallForward = 0.1e-6;
double smallCutoff = 0.9e-6;
for (VolatilityFunctionProvider<SABRFormulaData> func : FUNCTIONS) {
SABRExtrapolationRightFunction right = new SABRExtrapolationRightFunction(smallForward, SABR_DATA, smallCutoff, TIME_TO_EXPIRY, MU, func);
EuropeanVanillaOption optionBase = new EuropeanVanillaOption(smallCutoff, TIME_TO_EXPIRY, false);
EuropeanVanillaOption optionUp = new EuropeanVanillaOption(smallCutoff + EPS * 0.1, TIME_TO_EXPIRY, false);
EuropeanVanillaOption optionDw = new EuropeanVanillaOption(smallCutoff - EPS * 0.1, TIME_TO_EXPIRY, false);
double priceBase = right.price(optionBase);
double priceUp = right.price(optionUp);
double priceDw = right.price(optionDw);
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 = new SABRExtrapolationRightFunction(FORWARD * 0.01, SABR_DATA, CUT_OFF_STRIKE, smallExpiry, MU, func);
EuropeanVanillaOption optionBase = new EuropeanVanillaOption(CUT_OFF_STRIKE, smallExpiry, false);
EuropeanVanillaOption optionUp = new EuropeanVanillaOption(CUT_OFF_STRIKE + EPS * 0.1, smallExpiry, false);
EuropeanVanillaOption optionDw = new EuropeanVanillaOption(CUT_OFF_STRIKE - EPS * 0.1, smallExpiry, false);
double priceBase = right.price(optionBase);
double priceUp = right.price(optionUp);
double priceDw = right.price(optionDw);
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);
}
}
}