/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.forex.provider; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; import org.threeten.bp.Period; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.forex.definition.ForexDefinition; import com.opengamma.analytics.financial.forex.definition.ForexOptionVanillaDefinition; import com.opengamma.analytics.financial.forex.derivative.Forex; import com.opengamma.analytics.financial.forex.derivative.ForexOptionVanilla; import com.opengamma.analytics.financial.forex.method.FXMatrix; import com.opengamma.analytics.financial.forex.method.PresentValueForexBlackVolatilitySensitivity; import com.opengamma.analytics.financial.model.option.definition.SmileDeltaParameters; 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.BlackImpliedVolatilityFormula; import com.opengamma.analytics.financial.model.volatility.surface.SmileDeltaTermStructureParameters; import com.opengamma.analytics.financial.model.volatility.surface.SmileDeltaTermStructureParametersStrikeInterpolation; import com.opengamma.analytics.financial.provider.description.forex.BlackForexSmileProvider; import com.opengamma.analytics.financial.provider.description.forex.BlackForexVannaVolgaProvider; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity; import com.opengamma.analytics.financial.schedule.ScheduleCalculator; import com.opengamma.analytics.financial.util.AssertSensitivityObjects; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory; import com.opengamma.analytics.math.interpolation.Interpolator1D; import com.opengamma.analytics.math.interpolation.Interpolator1DFactory; import com.opengamma.analytics.util.amount.SurfaceValue; import com.opengamma.analytics.util.time.TimeCalculator; import com.opengamma.financial.convention.businessday.BusinessDayConvention; import com.opengamma.financial.convention.businessday.BusinessDayConventions; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.calendar.MondayToFridayCalendar; import com.opengamma.util.money.Currency; import com.opengamma.util.money.MultipleCurrencyAmount; import com.opengamma.util.test.TestGroup; import com.opengamma.util.time.DateUtils; import com.opengamma.util.tuple.DoublesPair; import com.opengamma.util.tuple.Pairs; /** * Test. */ @Test(groups = TestGroup.UNIT) public class ForexOptionVanillaVannaVolgaMethodTest { private static final MulticurveProviderDiscount MULTICURVES = MulticurveProviderDiscountForexDataSets.createMulticurvesForex(); private static final FXMatrix FX_MATRIX = MULTICURVES.getFxRates(); private static final Currency EUR = Currency.EUR; private static final Currency USD = Currency.USD; private static final double SPOT = FX_MATRIX.getFxRate(EUR, USD); // General private static final Calendar CALENDAR = new MondayToFridayCalendar("A"); private static final BusinessDayConvention BUSINESS_DAY = BusinessDayConventions.MODIFIED_FOLLOWING; private static final int SETTLEMENT_DAYS = 2; // Smile data private static final Period[] EXPIRY_PERIOD = new Period[] {Period.ofMonths(3), Period.ofMonths(6), Period.ofYears(1), Period.ofYears(2), Period.ofYears(5)}; private static final int NB_EXP = EXPIRY_PERIOD.length; private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2011, 6, 13); private static final ZonedDateTime REFERENCE_SPOT = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, SETTLEMENT_DAYS, CALENDAR); private static final ZonedDateTime[] PAY_DATE = new ZonedDateTime[NB_EXP]; private static final ZonedDateTime[] EXPIRY_DATE = new ZonedDateTime[NB_EXP]; private static final double[] TIME_TO_EXPIRY = new double[NB_EXP + 1]; static { TIME_TO_EXPIRY[0] = 0.0; for (int loopexp = 0; loopexp < NB_EXP; loopexp++) { PAY_DATE[loopexp] = ScheduleCalculator.getAdjustedDate(REFERENCE_SPOT, EXPIRY_PERIOD[loopexp], BUSINESS_DAY, CALENDAR); EXPIRY_DATE[loopexp] = ScheduleCalculator.getAdjustedDate(PAY_DATE[loopexp], -SETTLEMENT_DAYS, CALENDAR); TIME_TO_EXPIRY[loopexp + 1] = TimeCalculator.getTimeBetween(REFERENCE_DATE, EXPIRY_DATE[loopexp]); } } private static final double[] ATM = {0.11, 0.115, 0.12, 0.12, 0.125, 0.13}; private static final double[] DELTA = new double[] {0.25}; private static final double[][] RISK_REVERSAL = new double[][] { {0.015}, {0.020}, {0.025}, {0.03}, {0.025}, {0.030}}; private static final double[][] STRANGLE = new double[][] { {0.002}, {0.003}, {0.004}, {0.0045}, {0.0045}, {0.0045}}; private static final double[][] RISK_REVERSAL_FLAT = new double[][] { {0.0}, {0.0}, {0.0}, {0.0}, {0.0}, {0.0}}; private static final double[][] STRANGLE_FLAT = new double[][] { {0.0}, {0.0}, {0.0}, {0.0}, {0.0}, {0.0}}; private static final Interpolator1D INTERPOLATOR_STRIKE = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR, Interpolator1DFactory.LINEAR_EXTRAPOLATOR); private static final SmileDeltaTermStructureParameters SMILE_TERM = new SmileDeltaTermStructureParameters(TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, STRANGLE); private static final SmileDeltaTermStructureParametersStrikeInterpolation SMILE_TERM_STRIKE_INT = new SmileDeltaTermStructureParametersStrikeInterpolation( TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, STRANGLE, INTERPOLATOR_STRIKE); private static final SmileDeltaTermStructureParametersStrikeInterpolation SMILE_TERM_STRIKE_INT_FLAT = new SmileDeltaTermStructureParametersStrikeInterpolation( TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL_FLAT, STRANGLE_FLAT, INTERPOLATOR_STRIKE); // Methods and curves private static final ForexOptionVanillaVannaVolgaMethod METHOD_VANNA_VOLGA = ForexOptionVanillaVannaVolgaMethod.getInstance(); private static final ForexOptionVanillaBlackSmileMethod METHOD_BLACK = ForexOptionVanillaBlackSmileMethod.getInstance(); private static final ForexDiscountingMethod METHOD_DISC = ForexDiscountingMethod.getInstance(); private static final BlackForexSmileProvider SMILE_MULTICURVES = new BlackForexSmileProvider(MULTICURVES, SMILE_TERM_STRIKE_INT, Pairs.of(EUR, USD)); private static final BlackForexSmileProvider SMILE_FLAT_MULTICURVES = new BlackForexSmileProvider(MULTICURVES, SMILE_TERM_STRIKE_INT_FLAT, Pairs.of(EUR, USD)); private static final BlackForexVannaVolgaProvider VANNAVOLGA_MULTICURVES = new BlackForexVannaVolgaProvider(MULTICURVES, SMILE_TERM, Pairs.of(EUR, USD)); private static final BlackImpliedVolatilityFormula BLACK_IMPLIED_VOL = new BlackImpliedVolatilityFormula(); private static final BlackPriceFunction BLACK_FUNCTION = new BlackPriceFunction(); private static final double TOLERANCE_PV = 1.0E-2; private static final double TOLERANCE_PV_DELTA = 1.0E-0; private static final double TOLERANCE_W = 1.0E-10; @Test /** * Tests put/call parity. */ public void putCallParity() { final int nbStrike = 20; final double strikeMin = 1.00; final double strikeRange = 0.80; final double[] strikes = new double[nbStrike + 1]; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] call = new ForexOptionVanilla[nbStrike + 1]; final ForexOptionVanilla[] put = new ForexOptionVanilla[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition callDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); final ForexOptionVanillaDefinition putDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, !isCall, !isLong); call[loopstrike] = callDefinition.toDerivative(REFERENCE_DATE); put[loopstrike] = putDefinition.toDerivative(REFERENCE_DATE); final Forex forexForward = forexUnderlyingDefinition.toDerivative(REFERENCE_DATE); // Present value final MultipleCurrencyAmount pvCall = METHOD_VANNA_VOLGA.presentValue(call[loopstrike], VANNAVOLGA_MULTICURVES); final MultipleCurrencyAmount pvPut = METHOD_VANNA_VOLGA.presentValue(put[loopstrike], VANNAVOLGA_MULTICURVES); final MultipleCurrencyAmount pvForward = METHOD_DISC.presentValue(forexForward, MULTICURVES); assertEquals("Forex vanilla option: vanna-volga present value put/call parity", pvForward.getAmount(USD) + pvForward.getAmount(EUR) * SPOT, pvCall.getAmount(USD) + pvPut.getAmount(USD), TOLERANCE_PV); // Currency exposure final MultipleCurrencyAmount ceCall = METHOD_VANNA_VOLGA.currencyExposure(call[loopstrike], VANNAVOLGA_MULTICURVES); final MultipleCurrencyAmount cePut = METHOD_VANNA_VOLGA.currencyExposure(put[loopstrike], VANNAVOLGA_MULTICURVES); final MultipleCurrencyAmount ceForward = METHOD_DISC.currencyExposure(forexForward, MULTICURVES); assertEquals("Forex vanilla option: vanna-volga currency exposure put/call parity", ceForward.getAmount(USD), ceCall.getAmount(USD) + cePut.getAmount(USD), TOLERANCE_PV); assertEquals("Forex vanilla option: vanna-volga currency exposure put/call parity", ceForward.getAmount(EUR), ceCall.getAmount(EUR) + cePut.getAmount(EUR), TOLERANCE_PV); // Vega final PresentValueForexBlackVolatilitySensitivity pvbsCall = METHOD_VANNA_VOLGA.presentValueBlackVolatilitySensitivity(call[loopstrike], VANNAVOLGA_MULTICURVES); final PresentValueForexBlackVolatilitySensitivity pvbsPut = METHOD_VANNA_VOLGA.presentValueBlackVolatilitySensitivity(put[loopstrike], VANNAVOLGA_MULTICURVES); assertTrue( "Forex vanilla option: vanna-volga sensitivity put/call parity - strike " + loopstrike, PresentValueForexBlackVolatilitySensitivity.compare(pvbsCall.plus(pvbsPut), new PresentValueForexBlackVolatilitySensitivity(EUR, USD, SurfaceValue.from(DoublesPair.of(0.0d, 0.0d), 0.0d)), TOLERANCE_PV)); // Curve sensitivty final MultipleCurrencyMulticurveSensitivity pvcsCall = METHOD_VANNA_VOLGA.presentValueCurveSensitivity(call[loopstrike], VANNAVOLGA_MULTICURVES); final MultipleCurrencyMulticurveSensitivity pvcsPut = METHOD_VANNA_VOLGA.presentValueCurveSensitivity(put[loopstrike], VANNAVOLGA_MULTICURVES); final MultipleCurrencyMulticurveSensitivity pvcsForward = METHOD_DISC.presentValueCurveSensitivity(forexForward, MULTICURVES).converted(USD, FX_MATRIX); final MultipleCurrencyMulticurveSensitivity pvcsOpt = pvcsCall.plus(pvcsPut).cleaned(); AssertSensitivityObjects.assertEquals("Forex vanilla option: vanna-volga curve sensitivity put/call parity", pvcsForward, pvcsOpt, TOLERANCE_PV_DELTA); } } @Test /** * Tests vanna-volga weights. */ public void weight() { final int nbStrike = 10; final double strikeMin = 1.00; final double strikeRange = 0.80; final double[] strikes = new double[nbStrike + 1]; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double forward = METHOD_BLACK.forwardForexRate(forexOption[0], MULTICURVES); final double dfDomestic = MULTICURVES.getDiscountFactor(USD, forexOption[0].getUnderlyingForex().getPaymentTime()); // USD final SmileDeltaParameters smileAtTime = VANNAVOLGA_MULTICURVES.getSmile(EUR, USD, forexOption[0].getTimeToExpiry()); final double[] strikesVV = smileAtTime.getStrike(forward); final double[] volVV = smileAtTime.getVolatility(); for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { final double[] weightsComputed = METHOD_VANNA_VOLGA.vannaVolgaWeights(forexOption[loopstrike], forward, dfDomestic, strikesVV, volVV); final double[] vega = new double[3]; final BlackFunctionData dataBlackATM = new BlackFunctionData(forward, dfDomestic, volVV[1]); for (int loopvv = 0; loopvv < 3; loopvv++) { final EuropeanVanillaOption optionVV = new EuropeanVanillaOption(strikesVV[loopvv], forexOption[loopstrike].getTimeToExpiry(), true); vega[loopvv] = BLACK_FUNCTION.getVegaFunction(optionVV).evaluate(dataBlackATM); } final double vegaFlat = BLACK_FUNCTION.getVegaFunction(forexOption[loopstrike]).evaluate(dataBlackATM); vega[1] = vegaFlat; final double lnk21 = Math.log(strikesVV[1] / strikesVV[0]); final double lnk31 = Math.log(strikesVV[2] / strikesVV[0]); final double lnk32 = Math.log(strikesVV[2] / strikesVV[1]); final double[] lnk = new double[3]; for (int loopvv = 0; loopvv < 3; loopvv++) { lnk[loopvv] = Math.log(strikesVV[loopvv] / strikes[loopstrike]); } final double[] weightExpected = new double[3]; weightExpected[0] = vegaFlat * lnk[1] * lnk[2] / (vega[0] * lnk21 * lnk31); weightExpected[1] = -vegaFlat * lnk[0] * lnk[2] / (vega[1] * lnk21 * lnk32); weightExpected[2] = vegaFlat * lnk[0] * lnk[1] / (vega[2] * lnk31 * lnk32); for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { assertEquals("Vanna-volga: adjustment weights", weightExpected[loopvv], weightsComputed[loopvv], TOLERANCE_W); } } } @Test /** * Tests the method with hard-coded values. */ public void presentValueHardCoded() { final int nbStrike = 10; final double strikeMin = 1.00; final double strikeRange = 0.80; final double[] strikes = new double[nbStrike + 1]; final double[] pvExpected = new double[] {3.860405407112769E7, 3.0897699603079587E7, 2.3542824458812844E7, 1.6993448607300103E7, 1.1705393621236656E7, 7865881.826, 5312495.846, 3680367.677, 2607701.430, 1849818.30, 1282881.98}; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double[] pvVV = new double[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { pvVV[loopstrike] = METHOD_VANNA_VOLGA.presentValue(forexOption[loopstrike], VANNAVOLGA_MULTICURVES).getAmount(USD); assertEquals("Forex vanilla option: present value vanna-volga / hard-coded", pvExpected[loopstrike], pvVV[loopstrike], TOLERANCE_PV); } } @Test /** * Check the price implied by the vanna-volga method and compares it to the market prices at the market data points. */ public void presentValueAtMarketData() { final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexDefinition forexUnderlyingSpotDefinition = new ForexDefinition(EUR, USD, optionPay, notional, SPOT); final ForexOptionVanillaDefinition forexOptionSpotDefinition = new ForexOptionVanillaDefinition(forexUnderlyingSpotDefinition, optionExpiry, isCall, isLong); final ForexOptionVanilla forexOptionSpot = forexOptionSpotDefinition.toDerivative(REFERENCE_DATE); final double forward = METHOD_BLACK.forwardForexRate(forexOptionSpot, MULTICURVES); final SmileDeltaParameters smileTime = SMILE_TERM.getSmileForTime(forexOptionSpot.getTimeToExpiry()); final double[] strikes = smileTime.getStrike(forward); final int nbStrike = strikes.length; final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike]; for (int loopstrike = 0; loopstrike < nbStrike; loopstrike++) { final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double[] pvVV = new double[nbStrike]; final double[] pvInt = new double[nbStrike]; for (int loopstrike = 0; loopstrike < nbStrike; loopstrike++) { pvVV[loopstrike] = METHOD_VANNA_VOLGA.presentValue(forexOption[loopstrike], VANNAVOLGA_MULTICURVES).getAmount(USD); pvInt[loopstrike] = METHOD_BLACK.presentValue(forexOption[loopstrike], SMILE_MULTICURVES).getAmount(USD); assertEquals("Forex vanilla option: currency exposure put/call parity domestic", pvInt[loopstrike], pvVV[loopstrike], TOLERANCE_PV); } } @Test /** * Tests the currency exposure in the Vanna-Volga method. */ public void currencyExposure() { final int nbStrike = 10; final double strikeMin = 1.00; final double strikeRange = 0.80; final double[] strikes = new double[nbStrike + 1]; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double forward = METHOD_BLACK.forwardForexRate(forexOption[0], MULTICURVES); final double dfDomestic = MULTICURVES.getDiscountFactor(USD, forexOption[0].getUnderlyingForex().getPaymentTime()); // USD final SmileDeltaParameters smileAtTime = VANNAVOLGA_MULTICURVES.getSmile(EUR, USD, forexOption[0].getTimeToExpiry()); final double[] strikesVV = smileAtTime.getStrike(forward); final double[] volVV = smileAtTime.getVolatility(); final ForexOptionVanilla[] optReference = new ForexOptionVanilla[3]; for (int loopvv = 0; loopvv < 3; loopvv++) { final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikesVV[loopvv]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); optReference[loopvv] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final MultipleCurrencyAmount[] ceVV = new MultipleCurrencyAmount[nbStrike + 1]; final MultipleCurrencyAmount[] ceFlat = new MultipleCurrencyAmount[nbStrike + 1]; final MultipleCurrencyAmount[] ceExpected = new MultipleCurrencyAmount[nbStrike + 1]; final MultipleCurrencyAmount[] ceVVATM = new MultipleCurrencyAmount[3]; final MultipleCurrencyAmount[] ceVVsmile = new MultipleCurrencyAmount[3]; final MultipleCurrencyAmount[] ceVVadj = new MultipleCurrencyAmount[3]; for (int loopvv = 0; loopvv < 3; loopvv++) { ceVVATM[loopvv] = METHOD_BLACK.currencyExposure(optReference[loopvv], SMILE_FLAT_MULTICURVES); ceVVsmile[loopvv] = METHOD_BLACK.currencyExposure(optReference[loopvv], SMILE_MULTICURVES); ceVVadj[loopvv] = ceVVsmile[loopvv].plus(ceVVATM[loopvv].multipliedBy(-1.0)); } for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { ceVV[loopstrike] = METHOD_VANNA_VOLGA.currencyExposure(forexOption[loopstrike], VANNAVOLGA_MULTICURVES); final double[] weights = METHOD_VANNA_VOLGA.vannaVolgaWeights(forexOption[loopstrike], forward, dfDomestic, strikesVV, volVV); ceFlat[loopstrike] = METHOD_BLACK.currencyExposure(forexOption[loopstrike], SMILE_FLAT_MULTICURVES); ceExpected[loopstrike] = ceFlat[loopstrike]; for (int loopvv = 0; loopvv < 3; loopvv++) { ceExpected[loopstrike] = ceExpected[loopstrike].plus(ceVVadj[loopvv].multipliedBy(weights[loopvv])); } assertEquals("Forex vanilla option: currency exposure vanna-volga", ceExpected[loopstrike].getAmount(EUR), ceVV[loopstrike].getAmount(EUR), TOLERANCE_PV); assertEquals("Forex vanilla option: currency exposure vanna-volga", ceExpected[loopstrike].getAmount(USD), ceVV[loopstrike].getAmount(USD), TOLERANCE_PV); } } @Test /** * Compare results with the Black results. They should be different but not too much. */ public void comparisonBlack() { final int nbStrike = 20; final double strikeMin = 1.00; final double strikeRange = 0.50; final double[] strikes = new double[nbStrike + 1]; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double[] pvVV = new double[nbStrike + 1]; final double[] pvInt = new double[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { pvVV[loopstrike] = METHOD_VANNA_VOLGA.presentValue(forexOption[loopstrike], VANNAVOLGA_MULTICURVES).getAmount(USD); pvInt[loopstrike] = METHOD_BLACK.presentValue(forexOption[loopstrike], SMILE_MULTICURVES).getAmount(USD); assertEquals("Forex vanilla option: present value vanna-volga vs Black " + loopstrike, 1, pvVV[loopstrike] / pvInt[loopstrike], 0.15); } final MultipleCurrencyAmount[] ceVV = new MultipleCurrencyAmount[nbStrike + 1]; final MultipleCurrencyAmount[] ceInt = new MultipleCurrencyAmount[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { ceVV[loopstrike] = METHOD_VANNA_VOLGA.currencyExposure(forexOption[loopstrike], VANNAVOLGA_MULTICURVES); ceInt[loopstrike] = METHOD_BLACK.currencyExposure(forexOption[loopstrike], SMILE_MULTICURVES); assertEquals("Forex vanilla option: currency exposure vanna-volga vs Black " + loopstrike, 1, ceVV[loopstrike].getAmount(EUR) / ceInt[loopstrike].getAmount(EUR), 0.15); } final MultipleCurrencyMulticurveSensitivity[] pvcsVV = new MultipleCurrencyMulticurveSensitivity[nbStrike + 1]; final MultipleCurrencyMulticurveSensitivity[] pvcsInt = new MultipleCurrencyMulticurveSensitivity[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { pvcsVV[loopstrike] = METHOD_VANNA_VOLGA.presentValueCurveSensitivity(forexOption[loopstrike], VANNAVOLGA_MULTICURVES); pvcsInt[loopstrike] = METHOD_BLACK.presentValueCurveSensitivity(forexOption[loopstrike], SMILE_MULTICURVES); AssertSensitivityObjects.assertEquals("Forex vanilla option: curve sensitivity vanna-volga vs Black " + loopstrike, pvcsVV[loopstrike], pvcsInt[loopstrike], 3.0E+6); // assertEquals("Forex vanilla option: curve sensitivity vanna-volga vs Black " + loopstrike, 1, pvcsVV[loopstrike].getSensitivity(USD).getSensitivities().get(NOT_USED_2[1]).get(0).getSecond() // / pvcsInt[loopstrike].getSensitivity(USD).getSensitivities().get(NOT_USED_2[1]).get(0).getSecond(), 0.15); } } @Test(enabled = false) /** * Analyzes the smile implied by the vanna-volga method and compares it to a quadratic interpolation/linear extrapolation. * Used to produce the graphs of the documentation. */ public void analysisSmileCall() { final int nbStrike = 50; final double strikeMin = 1.00; final double strikeRange = 0.80; final double[] strikes = new double[nbStrike + 1]; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double[] pvVV = new double[nbStrike + 1]; final double[] pvInt = new double[nbStrike + 1]; final double[] volVV = new double[nbStrike + 1]; final double[] volInt = new double[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { pvVV[loopstrike] = METHOD_VANNA_VOLGA.presentValue(forexOption[loopstrike], VANNAVOLGA_MULTICURVES).getAmount(USD); pvInt[loopstrike] = METHOD_BLACK.presentValue(forexOption[loopstrike], SMILE_MULTICURVES).getAmount(USD); final double forward = METHOD_BLACK.forwardForexRate(forexOption[loopstrike], MULTICURVES); final double df = MULTICURVES.getDiscountFactor(USD, forexOption[loopstrike].getUnderlyingForex().getPaymentTime()); volVV[loopstrike] = BLACK_IMPLIED_VOL.getImpliedVolatility(new BlackFunctionData(forward, df, 0.20), forexOption[loopstrike], pvVV[loopstrike] / notional); volInt[loopstrike] = METHOD_BLACK.impliedVolatility(forexOption[loopstrike], SMILE_MULTICURVES); } } @Test(enabled = true) /** * Analyzes the vega for different strikes. * Used to produce the graphs of the documentation. */ public void analysisVega() { final int nbStrike = 50; final double strikeMin = 0.85; final double strikeRange = 1.00; final double[] strikes = new double[nbStrike + 1]; final boolean isCall = true; final boolean isLong = true; final double notional = 1000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double forward = METHOD_BLACK.forwardForexRate(forexOption[0], MULTICURVES); final SmileDeltaParameters smileTime = SMILE_TERM.getSmileForTime(forexOption[0].getTimeToExpiry()); final double[] strikesVV = smileTime.getStrike(forward); final PresentValueForexBlackVolatilitySensitivity[] vegaObject = new PresentValueForexBlackVolatilitySensitivity[nbStrike + 1]; final double[][] vegaVV = new double[3][nbStrike + 1]; final double[] vegaBlack = new double[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { vegaObject[loopstrike] = METHOD_VANNA_VOLGA.presentValueBlackVolatilitySensitivity(forexOption[loopstrike], VANNAVOLGA_MULTICURVES); for (int loopvv = 0; loopvv < 3; loopvv++) { final DoublesPair point = DoublesPair.of(forexOption[loopstrike].getTimeToExpiry(), strikesVV[loopvv]); vegaVV[loopvv][loopstrike] = vegaObject[loopstrike].getVega().getMap().get(point); } vegaBlack[loopstrike] = METHOD_BLACK.presentValueBlackVolatilitySensitivity(forexOption[loopstrike], SMILE_MULTICURVES).toSingleValue().getAmount(); } } @Test(enabled = true) /** * Analyzes the price implied by the vanna-volga method and compares it to the market prices at the market data points. */ public void analysisAtData() { final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexDefinition forexUnderlyingSpotDefinition = new ForexDefinition(EUR, USD, optionPay, notional, SPOT); final ForexOptionVanillaDefinition forexOptionSpotDefinition = new ForexOptionVanillaDefinition(forexUnderlyingSpotDefinition, optionExpiry, isCall, isLong); final ForexOptionVanilla forexOptionSpot = forexOptionSpotDefinition.toDerivative(REFERENCE_DATE); final double forward = METHOD_BLACK.forwardForexRate(forexOptionSpot, MULTICURVES); final SmileDeltaParameters smileTime = SMILE_TERM.getSmileForTime(forexOptionSpot.getTimeToExpiry()); final double[] strikes = smileTime.getStrike(forward); final int nbStrike = strikes.length; final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike]; for (int loopstrike = 0; loopstrike < nbStrike; loopstrike++) { final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); final ForexOptionVanillaDefinition forexOptionDefinition = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); forexOption[loopstrike] = forexOptionDefinition.toDerivative(REFERENCE_DATE); } final double[] pvVV = new double[nbStrike]; final double[] pvInt = new double[nbStrike]; final double[] volVV = new double[nbStrike]; final double[] volInt = new double[nbStrike]; for (int loopstrike = 0; loopstrike < nbStrike; loopstrike++) { pvVV[loopstrike] = METHOD_VANNA_VOLGA.presentValue(forexOption[loopstrike], VANNAVOLGA_MULTICURVES).getAmount(USD); pvInt[loopstrike] = METHOD_BLACK.presentValue(forexOption[loopstrike], SMILE_MULTICURVES).getAmount(USD); final double df = MULTICURVES.getDiscountFactor(USD, forexOption[loopstrike].getUnderlyingForex().getPaymentTime()); // USD discounting volVV[loopstrike] = BLACK_IMPLIED_VOL.getImpliedVolatility(new BlackFunctionData(forward, df, 0.20), forexOption[loopstrike], pvVV[loopstrike] / notional); volInt[loopstrike] = METHOD_BLACK.impliedVolatility(forexOption[loopstrike], SMILE_MULTICURVES); } } @Test(enabled = false) /** * Analyzes the performance of the vanna-volga method. */ public void performance() { long startTime, endTime; final int nbTest = 1000; //1000 final int nbStrike = 50; final double strikeMin = 1.00; final double strikeRange = 0.80; final double[] strikes = new double[nbStrike + 1]; final boolean isCall = true; final boolean isLong = true; final double notional = 100000000; final ZonedDateTime optionExpiry = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofMonths(18), BUSINESS_DAY, CALENDAR); final ZonedDateTime optionPay = ScheduleCalculator.getAdjustedDate(optionExpiry, SETTLEMENT_DAYS, CALENDAR); final ForexOptionVanilla[] forexOption = new ForexOptionVanilla[nbStrike + 1]; final ForexOptionVanillaDefinition[] forexOptionDefinition = new ForexOptionVanillaDefinition[nbStrike + 1]; for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { strikes[loopstrike] = strikeMin + loopstrike * strikeRange / nbStrike; final ForexDefinition forexUnderlyingDefinition = new ForexDefinition(EUR, USD, optionPay, notional, strikes[loopstrike]); forexOptionDefinition[loopstrike] = new ForexOptionVanillaDefinition(forexUnderlyingDefinition, optionExpiry, isCall, isLong); } final double[] pvVV = new double[nbStrike + 1]; final double[] pvInt = new double[nbStrike + 1]; final MultipleCurrencyAmount[] ceVV = new MultipleCurrencyAmount[nbStrike + 1]; final MultipleCurrencyAmount[] ceInt = new MultipleCurrencyAmount[nbStrike + 1]; final PresentValueForexBlackVolatilitySensitivity[] pvbsVV = new PresentValueForexBlackVolatilitySensitivity[nbStrike + 1]; final PresentValueForexBlackVolatilitySensitivity[] pvbsInt = new PresentValueForexBlackVolatilitySensitivity[nbStrike + 1]; final MultipleCurrencyMulticurveSensitivity[] pvcsVV = new MultipleCurrencyMulticurveSensitivity[nbStrike + 1]; final MultipleCurrencyMulticurveSensitivity[] pvcsInt = new MultipleCurrencyMulticurveSensitivity[nbStrike + 1]; startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { forexOption[loopstrike] = forexOptionDefinition[loopstrike].toDerivative(REFERENCE_DATE); pvInt[loopstrike] = METHOD_BLACK.presentValue(forexOption[loopstrike], SMILE_MULTICURVES).getAmount(USD); ceInt[loopstrike] = METHOD_BLACK.currencyExposure(forexOption[loopstrike], SMILE_MULTICURVES); pvbsInt[loopstrike] = METHOD_BLACK.presentValueBlackVolatilitySensitivity(forexOption[loopstrike], SMILE_MULTICURVES); pvcsInt[loopstrike] = METHOD_BLACK.presentValueCurveSensitivity(forexOption[loopstrike], SMILE_MULTICURVES); } } endTime = System.currentTimeMillis(); System.out.println(nbTest + " x " + (nbStrike + 1) + " vanilla forex options with Black: " + (endTime - startTime) + " ms"); // Performance note: conversion + pv + ce + pvbs + pvcs: 06-Dec-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 410 ms for 1000x51 options. startTime = System.currentTimeMillis(); for (int looptest = 0; looptest < nbTest; looptest++) { for (int loopstrike = 0; loopstrike <= nbStrike; loopstrike++) { forexOption[loopstrike] = forexOptionDefinition[loopstrike].toDerivative(REFERENCE_DATE); pvVV[loopstrike] = METHOD_VANNA_VOLGA.presentValue(forexOption[loopstrike], VANNAVOLGA_MULTICURVES).getAmount(USD); ceVV[loopstrike] = METHOD_VANNA_VOLGA.currencyExposure(forexOption[loopstrike], VANNAVOLGA_MULTICURVES); pvbsVV[loopstrike] = METHOD_VANNA_VOLGA.presentValueBlackVolatilitySensitivity(forexOption[loopstrike], VANNAVOLGA_MULTICURVES); pvcsVV[loopstrike] = METHOD_VANNA_VOLGA.presentValueCurveSensitivity(forexOption[loopstrike], VANNAVOLGA_MULTICURVES); } } endTime = System.currentTimeMillis(); System.out.println(nbTest + " x " + (nbStrike + 1) + " vanilla forex options with Vanna-Volga: " + (endTime - startTime) + " ms"); // Performance note: conversion + pv + ce + pvbs + pvcs: 06-Dec-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 625 ms for 1000x51 options. } }