/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.fxopt;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.CurrencyPair;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.pricer.DiscountFactors;
import com.opengamma.strata.pricer.ZeroRateSensitivity;
import com.opengamma.strata.pricer.impl.option.BlackBarrierPriceFormulaRepository;
import com.opengamma.strata.pricer.impl.option.BlackOneTouchAssetPriceFormulaRepository;
import com.opengamma.strata.pricer.impl.option.BlackOneTouchCashPriceFormulaRepository;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.fx.ResolvedFxSingle;
import com.opengamma.strata.product.fxopt.ResolvedFxSingleBarrierOption;
import com.opengamma.strata.product.fxopt.ResolvedFxVanillaOption;
import com.opengamma.strata.product.option.SimpleConstantContinuousBarrier;
/**
* Pricer for FX barrier option products in Black-Scholes world.
* <p>
* This function provides the ability to price an {@link ResolvedFxSingleBarrierOption}.
* <p>
* All of the computation is be based on the counter currency of the underlying FX transaction.
* For example, price, PV and risk measures of the product will be expressed in USD for an option on EUR/USD.
*/
public class BlackFxSingleBarrierOptionProductPricer {
/**
* Default implementation.
*/
public static final BlackFxSingleBarrierOptionProductPricer DEFAULT = new BlackFxSingleBarrierOptionProductPricer();
/**
* Pricer for barrier option without rebate.
*/
private static final BlackBarrierPriceFormulaRepository BARRIER_PRICER = new BlackBarrierPriceFormulaRepository();
/**
* Pricer for rebate.
*/
private static final BlackOneTouchAssetPriceFormulaRepository ASSET_REBATE_PRICER =
new BlackOneTouchAssetPriceFormulaRepository();
/**
* Pricer for rebate.
*/
private static final BlackOneTouchCashPriceFormulaRepository CASH_REBATE_PRICER =
new BlackOneTouchCashPriceFormulaRepository();
/**
* Creates an instance.
*/
public BlackFxSingleBarrierOptionProductPricer() {
}
//-------------------------------------------------------------------------
/**
* Calculates the present value of the FX barrier option product.
* <p>
* The present value of the product is the value on the valuation date.
* It is expressed in the counter currency.
* <p>
* The volatility used in this computation is the Black implied volatility at expiry time and strike.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the present value of the product
*/
public CurrencyAmount presentValue(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
double price = price(option, ratesProvider, volatilities);
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
return CurrencyAmount.of(underlyingOption.getCounterCurrency(), signedNotional(underlyingOption) * price);
}
/**
* Calculates the price of the FX barrier option product.
* <p>
* The price of the product is the value on the valuation date for one unit of the base currency
* and is expressed in the counter currency. The price does not take into account the long/short flag.
* See {@link #presentValue} for scaling and currency.
* <p>
* The volatility used in this computation is the Black implied volatility at expiry time and strike.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the price of the product
*/
public double price(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
validate(option, ratesProvider, volatilities);
SimpleConstantContinuousBarrier barrier = (SimpleConstantContinuousBarrier) option.getBarrier();
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
if (volatilities.relativeTime(underlyingOption.getExpiry()) < 0d) {
return 0d;
}
ResolvedFxSingle underlyingFx = underlyingOption.getUnderlying();
Currency ccyBase = underlyingFx.getBaseCurrencyPayment().getCurrency();
Currency ccyCounter = underlyingFx.getCounterCurrencyPayment().getCurrency();
CurrencyPair currencyPair = underlyingFx.getCurrencyPair();
DiscountFactors baseDiscountFactors = ratesProvider.discountFactors(ccyBase);
DiscountFactors counterDiscountFactors = ratesProvider.discountFactors(ccyCounter);
double rateBase = baseDiscountFactors.zeroRate(underlyingFx.getPaymentDate());
double rateCounter = counterDiscountFactors.zeroRate(underlyingFx.getPaymentDate());
double costOfCarry = rateCounter - rateBase;
double dfBase = baseDiscountFactors.discountFactor(underlyingFx.getPaymentDate());
double dfCounter = counterDiscountFactors.discountFactor(underlyingFx.getPaymentDate());
double todayFx = ratesProvider.fxRate(currencyPair);
double strike = underlyingOption.getStrike();
double forward = todayFx * dfBase / dfCounter;
double volatility = volatilities.volatility(currencyPair, underlyingOption.getExpiry(), strike, forward);
double timeToExpiry = volatilities.relativeTime(underlyingOption.getExpiry());
double price = BARRIER_PRICER.price(
todayFx, strike, timeToExpiry, costOfCarry, rateCounter, volatility, underlyingOption.getPutCall().isCall(), barrier);
if (option.getRebate().isPresent()) {
CurrencyAmount rebate = option.getRebate().get();
double priceRebate = rebate.getCurrency().equals(ccyCounter) ?
CASH_REBATE_PRICER.price(todayFx, timeToExpiry, costOfCarry, rateCounter, volatility, barrier.inverseKnockType()) :
ASSET_REBATE_PRICER.price(todayFx, timeToExpiry, costOfCarry, rateCounter, volatility, barrier.inverseKnockType());
price += priceRebate * rebate.getAmount() / Math.abs(underlyingFx.getBaseCurrencyPayment().getAmount());
}
return price;
}
//-------------------------------------------------------------------------
/**
* Calculates the present value sensitivity of the FX barrier option product.
* <p>
* The present value sensitivity of the product is the sensitivity of {@link #presentValue} to
* the underlying curves.
* <p>
* The volatility is fixed in this sensitivity computation, i.e., sticky-strike.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the present value curve sensitivity of the product
*/
public PointSensitivityBuilder presentValueSensitivityRatesStickyStrike(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
if (volatilities.relativeTime(underlyingOption.getExpiry()) <= 0d) {
return PointSensitivityBuilder.none();
}
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities);
ResolvedFxSingle underlyingFx = underlyingOption.getUnderlying();
CurrencyPair currencyPair = underlyingFx.getCurrencyPair();
double signedNotional = signedNotional(underlyingOption);
double counterYearFraction =
ratesProvider.discountFactors(currencyPair.getCounter()).relativeYearFraction(underlyingFx.getPaymentDate());
ZeroRateSensitivity counterSensi = ZeroRateSensitivity.of(
currencyPair.getCounter(),
counterYearFraction,
signedNotional * (priceDerivatives.getDerivative(2) + priceDerivatives.getDerivative(3)));
double baseYearFraction =
ratesProvider.discountFactors(currencyPair.getBase()).relativeYearFraction(underlyingFx.getPaymentDate());
ZeroRateSensitivity baseSensi = ZeroRateSensitivity.of(
currencyPair.getBase(),
baseYearFraction,
currencyPair.getCounter(),
-priceDerivatives.getDerivative(3) * signedNotional);
return counterSensi.combinedWith(baseSensi);
}
//-------------------------------------------------------------------------
/**
* Calculates the present value delta of the FX barrier option product.
* <p>
* The present value delta is the first derivative of {@link #presentValue} with respect to spot.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the present value delta of the product
*/
public CurrencyAmount presentValueDelta(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
double delta = delta(option, ratesProvider, volatilities);
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
return CurrencyAmount.of(underlyingOption.getCounterCurrency(), signedNotional(underlyingOption) * delta);
}
/**
* Calculates the delta of the FX barrier option product.
* <p>
* The delta is the first derivative of {@link #price} with respect to spot.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the delta of the product
*/
public double delta(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
if (volatilities.relativeTime(option.getUnderlyingOption().getExpiry()) < 0d) {
return 0d;
}
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities);
return priceDerivatives.getDerivative(0);
}
//-------------------------------------------------------------------------
/**
* Calculates the present value gamma of the FX barrier option product.
* <p>
* The present value gamma is the second derivative of {@link #presentValue} with respect to spot.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the present value gamma of the product
*/
public CurrencyAmount presentValueGamma(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
double gamma = gamma(option, ratesProvider, volatilities);
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
return CurrencyAmount.of(underlyingOption.getCounterCurrency(), signedNotional(underlyingOption) * gamma);
}
/**
* Calculates the gamma of the FX barrier option product.
* <p>
* The delta is the second derivative of {@link #price} with respect to spot.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the gamma of the product
*/
public double gamma(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities);
return priceDerivatives.getDerivative(6);
}
//-------------------------------------------------------------------------
/**
* Computes the present value sensitivity to the black volatility used in the pricing.
* <p>
* The result is a single sensitivity to the volatility used. This is also called Black vega.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the present value sensitivity
*/
public PointSensitivityBuilder presentValueSensitivityModelParamsVolatility(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
if (volatilities.relativeTime(underlyingOption.getExpiry()) <= 0d) {
return PointSensitivityBuilder.none();
}
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities);
ResolvedFxSingle underlyingFx = underlyingOption.getUnderlying();
CurrencyPair currencyPair = underlyingFx.getCurrencyPair();
Currency ccyBase = currencyPair.getBase();
Currency ccyCounter = currencyPair.getCounter();
double dfBase = ratesProvider.discountFactor(ccyBase, underlyingFx.getPaymentDate());
double dfCounter = ratesProvider.discountFactor(ccyCounter, underlyingFx.getPaymentDate());
double todayFx = ratesProvider.fxRate(currencyPair);
double forward = todayFx * dfBase / dfCounter;
return FxOptionSensitivity.of(
volatilities.getName(),
currencyPair,
volatilities.relativeTime(underlyingOption.getExpiry()),
underlyingOption.getStrike(),
forward,
ccyCounter,
priceDerivatives.getDerivative(4) * signedNotional(underlyingOption));
}
/**
* Calculates the vega of the FX barrier option product.
* <p>
* The delta is the first derivative of {@link #price} with respect to Black volatility.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the vega of the product
*/
public double vega(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities);
return priceDerivatives.getDerivative(4);
}
//-------------------------------------------------------------------------
/**
* Calculates the present value theta of the FX barrier option product.
* <p>
* The present value theta is the negative of the first derivative of {@link #presentValue} with time parameter.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the present value theta of the product
*/
public CurrencyAmount presentValueTheta(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
double theta = theta(option, ratesProvider, volatilities);
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
return CurrencyAmount.of(underlyingOption.getCounterCurrency(), signedNotional(underlyingOption) * theta);
}
/**
* Calculates the theta of the FX barrier option product.
* <p>
* The theta is the negative of the first derivative of {@link #price} with respect to time parameter.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the theta of the product
*/
public double theta(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities);
return -priceDerivatives.getDerivative(5);
}
//-------------------------------------------------------------------------
/**
* Calculates the currency exposure of the FX barrier option product.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the currency exposure
*/
public MultiCurrencyAmount currencyExposure(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
if (volatilities.relativeTime(underlyingOption.getExpiry()) < 0d) {
return MultiCurrencyAmount.empty();
}
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities);
double price = priceDerivatives.getValue();
double delta = priceDerivatives.getDerivative(0);
CurrencyPair currencyPair = underlyingOption.getUnderlying().getCurrencyPair();
double todayFx = ratesProvider.fxRate(currencyPair);
double signedNotional = signedNotional(underlyingOption);
CurrencyAmount domestic = CurrencyAmount.of(currencyPair.getCounter(), (price - delta * todayFx) * signedNotional);
CurrencyAmount foreign = CurrencyAmount.of(currencyPair.getBase(), delta * signedNotional);
return MultiCurrencyAmount.of(domestic, foreign);
}
//-------------------------------------------------------------------------
// The derivatives are [0] spot, [1] strike, [2] rate, [3] cost-of-carry, [4] volatility, [5] timeToExpiry, [6] spot twice
private ValueDerivatives priceDerivatives(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
validate(option, ratesProvider, volatilities);
SimpleConstantContinuousBarrier barrier = (SimpleConstantContinuousBarrier) option.getBarrier();
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
double[] derivatives = new double[7];
if (volatilities.relativeTime(underlyingOption.getExpiry()) < 0d) {
return ValueDerivatives.of(0d, DoubleArray.ofUnsafe(derivatives));
}
ResolvedFxSingle underlyingFx = underlyingOption.getUnderlying();
CurrencyPair currencyPair = underlyingFx.getCurrencyPair();
Currency ccyBase = currencyPair.getBase();
Currency ccyCounter = currencyPair.getCounter();
DiscountFactors baseDiscountFactors = ratesProvider.discountFactors(ccyBase);
DiscountFactors counterDiscountFactors = ratesProvider.discountFactors(ccyCounter);
double rateBase = baseDiscountFactors.zeroRate(underlyingFx.getPaymentDate());
double rateCounter = counterDiscountFactors.zeroRate(underlyingFx.getPaymentDate());
double costOfCarry = rateCounter - rateBase;
double dfBase = baseDiscountFactors.discountFactor(underlyingFx.getPaymentDate());
double dfCounter = counterDiscountFactors.discountFactor(underlyingFx.getPaymentDate());
double todayFx = ratesProvider.fxRate(currencyPair);
double strike = underlyingOption.getStrike();
double forward = todayFx * dfBase / dfCounter;
double volatility = volatilities.volatility(currencyPair, underlyingOption.getExpiry(), strike, forward);
double timeToExpiry = volatilities.relativeTime(underlyingOption.getExpiry());
ValueDerivatives valueDerivatives = BARRIER_PRICER.priceAdjoint(
todayFx, strike, timeToExpiry, costOfCarry, rateCounter, volatility, underlyingOption.getPutCall().isCall(), barrier);
if (!option.getRebate().isPresent()) {
return valueDerivatives;
}
CurrencyAmount rebate = option.getRebate().get();
ValueDerivatives valueDerivativesRebate = rebate.getCurrency().equals(ccyCounter) ?
CASH_REBATE_PRICER.priceAdjoint(todayFx, timeToExpiry, costOfCarry, rateCounter, volatility, barrier.inverseKnockType()) :
ASSET_REBATE_PRICER.priceAdjoint(todayFx, timeToExpiry, costOfCarry, rateCounter, volatility, barrier.inverseKnockType());
double rebateRate = rebate.getAmount() / Math.abs(underlyingFx.getBaseCurrencyPayment().getAmount());
double price = valueDerivatives.getValue() + rebateRate * valueDerivativesRebate.getValue();
derivatives[0] = valueDerivatives.getDerivative(0) + rebateRate * valueDerivativesRebate.getDerivative(0);
derivatives[1] = valueDerivatives.getDerivative(1);
for (int i = 2; i < 7; ++i) {
derivatives[i] = valueDerivatives.getDerivative(i) + rebateRate * valueDerivativesRebate.getDerivative(i - 1);
}
return ValueDerivatives.of(price, DoubleArray.ofUnsafe(derivatives));
}
//-------------------------------------------------------------------------
private void validate(ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
ArgChecker.isTrue(option.getBarrier() instanceof SimpleConstantContinuousBarrier,
"Barrier should be SimpleConstantContinuousBarrier");
ArgChecker.isTrue(ratesProvider.getValuationDate().isEqual(volatilities.getValuationDateTime().toLocalDate()),
"Volatility and rate data must be for the same date");
}
// signed notional amount to computed present value and value Greeks
private double signedNotional(ResolvedFxVanillaOption option) {
return (option.getLongShort().isLong() ? 1d : -1d) *
Math.abs(option.getUnderlying().getBaseCurrencyPayment().getAmount());
}
}