/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.impl.option; import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg; import static com.opengamma.strata.product.common.PutCall.CALL; import static com.opengamma.strata.product.common.PutCall.PUT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import org.testng.annotations.Test; import com.opengamma.strata.basics.value.ValueDerivatives; /** * Test {@link NormalPriceFunction}, */ @Test public class NormalPriceFunctionTest { private static final double T = 4.5; private static final double F = 104; private static final double DELTA = 10; private static final EuropeanVanillaOption ITM_CALL = EuropeanVanillaOption.of(F - DELTA, T, CALL); private static final EuropeanVanillaOption OTM_CALL = EuropeanVanillaOption.of(F + DELTA, T, CALL); private static final EuropeanVanillaOption ITM_PUT = EuropeanVanillaOption.of(F + DELTA, T, PUT); private static final EuropeanVanillaOption OTM_PUT = EuropeanVanillaOption.of(F - DELTA, T, PUT); private static final double DF = 0.9; private static final double SIGMA = 20.0; private static final NormalFunctionData VOL_DATA = NormalFunctionData.of(F, DF, SIGMA); private static final NormalFunctionData ZERO_VOL_DATA = NormalFunctionData.of(F, DF, 0); private static final NormalPriceFunction FUNCTION = new NormalPriceFunction(); public void testInvalid() { assertThrowsIllegalArg(() -> FUNCTION.getPriceFunction(null)); assertThrowsIllegalArg(() -> FUNCTION.getPriceFunction(ITM_CALL).apply((NormalFunctionData) null)); } public void testZeroVolPrice() { assertEquals(FUNCTION.getPriceFunction(ITM_CALL).apply(ZERO_VOL_DATA), DF * DELTA, 1e-15); assertEquals(FUNCTION.getPriceFunction(OTM_CALL).apply(ZERO_VOL_DATA), 0, 1e-15); assertEquals(FUNCTION.getPriceFunction(ITM_PUT).apply(ZERO_VOL_DATA), DF * DELTA, 1e-15); assertEquals(FUNCTION.getPriceFunction(OTM_PUT).apply(ZERO_VOL_DATA), 0, 1e-15); } public void testPriceAdjoint() { // Price double price = FUNCTION.getPriceFunction(ITM_CALL).apply(VOL_DATA); ValueDerivatives priceAdjoint = FUNCTION.getPriceAdjoint(ITM_CALL, VOL_DATA); assertEquals(priceAdjoint.getValue(), price, 1E-10); // Price with 0 volatility double price0 = FUNCTION.getPriceFunction(ITM_CALL).apply(ZERO_VOL_DATA); ValueDerivatives price0Adjoint = FUNCTION.getPriceAdjoint(ITM_CALL, ZERO_VOL_DATA); assertEquals(price0Adjoint.getValue(), price0, 1E-10); // Derivative forward. double deltaF = 0.01; NormalFunctionData dataFP = NormalFunctionData.of(F + deltaF, DF, SIGMA); NormalFunctionData dataFM = NormalFunctionData.of(F - deltaF, DF, SIGMA); double priceFP = FUNCTION.getPriceFunction(ITM_CALL).apply(dataFP); double priceFM = FUNCTION.getPriceFunction(ITM_CALL).apply(dataFM); double derivativeF_FD = (priceFP - priceFM) / (2 * deltaF); assertEquals(priceAdjoint.getDerivative(0), derivativeF_FD, 1E-7); // Derivative strike. double deltaK = 0.01; EuropeanVanillaOption optionKP = EuropeanVanillaOption.of(F - DELTA + deltaK, T, CALL); EuropeanVanillaOption optionKM = EuropeanVanillaOption.of(F - DELTA - deltaK, T, CALL); double priceKP = FUNCTION.getPriceFunction(optionKP).apply(VOL_DATA); double priceKM = FUNCTION.getPriceFunction(optionKM).apply(VOL_DATA); double derivativeK_FD = (priceKP - priceKM) / (2 * deltaK); assertEquals(priceAdjoint.getDerivative(2), derivativeK_FD, 1E-7); // Derivative volatility. double deltaV = 0.0001; NormalFunctionData dataVP = NormalFunctionData.of(F, DF, SIGMA + deltaV); NormalFunctionData dataVM = NormalFunctionData.of(F, DF, SIGMA - deltaV); double priceVP = FUNCTION.getPriceFunction(ITM_CALL).apply(dataVP); double priceVM = FUNCTION.getPriceFunction(ITM_CALL).apply(dataVM); double derivativeV_FD = (priceVP - priceVM) / (2 * deltaV); assertEquals(priceAdjoint.getDerivative(1), derivativeV_FD, 1E-6); } private static final EuropeanVanillaOption ATM_CALL = EuropeanVanillaOption.of(F, T, CALL); private static final EuropeanVanillaOption ATM_PUT = EuropeanVanillaOption.of(F, T, PUT); // Test getDelta, getGamma and getVega public void greeksTest() { double tol = 1.0e-12; double eps = 1.0e-5; EuropeanVanillaOption[] options = new EuropeanVanillaOption[] { ITM_CALL, ITM_PUT, OTM_CALL, OTM_PUT, ATM_CALL, ATM_PUT}; for (EuropeanVanillaOption option : options) { // consistency with getPriceFunction for first order derivatives ValueDerivatives price = FUNCTION.getPriceAdjoint(option, VOL_DATA); double delta = FUNCTION.getDelta(option, VOL_DATA); double vega = FUNCTION.getVega(option, VOL_DATA); assertEquals(price.getDerivative(0), delta, tol); assertEquals(price.getDerivative(1), vega, tol); // testing second order derivative against finite difference approximation NormalFunctionData dataUp = NormalFunctionData.of(F + eps, DF, SIGMA); NormalFunctionData dataDw = NormalFunctionData.of(F - eps, DF, SIGMA); double deltaUp = FUNCTION.getDelta(option, dataUp); double deltaDw = FUNCTION.getDelta(option, dataDw); double ref = 0.5 * (deltaUp - deltaDw) / eps; double gamma = FUNCTION.getGamma(option, VOL_DATA); assertEquals(gamma, ref, eps); EuropeanVanillaOption optionUp = EuropeanVanillaOption.of(option.getStrike(), T + eps, option.getPutCall()); EuropeanVanillaOption optionDw = EuropeanVanillaOption.of(option.getStrike(), T - eps, option.getPutCall()); double priceTimeUp = FUNCTION.getPriceFunction(optionUp).apply(VOL_DATA); double priceTimeDw = FUNCTION.getPriceFunction(optionDw).apply(VOL_DATA); ref = -0.5 * (priceTimeUp - priceTimeDw) / eps; double theta = FUNCTION.getTheta(option, VOL_DATA); assertEquals(theta, ref, eps); } } // Testing the branch for sigmaRootT < 1e-16 public void smallParameterGreeksTest() { double eps = 1.0e-5; NormalFunctionData dataVolUp = NormalFunctionData.of(F, DF, eps); NormalFunctionData dataFwUp = NormalFunctionData.of(F + eps, DF, 0.0); NormalFunctionData dataFwDw = NormalFunctionData.of(F - eps, DF, 0.0); EuropeanVanillaOption[] options = new EuropeanVanillaOption[] { ITM_CALL, ITM_PUT, OTM_CALL, OTM_PUT, ATM_CALL, ATM_PUT}; for (EuropeanVanillaOption option : options) { double delta = FUNCTION.getDelta(option, ZERO_VOL_DATA); double priceUp = FUNCTION.getPriceFunction(option).apply(dataFwUp); double priceDw = FUNCTION.getPriceFunction(option).apply(dataFwDw); double refDelta = 0.5 * (priceUp - priceDw) / eps; assertEquals(delta, refDelta, eps); double vega = FUNCTION.getVega(option, ZERO_VOL_DATA); double priceVolUp = FUNCTION.getPriceFunction(option).apply(dataVolUp); double price = FUNCTION.getPriceFunction(option).apply(ZERO_VOL_DATA); double refVega = (priceVolUp - price) / eps; assertEquals(vega, refVega, eps); double gamma = FUNCTION.getGamma(option, ZERO_VOL_DATA); double deltaUp = FUNCTION.getDelta(option, dataFwUp); double deltaDw = FUNCTION.getDelta(option, dataFwDw); double refGamma = 0.5 * (deltaUp - deltaDw) / eps; if (Math.abs(refGamma) > 0.1 / eps) { // infinity handled assertTrue(Double.isInfinite(gamma)); } else { assertEquals(gamma, refGamma, eps); } EuropeanVanillaOption optionUp = EuropeanVanillaOption.of(option.getStrike(), T + eps, option.getPutCall()); EuropeanVanillaOption optionDw = EuropeanVanillaOption.of(option.getStrike(), T - eps, option.getPutCall()); double priceTimeUp = FUNCTION.getPriceFunction(optionUp).apply(ZERO_VOL_DATA); double priceTimeDw = FUNCTION.getPriceFunction(optionDw).apply(ZERO_VOL_DATA); double refTheta = -0.5 * (priceTimeUp - priceTimeDw) / eps; double theta = FUNCTION.getTheta(option, ZERO_VOL_DATA); assertEquals(theta, refTheta, eps); } } }