/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.interestrate.capletstripping;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.opengamma.analytics.financial.interestrate.annuity.derivative.Annuity;
import com.opengamma.analytics.financial.interestrate.payments.derivative.CapFloorIbor;
import com.opengamma.analytics.financial.interestrate.payments.derivative.Coupon;
import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponIbor;
import com.opengamma.analytics.financial.interestrate.payments.derivative.Payment;
import com.opengamma.analytics.financial.interestrate.payments.provider.CapFloorIborInArrearsSmileModelCapGenericReplicationMethod;
import com.opengamma.analytics.financial.interestrate.payments.provider.CouponIborInArrearsSmileModelReplicationMethod;
import com.opengamma.analytics.financial.interestrate.swap.derivative.Swap;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.analytics.financial.model.volatility.SimpleOptionData;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.GeneralSmileInterpolator;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.InterpolatedSmileFunction;
import com.opengamma.analytics.financial.provider.calculator.discounting.ParRateDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueCurveSensitivityDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.generic.MarketQuoteSensitivityBlockCalculator;
import com.opengamma.analytics.financial.provider.curve.CurveBuildingBlockBundle;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderInterface;
import com.opengamma.analytics.financial.provider.description.interestrate.ParameterProviderInterface;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.ForwardSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyParameterSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.parameter.ParameterSensitivityParameterCalculator;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolator;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.analytics.math.interpolation.GridInterpolator2D;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.surface.InterpolatedDoublesSurface;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.tuple.DoublesPair;
import com.opengamma.util.tuple.Pairs;
/**
* This class wraps {@link CapletStripper} and {@link GeneralSmileInterpolator}.
* Given multi-curves and market caps, derive caplet volatility surface.
* Then the volatility surface is used to compute pv of target in-arrear product
*/
public class CouponInArrearsCalculation {
private static final PresentValueDiscountingCalculator PVDC = PresentValueDiscountingCalculator.getInstance();
private static final ParRateDiscountingCalculator PRC = ParRateDiscountingCalculator.getInstance();
private static final PresentValueCurveSensitivityDiscountingCalculator PVCSDC =
PresentValueCurveSensitivityDiscountingCalculator.getInstance();
private static final ParameterSensitivityParameterCalculator<ParameterProviderInterface> PSC =
new ParameterSensitivityParameterCalculator<>(PVCSDC);
private static final MarketQuoteSensitivityBlockCalculator<ParameterProviderInterface> MQSBC =
new MarketQuoteSensitivityBlockCalculator<>(PSC);
private final double[] _mrkPrices;
private final double[] _errors;
private final CapletStrippingResult _capletStrippingResult;
private final MulticurveProviderInterface _curves;
private final InterpolatedDoublesSurface _surface;
private final double _time;
/**
* Constructor
* @param stripper The caplet stripper
* @param caps The market caps
* @param mrkPrices The market cap prices/volatilities
* @param type PRICE or VOL
* @param errors The error values
* @param guess The guess parameters
* @param curves The multi curve
*/
public CouponInArrearsCalculation(CapletStripper stripper, List<CapFloor> caps, double[] mrkPrices,
MarketDataType type, double[] errors, DoubleMatrix1D guess, MulticurveProviderInterface curves) {
ArgumentChecker.notNull(stripper, "stripper");
ArgumentChecker.notNull(caps, "caps");
ArgumentChecker.notNull(type, "type");
ArgumentChecker.notNull(guess, "guess");
ArgumentChecker.notNull(curves, "curves");
ArgumentChecker.notNull(mrkPrices, "mrkPrices");
ArgumentChecker.notNull(errors, "errors");
_mrkPrices = Arrays.copyOf(mrkPrices, mrkPrices.length);
_errors = Arrays.copyOf(errors, errors.length);
// perform caplet stripping
long t0 = System.nanoTime();
_capletStrippingResult = stripper.solve(_mrkPrices, type, _errors, guess);
long t1 = System.nanoTime();
_time = (t1 - t0) * 1e-9;
_curves = curves;
// The stripper works with discrete caplets. Need to construct a continuous surface in order to sample at any expiry-strike
// For strippers that work with a volatility surface (e.g. the smile based strippers), we have thrown away information
// which would be useful here
DoublesPair[] expStrikes = _capletStrippingResult.getPricer().getExpiryStrikeArray();
DoubleMatrix1D vols = _capletStrippingResult.getCapletVols();
CombinedInterpolatorExtrapolator interpolator = CombinedInterpolatorExtrapolatorFactory.getInterpolator(
Interpolator1DFactory.LINEAR, Interpolator1DFactory.LINEAR_EXTRAPOLATOR);
GridInterpolator2D interpolator2D = new GridInterpolator2D(interpolator, interpolator);
_surface = new InterpolatedDoublesSurface(expStrikes, vols.getData(), interpolator2D);
}
/**
* Computes non-corrected price
* @param option caplet as option
* @return The caplet price
*/
public double simpleOptionPrice(SimpleOptionData option) {
ArgumentChecker.notNull(option, "option");
return BlackFormulaRepository.price(option, _surface.getZValue(option.getTimeToExpiry(), option.getStrike()));
}
/**
* Computes non-corrected price
* @param caplet The in-arrears caplet
* @return The caplet price
*/
public double simpleCapletPrice(CapFloorIbor caplet) {
ArgumentChecker.notNull(caplet, "caplet");
// Construct a "standard" CapFloorIbor whose paymentTime is set to be fixingPeriodEndTime
CapFloorIbor capStandard = new CapFloorIbor(caplet.getCurrency(), caplet.getFixingPeriodEndTime(),
caplet.getPaymentYearFraction(), caplet.getNotional(), caplet.getFixingTime(), caplet.getIndex(),
caplet.getFixingPeriodStartTime(), caplet.getFixingPeriodEndTime(), caplet.getFixingAccrualFactor(),
caplet.getStrike(), caplet.isCap());
SimpleOptionData option = CapFloorDecomposer.toOption(capStandard, _curves);
return simpleOptionPrice(option);
}
/**
* Compute present value of in-arrears caplet
* @param caplet The caplet being priced
* @param interpolator The smile interpolator and extrapolator
* @return The present value
*/
public MultipleCurrencyAmount presentValue(CapFloorIbor caplet, final GeneralSmileInterpolator interpolator) {
ArgumentChecker.notNull(caplet, "caplet");
ArgumentChecker.notNull(interpolator, "interpolator");
double expiry = caplet.getFixingTime();
// Pick up relevant caplet strikes and vols
double[] sampleStrikes = _capletStrippingResult.getPricer().getStrikes();
int nStrikes = sampleStrikes.length;
double[] sampleVols = new double[nStrikes];
for (int i = 0; i < nStrikes; i++) {
sampleVols[i] = _surface.getZValue(expiry, sampleStrikes[i]);
}
// construct a interpolated/extrapolated smile
double forward = caplet.accept(PRC, _curves);
InterpolatedSmileFunction smileFunction = new InterpolatedSmileFunction(interpolator, forward, sampleStrikes,
expiry, sampleVols);
// compute pv
CapFloorIborInArrearsSmileModelCapGenericReplicationMethod inArrearsCal = new CapFloorIborInArrearsSmileModelCapGenericReplicationMethod(
smileFunction);
return inArrearsCal.presentValue(caplet, _curves);
}
/**
* Compute present value of coupon in arrears
* @param couponIbor The coupon being priced
* @param interpolator The smile interpolator and extrapolator
* @return The present value
*/
public MultipleCurrencyAmount presentValue(CouponIbor couponIbor, final GeneralSmileInterpolator interpolator) {
ArgumentChecker.notNull(couponIbor, "couponIbor");
ArgumentChecker.notNull(interpolator, "interpolator");
double expiry = couponIbor.getFixingTime();
// Pick up relevant caplet strikes and vols
double[] sampleStrikes = _capletStrippingResult.getPricer().getStrikes();
int nStrikes = sampleStrikes.length;
double[] sampleVols = new double[nStrikes];
for (int i = 0; i < nStrikes; i++) {
sampleVols[i] = _surface.getZValue(expiry, sampleStrikes[i]);
}
// construct a interpolated/extrapolated smile
double forward = _curves.getSimplyCompoundForwardRate(couponIbor.getIndex(),
couponIbor.getFixingPeriodStartTime(), couponIbor.getFixingPeriodEndTime(),
couponIbor.getFixingAccrualFactor());
InterpolatedSmileFunction smileFunction = new InterpolatedSmileFunction(interpolator, forward, sampleStrikes,
expiry, sampleVols);
// compute pv
CouponIborInArrearsSmileModelReplicationMethod inArrearsCal = new CouponIborInArrearsSmileModelReplicationMethod(
smileFunction);
return inArrearsCal.presentValue(couponIbor, _curves);
}
/**
* Compute present value of in-arrears swap
* @param swap The swap being priced, assuming
* @param interpolator The smile interpolator and extrapolator
* @return The present value
*/
public MultipleCurrencyAmount presentValue(Swap<? extends Payment, ? extends Payment> swap,
final GeneralSmileInterpolator interpolator) {
ArgumentChecker.notNull(swap, "swap");
ArgumentChecker.notNull(interpolator, "interpolator");
MultipleCurrencyAmount firstPV;
MultipleCurrencyAmount secondPV;
if (swap.getFirstLeg().getNthPayment(0) instanceof CouponIbor) {
firstPV = presentValue((CouponIbor) swap.getFirstLeg().getNthPayment(0), interpolator);
for (int j = 1; j < swap.getFirstLeg().getNumberOfPayments(); j++) {
firstPV = firstPV.plus(presentValue((CouponIbor) swap.getFirstLeg().getNthPayment(j),
interpolator));
}
} else {
firstPV = swap.getFirstLeg().accept(PVDC, _curves);
}
if (swap.getSecondLeg().getNthPayment(0) instanceof CouponIbor) {
secondPV = presentValue((CouponIbor) swap.getSecondLeg().getNthPayment(0), interpolator);
for (int j = 1; j < swap.getSecondLeg().getNumberOfPayments(); j++) {
secondPV = secondPV.plus(presentValue((CouponIbor) swap.getSecondLeg().getNthPayment(j),
interpolator));
}
} else {
secondPV = swap.getSecondLeg().accept(PVDC, _curves);
}
return firstPV.plus(secondPV);
}
/**
* Compute parameter sensitivity for coupon in arrears
* @param couponIbor The coupon in arrears
* @param interpolator The smile interpolator and extrapolator
* @return The parameter sensitivity
*/
public MultipleCurrencyParameterSensitivity presentValueCurveSensitivity(final CouponIbor couponIbor,
final GeneralSmileInterpolator interpolator) {
ArgumentChecker.notNull(couponIbor, "couponIbor");
ArgumentChecker.notNull(interpolator, "interpolator");
double expiry = couponIbor.getFixingTime();
// Pick up relevant caplet strikes and vols
double[] sampleStrikes = _capletStrippingResult.getPricer().getStrikes();
int nStrikes = sampleStrikes.length;
double[] sampleVols = new double[nStrikes];
for (int i = 0; i < nStrikes; i++) {
sampleVols[i] = _surface.getZValue(expiry, sampleStrikes[i]);
}
// construct a interpolated/extrapolated smile
double forward = _curves.getSimplyCompoundForwardRate(couponIbor.getIndex(),
couponIbor.getFixingPeriodStartTime(), couponIbor.getFixingPeriodEndTime(),
couponIbor.getFixingAccrualFactor());
InterpolatedSmileFunction smileFunction = new InterpolatedSmileFunction(interpolator, forward, sampleStrikes,
expiry, sampleVols);
// compute pv
CouponIborInArrearsSmileModelReplicationMethod inArrearsCal = new CouponIborInArrearsSmileModelReplicationMethod(
smileFunction);
MultipleCurrencyMulticurveSensitivity sense = inArrearsCal.presentValueCurveSensitivity(couponIbor, _curves);
return pointToParameterSensitivity(sense);
}
private MultipleCurrencyParameterSensitivity pointToParameterSensitivity(
final MultipleCurrencyMulticurveSensitivity sensitivity) {
MultipleCurrencyParameterSensitivity result = new MultipleCurrencyParameterSensitivity();
// YieldAndDiscount
for (final Currency ccySensi : sensitivity.getCurrencies()) {
final Map<String, List<DoublesPair>> sensitivityDsc = sensitivity.getSensitivity(ccySensi)
.getYieldDiscountingSensitivities();
for (final Map.Entry<String, List<DoublesPair>> entry : sensitivityDsc.entrySet()) {
if (_curves.getAllNames().contains(entry.getKey())) {
result = result
.plus(Pairs.of(entry.getKey(), ccySensi),
new DoubleMatrix1D(_curves.parameterSensitivity(entry.getKey(), entry.getValue())));
}
}
}
// Forward
for (final Currency ccySensi : sensitivity.getCurrencies()) {
final Map<String, List<ForwardSensitivity>> sensitivityFwd = sensitivity.getSensitivity(ccySensi)
.getForwardSensitivities();
for (final Map.Entry<String, List<ForwardSensitivity>> entry : sensitivityFwd.entrySet()) {
if (_curves.getAllNames().contains(entry.getKey())) {
result = result.plus(Pairs.of(entry.getKey(), ccySensi),
new DoubleMatrix1D(_curves.parameterForwardSensitivity(entry.getKey(), entry.getValue())));
}
}
}
return result;
}
/**
* Compute parameter sensitivity of swap
* @param swap The swap
* @param blockBundle Block bundle
* @param interpolator The smile interpolator and extrapolator
* @return The parameter sensitivity
*/
public MultipleCurrencyParameterSensitivity presentValueCurveSensitivity(
final Swap<? extends Payment, ? extends Payment> swap, final CurveBuildingBlockBundle blockBundle,
final GeneralSmileInterpolator interpolator) {
ArgumentChecker.notNull(swap, "swap");
ArgumentChecker.notNull(blockBundle, "blockBundle");
ArgumentChecker.notNull(interpolator, "interpolator");
MultipleCurrencyParameterSensitivity firstSense;
MultipleCurrencyParameterSensitivity secondSense;
if (swap.getFirstLeg().getNthPayment(0) instanceof CouponIbor) {
firstSense = presentValueCurveSensitivity((CouponIbor) swap.getFirstLeg()
.getNthPayment(0), interpolator);
for (int j = 1; j < swap.getFirstLeg().getNumberOfPayments(); j++) {
firstSense = firstSense
.plus(presentValueCurveSensitivity((CouponIbor) swap.getFirstLeg().getNthPayment(j), interpolator));
}
} else {
firstSense = MQSBC.fromInstrument(swap.getFirstLeg(), _curves, blockBundle);
}
if (swap.getSecondLeg().getNthPayment(0) instanceof CouponIbor) {
secondSense = presentValueCurveSensitivity((CouponIbor) swap.getSecondLeg()
.getNthPayment(0), interpolator);
for (int j = 1; j < swap.getSecondLeg().getNumberOfPayments(); j++) {
secondSense = secondSense.plus(presentValueCurveSensitivity((CouponIbor) swap.getSecondLeg().getNthPayment(j),
interpolator));
}
} else {
secondSense = MQSBC.fromInstrument(swap.getSecondLeg(), _curves, blockBundle);
}
MultipleCurrencyParameterSensitivity res = firstSense.plus(secondSense);
return res;
}
/**
* Compute par rate of swap (assuming the first leg is fixed leg)
* @param swap The swap
* @param interpolator The smile interpolator and extrapolator
* @return The par rate
*/
public Double parRate(final Swap<? extends Payment, ? extends Payment> swap,
final GeneralSmileInterpolator interpolator) {
ArgumentChecker.notNull(swap, "swap");
ArgumentChecker.notNull(interpolator, "interpolator");
MultipleCurrencyAmount pvSecond;
Annuity<? extends Payment> annuity = swap.getFirstLeg();
ArgumentChecker.isTrue(annuity.getNthPayment(0) instanceof Coupon, "The first leg should be coupon payment");
double pvbp = 0;
for (int loopcpn = 0; loopcpn < annuity.getPayments().length; loopcpn++) {
pvbp += ((Coupon) annuity.getNthPayment(loopcpn)).getPaymentYearFraction() *
Math.abs(((Coupon) annuity.getNthPayment(loopcpn)).getNotional())
*
_curves.getDiscountFactor(annuity.getNthPayment(loopcpn).getCurrency(),
annuity.getNthPayment(loopcpn).getPaymentTime());
}
if (swap.getSecondLeg().getNthPayment(0) instanceof CouponIbor) {
pvSecond = presentValue((CouponIbor) swap.getSecondLeg().getNthPayment(0), interpolator);
for (int j = 1; j < swap.getSecondLeg().getNumberOfPayments(); j++) {
pvSecond = pvSecond.plus(presentValue((CouponIbor) swap.getSecondLeg().getNthPayment(j),
interpolator));
}
} else {
pvSecond = swap.getSecondLeg().accept(PVDC, _curves);
}
double pvSecondDouble = pvSecond.getAmount(swap.getSecondLeg().getCurrency()) *
Math.signum(((Coupon) swap.getSecondLeg().getNthPayment(0)).getNotional());
return pvSecondDouble / pvbp;
}
/**
* Gets the time.
* @return the time
*/
public double getTime() {
return _time;
}
/**
* Gets the chi-square
* @return chi-square
*/
public double getChiSq() {
return _capletStrippingResult.getChiSqr();
}
}