@Test
 /** Test the present value using the method with the direct formula with extrapolation. */
 public void presentValueAboveCutOff() {
   CurrencyAmount methodPrice = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE);
   final double df =
       CURVES.getCurve(FUNDING_CURVE_NAME).getDiscountFactor(CAP_HIGH_LONG.getPaymentTime());
   final double forward = CAP_HIGH_LONG.accept(PRC, CURVES);
   final double maturity =
       CAP_HIGH_LONG.getFixingPeriodEndTime() - CAP_LONG.getFixingPeriodStartTime();
   final DoublesPair expiryMaturity = new DoublesPair(CAP_HIGH_LONG.getFixingTime(), maturity);
   final double alpha = SABR_PARAMETERS.getAlpha(expiryMaturity);
   final double beta = SABR_PARAMETERS.getBeta(expiryMaturity);
   final double rho = SABR_PARAMETERS.getRho(expiryMaturity);
   final double nu = SABR_PARAMETERS.getNu(expiryMaturity);
   final SABRFormulaData sabrParam = new SABRFormulaData(alpha, beta, rho, nu);
   final SABRExtrapolationRightFunction sabrExtrapolation =
       new SABRExtrapolationRightFunction(
           forward, sabrParam, CUT_OFF_STRIKE, CAP_HIGH_LONG.getFixingTime(), MU);
   final EuropeanVanillaOption option =
       new EuropeanVanillaOption(
           CAP_HIGH_LONG.getStrike(), CAP_HIGH_LONG.getFixingTime(), CAP_HIGH_LONG.isCap());
   final double expectedPrice =
       sabrExtrapolation.price(option)
           * CAP_HIGH_LONG.getNotional()
           * CAP_HIGH_LONG.getPaymentYearFraction()
           * df;
   assertEquals(
       "Cap/floor: SABR with extrapolation pricing", expectedPrice, methodPrice.getAmount(), 1E-2);
   methodPrice = METHOD.presentValue(CAP_HIGH_LONG, SABR_BUNDLE);
   assertEquals(
       "Cap/floor: SABR with extrapolation pricing", expectedPrice, methodPrice.getAmount(), 1E-2);
 }
  /**
   * Computes the present value of an Ibor cap/floor in arrears by replication based on the paper,
   * "Swap and Cap/Floors with Fixing in Arrears or Payment Delay," OpenGamma Quantitative
   * Documentation
   * http://developers.opengamma.com/quantitative-research/In-Arrears-and-Payment-Delay-Swaps-and-Caps-OpenGamma.pdf
   *
   * @param cap The cap/floor
   * @param curves The curves
   * @return The present value
   */
  public MultipleCurrencyAmount presentValue(
      final CapFloorIbor cap, final MulticurveProviderInterface curves) {
    ArgumentChecker.notNull(cap, "The cap/floor shoud not be null");
    ArgumentChecker.notNull(curves, "curves");
    final Currency ccy = cap.getCurrency();
    // Construct a "standard" CapFloorIbor whose paymentTime is set to be fixingPeriodEndTime
    CapFloorIbor capStandard =
        new CapFloorIbor(
            cap.getCurrency(),
            cap.getFixingPeriodEndTime(),
            cap.getPaymentYearFraction(),
            cap.getNotional(),
            cap.getFixingTime(),
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor(),
            cap.getStrike(),
            cap.isCap());
    final double forward =
        curves.getSimplyCompoundForwardRate(
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor());
    final double beta =
        (1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());

    final double df =
        curves.getDiscountFactor(capStandard.getCurrency(), capStandard.getPaymentTime());
    final double strikePart =
        (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike())
            * presentValueStandard(
                forward,
                capStandard.getStrike(),
                capStandard.getFixingTime(),
                capStandard.isCap(),
                df,
                capStandard.getNotional(),
                capStandard.getPaymentYearFraction());

    final InArrearsIntegrant integrant = new InArrearsIntegrant(capStandard, curves);
    double integralPart;
    try {
      if (cap.isCap()) {
        double atmVol = _smileFunction.getVolatility(forward);
        double upper = forward * Math.exp(6.0 * atmVol * Math.sqrt(cap.getFixingTime()));
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, strike, upper);
        double reminder = integrant.evaluate(upper) * upper;
        double error = reminder / integralPart;

        int count = 0;
        while (Math.abs(error) > REL_ERROR && count < MAX_COUNT) {
          integralPart += INTEGRATOR.integrate(integrant, upper, 2.0 * upper);
          upper *= 2.0;
          // The increase of integralPart in the next loop is bounded by reminder
          reminder = integrant.evaluate(upper) * upper;
          error = reminder / integralPart;
          ++count;
          if (count == MAX_COUNT) {
            LOGGER.info(
                "Maximum iteration count, "
                    + MAX_COUNT
                    + ", has been reached. Relative error is greater than "
                    + REL_ERROR);
          }
        }
      } else {
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, REL_TOL * strike, strike);
      }
    } catch (final Exception e) {
      throw new MathException(e);
    }
    integralPart *= 2.0 * cap.getFixingAccrualFactor();
    final double pv = (strikePart + integralPart) / beta;
    return MultipleCurrencyAmount.of(cap.getCurrency(), pv);
  }
  /**
   * Computes the pv sensitivity of an Ibor cap/floor in arrears
   *
   * @param cap The cap/floor
   * @param curves The curves
   * @return The sensitivity
   */
  public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(
      final CapFloorIbor cap, final MulticurveProviderInterface curves) {
    ArgumentChecker.notNull(cap, "The cap/floor shoud not be null");
    ArgumentChecker.notNull(curves, "curves");
    final Currency ccy = cap.getCurrency();
    // Construct a "standard" CapFloorIbor whose paymentTime is set to be fixingPeriodEndTime
    CapFloorIbor capStandard =
        new CapFloorIbor(
            cap.getCurrency(),
            cap.getFixingPeriodEndTime(),
            cap.getPaymentYearFraction(),
            cap.getNotional(),
            cap.getFixingTime(),
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor(),
            cap.getStrike(),
            cap.isCap());
    final double forward =
        curves.getSimplyCompoundForwardRate(
            cap.getIndex(),
            cap.getFixingPeriodStartTime(),
            cap.getFixingPeriodEndTime(),
            cap.getFixingAccrualFactor());
    final double beta =
        (1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());

    double df = curves.getDiscountFactor(capStandard.getCurrency(), capStandard.getPaymentTime());
    double strikePart =
        (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike())
            * presentValueStandard(
                forward,
                capStandard.getStrike(),
                capStandard.getFixingTime(),
                capStandard.isCap(),
                df,
                capStandard.getNotional(),
                capStandard.getPaymentYearFraction());
    double strikePartDelta =
        (1.0 + cap.getFixingAccrualFactor() * capStandard.getStrike())
            * presentValueDeltaStandard(
                forward,
                capStandard.getStrike(),
                capStandard.getFixingTime(),
                capStandard.isCap(),
                df,
                capStandard.getNotional(),
                capStandard.getPaymentYearFraction());

    final InArrearsIntegrant integrant = new InArrearsIntegrant(capStandard, curves);
    double integralPart;
    double upper = 0.0;
    try {
      if (cap.isCap()) {
        double atmVol = _smileFunction.getVolatility(forward);
        upper = forward * Math.exp(6.0 * atmVol * Math.sqrt(cap.getFixingTime()));
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, strike, upper);
        double reminder = integrant.evaluate(upper) * upper;
        double error = reminder / integralPart;

        int count = 0;
        while (Math.abs(error) > REL_ERROR && count < MAX_COUNT) {
          integralPart += INTEGRATOR.integrate(integrant, upper, 2.0 * upper);
          upper *= 2.0;
          // The increase of integralPart in the next loop is bounded by reminder
          reminder = integrant.evaluate(upper) * upper;
          error = reminder / integralPart;
          ++count;
          if (count == MAX_COUNT) {
            LOGGER.info(
                "Maximum iteration count, "
                    + MAX_COUNT
                    + ", has been reached. Relative error is greater than "
                    + REL_ERROR);
          }
        }
      } else {
        double strike = cap.getStrike();
        integralPart = INTEGRATOR.integrate(integrant, REL_TOL * strike, strike);
      }
    } catch (final Exception e) {
      throw new MathException(e);
    }
    integralPart *= 2.0 * cap.getFixingAccrualFactor();
    double pv = (strikePart + integralPart) / beta;

    double betaFwd =
        cap.getFixingAccrualFactor()
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());
    double betaDscStart =
        (1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            * cap.getFixingPeriodStartTime()
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());
    double betaDscEnd =
        -(1.0 + cap.getFixingAccrualFactor() * forward)
            * curves.getDiscountFactor(ccy, cap.getFixingPeriodEndTime())
            * cap.getFixingPeriodEndTime()
            / curves.getDiscountFactor(ccy, cap.getFixingPeriodStartTime());

    List<DoublesPair> listDiscounting = new ArrayList<>();
    double strikePartDsc = -capStandard.getPaymentTime() * strikePart;
    double integralPartDsc = -capStandard.getPaymentTime() * integralPart;
    listDiscounting.add(
        DoublesPair.of(capStandard.getPaymentTime(), (strikePartDsc + integralPartDsc) / beta));
    listDiscounting.add(DoublesPair.of(cap.getFixingPeriodStartTime(), -pv * betaDscStart / beta));
    listDiscounting.add(DoublesPair.of(cap.getFixingPeriodEndTime(), -pv * betaDscEnd / beta));
    Map<String, List<DoublesPair>> mapDsc = new HashMap<>();
    mapDsc.put(curves.getName(capStandard.getCurrency()), listDiscounting);

    final List<ForwardSensitivity> listForward = new ArrayList<>();
    double strikePartFwd = strikePartDelta;
    double integralPartFwd = 0.0;
    final InArrearsDeltaIntegrant integrantFwd = new InArrearsDeltaIntegrant(capStandard, curves);
    try {
      if (cap.isCap()) {
        double strike = cap.getStrike();
        integralPartFwd = INTEGRATOR.integrate(integrantFwd, strike, upper);
      } else {
        double strike = cap.getStrike();
        integralPartFwd = INTEGRATOR.integrate(integrantFwd, REL_TOL * strike, strike);
      }
    } catch (final Exception e) {
      throw new MathException(e);
    }
    integralPartFwd *= 2.0 * cap.getFixingAccrualFactor();
    listForward.add(
        new SimplyCompoundedForwardSensitivity(
            capStandard.getFixingPeriodStartTime(),
            capStandard.getFixingPeriodEndTime(),
            capStandard.getFixingAccrualFactor(),
            (strikePartFwd + integralPartFwd - pv * betaFwd) / beta));
    Map<String, List<ForwardSensitivity>> mapFwd = new HashMap<>();
    mapFwd.put(curves.getName(capStandard.getIndex()), listForward);

    return MultipleCurrencyMulticurveSensitivity.of(
        cap.getCurrency(), MulticurveSensitivity.of(mapDsc, mapFwd));
  }