/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate.future.provider; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import java.util.Map.Entry; import org.testng.annotations.Test; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.instrument.future.BondFuturesDataSets; import com.opengamma.analytics.financial.instrument.future.BondFuturesOptionMarginSecurityDefinition; import com.opengamma.analytics.financial.instrument.future.BondFuturesSecurityDefinition; import com.opengamma.analytics.financial.interestrate.future.derivative.BondFuturesOptionMarginSecurity; import com.opengamma.analytics.financial.interestrate.sensitivity.PresentValueBlackBondFuturesCubeSensitivity; import com.opengamma.analytics.financial.legalentity.LegalEntity; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.BlackFunctionData; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.BlackPriceFunction; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.financial.provider.description.IssuerProviderDiscountDataSets; import com.opengamma.analytics.financial.provider.description.StandardDataSetsBlack; import com.opengamma.analytics.financial.provider.description.interestrate.BlackBondFuturesExpLogMoneynessProviderDiscount; import com.opengamma.analytics.financial.provider.description.interestrate.IssuerProviderDiscount; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory; import com.opengamma.analytics.math.interpolation.GridInterpolator2D; import com.opengamma.analytics.math.interpolation.Interpolator1D; import com.opengamma.analytics.math.interpolation.Interpolator1DFactory; import com.opengamma.analytics.math.surface.InterpolatedDoublesSurface; import com.opengamma.util.test.TestGroup; import com.opengamma.util.time.DateUtils; import com.opengamma.util.tuple.Triple; /** * Test. */ @Test(groups = TestGroup.UNIT) public class BondFuturesOptionMarginSecurityBlackExpLogMoneynessMethodTest { /** Bond future option: Bobl */ private static final BondFuturesSecurityDefinition BOBLM4_DEFINITION = BondFuturesDataSets.boblM4Definition(); private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2014, 3, 31); private static final double STRIKE_116 = 1.16; // To be close to ATM for the data set used. private static final ZonedDateTime EXPIRY_DATE_OPT = DateUtils.getUTCDate(2014, 6, 5); private static final ZonedDateTime LAST_TRADING_DATE_OPT = DateUtils.getUTCDate(2014, 6, 4); private static final boolean IS_CALL = true; private static final BondFuturesOptionMarginSecurityDefinition CALL_BOBL_116_DEFINITION = new BondFuturesOptionMarginSecurityDefinition(BOBLM4_DEFINITION, LAST_TRADING_DATE_OPT, EXPIRY_DATE_OPT, STRIKE_116, IS_CALL); private static final BondFuturesOptionMarginSecurityDefinition PUT_BOBL_116_DEFINITION = new BondFuturesOptionMarginSecurityDefinition(BOBLM4_DEFINITION, LAST_TRADING_DATE_OPT, EXPIRY_DATE_OPT, STRIKE_116, !IS_CALL); public static final BondFuturesOptionMarginSecurity CALL_BOBL_116 = CALL_BOBL_116_DEFINITION.toDerivative(REFERENCE_DATE); public static final BondFuturesOptionMarginSecurity PUT_BOBL_116 = PUT_BOBL_116_DEFINITION.toDerivative(REFERENCE_DATE); /** Black surface expiry/log-moneyness */ final private static InterpolatedDoublesSurface BLACK_SURFACE = StandardDataSetsBlack.blackSurfaceExpiryLogMoneyness(); /** Curves for a specific issuer name */ private static final IssuerProviderDiscount ISSUER_SPECIFIC_MULTICURVES = IssuerProviderDiscountDataSets.getIssuerSpecificProvider(); /** The legal entity */ private static final LegalEntity[] LEGAL_ENTITIES = IssuerProviderDiscountDataSets.getIssuers(); private static final LegalEntity LEGAL_ENTITY_GERMANY = LEGAL_ENTITIES[2]; /** The Black bond futures provider **/ private static final BlackBondFuturesExpLogMoneynessProviderDiscount BLACK_FLAT_BNDFUT = new BlackBondFuturesExpLogMoneynessProviderDiscount(ISSUER_SPECIFIC_MULTICURVES, BLACK_SURFACE, LEGAL_ENTITY_GERMANY); /** Methods and calculators */ private static final BondFuturesOptionMarginSecurityBlackBondFuturesMethod METHOD_OPT = BondFuturesOptionMarginSecurityBlackBondFuturesMethod.getDefaultInstance(); private static final BondFuturesSecurityDiscountingMethod METHOD_FUTURE = BondFuturesSecurityDiscountingMethod.getInstance(); private static final BlackPriceFunction BLACK_FUNCTION = new BlackPriceFunction(); private static final BondFutureOptionMarginSecurityBlackPriceMethod METHOD_SMILE = BondFutureOptionMarginSecurityBlackPriceMethod .getInstance(); /** Tolerances */ private static final double TOLERANCE_RATE = 1.0E-10; private static final double TOLERANCE_DELTA = 1.0E-8; public void impliedVolatility() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final double logmoney = Math.log(STRIKE_116 / priceFutures); final double expiry = CALL_BOBL_116.getExpirationTime(); final double ivExpected = BLACK_SURFACE.getZValue(expiry, logmoney); final double ivComputed = METHOD_OPT.impliedVolatility(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: impliedVolatility", ivExpected, ivComputed, TOLERANCE_RATE); } public void futurePrice() { final double priceExpected = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final double priceComputed = METHOD_OPT.underlyingFuturePrice(CALL_BOBL_116, ISSUER_SPECIFIC_MULTICURVES); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: underlying futures price", priceExpected, priceComputed, TOLERANCE_RATE); double priceSmile = METHOD_SMILE.underlyingFuturePrice(CALL_BOBL_116, ISSUER_SPECIFIC_MULTICURVES); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: underlying futures price", priceSmile, priceComputed, TOLERANCE_RATE); } public void priceFromFuturesPrice() { final double price = 1.26; final EuropeanVanillaOption option = new EuropeanVanillaOption(STRIKE_116, CALL_BOBL_116.getExpirationTime(), CALL_BOBL_116.isCall()); final double logmoney = Math.log(STRIKE_116 / price); final double expiry = CALL_BOBL_116.getExpirationTime(); final double volatility = BLACK_SURFACE.getZValue(expiry, logmoney); final BlackFunctionData dataBlack = new BlackFunctionData(price, 1.0, volatility); final double priceExpected = BLACK_FUNCTION.getPriceFunction(option).evaluate(dataBlack); final double priceComputed = METHOD_OPT.price(CALL_BOBL_116, BLACK_FLAT_BNDFUT, price); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: underlying futures price", priceExpected, priceComputed, TOLERANCE_RATE); double priceSmile = METHOD_SMILE.priceFromUnderlyingPrice(CALL_BOBL_116, BLACK_FLAT_BNDFUT, price); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: underlying futures price", priceSmile, priceComputed, TOLERANCE_RATE); } public void priceFromCurves() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final double priceExpected = METHOD_OPT.price(CALL_BOBL_116, BLACK_FLAT_BNDFUT, priceFutures); final double priceComputed = METHOD_OPT.price(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: underlying futures price", priceExpected, priceComputed, TOLERANCE_RATE); double priceSmile = METHOD_SMILE.price(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: underlying futures price", priceSmile, priceComputed, TOLERANCE_RATE); } public void putCallParity() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final double priceCallComputed = METHOD_OPT.price(CALL_BOBL_116, BLACK_FLAT_BNDFUT); final double pricePutComputed = METHOD_OPT.price(PUT_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: put call parity price", priceCallComputed - pricePutComputed, priceFutures - STRIKE_116, TOLERANCE_RATE); } public void priceBlackSensitivity() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final EuropeanVanillaOption option = new EuropeanVanillaOption(STRIKE_116, CALL_BOBL_116.getExpirationTime(), CALL_BOBL_116.isCall()); final double logmoney = Math.log(STRIKE_116 / priceFutures); final double expiry = CALL_BOBL_116.getExpirationTime(); final double volatility = BLACK_SURFACE.getZValue(expiry, logmoney); final BlackFunctionData dataBlack = new BlackFunctionData(priceFutures, 1.0, volatility); final double[] priceAD = BLACK_FUNCTION.getPriceAdjoint(option, dataBlack); final double vega = priceAD[2]; final PresentValueBlackBondFuturesCubeSensitivity vegaComputed = METHOD_OPT.priceBlackSensitivity(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: Black parameters sensitivity", vega, vegaComputed.getSensitivity().toSingleValue(), TOLERANCE_DELTA); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: Black parameters sensitivity", 1, vegaComputed.getSensitivity().getMap().size()); final Entry<Triple<Double, Double, Double>, Double> point = vegaComputed.getSensitivity().getMap().entrySet().iterator().next(); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: Black parameters sensitivity", CALL_BOBL_116.getExpirationTime(), point.getKey().getFirst(), TOLERANCE_RATE); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: Black parameters sensitivity", CALL_BOBL_116.getUnderlyingFuture().getTradingLastTime() - CALL_BOBL_116.getExpirationTime(), point.getKey().getSecond(), TOLERANCE_RATE); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: Black parameters sensitivity", CALL_BOBL_116.getStrike(), point.getKey().getThird(), TOLERANCE_RATE); } public void theoreticalDelta() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final EuropeanVanillaOption option = new EuropeanVanillaOption(STRIKE_116, CALL_BOBL_116.getExpirationTime(), CALL_BOBL_116.isCall()); final double logmoney = Math.log(STRIKE_116 / priceFutures); final double expiry = CALL_BOBL_116.getExpirationTime(); final double volatility = BLACK_SURFACE.getZValue(expiry, logmoney); final BlackFunctionData dataBlack = new BlackFunctionData(priceFutures, 1.0, volatility); final double[] priceAD = BLACK_FUNCTION.getPriceAdjoint(option, dataBlack); final double deltaCallExpected = priceAD[1]; final double deltaCallComputed = METHOD_OPT.deltaUnderlyingPrice(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: delta", deltaCallExpected, deltaCallComputed, TOLERANCE_DELTA); assertTrue("BondFuturesOptionMarginSecurityBlackFlatMethod: delta", (0.0d < deltaCallComputed) && (deltaCallComputed < 1.0d)); final double deltaPutComputed = METHOD_OPT.deltaUnderlyingPrice(PUT_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: delta", deltaCallExpected - 1.0d, deltaPutComputed, TOLERANCE_DELTA); double deltaCallSmile = METHOD_SMILE.delta(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: delta", deltaCallSmile, deltaCallComputed, TOLERANCE_DELTA); double deltaPutSmile = METHOD_SMILE.delta(PUT_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: delta", deltaPutSmile, deltaPutComputed, TOLERANCE_DELTA); } public void theoreticalGamma() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final EuropeanVanillaOption option = new EuropeanVanillaOption(STRIKE_116, CALL_BOBL_116.getExpirationTime(), CALL_BOBL_116.isCall()); final double logmoney = Math.log(STRIKE_116 / priceFutures); final double expiry = CALL_BOBL_116.getExpirationTime(); final double volatility = BLACK_SURFACE.getZValue(expiry, logmoney); final BlackFunctionData dataBlack = new BlackFunctionData(priceFutures, 1.0, volatility); final double[] firstDerivs = new double[3]; final double[][] secondDerivs = new double[3][3]; BLACK_FUNCTION.getPriceAdjoint2(option, dataBlack, firstDerivs, secondDerivs); final double gammaCallExpected = secondDerivs[0][0]; final double gammaCallComputed = METHOD_OPT.gammaUnderlyingPrice(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: gamma", gammaCallExpected, gammaCallComputed, TOLERANCE_DELTA); assertTrue("BondFuturesOptionMarginSecurityBlackFlatMethod: gamma", 0.0d < gammaCallComputed); double gammaCallSmile = METHOD_SMILE.gamma(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: gamma", gammaCallSmile, gammaCallComputed, TOLERANCE_DELTA); } public void theoreticalVega() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final EuropeanVanillaOption option = new EuropeanVanillaOption(STRIKE_116, CALL_BOBL_116.getExpirationTime(), CALL_BOBL_116.isCall()); final double logmoney = Math.log(STRIKE_116 / priceFutures); final double expiry = CALL_BOBL_116.getExpirationTime(); final double volatility = BLACK_SURFACE.getZValue(expiry, logmoney); final BlackFunctionData dataBlack = new BlackFunctionData(priceFutures, 1.0, volatility); final double[] priceAD = BLACK_FUNCTION.getPriceAdjoint(option, dataBlack); final double vegaCallExpected = priceAD[2]; final double vegaCallComputed = METHOD_OPT.vegaUnderlyingPrice(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: vega", vegaCallExpected, vegaCallComputed, TOLERANCE_DELTA); assertTrue("BondFuturesOptionMarginSecurityBlackFlatMethod: vega", (0.0d < vegaCallComputed) && (vegaCallComputed < 1.0d)); double vegaCallSmile = METHOD_SMILE.vega(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: vega", vegaCallSmile, vegaCallComputed, TOLERANCE_DELTA); } public void theoreticalTheta() { final double priceFutures = METHOD_FUTURE.price(CALL_BOBL_116.getUnderlyingFuture(), ISSUER_SPECIFIC_MULTICURVES); final double logmoney = Math.log(STRIKE_116 / priceFutures); final double expiry = CALL_BOBL_116.getExpirationTime(); final double volatility = BLACK_SURFACE.getZValue(expiry, logmoney); final double rate = -Math.log(ISSUER_SPECIFIC_MULTICURVES.getMulticurveProvider().getDiscountFactor(CALL_BOBL_116.getCurrency(), CALL_BOBL_116.getExpirationTime())) / CALL_BOBL_116.getExpirationTime(); final double thetaCallExpected = BlackFormulaRepository.theta(priceFutures, STRIKE_116, CALL_BOBL_116.getExpirationTime(), volatility, CALL_BOBL_116.isCall(), rate); final double thetaCallComputed = METHOD_OPT.theta(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: theta", thetaCallExpected, thetaCallComputed, TOLERANCE_DELTA); double thetaSmile = METHOD_SMILE.theta(CALL_BOBL_116, BLACK_FLAT_BNDFUT); assertEquals("BondFuturesOptionMarginSecurityBlackFlatMethod: theta", thetaSmile, thetaCallComputed, TOLERANCE_DELTA); } private static final Interpolator1D LINEAR_FLAT = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.LINEAR, Interpolator1DFactory.FLAT_EXTRAPOLATOR, Interpolator1DFactory.FLAT_EXTRAPOLATOR); private static final Interpolator1D TIME_SQUARE_FLAT = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.TIME_SQUARE, Interpolator1DFactory.FLAT_EXTRAPOLATOR, Interpolator1DFactory.FLAT_EXTRAPOLATOR); private static final GridInterpolator2D INTERPOLATOR_TIMESQUARE_LINEAR_2D = new GridInterpolator2D(TIME_SQUARE_FLAT, LINEAR_FLAT); private static final GridInterpolator2D INTERPOLATOR_LINEAR_LINEAR_2D = new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT); private static final GridInterpolator2D INTERPOLATOR_LINEAR_TIMESQUARE_2D = new GridInterpolator2D(LINEAR_FLAT, TIME_SQUARE_FLAT); private static final double[] EXP = new double[] {5.0d / 365.0d, 0.20, 0.45 }; private static final double[] LOGMONEY = new double[] {-0.010, -0.005, 0.000, 0.005, 0.010 }; private static final double[] VALUE_EXP_MON = new double[] {0.50, 0.50, 0.45, 0.49, 0.49, 0.44, 0.47, 0.47, 0.42, 0.48, 0.48, 0.43, 0.51, 0.51, 0.46 }; private static final double[] VALUE_MON_EXP = new double[VALUE_EXP_MON.length]; private static final double[] VALUE_EXP_MON_VAR = new double[VALUE_EXP_MON.length]; static { for (int loopmon = 0; loopmon < LOGMONEY.length; loopmon++) { for (int loopexp = 0; loopexp < EXP.length; loopexp++) { VALUE_MON_EXP[loopmon + loopexp * LOGMONEY.length] = VALUE_EXP_MON[loopexp + loopmon * EXP.length]; VALUE_EXP_MON_VAR[loopexp + loopmon * EXP.length] = Math.pow(VALUE_EXP_MON[loopexp + loopmon * EXP.length], 2) * EXP[loopexp]; } } } private static final InterpolatedDoublesSurface BLACK_SURFACE_EXP_LOGMONEY = InterpolatedDoublesSurface.fromGrid(EXP, LOGMONEY, VALUE_EXP_MON, INTERPOLATOR_TIMESQUARE_LINEAR_2D); // Replace LINEAR by "SQUARE_LINEAR" (TBC) private static final InterpolatedDoublesSurface BLACK_SURFACE_LOGMONEY_EXP = InterpolatedDoublesSurface.fromGrid(LOGMONEY, EXP, VALUE_MON_EXP, INTERPOLATOR_LINEAR_TIMESQUARE_2D); private static final InterpolatedDoublesSurface BLACK_SURFACE_EXP_LOGMONEY_VAR = InterpolatedDoublesSurface.fromGrid(EXP, LOGMONEY, VALUE_EXP_MON_VAR, INTERPOLATOR_LINEAR_LINEAR_2D); private static final double TOLERANCE_VOL = 2.0E-4; public void interpolation() { final int nbExpTest = 4; final int nbMonTest = 5; final double startExp = 5.0d / 365.0d; final double stepExp = 0.10; final double startMon = -0.011; final double stepMon = 0.005; for (int loopmon = 0; loopmon < nbMonTest; loopmon++) { for (int loopexp = 0; loopexp < nbExpTest; loopexp++) { double exp = startExp + loopexp * stepExp; double mon = startMon + loopmon * stepMon; double intExpMon = BLACK_SURFACE_EXP_LOGMONEY.getZValue(exp, mon); double intMonExp = BLACK_SURFACE_LOGMONEY_EXP.getZValue(mon, exp); double intVar = BLACK_SURFACE_EXP_LOGMONEY_VAR.getZValue(exp, mon); assertEquals("Time square interpolation: change of order", intExpMon, intMonExp, TOLERANCE_VOL); assertEquals("Time square interpolation: change of order " + loopmon + " - " + loopexp, intExpMon, Math.sqrt(intVar / exp), TOLERANCE_VOL); } } } }