com.opengamma.strata.pricer.fxopt.ImpliedTrinomialTreeFxSingleBarrierOptionProductPricer Maven / Gradle / Ivy
/*
* 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 java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.collect.ImmutableMap;
import com.google.common.math.DoubleMath;
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.curve.Curve;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
import com.opengamma.strata.pricer.DiscountFactors;
import com.opengamma.strata.pricer.impl.tree.ConstantContinuousSingleBarrierKnockoutFunction;
import com.opengamma.strata.pricer.impl.tree.EuropeanVanillaOptionFunction;
import com.opengamma.strata.pricer.impl.tree.TrinomialTree;
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider;
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 under implied trinomial tree.
*
* This function provides the ability to price an {@link ResolvedFxSingleBarrierOption}.
*
* 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 ImpliedTrinomialTreeFxSingleBarrierOptionProductPricer {
/**
* The trinomial tree.
*/
private static final TrinomialTree TREE = new TrinomialTree();
/**
* Small parameter.
*/
private static final double SMALL = 1.0e-12;
/**
* Default number of time steps.
*/
private static final int NUM_STEPS_DEFAULT = 51;
/**
* Default implementation.
*/
public static final ImpliedTrinomialTreeFxSingleBarrierOptionProductPricer DEFAULT =
new ImpliedTrinomialTreeFxSingleBarrierOptionProductPricer(NUM_STEPS_DEFAULT);
/**
* Number of time steps.
*/
private final ImpliedTrinomialTreeFxOptionCalibrator calibrator;
/**
* Pricer with the default number of time steps.
*/
public ImpliedTrinomialTreeFxSingleBarrierOptionProductPricer() {
this(NUM_STEPS_DEFAULT);
}
/**
* Pricer with the specified number of time steps.
*
* @param nSteps number of time steps
*/
public ImpliedTrinomialTreeFxSingleBarrierOptionProductPricer(int nSteps) {
this.calibrator = new ImpliedTrinomialTreeFxOptionCalibrator(nSteps);
}
//-------------------------------------------------------------------------
/**
* Obtains the calibrator.
*
* @return the calibrator
*/
public ImpliedTrinomialTreeFxOptionCalibrator getCalibrator() {
return calibrator;
}
//-------------------------------------------------------------------------
/**
* Calculates the price of the FX barrier option product.
*
* 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 {@linkplain #presentValue(ResolvedFxSingleBarrierOption, RatesProvider, BlackFxOptionVolatilities) presentValue}
* for scaling and currency.
*
* The trinomial tree is first calibrated to Black volatilities,
* then the price is computed based on the calibrated tree.
*
* @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) {
RecombiningTrinomialTreeData treeData =
calibrator.calibrateTrinomialTree(option.getUnderlyingOption(), ratesProvider, volatilities);
return price(option, ratesProvider, volatilities, treeData);
}
/**
* Calculates the price of the FX barrier option product.
*
* 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 {@linkplain #presentValue(ResolvedFxSingleBarrierOption, RatesProvider, BlackFxOptionVolatilities, RecombiningTrinomialTreeData) presnetValue}
* for scaling and currency.
*
* This assumes the tree is already calibrated and the tree data is stored as {@code RecombiningTrinomialTreeData}.
* The tree data should be consistent with the pricer and other inputs, see {@link #validateData}.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @param treeData the trinomial tree data
* @return the price of the product
*/
public double price(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities,
RecombiningTrinomialTreeData treeData) {
return priceDerivatives(option, ratesProvider, volatilities, treeData).getValue();
}
//-------------------------------------------------------------------------
/**
* Calculates the present value of the FX barrier option product.
*
* The present value of the product is the value on the valuation date.
* It is expressed in the counter currency.
*
* The trinomial tree is first calibrated to Black volatilities,
* then the price is computed based on the calibrated tree.
*
* @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) {
RecombiningTrinomialTreeData treeData =
calibrator.calibrateTrinomialTree(option.getUnderlyingOption(), ratesProvider, volatilities);
return presentValue(option, ratesProvider, volatilities, treeData);
}
/**
* Calculates the present value of the FX barrier option product.
*
* The present value of the product is the value on the valuation date.
* It is expressed in the counter currency.
*
* This assumes the tree is already calibrated and the tree data is stored as {@code RecombiningTrinomialTreeData}.
* The tree data should be consistent with the pricer and other inputs, see {@link #validateData}.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @param treeData the trinomial tree data
* @return the present value of the product
*/
public CurrencyAmount presentValue(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities,
RecombiningTrinomialTreeData treeData) {
double price = price(option, ratesProvider, volatilities, treeData);
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
return CurrencyAmount.of(underlyingOption.getCounterCurrency(), signedNotional(underlyingOption) * price);
}
//-------------------------------------------------------------------------
/**
* Calculates the present value sensitivity of the FX barrier option product.
*
* The present value sensitivity of the product is the sensitivity of {@link #presentValue} to
* the underlying curve parameters.
*
* The sensitivity is computed by bump and re-price.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @return the present value of the product
*/
public CurrencyParameterSensitivities presentValueSensitivityRates(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities) {
RecombiningTrinomialTreeData baseTreeData =
calibrator.calibrateTrinomialTree(option.getUnderlyingOption(), ratesProvider, volatilities);
return presentValueSensitivityRates(option, ratesProvider, volatilities, baseTreeData);
}
/**
* Calculates the present value sensitivity of the FX barrier option product.
*
* The present value sensitivity of the product is the sensitivity of {@link #presentValue} to
* the underlying curve parameters.
*
* The sensitivity is computed by bump and re-price.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @param baseTreeData the trinomial tree data
* @return the present value of the product
*/
public CurrencyParameterSensitivities presentValueSensitivityRates(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities,
RecombiningTrinomialTreeData baseTreeData) {
ArgChecker.isTrue(baseTreeData.getNumberOfSteps() == calibrator.getNumberOfSteps(),
"the number of steps mismatch between pricer and trinomial tree data");
double shift = 1.0e-5;
CurrencyAmount pvBase = presentValue(option, ratesProvider, volatilities, baseTreeData);
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
ResolvedFxSingle underlyingFx = underlyingOption.getUnderlying();
CurrencyPair currencyPair = underlyingFx.getCurrencyPair();
ImmutableRatesProvider immRatesProvider = ratesProvider.toImmutableRatesProvider();
ImmutableMap baseCurves = immRatesProvider.getDiscountCurves();
CurrencyParameterSensitivities result = CurrencyParameterSensitivities.empty();
for (Entry entry : baseCurves.entrySet()) {
if (currencyPair.contains(entry.getKey())) {
Curve curve = entry.getValue();
int nParams = curve.getParameterCount();
DoubleArray sensitivity = DoubleArray.of(nParams, i -> {
Curve dscBumped = curve.withParameter(i, curve.getParameter(i) + shift);
Map mapBumped = new HashMap<>(baseCurves);
mapBumped.put(entry.getKey(), dscBumped);
ImmutableRatesProvider providerDscBumped = immRatesProvider.toBuilder().discountCurves(mapBumped).build();
double pvBumped = presentValue(option, providerDscBumped, volatilities).getAmount();
return (pvBumped - pvBase.getAmount()) / shift;
});
result = result.combinedWith(curve.createParameterSensitivity(pvBase.getCurrency(), sensitivity));
}
}
return result;
}
//-------------------------------------------------------------------------
/**
* Calculates the currency exposure of the FX barrier option product.
*
* The trinomial tree is first calibrated to Black volatilities,
* then the price is computed based on the calibrated tree.
*
* @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) {
RecombiningTrinomialTreeData treeData =
calibrator.calibrateTrinomialTree(option.getUnderlyingOption(), ratesProvider, volatilities);
return currencyExposure(option, ratesProvider, volatilities, treeData);
}
/**
* Calculates the currency exposure of the FX barrier option product.
*
* This assumes the tree is already calibrated and the tree data is stored as {@code RecombiningTrinomialTreeData}.
* The tree data should be consistent with the pricer and other inputs, see {@link #validateData}.
*
* @param option the option product
* @param ratesProvider the rates provider
* @param volatilities the Black volatility provider
* @param treeData the trinomial tree data
* @return the currency exposure
*/
public MultiCurrencyAmount currencyExposure(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities,
RecombiningTrinomialTreeData treeData) {
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
ValueDerivatives priceDerivatives = priceDerivatives(option, ratesProvider, volatilities, treeData);
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);
}
//-------------------------------------------------------------------------
private ValueDerivatives priceDerivatives(
ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities,
RecombiningTrinomialTreeData data) {
validate(option, ratesProvider, volatilities);
validateData(option, ratesProvider, volatilities, data);
int nSteps = data.getNumberOfSteps();
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
double timeToExpiry = data.getTime(nSteps);
ResolvedFxSingle underlyingFx = underlyingOption.getUnderlying();
Currency ccyBase = underlyingFx.getCounterCurrencyPayment().getCurrency();
Currency ccyCounter = underlyingFx.getCounterCurrencyPayment().getCurrency();
DiscountFactors baseDiscountFactors = ratesProvider.discountFactors(ccyBase);
DiscountFactors counterDiscountFactors = ratesProvider.discountFactors(ccyCounter);
double rebateAtExpiry = 0d; // used to price knock-in option
double rebateAtExpiryDerivative = 0d; // used to price knock-in option
double notional = Math.abs(underlyingFx.getBaseCurrencyPayment().getAmount());
double[] rebateArray = new double[nSteps + 1];
SimpleConstantContinuousBarrier barrier = (SimpleConstantContinuousBarrier) option.getBarrier();
if (option.getRebate().isPresent()) {
CurrencyAmount rebateCurrencyAmount = option.getRebate().get();
double rebatePerUnit = rebateCurrencyAmount.getAmount() / notional;
boolean isCounter = rebateCurrencyAmount.getCurrency().equals(ccyCounter);
double rebate = isCounter ? rebatePerUnit : rebatePerUnit * barrier.getBarrierLevel();
if (barrier.getKnockType().isKnockIn()) { // use in-out parity
double dfCounterAtExpiry = counterDiscountFactors.discountFactor(timeToExpiry);
double dfBaseAtExpiry = baseDiscountFactors.discountFactor(timeToExpiry);
for (int i = 0; i < nSteps + 1; ++i) {
rebateArray[i] = isCounter ?
rebate * dfCounterAtExpiry / counterDiscountFactors.discountFactor(data.getTime(i)) :
rebate * dfBaseAtExpiry / baseDiscountFactors.discountFactor(data.getTime(i));
}
if (isCounter) {
rebateAtExpiry = rebatePerUnit * dfCounterAtExpiry;
} else {
rebateAtExpiry = rebatePerUnit * data.getSpot() * dfBaseAtExpiry;
rebateAtExpiryDerivative = rebatePerUnit * dfBaseAtExpiry;
}
} else {
Arrays.fill(rebateArray, rebate);
}
}
ConstantContinuousSingleBarrierKnockoutFunction barrierFunction = ConstantContinuousSingleBarrierKnockoutFunction.of(
underlyingOption.getStrike(),
timeToExpiry,
underlyingOption.getPutCall(),
nSteps,
barrier.getBarrierType(),
barrier.getBarrierLevel(),
DoubleArray.ofUnsafe(rebateArray));
ValueDerivatives barrierPrice = TREE.optionPriceAdjoint(barrierFunction, data);
if (barrier.getKnockType().isKnockIn()) { // use in-out parity
EuropeanVanillaOptionFunction vanillaFunction = EuropeanVanillaOptionFunction.of(
underlyingOption.getStrike(), timeToExpiry, underlyingOption.getPutCall(), nSteps);
ValueDerivatives vanillaPrice = TREE.optionPriceAdjoint(vanillaFunction, data);
return ValueDerivatives.of(vanillaPrice.getValue() + rebateAtExpiry - barrierPrice.getValue(),
DoubleArray.of(vanillaPrice.getDerivative(0) + rebateAtExpiryDerivative - barrierPrice.getDerivative(0)));
}
return barrierPrice;
}
//-------------------------------------------------------------------------
private void validateData(ResolvedFxSingleBarrierOption option,
RatesProvider ratesProvider,
BlackFxOptionVolatilities volatilities,
RecombiningTrinomialTreeData data) {
ResolvedFxVanillaOption underlyingOption = option.getUnderlyingOption();
ArgChecker.isTrue(DoubleMath.fuzzyEquals(data.getTime(data.getNumberOfSteps()),
volatilities.relativeTime(underlyingOption.getExpiry()), SMALL),
"time to expiry mismatch between pricing option and trinomial tree data");
ArgChecker.isTrue(DoubleMath.fuzzyEquals(data.getSpot(),
ratesProvider.fxRate(underlyingOption.getUnderlying().getCurrencyPair()), SMALL),
"today's FX rate mismatch between rates provider and trinomial tree data");
}
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());
}
}