/**
* 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;
}
}