com.opengamma.strata.pricer.swaption.HullWhiteSwaptionPhysicalProductPricer Maven / Gradle / Ivy
/*
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.swaption;
import java.time.LocalDate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.currency.Payment;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.DoubleArrayMath;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.math.impl.statistics.distribution.NormalDistribution;
import com.opengamma.strata.math.impl.statistics.distribution.ProbabilityDistribution;
import com.opengamma.strata.pricer.DiscountingPaymentPricer;
import com.opengamma.strata.pricer.impl.rate.swap.CashFlowEquivalentCalculator;
import com.opengamma.strata.pricer.model.HullWhiteOneFactorPiecewiseConstantParametersProvider;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.common.SettlementType;
import com.opengamma.strata.product.swap.NotionalExchange;
import com.opengamma.strata.product.swap.ResolvedSwap;
import com.opengamma.strata.product.swap.ResolvedSwapLeg;
import com.opengamma.strata.product.swap.SwapLegType;
import com.opengamma.strata.product.swaption.ResolvedSwaption;
/**
* Pricer for swaption with physical settlement in Hull-White one factor model with piecewise constant volatility.
*
* Reference: Henrard, M. "The Irony in the derivatives discounting Part II: the crisis", Wilmott Journal, 2010, 2, 301-316
*/
public class HullWhiteSwaptionPhysicalProductPricer {
/**
* Normal distribution function.
*/
private static final ProbabilityDistribution NORMAL = new NormalDistribution(0, 1);
/**
* The small parameter.
*/
private static final double SMALL = 1.0e-9;
/**
* Default implementation.
*/
public static final HullWhiteSwaptionPhysicalProductPricer DEFAULT =
new HullWhiteSwaptionPhysicalProductPricer(DiscountingPaymentPricer.DEFAULT);
/**
* Pricer for {@link Payment}.
*/
private final DiscountingPaymentPricer paymentPricer;
/**
* Creates an instance.
*
* @param paymentPricer the pricer for {@link Payment}
*/
public HullWhiteSwaptionPhysicalProductPricer(DiscountingPaymentPricer paymentPricer) {
this.paymentPricer = ArgChecker.notNull(paymentPricer, "paymentPricer");
}
/**
* Calculates the present value of the swaption product.
*
* The result is expressed using the currency of the swapion.
*
* @param swaption the product
* @param ratesProvider the rates provider
* @param hwProvider the Hull-White model parameter provider
* @return the present value
*/
public CurrencyAmount presentValue(
ResolvedSwaption swaption,
RatesProvider ratesProvider,
HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {
validate(swaption, ratesProvider, hwProvider);
ResolvedSwap swap = swaption.getUnderlying();
LocalDate expiryDate = swaption.getExpiryDate();
if (expiryDate.isBefore(ratesProvider.getValuationDate())) { // Option has expired already
return CurrencyAmount.of(swap.getLegs().get(0).getCurrency(), 0d);
}
ResolvedSwapLeg cashFlowEquiv = CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, ratesProvider);
int nPayments = cashFlowEquiv.getPaymentEvents().size();
double[] alpha = new double[nPayments];
double[] discountedCashFlow = new double[nPayments];
for (int loopcf = 0; loopcf < nPayments; loopcf++) {
NotionalExchange payment = (NotionalExchange) cashFlowEquiv.getPaymentEvents().get(loopcf);
LocalDate maturityDate = payment.getPaymentDate();
alpha[loopcf] = hwProvider.alpha(ratesProvider.getValuationDate(), expiryDate, expiryDate, maturityDate);
discountedCashFlow[loopcf] = paymentPricer.presentValueAmount(payment.getPayment(), ratesProvider);
}
double omega = (swap.getLegs(SwapLegType.FIXED).get(0).getPayReceive().isPay() ? -1d : 1d);
double kappa = computeKappa(hwProvider, discountedCashFlow, alpha, omega);
double pv = 0.0;
for (int loopcf = 0; loopcf < nPayments; loopcf++) {
pv += discountedCashFlow[loopcf] * NORMAL.getCDF(omega * (kappa + alpha[loopcf]));
}
return CurrencyAmount.of(cashFlowEquiv.getCurrency(), pv * (swaption.getLongShort().isLong() ? 1d : -1d));
}
//-------------------------------------------------------------------------
/**
* Calculates the currency exposure of the swaption product.
*
* @param swaption the product
* @param ratesProvider the rates provider
* @param hwProvider the Hull-White model parameter provider
* @return the currency exposure
*/
public MultiCurrencyAmount currencyExposure(
ResolvedSwaption swaption,
RatesProvider ratesProvider,
HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {
return MultiCurrencyAmount.of(presentValue(swaption, ratesProvider, hwProvider));
}
//-------------------------------------------------------------------------
/**
* Calculates the present value sensitivity of the swaption product.
*
* The present value sensitivity of the product is the sensitivity of the present value to
* the underlying curves.
*
* @param swaption the product
* @param ratesProvider the rates provider
* @param hwProvider the Hull-White model parameter provider
* @return the point sensitivity to the rate curves
*/
public PointSensitivityBuilder presentValueSensitivityRates(
ResolvedSwaption swaption,
RatesProvider ratesProvider,
HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {
validate(swaption, ratesProvider, hwProvider);
ResolvedSwap swap = swaption.getUnderlying();
LocalDate expiryDate = swaption.getExpiryDate();
if (expiryDate.isBefore(ratesProvider.getValuationDate())) { // Option has expired already
return PointSensitivityBuilder.none();
}
ImmutableMap cashFlowEquivSensi =
CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivitySwap(swap, ratesProvider);
ImmutableList list = cashFlowEquivSensi.keySet().asList();
ImmutableList listSensi = cashFlowEquivSensi.values().asList();
int nPayments = list.size();
double[] alpha = new double[nPayments];
double[] discountedCashFlow = new double[nPayments];
for (int loopcf = 0; loopcf < nPayments; loopcf++) {
Payment payment = list.get(loopcf);
alpha[loopcf] = hwProvider.alpha(ratesProvider.getValuationDate(), expiryDate, expiryDate, payment.getDate());
discountedCashFlow[loopcf] = paymentPricer.presentValueAmount(payment, ratesProvider);
}
double omega = (swap.getLegs(SwapLegType.FIXED).get(0).getPayReceive().isPay() ? -1d : 1d);
double kappa = computeKappa(hwProvider, discountedCashFlow, alpha, omega);
PointSensitivityBuilder point = PointSensitivityBuilder.none();
for (int loopcf = 0; loopcf < nPayments; loopcf++) {
Payment payment = list.get(loopcf);
double cdf = NORMAL.getCDF(omega * (kappa + alpha[loopcf]));
point = point.combinedWith(paymentPricer.presentValueSensitivity(payment, ratesProvider).multipliedBy(cdf));
if (!listSensi.get(loopcf).equals(PointSensitivityBuilder.none())) {
point = point.combinedWith(listSensi.get(loopcf)
.multipliedBy(cdf * ratesProvider.discountFactor(payment.getCurrency(), payment.getDate())));
}
}
return swaption.getLongShort().isLong() ? point : point.multipliedBy(-1d);
}
//-------------------------------------------------------------------------
/**
* Calculates the present value sensitivity to piecewise constant volatility parameters of the Hull-White model.
*
* @param swaption the product
* @param ratesProvider the rates provider
* @param hwProvider the Hull-White model parameter provider
* @return the present value Hull-White model parameter sensitivity of the swaption product
*/
public DoubleArray presentValueSensitivityModelParamsHullWhite(
ResolvedSwaption swaption,
RatesProvider ratesProvider,
HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {
validate(swaption, ratesProvider, hwProvider);
ResolvedSwap swap = swaption.getUnderlying();
LocalDate expiryDate = swaption.getExpiryDate();
if (expiryDate.isBefore(ratesProvider.getValuationDate())) { // Option has expired already
return DoubleArray.EMPTY;
}
ResolvedSwapLeg cashFlowEquiv = CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, ratesProvider);
int nPayments = cashFlowEquiv.getPaymentEvents().size();
double[] alpha = new double[nPayments];
double[][] alphaAdjoint = new double[nPayments][];
double[] discountedCashFlow = new double[nPayments];
for (int loopcf = 0; loopcf < nPayments; loopcf++) {
NotionalExchange payment = (NotionalExchange) cashFlowEquiv.getPaymentEvents().get(loopcf);
ValueDerivatives valueDeriv = hwProvider.alphaAdjoint(
ratesProvider.getValuationDate(), expiryDate, expiryDate, payment.getPaymentDate());
alpha[loopcf] = valueDeriv.getValue();
alphaAdjoint[loopcf] = valueDeriv.getDerivatives().toArray();
discountedCashFlow[loopcf] = paymentPricer.presentValueAmount(payment.getPayment(), ratesProvider);
}
double omega = (swap.getLegs(SwapLegType.FIXED).get(0).getPayReceive().isPay() ? -1d : 1d);
double kappa = computeKappa(hwProvider, discountedCashFlow, alpha, omega);
int nParams = alphaAdjoint[0].length;
if (Math.abs(kappa) > 1d / SMALL) { // decays exponentially
return DoubleArray.filled(nParams);
}
double[] pvSensi = new double[nParams];
double sign = (swaption.getLongShort().isLong() ? 1d : -1d);
for (int i = 0; i < nParams; ++i) {
for (int loopcf = 0; loopcf < nPayments; loopcf++) {
pvSensi[i] += sign * discountedCashFlow[loopcf] * NORMAL.getPDF(omega * (kappa + alpha[loopcf])) *
omega * alphaAdjoint[loopcf][i];
}
}
return DoubleArray.ofUnsafe(pvSensi);
}
//-------------------------------------------------------------------------
// validate that the rates and volatilities providers are coherent
private void validate(ResolvedSwaption swaption, RatesProvider ratesProvider,
HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider) {
ArgChecker.isTrue(hwProvider.getValuationDateTime().toLocalDate().equals(ratesProvider.getValuationDate()),
"Hull-White model data and rate data should be for the same date");
ArgChecker.isFalse(swaption.getUnderlying().isCrossCurrency(), "underlying swap should be single currency");
ArgChecker.isTrue(swaption.getSwaptionSettlement().getSettlementType().equals(SettlementType.PHYSICAL),
"swaption should be physical settlement");
}
// handling short time to expiry
private double computeKappa(HullWhiteOneFactorPiecewiseConstantParametersProvider hwProvider,
double[] discountedCashFlow, double[] alpha, double omega) {
double kappa = 0d;
if (DoubleArrayMath.fuzzyEqualsZero(alpha, SMALL)) { // threshold coherent to rootfinder in kappa computation
double totalPv = DoubleArrayMath.sum(discountedCashFlow);
kappa = totalPv * omega > 0d ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
} else {
kappa = hwProvider.getModel().kappa(DoubleArray.ofUnsafe(discountedCashFlow), DoubleArray.ofUnsafe(alpha));
}
return kappa;
}
}