/** * 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 java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.Validate; import com.opengamma.analytics.financial.forex.derivative.ForexOptionVanilla; 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.provider.description.forex.BlackForexVannaVolgaProviderInterface; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderInterface; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MulticurveSensitivity; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity; import com.opengamma.analytics.util.amount.SurfaceValue; import com.opengamma.util.money.CurrencyAmount; import com.opengamma.util.money.MultipleCurrencyAmount; import com.opengamma.util.tuple.DoublesPair; /** * Pricing method for vanilla Forex option transactions with Vanna-Volga method. * <p>Reference: The vanna-volga method for implied volatilities (2007), A. Castagna and F. Mercurio, Risk, 106-111, January 2007. * <p>OG implementation: Vanna-volga method for Forex options, version 1.0, June 2012. * <p>The reference volatility used for Black computation is the second volatility (usually corresponding to the ATM strike). */ public final class ForexOptionVanillaVannaVolgaMethod { /** * The method unique instance. */ private static final ForexOptionVanillaVannaVolgaMethod INSTANCE = new ForexOptionVanillaVannaVolgaMethod(); /** * Return the unique instance of the class. * @return The instance. */ public static ForexOptionVanillaVannaVolgaMethod getInstance() { return INSTANCE; } /** * Private constructor. */ private ForexOptionVanillaVannaVolgaMethod() { } /** * The Black function used in the pricing. */ private static final BlackPriceFunction BLACK_FUNCTION = new BlackPriceFunction(); /** * Computes the present value of the vanilla option with the Black function and a volatility from a volatility surface. * @param optionForex The Forex option. * @param smileMulticurves The curve and smile data. * @return The present value. The value is in the domestic currency (currency 2). */ public MultipleCurrencyAmount presentValue(final ForexOptionVanilla optionForex, final BlackForexVannaVolgaProviderInterface smileMulticurves) { Validate.notNull(optionForex, "Forex option"); Validate.notNull(smileMulticurves, "Smile"); Validate.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data"); final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider(); final double dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency2(), optionForex.getUnderlyingForex().getPaymentTime()); final double dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency1(), optionForex.getUnderlyingForex().getPaymentTime()); final double spot = multicurves.getFxRate(optionForex.getCurrency1(), optionForex.getCurrency2()); final double forward = spot * dfForeign / dfDomestic; final SmileDeltaParameters smileAtTime = smileMulticurves.getSmile(optionForex.getCurrency1(), optionForex.getCurrency2(), optionForex.getTimeToExpiry()); final double[] strikesVV = smileAtTime.getStrike(forward); final double[] volVV = smileAtTime.getVolatility(); final double volATM = volVV[1]; final double[] priceVVATM = new double[3]; final double[] priceVVsmile = new double[3]; final BlackFunctionData dataBlackATM = new BlackFunctionData(forward, dfDomestic, volATM); for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { // Implementation note: The adjustment for K2 is 0 final BlackFunctionData dataBlackSmile = new BlackFunctionData(forward, dfDomestic, volVV[loopvv]); final EuropeanVanillaOption optionVV = new EuropeanVanillaOption(strikesVV[loopvv], optionForex.getTimeToExpiry(), true); priceVVATM[loopvv] = BLACK_FUNCTION.getPriceFunction(optionVV).evaluate(dataBlackATM); priceVVsmile[loopvv] = BLACK_FUNCTION.getPriceFunction(optionVV).evaluate(dataBlackSmile); } final double priceFlat = BLACK_FUNCTION.getPriceFunction(optionForex).evaluate(dataBlackATM); final double[] x = vannaVolgaWeights(optionForex, forward, dfDomestic, strikesVV, volVV); double price = priceFlat; for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { price += x[loopvv] * (priceVVsmile[loopvv] - priceVVATM[loopvv]); } price *= Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount()) * (optionForex.isLong() ? 1.0 : -1.0); final CurrencyAmount pvCurrency = CurrencyAmount.of(optionForex.getUnderlyingForex().getCurrency2(), price); return MultipleCurrencyAmount.of(pvCurrency); } /** * Computes the currency exposure of the vanilla option with the Black function and a volatility from a volatility surface. The exposure is computed in both option currencies. * @param optionForex The Forex option. * @param smileMulticurves The curve and smile data. * @return The currency exposure */ public MultipleCurrencyAmount currencyExposure(final ForexOptionVanilla optionForex, final BlackForexVannaVolgaProviderInterface smileMulticurves) { Validate.notNull(optionForex, "Forex option"); Validate.notNull(smileMulticurves, "Smile"); Validate.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data"); final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider(); final double dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency2(), optionForex.getUnderlyingForex().getPaymentTime()); final double dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency1(), optionForex.getUnderlyingForex().getPaymentTime()); final double spot = multicurves.getFxRate(optionForex.getCurrency1(), optionForex.getCurrency2()); final double forward = spot * dfForeign / dfDomestic; final SmileDeltaParameters smileAtTime = smileMulticurves.getSmile(optionForex.getCurrency1(), optionForex.getCurrency2(), optionForex.getTimeToExpiry()); final double[] strikesVV = smileAtTime.getStrike(forward); final double[] volVV = smileAtTime.getVolatility(); final double volATM = volVV[1]; final double[][] priceVVATM = new double[3][]; final double[][] priceVVsmile = new double[3][]; final BlackFunctionData dataBlackATM = new BlackFunctionData(forward, dfDomestic, volATM); for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { // Implementation note: The adjustment for K2 is 0 final BlackFunctionData dataBlackSmile = new BlackFunctionData(forward, dfDomestic, volVV[loopvv]); final EuropeanVanillaOption optionVV = new EuropeanVanillaOption(strikesVV[loopvv], optionForex.getTimeToExpiry(), true); priceVVATM[loopvv] = BLACK_FUNCTION.getPriceAdjoint(optionVV, dataBlackATM); priceVVsmile[loopvv] = BLACK_FUNCTION.getPriceAdjoint(optionVV, dataBlackSmile); } final double[] priceFlat = BLACK_FUNCTION.getPriceAdjoint(optionForex, dataBlackATM); final double[] x = vannaVolgaWeights(optionForex, forward, dfDomestic, strikesVV, volVV); double price = priceFlat[0]; for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { price += x[loopvv] * (priceVVsmile[loopvv][0] - priceVVATM[loopvv][0]); } price *= Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount()) * (optionForex.isLong() ? 1.0 : -1.0); double deltaSpot = priceFlat[1]; for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { deltaSpot += x[loopvv] * (priceVVsmile[loopvv][1] - priceVVATM[loopvv][1]); } deltaSpot *= dfForeign / dfDomestic; final double sign = (optionForex.isLong() ? 1.0 : -1.0); final CurrencyAmount[] currencyExposure = new CurrencyAmount[2]; // Implementation note: foreign currency (currency 1) exposure = Delta_spot * amount1. currencyExposure[0] = CurrencyAmount.of(optionForex.getUnderlyingForex().getCurrency1(), deltaSpot * Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount()) * sign); // Implementation note: domestic currency (currency 2) exposure = -Delta_spot * amount1 * spot+PV currencyExposure[1] = CurrencyAmount.of(optionForex.getUnderlyingForex().getCurrency2(), -deltaSpot * Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount()) * spot * sign + price); return MultipleCurrencyAmount.of(currencyExposure); } /** * Computes the volatility sensitivity of the vanilla option to the reference volatilities. * @param optionForex The Forex option. * @param smileMulticurves The curve and smile data. * @return The volatility sensitivity. The sensitivity figures are, like the present value, in the domestic currency (currency 2). */ public PresentValueForexBlackVolatilitySensitivity presentValueBlackVolatilitySensitivity(final ForexOptionVanilla optionForex, final BlackForexVannaVolgaProviderInterface smileMulticurves) { Validate.notNull(optionForex, "Forex option"); Validate.notNull(smileMulticurves, "Smile"); Validate.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data"); final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider(); final double dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency2(), optionForex.getUnderlyingForex().getPaymentTime()); final double dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency1(), optionForex.getUnderlyingForex().getPaymentTime()); final double spot = multicurves.getFxRate(optionForex.getCurrency1(), optionForex.getCurrency2()); final double forward = spot * dfForeign / dfDomestic; final SmileDeltaParameters smileAtTime = smileMulticurves.getSmile(optionForex.getCurrency1(), optionForex.getCurrency2(), optionForex.getTimeToExpiry()); final double[] strikesVV = smileAtTime.getStrike(forward); final double[] volVV = smileAtTime.getVolatility(); final double volATM = volVV[1]; final double[] priceVVATM = new double[3]; final double[] priceVVsmile = new double[3]; final double[] vegaSmile = new double[3]; final BlackFunctionData dataBlackATM = new BlackFunctionData(forward, dfDomestic, volATM); for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { // Implementation note: The adjustment for K2 is 0 final BlackFunctionData dataBlackSmile = new BlackFunctionData(forward, dfDomestic, volVV[loopvv]); final EuropeanVanillaOption optionVV = new EuropeanVanillaOption(strikesVV[loopvv], optionForex.getTimeToExpiry(), true); priceVVATM[loopvv] = BLACK_FUNCTION.getPriceFunction(optionVV).evaluate(dataBlackATM); priceVVsmile[loopvv] = BLACK_FUNCTION.getPriceFunction(optionVV).evaluate(dataBlackSmile); vegaSmile[loopvv] = BLACK_FUNCTION.getVegaFunction(optionVV).evaluate(dataBlackSmile); } // final double priceFlat = BLACK_FUNCTION.getPriceFunction(optionForex).evaluate(dataBlackATM); final double[] vega = new double[3]; final double[] x = vannaVolgaWeights(optionForex, forward, dfDomestic, strikesVV, volVV, vega); // double price = priceFlat; // for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { // price += x[loopvv] * (priceVVsmile[loopvv] - priceVVATM[loopvv]); // } // price *= Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount()) * (optionForex.isLong() ? 1.0 : -1.0); final double[] vegaReference = new double[3]; vegaReference[0] = x[0] * vegaSmile[0]; vegaReference[2] = x[2] * vegaSmile[2]; vegaReference[1] = vega[1] - x[0] * vega[0] - x[2] * vega[2]; final SurfaceValue result = new SurfaceValue(); for (int loopvv = 0; loopvv < 3; loopvv++) { final DoublesPair point = DoublesPair.of(optionForex.getTimeToExpiry(), strikesVV[loopvv]); result.add(point, vegaReference[loopvv] * Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount()) * (optionForex.isLong() ? 1.0 : -1.0)); } final PresentValueForexBlackVolatilitySensitivity sensi = new PresentValueForexBlackVolatilitySensitivity(optionForex.getUnderlyingForex().getCurrency1(), optionForex.getUnderlyingForex() .getCurrency2(), result); // TODO: Review when the currency order is not in the standard order. return sensi; } /** * Computes the curve sensitivity of the option present value. The sensitivity of the volatility and the weights on the forward (and on the curves) is not taken into account. * @param optionForex The Forex option. * @param smileMulticurves The curve and smile data. * @return The curve sensitivity. */ public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(final ForexOptionVanilla optionForex, final BlackForexVannaVolgaProviderInterface smileMulticurves) { Validate.notNull(optionForex, "Forex option"); Validate.notNull(smileMulticurves, "Smile"); Validate.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data"); final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider(); final SmileDeltaParameters smileAtTime = smileMulticurves.getSmile(optionForex.getCurrency1(), optionForex.getCurrency2(), optionForex.getTimeToExpiry()); final double payTime = optionForex.getUnderlyingForex().getPaymentTime(); // Forward sweep final double dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency2(), payTime); final double dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency1(), payTime); final double spot = multicurves.getFxRate(optionForex.getCurrency1(), optionForex.getCurrency2()); final double forward = spot * dfForeign / dfDomestic; final double[] strikesVV = smileAtTime.getStrike(forward); final double[] volVV = smileAtTime.getVolatility(); final double volATM = volVV[1]; final double[][] priceVVAdjATM = new double[3][]; final double[][] priceVVAdjsmile = new double[3][]; final BlackFunctionData dataBlackATM = new BlackFunctionData(forward, dfDomestic, volATM); for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { // Implementation note: The adjustment for K2 is 0 final BlackFunctionData dataBlackSmile = new BlackFunctionData(forward, dfDomestic, volVV[loopvv]); final EuropeanVanillaOption optionVV = new EuropeanVanillaOption(strikesVV[loopvv], optionForex.getTimeToExpiry(), true); priceVVAdjATM[loopvv] = BLACK_FUNCTION.getPriceAdjoint(optionVV, dataBlackATM); priceVVAdjsmile[loopvv] = BLACK_FUNCTION.getPriceAdjoint(optionVV, dataBlackSmile); } final double[] priceFlat = BLACK_FUNCTION.getPriceAdjoint(optionForex, dataBlackATM); final double[] x = vannaVolgaWeights(optionForex, forward, dfDomestic, strikesVV, volVV); final double factor = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount()) * (optionForex.isLong() ? 1.0 : -1.0); // double pv = priceFlat[0]; // for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { // pv += x[loopvv] * (priceVVAdjsmile[loopvv][0] - priceVVAdjATM[loopvv][0]); // } // pv *= factor; // Backward sweep final double pvBar = 1.0; final double[] priceVVATMBar = new double[3]; final double[] priceVVsmileBar = new double[3]; for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { priceVVATMBar[loopvv] = -x[loopvv] * factor * pvBar; priceVVsmileBar[loopvv] = x[loopvv] * factor * pvBar; } final double priceFlatBar = factor * pvBar; double forwardBar = priceFlat[1] * priceFlatBar; double dfDomesticBar = priceFlat[0] / dfDomestic * priceFlatBar; for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { forwardBar += priceVVAdjATM[loopvv][1] * priceVVATMBar[loopvv]; forwardBar += priceVVAdjsmile[loopvv][1] * priceVVsmileBar[loopvv]; dfDomesticBar += priceVVAdjATM[loopvv][0] / dfDomestic * priceVVATMBar[loopvv]; dfDomesticBar += priceVVAdjsmile[loopvv][0] / dfDomestic * priceVVsmileBar[loopvv]; } dfDomesticBar += -spot * dfForeign / (dfDomestic * dfDomestic) * forwardBar; final double dfForeignBar = spot / dfDomestic * forwardBar; final double rForeignBar = -payTime * dfForeign * dfForeignBar; final double rDomesticBar = -payTime * dfDomestic * dfDomesticBar; // Sensitivity object final Map<String, List<DoublesPair>> resultMap = new HashMap<>(); final List<DoublesPair> listForeign = new ArrayList<>(); listForeign.add(DoublesPair.of(payTime, rForeignBar)); resultMap.put(multicurves.getName(optionForex.getCurrency1()), listForeign); final List<DoublesPair> listDomestic = new ArrayList<>(); listDomestic.add(DoublesPair.of(payTime, rDomesticBar)); resultMap.put(multicurves.getName(optionForex.getCurrency2()), listDomestic); final MulticurveSensitivity result = MulticurveSensitivity.ofYieldDiscounting(resultMap); return MultipleCurrencyMulticurveSensitivity.of(optionForex.getUnderlyingForex().getCurrency2(), result); } /** * Computes the weights used for adjustment in the vanna-volga method. * The weight for the second adjustment (corresponding to ATM strike) is not computed as the adjustment itself is 0 for that strike in our implementation. * @param optionForex The option. * @param forward The forward FX rate. * @param dfDomestic The discounting factor to the payment date in the domestic currency. * @param strikesReference The reference strikes used for the vanna-volga method. * @param volatilitiesReference The volatilities at the reference strikes. * @return The weights. */ public double[] vannaVolgaWeights(final ForexOptionVanilla optionForex, final double forward, final double dfDomestic, final double[] strikesReference, final double[] volatilitiesReference) { return vannaVolgaWeights(optionForex, forward, dfDomestic, strikesReference, volatilitiesReference, new double[3]); } /** * Computes the weights used for adjustment in the vanna-volga method. * The weight for the second adjustment (corresponding to ATM strike) is not computed as the adjustment itself is 0 for that strike in our implementation. * @param optionForex The option. * @param forward The forward FX rate. * @param dfDomestic The discounting factor to the payment date in the domestic currency. * @param strikesReference The reference strikes used for the vanna-volga method. * @param volatilitiesReference The volatilities at the reference strikes. * @param vega The vega using the base volatility at the reference points (index 0 and 2) and at the strike (index 1). The array is changed with the method call. * @return The weights. */ public double[] vannaVolgaWeights(final ForexOptionVanilla optionForex, final double forward, final double dfDomestic, final double[] strikesReference, final double[] volatilitiesReference, final double[] vega) { final double strike = optionForex.getStrike(); final double volATM = volatilitiesReference[1]; // The reference volatility is the "middle" one, which is often ATM. final BlackFunctionData dataBlackATM = new BlackFunctionData(forward, dfDomestic, volATM); for (int loopvv = 0; loopvv < 3; loopvv = loopvv + 2) { // Implementation note: The adjustment for K2 is 0 final EuropeanVanillaOption optionVV = new EuropeanVanillaOption(strikesReference[loopvv], optionForex.getTimeToExpiry(), true); vega[loopvv] = BLACK_FUNCTION.getVegaFunction(optionVV).evaluate(dataBlackATM); } final double vegaFlat = BLACK_FUNCTION.getVegaFunction(optionForex).evaluate(dataBlackATM); vega[1] = vegaFlat; final double lnk21 = Math.log(strikesReference[1] / strikesReference[0]); final double lnk31 = Math.log(strikesReference[2] / strikesReference[0]); final double lnk32 = Math.log(strikesReference[2] / strikesReference[1]); final double[] lnk = new double[3]; for (int loopvv = 0; loopvv < 3; loopvv++) { lnk[loopvv] = Math.log(strikesReference[loopvv] / strike); } final double[] x = new double[3]; x[0] = vegaFlat * lnk[1] * lnk[2] / (vega[0] * lnk21 * lnk31); x[2] = vegaFlat * lnk[0] * lnk[1] / (vega[2] * lnk31 * lnk32); return x; } }