/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate.payments.provider; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.opengamma.analytics.financial.interestrate.annuity.derivative.AnnuityPaymentFixed; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponCMS; import com.opengamma.analytics.financial.interestrate.payments.derivative.Payment; import com.opengamma.analytics.financial.interestrate.swap.derivative.SwapFixedCoupon; import com.opengamma.analytics.financial.model.interestrate.HullWhiteOneFactorPiecewiseConstantInterestRateModel; import com.opengamma.analytics.financial.model.interestrate.definition.HullWhiteOneFactorPiecewiseConstantParameters; import com.opengamma.analytics.financial.provider.calculator.discounting.CashFlowEquivalentCalculator; import com.opengamma.analytics.financial.provider.calculator.discounting.CashFlowEquivalentCurveSensitivityCalculator; import com.opengamma.analytics.financial.provider.description.interestrate.HullWhiteOneFactorProviderInterface; 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.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.Pair; /** * Pricing method of a CMS coupon in the Hull-White (extended Vasicek) model by approximation. * <P> Reference: M. Henrard. CMS Swaps and Caps in One-Factor Gaussian Models, SSRN working paper 985551, February 2008. * Available at http://ssrn.com/abstract=985551 */ public final class CouponCMSHullWhiteApproximationMethod { /** * The method unique instance. */ private static final CouponCMSHullWhiteApproximationMethod INSTANCE = new CouponCMSHullWhiteApproximationMethod(); /** * Private constructor. */ private CouponCMSHullWhiteApproximationMethod() { } /** * Return the unique instance of the class. * @return The instance. */ public static CouponCMSHullWhiteApproximationMethod getInstance() { return INSTANCE; } /** * The model used in computations. */ private static final HullWhiteOneFactorPiecewiseConstantInterestRateModel MODEL = new HullWhiteOneFactorPiecewiseConstantInterestRateModel(); /** * The cash flow equivalent calculator used in computations. */ private static final CashFlowEquivalentCalculator CFEC = CashFlowEquivalentCalculator.getInstance(); /** * The cash flow equivalent curve sensitivity calculator used in computations. */ private static final CashFlowEquivalentCurveSensitivityCalculator CFECSC = CashFlowEquivalentCurveSensitivityCalculator.getInstance(); /** * Compute the present value of a CMS coupon with the Hull-White (extended Vasicek) model by approximation. * @param cms The CMS coupon. * @param multicurvesHW The Hull-White and multi-curves provider. * @return The coupon price. */ public MultipleCurrencyAmount presentValue(final CouponCMS cms, final HullWhiteOneFactorProviderInterface multicurvesHW) { ArgumentChecker.notNull(cms, "CMS"); ArgumentChecker.notNull(multicurvesHW, "Hull-White provider"); Currency ccy = cms.getCurrency(); HullWhiteOneFactorPiecewiseConstantParameters parameters = multicurvesHW.getHullWhiteParameters(); MulticurveProviderInterface multicurves = multicurvesHW.getMulticurveProvider(); final double expiryTime = cms.getFixingTime(); final SwapFixedCoupon<? extends Payment> swap = cms.getUnderlyingSwap(); final double dfPayment = multicurves.getDiscountFactor(ccy, cms.getPaymentTime()); final int nbFixed = cms.getUnderlyingSwap().getFixedLeg().getNumberOfPayments(); final double[] alphaFixed = new double[nbFixed]; final double[] dfFixed = new double[nbFixed]; final double[] discountedCashFlowFixed = new double[nbFixed]; for (int loopcf = 0; loopcf < nbFixed; loopcf++) { alphaFixed[loopcf] = MODEL.alpha(parameters, 0.0, expiryTime, expiryTime, swap.getFixedLeg().getNthPayment(loopcf).getPaymentTime()); dfFixed[loopcf] = multicurves.getDiscountFactor(ccy, swap.getFixedLeg().getNthPayment(loopcf).getPaymentTime()); discountedCashFlowFixed[loopcf] = dfFixed[loopcf] * swap.getFixedLeg().getNthPayment(loopcf).getPaymentYearFraction() * swap.getFixedLeg().getNthPayment(loopcf).getNotional(); } final AnnuityPaymentFixed cfeIbor = swap.getSecondLeg().accept(CFEC, multicurves); final double[] alphaIbor = new double[cfeIbor.getNumberOfPayments()]; final double[] dfIbor = new double[cfeIbor.getNumberOfPayments()]; final double[] discountedCashFlowIbor = new double[cfeIbor.getNumberOfPayments()]; for (int loopcf = 0; loopcf < cfeIbor.getNumberOfPayments(); loopcf++) { alphaIbor[loopcf] = MODEL.alpha(parameters, 0.0, expiryTime, expiryTime, cfeIbor.getNthPayment(loopcf).getPaymentTime()); dfIbor[loopcf] = multicurves.getDiscountFactor(ccy, cfeIbor.getNthPayment(loopcf).getPaymentTime()); discountedCashFlowIbor[loopcf] = dfIbor[loopcf] * cfeIbor.getNthPayment(loopcf).getAmount(); } final double alphaPayment = MODEL.alpha(parameters, 0.0, expiryTime, expiryTime, cms.getPaymentTime()); final double x0 = -alphaPayment; final double a0 = MODEL.swapRate(x0, discountedCashFlowFixed, alphaFixed, discountedCashFlowIbor, alphaIbor); final double a2 = MODEL.swapRateDx2(x0, discountedCashFlowFixed, alphaFixed, discountedCashFlowIbor, alphaIbor); final double pv = (a0 + a2 / 2) * dfPayment * cms.getNotional() * cms.getPaymentYearFraction(); return MultipleCurrencyAmount.of(cms.getCurrency(), pv); } /** * Compute the present value of a CMS coupon with the Hull-White (extended Vasicek) model by approximation. * @param cms The CMS coupon. * @param multicurvesHW The Hull-White and multi-curves provider. * @return The present value curve sensitivity. */ public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(final CouponCMS cms, final HullWhiteOneFactorProviderInterface multicurvesHW) { ArgumentChecker.notNull(cms, "CMS"); ArgumentChecker.notNull(multicurvesHW, "Hull-White provider"); Currency ccy = cms.getCurrency(); HullWhiteOneFactorPiecewiseConstantParameters parameters = multicurvesHW.getHullWhiteParameters(); MulticurveProviderInterface multicurves = multicurvesHW.getMulticurveProvider(); final double expiryTime = cms.getFixingTime(); final SwapFixedCoupon<? extends Payment> swap = cms.getUnderlyingSwap(); final double payTimeCMS = cms.getPaymentTime(); final double dfPayment = multicurves.getDiscountFactor(ccy, payTimeCMS); final int nbFixed = cms.getUnderlyingSwap().getFixedLeg().getNumberOfPayments(); final double[] alphaFixed = new double[nbFixed]; final double[] dfFixed = new double[nbFixed]; final double[] discountedCashFlowFixed = new double[nbFixed]; for (int loopcf = 0; loopcf < nbFixed; loopcf++) { alphaFixed[loopcf] = MODEL.alpha(parameters, 0.0, expiryTime, expiryTime, swap.getFixedLeg().getNthPayment(loopcf).getPaymentTime()); dfFixed[loopcf] = multicurves.getDiscountFactor(ccy, swap.getFixedLeg().getNthPayment(loopcf).getPaymentTime()); discountedCashFlowFixed[loopcf] = dfFixed[loopcf] * swap.getFixedLeg().getNthPayment(loopcf).getPaymentYearFraction() * swap.getFixedLeg().getNthPayment(loopcf).getNotional(); } final AnnuityPaymentFixed cfeIbor = swap.getSecondLeg().accept(CFEC, multicurves); final int nbIbor = cfeIbor.getNumberOfPayments(); final double[] alphaIbor = new double[cfeIbor.getNumberOfPayments()]; final double[] dfIbor = new double[cfeIbor.getNumberOfPayments()]; final double[] discountedCashFlowIbor = new double[cfeIbor.getNumberOfPayments()]; for (int loopcf = 0; loopcf < cfeIbor.getNumberOfPayments(); loopcf++) { alphaIbor[loopcf] = MODEL.alpha(parameters, 0.0, expiryTime, expiryTime, cfeIbor.getNthPayment(loopcf).getPaymentTime()); dfIbor[loopcf] = multicurves.getDiscountFactor(ccy, cfeIbor.getNthPayment(loopcf).getPaymentTime()); discountedCashFlowIbor[loopcf] = dfIbor[loopcf] * cfeIbor.getNthPayment(loopcf).getAmount(); } final double alphaPayment = MODEL.alpha(parameters, 0.0, expiryTime, expiryTime, cms.getPaymentTime()); final double x0 = -alphaPayment; final double a0 = MODEL.swapRate(x0, discountedCashFlowFixed, alphaFixed, discountedCashFlowIbor, alphaIbor); final double a2 = MODEL.swapRateDx2(x0, discountedCashFlowFixed, alphaFixed, discountedCashFlowIbor, alphaIbor); // final double pv = (a0 + a2 / 2) * dfPayment * cms.getNotional() * cms.getPaymentYearFraction(); // Backward sweep final double pvBar = 1.0; final double a2Bar = 0.5 * dfPayment * cms.getNotional() * cms.getPaymentYearFraction() * pvBar; final double a0Bar = dfPayment * cms.getNotional() * cms.getPaymentYearFraction() * pvBar; final double[] discountedCashFlowAdjIborBar0 = MODEL.swapRateDdcfi1(x0, discountedCashFlowFixed, alphaFixed, discountedCashFlowIbor, alphaIbor); final double[] discountedCashFlowAdjFixedBar0 = MODEL.swapRateDdcff1(x0, discountedCashFlowFixed, alphaFixed, discountedCashFlowIbor, alphaIbor); final Pair<double[], double[]> discountedCashFlowAdjXBar2 = MODEL.swapRateDx2Ddcf1(x0, discountedCashFlowFixed, alphaFixed, discountedCashFlowIbor, alphaIbor); final double[] discountedCashFlowAdjFixedBar2 = discountedCashFlowAdjXBar2.getFirst(); final double[] discountedCashFlowAdjIborBar2 = discountedCashFlowAdjXBar2.getSecond(); final double[] discountedCashFlowIborBar = new double[nbIbor]; for (int loopcf = 0; loopcf < nbIbor; loopcf++) { discountedCashFlowIborBar[loopcf] = discountedCashFlowAdjIborBar0[loopcf] * a0Bar + discountedCashFlowAdjIborBar2[loopcf] * a2Bar; } final double[] dfIborBar = new double[nbIbor]; for (int loopcf = 0; loopcf < nbIbor; loopcf++) { dfIborBar[loopcf] = cfeIbor.getNthPayment(loopcf).getAmount() * discountedCashFlowIborBar[loopcf]; } final double[] cfeIborAmountBar = new double[nbIbor]; for (int loopcf = 0; loopcf < nbIbor; loopcf++) { cfeIborAmountBar[loopcf] = dfIbor[loopcf] * discountedCashFlowIborBar[loopcf]; // OK } final double[] discountedCashFlowFixedBar = new double[nbFixed]; for (int loopcf = 0; loopcf < nbFixed; loopcf++) { discountedCashFlowFixedBar[loopcf] = discountedCashFlowAdjFixedBar0[loopcf] * a0Bar + discountedCashFlowAdjFixedBar2[loopcf] * a2Bar; } final double[] dfFixedBar = new double[nbFixed]; for (int loopcf = 0; loopcf < nbFixed; loopcf++) { dfFixedBar[loopcf] = swap.getFixedLeg().getNthPayment(loopcf).getPaymentYearFraction() * swap.getFixedLeg().getNthPayment(loopcf).getNotional() * discountedCashFlowFixedBar[loopcf]; } final List<DoublesPair> listDfSensi = new ArrayList<>(); for (int loopcf = 0; loopcf < nbIbor; loopcf++) { final DoublesPair dfSensi = DoublesPair.of(cfeIbor.getNthPayment(loopcf).getPaymentTime(), -cfeIbor.getNthPayment(loopcf).getPaymentTime() * dfIbor[loopcf] * dfIborBar[loopcf]); listDfSensi.add(dfSensi); } for (int loopcf = 0; loopcf < nbFixed; loopcf++) { final DoublesPair dfSensi = DoublesPair.of(swap.getFixedLeg().getNthPayment(loopcf).getPaymentTime(), -swap.getFixedLeg().getNthPayment(loopcf).getPaymentTime() * dfFixed[loopcf] * dfFixedBar[loopcf]); listDfSensi.add(dfSensi); } final Map<String, List<DoublesPair>> pvsDF = new HashMap<>(); pvsDF.put(multicurvesHW.getMulticurveProvider().getName(ccy), listDfSensi); final double dfPaymentBar = (a0 + a2 / 2) * cms.getNotional() * cms.getPaymentYearFraction() * pvBar; final DoublesPair dfPaymentSensi = DoublesPair.of(payTimeCMS, -payTimeCMS * dfPayment * dfPaymentBar); // Sensi to dfPayment listDfSensi.add(dfPaymentSensi); MulticurveSensitivity sensitivity = MulticurveSensitivity.ofYieldDiscounting(pvsDF); sensitivity = sensitivity.cleaned(); // Sensitivity from the CFE final Map<Double, MulticurveSensitivity> cfeCurveSensi = swap.accept(CFECSC, multicurvesHW.getMulticurveProvider()); for (int loopcf = 0; loopcf < nbIbor; loopcf++) { final MulticurveSensitivity sensiCfe = cfeCurveSensi.get(cfeIbor.getNthPayment(loopcf).getPaymentTime()); if (!(sensiCfe == null)) { // There is some sensitivity to that cfe. No sensi of the last cf. sensitivity = sensitivity.plus(sensiCfe.multipliedBy(cfeIborAmountBar[loopcf])); } } sensitivity = sensitivity.cleaned(); return MultipleCurrencyMulticurveSensitivity.of(ccy, sensitivity); } }