/**
  * Constructor of the fixed-ibor swap from its two legs. This constructor is intended to be used
  * when there is an initial floating rate defined in the swap contract - the stream of payments on
  * the floating leg then consists of a {@link CouponFixedDefinition} and then a series of {@link
  * CouponIborSpreadDefinition}.
  *
  * @param fixedLeg The fixed leg.
  * @param iborLeg The ibor leg.
  */
 public SwapFixedIborSpreadDefinition(
     final AnnuityCouponFixedDefinition fixedLeg,
     final AnnuityDefinition<? extends PaymentDefinition> iborLeg) {
   super(fixedLeg, iborLeg);
   ArgumentChecker.isTrue(
       fixedLeg.getCurrency().equals(iborLeg.getCurrency()), "legs should have the same currency");
 }
 /**
  * Vanilla swap builder from the settlement date, a CMS index and other details of a swap.
  *
  * @param settlementDate The settlement date.
  * @param cmsIndex The CMS index from which the swap is constructed.
  * @param notional The swap notional
  * @param fixedRate The swap fixed rate.
  * @param isPayer The payer flag of the fixed leg.
  * @param calendar The holiday calendar for the ibor index.
  * @return The vanilla swap.
  */
 public static SwapFixedIborDefinition from(
     final ZonedDateTime settlementDate,
     final IndexSwap cmsIndex,
     final double notional,
     final double fixedRate,
     final boolean isPayer,
     final Calendar calendar) {
   ArgumentChecker.notNull(settlementDate, "settlement date");
   ArgumentChecker.notNull(cmsIndex, "CMS index");
   ArgumentChecker.notNull(calendar, "calendar");
   final AnnuityCouponFixedDefinition fixedLeg =
       AnnuityCouponFixedDefinition.from(
           cmsIndex.getCurrency(),
           settlementDate,
           cmsIndex.getTenor(),
           cmsIndex.getFixedLegPeriod(),
           calendar,
           cmsIndex.getFixedLegDayCount(),
           cmsIndex.getIborIndex().getBusinessDayConvention(),
           cmsIndex.getIborIndex().isEndOfMonth(),
           notional,
           fixedRate,
           isPayer);
   final AnnuityCouponIborDefinition iborLeg =
       AnnuityCouponIborDefinition.from(
           settlementDate,
           cmsIndex.getTenor(),
           notional,
           cmsIndex.getIborIndex(),
           !isPayer,
           calendar);
   return new SwapFixedIborDefinition(fixedLeg, iborLeg);
 }
 /**
  * Vanilla swap builder from the settlement date, a swap generator and other details of a swap.
  *
  * @param settlementDate The settlement date.
  * @param maturityDate The swap maturity date.
  * @param generator The swap generator.
  * @param notionalFixed The fixed leg notional.
  * @param notionalIbor The ibor leg notional.
  * @param fixedRate The swap fixed rate.
  * @param isPayer The payer flag of the fixed leg.
  * @param calendar The holiday calendar for the ibor index.
  * @return The vanilla swap.
  */
 public static SwapFixedIborDefinition from(
     final ZonedDateTime settlementDate,
     final ZonedDateTime maturityDate,
     final GeneratorSwapFixedIbor generator,
     final double notionalFixed,
     final double notionalIbor,
     final double fixedRate,
     final boolean isPayer,
     final Calendar calendar) {
   ArgumentChecker.notNull(settlementDate, "Settlement date");
   ArgumentChecker.notNull(maturityDate, "Maturity date");
   ArgumentChecker.notNull(generator, "Swap generator");
   final AnnuityCouponFixedDefinition fixedLeg =
       AnnuityCouponFixedDefinition.from(
           generator.getCurrency(),
           settlementDate,
           maturityDate,
           generator.getFixedLegPeriod(),
           generator.getCalendar(),
           generator.getFixedLegDayCount(),
           generator.getIborIndex().getBusinessDayConvention(),
           generator.getIborIndex().isEndOfMonth(),
           notionalFixed,
           fixedRate,
           isPayer);
   final AnnuityCouponIborDefinition iborLeg =
       AnnuityCouponIborDefinition.from(
           settlementDate,
           maturityDate,
           notionalIbor,
           generator.getIborIndex(),
           !isPayer,
           calendar);
   return new SwapFixedIborDefinition(fixedLeg, iborLeg);
 }
 /**
  * Swap with spread builder from the all the details on the fixed and ibor leg. The currency is
  * the currency of the Ibor index.
  *
  * @param settlementDate The settlement date.
  * @param maturityDate The swap maturity date.
  * @param fixedLegPeriod The payment period for the fixed leg.
  * @param fixedLegDayCount The fixed leg day count.
  * @param fixedLegBusinessDayConvention The fixed leg business day convention.
  * @param fixedLegEOM The fixed leg end-of-month rule application.
  * @param fixedLegNotional The fixed leg notional.
  * @param fixedLegRate The fixed leg rate.
  * @param iborLegPeriod The Ibor leg payment period.
  * @param iborLegDayCount The Ibor leg day count convention.
  * @param iborLegBusinessDayConvention The Ibor leg business day convention.
  * @param iborLegEOM The Ibor leg end-of-month.
  * @param iborLegNotional The Ibor leg notional.
  * @param iborIndex The Ibor index.
  * @param iborLegSpread The Ibor leg spread.
  * @param isPayer The payer flag for the fixed leg.
  * @param calendar The holiday calendar of the ibor leg.
  * @return The swap.
  */
 public static SwapFixedIborSpreadDefinition from(
     final ZonedDateTime settlementDate,
     final ZonedDateTime maturityDate,
     final Period fixedLegPeriod,
     final DayCount fixedLegDayCount,
     final BusinessDayConvention fixedLegBusinessDayConvention,
     final boolean fixedLegEOM,
     final double fixedLegNotional,
     final double fixedLegRate,
     final Period iborLegPeriod,
     final DayCount iborLegDayCount,
     final BusinessDayConvention iborLegBusinessDayConvention,
     final boolean iborLegEOM,
     final double iborLegNotional,
     final IborIndex iborIndex,
     final double iborLegSpread,
     final boolean isPayer,
     final Calendar calendar) {
   ArgumentChecker.notNull(iborIndex, "Ibor index");
   final AnnuityCouponFixedDefinition fixedLeg =
       AnnuityCouponFixedDefinition.from(
           iborIndex.getCurrency(),
           settlementDate,
           maturityDate,
           fixedLegPeriod,
           calendar,
           fixedLegDayCount,
           fixedLegBusinessDayConvention,
           fixedLegEOM,
           fixedLegNotional,
           fixedLegRate,
           isPayer);
   final AnnuityCouponIborSpreadDefinition iborLeg =
       AnnuityCouponIborSpreadDefinition.from(
           settlementDate,
           maturityDate,
           iborLegPeriod,
           iborLegNotional,
           iborIndex,
           !isPayer,
           iborLegBusinessDayConvention,
           iborLegEOM,
           iborLegDayCount,
           iborLegSpread,
           calendar);
   return new SwapFixedIborSpreadDefinition(fixedLeg, iborLeg);
 }
/**
 * 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);
  }
}