// ------------------------------------------------------------------------- @Override public double volatility( double forward, double strike, double timeToExpiry, SabrFormulaData data) { ArgChecker.notNull(data, "data"); double alpha = data.getAlpha(); double beta = data.getBeta(); double rho = data.getRho(); double nu = data.getNu(); return volatility(forward, strike, timeToExpiry, alpha, beta, rho, nu); }
@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 public void testVolatilityAdjointLargeRhoZGreaterThan1() { double eps = 1e-11; double tol = 1e-4; SabrFormulaData data = DATA.withRho(1.0 - 1e-9).withAlpha(0.15 * ALPHA); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); }
@Test public void testVolatilityAdjointLargeRhoZLessThan1() { double eps = 1e-4; double tol = 1e-5; SabrFormulaData data = DATA.withRho(1.0 - 1e-9); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); }
/** Test the rho = 1 edge case */ @Test public void testVolatilityAdjointRho1() { double eps = 1e-4; double tol = 1e-5; SabrFormulaData data = DATA.withRho(1.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); }
/** Test the nu = 0 edge case */ @Test public void testVolatilityAdjointNu0() { double eps = 1e-5; double tol = 1e-6; SabrFormulaData data = DATA.withNu(0.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, 2e-4); testVolatilityAdjoint(F, CALL_OTM, data, eps, 5e-5); }
/** Test the beta = 1 edge case */ @Test public void testVolatilityAdjointBeta1() { double eps = 1e-6; double tol = 1e-6; SabrFormulaData data = DATA.withBeta(1.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); }
@Test public void testVolatilityAdjointSmallAlpha() { double eps = 1e-7; double tol = 1e-3; SabrFormulaData data = DATA.withAlpha(1e-5); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, 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); }
// TODO write a fuzzer that hits SABR with random parameters @Test(enabled = false) public void testRandomParameters() { double eps = 1e-5; double tol = 1e-3; for (int count = 0; count < 100; count++) { double alpha = Math.exp(NORMAL.nextRandom() * 0.2 - 2); double beta = Math.random(); // TODO Uniform numbers in distribution double nu = Math.exp(NORMAL.nextRandom() * 0.3 - 1); double rho = 2 * Math.random() - 1; SabrFormulaData data = SabrFormulaData.of(alpha, beta, rho, nu); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); } }
/** * 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); }
@SuppressWarnings("null") private double fdSensitivity( EuropeanVanillaOption optionData, double forward, SabrFormulaData sabrData, SabrParameter param, double delta) { Function<SabrFormulaData, Double> funcC = null; Function<SabrFormulaData, Double> funcB = null; Function<SabrFormulaData, Double> funcA = null; SabrFormulaData dataC = null; SabrFormulaData dataB = sabrData; SabrFormulaData dataA = null; Function<SabrFormulaData, Double> func = getVolatilityFunction(optionData, forward); FiniteDifferenceType fdType = null; switch (param) { case Strike: double strike = optionData.getStrike(); if (strike >= delta) { fdType = FiniteDifferenceType.CENTRAL; funcA = getVolatilityFunction(withStrike(optionData, strike - delta), forward); funcC = getVolatilityFunction(withStrike(optionData, strike + delta), forward); } else { fdType = FiniteDifferenceType.FORWARD; funcA = func; funcB = getVolatilityFunction(withStrike(optionData, strike + delta), forward); funcC = getVolatilityFunction(withStrike(optionData, strike + 2 * delta), forward); } dataC = sabrData; dataB = sabrData; dataA = sabrData; break; case Forward: if (forward > delta) { fdType = FiniteDifferenceType.CENTRAL; funcA = getVolatilityFunction(optionData, forward - delta); funcC = getVolatilityFunction(optionData, forward + delta); } else { fdType = FiniteDifferenceType.FORWARD; funcA = func; funcB = getVolatilityFunction(optionData, forward + delta); funcC = getVolatilityFunction(optionData, forward + 2 * delta); } dataC = sabrData; dataB = sabrData; dataA = sabrData; break; case Alpha: double a = sabrData.getAlpha(); if (a >= delta) { fdType = FiniteDifferenceType.CENTRAL; dataA = sabrData.withAlpha(a - delta); dataC = sabrData.withAlpha(a + delta); } else { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withAlpha(a + delta); dataC = sabrData.withAlpha(a + 2 * delta); } funcC = func; funcB = func; funcA = func; break; case Beta: double b = sabrData.getBeta(); if (b >= delta) { fdType = FiniteDifferenceType.CENTRAL; dataA = sabrData.withBeta(b - delta); dataC = sabrData.withBeta(b + delta); } else { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withBeta(b + delta); dataC = sabrData.withBeta(b + 2 * delta); } funcC = func; funcB = func; funcA = func; break; case Nu: double n = sabrData.getNu(); if (n >= delta) { fdType = FiniteDifferenceType.CENTRAL; dataA = sabrData.withNu(n - delta); dataC = sabrData.withNu(n + delta); } else { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withNu(n + delta); dataC = sabrData.withNu(n + 2 * delta); } funcC = func; funcB = func; funcA = func; break; case Rho: double r = sabrData.getRho(); if ((r + 1) < delta) { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withRho(r + delta); dataC = sabrData.withRho(r + 2 * delta); } else if ((1 - r) < delta) { fdType = FiniteDifferenceType.BACKWARD; dataA = sabrData.withRho(r - 2 * delta); dataB = sabrData.withRho(r - delta); dataC = sabrData; } else { fdType = FiniteDifferenceType.CENTRAL; dataC = sabrData.withRho(r + delta); dataA = sabrData.withRho(r - delta); } funcC = func; funcB = func; funcA = func; break; default: throw new MathException("enum not found"); } if (fdType != null) { switch (fdType) { case FORWARD: return (-1.5 * funcA.apply(dataA) + 2.0 * funcB.apply(dataB) - 0.5 * funcC.apply(dataC)) / delta; case BACKWARD: return (0.5 * funcA.apply(dataA) - 2.0 * funcB.apply(dataB) + 1.5 * funcC.apply(dataC)) / delta; case CENTRAL: return (funcC.apply(dataC) - funcA.apply(dataA)) / 2.0 / delta; default: throw new MathException("enum not found"); } } throw new MathException("enum not found"); }
/** Test {@link SabrHaganVolatilityFunctionProvider}. */ @Test public class SabrHaganVolatilityFunctionProviderTest extends SabrVolatilityFunctionProviderTestCase { private static ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0d, 1d, new MersenneTwister64(MersenneTwister.DEFAULT_SEED)); private static SabrHaganVolatilityFunctionProvider FUNCTION = SabrHaganVolatilityFunctionProvider.DEFAULT; private static final double ALPHA = 0.05; private static final double BETA = 0.50; private static final double RHO = -0.25; private static final double NU = 0.4; private static final double F = 0.05; private static final SabrFormulaData DATA = SabrFormulaData.of(ALPHA, BETA, RHO, NU); private static final double T = 4.5; private static final double STRIKE_ITM = 0.0450; private static final double STRIKE_OTM = 0.0550; private static EuropeanVanillaOption CALL_ATM = EuropeanVanillaOption.of(F, T, CALL); private static EuropeanVanillaOption CALL_ITM = EuropeanVanillaOption.of(STRIKE_ITM, T, CALL); private static EuropeanVanillaOption CALL_OTM = EuropeanVanillaOption.of(STRIKE_OTM, T, CALL); @Override protected VolatilityFunctionProvider<SabrFormulaData> getFunction() { return FUNCTION; } @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 /** * Tests the first order adjoint derivatives for the SABR Hagan volatility function. The * derivatives with respect to the forward, strike, alpha, beta, rho and nu are provided. */ public void testVolatilityAdjointDebug() { double eps = 1e-6; double tol = 1e-5; testVolatilityAdjoint(F, CALL_ATM, DATA, eps, tol); testVolatilityAdjoint(F, CALL_ITM, DATA, eps, tol); testVolatilityAdjoint(F, CALL_OTM, DATA, eps, tol); } /** * Test small strike edge case. Vol -> infinity as strike -> 0, so the strike is floored - tested * against finite difference below this floor will give spurious results */ @Test public void testVolatilityAdjointSmallStrike() { double eps = 1e-10; double tol = 1e-6; double strike = 2e-6 * F; testVolatilityAdjoint(F, withStrike(CALL_ATM, strike), DATA, eps, tol); } /** * 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); } @Test public void testVolatilityAdjointSmallAlpha() { double eps = 1e-7; double tol = 1e-3; SabrFormulaData data = DATA.withAlpha(1e-5); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); } /** Test the beta = 0 edge case */ @Test public void testVolatilityAdjointBeta0() { double eps = 1e-5; double tol = 1e-6; SabrFormulaData data = DATA.withBeta(0.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); } /** Test the beta = 1 edge case */ @Test public void testVolatilityAdjointBeta1() { double eps = 1e-6; double tol = 1e-6; SabrFormulaData data = DATA.withBeta(1.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); } /** Test the nu = 0 edge case */ @Test public void testVolatilityAdjointNu0() { double eps = 1e-5; double tol = 1e-6; SabrFormulaData data = DATA.withNu(0.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, 2e-4); testVolatilityAdjoint(F, CALL_OTM, data, eps, 5e-5); } /** Test the rho = -1 edge case */ @Test public void testVolatilityAdjointRhoM1() { double eps = 1e-5; double tol = 1e-6; SabrFormulaData data = DATA.withRho(-1.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); } /** Test the rho = 1 edge case */ @Test public void testVolatilityAdjointRho1() { double eps = 1e-4; double tol = 1e-5; SabrFormulaData data = DATA.withRho(1.0); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, tol); } @Test public void testVolatilityAdjointLargeRhoZLessThan1() { double eps = 1e-4; double tol = 1e-5; SabrFormulaData data = DATA.withRho(1.0 - 1e-9); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); } @Test public void testVolatilityAdjointLargeRhoZGreaterThan1() { double eps = 1e-11; double tol = 1e-4; SabrFormulaData data = DATA.withRho(1.0 - 1e-9).withAlpha(0.15 * ALPHA); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); } @Test /** * Tests the second order adjoint derivatives for the SABR Hagan volatility function. Only the * derivatives with respect to the forward and the strike are provided. */ public void volatilityAdjoint2() { volatilityAdjoint2ForInstrument(CALL_ITM, 1.0E-6, 1.0E-2); volatilityAdjoint2ForInstrument( CALL_ATM, 1.0E-6, 1.0E+2); // ATM the second order derivative is poor. volatilityAdjoint2ForInstrument(CALL_OTM, 1.0E-6, 1.0E-2); } // TODO write a fuzzer that hits SABR with random parameters @Test(enabled = false) public void testRandomParameters() { double eps = 1e-5; double tol = 1e-3; for (int count = 0; count < 100; count++) { double alpha = Math.exp(NORMAL.nextRandom() * 0.2 - 2); double beta = Math.random(); // TODO Uniform numbers in distribution double nu = Math.exp(NORMAL.nextRandom() * 0.3 - 1); double rho = 2 * Math.random() - 1; SabrFormulaData data = SabrFormulaData.of(alpha, beta, rho, nu); testVolatilityAdjoint(F, CALL_ATM, data, eps, tol); testVolatilityAdjoint(F, CALL_ITM, data, eps, tol); testVolatilityAdjoint(F, CALL_OTM, data, eps, 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); } /** * 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); } } public void coverage() { coverImmutableBean(FUNCTION); } public void test_serialization() { assertSerialization(FUNCTION); } // ------------------------------------------------------------------------- private enum SabrParameter { Forward, Strike, Alpha, Beta, Nu, Rho } 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); } private double[] toArray(ValueDerivatives valueDerivatives) { double[] derivatives = valueDerivatives.getDerivatives().toArray(); double[] res = new double[derivatives.length + 1]; res[0] = valueDerivatives.getValue(); System.arraycopy(derivatives, 0, res, 1, derivatives.length); return res; } 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); } private void assertEqualsRelTol(String msg, double exp, double act, double tol) { double delta = (Math.abs(exp) + Math.abs(act)) * tol / 2.0; assertEquals(act, exp, delta, msg); } @SuppressWarnings("null") private double fdSensitivity( EuropeanVanillaOption optionData, double forward, SabrFormulaData sabrData, SabrParameter param, double delta) { Function<SabrFormulaData, Double> funcC = null; Function<SabrFormulaData, Double> funcB = null; Function<SabrFormulaData, Double> funcA = null; SabrFormulaData dataC = null; SabrFormulaData dataB = sabrData; SabrFormulaData dataA = null; Function<SabrFormulaData, Double> func = getVolatilityFunction(optionData, forward); FiniteDifferenceType fdType = null; switch (param) { case Strike: double strike = optionData.getStrike(); if (strike >= delta) { fdType = FiniteDifferenceType.CENTRAL; funcA = getVolatilityFunction(withStrike(optionData, strike - delta), forward); funcC = getVolatilityFunction(withStrike(optionData, strike + delta), forward); } else { fdType = FiniteDifferenceType.FORWARD; funcA = func; funcB = getVolatilityFunction(withStrike(optionData, strike + delta), forward); funcC = getVolatilityFunction(withStrike(optionData, strike + 2 * delta), forward); } dataC = sabrData; dataB = sabrData; dataA = sabrData; break; case Forward: if (forward > delta) { fdType = FiniteDifferenceType.CENTRAL; funcA = getVolatilityFunction(optionData, forward - delta); funcC = getVolatilityFunction(optionData, forward + delta); } else { fdType = FiniteDifferenceType.FORWARD; funcA = func; funcB = getVolatilityFunction(optionData, forward + delta); funcC = getVolatilityFunction(optionData, forward + 2 * delta); } dataC = sabrData; dataB = sabrData; dataA = sabrData; break; case Alpha: double a = sabrData.getAlpha(); if (a >= delta) { fdType = FiniteDifferenceType.CENTRAL; dataA = sabrData.withAlpha(a - delta); dataC = sabrData.withAlpha(a + delta); } else { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withAlpha(a + delta); dataC = sabrData.withAlpha(a + 2 * delta); } funcC = func; funcB = func; funcA = func; break; case Beta: double b = sabrData.getBeta(); if (b >= delta) { fdType = FiniteDifferenceType.CENTRAL; dataA = sabrData.withBeta(b - delta); dataC = sabrData.withBeta(b + delta); } else { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withBeta(b + delta); dataC = sabrData.withBeta(b + 2 * delta); } funcC = func; funcB = func; funcA = func; break; case Nu: double n = sabrData.getNu(); if (n >= delta) { fdType = FiniteDifferenceType.CENTRAL; dataA = sabrData.withNu(n - delta); dataC = sabrData.withNu(n + delta); } else { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withNu(n + delta); dataC = sabrData.withNu(n + 2 * delta); } funcC = func; funcB = func; funcA = func; break; case Rho: double r = sabrData.getRho(); if ((r + 1) < delta) { fdType = FiniteDifferenceType.FORWARD; dataA = sabrData; dataB = sabrData.withRho(r + delta); dataC = sabrData.withRho(r + 2 * delta); } else if ((1 - r) < delta) { fdType = FiniteDifferenceType.BACKWARD; dataA = sabrData.withRho(r - 2 * delta); dataB = sabrData.withRho(r - delta); dataC = sabrData; } else { fdType = FiniteDifferenceType.CENTRAL; dataC = sabrData.withRho(r + delta); dataA = sabrData.withRho(r - delta); } funcC = func; funcB = func; funcA = func; break; default: throw new MathException("enum not found"); } if (fdType != null) { switch (fdType) { case FORWARD: return (-1.5 * funcA.apply(dataA) + 2.0 * funcB.apply(dataB) - 0.5 * funcC.apply(dataC)) / delta; case BACKWARD: return (0.5 * funcA.apply(dataA) - 2.0 * funcB.apply(dataB) + 1.5 * funcC.apply(dataC)) / delta; case CENTRAL: return (funcC.apply(dataC) - funcA.apply(dataA)) / 2.0 / delta; default: throw new MathException("enum not found"); } } throw new MathException("enum not found"); } private Function<SabrFormulaData, Double> getVolatilityFunction( EuropeanVanillaOption option, double forward) { return new Function<SabrFormulaData, Double>() { @Override public Double apply(SabrFormulaData data) { ArgChecker.notNull(data, "data"); return FUNCTION.getVolatility(forward, option.getStrike(), option.getTimeToExpiry(), data); } }; } private EuropeanVanillaOption withStrike(EuropeanVanillaOption option, double strike) { return EuropeanVanillaOption.of(strike, option.getTimeToExpiry(), option.getPutCall()); } }
/** * 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); } }
/** * Computes the first and second order derivatives of the Black implied volatility in the SABR * model. * * <p>The first derivative values will be stored in the input array {@code volatilityD} The array * contains, [0] Derivative w.r.t the forward, [1] the derivative w.r.t the strike, [2] the * derivative w.r.t. to alpha, [3] the derivative w.r.t. to beta, [4] the derivative w.r.t. to * rho, and [5] the derivative w.r.t. to nu. Thus the length of the array should be 6. * * <p>The second derivative values will be stored in the input array {@code volatilityD2}. Only * the second order derivative with respect to the forward and strike are implemented. The array * contains [0][0] forward-forward; [0][1] forward-strike; [1][1] strike-strike. Thus the size * should be 2 x 2. * * <p>Around ATM, a first order expansion is used to due to some 0/0-type indetermination. The * second order derivative produced is poor around ATM. * * @param forward the forward value of the underlying * @param strike the strike value of the option * @param timeToExpiry the time to expiry of the option * @param data the SABR data. * @param volatilityD the array used to return the first order derivative * @param volatilityD2 the array of array used to return the second order derivative * @return the Black implied volatility */ @Override public double volatilityAdjoint2( double forward, double strike, double timeToExpiry, SabrFormulaData data, double[] volatilityD, double[][] volatilityD2) { double k = Math.max(strike, 0.000001); double alpha = data.getAlpha(); double beta = data.getBeta(); double rho = data.getRho(); double nu = data.getNu(); // Forward double h0 = (1 - beta) / 2; double h1 = forward * k; double h1h0 = Math.pow(h1, h0); double h12 = h1h0 * h1h0; double h2 = Math.log(forward / k); double h22 = h2 * h2; double h23 = h22 * h2; double h24 = h23 * h2; double f1 = h1h0 * (1 + h0 * h0 / 6.0 * (h22 + h0 * h0 / 20.0 * h24)); double f2 = nu / alpha * h1h0 * h2; double f3 = h0 * h0 / 6.0 * alpha * alpha / h12 + rho * beta * nu * alpha / 4.0 / h1h0 + (2 - 3 * rho * rho) / 24.0 * nu * nu; double sqrtf2 = Math.sqrt(1 - 2 * rho * f2 + f2 * f2); double f2x = 0.0; double x = 0.0, xp = 0, xpp = 0; if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) { f2x = 1.0 - 0.5 * f2 * rho; // small f2 expansion to f2^2 terms } else { if (DoubleMath.fuzzyEquals(rho, 1.0, RHO_EPS)) { x = f2 < 1.0 ? -Math.log(1.0 - f2) - 0.5 * Math.pow(f2 / (f2 - 1.0), 2) * (1.0 - rho) : Math.log(2.0 * f2 - 2.0) - Math.log(1.0 - rho); } else { x = Math.log((sqrtf2 + f2 - rho) / (1 - rho)); } xp = 1. / sqrtf2; xpp = (rho - f2) / Math.pow(sqrtf2, 3.0); f2x = f2 / x; } double sigma = Math.max(MIN_VOL, alpha / f1 * f2x * (1 + f3 * timeToExpiry)); // First level double h0Dbeta = -0.5; double sigmaDf1 = -sigma / f1; double sigmaDf2 = 0; if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) { sigmaDf2 = alpha / f1 * (1 + f3 * timeToExpiry) * -0.5 * rho; } else { sigmaDf2 = alpha / f1 * (1 + f3 * timeToExpiry) * (1.0 / x - f2 * xp / (x * x)); } double sigmaDf3 = alpha / f1 * f2x * timeToExpiry; double sigmaDf4 = f2x / f1 * (1 + f3 * timeToExpiry); double sigmaDx = -alpha / f1 * f2 / (x * x) * (1 + f3 * timeToExpiry); double[][] sigmaD2ff = new double[3][3]; sigmaD2ff[0][0] = -sigmaDf1 / f1 + sigma / (f1 * f1); // OK sigmaD2ff[0][1] = -sigmaDf2 / f1; sigmaD2ff[0][2] = -sigmaDf3 / f1; if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) { sigmaD2ff[1][2] = alpha / f1 * -0.5 * rho * timeToExpiry; } else { sigmaD2ff[1][1] = alpha / f1 * (1 + f3 * timeToExpiry) * (-2 * xp / (x * x) - f2 * xpp / (x * x) + 2 * f2 * xp * xp / (x * x * x)); sigmaD2ff[1][2] = alpha / f1 * timeToExpiry * (1.0 / x - f2 * xp / (x * x)); } sigmaD2ff[2][2] = 0.0; // double sigma = alpha / f1 * f2x * (1 + f3 * theta); // Second level double[] f1Dh = new double[3]; double[] f2Dh = new double[3]; double[] f3Dh = new double[3]; f1Dh[0] = h1h0 * (h0 * (h22 / 3.0 + h0 * h0 / 40.0 * h24)) + Math.log(h1) * f1; f1Dh[1] = h0 * f1 / h1; f1Dh[2] = h1h0 * (h0 * h0 / 6.0 * (2.0 * h2 + h0 * h0 / 5.0 * h23)); f2Dh[0] = Math.log(h1) * f2; f2Dh[1] = h0 * f2 / h1; f2Dh[2] = nu / alpha * h1h0; f3Dh[0] = h0 / 3.0 * alpha * alpha / h12 - 2 * h0 * h0 / 6.0 * alpha * alpha / h12 * Math.log(h1) - rho * beta * nu * alpha / 4.0 / h1h0 * Math.log(h1); f3Dh[1] = -2 * h0 * h0 / 6.0 * alpha * alpha / h12 * h0 / h1 - rho * beta * nu * alpha / 4.0 / h1h0 * h0 / h1; f3Dh[2] = 0.0; double[] f1Dp = new double[4]; // Derivative to sabr parameters double[] f2Dp = new double[4]; double[] f3Dp = new double[4]; double[] f4Dp = new double[4]; f1Dp[0] = 0.0; f1Dp[1] = f1Dh[0] * h0Dbeta; f1Dp[2] = 0.0; f1Dp[3] = 0.0; f2Dp[0] = -f2 / alpha; f2Dp[1] = f2Dh[0] * h0Dbeta; f2Dp[2] = 0.0; f2Dp[3] = h1h0 * h2 / alpha; f3Dp[0] = h0 * h0 / 3.0 * alpha / h12 + rho * beta * nu / 4.0 / h1h0; f3Dp[1] = rho * nu * alpha / 4.0 / h1h0 + f3Dh[0] * h0Dbeta; f3Dp[2] = beta * nu * alpha / 4.0 / h1h0 - rho / 4.0 * nu * nu; f3Dp[3] = rho * beta * alpha / 4.0 / h1h0 + (2 - 3 * rho * rho) / 12.0 * nu; f4Dp[0] = 1.0; f4Dp[1] = 0.0; f4Dp[2] = 0.0; f4Dp[3] = 0.0; double sigmaDh1 = sigmaDf1 * f1Dh[1] + sigmaDf2 * f2Dh[1] + sigmaDf3 * f3Dh[1]; double sigmaDh2 = sigmaDf1 * f1Dh[2] + sigmaDf2 * f2Dh[2] + sigmaDf3 * f3Dh[2]; double[][] f1D2hh = new double[2][2]; // No h0 double[][] f2D2hh = new double[2][2]; double[][] f3D2hh = new double[2][2]; f1D2hh[0][0] = h0 * (h0 - 1) * f1 / (h1 * h1); f1D2hh[0][1] = h0 * h1h0 / h1 * h0 * h0 / 6.0 * (2.0 * h2 + 4.0 * h0 * h0 / 20.0 * h23); f1D2hh[1][1] = h1h0 * (h0 * h0 / 6.0 * (2.0 + 12.0 * h0 * h0 / 20.0 * h2)); f2D2hh[0][0] = h0 * (h0 - 1) * f2 / (h1 * h1); f2D2hh[0][1] = nu / alpha * h0 * h1h0 / h1; f2D2hh[1][1] = 0.0; f3D2hh[0][0] = 2 * h0 * (2 * h0 + 1) * h0 * h0 / 6.0 * alpha * alpha / (h12 * h1 * h1) + h0 * (h0 + 1) * rho * beta * nu * alpha / 4.0 / (h1h0 * h1 * h1); f3D2hh[0][1] = 0.0; f3D2hh[1][1] = 0.0; double[][] sigmaD2hh = new double[2][2]; // No h0 for (int loopx = 0; loopx < 2; loopx++) { for (int loopy = loopx; loopy < 2; loopy++) { sigmaD2hh[loopx][loopy] = (sigmaD2ff[0][0] * f1Dh[loopy + 1] + sigmaD2ff[0][1] * f2Dh[loopy + 1] + sigmaD2ff[0][2] * f3Dh[loopy + 1]) * f1Dh[loopx + 1] + sigmaDf1 * f1D2hh[loopx][loopy] + (sigmaD2ff[0][1] * f1Dh[loopy + 1] + sigmaD2ff[1][1] * f2Dh[loopy + 1] + sigmaD2ff[1][2] * f3Dh[loopy + 1]) * f2Dh[loopx + 1] + sigmaDf2 * f2D2hh[loopx][loopy] + (sigmaD2ff[0][2] * f1Dh[loopy + 1] + sigmaD2ff[1][2] * f2Dh[loopy + 1] + sigmaD2ff[2][2] * f3Dh[loopy + 1]) * f3Dh[loopx + 1] + sigmaDf3 * f3D2hh[loopx][loopy]; } } // Third level double h1Df = k; double h1Dk = forward; double h1D2ff = 0.0; double h1D2kf = 1.0; double h1D2kk = 0.0; double h2Df = 1.0 / forward; double h2Dk = -1.0 / k; double h2D2ff = -1 / (forward * forward); double h2D2fk = 0.0; double h2D2kk = 1.0 / (k * k); volatilityD[0] = sigmaDh1 * h1Df + sigmaDh2 * h2Df; volatilityD[1] = sigmaDh1 * h1Dk + sigmaDh2 * h2Dk; volatilityD[2] = sigmaDf1 * f1Dp[0] + sigmaDf2 * f2Dp[0] + sigmaDf3 * f3Dp[0] + sigmaDf4 * f4Dp[0]; volatilityD[3] = sigmaDf1 * f1Dp[1] + sigmaDf2 * f2Dp[1] + sigmaDf3 * f3Dp[1] + sigmaDf4 * f4Dp[1]; if (DoubleMath.fuzzyEquals(f2, 0.0, SMALL_Z)) { volatilityD[4] = -0.5 * f2 + sigmaDf3 * f3Dp[2]; } else { double xDr; if (DoubleMath.fuzzyEquals(rho, 1.0, RHO_EPS)) { xDr = f2 > 1.0 ? 1.0 / (1.0 - rho) + (0.5 - f2) / (f2 - 1.0) / (f2 - 1.0) : 0.5 * Math.pow(f2 / (1.0 - f2), 2.0) + 0.25 * (f2 - 4.0) * Math.pow(f2 / (f2 - 1.0), 3) / (f2 - 1.0) * (1.0 - rho); if (Doubles.isFinite(xDr)) { volatilityD[4] = sigmaDf1 * f1Dp[2] + sigmaDx * xDr + sigmaDf3 * f3Dp[2] + sigmaDf4 * f4Dp[2]; } else { volatilityD[4] = Double.NEGATIVE_INFINITY; } } else { xDr = (-f2 / sqrtf2 - 1 + (sqrtf2 + f2 - rho) / (1 - rho)) / (sqrtf2 + f2 - rho); volatilityD[4] = sigmaDf1 * f1Dp[2] + sigmaDx * xDr + sigmaDf3 * f3Dp[2] + sigmaDf4 * f4Dp[2]; } } volatilityD[5] = sigmaDf1 * f1Dp[3] + sigmaDf2 * f2Dp[3] + sigmaDf3 * f3Dp[3] + sigmaDf4 * f4Dp[3]; volatilityD2[0][0] = (sigmaD2hh[0][0] * h1Df + sigmaD2hh[0][1] * h2Df) * h1Df + sigmaDh1 * h1D2ff + (sigmaD2hh[0][1] * h1Df + sigmaD2hh[1][1] * h2Df) * h2Df + sigmaDh2 * h2D2ff; volatilityD2[0][1] = (sigmaD2hh[0][0] * h1Dk + sigmaD2hh[0][1] * h2Dk) * h1Df + sigmaDh1 * h1D2kf + (sigmaD2hh[0][1] * h1Dk + sigmaD2hh[1][1] * h2Dk) * h2Df + sigmaDh2 * h2D2fk; volatilityD2[1][0] = volatilityD2[0][1]; volatilityD2[1][1] = (sigmaD2hh[0][0] * h1Dk + sigmaD2hh[0][1] * h2Dk) * h1Dk + sigmaDh1 * h1D2kk + (sigmaD2hh[0][1] * h1Dk + sigmaD2hh[1][1] * h2Dk) * h2Dk + sigmaDh2 * h2D2kk; return sigma; }