/** * Copyright (C) 2009 - 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 org.testng.annotations.Test; import com.opengamma.analytics.math.statistics.distribution.NormalDistribution; import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution; import com.opengamma.util.test.TestGroup; /** * Test. */ @Test(groups = TestGroup.UNIT) public class BlackPriceFunctionTest { private static final double T = 4.5; private static final double F = 104; private static final double DELTA = 10; private static final EuropeanVanillaOption ATM_CALL = new EuropeanVanillaOption(F, T, true); private static final EuropeanVanillaOption ITM_CALL = new EuropeanVanillaOption(F - DELTA, T, true); private static final EuropeanVanillaOption OTM_CALL = new EuropeanVanillaOption(F + DELTA, T, true); private static final EuropeanVanillaOption CALL_0 = new EuropeanVanillaOption(0.0, T, true); private static final EuropeanVanillaOption ITM_PUT = new EuropeanVanillaOption(F + DELTA, T, false); private static final EuropeanVanillaOption OTM_PUT = new EuropeanVanillaOption(F - DELTA, T, false); private static final double DF = 0.9; private static final double SIGMA = 0.5; private static final BlackFunctionData ATM_DATA = new BlackFunctionData(F, DF, SIGMA); private static final BlackFunctionData ZERO_VOL_DATA = new BlackFunctionData(F, DF, 0); private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1); private static final BlackPriceFunction FUNCTION = new BlackPriceFunction(); @Test(expectedExceptions = IllegalArgumentException.class) public void testNullOption1() { FUNCTION.getPriceFunction(null); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNullOption2() { FUNCTION.getPriceFunction(null); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNullData1() { FUNCTION.getPriceFunction(ATM_CALL).evaluate((BlackFunctionData) null); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNullData2() { FUNCTION.getVegaFunction(ATM_CALL).evaluate((BlackFunctionData) null); } @Test public void testATMPrice() { final double sigmaRootT = ATM_DATA.getBlackVolatility() * Math.sqrt(ATM_CALL.getTimeToExpiry()); assertEquals(DF * F * (2 * NORMAL.getCDF(sigmaRootT / 2) - 1), FUNCTION.getPriceFunction(ATM_CALL).evaluate(ATM_DATA), 1e-14); } @Test public void testZeroVolPrice() { assertEquals(DF * DELTA, FUNCTION.getPriceFunction(ITM_CALL).evaluate(ZERO_VOL_DATA), 1e-15); assertEquals(0, FUNCTION.getPriceFunction(OTM_CALL).evaluate(ZERO_VOL_DATA), 1e-15); assertEquals(DF * DELTA, FUNCTION.getPriceFunction(ITM_PUT).evaluate(ZERO_VOL_DATA), 1e-15); assertEquals(0, FUNCTION.getPriceFunction(OTM_PUT).evaluate(ZERO_VOL_DATA), 1e-15); } @Test public void priceAdjoint() { // Price double price = FUNCTION.getPriceFunction(ITM_CALL).evaluate(ATM_DATA); double[] priceAdjoint = FUNCTION.getPriceAdjoint(ITM_CALL, ATM_DATA); assertEquals(price, priceAdjoint[0], 1E-10); // Price with 0 volatility double price0 = FUNCTION.getPriceFunction(ITM_CALL).evaluate(ZERO_VOL_DATA); double[] price0Adjoint = FUNCTION.getPriceAdjoint(ITM_CALL, ZERO_VOL_DATA); assertEquals(price0, price0Adjoint[0], 1E-10); // Derivative forward. double deltaF = 0.01; BlackFunctionData dataFP = new BlackFunctionData(F + deltaF, DF, SIGMA); BlackFunctionData dataFM = new BlackFunctionData(F - deltaF, DF, SIGMA); double priceFP = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataFP); double priceFM = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataFM); double derivativeF_FD = (priceFP - priceFM) / (2 * deltaF); assertEquals(derivativeF_FD, priceAdjoint[1], 1E-7); // Derivative strike. double deltaK = 0.01; EuropeanVanillaOption optionKP = new EuropeanVanillaOption(F - DELTA + deltaK, T, true); EuropeanVanillaOption optionKM = new EuropeanVanillaOption(F - DELTA - deltaK, T, true); double priceKP = FUNCTION.getPriceFunction(optionKP).evaluate(ATM_DATA); double priceKM = FUNCTION.getPriceFunction(optionKM).evaluate(ATM_DATA); double derivativeK_FD = (priceKP - priceKM) / (2 * deltaK); assertEquals(derivativeK_FD, priceAdjoint[3], 1E-7); // Derivative volatility. double deltaV = 0.0001; BlackFunctionData dataVP = new BlackFunctionData(F, DF, SIGMA + deltaV); BlackFunctionData dataVM = new BlackFunctionData(F, DF, SIGMA - deltaV); double priceVP = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataVP); double priceVM = FUNCTION.getPriceFunction(ITM_CALL).evaluate(dataVM); double derivativeV_FD = (priceVP - priceVM) / (2 * deltaV); assertEquals(derivativeV_FD, priceAdjoint[2], 1E-6); } @Test(enabled = false) /** * Tests the numerical stability of a finite difference approach to derivativ computation. */ public void priceADStability() { final double forward = 1.0; final double df = 1.0; // 0 rate final double sigma = 0.20; final BlackFunctionData dataBlack = new BlackFunctionData(forward, df, sigma); final double expiration = 7.0d / 365.0d; // 1 week final EuropeanVanillaOption atmCall = new EuropeanVanillaOption(forward, expiration, true); final double price = FUNCTION.getPriceFunction(atmCall).evaluate(dataBlack); double startingShift = 1.0E-4; double ratio = Math.sqrt(2.0); final int nbShift = 75; final double[] eps = new double[nbShift + 1]; final double[] priceAdjoint = FUNCTION.getPriceAdjoint(atmCall, dataBlack); final double[] derivativeF_FD = new double[nbShift]; final double[] diff = new double[nbShift]; eps[0] = startingShift; for (int loopshift = 0; loopshift < nbShift; loopshift++) { final BlackFunctionData dataBlackShifted = new BlackFunctionData(forward + eps[loopshift], df, sigma); final double priceShifted = FUNCTION.getPriceFunction(atmCall).evaluate(dataBlackShifted); derivativeF_FD[loopshift] = (priceShifted - price) / eps[loopshift]; diff[loopshift] = derivativeF_FD[loopshift] - priceAdjoint[1]; eps[loopshift + 1] = eps[loopshift] / ratio; } // int t = 0; // t++; } @Test public void testPriceAdjointStrike0() { // Price double price = FUNCTION.getPriceFunction(CALL_0).evaluate(ATM_DATA); double[] priceAdjoint = FUNCTION.getPriceAdjoint(CALL_0, ATM_DATA); assertEquals(price, priceAdjoint[0], 1E-10); // Derivative forward. double deltaF = 0.01; BlackFunctionData dataFP = new BlackFunctionData(F + deltaF, DF, SIGMA); BlackFunctionData dataFM = new BlackFunctionData(F - deltaF, DF, SIGMA); double priceFP = FUNCTION.getPriceFunction(CALL_0).evaluate(dataFP); double priceFM = FUNCTION.getPriceFunction(CALL_0).evaluate(dataFM); double derivativeF_FD = (priceFP - priceFM) / (2 * deltaF); assertEquals(derivativeF_FD, priceAdjoint[1], 1E-7); // Derivative strike. double deltaK = 0.01; EuropeanVanillaOption optionKP = new EuropeanVanillaOption(0.0 + deltaK, T, true); double priceKP = FUNCTION.getPriceFunction(optionKP).evaluate(ATM_DATA); double derivativeK_FD = (priceKP - price) / (deltaK); assertEquals(derivativeK_FD, priceAdjoint[3], 1E-7); // Derivative volatility. double deltaV = 0.0001; BlackFunctionData dataVP = new BlackFunctionData(F, DF, SIGMA + deltaV); BlackFunctionData dataVM = new BlackFunctionData(F, DF, SIGMA - deltaV); double priceVP = FUNCTION.getPriceFunction(CALL_0).evaluate(dataVP); double priceVM = FUNCTION.getPriceFunction(CALL_0).evaluate(dataVM); double derivativeV_FD = (priceVP - priceVM) / (2 * deltaV); assertEquals(derivativeV_FD, priceAdjoint[2], 1E-6); } private static final double TOLERANCE_1 = 1.0E-10; private static final double TOLERANCE_2_FWD_FWD = 1.0E-6; private static final double TOLERANCE_2_VOL_VOL = 1.0E-6; private static final double TOLERANCE_2_STR_STR = 1.0E-6; private static final double TOLERANCE_2_FWD_VOL = 1.0E-7; private static final double TOLERANCE_2_FWD_STR = 1.0E-6; private static final double TOLERANCE_2_STR_VOL = 1.0E-6; /** Tests second order Algorithmic Differentiation version of BlackFunction with several data sets. */ @Test public void testPriceAdjoint2() { // forward, numeraire, sigma, strike, time double[][] testData = { {104.0d, 0.9d, 0.50d, 94.0d, 4.5d}, {104.0d, 0.9d, 0.50d, 124.0d, 4.5d}, {104.0d, 0.9d, 0.50d, 104.0d, 4.5d}, {0.0250d, 1000.0d, 0.25d, 0.0150d, 10.0d}, {0.0250d, 1000.0d, 0.25d, 0.0400d, 10.0d}, {1700.0d, 0.9d, 1.00d, 1500.0d, 0.01d}, {1700.0d, 0.9d, 1.00d, 1900.0d, 20.0d} }; int nbTest = testData.length; for(int i=0; i<nbTest; i++) { testPriceAdjointSecondOrder(testData[i][0],testData[i][1],testData[i][2],testData[i][3],testData[i][4], true, i); testPriceAdjointSecondOrder(testData[i][0],testData[i][1],testData[i][2],testData[i][3],testData[i][4], false, i); } } private void testPriceAdjointSecondOrder(double forward, double numeraire, double sigma, double strike, double time, boolean isCall, int i) { EuropeanVanillaOption option = new EuropeanVanillaOption(strike, time, isCall); BlackFunctionData data = new BlackFunctionData(forward, numeraire, sigma); // Price double[] priceAdjoint = FUNCTION.getPriceAdjoint(option, data); double[] bsD = new double[3]; double[][] bsD2 = new double[3][3]; double bs = FUNCTION.getPriceAdjoint2(option, data, bsD, bsD2); assertEquals("AD Second order: price", priceAdjoint[0], bs, TOLERANCE_1); // First derivative for (int loopder = 0; loopder < 3; loopder++) { assertEquals("AD Second order: 1st", priceAdjoint[loopder + 1], bsD[loopder], TOLERANCE_1); } // Second derivative // Derivative forward-forward. double deltaF = 1.0E-3 * forward; BlackFunctionData dataFP = new BlackFunctionData(forward + deltaF, numeraire, sigma); BlackFunctionData dataFM = new BlackFunctionData(forward - deltaF, numeraire, sigma); double[] priceAdjointFP = FUNCTION.getPriceAdjoint(option, dataFP); double[] priceAdjointFM = FUNCTION.getPriceAdjoint(option, dataFM); double derivativeFF_FD = (priceAdjointFP[1] - priceAdjointFM[1]) / (2 * deltaF); assertEquals("AD Second order: 2nd - fwd-fwd " + i, derivativeFF_FD, bsD2[0][0], TOLERANCE_2_FWD_FWD * Math.abs(bs / (deltaF * deltaF))); // Derivative volatility-volatility. double deltaV = 0.00001; double deltaV2 = (deltaV * deltaV); BlackFunctionData dataVP = new BlackFunctionData(forward, numeraire, sigma + deltaV); BlackFunctionData dataVM = new BlackFunctionData(forward, numeraire, sigma - deltaV); double[] priceAdjointVP = FUNCTION.getPriceAdjoint(option, dataVP); double[] priceAdjointVM = FUNCTION.getPriceAdjoint(option, dataVM); double derivativeVV_FD = (priceAdjointVP[2] - priceAdjointVM[2]) / (2 * deltaV); assertEquals("AD Second order: 2nd - vol-vol " + i, derivativeVV_FD, bsD2[1][1], TOLERANCE_2_VOL_VOL * Math.abs(bs / deltaV2)); // Derivative forward-volatility. double derivativeFV_FD = (priceAdjointVP[1] - priceAdjointVM[1]) / (2 * deltaV); assertEquals("AD Second order: 2nd - fwd-vol " + i, derivativeFV_FD, bsD2[1][0], TOLERANCE_2_FWD_VOL * Math.abs(bs / (deltaF * deltaV))); assertEquals("AD Second order: 2nd - fwd-vol", bsD2[0][1], bsD2[1][0], TOLERANCE_1); // Derivative strike-strike. double deltaK = 1.0E-4 * strike; EuropeanVanillaOption optionKP = new EuropeanVanillaOption(strike + deltaK, time, isCall); EuropeanVanillaOption optionKM = new EuropeanVanillaOption(strike - deltaK, time, isCall); double[] priceAdjointKP = FUNCTION.getPriceAdjoint(optionKP, data); double[] priceAdjointKM = FUNCTION.getPriceAdjoint(optionKM, data); double derivativeKK_FD = (priceAdjointKP[3] - priceAdjointKM[3]) / (2 * deltaK); assertEquals("AD Second order: 2nd - strike-strike " + i, derivativeKK_FD, bsD2[2][2], TOLERANCE_2_STR_STR * Math.abs(derivativeKK_FD)); // Derivative forward-strike. double derivativeFK_FD = (priceAdjointKP[1] - priceAdjointKM[1]) / (2 * deltaK); assertEquals("AD Second order: 2nd - fwd-str " + i, derivativeFK_FD, bsD2[2][0], TOLERANCE_2_FWD_STR * Math.abs(bs / (deltaF * deltaK))); assertEquals("AD Second order: 2nd - fwd-str", bsD2[0][2], bsD2[2][0], TOLERANCE_1); // Derivative strike-volatility. double derivativeKV_FD = (priceAdjointVP[3] - priceAdjointVM[3]) / (2 * deltaV); assertEquals("AD Second order: 2nd - str-vol " + i, derivativeKV_FD, bsD2[2][1], TOLERANCE_2_STR_VOL * Math.abs(bs / (deltaV * deltaK))); assertEquals("AD Second order: 2nd - str-vol", bsD2[1][2], bsD2[2][1], TOLERANCE_1); } @Test(enabled = false) /** * Assess the performance of the adjoint implementation against a finite difference and a non-optimized adjoint implementation. */ public void performanceAdjoint() { // Used only to assess performance double[] bsD = new double[3]; double[][] bsD2 = new double[3][3]; @SuppressWarnings("unused") double bsP; @SuppressWarnings("unused") double[] bsPD = new double[4]; long startTime, endTime; int nbTest = 1000000; startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { bsP = FUNCTION.getPriceFunction(ITM_CALL).evaluate(ATM_DATA); } endTime = System.currentTimeMillis(); System.out.println(nbTest + " Black price : " + (endTime - startTime) + " ms"); startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { bsPD = FUNCTION.getPriceAdjoint(ITM_CALL, ATM_DATA); } endTime = System.currentTimeMillis(); System.out.println(nbTest + " Black price + first order adjoint: " + (endTime - startTime) + " ms"); startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { bsP = FUNCTION.getPriceAdjoint2(ITM_CALL, ATM_DATA, bsD, bsD2); } endTime = System.currentTimeMillis(); System.out.println(nbTest + " Black price + adjoint (first and second order): " + (endTime - startTime) + " ms"); // Performance note: price: 14-Jun-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 175 ms for 1000000. // Performance note: price+1st order derivatives: 14-Jun-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 195 ms for 1000000. // Performance note: price+1st and 2nd order derivatives: 14-Jun-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 250 ms for 1000000. } }