/**
 * Tests related to the pricing of CMS coupons with Hull-White (extended Vasicek) model and
 * different numerical methods.
 */
public class CouponCMSHullWhiteMethodsTest {

  private static final Calendar TARGET = new MondayToFridayCalendar("TARGET");
  private static final GeneratorSwapFixedIborMaster GENERATOR_SWAP_MASTER =
      GeneratorSwapFixedIborMaster.getInstance();
  private static final GeneratorSwapFixedIbor GENERATOR_EUR1YEURIBOR6M =
      GENERATOR_SWAP_MASTER.getGenerator("EUR1YEURIBOR6M", TARGET);
  private static final Period TENOR_SWAP = Period.ofYears(10);
  private static final IndexSwap SWAP_EUR10Y = new IndexSwap(GENERATOR_EUR1YEURIBOR6M, TENOR_SWAP);

  private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2012, 1, 17);

  // Coupon CMS: 6m fixing in advance (payment in arrears); ACT/360
  private static final Period TENOR_COUPON = Period.ofMonths(6);
  private static final Period TENOR_FIXING = Period.ofMonths(60);
  private static final DayCount ACT360 = DayCountFactory.INSTANCE.getDayCount("Actual/360");
  private static final ZonedDateTime FIXING_DATE =
      ScheduleCalculator.getAdjustedDate(
          REFERENCE_DATE,
          TENOR_FIXING,
          GENERATOR_EUR1YEURIBOR6M.getBusinessDayConvention(),
          TARGET,
          GENERATOR_EUR1YEURIBOR6M.isEndOfMonth());
  private static final ZonedDateTime START_DATE =
      ScheduleCalculator.getAdjustedDate(
          FIXING_DATE, GENERATOR_EUR1YEURIBOR6M.getSpotLag(), TARGET);
  private static final ZonedDateTime PAYMENT_DATE =
      ScheduleCalculator.getAdjustedDate(
          START_DATE,
          TENOR_COUPON,
          GENERATOR_EUR1YEURIBOR6M.getBusinessDayConvention(),
          TARGET,
          GENERATOR_EUR1YEURIBOR6M.isEndOfMonth());
  private static final double NOTIONAL = 100000000; // 100m
  private static final double ACCRUAL_FACTOR = ACT360.getDayCountFraction(START_DATE, PAYMENT_DATE);
  private static final CouponCMSDefinition CPN_CMS_DEFINITION =
      CouponCMSDefinition.from(
          PAYMENT_DATE, START_DATE, PAYMENT_DATE, ACCRUAL_FACTOR, NOTIONAL, SWAP_EUR10Y);

  private static final YieldCurveBundle CURVES = TestsDataSetsSABR.createCurves2();
  private static final String[] CURVE_NAMES = TestsDataSetsSABR.curves2Names();
  private static final HullWhiteOneFactorPiecewiseConstantParameters PARAMETERS_HW =
      TestsDataSetHullWhite.createHullWhiteParameters();
  private static final HullWhiteOneFactorPiecewiseConstantDataBundle BUNDLE_HW =
      new HullWhiteOneFactorPiecewiseConstantDataBundle(PARAMETERS_HW, CURVES);

  private static final CouponCMS CPN_CMS =
      (CouponCMS)
          CPN_CMS_DEFINITION.toDerivative(
              REFERENCE_DATE, new String[] {CURVE_NAMES[0], CURVE_NAMES[2]});

  private static final CouponCMSHullWhiteNumericalIntegrationMethod METHOD_NI =
      CouponCMSHullWhiteNumericalIntegrationMethod.getInstance();
  private static final CouponCMSHullWhiteApproximationMethod METHOD_APP =
      CouponCMSHullWhiteApproximationMethod.getInstance();
  private static final CouponCMSDiscountingMethod METHOD_DSC =
      CouponCMSDiscountingMethod.getInstance();
  private static final double TOLERANCE_PRICE = 1.0E-2;
  private static final double TOLERANCE_PRICE_APP = 5.0E+0;

  @Test
  public void presentValueNumericalIntegration() {
    CurrencyAmount pvNumericalIntegration = METHOD_NI.presentValue(CPN_CMS, BUNDLE_HW);
    double pvPrevious = 1124760.482; // From previous run
    assertEquals(
        "Coupon CMS - Hull-White - present value - numerical integration",
        pvPrevious,
        pvNumericalIntegration.getAmount(),
        TOLERANCE_PRICE);
    // Comparison with non-adjusted figures: to have the right order of magnitude
    CurrencyAmount pvDiscounting = METHOD_DSC.presentValue(CPN_CMS, BUNDLE_HW);
    assertEquals(
        "Coupon CMS - Hull-White - present value - numerical integration",
        1.0,
        pvDiscounting.getAmount() / pvNumericalIntegration.getAmount(),
        0.20);
  }

  @Test
  public void presentValueApproximation() {
    CurrencyAmount pvNumericalIntegration = METHOD_NI.presentValue(CPN_CMS, BUNDLE_HW);
    CurrencyAmount pvApproximation = METHOD_APP.presentValue(CPN_CMS, BUNDLE_HW);
    assertEquals(
        "Coupon CMS - Hull-White - present value - approximation",
        pvApproximation.getAmount(),
        pvNumericalIntegration.getAmount(),
        TOLERANCE_PRICE_APP);
  }
}
/**
 * Test class for the replication method for CMS caplet/floorlet using a SABR smile with
 * extrapolation.
 */
@Test
public class CapFloorCMSSABRExtrapolationRightReplicationMethodTest {

  private static final MulticurveProviderDiscount MULTICURVES =
      MulticurveProviderDiscountDataSets.createMulticurveEurUsd();
  private static final IborIndex EURIBOR6M =
      MulticurveProviderDiscountDataSets.getIndexesIborMulticurveEurUsd()[1];
  private static final Currency EUR = EURIBOR6M.getCurrency();
  private static final HolidayCalendar CALENDAR =
      MulticurveProviderDiscountDataSets.getEURCalendar();

  private static final SABRInterestRateParameters SABR_PARAMETER = SABRDataSets.createSABR1();
  private static final GeneratorSwapFixedIbor EUR1YEURIBOR6M =
      GeneratorSwapFixedIborMaster.getInstance().getGenerator("EUR1YEURIBOR6M", CALENDAR);
  private static final SABRSwaptionProviderDiscount SABR_MULTICURVES =
      new SABRSwaptionProviderDiscount(MULTICURVES, SABR_PARAMETER, EUR1YEURIBOR6M);

  // Swap 5Y
  private static final BusinessDayConvention BUSINESS_DAY =
      BusinessDayConventions.MODIFIED_FOLLOWING;
  private static final boolean IS_EOM = true;
  private static final Period ANNUITY_TENOR = Period.ofYears(5);
  private static final ZonedDateTime SETTLEMENT_DATE = DateUtils.getUTCDate(2020, 4, 28);
  // Fixed leg: Semi-annual bond
  private static final Period FIXED_PAYMENT_PERIOD = Period.ofMonths(6);
  private static final DayCount FIXED_DAY_COUNT = DayCounts.THIRTY_U_360;
  private static final double RATE = 0.0325;
  private static final boolean FIXED_IS_PAYER = true;
  private static final AnnuityCouponFixedDefinition FIXED_ANNUITY =
      AnnuityCouponFixedDefinition.from(
          EUR,
          SETTLEMENT_DATE,
          ANNUITY_TENOR,
          FIXED_PAYMENT_PERIOD,
          CALENDAR,
          FIXED_DAY_COUNT,
          BUSINESS_DAY,
          IS_EOM,
          1.0,
          RATE,
          FIXED_IS_PAYER);
  // Ibor leg: quarterly money
  private static final AnnuityCouponIborDefinition IBOR_ANNUITY =
      AnnuityCouponIborDefinition.from(
          SETTLEMENT_DATE, ANNUITY_TENOR, 1.0, EURIBOR6M, !FIXED_IS_PAYER, CALENDAR);
  // CMS coupon construction
  private static final IndexSwap CMS_INDEX =
      new IndexSwap(FIXED_PAYMENT_PERIOD, FIXED_DAY_COUNT, EURIBOR6M, ANNUITY_TENOR, CALENDAR);
  private static final SwapFixedIborDefinition SWAP_DEFINITION =
      new SwapFixedIborDefinition(FIXED_ANNUITY, IBOR_ANNUITY);
  private static final ZonedDateTime FIXING_DATE =
      ScheduleCalculator.getAdjustedDate(SETTLEMENT_DATE, -EURIBOR6M.getSpotLag(), CALENDAR);
  private static final ZonedDateTime ACCRUAL_START_DATE = SETTLEMENT_DATE; // pre-fixed
  private static final ZonedDateTime ACCRUAL_END_DATE =
      ScheduleCalculator.getAdjustedDate(
          ACCRUAL_START_DATE, FIXED_PAYMENT_PERIOD, BUSINESS_DAY, CALENDAR);
  private static final ZonedDateTime PAYMENT_DATE = ACCRUAL_END_DATE;
  private static final DayCount PAYMENT_DAY_COUNT = DayCounts.ACT_360;
  private static final double ACCRUAL_FACTOR =
      DayCountUtils.yearFraction(PAYMENT_DAY_COUNT, ACCRUAL_START_DATE, ACCRUAL_END_DATE);
  private static final double NOTIONAL = 10000000; // 10m
  private static final CouponCMSDefinition CMS_COUPON_RECEIVER_DEFINITION =
      CouponCMSDefinition.from(
          PAYMENT_DATE,
          ACCRUAL_START_DATE,
          ACCRUAL_END_DATE,
          ACCRUAL_FACTOR,
          NOTIONAL,
          FIXING_DATE,
          SWAP_DEFINITION,
          CMS_INDEX);
  private static final CouponCMSDefinition CMS_COUPON_PAYER_DEFINITION =
      CouponCMSDefinition.from(
          PAYMENT_DATE,
          ACCRUAL_START_DATE,
          ACCRUAL_END_DATE,
          ACCRUAL_FACTOR,
          -NOTIONAL,
          FIXING_DATE,
          SWAP_DEFINITION,
          CMS_INDEX);
  // Cap/Floor construction
  private static final double STRIKE = 0.04;
  private static final boolean IS_CAP = true;
  private static final CapFloorCMSDefinition CMS_CAP_LONG_DEFINITION =
      CapFloorCMSDefinition.from(CMS_COUPON_RECEIVER_DEFINITION, STRIKE, IS_CAP);
  private static final CapFloorCMSDefinition CMS_CAP_SHORT_DEFINITION =
      CapFloorCMSDefinition.from(CMS_COUPON_PAYER_DEFINITION, STRIKE, IS_CAP);
  private static final CapFloorCMSDefinition CMS_CAP_0_DEFINITION =
      CapFloorCMSDefinition.from(CMS_COUPON_RECEIVER_DEFINITION, 0.0, IS_CAP);
  // to derivatives
  private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2010, 8, 18);

  private static final CouponCMS CMS_COUPON =
      (CouponCMS) CMS_COUPON_RECEIVER_DEFINITION.toDerivative(REFERENCE_DATE);
  private static final CapFloorCMS CMS_CAP_0 =
      (CapFloorCMS) CMS_CAP_0_DEFINITION.toDerivative(REFERENCE_DATE);
  private static final CapFloorCMS CMS_CAP_LONG =
      (CapFloorCMS) CMS_CAP_LONG_DEFINITION.toDerivative(REFERENCE_DATE);
  private static final CapFloorCMS CMS_CAP_SHORT =
      (CapFloorCMS) CMS_CAP_SHORT_DEFINITION.toDerivative(REFERENCE_DATE);
  // Calculators & methods
  private static final CapFloorCMSSABRReplicationMethod METHOD_STANDARD_CAP =
      CapFloorCMSSABRReplicationMethod.getDefaultInstance();
  private static final CouponCMSSABRReplicationMethod METHOD_STANDARD_CPN =
      CouponCMSSABRReplicationMethod.getInstance();
  private static final CouponCMSDiscountingMethod METHOD_DSC_CPN =
      CouponCMSDiscountingMethod.getInstance();

  private static final double CUT_OFF_STRIKE = 0.10;
  private static final double MU = 2.50;
  private static final CapFloorCMSSABRExtrapolationRightReplicationMethod METHOD_EXTRAPOLATION_CAP =
      new CapFloorCMSSABRExtrapolationRightReplicationMethod(CUT_OFF_STRIKE, MU);
  private static final CouponCMSSABRExtrapolationRightReplicationMethod METHOD_EXTRAPOLATION_CPN =
      new CouponCMSSABRExtrapolationRightReplicationMethod(CUT_OFF_STRIKE, MU);
  // Calculators
  private static final PresentValueSABRSwaptionRightExtrapolationCalculator PVSSXC =
      new PresentValueSABRSwaptionRightExtrapolationCalculator(CUT_OFF_STRIKE, MU);
  private static final PresentValueCurveSensitivitySABRSwaptionRightExtrapolationCalculator
      PVCSSSXC =
          new PresentValueCurveSensitivitySABRSwaptionRightExtrapolationCalculator(
              CUT_OFF_STRIKE, MU);
  private static final PresentValueSABRSensitivitySABRSwaptionRightExtrapolationCalculator
      PVSSSSXC =
          new PresentValueSABRSensitivitySABRSwaptionRightExtrapolationCalculator(
              CUT_OFF_STRIKE, MU);

  private static final double SHIFT = 1.0E-6;
  private static final ParameterSensitivityParameterCalculator<SABRSwaptionProviderInterface>
      PS_SS_C = new ParameterSensitivityParameterCalculator<>(PVCSSSXC);
  private static final ParameterSensitivitySABRSwaptionDiscountInterpolatedFDCalculator PS_SS_FDC =
      new ParameterSensitivitySABRSwaptionDiscountInterpolatedFDCalculator(PVSSXC, SHIFT);

  private static final double TOLERANCE_PV = 1.0E-2;
  private static final double TOLERANCE_PV_DELTA = 5.0E+3; // 0.01 currency unit for 1 bp.

  /**
   * Test the present value for a CMS coupon with pricing by replication in the SABR with
   * extrapolation framework. The present value is tested against hard-coded value and cap of strike
   * 0.
   */
  public void presentValue() {
    // CMS cap/floor with strike 0 has the same price as a CMS coupon.
    final double priceCouponStd =
        METHOD_STANDARD_CPN.presentValue(CMS_COUPON, SABR_MULTICURVES).getAmount(EUR).getAmount();
    final double rateCouponStd =
        priceCouponStd
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    final double priceCouponExtra =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final double rateCouponExtra =
        priceCouponExtra
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    final double priceCouponNoAdj =
        METHOD_DSC_CPN.presentValue(CMS_COUPON, MULTICURVES).getAmount(EUR).getAmount();
    final double rateCouponNoAdj =
        priceCouponNoAdj
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    assertEquals(
        "Extrapolation: comparison with standard method", rateCouponStd > rateCouponExtra, true);
    assertEquals(
        "Extrapolation: comparison with no convexity adjustment",
        rateCouponExtra > rateCouponNoAdj,
        true);
    final double rateCouponExtraExpected = 0.0189864; // From previous run.
    assertEquals("Extrapolation: hard-coded value", rateCouponExtraExpected, rateCouponExtra, 1E-6);
    final double priceCap0Extra =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_0, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    assertEquals(
        "Extrapolation: CMS coupon vs Cap 0", priceCouponExtra, priceCap0Extra, TOLERANCE_PV);
  }

  /**
   * Tests the price of CMS coupon and cap/floor using replication in the SABR framework. Method v
   * Calculator.
   */
  public void presentValueMethodVsCalculator() {
    final double pvMethod =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final double pvCalculator =
        CMS_CAP_LONG.accept(PVSSXC, SABR_MULTICURVES).getAmount(EUR).getAmount();
    assertEquals(
        "CMS cap/floor SABR: Present value : method vs calculator",
        pvMethod,
        pvCalculator,
        TOLERANCE_PV);
  }

  /**
   * Test the present value for a CMS cap with pricing by replication in the SABR with extrapolation
   * framework. The present value is tested against hard-coded value and a long/short parity is
   * tested.
   */
  public void presentValueReplicationCap() {
    // CMS cap/floor with strike 0 has the same price as a CMS coupon.
    final double priceCapLongStd =
        METHOD_STANDARD_CAP.presentValue(CMS_CAP_LONG, SABR_MULTICURVES).getAmount(EUR).getAmount();
    final double priceCapLongExtra =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final double priceCapShortExtra =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_SHORT, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    assertEquals(
        "CMS cap by replication - Extrapolation: comparison with standard method",
        priceCapLongStd > priceCapLongExtra,
        true);
    final double priceCapExtraExpected = 30696.572; // From previous run.
    assertEquals(
        "CMS cap by replication - Extrapolation: hard-coded value",
        priceCapExtraExpected,
        priceCapLongExtra,
        TOLERANCE_PV);
    assertEquals(
        "CMS cap by replication - Extrapolation: long/short parity",
        -priceCapShortExtra,
        priceCapLongExtra,
        TOLERANCE_PV);
  }

  /**
   * Test the present value rate sensitivity for a CMS cap with pricing by replication in the SABR
   * with extrapolation framework.
   */
  public void presentValueCurveSensitivity() {
    final MultipleCurrencyParameterSensitivity pvpsCapLongExact =
        PS_SS_C.calculateSensitivity(
            CMS_CAP_LONG, SABR_MULTICURVES, SABR_MULTICURVES.getMulticurveProvider().getAllNames());
    final MultipleCurrencyParameterSensitivity pvpsCapLongFD =
        PS_SS_FDC.calculateSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    AssertSensitivityObjects.assertEquals(
        "SwaptionPhysicalFixedIborSABRMethod: presentValueCurveSensitivity ",
        pvpsCapLongExact,
        pvpsCapLongFD,
        TOLERANCE_PV_DELTA);
    final MultipleCurrencyParameterSensitivity pvpsCapShortExact =
        PS_SS_C.calculateSensitivity(
            CMS_CAP_SHORT,
            SABR_MULTICURVES,
            SABR_MULTICURVES.getMulticurveProvider().getAllNames());
    final MultipleCurrencyParameterSensitivity pvpsCapShortFD =
        PS_SS_FDC.calculateSensitivity(CMS_CAP_SHORT, SABR_MULTICURVES);
    AssertSensitivityObjects.assertEquals(
        "SwaptionPhysicalFixedIborSABRMethod: presentValueCurveSensitivity ",
        pvpsCapShortExact,
        pvpsCapShortFD,
        TOLERANCE_PV_DELTA);
  }

  /**
   * Test the present value rate sensitivity for a CMS cap with pricing by replication in the SABR
   * with extrapolation framework. Method v Calculator.
   */
  public void presentValueCurveSensitivityMethodVsCalculator() {
    final MultipleCurrencyMulticurveSensitivity pvcsMethod =
        METHOD_EXTRAPOLATION_CAP.presentValueCurveSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    final MultipleCurrencyMulticurveSensitivity pvcsCalculator =
        CMS_CAP_LONG.accept(PVCSSSXC, SABR_MULTICURVES);
    AssertSensitivityObjects.assertEquals(
        "CMS cap/floor SABR: Present value : method vs calculator",
        pvcsMethod,
        pvcsCalculator,
        TOLERANCE_PV_DELTA);
  }

  /** Tests the cap present value SABR parameters sensitivity vs finite difference. */
  public void presentValueSABRSensitivity() {
    final double pv =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final PresentValueSABRSensitivityDataBundle pvsCapLong =
        METHOD_EXTRAPOLATION_CAP.presentValueSABRSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    // SABR sensitivity vs finite difference
    final double shift = 0.0001;
    final double shiftAlpha = 0.00001;
    final double maturity =
        CMS_CAP_LONG
                .getUnderlyingSwap()
                .getFixedLeg()
                .getNthPayment(
                    CMS_CAP_LONG.getUnderlyingSwap().getFixedLeg().getNumberOfPayments() - 1)
                .getPaymentTime()
            - CMS_CAP_LONG.getSettlementTime();
    final DoublesPair expectedExpiryTenor = DoublesPair.of(CMS_CAP_LONG.getFixingTime(), maturity);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterAlphaBumped =
        SABRDataSets.createSABR1AlphaBumped(shiftAlpha);
    final SABRSwaptionProviderDiscount sabrBundleAlphaBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterAlphaBumped, EUR1YEURIBOR6M);
    final double pvLongPayerAlphaBumped =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, sabrBundleAlphaBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedAlphaSensi = (pvLongPayerAlphaBumped - pv) / shiftAlpha;
    assertEquals("Number of alpha sensitivity", pvsCapLong.getAlpha().getMap().keySet().size(), 1);
    assertEquals(
        "Alpha sensitivity expiry/tenor",
        pvsCapLong.getAlpha().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Alpha sensitivity value",
        expectedAlphaSensi,
        pvsCapLong.getAlpha().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Rho sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterRhoBumped = SABRDataSets.createSABR1RhoBumped();
    final SABRSwaptionProviderDiscount sabrBundleRhoBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterRhoBumped, EUR1YEURIBOR6M);
    final double pvLongPayerRhoBumped =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, sabrBundleRhoBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedRhoSensi = (pvLongPayerRhoBumped - pv) / shift;
    assertEquals("Number of rho sensitivity", pvsCapLong.getRho().getMap().keySet().size(), 1);
    assertEquals(
        "Rho sensitivity expiry/tenor",
        pvsCapLong.getRho().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Rho sensitivity value",
        expectedRhoSensi,
        pvsCapLong.getRho().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterNuBumped = SABRDataSets.createSABR1NuBumped();
    final SABRSwaptionProviderDiscount sabrBundleNuBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterNuBumped, EUR1YEURIBOR6M);
    final double pvLongPayerNuBumped =
        METHOD_EXTRAPOLATION_CAP
            .presentValue(CMS_CAP_LONG, sabrBundleNuBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedNuSensi = (pvLongPayerNuBumped - pv) / shift;
    assertEquals("Number of nu sensitivity", pvsCapLong.getNu().getMap().keySet().size(), 1);
    assertTrue(
        "Nu sensitivity expiry/tenor",
        pvsCapLong.getNu().getMap().keySet().contains(expectedExpiryTenor));
    assertEquals(
        "Nu sensitivity value",
        expectedNuSensi,
        pvsCapLong.getNu().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
  }

  /** Tests the coupon present value SABR parameters sensitivity vs finite difference. */
  public void presentValueSABRSensitivityCoupon() {
    final double pv =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, SABR_MULTICURVES)
            .getAmount(EUR)
            .getAmount();
    final PresentValueSABRSensitivityDataBundle pvsCpn =
        METHOD_EXTRAPOLATION_CPN.presentValueSABRSensitivity(CMS_COUPON, SABR_MULTICURVES);
    // SABR sensitivity vs finite difference
    final double shift = 0.0001;
    final double shiftAlpha = 0.00001;
    final double maturity =
        CMS_COUPON
                .getUnderlyingSwap()
                .getFixedLeg()
                .getNthPayment(
                    CMS_COUPON.getUnderlyingSwap().getFixedLeg().getNumberOfPayments() - 1)
                .getPaymentTime()
            - CMS_COUPON.getSettlementTime();
    final DoublesPair expectedExpiryTenor = DoublesPair.of(CMS_COUPON.getFixingTime(), maturity);
    // Alpha sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterAlphaBumped =
        SABRDataSets.createSABR1AlphaBumped(shiftAlpha);
    final SABRSwaptionProviderDiscount sabrBundleAlphaBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterAlphaBumped, EUR1YEURIBOR6M);
    final double pvLongPayerAlphaBumped =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, sabrBundleAlphaBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedAlphaSensi = (pvLongPayerAlphaBumped - pv) / shiftAlpha;
    assertEquals("Number of alpha sensitivity", pvsCpn.getAlpha().getMap().keySet().size(), 1);
    assertEquals(
        "Alpha sensitivity expiry/tenor",
        pvsCpn.getAlpha().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Alpha sensitivity value",
        expectedAlphaSensi,
        pvsCpn.getAlpha().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Rho sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterRhoBumped = SABRDataSets.createSABR1RhoBumped();
    final SABRSwaptionProviderDiscount sabrBundleRhoBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterRhoBumped, EUR1YEURIBOR6M);
    final double pvLongPayerRhoBumped =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, sabrBundleRhoBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedRhoSensi = (pvLongPayerRhoBumped - pv) / shift;
    assertEquals("Number of rho sensitivity", pvsCpn.getRho().getMap().keySet().size(), 1);
    assertEquals(
        "Rho sensitivity expiry/tenor",
        pvsCpn.getRho().getMap().keySet().contains(expectedExpiryTenor),
        true);
    assertEquals(
        "Rho sensitivity value",
        expectedRhoSensi,
        pvsCpn.getRho().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
    // Nu sensitivity vs finite difference computation
    final SABRInterestRateParameters sabrParameterNuBumped = SABRDataSets.createSABR1NuBumped();
    final SABRSwaptionProviderDiscount sabrBundleNuBumped =
        new SABRSwaptionProviderDiscount(MULTICURVES, sabrParameterNuBumped, EUR1YEURIBOR6M);
    final double pvLongPayerNuBumped =
        METHOD_EXTRAPOLATION_CPN
            .presentValue(CMS_COUPON, sabrBundleNuBumped)
            .getAmount(EUR)
            .getAmount();
    final double expectedNuSensi = (pvLongPayerNuBumped - pv) / shift;
    assertEquals("Number of nu sensitivity", pvsCpn.getNu().getMap().keySet().size(), 1);
    assertTrue(
        "Nu sensitivity expiry/tenor",
        pvsCpn.getNu().getMap().keySet().contains(expectedExpiryTenor));
    assertEquals(
        "Nu sensitivity value",
        expectedNuSensi,
        pvsCpn.getNu().getMap().get(expectedExpiryTenor),
        TOLERANCE_PV_DELTA);
  }

  /** Tests the present value SABR parameters sensitivity: Method vs Calculator. */
  public void presentValueSABRSensitivityMethodVsCalculator() {
    final PresentValueSABRSensitivityDataBundle pvssMethod =
        METHOD_EXTRAPOLATION_CAP.presentValueSABRSensitivity(CMS_CAP_LONG, SABR_MULTICURVES);
    final PresentValueSABRSensitivityDataBundle pvssCalculator =
        CMS_CAP_LONG.accept(PVSSSSXC, SABR_MULTICURVES);
    assertEquals(
        "CMS cap/floor SABR: Present value SABR sensitivity: method vs calculator",
        pvssMethod,
        pvssCalculator);
  }

  /** Tests the present value strike sensitivity: Cap. */
  public void presentValueStrikeSensitivityCap() {
    final double[] strikes = new double[] {0.0001, 0.0010, 0.0050, 0.0100, 0.0200, 0.0400, 0.0500};
    final int nbStrikes = strikes.length;
    final double shift = 1.0E-5;
    final double[] errorRelative = new double[nbStrikes];
    for (int loopstrike = 0; loopstrike < nbStrikes; loopstrike++) {
      final CapFloorCMSDefinition cmsCapDefinition =
          CapFloorCMSDefinition.from(CMS_COUPON_RECEIVER_DEFINITION, strikes[loopstrike], IS_CAP);
      final CapFloorCMSDefinition cmsCapShiftUpDefinition =
          CapFloorCMSDefinition.from(
              CMS_COUPON_RECEIVER_DEFINITION, strikes[loopstrike] + shift, IS_CAP);
      final CapFloorCMSDefinition cmsCapShiftDoDefinition =
          CapFloorCMSDefinition.from(
              CMS_COUPON_RECEIVER_DEFINITION, strikes[loopstrike] - shift, IS_CAP);
      final CapFloorCMS cmsCap = (CapFloorCMS) cmsCapDefinition.toDerivative(REFERENCE_DATE);
      final CapFloorCMS cmsCapShiftUp =
          (CapFloorCMS) cmsCapShiftUpDefinition.toDerivative(REFERENCE_DATE);
      final CapFloorCMS cmsCapShiftDo =
          (CapFloorCMS) cmsCapShiftDoDefinition.toDerivative(REFERENCE_DATE);
      final double pvShiftUp =
          METHOD_EXTRAPOLATION_CAP
              .presentValue(cmsCapShiftUp, SABR_MULTICURVES)
              .getAmount(EUR)
              .getAmount();
      final double pvShiftDo =
          METHOD_EXTRAPOLATION_CAP
              .presentValue(cmsCapShiftDo, SABR_MULTICURVES)
              .getAmount(EUR)
              .getAmount();
      final double sensiExpected = (pvShiftUp - pvShiftDo) / (2 * shift);
      final double sensiComputed =
          METHOD_EXTRAPOLATION_CAP.presentValueStrikeSensitivity(cmsCap, SABR_MULTICURVES);
      errorRelative[loopstrike] = (sensiExpected - sensiComputed) / sensiExpected;
      assertEquals(
          "CMS cap/floor SABR: Present value strike sensitivity " + loopstrike,
          0,
          errorRelative[loopstrike],
          5.0E-3);
    }
  }

  /**
   * Tests to estimate the impact of mu on the CMS coupon pricing. "enabled = false" for the
   * standard testing.
   */
  public void testPriceMultiMu() {
    final double[] mu = new double[] {1.10, 1.30, 1.55, 2.25, 3.50, 6.00, 15.0};
    final int nbMu = mu.length;
    final double priceCouponStd =
        METHOD_STANDARD_CPN.presentValue(CMS_COUPON, SABR_MULTICURVES).getAmount(EUR).getAmount();
    @SuppressWarnings("unused")
    final double rateCouponStd =
        priceCouponStd
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    final double[] priceCouponExtra = new double[nbMu];
    final double[] rateCouponExtra = new double[nbMu];
    for (int loopmu = 0; loopmu < nbMu; loopmu++) {
      final CouponCMSSABRExtrapolationRightReplicationMethod methodExtrapolation =
          new CouponCMSSABRExtrapolationRightReplicationMethod(CUT_OFF_STRIKE, mu[loopmu]);
      priceCouponExtra[loopmu] =
          methodExtrapolation.presentValue(CMS_COUPON, SABR_MULTICURVES).getAmount(EUR).getAmount();
      rateCouponExtra[loopmu] =
          priceCouponExtra[loopmu]
              / (CMS_COUPON.getPaymentYearFraction()
                  * CMS_COUPON.getNotional()
                  * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    }
    final double priceCouponNoAdj =
        METHOD_DSC_CPN.presentValue(CMS_COUPON, MULTICURVES).getAmount(EUR).getAmount();
    final double rateCouponNoAdj =
        priceCouponNoAdj
            / (CMS_COUPON.getPaymentYearFraction()
                * CMS_COUPON.getNotional()
                * MULTICURVES.getDiscountFactor(EUR, CMS_COUPON.getPaymentTime()));
    for (int loopmu = 1; loopmu < nbMu; loopmu++) {
      assertTrue(
          "Extrapolation: comparison with standard method",
          rateCouponExtra[loopmu - 1] > rateCouponExtra[loopmu]);
    }
    assertTrue(
        "Extrapolation: comparison with standard method",
        rateCouponExtra[nbMu - 1] > rateCouponNoAdj);
  }
}