@Test /** * Test if the Hagan volatility function implementation around ATM is numerically stable enough * (the finite difference slope should be small enough). */ public void testATMSmoothness() { double timeToExpiry = 1; double alpha = 0.05; double beta = 0.5; double nu = 0.50; double rho = -0.25; int nbPoints = 100; double forward = 0.05; double[] sabrVolatilty = new double[2 * nbPoints + 1]; double range = 5E-9; double strike[] = new double[2 * nbPoints + 1]; for (int looppts = -nbPoints; looppts <= nbPoints; looppts++) { strike[looppts + nbPoints] = forward + ((double) looppts) / nbPoints * range; SabrFormulaData SabrData = SabrFormulaData.of(alpha, beta, rho, nu); sabrVolatilty[looppts + nbPoints] = FUNCTION.getVolatility(forward, strike[looppts + nbPoints], timeToExpiry, SabrData); } for (int looppts = -nbPoints; looppts < nbPoints; looppts++) { assertTrue( Math.abs(sabrVolatilty[looppts + nbPoints + 1] - sabrVolatilty[looppts + nbPoints]) / (strike[looppts + nbPoints + 1] - strike[looppts + nbPoints]) < 20.0); } }
/** * Test the alpha = 0 edge case. Implied vol is zero for alpha = 0, and except in the ATM case, * the alpha sensitivity is infinite. We choose to (arbitrarily) return 1e7 in this case. */ @Test public void testVolatilityAdjointAlpha0() { double eps = 1e-5; double tol = 1e-6; SabrFormulaData data = DATA.withAlpha(0.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); double volatility = FUNCTION.getVolatility(F, STRIKE_ITM, T, data); ValueDerivatives volatilityAdjoint = FUNCTION.getVolatilityAdjoint(F, STRIKE_ITM, T, data); assertEquals(volatility, volatilityAdjoint.getValue(), tol); assertEquals(0.0, volatilityAdjoint.getDerivative(0), tol); assertEquals(0.0, volatilityAdjoint.getDerivative(1), tol); assertEquals(1e7, volatilityAdjoint.getDerivative(2), tol); assertEquals(0.0, volatilityAdjoint.getDerivative(3), tol); assertEquals(0.0, volatilityAdjoint.getDerivative(4), tol); assertEquals(0.0, volatilityAdjoint.getDerivative(5), tol); }
private void testVolatilityAdjoint( double forward, EuropeanVanillaOption optionData, SabrFormulaData sabrData, double eps, double tol) { double volatility = FUNCTION.getVolatility( forward, optionData.getStrike(), optionData.getTimeToExpiry(), sabrData); double[] volatilityAdjoint = toArray( FUNCTION.getVolatilityAdjoint( forward, optionData.getStrike(), optionData.getTimeToExpiry(), sabrData)); assertEquals(volatility, volatilityAdjoint[0], tol); assertEqualsRelTol( "Forward Sensitivity" + sabrData.toString(), fdSensitivity(optionData, forward, sabrData, SabrParameter.Forward, eps), volatilityAdjoint[1], tol); assertEqualsRelTol( "Strike Sensitivity" + sabrData.toString(), fdSensitivity(optionData, forward, sabrData, SabrParameter.Strike, eps), volatilityAdjoint[2], tol); assertEqualsRelTol( "Alpha Sensitivity" + sabrData.toString(), fdSensitivity(optionData, forward, sabrData, SabrParameter.Alpha, eps), volatilityAdjoint[3], tol); assertEqualsRelTol( "Beta Sensitivity" + sabrData.toString(), fdSensitivity(optionData, forward, sabrData, SabrParameter.Beta, eps), volatilityAdjoint[4], tol); assertEqualsRelTol( "Rho Sensitivity" + sabrData.toString(), fdSensitivity(optionData, forward, sabrData, SabrParameter.Rho, eps), volatilityAdjoint[5], tol); assertEqualsRelTol( "Nu Sensitivity" + sabrData.toString(), fdSensitivity(optionData, forward, sabrData, SabrParameter.Nu, eps), volatilityAdjoint[6], tol); }
/** Calculate the true SABR delta and gamma and compare with that found by finite difference */ @Test(enabled = false) public void testGreeks() { double eps = 1e-3; double f = 1.2; double k = 1.4; double t = 5.0; double alpha = 0.3; double beta = 0.6; double rho = -0.4; double nu = 0.4; SabrFormulaData sabrData = SabrFormulaData.of(alpha, beta, rho, nu); ValueDerivatives adj = FUNCTION.getVolatilityAdjoint(f, k, t, sabrData); double bsDelta = BlackFormulaRepository.delta(f, k, t, adj.getValue(), true); double bsVega = BlackFormulaRepository.vega(f, k, t, adj.getValue()); double volForwardSense = adj.getDerivative(1); double delta = bsDelta + bsVega * volForwardSense; SabrFormulaData data = SabrFormulaData.of(alpha, beta, rho, nu); double volUp = FUNCTION.getVolatility(f + eps, k, t, data); double volDown = FUNCTION.getVolatility(f - eps, k, t, data); double priceUp = BlackFormulaRepository.price(f + eps, k, t, volUp, true); double price = BlackFormulaRepository.price(f, k, t, adj.getValue(), true); double priceDown = BlackFormulaRepository.price(f - eps, k, t, volDown, true); double fdDelta = (priceUp - priceDown) / 2 / eps; assertEquals(fdDelta, delta, 1e-6); double bsVanna = BlackFormulaRepository.vanna(f, k, t, adj.getValue()); double bsGamma = BlackFormulaRepository.gamma(f, k, t, adj.getValue()); double[] volD1 = new double[5]; double[][] volD2 = new double[2][2]; FUNCTION.getVolatilityAdjoint2(f, k, t, sabrData, volD1, volD2); double d2Sigmad2Fwd = volD2[0][0]; double gamma = bsGamma + 2 * bsVanna * adj.getDerivative(1) + bsVega * d2Sigmad2Fwd; double fdGamma = (priceUp + priceDown - 2 * price) / eps / eps; double d2Sigmad2FwdFD = (volUp + volDown - 2 * adj.getValue()) / eps / eps; assertEquals(d2Sigmad2FwdFD, d2Sigmad2Fwd, 1e-4); assertEquals(fdGamma, gamma, 1e-2); }
private void volatilityAdjoint2ForInstrument( EuropeanVanillaOption option, double tolerance1, double tolerance2) { // vol double volatility = FUNCTION.getVolatility(F, option.getStrike(), option.getTimeToExpiry(), DATA); double[] volatilityAdjoint = toArray( FUNCTION.getVolatilityAdjoint(F, option.getStrike(), option.getTimeToExpiry(), DATA)); double[] volD = new double[6]; double[][] volD2 = new double[2][2]; double vol = FUNCTION.getVolatilityAdjoint2( F, option.getStrike(), option.getTimeToExpiry(), DATA, volD, volD2); assertEquals(volatility, vol, tolerance1); // Derivative for (int loopder = 0; loopder < 6; loopder++) { assertEquals(volatilityAdjoint[loopder + 1], volD[loopder], tolerance1); } // Derivative forward-forward double deltaF = 0.000001; double volatilityFP = FUNCTION.getVolatility(F + deltaF, option.getStrike(), option.getTimeToExpiry(), DATA); double volatilityFM = FUNCTION.getVolatility(F - deltaF, option.getStrike(), option.getTimeToExpiry(), DATA); double derivativeFF_FD = (volatilityFP + volatilityFM - 2 * volatility) / (deltaF * deltaF); assertEquals(derivativeFF_FD, volD2[0][0], tolerance2); // Derivative strike-strike double deltaK = 0.000001; double volatilityKP = FUNCTION.getVolatility(F, option.getStrike() + deltaK, option.getTimeToExpiry(), DATA); double volatilityKM = FUNCTION.getVolatility(F, option.getStrike() - deltaK, option.getTimeToExpiry(), DATA); double derivativeKK_FD = (volatilityKP + volatilityKM - 2 * volatility) / (deltaK * deltaK); assertEquals(derivativeKK_FD, volD2[1][1], tolerance2); // Derivative strike-forward double volatilityFPKP = FUNCTION.getVolatility( F + deltaF, option.getStrike() + deltaK, option.getTimeToExpiry(), DATA); double derivativeFK_FD = (volatilityFPKP + volatility - volatilityFP - volatilityKP) / (deltaF * deltaK); assertEquals(derivativeFK_FD, volD2[0][1], tolerance2); assertEquals(volD2[0][1], volD2[1][0], 1E-6); }
/** * Check that $\rho \simeq 1$ case is smoothly connected with a general case, i.e., comparing the * approximated computation and full computation around the cutoff, which is currently $\rho = 1.0 * - 1.0e-5$ Note that the resulting numbers contain a large error if $\rho \simeq 1$ and $z * \simeq 1$ are true at the same time */ @Test public void largeRhoSmoothnessTest() { double rhoEps = 1.e-5; // rhoIn is larger than the cutoff, // thus vol and sensitivities are computed by approximation formulas which are regular in the // limit rho -> 1. double rhoIn = 1.0 - 0.5 * rhoEps; // rhoOut is smaller than the cutoff, thus vol and sensitivities are computed by full formula. double rhoOut = 1.0 - 1.5 * rhoEps; SabrFormulaData dataIn = SabrFormulaData.of(ALPHA, BETA, rhoIn, NU); SabrFormulaData dataOut = SabrFormulaData.of(ALPHA, BETA, rhoOut, NU); /* * z<1 case, i.e., finite values in the rho->1 limit */ double volatilityOut = FUNCTION.getVolatility(F, STRIKE_OTM, T, dataOut); double[] adjointOut = toArray(FUNCTION.getVolatilityAdjoint(F, STRIKE_OTM, T, dataOut)); double[] volatilityDOut = new double[6]; double[][] volatilityD2Out = new double[2][2]; double volatility2Out = FUNCTION.getVolatilityAdjoint2(F, STRIKE_OTM, T, dataOut, volatilityDOut, volatilityD2Out); double volatilityIn = FUNCTION.getVolatility(F, STRIKE_OTM, T, dataIn); double[] adjointIn = toArray(FUNCTION.getVolatilityAdjoint(F, STRIKE_OTM, T, dataIn)); double[] volatilityDIn = new double[6]; double[][] volatilityD2In = new double[2][2]; double volatility2In = FUNCTION.getVolatilityAdjoint2(F, STRIKE_OTM, T, dataIn, volatilityDIn, volatilityD2In); assertEquals(volatilityOut, volatilityIn, rhoEps); assertEquals(volatility2Out, volatility2In, rhoEps); for (int i = 0; i < adjointOut.length; ++i) { double ref = adjointOut[i]; assertEquals(adjointOut[i], adjointIn[i], Math.max(Math.abs(ref), 1.0) * 1.e-3); } for (int i = 0; i < volatilityDOut.length; ++i) { double ref = volatilityDOut[i]; assertEquals(volatilityDOut[i], volatilityDIn[i], Math.max(Math.abs(ref), 1.0) * 1.e-3); } /* * z>1 case, runs into infinity or 0. * Convergence speed is much faster (and typically smoother). */ rhoIn = 1.0 - 0.999 * rhoEps; rhoOut = 1.0 - 1.001 * rhoEps; dataIn = SabrFormulaData.of(ALPHA, BETA, rhoIn, NU); dataOut = SabrFormulaData.of(ALPHA, BETA, rhoOut, NU); volatilityOut = FUNCTION.getVolatility(3.0 * F, STRIKE_ITM, T, dataOut); adjointOut = toArray(FUNCTION.getVolatilityAdjoint(3.0 * F, STRIKE_ITM, T, dataOut)); volatilityDOut = new double[6]; volatilityD2Out = new double[2][2]; volatility2Out = FUNCTION.getVolatilityAdjoint2( 3.0 * F, STRIKE_ITM, T, dataOut, volatilityDOut, volatilityD2Out); volatilityIn = FUNCTION.getVolatility(3.0 * F, STRIKE_ITM, T, dataIn); adjointIn = toArray(FUNCTION.getVolatilityAdjoint(3.0 * F, STRIKE_ITM, T, dataIn)); volatilityDIn = new double[6]; volatilityD2In = new double[2][2]; volatility2In = FUNCTION.getVolatilityAdjoint2( 3.0 * F, STRIKE_ITM, T, dataIn, volatilityDIn, volatilityD2In); assertEquals(volatilityOut, volatilityIn, rhoEps); assertEquals(volatility2Out, volatility2In, rhoEps); for (int i = 0; i < adjointOut.length; ++i) { double ref = adjointOut[i]; assertEquals(ref, adjointIn[i], Math.max(Math.abs(ref), 1.0) * 1.e-2); } for (int i = 0; i < volatilityDOut.length; ++i) { double ref = volatilityDOut[i]; assertEquals(ref, volatilityDIn[i], Math.max(Math.abs(ref), 1.0) * 1.e-2); } }