/**
* Copyright (C) 2011 - 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 com.opengamma.analytics.financial.forex.derivative.ForexOptionDigital;
import com.opengamma.analytics.financial.forex.method.PresentValueForexBlackVolatilityNodeSensitivityDataBundle;
import com.opengamma.analytics.financial.forex.method.PresentValueForexBlackVolatilitySensitivity;
import com.opengamma.analytics.financial.model.volatility.VolatilityAndBucketedSensitivities;
import com.opengamma.analytics.financial.model.volatility.surface.SmileDeltaTermStructureParametersStrikeInterpolation;
import com.opengamma.analytics.financial.provider.description.forex.BlackForexSmileProviderInterface;
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.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix2D;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
import com.opengamma.analytics.util.amount.SurfaceValue;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.CurrencyAmount;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.tuple.DoublesPair;
/**
* Pricing method for digital Forex option transactions with Black function and a volatility provider.
*/
public final class ForexOptionDigitalBlackSmileMethod {
/**
* The method unique instance.
*/
private static final ForexOptionDigitalBlackSmileMethod INSTANCE = new ForexOptionDigitalBlackSmileMethod();
/**
* Private constructor.
*/
private ForexOptionDigitalBlackSmileMethod() {
}
/**
* Return the unique instance of the class.
* @return The instance.
*/
public static ForexOptionDigitalBlackSmileMethod getInstance() {
return INSTANCE;
}
/**
* The normal probability distribution used.
*/
private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
/**
* Computes the present value of the digital 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 ForexOptionDigital optionForex, final BlackForexSmileProviderInterface smileMulticurves) {
ArgumentChecker.notNull(optionForex, "Forex option");
ArgumentChecker.notNull(smileMulticurves, "Smile");
ArgumentChecker.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data");
final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider();
final double expiry = optionForex.getExpirationTime();
final Currency domesticCcy;
final Currency foreignCcy;
final double strike;
final double dfDomestic;
final double dfForeign;
final double amount;
final double omega;
if (optionForex.payDomestic()) {
domesticCcy = optionForex.getUnderlyingForex().getCurrency2();
foreignCcy = optionForex.getUnderlyingForex().getCurrency1();
dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency2(), optionForex.getUnderlyingForex().getPaymentTime());
dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency1(), optionForex.getUnderlyingForex().getPaymentTime());
strike = optionForex.getStrike();
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency2().getAmount());
omega = optionForex.isCall() ? 1.0 : -1.0;
} else {
strike = 1.0 / optionForex.getStrike();
domesticCcy = optionForex.getUnderlyingForex().getCurrency1();
foreignCcy = optionForex.getUnderlyingForex().getCurrency2();
dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency1(), optionForex.getUnderlyingForex().getPaymentTime());
dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency2(), optionForex.getUnderlyingForex().getPaymentTime());
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount());
omega = optionForex.isCall() ? -1.0 : 1.0;
}
final double spot = multicurves.getFxRate(foreignCcy, domesticCcy);
final double forward = spot * dfForeign / dfDomestic;
final double volatility = smileMulticurves.getVolatility(foreignCcy, domesticCcy, optionForex.getExpirationTime(), strike, forward);
final double sigmaRootT = volatility * Math.sqrt(expiry);
final double dM = Math.log(forward / strike) / sigmaRootT - 0.5 * sigmaRootT;
final double pv = amount * dfDomestic * NORMAL.getCDF(omega * dM) * (optionForex.isLong() ? 1.0 : -1.0);
final CurrencyAmount priceCurrency = CurrencyAmount.of(domesticCcy, pv);
return MultipleCurrencyAmount.of(priceCurrency);
}
/**
* Computes the currency exposure of the digital 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 ForexOptionDigital optionForex, final BlackForexSmileProviderInterface smileMulticurves) {
ArgumentChecker.notNull(optionForex, "Forex option");
ArgumentChecker.notNull(smileMulticurves, "Smile");
ArgumentChecker.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data");
final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider();
final double expiry = optionForex.getExpirationTime();
final Currency domesticCcy;
final Currency foreignCcy;
final double strike;
final double dfDomestic;
final double dfForeign;
final double amount;
final double omega;
if (optionForex.payDomestic()) {
domesticCcy = optionForex.getUnderlyingForex().getCurrency2();
foreignCcy = optionForex.getUnderlyingForex().getCurrency1();
dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency2(), optionForex.getUnderlyingForex().getPaymentTime());
dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency1(), optionForex.getUnderlyingForex().getPaymentTime());
strike = optionForex.getStrike();
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency2().getAmount());
omega = optionForex.isCall() ? 1.0 : -1.0;
} else {
strike = 1.0 / optionForex.getStrike();
domesticCcy = optionForex.getUnderlyingForex().getCurrency1();
foreignCcy = optionForex.getUnderlyingForex().getCurrency2();
dfDomestic = multicurves.getDiscountFactor(optionForex.getCurrency1(), optionForex.getUnderlyingForex().getPaymentTime());
dfForeign = multicurves.getDiscountFactor(optionForex.getCurrency2(), optionForex.getUnderlyingForex().getPaymentTime());
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount());
omega = optionForex.isCall() ? -1.0 : 1.0;
}
final double spot = multicurves.getFxRate(foreignCcy, domesticCcy);
final double forward = spot * dfForeign / dfDomestic;
final double volatility = smileMulticurves.getVolatility(foreignCcy, domesticCcy, optionForex.getExpirationTime(), strike, forward);
final double sigmaRootT = volatility * Math.sqrt(expiry);
final double dM = Math.log(forward / strike) / sigmaRootT - 0.5 * sigmaRootT;
final double pv = amount * dfDomestic * NORMAL.getCDF(omega * dM) * (optionForex.isLong() ? 1.0 : -1.0);
final double deltaSpot = amount * dfDomestic * NORMAL.getPDF(dM) * omega / (sigmaRootT * spot) * (optionForex.isLong() ? 1.0 : -1.0);
final CurrencyAmount[] currencyExposure = new CurrencyAmount[2];
// Implementation note: foreign currency exposure = Delta_spot * amount1.
currencyExposure[0] = CurrencyAmount.of(foreignCcy, deltaSpot);
// Implementation note: domestic currency (currency 2) exposure = -Delta_spot * amount1 * spot + PV
currencyExposure[1] = CurrencyAmount.of(domesticCcy, -deltaSpot * spot + pv);
return MultipleCurrencyAmount.of(currencyExposure);
}
/**
* Computes the curve sensitivity of the option present value. The sensitivity of the volatility on the forward (and on the curves) is not taken into account. It is the curve
* sensitivity in the Black model where the volatility is suppose to be constant for curve and forward changes.
* @param optionForex The Forex option.
* @param smileMulticurves The curve and smile data.
* @return The curve sensitivity.
*/
public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(final ForexOptionDigital optionForex, final BlackForexSmileProviderInterface smileMulticurves) {
ArgumentChecker.notNull(optionForex, "Forex option");
ArgumentChecker.notNull(smileMulticurves, "Smile");
ArgumentChecker.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data");
final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider();
final double payTime = optionForex.getUnderlyingForex().getPaymentTime();
final double expiry = optionForex.getExpirationTime();
// Forward sweep
final Currency domesticCcy;
final Currency foreignCcy;
final double strike;
final double amount;
final double omega;
if (optionForex.payDomestic()) {
domesticCcy = optionForex.getUnderlyingForex().getCurrency2();
foreignCcy = optionForex.getUnderlyingForex().getCurrency1();
strike = optionForex.getStrike();
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency2().getAmount());
omega = optionForex.isCall() ? 1.0 : -1.0;
} else {
strike = 1.0 / optionForex.getStrike();
domesticCcy = optionForex.getUnderlyingForex().getCurrency1();
foreignCcy = optionForex.getUnderlyingForex().getCurrency2();
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount());
omega = optionForex.isCall() ? -1.0 : 1.0;
}
final double dfDomestic = multicurves.getDiscountFactor(domesticCcy, payTime);
final double dfForeign = multicurves.getDiscountFactor(foreignCcy, payTime);
final double spot = multicurves.getFxRate(foreignCcy, domesticCcy);
final double forward = spot * dfForeign / dfDomestic;
final double volatility = smileMulticurves.getVolatility(foreignCcy, domesticCcy, optionForex.getExpirationTime(), strike, forward);
final double sigmaRootT = volatility * Math.sqrt(expiry);
final double dM = Math.log(forward / strike) / sigmaRootT - 0.5 * sigmaRootT;
final double pv = amount * dfDomestic * NORMAL.getCDF(omega * dM) * (optionForex.isLong() ? 1.0 : -1.0);
// Backward sweep
final double pvBar = 1.0;
final double dMBar = amount * dfDomestic * NORMAL.getPDF(omega * dM) * (optionForex.isLong() ? 1.0 : -1.0) * omega * pvBar;
final double forwardBar = 1 / (forward * sigmaRootT) * dMBar;
final double dfForeignBar = spot / dfDomestic * forwardBar;
final double dfDomesticBar = -spot / (dfDomestic * dfDomestic) * dfForeign * forwardBar + pv / dfDomestic * pvBar;
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(foreignCcy), listForeign);
final List<DoublesPair> listDomestic = new ArrayList<>();
listDomestic.add(DoublesPair.of(payTime, rDomesticBar));
resultMap.put(multicurves.getName(domesticCcy), listDomestic);
final MulticurveSensitivity result = MulticurveSensitivity.ofYieldDiscounting(resultMap);
return MultipleCurrencyMulticurveSensitivity.of(domesticCcy, result);
}
/**
* Computes the volatility sensitivity of the digital option with the Black function and a volatility from a volatility surface. The sensitivity
* is computed with respect to the computed Black implied volatility and not with respect to the volatility surface input.
* @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 ForexOptionDigital optionForex, final BlackForexSmileProviderInterface smileMulticurves) {
ArgumentChecker.notNull(optionForex, "Forex option");
ArgumentChecker.notNull(smileMulticurves, "Smile");
ArgumentChecker.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data");
final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider();
final double payTime = optionForex.getUnderlyingForex().getPaymentTime();
final double expiry = optionForex.getExpirationTime();
// Forward sweep
final Currency domesticCcy;
final Currency foreignCcy;
final double strike;
final double amount;
final double omega;
if (optionForex.payDomestic()) {
domesticCcy = optionForex.getUnderlyingForex().getCurrency2();
foreignCcy = optionForex.getUnderlyingForex().getCurrency1();
strike = optionForex.getStrike();
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency2().getAmount());
omega = optionForex.isCall() ? 1.0 : -1.0;
} else {
strike = 1.0 / optionForex.getStrike();
domesticCcy = optionForex.getUnderlyingForex().getCurrency1();
foreignCcy = optionForex.getUnderlyingForex().getCurrency2();
amount = Math.abs(optionForex.getUnderlyingForex().getPaymentCurrency1().getAmount());
omega = optionForex.isCall() ? -1.0 : 1.0;
}
final double dfDomestic = multicurves.getDiscountFactor(domesticCcy, payTime);
final double dfForeign = multicurves.getDiscountFactor(foreignCcy, payTime);
final double spot = multicurves.getFxRate(foreignCcy, domesticCcy);
final double forward = spot * dfForeign / dfDomestic;
final double volatility = smileMulticurves.getVolatility(foreignCcy, domesticCcy, optionForex.getExpirationTime(), strike, forward);
final double sigmaRootT = volatility * Math.sqrt(expiry);
final double dM = Math.log(forward / strike) / sigmaRootT - 0.5 * sigmaRootT;
// Backward sweep
final double pvBar = 1.0;
final double dMBar = amount * dfDomestic * NORMAL.getPDF(omega * dM) * (optionForex.isLong() ? 1.0 : -1.0) * omega * pvBar;
final double sigmaRootTBar = (-Math.log(forward / strike) / (sigmaRootT * sigmaRootT) - 0.5) * dMBar;
final double volatilityBar = Math.sqrt(expiry) * sigmaRootTBar;
final DoublesPair point = DoublesPair.of(optionForex.getExpirationTime(), (foreignCcy == smileMulticurves.getCurrencyPair().getFirst()) ? strike : 1.0 / strike);
// Implementation note: The strike should be in the same currency order as the input data.
final SurfaceValue result = SurfaceValue.from(point, volatilityBar);
final PresentValueForexBlackVolatilitySensitivity sensi = new PresentValueForexBlackVolatilitySensitivity(foreignCcy, domesticCcy, result);
return sensi;
}
/**
* Computes the volatility sensitivity with respect to input data for a digital option with the Black function and a volatility from a volatility surface. The sensitivity
* is computed with respect to each node in the volatility surface.
* @param optionForex The Forex option.
* @param smileMulticurves The curve and smile data.
* @return The volatility node sensitivity. The sensitivity figures are, like the present value, in the domestic currency (currency 2).
*/
public PresentValueForexBlackVolatilityNodeSensitivityDataBundle presentValueBlackVolatilityNodeSensitivity(final ForexOptionDigital optionForex,
final BlackForexSmileProviderInterface smileMulticurves) {
ArgumentChecker.notNull(optionForex, "Forex option");
ArgumentChecker.notNull(smileMulticurves, "Smile");
ArgumentChecker.isTrue(smileMulticurves.checkCurrencies(optionForex.getCurrency1(), optionForex.getCurrency2()), "Option currencies not compatible with smile data");
final MulticurveProviderInterface multicurves = smileMulticurves.getMulticurveProvider();
final PresentValueForexBlackVolatilitySensitivity pointSensitivity = presentValueBlackVolatilitySensitivity(optionForex, smileMulticurves); // In dom ccy
final SmileDeltaTermStructureParametersStrikeInterpolation volatilityModel = smileMulticurves.getVolatility();
final double payTime = optionForex.getUnderlyingForex().getPaymentTime();
final double expiry = optionForex.getExpirationTime();
// Forward sweep
final Currency domesticCcy;
final Currency foreignCcy;
final double strike;
if (optionForex.payDomestic()) {
domesticCcy = optionForex.getUnderlyingForex().getCurrency2();
foreignCcy = optionForex.getUnderlyingForex().getCurrency1();
strike = optionForex.getStrike();
} else {
strike = 1.0 / optionForex.getStrike();
domesticCcy = optionForex.getUnderlyingForex().getCurrency1();
foreignCcy = optionForex.getUnderlyingForex().getCurrency2();
}
final double dfDomestic = multicurves.getDiscountFactor(domesticCcy, payTime);
final double dfForeign = multicurves.getDiscountFactor(foreignCcy, payTime);
final double spot = multicurves.getFxRate(foreignCcy, domesticCcy);
final double forward = spot * dfForeign / dfDomestic;
final VolatilityAndBucketedSensitivities volAndSensitivities = smileMulticurves.getVolatilityAndSensitivities(foreignCcy, domesticCcy, expiry, strike, forward);
final double[][] nodeWeight = volAndSensitivities.getBucketedSensitivities();
final DoublesPair point = DoublesPair.of(optionForex.getExpirationTime(), (foreignCcy == smileMulticurves.getCurrencyPair().getFirst()) ? strike : 1.0 / strike);
final double[][] vega = new double[volatilityModel.getNumberExpiration()][volatilityModel.getNumberStrike()];
for (int loopexp = 0; loopexp < volatilityModel.getNumberExpiration(); loopexp++) {
for (int loopstrike = 0; loopstrike < volatilityModel.getNumberStrike(); loopstrike++) {
vega[loopexp][loopstrike] = nodeWeight[loopexp][loopstrike] * pointSensitivity.getVega().getMap().get(point);
}
}
return new PresentValueForexBlackVolatilityNodeSensitivityDataBundle(optionForex.getUnderlyingForex().getCurrency1(), optionForex.getUnderlyingForex().getCurrency2(), new DoubleMatrix1D(
volatilityModel.getTimeToExpiration()), new DoubleMatrix1D(volatilityModel.getDeltaFull()), new DoubleMatrix2D(vega));
}
}