@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 sensitivity to the strike of a CMS cap/floor by replication in SABR
   * framework with extrapolation on the right.
   *
   * @param cmsCapFloor The CMS cap/floor.
   * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function.
   * @return The present value sensitivity to strike.
   */
  @Override
  public double presentValueStrikeSensitivity(
      final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
    final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter();
    final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap();
    final double forward = underlyingSwap.accept(PRC, sabrData);
    final double discountFactor =
        sabrData
            .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName())
            .getDiscountFactor(cmsCapFloor.getPaymentTime());
    final double strike = cmsCapFloor.getStrike();
    final double maturity =
        underlyingSwap
                .getFixedLeg()
                .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1)
                .getPaymentTime()
            - cmsCapFloor.getSettlementTime();
    final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity);
    final double alpha = sabrParameter.getAlpha(expiryMaturity);
    final double beta = sabrParameter.getBeta(expiryMaturity);
    final double rho = sabrParameter.getRho(expiryMaturity);
    final double nu = sabrParameter.getNu(expiryMaturity);
    final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu);
    final CMSStrikeIntegrant integrant =
        new CMSStrikeIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
    final double factor = discountFactor * integrant.g(forward) / integrant.h(forward);
    final double absoluteTolerance = 1.0E-9;
    final double relativeTolerance = 1.0E-5;
    final RungeKuttaIntegrator1D integrator =
        new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
    final SABRExtrapolationRightFunction sabrExtrapolation =
        new SABRExtrapolationRightFunction(
            forward, sabrPoint, _cutOffStrike, cmsCapFloor.getFixingTime(), _mu);
    final EuropeanVanillaOption option =
        new EuropeanVanillaOption(strike, cmsCapFloor.getFixingTime(), cmsCapFloor.isCap());
    final double[] kpkpp = integrant.kpkpp(strike);
    double firstPart;
    double thirdPart;
    if (cmsCapFloor.isCap()) {
      firstPart = -kpkpp[0] * integrant.bs(strike);
      thirdPart = integrator.integrate(integrant, strike, strike + getIntegrationInterval());
    } else {
      firstPart = 3 * kpkpp[0] * integrant.bs(strike);
      thirdPart = integrator.integrate(integrant, 0.0, strike);
    }
    final double secondPart = integrant.k(strike) * sabrExtrapolation.priceDerivativeStrike(option);

    return cmsCapFloor.getNotional()
        * cmsCapFloor.getPaymentYearFraction()
        * factor
        * (firstPart + secondPart + thirdPart);
  }
  /** Check C2 smoothness of Benaim-Dodgson-Kainth extrapolation */
  @Test
  public void BDKSmoothnessGeneralTest() {
    double eps = 1.0e-5;

    double expiry = 1.5;
    double forward = 1.1;
    int nStrikes = 10;
    double[] strikes = new double[nStrikes];
    double[] impliedVols =
        new double[] {0.97, 0.92, 0.802, 0.745, 0.781, 0.812, 0.8334, 0.878, 0.899, 0.9252};
    for (int i = 0; i < nStrikes; ++i) {
      strikes[i] = forward * (0.85 + i * 0.05);
    }

    double muLow =
        strikes[0]
            * BlackFormulaRepository.dualDelta(forward, strikes[0], expiry, impliedVols[0], false)
            / BlackFormulaRepository.price(forward, strikes[0], expiry, impliedVols[0], false);
    double muHigh =
        -strikes[nStrikes - 1]
            * BlackFormulaRepository.dualDelta(
                forward, strikes[nStrikes - 1], expiry, impliedVols[nStrikes - 1], true)
            / BlackFormulaRepository.price(
                forward, strikes[nStrikes - 1], expiry, impliedVols[nStrikes - 1], true);

    SmileExtrapolationFunctionSABRProvider extrapBDK =
        new BenaimDodgsonKainthExtrapolationFunctionProvider(muLow, muHigh);
    SmileInterpolatorSABRWithExtrapolation interpBDK =
        new SmileInterpolatorSABRWithExtrapolation(
            new SABRBerestyckiVolatilityFunction(), extrapBDK);
    InterpolatedSmileFunction funcBDK =
        new InterpolatedSmileFunction(interpBDK, forward, strikes, expiry, impliedVols);

    List<SABRFormulaData> modelParams =
        (new SmileInterpolatorSABR())
            .getFittedModelParameters(forward, strikes, expiry, impliedVols);
    SABRExtrapolationLeftFunction sabrLeftExtrapolation =
        new SABRExtrapolationLeftFunction(
            forward,
            modelParams.get(0),
            strikes[0],
            expiry,
            muLow,
            new SABRHaganVolatilityFunction());
    SABRExtrapolationRightFunction sabrRightExtrapolation =
        new SABRExtrapolationRightFunction(
            forward,
            modelParams.get(nStrikes - 3),
            strikes[nStrikes - 1],
            expiry,
            muHigh,
            new SABRHaganVolatilityFunction());

    /*
     * left interpolation
     */
    {
      // Checking underlying extrapolation
      double boundaryValue =
          sabrLeftExtrapolation.price(new EuropeanVanillaOption(strikes[0], expiry, false));
      double CutoffUp = strikes[0] + eps;
      double CutoffDw = strikes[0] - eps;
      double optionPriceExt =
          sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffDw, expiry, false));
      double optionPriceInt =
          sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffUp, expiry, false));
      assertEquals(boundaryValue, optionPriceExt, eps);
      assertEquals(boundaryValue, optionPriceInt, eps);
      double optionPriceExtDw =
          sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffDw - eps, expiry, false));
      double firstExt = (1.5 * boundaryValue + 0.5 * optionPriceExtDw - 2.0 * optionPriceExt) / eps;
      double optionPriceIntUp =
          sabrLeftExtrapolation.price(new EuropeanVanillaOption(CutoffUp + eps, expiry, false));
      double firstInt = (2.0 * optionPriceInt - 0.5 * optionPriceIntUp - 1.5 * boundaryValue) / eps;
      assertEquals(firstInt, firstExt, eps);
      double secondExt = (boundaryValue + optionPriceExtDw - 2.0 * optionPriceExt) / eps / eps;
      double secondInt = (optionPriceIntUp + boundaryValue - 2.0 * optionPriceInt) / eps / eps;
      assertEquals(secondInt, secondExt, Math.abs(secondInt) * 1.0e-3);

      // Checking volatility function
      double volInt = funcBDK.getVolatility(CutoffUp);
      double volExt = funcBDK.getVolatility(CutoffDw);
      double volBoundary = funcBDK.getVolatility(strikes[0]);
      assertEquals(volBoundary, volInt, eps);
      assertEquals(volBoundary, volExt, eps);
      double volExtDw = funcBDK.getVolatility(CutoffDw - eps);
      double volFirstExt = (1.5 * volBoundary + 0.5 * volExtDw - 2.0 * volExt) / eps;
      double volIntUp = funcBDK.getVolatility(CutoffUp + eps);
      double volFirstInt = (2.0 * volInt - 0.5 * volIntUp - 1.5 * volBoundary) / eps;
      assertEquals(volFirstInt, volFirstExt, eps);
      double volSecondExt = (volBoundary + volExtDw - 2.0 * volExt) / eps / eps;
      double volSecondInt = (volIntUp + volBoundary - 2.0 * volInt) / eps / eps;
      assertEquals(volSecondInt, volSecondExt, Math.abs(volSecondInt) * 1.0e-3);
    }

    /*
     * right interpolation
     */
    {
      // Checking underlying extrapolation
      double boundaryValue =
          sabrRightExtrapolation.price(
              new EuropeanVanillaOption(strikes[nStrikes - 1], expiry, true));
      double CutoffUp = strikes[nStrikes - 1] + eps;
      double CutoffDw = strikes[nStrikes - 1] - eps;
      double optionPriceExt =
          sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffUp, expiry, true));
      double optionPriceInt =
          sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffDw, expiry, true));
      assertEquals(boundaryValue, optionPriceExt, eps);
      assertEquals(boundaryValue, optionPriceInt, eps);
      double optionPriceExtUp =
          sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffUp + eps, expiry, true));
      double firstExt = (2.0 * optionPriceExt - 0.5 * optionPriceExtUp - 1.5 * boundaryValue) / eps;
      double optionPriceIntDw =
          sabrRightExtrapolation.price(new EuropeanVanillaOption(CutoffDw - eps, expiry, true));
      double firstInt =
          (-2.0 * optionPriceInt + 1.5 * boundaryValue + 0.5 * optionPriceIntDw) / eps;
      assertEquals(firstInt, firstExt, eps);
      double secondExt = (optionPriceExtUp + boundaryValue - 2.0 * optionPriceExt) / eps / eps;
      double secondInt = (boundaryValue + optionPriceIntDw - 2.0 * optionPriceInt) / eps / eps;
      assertEquals(secondInt, secondExt, Math.abs(secondInt) * 1.0e-3);

      // Checking volatility function
      double volInt = funcBDK.getVolatility(CutoffDw);
      double volExt = funcBDK.getVolatility(CutoffUp);
      double volBoundary = funcBDK.getVolatility(strikes[nStrikes - 1]);
      assertEquals(volBoundary, volInt, eps);
      assertEquals(volBoundary, volExt, eps);
      double volExtUp = funcBDK.getVolatility(CutoffUp + eps);
      double volFirstExt = (2.0 * volExt - 0.5 * volExtUp - 1.5 * volBoundary) / eps;
      double volIntDw = funcBDK.getVolatility(CutoffDw - eps);
      double volFirstInt = (-2.0 * volInt + 1.5 * volBoundary + 0.5 * volIntDw) / eps;
      assertEquals(volFirstInt, volFirstExt, eps);
      double volSecondExt = (volBoundary + volExtUp - 2.0 * volExt) / eps / eps;
      double volSecondInt = (volIntDw + volBoundary - 2.0 * volInt) / eps / eps;
      assertEquals(volSecondInt, volSecondExt, Math.abs(volSecondInt) * 1.0e-3);
    }
  }
 /**
  * The Black-Scholes formula with numeraire 1 as function of the strike.
  *
  * @param strike The strike.
  * @return The Black-Scholes formula.
  */
 double bs(final double strike) {
   final EuropeanVanillaOption option =
       new EuropeanVanillaOption(strike, _timeToExpiry, _isCall);
   return _sabrExtrapolation.price(option);
 }
 /**
  * Computes the present value sensitivity to the SABR parameters of a CMS cap/floor by replication
  * in SABR framework with extrapolation on the right.
  *
  * @param cmsCapFloor The CMS cap/floor.
  * @param sabrData The SABR data bundle. The SABR function need to be the Hagan function.
  * @return The present value sensitivity to SABR parameters.
  */
 @Override
 public PresentValueSABRSensitivityDataBundle presentValueSABRSensitivity(
     final CapFloorCMS cmsCapFloor, final SABRInterestRateDataBundle sabrData) {
   final SABRInterestRateParameters sabrParameter = sabrData.getSABRParameter();
   final SwapFixedCoupon<? extends Payment> underlyingSwap = cmsCapFloor.getUnderlyingSwap();
   final double forward = underlyingSwap.accept(PRC, sabrData);
   final double discountFactorTp =
       sabrData
           .getCurve(underlyingSwap.getFixedLeg().getNthPayment(0).getFundingCurveName())
           .getDiscountFactor(cmsCapFloor.getPaymentTime());
   final double strike = cmsCapFloor.getStrike();
   final double maturity =
       underlyingSwap
               .getFixedLeg()
               .getNthPayment(underlyingSwap.getFixedLeg().getNumberOfPayments() - 1)
               .getPaymentTime()
           - cmsCapFloor.getSettlementTime();
   final DoublesPair expiryMaturity = new DoublesPair(cmsCapFloor.getFixingTime(), maturity);
   final double alpha = sabrParameter.getAlpha(expiryMaturity);
   final double beta = sabrParameter.getBeta(expiryMaturity);
   final double rho = sabrParameter.getRho(expiryMaturity);
   final double nu = sabrParameter.getNu(expiryMaturity);
   final SABRFormulaData sabrPoint = new SABRFormulaData(alpha, beta, rho, nu);
   final CMSVegaIntegrant integrantVega =
       new CMSVegaIntegrant(cmsCapFloor, sabrPoint, forward, _cutOffStrike, _mu);
   final double factor = discountFactorTp / integrantVega.h(forward) * integrantVega.g(forward);
   final SABRExtrapolationRightFunction sabrExtrapolation =
       new SABRExtrapolationRightFunction(
           forward, sabrPoint, _cutOffStrike, cmsCapFloor.getFixingTime(), _mu);
   final EuropeanVanillaOption option =
       new EuropeanVanillaOption(strike, cmsCapFloor.getFixingTime(), cmsCapFloor.isCap());
   final double factor2 = factor * integrantVega.k(strike);
   final double[] strikePartPrice = new double[4];
   sabrExtrapolation.priceAdjointSABR(option, strikePartPrice);
   for (int loopvega = 0; loopvega < 4; loopvega++) {
     strikePartPrice[loopvega] *= factor2;
   }
   final double absoluteTolerance =
       1.0 / (factor * Math.abs(cmsCapFloor.getNotional()) * cmsCapFloor.getPaymentYearFraction());
   final double relativeTolerance = 1E-3;
   final RungeKuttaIntegrator1D integrator =
       new RungeKuttaIntegrator1D(absoluteTolerance, relativeTolerance, getNbIteration());
   final double[] integralPart = new double[4];
   final double[] totalSensi = new double[4];
   for (int loopparameter = 0; loopparameter < 4; loopparameter++) {
     integrantVega.setParameterIndex(loopparameter);
     try {
       if (cmsCapFloor.isCap()) {
         integralPart[loopparameter] =
             discountFactorTp
                 * integrator.integrate(integrantVega, strike, strike + getIntegrationInterval());
       } else {
         integralPart[loopparameter] =
             discountFactorTp * integrator.integrate(integrantVega, 0.0, strike);
       }
     } catch (final Exception e) {
       throw new RuntimeException(e);
     }
     totalSensi[loopparameter] =
         (strikePartPrice[loopparameter] + integralPart[loopparameter])
             * cmsCapFloor.getNotional()
             * cmsCapFloor.getPaymentYearFraction();
   }
   final PresentValueSABRSensitivityDataBundle sensi = new PresentValueSABRSensitivityDataBundle();
   sensi.addAlpha(expiryMaturity, totalSensi[0]);
   sensi.addBeta(expiryMaturity, totalSensi[1]);
   sensi.addRho(expiryMaturity, totalSensi[2]);
   sensi.addNu(expiryMaturity, totalSensi[3]);
   return sensi;
 }